From c5e328e35992d205c09244cb20b90cf52df41c41 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Fri, 22 Apr 2022 12:51:38 -0700 Subject: [PATCH] obs-ffmpeg: Refactor FFmpeg video encoders This reduces code duplication between the different FFmpeg-based video encoder implementations --- plugins/obs-ffmpeg/CMakeLists.txt | 1 + plugins/obs-ffmpeg/obs-ffmpeg-av1.c | 359 +++------------ plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c | 417 ++++-------------- .../obs-ffmpeg/obs-ffmpeg-video-encoders.c | 294 ++++++++++++ .../obs-ffmpeg/obs-ffmpeg-video-encoders.h | 58 +++ 5 files changed, 497 insertions(+), 632 deletions(-) create mode 100644 plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.c create mode 100644 plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.h diff --git a/plugins/obs-ffmpeg/CMakeLists.txt b/plugins/obs-ffmpeg/CMakeLists.txt index 4c23d869a..c482ef191 100644 --- a/plugins/obs-ffmpeg/CMakeLists.txt +++ b/plugins/obs-ffmpeg/CMakeLists.txt @@ -22,6 +22,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/obs-ffmpeg-config.h.in target_sources( obs-ffmpeg PRIVATE obs-ffmpeg.c + obs-ffmpeg-video-encoders.c obs-ffmpeg-audio-encoders.c obs-ffmpeg-av1.c obs-ffmpeg-nvenc.c diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-av1.c b/plugins/obs-ffmpeg/obs-ffmpeg-av1.c index 7c84675fe..b5e1da28a 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-av1.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-av1.c @@ -1,5 +1,5 @@ /****************************************************************************** - Copyright (C) 2016 by Hugh Bailey + Copyright (C) 2022 by Hugh Bailey This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,23 +15,11 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "obs-ffmpeg-formats.h" +#include "obs-ffmpeg-video-encoders.h" #define do_log(level, format, ...) \ blog(level, "[AV1 encoder: '%s'] " format, \ - obs_encoder_get_name(enc->encoder), ##__VA_ARGS__) + obs_encoder_get_name(enc->ffve.encoder), ##__VA_ARGS__) #define error(format, ...) do_log(LOG_ERROR, format, ##__VA_ARGS__) #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) @@ -39,22 +27,10 @@ #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) struct av1_encoder { - obs_encoder_t *encoder; - const char *enc_name; + struct ffmpeg_video_encoder ffve; bool svtav1; - AVCodec *avcodec; - AVCodecContext *context; - int64_t start_ts; - bool first_packet; - - AVFrame *vframe; - - DARRAY(uint8_t) buffer; DARRAY(uint8_t) header; - - int height; - bool initialized; }; static const char *aom_av1_getname(void *unused) @@ -83,55 +59,6 @@ static void av1_video_info(void *data, struct video_scale_info *info) } } -static bool av1_init_codec(struct av1_encoder *enc) -{ - int ret; - - enc->context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - - ret = avcodec_open2(enc->context, enc->avcodec, NULL); - if (ret < 0) { - if (!obs_encoder_get_last_error(enc->encoder)) { - struct dstr error_message = {0}; - - dstr_copy(&error_message, - obs_module_text("Encoder.Error")); - dstr_replace(&error_message, "%1", enc->enc_name); - dstr_replace(&error_message, "%2", av_err2str(ret)); - dstr_cat(&error_message, "\r\n\r\n"); - - obs_encoder_set_last_error(enc->encoder, - error_message.array); - dstr_free(&error_message); - } - warn("Failed to open %s: %s", enc->enc_name, av_err2str(ret)); - return false; - } - - enc->vframe = av_frame_alloc(); - if (!enc->vframe) { - warn("Failed to allocate video frame"); - return false; - } - - enc->vframe->format = enc->context->pix_fmt; - enc->vframe->width = enc->context->width; - enc->vframe->height = enc->context->height; - enc->vframe->colorspace = enc->context->colorspace; - enc->vframe->color_range = enc->context->color_range; - - ret = av_frame_get_buffer(enc->vframe, base_get_alignment()); - if (ret < 0) { - warn("Failed to allocate vframe: %s", av_err2str(ret)); - return false; - } - - enc->initialized = true; - return true; -} - -enum RC_MODE { RC_MODE_CBR, RC_MODE_VBR, RC_MODE_CQP, RC_MODE_LOSSLESS }; - static bool av1_update(struct av1_encoder *enc, obs_data_t *settings) { const char *rc = obs_data_get_string(settings, "rate_control"); @@ -140,7 +67,7 @@ static bool av1_update(struct av1_encoder *enc, obs_data_t *settings) int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); int preset = (int)obs_data_get_int(settings, "preset"); - video_t *video = obs_encoder_video(enc->encoder); + video_t *video = obs_encoder_video(enc->ffve.encoder); const struct video_output_info *voi = video_output_get_info(video); struct video_scale_info info; @@ -148,97 +75,50 @@ static bool av1_update(struct av1_encoder *enc, obs_data_t *settings) info.colorspace = voi->colorspace; info.range = voi->range; - enc->context->thread_count = 0; + enc->ffve.context->thread_count = 0; av1_video_info(enc, &info); - av_opt_set_int(enc->context->priv_data, + av_opt_set_int(enc->ffve.context->priv_data, enc->svtav1 ? "preset" : "cpu-used", preset, 0); if (!enc->svtav1) { - av_opt_set(enc->context->priv_data, "usage", "realtime", 0); + av_opt_set(enc->ffve.context->priv_data, "usage", "realtime", + 0); #if 0 - av_opt_set_int(enc->context->priv_data, "tile-columns", 4, 0); - //av_opt_set_int(enc->context->priv_data, "tile-rows", 4, 0); + av_opt_set_int(enc->ffve.context->priv_data, "tile-columns", 4, 0); + //av_opt_set_int(enc->ffve.context->priv_data, "tile-rows", 4, 0); #else - av_opt_set_int(enc->context->priv_data, "tile-columns", 2, 0); - av_opt_set_int(enc->context->priv_data, "tile-rows", 2, 0); + av_opt_set_int(enc->ffve.context->priv_data, "tile-columns", 2, + 0); + av_opt_set_int(enc->ffve.context->priv_data, "tile-rows", 2, 0); #endif - av_opt_set_int(enc->context->priv_data, "row-mt", 1, 0); + av_opt_set_int(enc->ffve.context->priv_data, "row-mt", 1, 0); } if (enc->svtav1) - av_opt_set(enc->context->priv_data, "rc", "vbr", 0); + av_opt_set(enc->ffve.context->priv_data, "rc", "vbr", 0); if (astrcmpi(rc, "cqp") == 0) { bitrate = 0; - enc->context->global_quality = cqp; + enc->ffve.context->global_quality = cqp; if (enc->svtav1) - av_opt_set(enc->context->priv_data, "rc", "cqp", 0); + av_opt_set(enc->ffve.context->priv_data, "rc", "cqp", + 0); } else if (astrcmpi(rc, "vbr") != 0) { /* CBR by default */ const int64_t rate = bitrate * INT64_C(1000); - enc->context->rc_max_rate = rate; - enc->context->rc_min_rate = rate; + enc->ffve.context->rc_max_rate = rate; + enc->ffve.context->rc_min_rate = rate; cqp = 0; if (enc->svtav1) - av_opt_set(enc->context->priv_data, "rc", "cvbr", 0); + av_opt_set(enc->ffve.context->priv_data, "rc", "cvbr", + 0); } - const int rate = bitrate * 1000; - enc->context->bit_rate = rate; - enc->context->rc_buffer_size = rate; - enc->context->width = obs_encoder_get_width(enc->encoder); - enc->context->height = obs_encoder_get_height(enc->encoder); - enc->context->time_base = (AVRational){voi->fps_den, voi->fps_num}; - enc->context->pix_fmt = obs_to_ffmpeg_video_format(info.format); - enc->context->color_range = info.range == VIDEO_RANGE_FULL - ? AVCOL_RANGE_JPEG - : AVCOL_RANGE_MPEG; - - switch (info.colorspace) { - case VIDEO_CS_601: - enc->context->color_primaries = AVCOL_PRI_SMPTE170M; - enc->context->color_trc = AVCOL_TRC_SMPTE170M; - enc->context->colorspace = AVCOL_SPC_SMPTE170M; - break; - case VIDEO_CS_DEFAULT: - case VIDEO_CS_709: - enc->context->color_primaries = AVCOL_PRI_BT709; - enc->context->color_trc = AVCOL_TRC_BT709; - enc->context->colorspace = AVCOL_SPC_BT709; - break; - case VIDEO_CS_SRGB: - enc->context->color_primaries = AVCOL_PRI_BT709; - enc->context->color_trc = AVCOL_TRC_IEC61966_2_1; - enc->context->colorspace = AVCOL_SPC_BT709; - break; - case VIDEO_CS_2100_PQ: - enc->context->color_primaries = AVCOL_PRI_BT2020; - enc->context->color_trc = AVCOL_TRC_SMPTE2084; - enc->context->colorspace = AVCOL_SPC_BT2020_NCL; - break; - case VIDEO_CS_2100_HLG: - enc->context->color_primaries = AVCOL_PRI_BT2020; - enc->context->color_trc = AVCOL_TRC_ARIB_STD_B67; - enc->context->colorspace = AVCOL_SPC_BT2020_NCL; - } - - if (keyint_sec) - enc->context->gop_size = - keyint_sec * voi->fps_num / voi->fps_den; - - enc->height = enc->context->height; - const char *ffmpeg_opts = obs_data_get_string(settings, "ffmpeg_opts"); - struct obs_options opts = obs_parse_options(ffmpeg_opts); - - for (size_t i = 0; i < opts.count; i++) { - struct obs_option *opt = &opts.options[i]; - av_opt_set(enc->context->priv_data, opt->name, opt->value, 0); - } - - obs_free_options(opts); + ffmpeg_video_encoder_update(&enc->ffve, bitrate, keyint_sec, voi, &info, + ffmpeg_opts); info("settings:\n" "\tencoder: %s\n" @@ -250,69 +130,55 @@ static bool av1_update(struct av1_encoder *enc, obs_data_t *settings) "\twidth: %d\n" "\theight: %d\n" "\tffmpeg opts: %s\n", - enc->enc_name, rc, bitrate, cqp, enc->context->gop_size, preset, - enc->context->width, enc->context->height, ffmpeg_opts); + enc->ffve.enc_name, rc, bitrate, cqp, enc->ffve.context->gop_size, + preset, enc->ffve.context->width, enc->ffve.height, ffmpeg_opts); - return av1_init_codec(enc); + enc->ffve.context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + return ffmpeg_video_encoder_init_codec(&enc->ffve); } static void av1_destroy(void *data) { struct av1_encoder *enc = data; - if (enc->initialized) { - AVPacket pkt = {0}; - int r_pkt = 1; + ffmpeg_video_encoder_free(&enc->ffve); + da_free(enc->header); + bfree(enc); +} - /* flush remaining data */ - avcodec_send_frame(enc->context, NULL); +static void on_first_packet(void *data, AVPacket *pkt, struct darray *da) +{ + struct av1_encoder *enc = data; - while (r_pkt) { - if (avcodec_receive_packet(enc->context, &pkt) < 0) + if (enc->svtav1) { + da_copy_array(enc->header, enc->ffve.context->extradata, + enc->ffve.context->extradata_size); + } else { + for (int i = 0; i < pkt->side_data_elems; i++) { + AVPacketSideData *side_data = pkt->side_data + i; + if (side_data->type == AV_PKT_DATA_NEW_EXTRADATA) { + da_copy_array(enc->header, side_data->data, + side_data->size); break; - - if (r_pkt) - av_packet_unref(&pkt); + } } } - avcodec_free_context(&enc->context); - av_frame_unref(enc->vframe); - av_frame_free(&enc->vframe); - da_free(enc->buffer); - da_free(enc->header); - - bfree(enc); + darray_copy_array(1, da, pkt->data, pkt->size); } static void *av1_create_internal(obs_data_t *settings, obs_encoder_t *encoder, const char *enc_lib, const char *enc_name) { - struct av1_encoder *enc; + struct av1_encoder *enc = bzalloc(sizeof(*enc)); - enc = bzalloc(sizeof(*enc)); - enc->encoder = encoder; - enc->avcodec = avcodec_find_encoder_by_name(enc_lib); - enc->enc_name = enc_name; if (strcmp(enc_lib, "libsvtav1") == 0) enc->svtav1 = true; - enc->first_packet = true; - blog(LOG_INFO, "---------------------------------"); - - if (!enc->avcodec) { - obs_encoder_set_last_error(encoder, - "Couldn't find AV1 encoder"); - warn("Couldn't find AV1 encoder"); + if (!ffmpeg_video_encoder_init(&enc->ffve, enc, settings, encoder, + enc_lib, NULL, enc_name, NULL, + on_first_packet)) goto fail; - } - - enc->context = avcodec_alloc_context3(enc->avcodec); - if (!enc->context) { - warn("Failed to create codec context"); - goto fail; - } - if (!av1_update(enc, settings)) goto fail; @@ -333,132 +199,11 @@ static void *aom_av1_create(obs_data_t *settings, obs_encoder_t *encoder) return av1_create_internal(settings, encoder, "libaom-av1", "AOM AV1"); } -static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame, - int height, enum AVPixelFormat format) -{ - int h_chroma_shift, v_chroma_shift; - av_pix_fmt_get_chroma_sub_sample(format, &h_chroma_shift, - &v_chroma_shift); - for (int plane = 0; plane < MAX_AV_PLANES; plane++) { - if (!frame->data[plane]) - continue; - - int frame_rowsize = (int)frame->linesize[plane]; - int pic_rowsize = pic->linesize[plane]; - int bytes = frame_rowsize < pic_rowsize ? frame_rowsize - : pic_rowsize; - int plane_height = height >> (plane ? v_chroma_shift : 0); - - for (int y = 0; y < plane_height; y++) { - int pos_frame = y * frame_rowsize; - int pos_pic = y * pic_rowsize; - - memcpy(pic->data[plane] + pos_pic, - frame->data[plane] + pos_frame, bytes); - } - } -} - -#define SEC_TO_NSEC 1000000000LL -#define TIMEOUT_MAX_SEC 5 -#define TIMEOUT_MAX_NSEC (TIMEOUT_MAX_SEC * SEC_TO_NSEC) - static bool av1_encode(void *data, struct encoder_frame *frame, struct encoder_packet *packet, bool *received_packet) { struct av1_encoder *enc = data; - AVPacket av_pkt = {0}; - bool timeout = false; - int64_t cur_ts = (int64_t)os_gettime_ns(); - int got_packet; - int ret; - - if (!enc->start_ts) - enc->start_ts = cur_ts; - - copy_data(enc->vframe, frame, enc->height, enc->context->pix_fmt); - - enc->vframe->pts = frame->pts; - ret = avcodec_send_frame(enc->context, enc->vframe); - if (ret == 0) - ret = avcodec_receive_packet(enc->context, &av_pkt); - - got_packet = (ret == 0); - - if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) - ret = 0; - if (ret < 0) { - warn("av1_encode: Error encoding: %s", av_err2str(ret)); - return false; - } - - if (got_packet && av_pkt.size) { - if (enc->first_packet) { - if (enc->svtav1) { - da_copy_array(enc->header, - enc->context->extradata, - enc->context->extradata_size); - } else { - for (int i = 0; i < av_pkt.side_data_elems; - i++) { - AVPacketSideData *side_data = - av_pkt.side_data + i; - if (side_data->type == - AV_PKT_DATA_NEW_EXTRADATA) { - da_copy_array(enc->header, - side_data->data, - side_data->size); - break; - } - } - } - enc->first_packet = false; - } - da_copy_array(enc->buffer, av_pkt.data, av_pkt.size); - - packet->pts = av_pkt.pts; - packet->dts = av_pkt.dts; - packet->data = enc->buffer.array; - packet->size = enc->buffer.num; - packet->type = OBS_ENCODER_VIDEO; - packet->keyframe = !!(av_pkt.flags & AV_PKT_FLAG_KEY); - *received_packet = true; - - uint64_t recv_ts_nsec = - util_mul_div64((uint64_t)av_pkt.dts, - (uint64_t)SEC_TO_NSEC, - (uint64_t)enc->context->time_base.den) + - enc->start_ts; - -#if 0 - debug("cur: %lld, packet: %lld, diff: %lld", cur_ts, - recv_ts_nsec, cur_ts - recv_ts_nsec); -#endif - if (llabs(cur_ts - recv_ts_nsec) > TIMEOUT_MAX_NSEC) { - char timeout_str[16]; - snprintf(timeout_str, sizeof(timeout_str), "%d", - TIMEOUT_MAX_SEC); - - struct dstr error_text = {0}; - dstr_copy(&error_text, - obs_module_text("Encoder.Timeout")); - dstr_replace(&error_text, "%1", enc->enc_name); - dstr_replace(&error_text, "%2", timeout_str); - obs_encoder_set_last_error(enc->encoder, - error_text.array); - dstr_free(&error_text); - - error("Encoding queue duration surpassed %d " - "seconds, terminating encoder", - TIMEOUT_MAX_SEC); - timeout = true; - } - } else { - *received_packet = false; - } - - av_packet_unref(&av_pkt); - return !timeout; + return ffmpeg_video_encode(&enc->ffve, frame, packet, received_packet); } void av1_defaults(obs_data_t *settings) diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c index 0ee244de4..8c7beb7a9 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c @@ -15,55 +15,30 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include #include -#include -#include -#include -#include - -#include "obs-ffmpeg-formats.h" +#include "obs-ffmpeg-video-encoders.h" #define do_log(level, format, ...) \ blog(level, "[NVENC encoder: '%s'] " format, \ - obs_encoder_get_name(enc->encoder), ##__VA_ARGS__) + obs_encoder_get_name(enc->ffve.encoder), ##__VA_ARGS__) #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) struct nvenc_encoder { - obs_encoder_t *encoder; - - AVCodec *nvenc; - AVCodecContext *context; - - AVPacket *packet; - - AVFrame *vframe; - - DARRAY(uint8_t) buffer; - - uint8_t *header; - size_t header_size; - - uint8_t *sei; - size_t sei_size; - - int height; - bool first_packet; - bool initialized; + struct ffmpeg_video_encoder ffve; + DARRAY(uint8_t) header; + DARRAY(uint8_t) sei; }; +#define ENCODER_NAME "NVIDIA NVENC H.264" + static const char *nvenc_getname(void *unused) { UNUSED_PARAMETER(unused); - return "NVIDIA NVENC H.264"; + return ENCODER_NAME; } static inline bool valid_format(enum video_format format) @@ -77,7 +52,7 @@ static void nvenc_video_info(void *data, struct video_scale_info *info) struct nvenc_encoder *enc = data; enum video_format pref_format; - pref_format = obs_encoder_get_preferred_video_format(enc->encoder); + pref_format = obs_encoder_get_preferred_video_format(enc->ffve.encoder); if (!valid_format(pref_format)) { pref_format = valid_format(info->format) ? info->format @@ -89,90 +64,12 @@ static void nvenc_video_info(void *data, struct video_scale_info *info) static void set_psycho_aq(struct nvenc_encoder *enc, bool psycho_aq) { - av_opt_set_int(enc->context->priv_data, "spatial-aq", psycho_aq, 0); - av_opt_set_int(enc->context->priv_data, "temporal-aq", psycho_aq, 0); + av_opt_set_int(enc->ffve.context->priv_data, "spatial-aq", psycho_aq, + 0); + av_opt_set_int(enc->ffve.context->priv_data, "temporal-aq", psycho_aq, + 0); } -static bool nvenc_init_codec(struct nvenc_encoder *enc, bool psycho_aq) -{ - UNUSED_PARAMETER(psycho_aq); - - int ret; - - // avcodec_open2 will overwrite priv_data, we call this to get a - // local copy of the "gpu" setting for improved error messages. - int64_t gpu; - if (av_opt_get_int(enc->context->priv_data, "gpu", 0, &gpu) < 0) { - gpu = -1; - } - - ret = avcodec_open2(enc->context, enc->nvenc, NULL); - if (ret < 0) { - // if we were a fallback from jim-nvenc, there may already be a - // more useful error returned from that, so don't overwrite. - // this can be removed if / when ffmpeg fallback is removed. - if (!obs_encoder_get_last_error(enc->encoder)) { - struct dstr error_message = {0}; - - dstr_copy(&error_message, - obs_module_text("NVENC.Error")); - dstr_replace(&error_message, "%1", av_err2str(ret)); - dstr_cat(&error_message, "\r\n\r\n"); - - if (gpu > 0) { - // if a non-zero GPU failed, almost always - // user error. tell then to fix it. - char gpu_str[16]; - snprintf(gpu_str, sizeof(gpu_str) - 1, "%d", - (int)gpu); - gpu_str[sizeof(gpu_str) - 1] = 0; - - dstr_cat(&error_message, - obs_module_text("NVENC.BadGPUIndex")); - dstr_replace(&error_message, "%1", gpu_str); - } else if (ret == AVERROR_EXTERNAL) { - // special case for common NVENC error - dstr_cat(&error_message, - obs_module_text("NVENC.GenericError")); - } else { - dstr_cat(&error_message, - obs_module_text("NVENC.CheckDrivers")); - } - - obs_encoder_set_last_error(enc->encoder, - error_message.array); - dstr_free(&error_message); - } - warn("Failed to open NVENC codec: %s", av_err2str(ret)); - return false; - } - - enc->vframe = av_frame_alloc(); - if (!enc->vframe) { - warn("Failed to allocate video frame"); - return false; - } - - enc->vframe->format = enc->context->pix_fmt; - enc->vframe->width = enc->context->width; - enc->vframe->height = enc->context->height; - enc->vframe->colorspace = enc->context->colorspace; - enc->vframe->color_range = enc->context->color_range; - - ret = av_frame_get_buffer(enc->vframe, base_get_alignment()); - if (ret < 0) { - warn("Failed to allocate vframe: %s", av_err2str(ret)); - return false; - } - - enc->packet = av_packet_alloc(); - - enc->initialized = true; - return true; -} - -enum RC_MODE { RC_MODE_CBR, RC_MODE_VBR, RC_MODE_CQP, RC_MODE_LOSSLESS }; - static bool nvenc_update(struct nvenc_encoder *enc, obs_data_t *settings, bool psycho_aq) { @@ -186,7 +83,7 @@ static bool nvenc_update(struct nvenc_encoder *enc, obs_data_t *settings, bool cbr_override = obs_data_get_bool(settings, "cbr"); int bf = (int)obs_data_get_int(settings, "bf"); - video_t *video = obs_encoder_video(enc->encoder); + video_t *video = obs_encoder_video(enc->ffve.encoder); const struct video_output_info *voi = video_output_get_info(video); struct video_scale_info info; @@ -212,13 +109,13 @@ static bool nvenc_update(struct nvenc_encoder *enc, obs_data_t *settings, } nvenc_video_info(enc, &info); - av_opt_set_int(enc->context->priv_data, "cbr", false, 0); - av_opt_set(enc->context->priv_data, "profile", profile, 0); - av_opt_set(enc->context->priv_data, "preset", preset, 0); + av_opt_set_int(enc->ffve.context->priv_data, "cbr", false, 0); + av_opt_set(enc->ffve.context->priv_data, "profile", profile, 0); + av_opt_set(enc->ffve.context->priv_data, "preset", preset, 0); if (astrcmpi(rc, "cqp") == 0) { bitrate = 0; - enc->context->global_quality = cqp; + enc->ffve.context->global_quality = cqp; } else if (astrcmpi(rc, "lossless") == 0) { bitrate = 0; @@ -227,70 +124,26 @@ static bool nvenc_update(struct nvenc_encoder *enc, obs_data_t *settings, bool hp = (astrcmpi(preset, "hp") == 0 || astrcmpi(preset, "llhp") == 0); - av_opt_set(enc->context->priv_data, "preset", + av_opt_set(enc->ffve.context->priv_data, "preset", hp ? "losslesshp" : "lossless", 0); } else if (astrcmpi(rc, "vbr") != 0) { /* CBR by default */ - av_opt_set_int(enc->context->priv_data, "cbr", true, 0); + av_opt_set_int(enc->ffve.context->priv_data, "cbr", true, 0); const int64_t rate = bitrate * INT64_C(1000); - enc->context->rc_max_rate = rate; - enc->context->rc_min_rate = rate; + enc->ffve.context->rc_max_rate = rate; + enc->ffve.context->rc_min_rate = rate; cqp = 0; } - av_opt_set(enc->context->priv_data, "level", "auto", 0); - av_opt_set_int(enc->context->priv_data, "2pass", twopass, 0); - av_opt_set_int(enc->context->priv_data, "gpu", gpu, 0); + av_opt_set(enc->ffve.context->priv_data, "level", "auto", 0); + av_opt_set_int(enc->ffve.context->priv_data, "2pass", twopass, 0); + av_opt_set_int(enc->ffve.context->priv_data, "gpu", gpu, 0); set_psycho_aq(enc, psycho_aq); - const int rate = bitrate * 1000; - enc->context->bit_rate = rate; - enc->context->rc_buffer_size = rate; - enc->context->width = obs_encoder_get_width(enc->encoder); - enc->context->height = obs_encoder_get_height(enc->encoder); - enc->context->time_base = (AVRational){voi->fps_den, voi->fps_num}; - enc->context->pix_fmt = obs_to_ffmpeg_video_format(info.format); - enc->context->color_range = info.range == VIDEO_RANGE_FULL - ? AVCOL_RANGE_JPEG - : AVCOL_RANGE_MPEG; - enc->context->max_b_frames = bf; - - switch (info.colorspace) { - case VIDEO_CS_601: - enc->context->color_primaries = AVCOL_PRI_SMPTE170M; - enc->context->color_trc = AVCOL_TRC_SMPTE170M; - enc->context->colorspace = AVCOL_SPC_SMPTE170M; - break; - case VIDEO_CS_DEFAULT: - case VIDEO_CS_709: - enc->context->color_primaries = AVCOL_PRI_BT709; - enc->context->color_trc = AVCOL_TRC_BT709; - enc->context->colorspace = AVCOL_SPC_BT709; - break; - case VIDEO_CS_SRGB: - enc->context->color_primaries = AVCOL_PRI_BT709; - enc->context->color_trc = AVCOL_TRC_IEC61966_2_1; - enc->context->colorspace = AVCOL_SPC_BT709; - break; - case VIDEO_CS_2100_PQ: - enc->context->color_primaries = AVCOL_PRI_BT2020; - enc->context->color_trc = AVCOL_TRC_SMPTE2084; - enc->context->colorspace = AVCOL_SPC_BT2020_NCL; - break; - case VIDEO_CS_2100_HLG: - enc->context->color_primaries = AVCOL_PRI_BT2020; - enc->context->color_trc = AVCOL_TRC_ARIB_STD_B67; - enc->context->colorspace = AVCOL_SPC_BT2020_NCL; - } - - if (keyint_sec) - enc->context->gop_size = - keyint_sec * voi->fps_num / voi->fps_den; - else - enc->context->gop_size = 250; - - enc->height = enc->context->height; + const char *ffmpeg_opts = obs_data_get_string(settings, "ffmpeg_opts"); + ffmpeg_video_encoder_update(&enc->ffve, bitrate, keyint_sec, voi, &info, + ffmpeg_opts); info("settings:\n" "\trate_control: %s\n" @@ -305,12 +158,12 @@ static bool nvenc_update(struct nvenc_encoder *enc, obs_data_t *settings, "\tb-frames: %d\n" "\tpsycho-aq: %d\n" "\tGPU: %d\n", - rc, bitrate, cqp, enc->context->gop_size, preset, profile, - enc->context->width, enc->context->height, - twopass ? "true" : "false", enc->context->max_b_frames, psycho_aq, - gpu); + rc, bitrate, cqp, enc->ffve.context->gop_size, preset, profile, + enc->ffve.context->width, enc->ffve.height, + twopass ? "true" : "false", enc->ffve.context->max_b_frames, + psycho_aq, gpu); - return nvenc_init_codec(enc, psycho_aq); + return ffmpeg_video_encoder_init_codec(&enc->ffve); } static bool nvenc_reconfigure(void *data, obs_data_t *settings) @@ -324,81 +177,76 @@ static bool nvenc_reconfigure(void *data, obs_data_t *settings) bool vbr = astrcmpi(rc, "VBR") == 0; if (cbr || vbr) { const int64_t rate = bitrate * 1000; - enc->context->bit_rate = rate; - enc->context->rc_max_rate = rate; + enc->ffve.context->bit_rate = rate; + enc->ffve.context->rc_max_rate = rate; } #endif return true; } -static inline void flush_remaining_packets(struct nvenc_encoder *enc) -{ - int r_pkt = 1; - - while (r_pkt) { -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) - if (avcodec_receive_packet(enc->context, enc->packet) < 0) - break; -#else - if (avcodec_encode_video2(enc->context, enc->packet, NULL, - &r_pkt) < 0) - break; -#endif - - if (r_pkt) - av_packet_unref(enc->packet); - } -} - static void nvenc_destroy(void *data) { struct nvenc_encoder *enc = data; - - if (enc->initialized) - flush_remaining_packets(enc); - - av_packet_free(&enc->packet); - avcodec_free_context(&enc->context); - av_frame_unref(enc->vframe); - av_frame_free(&enc->vframe); - da_free(enc->buffer); - bfree(enc->header); - bfree(enc->sei); - + ffmpeg_video_encoder_free(&enc->ffve); + da_free(enc->header); + da_free(enc->sei); bfree(enc); } +static void on_init_error(void *data, int ret) +{ + struct nvenc_encoder *enc = data; + struct dstr error_message = {0}; + + int64_t gpu; + if (av_opt_get_int(enc->ffve.context->priv_data, "gpu", 0, &gpu) < 0) { + gpu = -1; + } + + dstr_copy(&error_message, obs_module_text("NVENC.Error")); + dstr_replace(&error_message, "%1", av_err2str(ret)); + dstr_cat(&error_message, "\r\n\r\n"); + + if (gpu > 0) { + // if a non-zero GPU failed, almost always + // user error. tell then to fix it. + char gpu_str[16]; + snprintf(gpu_str, sizeof(gpu_str) - 1, "%d", (int)gpu); + gpu_str[sizeof(gpu_str) - 1] = 0; + + dstr_cat(&error_message, obs_module_text("NVENC.BadGPUIndex")); + dstr_replace(&error_message, "%1", gpu_str); + } else if (ret == AVERROR_EXTERNAL) { + // special case for common NVENC error + dstr_cat(&error_message, obs_module_text("NVENC.GenericError")); + } else { + dstr_cat(&error_message, obs_module_text("NVENC.CheckDrivers")); + } + + obs_encoder_set_last_error(enc->ffve.encoder, error_message.array); + dstr_free(&error_message); +} + +static void on_first_packet(void *data, AVPacket *pkt, struct darray *da) +{ + struct nvenc_encoder *enc = data; + + darray_free(da); + obs_extract_avc_headers(pkt->data, pkt->size, (uint8_t **)&da->array, + &da->num, &enc->header.array, &enc->header.num, + &enc->sei.array, &enc->sei.num); + da->capacity = da->num; +} + static void *nvenc_create_internal(obs_data_t *settings, obs_encoder_t *encoder, bool psycho_aq) { - struct nvenc_encoder *enc; + struct nvenc_encoder *enc = bzalloc(sizeof(*enc)); -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100) - avcodec_register_all(); -#endif - - enc = bzalloc(sizeof(*enc)); - enc->encoder = encoder; - enc->nvenc = avcodec_find_encoder_by_name("h264_nvenc"); - if (!enc->nvenc) - enc->nvenc = avcodec_find_encoder_by_name("nvenc_h264"); - enc->first_packet = true; - - blog(LOG_INFO, "---------------------------------"); - - if (!enc->nvenc) { - obs_encoder_set_last_error(encoder, - "Couldn't find NVENC encoder"); - warn("Couldn't find encoder"); + if (!ffmpeg_video_encoder_init(&enc->ffve, enc, settings, encoder, + "h264_nvenc", "nvenc_h264", ENCODER_NAME, + on_init_error, on_first_packet)) goto fail; - } - - enc->context = avcodec_alloc_context3(enc->nvenc); - if (!enc->context) { - warn("Failed to create codec context"); - goto fail; - } - if (!nvenc_update(enc, settings, psycho_aq)) goto fail; @@ -423,92 +271,11 @@ static void *nvenc_create(obs_data_t *settings, obs_encoder_t *encoder) return enc; } -static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame, - int height, enum AVPixelFormat format) -{ - int h_chroma_shift, v_chroma_shift; - av_pix_fmt_get_chroma_sub_sample(format, &h_chroma_shift, - &v_chroma_shift); - for (int plane = 0; plane < MAX_AV_PLANES; plane++) { - if (!frame->data[plane]) - continue; - - int frame_rowsize = (int)frame->linesize[plane]; - int pic_rowsize = pic->linesize[plane]; - int bytes = frame_rowsize < pic_rowsize ? frame_rowsize - : pic_rowsize; - int plane_height = height >> (plane ? v_chroma_shift : 0); - - for (int y = 0; y < plane_height; y++) { - int pos_frame = y * frame_rowsize; - int pos_pic = y * pic_rowsize; - - memcpy(pic->data[plane] + pos_pic, - frame->data[plane] + pos_frame, bytes); - } - } -} - static bool nvenc_encode(void *data, struct encoder_frame *frame, struct encoder_packet *packet, bool *received_packet) { struct nvenc_encoder *enc = data; - int got_packet; - int ret; - - copy_data(enc->vframe, frame, enc->height, enc->context->pix_fmt); - - enc->vframe->pts = frame->pts; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) - ret = avcodec_send_frame(enc->context, enc->vframe); - if (ret == 0) - ret = avcodec_receive_packet(enc->context, enc->packet); - - got_packet = (ret == 0); - - if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) - ret = 0; -#else - ret = avcodec_encode_video2(enc->context, enc->packet, enc->vframe, - &got_packet); -#endif - if (ret < 0) { - warn("nvenc_encode: Error encoding: %s", av_err2str(ret)); - return false; - } - - if (got_packet && enc->packet->size) { - if (enc->first_packet) { - uint8_t *new_packet; - size_t size; - - enc->first_packet = false; - obs_extract_avc_headers(enc->packet->data, - enc->packet->size, &new_packet, - &size, &enc->header, - &enc->header_size, &enc->sei, - &enc->sei_size); - - da_copy_array(enc->buffer, new_packet, size); - bfree(new_packet); - } else { - da_copy_array(enc->buffer, enc->packet->data, - enc->packet->size); - } - - packet->pts = enc->packet->pts; - packet->dts = enc->packet->dts; - packet->data = enc->buffer.array; - packet->size = enc->buffer.num; - packet->type = OBS_ENCODER_VIDEO; - packet->keyframe = obs_avc_keyframe(packet->data, packet->size); - *received_packet = true; - } else { - *received_packet = false; - } - - av_packet_unref(enc->packet); - return true; + return ffmpeg_video_encode(&enc->ffve, frame, packet, received_packet); } void nvenc_defaults(obs_data_t *settings) @@ -651,8 +418,8 @@ static bool nvenc_extra_data(void *data, uint8_t **extra_data, size_t *size) { struct nvenc_encoder *enc = data; - *extra_data = enc->header; - *size = enc->header_size; + *extra_data = enc->header.array; + *size = enc->header.num; return true; } @@ -660,8 +427,8 @@ static bool nvenc_sei_data(void *data, uint8_t **extra_data, size_t *size) { struct nvenc_encoder *enc = data; - *extra_data = enc->sei; - *size = enc->sei_size; + *extra_data = enc->sei.array; + *size = enc->sei.num; return true; } diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.c b/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.c new file mode 100644 index 000000000..476e6309a --- /dev/null +++ b/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.c @@ -0,0 +1,294 @@ +#include "obs-ffmpeg-video-encoders.h" + +#define do_log(level, format, ...) \ + blog(level, "[%s encoder: '%s'] " format, enc->enc_name, \ + obs_encoder_get_name(enc->encoder), ##__VA_ARGS__) + +#define error(format, ...) do_log(LOG_ERROR, format, ##__VA_ARGS__) +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) +#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) + +bool ffmpeg_video_encoder_init_codec(struct ffmpeg_video_encoder *enc) +{ + int ret = avcodec_open2(enc->context, enc->avcodec, NULL); + if (ret < 0) { + if (!obs_encoder_get_last_error(enc->encoder)) { + if (enc->on_init_error) { + enc->on_init_error(enc->parent, ret); + + } else { + struct dstr error_message = {0}; + + dstr_copy(&error_message, + obs_module_text("Encoder.Error")); + dstr_replace(&error_message, "%1", + enc->enc_name); + dstr_replace(&error_message, "%2", + av_err2str(ret)); + dstr_cat(&error_message, "\r\n\r\n"); + + obs_encoder_set_last_error(enc->encoder, + error_message.array); + dstr_free(&error_message); + } + } + return false; + } + + enc->vframe = av_frame_alloc(); + if (!enc->vframe) { + warn("Failed to allocate video frame"); + return false; + } + + enc->vframe->format = enc->context->pix_fmt; + enc->vframe->width = enc->context->width; + enc->vframe->height = enc->context->height; + enc->vframe->colorspace = enc->context->colorspace; + enc->vframe->color_range = enc->context->color_range; + + ret = av_frame_get_buffer(enc->vframe, base_get_alignment()); + if (ret < 0) { + warn("Failed to allocate vframe: %s", av_err2str(ret)); + return false; + } + + enc->initialized = true; + return true; +} + +void ffmpeg_video_encoder_update(struct ffmpeg_video_encoder *enc, int bitrate, + int keyint_sec, + const struct video_output_info *voi, + const struct video_scale_info *info, + const char *ffmpeg_opts) +{ + const int rate = bitrate * 1000; + enc->context->bit_rate = rate; + enc->context->rc_buffer_size = rate; + enc->context->width = obs_encoder_get_width(enc->encoder); + enc->context->height = obs_encoder_get_height(enc->encoder); + enc->context->time_base = (AVRational){voi->fps_den, voi->fps_num}; + enc->context->pix_fmt = obs_to_ffmpeg_video_format(info->format); + enc->context->color_range = info->range == VIDEO_RANGE_FULL + ? AVCOL_RANGE_JPEG + : AVCOL_RANGE_MPEG; + + switch (info->colorspace) { + case VIDEO_CS_601: + enc->context->color_primaries = AVCOL_PRI_SMPTE170M; + enc->context->color_trc = AVCOL_TRC_SMPTE170M; + enc->context->colorspace = AVCOL_SPC_SMPTE170M; + break; + case VIDEO_CS_DEFAULT: + case VIDEO_CS_709: + enc->context->color_primaries = AVCOL_PRI_BT709; + enc->context->color_trc = AVCOL_TRC_BT709; + enc->context->colorspace = AVCOL_SPC_BT709; + break; + case VIDEO_CS_SRGB: + enc->context->color_primaries = AVCOL_PRI_BT709; + enc->context->color_trc = AVCOL_TRC_IEC61966_2_1; + enc->context->colorspace = AVCOL_SPC_BT709; + break; + case VIDEO_CS_2100_PQ: + enc->context->color_primaries = AVCOL_PRI_BT2020; + enc->context->color_trc = AVCOL_TRC_SMPTE2084; + enc->context->colorspace = AVCOL_SPC_BT2020_NCL; + break; + case VIDEO_CS_2100_HLG: + enc->context->color_primaries = AVCOL_PRI_BT2020; + enc->context->color_trc = AVCOL_TRC_ARIB_STD_B67; + enc->context->colorspace = AVCOL_SPC_BT2020_NCL; + } + + if (keyint_sec) + enc->context->gop_size = + keyint_sec * voi->fps_num / voi->fps_den; + + enc->height = enc->context->height; + + struct obs_options opts = obs_parse_options(ffmpeg_opts); + for (size_t i = 0; i < opts.count; i++) { + struct obs_option *opt = &opts.options[i]; + av_opt_set(enc->context->priv_data, opt->name, opt->value, 0); + } + obs_free_options(opts); +} + +void ffmpeg_video_encoder_free(struct ffmpeg_video_encoder *enc) +{ + if (enc->initialized) { + AVPacket pkt = {0}; + int r_pkt = 1; + + /* flush remaining data */ + avcodec_send_frame(enc->context, NULL); + + while (r_pkt) { + if (avcodec_receive_packet(enc->context, &pkt) < 0) + break; + + if (r_pkt) + av_packet_unref(&pkt); + } + } + + avcodec_free_context(&enc->context); + av_frame_unref(enc->vframe); + av_frame_free(&enc->vframe); + da_free(enc->buffer); +} + +bool ffmpeg_video_encoder_init(struct ffmpeg_video_encoder *enc, void *parent, + obs_data_t *settings, obs_encoder_t *encoder, + const char *enc_lib, const char *enc_lib2, + const char *enc_name, + init_error_cb on_init_error, + first_packet_cb on_first_packet) +{ + enc->encoder = encoder; + enc->parent = parent; + enc->avcodec = avcodec_find_encoder_by_name(enc_lib); + if (!enc->avcodec && enc_lib2) + enc->avcodec = avcodec_find_encoder_by_name(enc_lib2); + enc->enc_name = enc_name; + enc->on_init_error = on_init_error; + enc->on_first_packet = on_first_packet; + enc->first_packet = true; + + blog(LOG_INFO, "---------------------------------"); + + if (!enc->avcodec) { + struct dstr error_message; + dstr_printf(&error_message, "Couldn't find encoder: %s", + enc_lib); + obs_encoder_set_last_error(encoder, error_message.array); + dstr_free(&error_message); + + warn("Couldn't find encoder: '%s'", enc_lib); + return false; + } + + enc->context = avcodec_alloc_context3(enc->avcodec); + if (!enc->context) { + warn("Failed to create codec context"); + return false; + } + + return true; +} + +static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame, + int height, enum AVPixelFormat format) +{ + int h_chroma_shift, v_chroma_shift; + av_pix_fmt_get_chroma_sub_sample(format, &h_chroma_shift, + &v_chroma_shift); + for (int plane = 0; plane < MAX_AV_PLANES; plane++) { + if (!frame->data[plane]) + continue; + + int frame_rowsize = (int)frame->linesize[plane]; + int pic_rowsize = pic->linesize[plane]; + int bytes = frame_rowsize < pic_rowsize ? frame_rowsize + : pic_rowsize; + int plane_height = height >> (plane ? v_chroma_shift : 0); + + for (int y = 0; y < plane_height; y++) { + int pos_frame = y * frame_rowsize; + int pos_pic = y * pic_rowsize; + + memcpy(pic->data[plane] + pos_pic, + frame->data[plane] + pos_frame, bytes); + } + } +} + +#define SEC_TO_NSEC 1000000000LL +#define TIMEOUT_MAX_SEC 5 +#define TIMEOUT_MAX_NSEC (TIMEOUT_MAX_SEC * SEC_TO_NSEC) + +bool ffmpeg_video_encode(struct ffmpeg_video_encoder *enc, + struct encoder_frame *frame, + struct encoder_packet *packet, bool *received_packet) +{ + AVPacket av_pkt = {0}; + bool timeout = false; + int64_t cur_ts = (int64_t)os_gettime_ns(); + int got_packet; + int ret; + + if (!enc->start_ts) + enc->start_ts = cur_ts; + + copy_data(enc->vframe, frame, enc->height, enc->context->pix_fmt); + + enc->vframe->pts = frame->pts; + ret = avcodec_send_frame(enc->context, enc->vframe); + if (ret == 0) + ret = avcodec_receive_packet(enc->context, &av_pkt); + + got_packet = (ret == 0); + + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) + ret = 0; + if (ret < 0) { + warn("%s: Error encoding: %s", __func__, av_err2str(ret)); + return false; + } + + if (got_packet && av_pkt.size) { + if (enc->on_first_packet && enc->first_packet) { + enc->on_first_packet(enc->parent, &av_pkt, + &enc->buffer.da); + enc->first_packet = false; + } else { + da_copy_array(enc->buffer, av_pkt.data, av_pkt.size); + } + + packet->pts = av_pkt.pts; + packet->dts = av_pkt.dts; + packet->data = enc->buffer.array; + packet->size = enc->buffer.num; + packet->type = OBS_ENCODER_VIDEO; + packet->keyframe = !!(av_pkt.flags & AV_PKT_FLAG_KEY); + *received_packet = true; + + uint64_t recv_ts_nsec = + util_mul_div64((uint64_t)av_pkt.pts, + (uint64_t)SEC_TO_NSEC, + (uint64_t)enc->context->time_base.den) + + enc->start_ts; + +#if 0 + debug("cur: %lld, packet: %lld, diff: %lld", cur_ts, + recv_ts_nsec, cur_ts - recv_ts_nsec); +#endif + if (llabs(cur_ts - recv_ts_nsec) > TIMEOUT_MAX_NSEC) { + char timeout_str[16]; + snprintf(timeout_str, sizeof(timeout_str), "%d", + TIMEOUT_MAX_SEC); + + struct dstr error_text = {0}; + dstr_copy(&error_text, + obs_module_text("Encoder.Timeout")); + dstr_replace(&error_text, "%1", enc->enc_name); + dstr_replace(&error_text, "%2", timeout_str); + obs_encoder_set_last_error(enc->encoder, + error_text.array); + dstr_free(&error_text); + + error("Encoding queue duration surpassed %d " + "seconds, terminating encoder", + TIMEOUT_MAX_SEC); + timeout = true; + } + } else { + *received_packet = false; + } + + av_packet_unref(&av_pkt); + return !timeout; +} diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.h b/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.h new file mode 100644 index 000000000..8a4e293e2 --- /dev/null +++ b/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "obs-ffmpeg-formats.h" + +typedef void (*init_error_cb)(void *data, int ret); +typedef void (*first_packet_cb)(void *data, AVPacket *pkt, struct darray *out); + +struct ffmpeg_video_encoder { + obs_encoder_t *encoder; + const char *enc_name; + + AVCodec *avcodec; + AVCodecContext *context; + int64_t start_ts; + bool first_packet; + + AVFrame *vframe; + + DARRAY(uint8_t) buffer; + + int height; + bool initialized; + + void *parent; + init_error_cb on_init_error; + first_packet_cb on_first_packet; +}; + +extern bool ffmpeg_video_encoder_init(struct ffmpeg_video_encoder *enc, + void *parent, obs_data_t *settings, + obs_encoder_t *encoder, + const char *enc_lib, const char *enc_lib2, + const char *enc_name, + init_error_cb on_init_error, + first_packet_cb on_first_packet); +extern void ffmpeg_video_encoder_free(struct ffmpeg_video_encoder *enc); +extern bool ffmpeg_video_encoder_init_codec(struct ffmpeg_video_encoder *enc); +extern void ffmpeg_video_encoder_update(struct ffmpeg_video_encoder *enc, + int bitrate, int keyint_sec, + const struct video_output_info *voi, + const struct video_scale_info *info, + const char *ffmpeg_opts); +extern bool ffmpeg_video_encode(struct ffmpeg_video_encoder *enc, + struct encoder_frame *frame, + struct encoder_packet *packet, + bool *received_packet);