├── av ├── Stream.hpp ├── Rational.hpp ├── OptSetter.hpp ├── Scale.hpp ├── BSF.hpp ├── Frame.hpp ├── Packet.hpp ├── Resample.hpp ├── Decoder.hpp ├── StreamReader.hpp ├── InputFormat.hpp ├── OutputFormat.hpp ├── VideoCapture.hpp ├── StreamWriter.hpp ├── common.hpp └── Encoder.hpp ├── CONTRIBUTING.md ├── CMakeLists.txt ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── .clang-format ├── examples ├── transcode.cpp └── CMakeLists.txt ├── README.md └── CODE_OF_CONDUCT.md /av/Stream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace av 6 | { 7 | 8 | #if 0 9 | class StreamBase 10 | { 11 | public: 12 | explicit StreamBase(AVStream* stream) 13 | : stream_(stream) 14 | {} 15 | 16 | private: 17 | AVStream* stream_; 18 | }; 19 | #endif 20 | 21 | }// namespace av 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | To contribute you must follow the next rules: 2 | 1. Write modern C++ code without any external dependencies 3 | 2. Don't use exceptions, handle or forward each error inside the library. 4 | 3. Code must be able to compile with all popular compilers like gcc,clang and msvc 5 | 4. If you have some code to contribute - create a pull request 6 | 5. I guess that's it. Ask in DM if you have questions 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(libav_cpp) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") 7 | 8 | if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 10 | endif () 11 | 12 | 13 | file(GLOB AV_FILES av/*.hpp) 14 | 15 | if(LIBAV_CPP_ENABLE_EXAMPLES) 16 | add_subdirectory(examples) 17 | endif() 18 | 19 | 20 | add_library(av-cpp INTERFACE ${AV_FILES}) 21 | target_include_directories(av-cpp INTERFACE $) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /av/Rational.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace av 6 | { 7 | class Rational 8 | { 9 | public: 10 | explicit Rational(AVRational&& val) noexcept 11 | : val_(val) 12 | { 13 | } 14 | 15 | explicit Rational(const AVRational& val) noexcept 16 | : val_(val) 17 | { 18 | } 19 | 20 | explicit Rational(double val, int max = 1000000) noexcept 21 | : val_(av_d2q(val, max)) 22 | { 23 | } 24 | 25 | const AVRational& operator*() const noexcept 26 | { 27 | return val_; 28 | } 29 | AVRational& operator*() noexcept 30 | { 31 | return val_; 32 | } 33 | 34 | [[nodiscard]] double toDouble() const noexcept 35 | { 36 | return av_q2d(val_); 37 | } 38 | 39 | Rational inv() const noexcept 40 | { 41 | return Rational{av_inv_q(val_)}; 42 | } 43 | 44 | operator AVRational() const { return val_; } 45 | 46 | static Rational fromFPS(double fps) noexcept 47 | { 48 | return Rational{fps}.inv(); 49 | } 50 | 51 | private: 52 | AVRational val_; 53 | }; 54 | }// namespace av -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Gregory Istratov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /av/OptSetter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace av 8 | { 9 | 10 | using OptValue = std::variant; 11 | using OptValueMap = std::unordered_map; 12 | 13 | class OptSetter 14 | { 15 | public: 16 | static void set(void* obj, const OptValueMap& opts) 17 | { 18 | for (const auto& [k, var] : opts) 19 | { 20 | std::string_view sv = k; 21 | std::visit([obj, sv](auto&& arg) { visit(obj, sv, arg); }, var); 22 | } 23 | } 24 | 25 | private: 26 | static void visit(void* obj, std::string_view name, const std::string& s) noexcept 27 | { 28 | av_opt_set(obj, name.data(), s.data(), 0); 29 | }; 30 | 31 | static void visit(void* obj, std::string_view name, int i) noexcept 32 | { 33 | av_opt_set_int(obj, name.data(), i, 0); 34 | }; 35 | 36 | static void visit(void* obj, std::string_view name, double d) noexcept 37 | { 38 | av_opt_set_double(obj, name.data(), d, 0); 39 | }; 40 | 41 | static void visit(void* obj, std::string_view name, AVRational q) noexcept 42 | { 43 | av_opt_set_q(obj, name.data(), q, 0); 44 | }; 45 | }; 46 | 47 | }// namespace av 48 | -------------------------------------------------------------------------------- /av/Scale.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace av 7 | { 8 | 9 | class Scale : NoCopyable 10 | { 11 | explicit Scale(SwsContext* sws) noexcept 12 | : sws_(sws) 13 | {} 14 | 15 | public: 16 | static Expected> create(int inputWidth, int inputHeight, AVPixelFormat inputPixFmt, int outputWidth, int outputHeight, AVPixelFormat outputPixFmt) noexcept 17 | { 18 | auto sws = sws_getContext(inputWidth, inputHeight, inputPixFmt, 19 | outputWidth, outputHeight, outputPixFmt, 20 | SWS_BICUBIC, nullptr, nullptr, nullptr); 21 | 22 | if (!sws) 23 | RETURN_AV_ERROR("Failed to create sws context"); 24 | 25 | return Ptr{new Scale{sws}}; 26 | } 27 | 28 | ~Scale() 29 | { 30 | if (sws_) 31 | sws_freeContext(sws_); 32 | } 33 | 34 | void scale(const uint8_t* const srcSlice[], 35 | const int srcStride[], int srcSliceY, int srcSliceH, 36 | uint8_t* const dst[], const int dstStride[]) 37 | { 38 | sws_scale(sws_, srcSlice, srcStride, srcSliceY, srcSliceH, dst, dstStride); 39 | } 40 | 41 | void scale(const Frame& src, Frame& dst) 42 | { 43 | sws_scale(sws_, src.native()->data, src.native()->linesize, 0, src.native()->height, dst.native()->data, dst.native()->linesize); 44 | } 45 | 46 | private: 47 | SwsContext* sws_{nullptr}; 48 | }; 49 | 50 | }// namespace av 51 | -------------------------------------------------------------------------------- /av/BSF.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace av 8 | { 9 | 10 | class BSF : NoCopyable 11 | { 12 | BSF(AVBSFContext* bsfc) 13 | : bsfc_(bsfc) 14 | {} 15 | 16 | public: 17 | static Expected> create(const char* filters, AVCodecParameters* par) 18 | { 19 | AVBSFContext* bsfc = nullptr; 20 | const char* bsfName = filters; 21 | int err = av_bsf_list_parse_str(bsfName, &bsfc); 22 | if (err < 0) 23 | RETURN_AV_ERROR("Error parsing {} bitstream filter: {}", bsfName, avErrorStr(err)); 24 | 25 | err = avcodec_parameters_copy(bsfc->par_in, par); 26 | if (err < 0) 27 | { 28 | av_bsf_free(&bsfc); 29 | RETURN_AV_ERROR("Error bsf '{}' copying codec parameters: {}", bsfName, avErrorStr(err)); 30 | } 31 | 32 | err = av_bsf_init(bsfc); 33 | if (err < 0) 34 | { 35 | av_bsf_free(&bsfc); 36 | RETURN_AV_ERROR("Error initializing {} bitstream filter: {}", bsfName, avErrorStr(err)); 37 | } 38 | 39 | return Ptr{new BSF{bsfc}}; 40 | } 41 | 42 | ~BSF() 43 | { 44 | if (bsfc_) 45 | av_bsf_free(&bsfc_); 46 | } 47 | 48 | std::tuple apply(Packet& inPkt, std::vector& outPkts) noexcept 49 | { 50 | int err = av_bsf_send_packet(bsfc_, *inPkt); 51 | if (err < 0) 52 | { 53 | LOG_AV_ERROR("BSF packet send error: {}", avErrorStr(err)); 54 | return {Result::kFail, 0}; 55 | } 56 | 57 | for (auto& pkt : outPkts) 58 | pkt.dataUnref(); 59 | 60 | for (int i = 0;; ++i) 61 | { 62 | if (i >= (int)outPkts.size()) 63 | { 64 | outPkts.emplace_back(); 65 | } 66 | 67 | err = av_bsf_receive_packet(bsfc_, *outPkts[i]); 68 | if (err == AVERROR(EAGAIN)) 69 | return {Result::kSuccess, i}; 70 | 71 | if (err == AVERROR(EOF)) 72 | return {Result::kEOF, i}; 73 | 74 | if (err < 0) 75 | { 76 | LOG_AV_ERROR("BSF packet receive error: {}", avErrorStr(err)); 77 | return {Result::kFail, i}; 78 | } 79 | } 80 | } 81 | 82 | private: 83 | AVBSFContext* bsfc_{nullptr}; 84 | }; 85 | 86 | }// namespace av 87 | -------------------------------------------------------------------------------- /av/Frame.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace av 6 | { 7 | class Frame 8 | { 9 | explicit Frame(AVFrame* frame) noexcept 10 | : frame_(frame) 11 | {} 12 | 13 | public: 14 | Frame() noexcept 15 | : frame_(av_frame_alloc()) 16 | { 17 | } 18 | 19 | static Expected> create(int width, int height, AVPixelFormat pixFmt, int align = 0) noexcept 20 | { 21 | auto frame = av::makePtr(); 22 | if(!frame) 23 | RETURN_AV_ERROR("Failed to alloc frame"); 24 | 25 | auto f = frame->native(); 26 | f->width = width; 27 | f->height = height; 28 | f->format = pixFmt; 29 | 30 | auto err = av_frame_get_buffer(f, align); 31 | if(err < 0) 32 | RETURN_AV_ERROR("Failed to get buffer: {}", avErrorStr(err)); 33 | 34 | return frame; 35 | } 36 | 37 | ~Frame() 38 | { 39 | if (frame_) 40 | av_frame_free(&frame_); 41 | } 42 | 43 | auto* operator*() noexcept 44 | { 45 | return frame_; 46 | } 47 | const auto* operator*() const noexcept 48 | { 49 | return frame_; 50 | } 51 | 52 | AVFrame* native() noexcept 53 | { 54 | return frame_; 55 | } 56 | const AVFrame* native() const noexcept 57 | { 58 | return frame_; 59 | } 60 | 61 | Frame(Frame&& other) noexcept 62 | { 63 | frame_ = other.frame_; 64 | other.frame_ = nullptr; 65 | } 66 | 67 | Frame(const Frame& other) noexcept 68 | { 69 | frame_ = av_frame_alloc(); 70 | av_frame_ref(frame_, *other); 71 | } 72 | 73 | Frame& operator=(Frame&& other) noexcept 74 | { 75 | if (&other == this) 76 | return *this; 77 | 78 | av_frame_free(&frame_); 79 | frame_ = other.frame_; 80 | other.frame_ = nullptr; 81 | 82 | return *this; 83 | } 84 | 85 | Frame& operator=(const Frame& other) noexcept 86 | { 87 | if (&other == this) 88 | return *this; 89 | 90 | av_frame_unref(frame_); 91 | av_frame_ref(frame_, *other); 92 | 93 | return *this; 94 | } 95 | 96 | AVMediaType type() const noexcept 97 | { 98 | return type_; 99 | } 100 | void type(AVMediaType type) noexcept 101 | { 102 | type_ = type; 103 | } 104 | 105 | private: 106 | AVFrame* frame_{nullptr}; 107 | AVMediaType type_{AVMEDIA_TYPE_UNKNOWN}; 108 | }; 109 | 110 | }// namespace av 111 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Generated from CLion C/C++ Code Style settings 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: Consecutive 6 | AlignOperands: false 7 | AllowAllArgumentsOnNextLine: false 8 | AllowAllConstructorInitializersOnNextLine: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: Always 11 | AllowShortCaseLabelsOnASingleLine: true 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Never 14 | AllowShortLambdasOnASingleLine: All 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakTemplateDeclarations: MultiLine 18 | BreakBeforeBraces: Custom 19 | BraceWrapping: 20 | AfterCaseLabel: false 21 | AfterClass: true 22 | AfterStruct: true 23 | AfterExternBlock: true 24 | AfterObjCDeclaration: true 25 | AfterControlStatement: MultiLine 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterUnion: true 30 | BeforeCatch: true 31 | BeforeElse: true 32 | IndentBraces: false 33 | SplitEmptyFunction: false 34 | SplitEmptyRecord: true 35 | BreakBeforeBinaryOperators: NonAssignment 36 | BreakBeforeTernaryOperators: false 37 | BreakConstructorInitializers: BeforeColon 38 | BreakInheritanceList: BeforeColon 39 | ColumnLimit: 0 40 | CompactNamespaces: false 41 | ContinuationIndentWidth: 4 42 | IndentCaseLabels: true 43 | IndentPPDirectives: None 44 | IndentWidth: 4 45 | KeepEmptyLinesAtTheStartOfBlocks: true 46 | MaxEmptyLinesToKeep: 1 47 | NamespaceIndentation: None 48 | ObjCSpaceAfterProperty: false 49 | ObjCSpaceBeforeProtocolList: true 50 | PointerAlignment: Left 51 | ReflowComments: false 52 | SpaceAfterCStyleCast: true 53 | SpaceAfterLogicalNot: false 54 | SpaceAfterTemplateKeyword: false 55 | SpaceBeforeAssignmentOperators: true 56 | SpaceBeforeCpp11BracedList: false 57 | SpaceBeforeCtorInitializerColon: true 58 | SpaceBeforeInheritanceColon: true 59 | SpaceBeforeParens: ControlStatements 60 | SpaceBeforeRangeBasedForLoopColon: true 61 | SpaceInEmptyParentheses: false 62 | SpacesBeforeTrailingComments: 0 63 | SpacesInAngles: false 64 | SpacesInCStyleCastParentheses: false 65 | SpacesInContainerLiterals: false 66 | SpacesInParentheses: false 67 | SpacesInSquareBrackets: false 68 | TabWidth: 4 69 | UseTab: ForIndentation 70 | -------------------------------------------------------------------------------- /av/Packet.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace av 6 | { 7 | class Packet 8 | { 9 | Packet(AVPacket* packet) noexcept 10 | : packet_(packet) 11 | {} 12 | 13 | public: 14 | Packet() noexcept 15 | : packet_(av_packet_alloc()) 16 | { 17 | } 18 | 19 | static Expected> create() noexcept 20 | { 21 | auto packet = av_packet_alloc(); 22 | if (!packet) 23 | RETURN_AV_ERROR("Failed to alloc packet"); 24 | 25 | return Ptr{new Packet{packet}}; 26 | } 27 | 28 | static Expected> create(const std::vector& data) noexcept 29 | { 30 | auto packet = av_packet_alloc(); 31 | if (!packet) 32 | RETURN_AV_ERROR("Failed to alloc packet"); 33 | 34 | auto buffer = (uint8_t*) av_malloc(data.size()); 35 | if (!buffer) 36 | { 37 | av_packet_free(&packet); 38 | RETURN_AV_ERROR("Failed to allocate buffer"); 39 | } 40 | 41 | std::copy(data.begin(), data.end(), buffer); 42 | 43 | auto err = av_packet_from_data(packet, buffer, (int) data.size()); 44 | if (err < 0) 45 | { 46 | av_free(buffer); 47 | av_packet_free(&packet); 48 | RETURN_AV_ERROR("Failed to make packet from data: {}", avErrorStr(err)); 49 | } 50 | 51 | return Ptr{new Packet{packet}}; 52 | } 53 | 54 | ~Packet() 55 | { 56 | if (packet_) 57 | av_packet_free(&packet_); 58 | } 59 | 60 | Packet(Packet&& other) noexcept 61 | { 62 | packet_ = other.packet_; 63 | other.packet_ = nullptr; 64 | } 65 | 66 | Packet(const Packet& other) noexcept 67 | { 68 | packet_ = av_packet_alloc(); 69 | av_packet_ref(packet_, *other); 70 | } 71 | 72 | Packet& operator=(Packet&& other) noexcept 73 | { 74 | if (&other == this) 75 | return *this; 76 | 77 | av_packet_free(&packet_); 78 | packet_ = other.packet_; 79 | other.packet_ = nullptr; 80 | 81 | return *this; 82 | } 83 | 84 | Packet& operator=(const Packet& other) noexcept 85 | { 86 | if (&other == this) 87 | return *this; 88 | 89 | av_packet_unref(packet_); 90 | av_packet_ref(packet_, *other); 91 | 92 | return *this; 93 | } 94 | 95 | AVPacket* operator*() noexcept 96 | { 97 | return packet_; 98 | } 99 | AVPacket* operator*() const noexcept 100 | { 101 | return packet_; 102 | } 103 | 104 | AVPacket* native() noexcept 105 | { 106 | return packet_; 107 | } 108 | AVPacket* native() const noexcept 109 | { 110 | return packet_; 111 | } 112 | 113 | void dataUnref() noexcept 114 | { 115 | av_packet_unref(packet_); 116 | } 117 | 118 | private: 119 | AVPacket* packet_{nullptr}; 120 | }; 121 | 122 | }// namespace av 123 | -------------------------------------------------------------------------------- /av/Resample.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace av 7 | { 8 | 9 | class Resample : NoCopyable 10 | { 11 | explicit Resample(SwrContext* swr) noexcept 12 | : swr_(swr) 13 | {} 14 | 15 | public: 16 | static Expected> create(int inChannels, AVSampleFormat inSampleFmt, int inSampleRate, 17 | int outChannels, AVSampleFormat outSampleFmt, int outSampleRate) noexcept 18 | { 19 | /* 20 | * Create a resampler context for the conversion. 21 | * Set the conversion parameters. 22 | * Default channel layouts based on the number of channels 23 | * are assumed for simplicity (they are sometimes not detected 24 | * properly by the demuxer and/or decoder). 25 | */ 26 | 27 | LOG_AV_DEBUG("Creating swr context: input - channel_layout: {} sample_rate: {} format: {} output - channel_layout: {} sample_rate: {} format: {}", 28 | av_get_default_channel_layout(inChannels), inSampleRate, av_get_sample_fmt_name(inSampleFmt), 29 | av_get_default_channel_layout(outChannels), outSampleRate, av_get_sample_fmt_name(outSampleFmt)); 30 | 31 | auto swr = swr_alloc_set_opts(nullptr, 32 | av_get_default_channel_layout(outChannels), 33 | outSampleFmt, 34 | outSampleRate, 35 | av_get_default_channel_layout(inChannels), 36 | inSampleFmt, 37 | inSampleRate, 38 | 0, nullptr); 39 | 40 | if (!swr) 41 | RETURN_AV_ERROR("Failed to create swr context"); 42 | 43 | /* Open the resampler with the specified parameters. */ 44 | int err = 0; 45 | if ((err = swr_init(swr)) < 0) 46 | { 47 | swr_free(&swr); 48 | RETURN_AV_ERROR("Could not open resample context: {}", avErrorStr(err)); 49 | } 50 | 51 | return Ptr{new Resample{swr}}; 52 | } 53 | 54 | ~Resample() 55 | { 56 | if (swr_) 57 | swr_free(&swr_); 58 | } 59 | 60 | Expected convert(const Frame& input, Frame& output) noexcept 61 | { 62 | //LOG_AV_DEBUG("input - channel_layout: {} sample_rate: {} format: {}", input->channel_layout, input->sample_rate, av_get_sample_fmt_name((AVSampleFormat)input->format)); 63 | //LOG_AV_DEBUG("output - channel_layout: {} sample_rate: {} format: {}", output->channel_layout, output->sample_rate, av_get_sample_fmt_name((AVSampleFormat)output->format)); 64 | /* Convert the samples using the resampler. */ 65 | auto err = swr_convert_frame(swr_, *output, *input); 66 | if (err < 0) 67 | RETURN_AV_ERROR("Could not convert input samples: {}", avErrorStr(err)); 68 | 69 | return {}; 70 | } 71 | 72 | private: 73 | SwrContext* swr_{nullptr}; 74 | }; 75 | 76 | }// namespace av 77 | -------------------------------------------------------------------------------- /av/Decoder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace av 7 | { 8 | 9 | class Decoder : NoCopyable 10 | { 11 | explicit Decoder(AVCodecContext* codecContext) noexcept 12 | : codecContext_(codecContext) 13 | {} 14 | 15 | public: 16 | static Expected> create(AVCodec* codec, AVStream* stream, AVRational framerate = {}) 17 | { 18 | if (!av_codec_is_decoder(codec)) 19 | RETURN_AV_ERROR("{} is not a decoder", codec->name); 20 | 21 | auto codecContext = avcodec_alloc_context3(codec); 22 | if (!codecContext) 23 | RETURN_AV_ERROR("Could not alloc an encoding context"); 24 | 25 | auto ret = avcodec_parameters_to_context(codecContext, stream->codecpar); 26 | if (ret < 0) 27 | { 28 | avcodec_free_context(&codecContext); 29 | RETURN_AV_ERROR("Failed to copy parameters to context: {}", avErrorStr(ret)); 30 | } 31 | 32 | if (codecContext->codec_type == AVMEDIA_TYPE_VIDEO) 33 | { 34 | if (!framerate.num && !framerate.den) 35 | { 36 | avcodec_free_context(&codecContext); 37 | RETURN_AV_ERROR("Framerate is not set"); 38 | } 39 | 40 | codecContext->framerate = framerate; 41 | } 42 | 43 | AVDictionary* opts = nullptr; 44 | ret = avcodec_open2(codecContext, codecContext->codec, &opts); 45 | if (ret < 0) 46 | { 47 | avcodec_free_context(&codecContext); 48 | RETURN_AV_ERROR("Could not open video codec: {}", avErrorStr(ret)); 49 | } 50 | 51 | return Ptr{new Decoder{codecContext}}; 52 | } 53 | 54 | ~Decoder() 55 | { 56 | if (codecContext_) 57 | { 58 | avcodec_close(codecContext_); 59 | avcodec_free_context(&codecContext_); 60 | } 61 | } 62 | 63 | auto* operator*() noexcept 64 | { 65 | return codecContext_; 66 | } 67 | const auto* operator*() const noexcept 68 | { 69 | return codecContext_; 70 | } 71 | 72 | auto* native() noexcept 73 | { 74 | return codecContext_; 75 | } 76 | const auto* native() const noexcept 77 | { 78 | return codecContext_; 79 | } 80 | 81 | Expected decode(Packet& packet, Frame& frame) noexcept 82 | { 83 | int err = avcodec_send_packet(codecContext_, *packet); 84 | 85 | if (err == AVERROR(EAGAIN)) 86 | return Result::kEAGAIN; 87 | 88 | if (err == AVERROR_EOF) 89 | return Result::kEOF; 90 | 91 | if (err < 0) 92 | RETURN_AV_ERROR("Decoder error: {}", avErrorStr(err)); 93 | 94 | err = avcodec_receive_frame(codecContext_, *frame); 95 | 96 | if (err == AVERROR(EAGAIN)) 97 | return Result::kEAGAIN; 98 | 99 | if (err == AVERROR_EOF) 100 | return Result::kEOF; 101 | 102 | if (err < 0) 103 | RETURN_AV_ERROR("Decoder error: {}", avErrorStr(err)); 104 | 105 | return Result::kSuccess; 106 | } 107 | 108 | private: 109 | AVCodecContext* codecContext_{nullptr}; 110 | }; 111 | 112 | }// namespace av 113 | -------------------------------------------------------------------------------- /examples/transcode.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | // Since it a header only library there is no specific logging backend, so we must implement our own writeLog function 7 | // and place it in av namespace 8 | namespace av 9 | { 10 | void writeLog(LogLevel level, internal::SourceLocation&& loc, std::string msg) noexcept 11 | { 12 | std::cerr << loc.toString() << ": " << msg << std::endl; 13 | } 14 | }// namespace av 15 | 16 | template 17 | void println(std::string_view fmt, Args&&... args) noexcept 18 | { 19 | std::cout << av::internal::format(fmt, std::forward(args)...) << std::endl; 20 | } 21 | 22 | template 23 | Return assertExpected(av::Expected&& expected) noexcept 24 | { 25 | if (!expected) 26 | { 27 | std::cerr << " === Expected failure == \n" 28 | << expected.errorString() << std::endl; 29 | exit(EXIT_FAILURE); 30 | } 31 | 32 | if constexpr (std::is_same_v) 33 | return; 34 | else 35 | return expected.value(); 36 | } 37 | 38 | int main(int argc, const char* argv[]) 39 | { 40 | if (argc < 3) 41 | { 42 | std::cout << "Usage: transcode " << std::endl; 43 | return 0; 44 | } 45 | 46 | std::string_view input(argv[1]); 47 | std::string_view output(argv[2]); 48 | 49 | av_log_set_level(AV_LOG_VERBOSE); 50 | 51 | auto reader = assertExpected(av::StreamReader::create(input, true)); 52 | 53 | av::Frame frame; 54 | 55 | auto writer = assertExpected(av::StreamWriter::create(output)); 56 | 57 | { 58 | auto width = reader->frameWidth(); 59 | auto height = reader->frameHeight(); 60 | auto framerate = reader->framerate(); 61 | framerate = av_inv_q(framerate); 62 | auto pixFmt = reader->pixFmt(); 63 | 64 | av::OptValueMap codecOpts = {{"preset", "fast"}, {"crf", 29}}; 65 | 66 | assertExpected(writer->addVideoStream(AV_CODEC_ID_H264, width, height, pixFmt, framerate, std::move(codecOpts))); 67 | } 68 | 69 | { 70 | auto channels = reader->channels(); 71 | auto rate = reader->sampleRate(); 72 | auto format = reader->sampleFormat(); 73 | auto bitRate = 128 * 1024; 74 | 75 | assertExpected(writer->addAudioStream(AV_CODEC_ID_AAC, channels, format, rate, channels, rate, bitRate)); 76 | } 77 | 78 | assertExpected(writer->open()); 79 | 80 | int video_n = 0; 81 | int audio_n = 0; 82 | for (;;) 83 | { 84 | if (!assertExpected(reader->readFrame(frame))) 85 | break; 86 | 87 | if (frame.type() == AVMEDIA_TYPE_VIDEO) 88 | { 89 | if (video_n % 100 == 0) 90 | println("Writing video {} frame", video_n); 91 | 92 | assertExpected(writer->write(frame, 0)); 93 | 94 | video_n++; 95 | } 96 | else if (frame.type() == AVMEDIA_TYPE_AUDIO) 97 | { 98 | if (audio_n % 100 == 0) 99 | println("Writing audio {} frame", audio_n); 100 | 101 | assertExpected(writer->write(frame, 1)); 102 | 103 | audio_n++; 104 | } 105 | } 106 | 107 | println("Encoded {} video {} audio frames", video_n, audio_n); 108 | 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /av/StreamReader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace av 10 | { 11 | 12 | class StreamReader : NoCopyable 13 | { 14 | StreamReader() = default; 15 | 16 | public: 17 | static Expected> create(std::string_view url, bool enableAudio = false) noexcept 18 | { 19 | Ptr sr{new StreamReader}; 20 | 21 | auto iformExp = SimpleInputFormat::create(url, enableAudio); 22 | if (!iformExp) 23 | FORWARD_AV_ERROR(iformExp); 24 | 25 | sr->ic_ = iformExp.value(); 26 | 27 | sr->vStream_ = sr->ic_->videoStream(); 28 | 29 | if (enableAudio) 30 | sr->aStream_ = sr->ic_->audioStream(); 31 | 32 | return sr; 33 | } 34 | 35 | ~StreamReader() 36 | { 37 | } 38 | 39 | [[nodiscard]] Expected readFrame(Frame& frame) noexcept 40 | { 41 | Packet packet; 42 | 43 | for (;;) 44 | { 45 | packet.dataUnref(); 46 | auto successExp = ic_->readFrame(packet); 47 | if (!successExp) 48 | FORWARD_AV_ERROR(successExp); 49 | 50 | if (!successExp.value()) 51 | return false; 52 | 53 | Ptr dec; 54 | if (packet.native()->stream_index == std::get<0>(vStream_)->index) 55 | { 56 | dec = std::get<1>(vStream_); 57 | 58 | auto resExp = dec->decode(packet, frame); 59 | 60 | if (!resExp) 61 | FORWARD_AV_ERROR(resExp); 62 | 63 | if (resExp.value() != Result::kSuccess) 64 | continue; 65 | 66 | frame.type(AVMEDIA_TYPE_VIDEO); 67 | 68 | return true; 69 | } 70 | else if (std::get<0>(aStream_) && packet.native()->stream_index == std::get<0>(aStream_)->index) 71 | { 72 | dec = std::get<1>(aStream_); 73 | 74 | auto resExp = dec->decode(packet, frame); 75 | 76 | if (!resExp) 77 | FORWARD_AV_ERROR(resExp); 78 | 79 | if (resExp.value() != Result::kSuccess) 80 | continue; 81 | 82 | frame.type(AVMEDIA_TYPE_AUDIO); 83 | 84 | return true; 85 | } 86 | else 87 | { 88 | //LOG_ERROR("Unknown stream index {}", packet->stream_index); 89 | continue; 90 | } 91 | } 92 | } 93 | 94 | auto pixFmt() const noexcept 95 | { 96 | return std::get<1>(vStream_)->native()->pix_fmt; 97 | } 98 | 99 | auto frameWidth() const noexcept 100 | { 101 | return std::get<1>(vStream_)->native()->width; 102 | } 103 | 104 | auto frameHeight() const noexcept 105 | { 106 | return std::get<1>(vStream_)->native()->height; 107 | } 108 | 109 | auto framerate() const noexcept 110 | { 111 | return std::get<1>(vStream_)->native()->framerate; 112 | } 113 | 114 | auto channels() const noexcept 115 | { 116 | return std::get<1>(aStream_)->native()->channels; 117 | } 118 | 119 | auto sampleRate() const noexcept 120 | { 121 | return std::get<1>(aStream_)->native()->sample_rate; 122 | } 123 | 124 | auto sampleFormat() const noexcept 125 | { 126 | return std::get<1>(aStream_)->native()->sample_fmt; 127 | } 128 | 129 | private: 130 | Ptr ic_; 131 | std::tuple> vStream_; 132 | std::tuple> aStream_; 133 | }; 134 | 135 | }// namespace av 136 | -------------------------------------------------------------------------------- /av/InputFormat.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace av 8 | { 9 | 10 | class SimpleInputFormat : NoCopyable 11 | { 12 | explicit SimpleInputFormat(AVFormatContext* ic) noexcept 13 | : ic_(ic) 14 | {} 15 | 16 | public: 17 | static Expected> create(std::string_view url, bool enableAudio = false) noexcept 18 | { 19 | AVFormatContext* ic = nullptr; 20 | auto err = avformat_open_input(&ic, url.data(), nullptr, nullptr); 21 | if (err < 0) 22 | RETURN_AV_ERROR("Cannot open input '{}': {}", url, avErrorStr(err)); 23 | 24 | err = avformat_find_stream_info(ic, nullptr); 25 | if (err < 0) 26 | { 27 | avformat_close_input(&ic); 28 | RETURN_AV_ERROR("Cannot find stream info: {}", avErrorStr(err)); 29 | } 30 | 31 | Ptr res{new SimpleInputFormat{ic}}; 32 | res->url_ = url; 33 | 34 | { 35 | auto ret = res->findBestStream(AVMEDIA_TYPE_VIDEO); 36 | if (!ret) 37 | FORWARD_AV_ERROR(ret); 38 | } 39 | 40 | if (enableAudio) 41 | { 42 | auto ret = res->findBestStream(AVMEDIA_TYPE_AUDIO); 43 | if (!ret) 44 | FORWARD_AV_ERROR(ret); 45 | } 46 | 47 | av_dump_format(ic, 0, nullptr, 0); 48 | 49 | return res; 50 | } 51 | 52 | ~SimpleInputFormat() 53 | { 54 | avformat_close_input(&ic_); 55 | } 56 | 57 | Expected readFrame(Packet& packet) noexcept 58 | { 59 | int err = 0; 60 | for (;;) 61 | { 62 | err = av_read_frame(ic_, *packet); 63 | 64 | if (err == AVERROR(EAGAIN)) 65 | continue; 66 | 67 | if (err == AVERROR_EOF) 68 | { 69 | // flush cached frames from video decoder 70 | packet.native()->data = nullptr; 71 | packet.native()->size = 0; 72 | 73 | return false; 74 | } 75 | 76 | if (err < 0) 77 | RETURN_AV_ERROR("Failed to read frame: {}", avErrorStr(err)); 78 | 79 | return true; 80 | } 81 | } 82 | 83 | auto& videoStream() noexcept 84 | { 85 | return vStream_; 86 | } 87 | auto& audioStream() noexcept 88 | { 89 | return aStream_; 90 | } 91 | 92 | private: 93 | Expected findBestStream(AVMediaType type) noexcept 94 | { 95 | AVCodec* dec = nullptr; 96 | int stream_i = av_find_best_stream(ic_, type, -1, -1, &dec, 0); 97 | if (stream_i == AVERROR_STREAM_NOT_FOUND) 98 | RETURN_AV_ERROR("Failed to find {} stream in '{}'", av_get_media_type_string(type), url_); 99 | if (stream_i == AVERROR_DECODER_NOT_FOUND) 100 | RETURN_AV_ERROR("Failed to find decoder '{}' of '{}'", avcodec_get_name(ic_->streams[stream_i]->codecpar->codec_id), url_); 101 | 102 | if (type == AVMEDIA_TYPE_VIDEO) 103 | { 104 | const auto framerate = av_guess_frame_rate(ic_, ic_->streams[stream_i], nullptr); 105 | auto decContext = Decoder::create(dec, ic_->streams[stream_i], framerate); 106 | 107 | if (!decContext) 108 | FORWARD_AV_ERROR(decContext); 109 | 110 | std::get<0>(vStream_) = ic_->streams[stream_i]; 111 | std::get<1>(vStream_) = decContext.value(); 112 | } 113 | else if (type == AVMEDIA_TYPE_AUDIO) 114 | { 115 | auto decContext = Decoder::create(dec, ic_->streams[stream_i]); 116 | 117 | if (!decContext) 118 | FORWARD_AV_ERROR(decContext); 119 | 120 | std::get<0>(aStream_) = ic_->streams[stream_i]; 121 | std::get<1>(aStream_) = decContext.value(); 122 | } 123 | else 124 | RETURN_AV_ERROR("Not supported stream type '{}'", av_get_media_type_string(type)); 125 | 126 | return {}; 127 | } 128 | 129 | private: 130 | std::string url_; 131 | AVFormatContext* ic_{nullptr}; 132 | std::tuple> vStream_; 133 | std::tuple> aStream_; 134 | }; 135 | 136 | }// namespace av 137 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(../) 2 | 3 | function(findFFMpegNonDefault) 4 | set(FFMPEG_ROOT /home/greg/libsources/FFmpeg/release) 5 | #set(FFMPEG_ROOT /usr/include/x86_64-linux-gnu) 6 | set(AVUTIL_INCLUDE_DIRS "${FFMPEG_ROOT}/include") 7 | set(AVUTIL_LIBRARY_DIRS "${FFMPEG_ROOT}/lib") 8 | set(AVCODEC_INCLUDE_DIRS "${FFMPEG_ROOT}/include") 9 | set(AVCODEC_LIBRARY_DIRS "${FFMPEG_ROOT}/lib") 10 | set(AVFORMAT_INCLUDE_DIRS "${FFMPEG_ROOT}/include") 11 | set(AVFORMAT_LIBRARY_DIRS "${FFMPEG_ROOT}/lib") 12 | set(SWSCALE_INCLUDE_DIRS "${FFMPEG_ROOT}/include") 13 | set(SWSCALE_LIBRARY_DIRS "${FFMPEG_ROOT}/lib") 14 | set(SWRESAMPLE_INCLUDE_DIRS "${FFMPEG_ROOT}/include") 15 | set(SWRESAMPLE_LIBRARY_DIRS "${FFMPEG_ROOT}/lib") 16 | set(AVRESAMPLE_INCLUDE_DIRS "${FFMPEG_ROOT}/include") 17 | set(AVRESAMPLE_LIBRARY_DIRS "${FFMPEG_ROOT}/lib") 18 | 19 | # avcodec 20 | find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h PATHS ${AVCODEC_INCLUDE_DIRS} NO_DEFAULT_PATH) 21 | find_library(AVCODEC_LIBRARY avcodec PATHS ${AVCODEC_LIBRARY_DIRS} NO_DEFAULT_PATH) 22 | 23 | # avformat 24 | find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h PATHS ${AVFORMAT_INCLUDE_DIRS} NO_DEFAULT_PATH) 25 | find_library(AVFORMAT_LIBRARY avformat PATHS ${AVFORMAT_LIBRARY_DIRS} NO_DEFAULT_PATH) 26 | 27 | # avutil 28 | find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h PATHS ${AVUTIL_INCLUDE_DIRS} NO_DEFAULT_PATH) 29 | find_library(AVUTIL_LIBRARY avutil PATHS ${AVUTIL_LIBRARY_DIRS} NO_DEFAULT_PATH) 30 | 31 | # swscale 32 | find_path(SWSCALE_INCLUDE_DIR libswscale/swscale.h PATHS ${SWSCALE_INCLUDE_DIRS} NO_DEFAULT_PATH) 33 | find_library(SWSCALE_LIBRARY swscale PATHS ${SWSCALE_LIBRARY_DIRS} NO_DEFAULT_PATH) 34 | 35 | # swresample 36 | find_path(SWRESAMPLE_INCLUDE_DIR libswresample/swresample.h PATHS ${SWRESAMPLE_INCLUDE_DIRS} NO_DEFAULT_PATH) 37 | find_library(SWRESAMPLE_LIBRARY swresample PATHS ${SWRESAMPLE_LIBRARY_DIRS} NO_DEFAULT_PATH) 38 | 39 | if (AVCODEC_INCLUDE_DIR AND AVCODEC_LIBRARY) 40 | set(AVCODEC_FOUND TRUE) 41 | endif (AVCODEC_INCLUDE_DIR AND AVCODEC_LIBRARY) 42 | 43 | if (AVFORMAT_INCLUDE_DIR AND AVFORMAT_LIBRARY) 44 | set(AVFORMAT_FOUND TRUE) 45 | endif (AVFORMAT_INCLUDE_DIR AND AVFORMAT_LIBRARY) 46 | 47 | if (AVUTIL_INCLUDE_DIR AND AVUTIL_LIBRARY) 48 | set(AVUTIL_FOUND TRUE) 49 | endif (AVUTIL_INCLUDE_DIR AND AVUTIL_LIBRARY) 50 | 51 | if (SWSCALE_INCLUDE_DIR AND SWSCALE_LIBRARY) 52 | set(SWSCALE_FOUND TRUE) 53 | endif (SWSCALE_INCLUDE_DIR AND SWSCALE_LIBRARY) 54 | 55 | if (SWRESAMPLE_INCLUDE_DIR AND SWRESAMPLE_LIBRARY) 56 | set(SWRESAMPLE_FOUND TRUE) 57 | endif (SWRESAMPLE_INCLUDE_DIR AND SWRESAMPLE_LIBRARY) 58 | 59 | 60 | #include(FindPackageHandleStandardArgs) 61 | #if (SWRESAMPLE_FOUND) 62 | # FIND_PACKAGE_HANDLE_STANDARD_ARGS(FFmpeg DEFAULT_MSG AVUTIL_FOUND AVCODEC_FOUND AVFORMAT_FOUND SWSCALE_FOUND SWRESAMPLE_FOUND) 63 | #else() 64 | #FIND_PACKAGE_HANDLE_STANDARD_ARGS(FFmpeg DEFAULT_MSG AVUTIL_FOUND AVCODEC_FOUND AVFORMAT_FOUND SWSCALE_FOUND SWRESAMPLE_FOUND) 65 | #endif() 66 | #if (FFMPEG_FOUND) 67 | # if (SWRESAMPLE_FOUND) 68 | # set(FFMPEG_INCLUDE_DIRS ${AVCODEC_INCLUDE_DIR} ${AVFORMAT_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${SWSCALE_INCLUDE_DIR} ${SWRESAMPLE_INCLUDE_DIR}) 69 | # set(FFMPEG_LIBRARIES ${AVCODEC_LIBRARY} ${AVFORMAT_LIBRARY} ${AVUTIL_LIBRARY} ${SWSCALE_LIBRARY} ${SWRESAMPLE_LIBRARY}) 70 | # elseif (AVRESAMPLE_FOUND) 71 | set(FFMPEG_INCLUDE_DIRS ${AVCODEC_INCLUDE_DIR} ${AVFORMAT_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${SWSCALE_INCLUDE_DIR} ${SWRESAMPLE_INCLUDE_DIR} PARENT_SCOPE) 72 | set(FFMPEG_LIBRARIES ${AVCODEC_LIBRARY} ${AVFORMAT_LIBRARY} ${AVUTIL_LIBRARY} ${SWSCALE_LIBRARY} ${SWRESAMPLE_LIBRARY} PARENT_SCOPE) 73 | # endif() 74 | #endif(FFMPEG_FOUND) 75 | endfunction() 76 | findFFMpegNonDefault() 77 | 78 | message("AV files: ${AV_FILES}") 79 | 80 | add_executable(transcode ${AV_FILES} transcode.cpp) 81 | target_link_libraries(transcode PUBLIC ${FFMPEG_LIBRARIES}) 82 | -------------------------------------------------------------------------------- /av/OutputFormat.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace av 7 | { 8 | 9 | class OutputFormat : NoCopyable 10 | { 11 | explicit OutputFormat(AVFormatContext* oc) noexcept 12 | : oc_(oc) 13 | {} 14 | 15 | public: 16 | static Expected> create(std::string_view filename, std::string_view formatName = {}) noexcept 17 | { 18 | AVFormatContext* oc = nullptr; 19 | int err = avformat_alloc_output_context2(&oc, nullptr, formatName.empty() ? nullptr : formatName.data(), filename.data()); 20 | if (!oc || err < 0) 21 | RETURN_AV_ERROR("Failed to create output format context: {}", avErrorStr(err)); 22 | 23 | return Ptr(new OutputFormat(oc)); 24 | } 25 | 26 | ~OutputFormat() 27 | { 28 | if (oc_) 29 | { 30 | if (oc_->pb) 31 | { 32 | auto err = av_write_trailer(oc_); 33 | if (err < 0) 34 | LOG_AV_ERROR("Failed to write format trailer: {}", avErrorStr(err)); 35 | 36 | avio_close(oc_->pb); 37 | } 38 | 39 | avformat_free_context(oc_); 40 | } 41 | } 42 | 43 | auto* operator*() noexcept 44 | { 45 | return oc_; 46 | } 47 | const auto* operator*() const noexcept 48 | { 49 | return oc_; 50 | } 51 | 52 | auto* native() noexcept 53 | { 54 | return oc_; 55 | } 56 | const auto* native() const noexcept 57 | { 58 | return oc_; 59 | } 60 | 61 | // should be called before open() 62 | [[nodiscard]] Expected addStream(Ptr& codecContext) noexcept 63 | { 64 | auto stream = avformat_new_stream(oc_, nullptr); 65 | if (!stream) 66 | RETURN_AV_ERROR("Failed to create new stream"); 67 | 68 | /* copy the stream parameters to the muxer */ 69 | auto ret = avcodec_parameters_from_context(stream->codecpar, **codecContext); 70 | if (ret < 0) 71 | RETURN_AV_ERROR("Could not copy the stream parameters: {}", avErrorStr(ret)); 72 | 73 | stream->id = (int) oc_->nb_streams - 1; 74 | stream->time_base = codecContext->native()->time_base; 75 | 76 | /* Some formats want stream headers to be separate. */ 77 | if (oc_->oformat->flags & AVFMT_GLOBALHEADER) 78 | codecContext->native()->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; 79 | 80 | streams_.emplace_back(std::tuple{stream, codecContext}); 81 | 82 | int ind = (int) streams_.size() - 1; 83 | return ind; 84 | } 85 | 86 | [[nodiscard]] Expected open(std::string_view filename, int ioFlags = AVIO_FLAG_WRITE) noexcept 87 | { 88 | if (oc_->oformat->flags & AVFMT_NOFILE) 89 | RETURN_AV_ERROR("Failed to open avio context. Format context already associated with file."); 90 | 91 | auto err = avio_open(&oc_->pb, filename.data(), ioFlags); 92 | if (err < 0) 93 | RETURN_AV_ERROR("Failed to open io context for '{}': {}", filename, err); 94 | 95 | AVDictionary* opts = nullptr; 96 | err = avformat_write_header(oc_, &opts); 97 | if (err < 0) 98 | RETURN_AV_ERROR("Failed to write header: {}", avErrorStr(err)); 99 | 100 | av_dump_format(oc_, 0, nullptr, 1); 101 | 102 | return {}; 103 | } 104 | 105 | [[nodiscard]] Expected writePacket(Packet& packet, int streamIndex) noexcept 106 | { 107 | auto expectedStream = getStream(streamIndex); 108 | 109 | if (!expectedStream) 110 | FORWARD_AV_ERROR(expectedStream); 111 | 112 | auto [stream, codecContext] = expectedStream.value(); 113 | 114 | /* rescale output packet timestamp values from codec to stream timebase */ 115 | av_packet_rescale_ts(*packet, codecContext->native()->time_base, stream->time_base); 116 | packet.native()->stream_index = stream->index; 117 | packet.native()->pos = -1; 118 | 119 | auto ret = av_interleaved_write_frame(oc_, *packet); 120 | if (ret < 0) 121 | RETURN_AV_ERROR("Error writing output packet: {}", avErrorStr(ret)); 122 | 123 | return {}; 124 | } 125 | 126 | private: 127 | [[nodiscard]] Expected>> getStream(int index) 128 | { 129 | if (index < 0 || index >= (int)streams_.size()) 130 | RETURN_AV_ERROR("Stream index '{}' is out of range [{}-{}]", index, 0, streams_.size()); 131 | 132 | return streams_[index]; 133 | } 134 | 135 | private: 136 | AVFormatContext* oc_{nullptr}; 137 | std::vector>> streams_; 138 | }; 139 | 140 | }// namespace av 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libav-cpp 2 | FFmpeg C++ wrapper 3 | 4 | This is a header only, exceptionless simple C++ FFmpeg wrapper. 5 | Native FFmpeg C library has load of clumsy and excessive functions you need to call to be able to do something in it, 6 | it's very verbosy and you may forget to free some resources and eventually get a memory leak or forget to initialize something and get segfault, etc ... 7 | 8 | Internally I use ffmpeg in my work projects, and until now it was done via opencv and half-reworked ffmpeg wrappers ripped out of opencv sources which are light years far from ideal C++ implementation and looks more like C with classes than real c++ code with error handling ready for production. 9 | 10 | The goal of this library is to make using ffmpeg in C++ safe, comfy and simplier than it's done in FFmpeg C API. 11 | So I hope this code will be helpful for someone and if you want to contribute you're welcome. 12 | 13 | The library doesn't use exceptions at all, it uses custom implementation of Expected for error handling. 14 | All members and functions are marked as noexcept ( tbh it uses std containers which in theory may throw std::bad_alloc or some terrible things, but have you ever met out of memory and do you really need to handle this? Especially if you run your software on servers with tons of ram and where software typically relauches automatically on fails. ) 15 | 16 | No more words to say, just take a look at transocding example! 17 | 18 | ```C++ 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | // Since it a header only library there is no specific logging backend, so we must implement our own writeLog function 25 | // and place it in av namespace 26 | namespace av 27 | { 28 | template 29 | void writeLog(LogLevel level, internal::SourceLocation&& loc, std::string_view fmt, Args&&... args) noexcept 30 | { 31 | std::cerr << loc.toString() << ": " << av::internal::format(fmt, std::forward(args)...) << std::endl; 32 | } 33 | }// namespace av 34 | 35 | template 36 | void println(std::string_view fmt, Args&&... args) noexcept 37 | { 38 | std::cout << av::internal::format(fmt, std::forward(args)...) << std::endl; 39 | } 40 | 41 | template 42 | Return assertExpected(av::Expected&& expected) noexcept 43 | { 44 | if (!expected) 45 | { 46 | std::cerr << " === Expected failure == \n" 47 | << expected.errorString() << std::endl; 48 | exit(EXIT_FAILURE); 49 | } 50 | 51 | if constexpr (std::is_same_v) 52 | return; 53 | else 54 | return expected.value(); 55 | } 56 | 57 | int main(int argc, const char* argv[]) 58 | { 59 | if (argc < 3) 60 | { 61 | std::cout << "Usage: transcode " << std::endl; 62 | return 0; 63 | } 64 | 65 | std::string_view input(argv[1]); 66 | std::string_view output(argv[2]); 67 | 68 | av_log_set_level(AV_LOG_VERBOSE); 69 | 70 | auto reader = assertExpected(av::StreamReader::create(input, true)); 71 | 72 | av::Frame frame; 73 | 74 | auto writer = assertExpected(av::StreamWriter::create(output)); 75 | 76 | auto width = reader->frameWidth(); 77 | auto height = reader->frameHeight(); 78 | auto framerate = reader->framerate(); 79 | framerate = av_inv_q(framerate); 80 | auto pixFmt = reader->pixFmt(); 81 | 82 | av::OptValueMap codecOpts = {{"preset", "fast"}, {"crf", 29}}; 83 | 84 | assertExpected(writer->addVideoStream(AV_CODEC_ID_H264, width, height, 85 | pixFmt, framerate, std::move(codecOpts))); 86 | 87 | auto channels = reader->channels(); 88 | auto rate = reader->sampleRate(); 89 | auto format = reader->sampleFormat(); 90 | auto bitRate = 128 * 1024; 91 | 92 | assertExpected(writer->addAudioStream(AV_CODEC_ID_AAC, channels, format, 93 | rate, channels, rate, bitRate)); 94 | 95 | assertExpected(writer->open()); 96 | 97 | int video_n = 0; 98 | int audio_n = 0; 99 | for (;;) 100 | { 101 | if (!assertExpected(reader->readFrame(frame))) 102 | break; 103 | 104 | if (frame.type() == AVMEDIA_TYPE_VIDEO) 105 | { 106 | if (video_n % 100 == 0) 107 | println("Writing video {} frame", video_n); 108 | 109 | assertExpected(writer->write(frame, 0)); 110 | 111 | video_n++; 112 | } 113 | else if (frame.type() == AVMEDIA_TYPE_AUDIO) 114 | { 115 | if (audio_n % 100 == 0) 116 | println("Writing audio {} frame", audio_n); 117 | 118 | assertExpected(writer->write(frame, 1)); 119 | 120 | audio_n++; 121 | } 122 | } 123 | 124 | println("Encoded {} video {} audio frames", video_n, audio_n); 125 | 126 | return 0; 127 | } 128 | ``` 129 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | DM. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /av/VideoCapture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace av 10 | { 11 | 12 | struct VideoCaptureParams 13 | { 14 | std::string url; 15 | bool rawMode{false}; 16 | bool useSEITimestamps{false}; 17 | int targetFrameWidth{0}; 18 | int targetFrameHeight{0}; 19 | }; 20 | 21 | class VideoCapture : NoCopyable 22 | { 23 | VideoCapture() = default; 24 | 25 | public: 26 | static Expected> create(const VideoCaptureParams& params) noexcept 27 | { 28 | Ptr sr{new VideoCapture}; 29 | sr->params_ = params; 30 | 31 | auto err = avformat_open_input(&sr->ic_, sr->params_.url.data(), nullptr, nullptr); 32 | if (err < 0) 33 | RETURN_AV_ERROR("Cannot open input '{}': {}", sr->params_.url, avErrorStr(err)); 34 | 35 | err = avformat_find_stream_info(sr->ic_, nullptr); 36 | if (err < 0) 37 | RETURN_AV_ERROR("Cannot find stream info: {}", avErrorStr(err)); 38 | 39 | { 40 | auto ret = sr->findBestStream(); 41 | if (!ret) 42 | FORWARD_AV_ERROR(ret); 43 | } 44 | 45 | av_dump_format(sr->ic_, 0, nullptr, 0); 46 | 47 | return sr; 48 | } 49 | 50 | ~VideoCapture() 51 | { 52 | if(ic_) 53 | avformat_close_input(&ic_); 54 | } 55 | 56 | Expected readFrameRaw(Packet& packet) noexcept 57 | { 58 | for(;;) 59 | { 60 | packet.dataUnref(); 61 | auto r = readNextFramePacket(packet); 62 | if(!r) 63 | FORWARD_AV_ERROR(r); 64 | 65 | if(!r.value()) 66 | return false; 67 | 68 | if (packet.native()->stream_index != stream_->index) 69 | continue; 70 | 71 | return true; 72 | } 73 | } 74 | 75 | [[nodiscard]] Expected readFrame(cv::Mat& mat) noexcept 76 | { 77 | if(!params_.rawMode) 78 | { 79 | Frame frame; 80 | 81 | { 82 | auto e = readFrameDecoded(frame); 83 | if (!e) 84 | FORWARD_AV_ERROR(e); 85 | 86 | if (!e.value()) 87 | return false; 88 | } 89 | 90 | scale_->scale(frame, *swsFrame_); 91 | 92 | const auto data = swsFrame_->native()->data[0]; 93 | const auto step = swsFrame_->native()->linesize[0]; 94 | 95 | try 96 | { 97 | const cv::Mat tmp(targetFrameHeight(), targetFameWidth(), CV_MAKETYPE(CV_8U, 3), data, step); 98 | tmp.copyTo(mat); 99 | } 100 | catch (const std::exception& e) 101 | { 102 | RETURN_AV_ERROR("opencv exception: {}", e.what()); 103 | } 104 | 105 | return true; 106 | } 107 | else 108 | { 109 | Packet pkt; 110 | auto e = readFrameRaw(pkt); 111 | if(!e) 112 | FORWARD_AV_ERROR(e); 113 | 114 | if(!e.value()) 115 | return false; 116 | 117 | try 118 | { 119 | const auto data = pkt.native()->data; 120 | const auto step = pkt.native()->size; 121 | const cv::Mat tmp(1, pkt.native()->size, CV_MAKETYPE(CV_8U, 1), data, step); 122 | tmp.copyTo(mat); 123 | } 124 | catch (const std::exception& e) 125 | { 126 | RETURN_AV_ERROR("opencv exception: {}", e.what()); 127 | } 128 | 129 | return true; 130 | } 131 | } 132 | 133 | auto pixFmt() const noexcept 134 | { 135 | return (AVPixelFormat)stream_->codecpar->format; 136 | } 137 | 138 | int nativeFrameWidth() const noexcept 139 | { 140 | return stream_->codecpar->width; 141 | } 142 | 143 | int nativeFrameHeight() const noexcept 144 | { 145 | return stream_->codecpar->height; 146 | } 147 | int targetFameWidth() const noexcept 148 | { 149 | return params_.targetFrameWidth; 150 | } 151 | 152 | int targetFrameHeight() const noexcept 153 | { 154 | return params_.targetFrameHeight; 155 | } 156 | 157 | AVRational framerate() const noexcept 158 | { 159 | return framerate_; 160 | } 161 | 162 | private: 163 | Expected readNextFramePacket(Packet& packet) noexcept 164 | { 165 | int err = 0; 166 | for (;;) 167 | { 168 | err = av_read_frame(ic_, *packet); 169 | 170 | if (err == AVERROR(EAGAIN)) 171 | continue; 172 | 173 | if (err == AVERROR_EOF) 174 | { 175 | // flush cached frames from video decoder 176 | packet.native()->data = nullptr; 177 | packet.native()->size = 0; 178 | 179 | return false; 180 | } 181 | 182 | if (err < 0) 183 | RETURN_AV_ERROR("Failed to read frame: {}", avErrorStr(err)); 184 | 185 | return true; 186 | } 187 | } 188 | 189 | [[nodiscard]] Expected readFrameDecoded(Frame& frame) noexcept 190 | { 191 | Packet packet; 192 | 193 | for (;;) 194 | { 195 | packet.dataUnref(); 196 | 197 | auto successExp = readFrameRaw(packet); 198 | if (!successExp) 199 | FORWARD_AV_ERROR(successExp); 200 | 201 | auto resExp = decoder_->decode(packet, frame); 202 | 203 | if (!resExp) 204 | FORWARD_AV_ERROR(resExp); 205 | 206 | if(resExp.value() == Result::kEOF) 207 | return false; 208 | 209 | if (resExp.value() != Result::kSuccess) 210 | continue; 211 | 212 | frame.type(AVMEDIA_TYPE_VIDEO); 213 | 214 | return true; 215 | 216 | } 217 | } 218 | 219 | Expected findBestStream() noexcept 220 | { 221 | AVCodec* dec = nullptr; 222 | int stream_i = AVERROR_STREAM_NOT_FOUND; 223 | 224 | if(!params_.rawMode) 225 | stream_i = av_find_best_stream(ic_, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0); 226 | else 227 | stream_i = av_find_best_stream(ic_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); 228 | 229 | if (stream_i == AVERROR_STREAM_NOT_FOUND) 230 | RETURN_AV_ERROR("Failed to find {} stream in '{}'", av_get_media_type_string(AVMEDIA_TYPE_VIDEO), params_.url); 231 | 232 | 233 | if (!params_.rawMode && stream_i == AVERROR_DECODER_NOT_FOUND) 234 | RETURN_AV_ERROR("Failed to find decoder '{}' of '{}'", avcodec_get_name(ic_->streams[stream_i]->codecpar->codec_id), params_.url); 235 | 236 | 237 | framerate_ = av_guess_frame_rate(ic_, ic_->streams[stream_i], nullptr); 238 | 239 | stream_ = ic_->streams[stream_i]; 240 | 241 | if(!params_.rawMode) 242 | { 243 | auto decContext = Decoder::create(dec, ic_->streams[stream_i], framerate_); 244 | 245 | if (!decContext) 246 | FORWARD_AV_ERROR(decContext); 247 | 248 | decoder_ = decContext.value(); 249 | 250 | params_.targetFrameWidth = params_.targetFrameWidth > 0 ? params_.targetFrameWidth : nativeFrameWidth(); 251 | params_.targetFrameHeight = params_.targetFrameHeight > 0 ? params_.targetFrameHeight : nativeFrameHeight(); 252 | 253 | auto scaleExp = Scale::create(decoder_->native()->coded_width, decoder_->native()->coded_height, 254 | pixFmt(), params_.targetFrameWidth, params_.targetFrameHeight, AV_PIX_FMT_RGB24); 255 | 256 | if(!scaleExp) 257 | FORWARD_AV_ERROR(scaleExp); 258 | 259 | scale_ = scaleExp.value(); 260 | 261 | auto frameExp = Frame::create(params_.targetFrameWidth, params_.targetFrameHeight, AV_PIX_FMT_RGB24); 262 | if(!frameExp) 263 | FORWARD_AV_ERROR(frameExp); 264 | 265 | swsFrame_ = frameExp.value(); 266 | } 267 | 268 | return {}; 269 | } 270 | 271 | private: 272 | VideoCaptureParams params_; 273 | AVFormatContext* ic_{nullptr}; 274 | AVStream* stream_{nullptr}; 275 | AVRational framerate_{}; 276 | Ptr decoder_; 277 | Ptr scale_; 278 | Ptr swsFrame_; 279 | }; 280 | 281 | } 282 | -------------------------------------------------------------------------------- /av/StreamWriter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace av 12 | { 13 | class StreamWriter : NoCopyable 14 | { 15 | StreamWriter() = default; 16 | 17 | public: 18 | [[nodiscard]] static Expected> create(std::string_view filename) noexcept 19 | { 20 | Ptr sw{new StreamWriter}; 21 | sw->filename_ = filename; 22 | 23 | auto fcExp = OutputFormat::create(filename); 24 | if (!fcExp) 25 | FORWARD_AV_ERROR(fcExp); 26 | 27 | sw->formatContext_ = fcExp.value(); 28 | 29 | return sw; 30 | } 31 | 32 | ~StreamWriter() 33 | { 34 | flushAllStreams(); 35 | } 36 | 37 | [[nodiscard]] Expected open() noexcept 38 | { 39 | return formatContext_->open(filename_); 40 | } 41 | 42 | [[nodiscard]] Expected addVideoStream(std::variant codecName, int inWidth, int inHeight, AVPixelFormat inPixFmt, AVRational frameRate, int outWidth, int outHeight, OptValueMap&& codecParams = {}) noexcept 43 | { 44 | auto stream = makePtr(); 45 | stream->type = AVMEDIA_TYPE_VIDEO; 46 | 47 | auto expc = std::visit([](auto&& v) { return Encoder::create(v); }, codecName); 48 | 49 | if (!expc) 50 | FORWARD_AV_ERROR(expc); 51 | 52 | Ptr c = expc.value(); 53 | 54 | c->setVideoParams(outWidth, outHeight, frameRate, std::move(codecParams)); 55 | auto cOpenEXp = c->open(); 56 | if (!cOpenEXp) 57 | FORWARD_AV_ERROR(cOpenEXp); 58 | 59 | auto frameExp = c->newWriteableVideoFrame(); 60 | if (!frameExp) 61 | FORWARD_AV_ERROR(frameExp); 62 | 63 | stream->frame = frameExp.value(); 64 | stream->encoder = c; 65 | 66 | auto swsExp = Scale::create(inWidth, inHeight, inPixFmt, outWidth, outHeight, c->native()->pix_fmt); 67 | if (!swsExp) 68 | FORWARD_AV_ERROR(swsExp); 69 | 70 | stream->sws = swsExp.value(); 71 | 72 | auto sIndExp = formatContext_->addStream(c); 73 | if (!sIndExp) 74 | FORWARD_AV_ERROR(sIndExp); 75 | 76 | stream->index = sIndExp.value(); 77 | int index = stream->index; 78 | 79 | streams_.emplace_back(std::move(stream)); 80 | 81 | if ((int)streams_.size() - 1 != index) 82 | RETURN_AV_ERROR("Stream index {} != streams count - 1 {}", index, streams_.size() - 1); 83 | 84 | const AVCodec* codec = c->native()->codec; 85 | LOG_AV_INFO("Added video stream #{} codec: {} {}x{} {} fps", index, codec->long_name, c->native()->width, c->native()->height, av_q2d(av_inv_q(c->native()->time_base))); 86 | 87 | return index; 88 | } 89 | 90 | [[nodiscard]] Expected addVideoStream(std::variant codecName, int inWidth, int inHeight, AVPixelFormat inPixFmt, AVRational frameRate, OptValueMap&& codecParams = {}) 91 | { 92 | return addVideoStream(codecName, inWidth, inHeight, inPixFmt, frameRate, inWidth, inHeight, std::move(codecParams)); 93 | } 94 | 95 | [[nodiscard]] Expected addAudioStream(std::variant codecName, int inChannels, AVSampleFormat inSampleFmt, int inSampleRate, 96 | int outChannels, int outSampleRate, int outBitRate, OptValueMap&& codecParams = {}) noexcept 97 | { 98 | auto stream = makePtr(); 99 | stream->type = AVMEDIA_TYPE_AUDIO; 100 | 101 | auto expc = std::visit([](auto&& v) { return Encoder::create(v); }, codecName); 102 | 103 | if (!expc) 104 | FORWARD_AV_ERROR(expc); 105 | 106 | Ptr c = expc.value(); 107 | 108 | #if 0 109 | Ptr c; 110 | if(std::holds_alternative(codecName)) 111 | c = makePtr(std::get(codecName)); 112 | else 113 | c = makePtr(std::get(codecName)); 114 | #endif 115 | c->setAudioParams(outChannels, outSampleRate, outBitRate, std::move(codecParams)); 116 | auto cOpenExp = c->open(); 117 | if (!cOpenExp) 118 | FORWARD_AV_ERROR(cOpenExp); 119 | 120 | auto frameExp = c->newWriteableAudioFrame(); 121 | if (!frameExp) 122 | FORWARD_AV_ERROR(frameExp); 123 | 124 | stream->frame = frameExp.value(); 125 | stream->encoder = c; 126 | 127 | auto swrExp = Resample::create(inChannels, inSampleFmt, inSampleRate, outChannels, c->native()->sample_fmt, outSampleRate); 128 | if (!swrExp) 129 | FORWARD_AV_ERROR(swrExp); 130 | 131 | stream->swr = swrExp.value(); 132 | 133 | auto sIndExp = formatContext_->addStream(c); 134 | if (!sIndExp) 135 | FORWARD_AV_ERROR(sIndExp); 136 | 137 | stream->index = sIndExp.value(); 138 | int index = stream->index; 139 | 140 | streams_.emplace_back(std::move(stream)); 141 | 142 | if ((int)streams_.size() - 1 != index) 143 | RETURN_AV_ERROR("Stream index {} != streams count - 1 {}", index, streams_.size() - 1); 144 | 145 | const AVCodec* codec = c->native()->codec; 146 | LOG_AV_INFO("Added audio stream #{} codec: {} {} Hz {} channels", index, codec->long_name, c->native()->sample_rate, c->native()->channels); 147 | 148 | return index; 149 | } 150 | 151 | [[nodiscard]] Expected write(Frame& frame, int streamIndex) noexcept 152 | { 153 | auto& stream = streams_[streamIndex]; 154 | 155 | if (stream->type == AVMEDIA_TYPE_VIDEO) 156 | { 157 | stream->sws->scale(frame, *stream->frame); 158 | stream->frame->native()->pts = stream->nextPts++; 159 | } 160 | else if (stream->type == AVMEDIA_TYPE_AUDIO) 161 | { 162 | stream->swr->convert(frame, *stream->frame); 163 | stream->frame->native()->pts = stream->nextPts; 164 | stream->nextPts += stream->frame->native()->nb_samples; 165 | } 166 | else 167 | RETURN_AV_ERROR("Unsupported/unknown stream type: {}", av_get_media_type_string(stream->type)); 168 | 169 | auto [res, sz] = stream->encoder->encodeFrame(*stream->frame, stream->packets); 170 | 171 | if (res == Result::kFail) 172 | RETURN_AV_ERROR("Encoder returned failure"); 173 | 174 | for (int i = 0; i < sz; ++i) 175 | { 176 | auto expected = formatContext_->writePacket(stream->packets[i], stream->index); 177 | if (!expected) 178 | LOG_AV_ERROR(expected.errorString()); 179 | } 180 | 181 | return {}; 182 | } 183 | 184 | void flushStream(int streamIndex) noexcept 185 | { 186 | auto& stream = streams_[streamIndex]; 187 | 188 | if (stream->flushed) 189 | return; 190 | 191 | auto [res, sz] = stream->encoder->flush(stream->packets); 192 | stream->flushed = true; 193 | 194 | if (res == Result::kFail) 195 | return; 196 | 197 | for (int i = 0; i < sz; ++i) 198 | { 199 | auto expected = formatContext_->writePacket(stream->packets[i], stream->index); 200 | if (!expected) 201 | LOG_AV_ERROR(expected.errorString()); 202 | } 203 | } 204 | 205 | void flushAllStreams() noexcept 206 | { 207 | for (auto& stream : streams_) 208 | { 209 | flushStream(stream->index); 210 | } 211 | } 212 | 213 | private: 214 | struct Stream 215 | { 216 | AVMediaType type{AVMEDIA_TYPE_UNKNOWN}; 217 | int index{-1}; 218 | Ptr encoder; 219 | Ptr sws; 220 | Ptr swr; 221 | Ptr frame; 222 | std::vector packets; 223 | int nextPts{0}; 224 | int sampleCount{0}; 225 | bool flushed{false}; 226 | }; 227 | 228 | private: 229 | std::string filename_; 230 | std::vector> streams_; 231 | Ptr formatContext_; 232 | }; 233 | 234 | }// namespace av 235 | -------------------------------------------------------------------------------- /av/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | extern "C" 13 | { 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | } 22 | 23 | namespace av 24 | { 25 | 26 | template 27 | using Ptr = std::shared_ptr; 28 | 29 | template 30 | auto makePtr(Args&&... args) 31 | { 32 | return std::make_shared(std::forward(args)...); 33 | } 34 | 35 | namespace internal 36 | { 37 | template 38 | std::string toString(T&& v) noexcept 39 | { 40 | if constexpr (std::is_convertible_v) 41 | return std::string(v); 42 | else 43 | return std::to_string(v); 44 | }; 45 | 46 | inline 47 | std::string toString(std::string_view v) noexcept 48 | { 49 | return v.data(); 50 | } 51 | 52 | template 53 | inline size_t formatInplaceEx(std::string& fmt, int offset, Args&&... args) noexcept 54 | { 55 | std::vector sargs; 56 | (sargs.emplace_back(toString(args)), ...); 57 | 58 | size_t pos = offset; 59 | if (fmt.capacity() < 1024) 60 | fmt.reserve(1024); 61 | 62 | for (const auto& arg : sargs) 63 | { 64 | pos = fmt.find("{}", pos); 65 | if (pos == std::string::npos) 66 | return pos; 67 | 68 | fmt.replace(pos, 2, arg); 69 | 70 | pos += arg.size(); 71 | if (pos >= fmt.size()) 72 | return fmt.size(); 73 | } 74 | 75 | return fmt.size(); 76 | } 77 | 78 | template 79 | inline size_t formatInplace(std::string& fmt, Args&&... args) noexcept 80 | { 81 | return formatInplaceEx(fmt, 0, std::forward(args)...); 82 | } 83 | 84 | template 85 | inline std::string format(std::string_view fmt, Args&&... args) noexcept 86 | { 87 | std::string result(fmt); 88 | formatInplace(result, std::forward(args)...); 89 | 90 | return result; 91 | } 92 | 93 | class SourceLocation 94 | { 95 | public: 96 | SourceLocation(const char* file, int line, const char* fun) noexcept 97 | : file_(file), fun_(fun), line_(line) 98 | { 99 | } 100 | 101 | [[nodiscard]] std::string toString() const noexcept 102 | { 103 | return format("{}:{} [{}]", file_, line_, fun_); 104 | } 105 | 106 | auto values() const noexcept 107 | { 108 | return std::tuple{file_, line_, fun_}; 109 | } 110 | 111 | private: 112 | const char* file_; 113 | const char* fun_; 114 | int line_; 115 | }; 116 | 117 | }// namespace internal 118 | 119 | #define MAKE_AV_SOURCE_LOCATION() (av::internal::SourceLocation(__FILE__, __LINE__, static_cast(__FUNCTION__))) 120 | 121 | enum LogLevel 122 | { 123 | Trace = 0, 124 | Debug = 1, 125 | Info = 2, 126 | Warn = 3, 127 | Err = 4, 128 | Critical = 5, 129 | Off = 6, 130 | n_levels 131 | }; 132 | 133 | 134 | void writeLog(LogLevel level, internal::SourceLocation&& loc, std::string msg) noexcept; 135 | 136 | #define LOG_AV_ERROR(...) av::writeLog(av::LogLevel::Err, MAKE_AV_SOURCE_LOCATION(), av::internal::format(__VA_ARGS__)) 137 | #define LOG_AV_DEBUG(...) av::writeLog(av::LogLevel::Debug, MAKE_AV_SOURCE_LOCATION(), av::internal::format(__VA_ARGS__)) 138 | #define LOG_AV_INFO(...) av::writeLog(av::LogLevel::Info, MAKE_AV_SOURCE_LOCATION(), av::internal::format(__VA_ARGS__)) 139 | 140 | enum class Result 141 | { 142 | kSuccess = 0, 143 | kEAGAIN, 144 | kEOF, 145 | kFail 146 | }; 147 | 148 | struct NoCopyable 149 | { 150 | NoCopyable() = default; 151 | NoCopyable(const NoCopyable&) = delete; 152 | NoCopyable& operator=(const NoCopyable&) = delete; 153 | }; 154 | 155 | inline std::string avErrorStr(int av_error_code) noexcept 156 | { 157 | const auto buf_size = 1024U; 158 | std::string err_string(buf_size, 0); 159 | 160 | if (0 != av_strerror(av_error_code, err_string.data(), buf_size - 1)) 161 | { 162 | return internal::format("Unknown error with code: {}", av_error_code); 163 | } 164 | 165 | const auto len = std::strlen(err_string.data()); 166 | err_string.resize(len); 167 | 168 | return err_string; 169 | } 170 | 171 | class ExpectedBase 172 | { 173 | public: 174 | ExpectedBase(const internal::SourceLocation& loc, std::string_view desc) noexcept 175 | : desc_(desc) 176 | { 177 | stack_.emplace_back(loc); 178 | } 179 | 180 | ExpectedBase(const internal::SourceLocation& loc, ExpectedBase&& other) noexcept 181 | : stack_(std::move(other.stack_)), desc_(std::move(other.desc_)) 182 | { 183 | stack_.emplace_back(loc); 184 | } 185 | 186 | ExpectedBase() = default; 187 | 188 | ExpectedBase(ExpectedBase&& o) noexcept 189 | : stack_(std::move(o.stack_)), desc_(std::move(o.desc_)) 190 | {} 191 | 192 | ExpectedBase& operator=(ExpectedBase&& o) noexcept 193 | { 194 | if (&o == this) 195 | return *this; 196 | 197 | stack_ = std::move(o.stack_); 198 | desc_ = std::move(o.desc_); 199 | 200 | return *this; 201 | } 202 | 203 | explicit operator bool() const noexcept 204 | { 205 | return stack_.empty(); 206 | } 207 | 208 | const auto& stack() const noexcept 209 | { 210 | return stack_; 211 | } 212 | 213 | const auto& errorDescription() const noexcept 214 | { 215 | return desc_; 216 | } 217 | 218 | [[nodiscard]] std::string errorString() const noexcept 219 | { 220 | if (stack_.empty()) 221 | return ""; 222 | 223 | std::string result; 224 | result.reserve(4 * 1024); 225 | 226 | int i = 0; 227 | size_t pos = 0; 228 | for (auto it = stack_.rbegin(); it != stack_.rend(); ++it) 229 | { 230 | result += "#{} {}\n"; 231 | pos = internal::formatInplaceEx(result, pos, i++, it->toString()); 232 | } 233 | 234 | result += "Error: {}"; 235 | internal::formatInplaceEx(result, pos, desc_); 236 | 237 | return result; 238 | } 239 | 240 | private: 241 | std::vector stack_; 242 | std::string desc_; 243 | }; 244 | 245 | template 246 | class Expected : public ExpectedBase 247 | { 248 | public: 249 | Expected(T&& value) 250 | : value_(std::move(value)) 251 | { 252 | } 253 | 254 | Expected(const T& value) 255 | : value_(value) 256 | { 257 | } 258 | 259 | using ExpectedBase::ExpectedBase; 260 | 261 | Expected(Expected&& o) noexcept = default; 262 | Expected& operator=(Expected&& o) noexcept = default; 263 | 264 | const auto& value() const noexcept 265 | { 266 | return value_; 267 | } 268 | 269 | private: 270 | T value_; 271 | }; 272 | 273 | template<> 274 | class Expected : public ExpectedBase 275 | { 276 | public: 277 | using ExpectedBase::ExpectedBase; 278 | 279 | Expected(Expected&& o) noexcept = default; 280 | Expected& operator=(Expected&& o) noexcept = default; 281 | }; 282 | 283 | #define RETURN_AV_ERROR(...) \ 284 | return \ 285 | { \ 286 | MAKE_AV_SOURCE_LOCATION(), av::internal::format(__VA_ARGS__) \ 287 | } 288 | #define FORWARD_AV_ERROR(err) \ 289 | return \ 290 | { \ 291 | MAKE_AV_SOURCE_LOCATION(), std::move(err) \ 292 | } 293 | 294 | }// namespace av 295 | -------------------------------------------------------------------------------- /av/Encoder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace av 9 | { 10 | class Encoder : NoCopyable 11 | { 12 | explicit Encoder(AVCodecContext* codecContext) noexcept 13 | : codecContext_(codecContext) 14 | {} 15 | 16 | public: 17 | static Expected> create(AVCodecID codecId, bool allowHWAccel = true) noexcept 18 | { 19 | /* find the encoder */ 20 | //codec_ = avcodec_find_encoder(codecId); 21 | 22 | void* it = nullptr; 23 | const AVCodec* codec = nullptr; 24 | for (;;) 25 | { 26 | codec = av_codec_iterate(&it); 27 | 28 | if (!codec) 29 | RETURN_AV_ERROR("Could not find encoder for '{}'", avcodec_get_name(codecId)); 30 | 31 | if (!av_codec_is_encoder(codec)) 32 | continue; 33 | 34 | if (codec->id != codecId) 35 | continue; 36 | 37 | if (!allowHWAccel && codec->capabilities & AV_CODEC_CAP_HARDWARE) 38 | continue; 39 | 40 | break; 41 | } 42 | 43 | auto codecContext = avcodec_alloc_context3(codec); 44 | if (!codecContext) 45 | RETURN_AV_ERROR("Could not alloc an encoding context"); 46 | 47 | Ptr c{new Encoder(codecContext)}; 48 | c->setGenericDefaultValues(); 49 | 50 | return c; 51 | } 52 | 53 | static Expected> create(std::string_view codecName) noexcept 54 | { 55 | /* find the encoder */ 56 | auto codec = avcodec_find_encoder_by_name(codecName.data()); 57 | 58 | if (!codec) 59 | RETURN_AV_ERROR("Could not find encoder '{}'", codecName); 60 | 61 | auto codecContext = avcodec_alloc_context3(codec); 62 | if (!codecContext) 63 | RETURN_AV_ERROR("Could not alloc an encoding context"); 64 | 65 | Ptr c{new Encoder(codecContext)}; 66 | c->setGenericDefaultValues(); 67 | 68 | return c; 69 | } 70 | 71 | ~Encoder() 72 | { 73 | if (codecContext_) 74 | { 75 | avcodec_free_context(&codecContext_); 76 | } 77 | } 78 | 79 | auto* operator*() noexcept 80 | { 81 | return codecContext_; 82 | } 83 | const auto* operator*() const noexcept 84 | { 85 | return codecContext_; 86 | } 87 | 88 | auto* native() noexcept 89 | { 90 | return codecContext_; 91 | } 92 | const auto* native() const noexcept 93 | { 94 | return codecContext_; 95 | } 96 | 97 | Expected open() noexcept 98 | { 99 | AVDictionary* opts = nullptr; 100 | auto ret = avcodec_open2(codecContext_, codecContext_->codec, &opts); 101 | if (ret < 0) 102 | { 103 | avcodec_free_context(&codecContext_); 104 | RETURN_AV_ERROR("Could not open video codec: {}", avErrorStr(ret)); 105 | } 106 | 107 | return {}; 108 | } 109 | 110 | void setVideoParams(int width, int height, double fps, OptValueMap&& valueMap) noexcept 111 | { 112 | auto framerate = av_d2q(1.0 / fps, 100000); 113 | setVideoParams(width, height, framerate, std::move(valueMap)); 114 | } 115 | 116 | void setVideoParams(int width, int height, AVRational framerate, OptValueMap&& valueMap) noexcept 117 | { 118 | /* Resolution must be a multiple of two. */ 119 | codecContext_->width = width; 120 | codecContext_->height = height; 121 | /* timebase: This is the fundamental unit of time (in seconds) in terms 122 | * of which frame timestamps are represented. For fixed-fps content, 123 | * timebase should be 1/framerate and timestamp increments should be 124 | * identical to 1. */ 125 | codecContext_->time_base = framerate; 126 | 127 | codecContext_->bit_rate = 0; 128 | if (codecContext_->priv_data) 129 | { 130 | OptSetter::set(codecContext_->priv_data, valueMap); 131 | } 132 | } 133 | 134 | void setAudioParams(int channels, int sampleRate, int bitRate, OptValueMap&& valueMap) noexcept 135 | { 136 | /* Resolution must be a multiple of two. */ 137 | codecContext_->channels = channels; 138 | codecContext_->channel_layout = av_get_default_channel_layout(channels); 139 | codecContext_->sample_rate = sampleRate; 140 | codecContext_->bit_rate = bitRate; 141 | 142 | /* Allow the use of the experimental encoder. */ 143 | codecContext_->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; 144 | /* timebase: This is the fundamental unit of time (in seconds) in terms 145 | * of which frame timestamps are represented. For fixed-fps content, 146 | * timebase should be 1/framerate and timestamp increments should be 147 | * identical to 1. */ 148 | 149 | if (codecContext_->priv_data) 150 | { 151 | OptSetter::set(codecContext_->priv_data, valueMap); 152 | } 153 | } 154 | 155 | [[nodiscard]] Expected> newWriteableVideoFrame() const noexcept 156 | { 157 | Ptr f = makePtr(); 158 | AVFrame* frame = f->native(); 159 | 160 | frame->width = codecContext_->width; 161 | frame->height = codecContext_->height; 162 | frame->format = codecContext_->pix_fmt; 163 | frame->pts = 0; 164 | 165 | /* allocate the buffers for the frame data */ 166 | auto ret = av_frame_get_buffer(frame, 0); 167 | if (ret < 0) 168 | RETURN_AV_ERROR("Could not allocate frame data: {}", avErrorStr(ret)); 169 | 170 | ret = av_frame_make_writable(frame); 171 | if (ret < 0) 172 | RETURN_AV_ERROR("Could not make frame writable: {}", avErrorStr(ret)); 173 | 174 | return f; 175 | } 176 | 177 | [[nodiscard]] Expected> newWriteableAudioFrame() const noexcept 178 | { 179 | auto f = makePtr(); 180 | auto frame = f->native(); 181 | 182 | frame->channel_layout = codecContext_->channel_layout; 183 | //frame->nb_samples = 1024; 184 | frame->sample_rate = codecContext_->sample_rate; 185 | frame->format = codecContext_->sample_fmt; 186 | frame->pts = 0; 187 | frame->pkt_dts = 0; 188 | 189 | return f; 190 | } 191 | 192 | std::tuple encodeFrame(Frame& frame, std::vector& packets) noexcept 193 | { 194 | if (!sendFrame(*frame)) 195 | return {Result::kFail, 0}; 196 | 197 | return receivePackets(packets); 198 | } 199 | 200 | std::tuple flush(std::vector& packets) noexcept 201 | { 202 | if (!sendFrame(nullptr)) 203 | return {Result::kFail, 0}; 204 | 205 | return receivePackets(packets); 206 | } 207 | 208 | private: 209 | bool sendFrame(AVFrame* frame) noexcept 210 | { 211 | // send the frame to the encoder 212 | auto err = avcodec_send_frame(codecContext_, frame); 213 | if (err < 0) 214 | { 215 | LOG_AV_ERROR("Error sending a frame to the encoder: {}", avErrorStr(err)); 216 | return false; 217 | } 218 | 219 | return true; 220 | } 221 | 222 | std::tuple receivePackets(std::vector& packets) noexcept 223 | { 224 | for (auto& pkt : packets) 225 | pkt.dataUnref(); 226 | 227 | for (int i = 0;; ++i) 228 | { 229 | if (i >= (int)packets.size()) 230 | { 231 | packets.emplace_back(); 232 | } 233 | 234 | auto err = avcodec_receive_packet(codecContext_, *packets[i]); 235 | if (err == AVERROR(EAGAIN)) 236 | return {Result::kSuccess, i}; 237 | 238 | if (err == AVERROR_EOF) 239 | return {Result::kEOF, i}; 240 | 241 | if (err < 0) 242 | { 243 | LOG_AV_ERROR("Codec packet receive error: {}", avErrorStr(err)); 244 | return {Result::kFail, i}; 245 | } 246 | } 247 | } 248 | 249 | void setGenericDefaultValues() noexcept 250 | { 251 | switch (codecContext_->codec->type) 252 | { 253 | case AVMEDIA_TYPE_AUDIO: 254 | codecContext_->sample_fmt = codecContext_->codec->sample_fmts ? codecContext_->codec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; 255 | codecContext_->bit_rate = 64000; 256 | codecContext_->sample_rate = 44100; 257 | if (codecContext_->codec->supported_samplerates) 258 | { 259 | codecContext_->sample_rate = codecContext_->codec->supported_samplerates[0]; 260 | for (int i = 0; codecContext_->codec->supported_samplerates[i]; i++) 261 | { 262 | if (codecContext_->codec->supported_samplerates[i] == 44100) 263 | codecContext_->sample_rate = 44100; 264 | } 265 | } 266 | codecContext_->channels = av_get_channel_layout_nb_channels(codecContext_->channel_layout); 267 | codecContext_->channel_layout = AV_CH_LAYOUT_STEREO; 268 | if (codecContext_->codec->channel_layouts) 269 | { 270 | codecContext_->channel_layout = codecContext_->codec->channel_layouts[0]; 271 | for (int i = 0; codecContext_->codec->channel_layouts[i]; i++) 272 | { 273 | if (codecContext_->codec->channel_layouts[i] == AV_CH_LAYOUT_STEREO) 274 | codecContext_->channel_layout = AV_CH_LAYOUT_STEREO; 275 | } 276 | } 277 | codecContext_->channels = av_get_channel_layout_nb_channels(codecContext_->channel_layout); 278 | break; 279 | 280 | case AVMEDIA_TYPE_VIDEO: 281 | codecContext_->gop_size = 12; /* emit one intra frame every twelve frames at most */ 282 | codecContext_->pix_fmt = codecContext_->codec->pix_fmts ? codecContext_->codec->pix_fmts[0] : AV_PIX_FMT_YUV420P; 283 | if (codecContext_->codec_id == AV_CODEC_ID_MPEG2VIDEO) 284 | { 285 | /* just for testing, we also add B-frames */ 286 | codecContext_->max_b_frames = 2; 287 | } 288 | if (codecContext_->codec_id == AV_CODEC_ID_MPEG1VIDEO) 289 | { 290 | /* Needed to avoid using macroblocks in which some coeffs overflow. 291 | * This does not happen with normal video, it just happens here as 292 | * the motion of the chroma plane does not match the luma plane. */ 293 | codecContext_->mb_decision = 2; 294 | } 295 | /* Some settings for libx264 encoding, restore dummy values for gop_size 296 | and qmin since they will be set to reasonable defaults by the libx264 297 | preset system. Also, use a crf encode with the default quality rating, 298 | this seems easier than finding an appropriate default bitrate. */ 299 | if (codecContext_->codec_id == AV_CODEC_ID_H264 || codecContext_->codec_id == AV_CODEC_ID_HEVC) 300 | { 301 | codecContext_->gop_size = -1; 302 | 303 | codecContext_->qmin = -1; 304 | 305 | codecContext_->bit_rate = 0; 306 | if (codecContext_->priv_data) { 307 | //av_opt_set(codecContext_->priv_data, "crf", "23", 0); 308 | // av_opt_set(codecContext_->priv_data, "preset", "ultrafast", 0); 309 | // av_opt_set(codecContext_->priv_data, "tune", "zerolatency", 0); 310 | } 311 | } 312 | break; 313 | 314 | default: 315 | break; 316 | } 317 | } 318 | 319 | private: 320 | AVCodecContext* codecContext_{nullptr}; 321 | }; 322 | 323 | }// namespace av 324 | --------------------------------------------------------------------------------