├── LICENSE ├── ffmpeg_encode.cpp └── ffmpeg_encode.h /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /ffmpeg_encode.cpp: -------------------------------------------------------------------------------- 1 | #include "ffmpeg_encode.h" 2 | 3 | #include 4 | 5 | extern "C" 6 | { 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | } 13 | 14 | 15 | FfmpegEncoder::FfmpegEncoder(const char *filename, const Params ¶ms) 16 | { 17 | Open(filename, params); 18 | } 19 | 20 | FfmpegEncoder::~FfmpegEncoder() 21 | { 22 | Close(); 23 | } 24 | 25 | bool FfmpegEncoder::Open(const char *filename, const Params ¶ms) 26 | { 27 | Close(); 28 | 29 | do 30 | { 31 | avformat_alloc_output_context2(&mContext.format_context, nullptr, nullptr, filename); 32 | if (!mContext.format_context) 33 | { 34 | std::cout << "could not allocate output format" << std::endl; 35 | break; 36 | } 37 | 38 | mContext.codec = avcodec_find_encoder(AV_CODEC_ID_H264); 39 | if (!mContext.codec) 40 | { 41 | std::cout << "could not find encoder" << std::endl; 42 | break; 43 | } 44 | 45 | mContext.stream = avformat_new_stream(mContext.format_context, nullptr); 46 | if (!mContext.stream) 47 | { 48 | std::cout << "could not create stream" << std::endl; 49 | break; 50 | } 51 | mContext.stream->id = (int)(mContext.format_context->nb_streams - 1); 52 | 53 | mContext.codec_context = avcodec_alloc_context3(mContext.codec); 54 | if (!mContext.codec_context) 55 | { 56 | std::cout << "could not allocate mContext codec context" << std::endl; 57 | break; 58 | } 59 | 60 | mContext.codec_context->codec_id = mContext.format_context->oformat->video_codec; 61 | mContext.codec_context->bit_rate = params.bitrate; 62 | mContext.codec_context->width = static_cast(params.width); 63 | mContext.codec_context->height = static_cast(params.height); 64 | mContext.stream->time_base = av_d2q(1.0 / params.fps, 120); 65 | mContext.codec_context->time_base = mContext.stream->time_base; 66 | mContext.codec_context->pix_fmt = params.dst_format; 67 | mContext.codec_context->gop_size = 12; 68 | mContext.codec_context->max_b_frames = 2; 69 | 70 | if (mContext.format_context->oformat->flags & AVFMT_GLOBALHEADER) 71 | mContext.codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; 72 | 73 | int ret = 0; 74 | if (params.preset) 75 | { 76 | ret = av_opt_set(mContext.codec_context->priv_data, "preset", params.preset, 0); 77 | if (ret != 0) 78 | { 79 | std::cout << "could not set preset: " << params.preset << std::endl; 80 | break; 81 | } 82 | } 83 | 84 | { 85 | ret = av_opt_set_int(mContext.codec_context->priv_data, "crf", params.crf, 0); 86 | if (ret != 0) 87 | { 88 | std::cout << "could not set crf: " << params.crf << std::endl; 89 | break; 90 | } 91 | } 92 | 93 | ret = avcodec_open2(mContext.codec_context, mContext.codec, nullptr); 94 | if (ret != 0) 95 | { 96 | std::cout << "could not open codec: " << ret << std::endl; 97 | break; 98 | } 99 | 100 | mContext.frame = av_frame_alloc(); 101 | if (!mContext.frame) 102 | { 103 | std::cout << "could not allocate mContext frame" << std::endl; 104 | break; 105 | } 106 | mContext.frame->format = mContext.codec_context->pix_fmt; 107 | mContext.frame->width = mContext.codec_context->width; 108 | mContext.frame->height = mContext.codec_context->height; 109 | 110 | ret = av_frame_get_buffer(mContext.frame, 32); 111 | if (ret < 0) 112 | { 113 | std::cout << "could not allocate the mContext frame data" << std::endl; 114 | break; 115 | } 116 | 117 | ret = avcodec_parameters_from_context(mContext.stream->codecpar, mContext.codec_context); 118 | if (ret < 0) 119 | { 120 | std::cout << "could not copy the stream parameters" << std::endl; 121 | break; 122 | } 123 | 124 | mContext.sws_context = sws_getContext( 125 | mContext.codec_context->width, mContext.codec_context->height, params.src_format, // src 126 | mContext.codec_context->width, mContext.codec_context->height, params.dst_format, // dst 127 | SWS_BICUBIC, nullptr, nullptr, nullptr 128 | ); 129 | if (!mContext.sws_context) 130 | { 131 | std::cout << "could not initialize the conversion context" << std::endl; 132 | break; 133 | } 134 | 135 | av_dump_format(mContext.format_context, 0, filename, 1); 136 | 137 | ret = avio_open(&mContext.format_context->pb, filename, AVIO_FLAG_WRITE); 138 | if (ret != 0) 139 | { 140 | std::cout << "could not open " << filename << std::endl; 141 | break; 142 | } 143 | 144 | ret = avformat_write_header(mContext.format_context, nullptr); 145 | if (ret < 0) 146 | { 147 | std::cout << "could not write" << std::endl; 148 | ret = avio_close(mContext.format_context->pb); 149 | if (ret != 0) 150 | std::cout << "failed to close file" << std::endl; 151 | break; 152 | } 153 | 154 | mContext.frame_index = 0; 155 | mIsOpen = true; 156 | return true; 157 | } while (false); 158 | 159 | Close(); 160 | 161 | return false; 162 | } 163 | 164 | void FfmpegEncoder::Close() 165 | { 166 | if (mIsOpen) 167 | { 168 | avcodec_send_frame(mContext.codec_context, nullptr); 169 | 170 | FlushPackets(); 171 | 172 | av_write_trailer(mContext.format_context); 173 | 174 | auto ret = avio_close(mContext.format_context->pb); 175 | if (ret != 0) 176 | std::cout << "failed to close file" << std::endl; 177 | } 178 | 179 | if (mContext.sws_context) 180 | sws_freeContext(mContext.sws_context); 181 | 182 | if (mContext.frame) 183 | av_frame_free(&mContext.frame); 184 | 185 | if (mContext.codec_context) 186 | avcodec_free_context(&mContext.codec_context); 187 | 188 | if (mContext.codec_context) 189 | avcodec_close(mContext.codec_context); 190 | 191 | if (mContext.format_context) 192 | avformat_free_context(mContext.format_context); 193 | 194 | mContext = {}; 195 | mIsOpen = false; 196 | } 197 | 198 | bool FfmpegEncoder::Write(const unsigned char *data) 199 | { 200 | if (!mIsOpen) 201 | return false; 202 | 203 | auto ret = av_frame_make_writable(mContext.frame); 204 | if (ret < 0) 205 | { 206 | std::cout << "frame not writable" << std::endl; 207 | return false; 208 | } 209 | 210 | const int in_linesize[1] = { mContext.codec_context->width * 3 }; 211 | 212 | sws_scale( 213 | mContext.sws_context, 214 | &data, in_linesize, 0, mContext.codec_context->height, // src 215 | mContext.frame->data, mContext.frame->linesize // dst 216 | ); 217 | mContext.frame->pts = mContext.frame_index++; 218 | 219 | ret = avcodec_send_frame(mContext.codec_context, mContext.frame); 220 | if (ret < 0) 221 | { 222 | std::cout << "error sending a frame for encoding" << std::endl; 223 | return false; 224 | } 225 | 226 | return FlushPackets(); 227 | } 228 | 229 | bool FfmpegEncoder::IsOpen() const 230 | { 231 | return mIsOpen; 232 | } 233 | 234 | 235 | bool FfmpegEncoder::FlushPackets() 236 | { 237 | int ret; 238 | do 239 | { 240 | AVPacket packet = { 0 }; 241 | 242 | ret = avcodec_receive_packet(mContext.codec_context, &packet); 243 | if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) 244 | break; 245 | 246 | if (ret < 0) 247 | { 248 | std::cout << "error encoding a frame: " << ret << std::endl; 249 | return false; 250 | } 251 | 252 | av_packet_rescale_ts(&packet, mContext.codec_context->time_base, mContext.stream->time_base); 253 | packet.stream_index = mContext.stream->index; 254 | 255 | ret = av_interleaved_write_frame(mContext.format_context, &packet); 256 | av_packet_unref(&packet); 257 | if (ret < 0) 258 | { 259 | std::cout << "error while writing output packet: " << ret << std::endl; 260 | return false; 261 | } 262 | } while (ret >= 0); 263 | 264 | return true; 265 | } 266 | 267 | -------------------------------------------------------------------------------- /ffmpeg_encode.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | // presets: 6 | // 7 | // ultrafast 8 | // superfast 9 | // veryfast 10 | // faster 11 | // fast 12 | // medium – default preset 13 | // slow 14 | // slower 15 | // veryslow 16 | 17 | 18 | class FfmpegEncoder 19 | { 20 | public: 21 | struct Params 22 | { 23 | uint32_t width; 24 | uint32_t height; 25 | double fps; 26 | uint32_t bitrate; 27 | const char *preset; 28 | 29 | uint32_t crf; //0–51 30 | 31 | enum AVPixelFormat src_format; 32 | enum AVPixelFormat dst_format; 33 | }; 34 | 35 | FfmpegEncoder() = default; 36 | FfmpegEncoder(const char *filename, const Params ¶ms); 37 | ~FfmpegEncoder(); 38 | 39 | bool Open(const char *filename, const Params ¶ms); 40 | 41 | void Close(); 42 | 43 | bool Write(const unsigned char *data); 44 | 45 | bool IsOpen() const; 46 | 47 | private: 48 | bool FlushPackets(); 49 | 50 | private: 51 | bool mIsOpen = false; 52 | 53 | struct Context 54 | { 55 | struct AVFormatContext *format_context = nullptr; 56 | struct AVStream *stream = nullptr; 57 | struct AVCodecContext *codec_context = nullptr; 58 | struct AVFrame *frame = nullptr; 59 | struct SwsContext *sws_context = nullptr; 60 | struct AVCodec *codec = nullptr; 61 | 62 | uint32_t frame_index = 0; 63 | }; 64 | 65 | Context mContext = {}; 66 | }; --------------------------------------------------------------------------------