From: Steinar H. Gunderson Date: Sun, 26 Sep 2021 18:36:48 +0000 (+0200) Subject: Support sending a separate x264 encode to disk. X-Git-Tag: 2.1.0~11 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=8202dbe236c5e206989c383004f9dba116ea12bd Support sending a separate x264 encode to disk. This is useful for machines that don't have Quick Sync, but where you want to have an archival copy on disk in higher quality than what you streamed out. --- diff --git a/nageru/flags.cpp b/nageru/flags.cpp index 769263b..ed35062 100644 --- a/nageru/flags.cpp +++ b/nageru/flags.cpp @@ -24,6 +24,7 @@ enum LongOption { OPTION_HTTP_UNCOMPRESSED_VIDEO, OPTION_HTTP_X264_VIDEO, OPTION_RECORD_X264_VIDEO, + OPTION_SEPARATE_X264_DISK_ENCODE, OPTION_X264_PRESET, OPTION_X264_TUNE, OPTION_X264_SPEEDCONTROL, @@ -33,6 +34,11 @@ enum LongOption { OPTION_X264_VBV_BUFSIZE, OPTION_X264_VBV_MAX_BITRATE, OPTION_X264_PARAM, + OPTION_X264_SEPARATE_DISK_PRESET, + OPTION_X264_SEPARATE_DISK_TUNE, + OPTION_X264_SEPARATE_DISK_BITRATE, + OPTION_X264_SEPARATE_DISK_CRF, + OPTION_X264_SEPARATE_DISK_PARAM, OPTION_HTTP_MUX, OPTION_HTTP_COARSE_TIMEBASE, OPTION_HTTP_AUDIO_CODEC, @@ -154,6 +160,8 @@ void usage(Program program) fprintf(stderr, " --http-x264-video send x264-compressed video to HTTP clients\n"); fprintf(stderr, " --record-x264-video store x264-compressed video to disk (implies --http-x264-video,\n"); fprintf(stderr, " removes the need for working VA-API encoding)\n"); + fprintf(stderr, " --separate-x264-disk-encode run a different x264 encoder for disk recording\n"); + fprintf(stderr, " (implies --record-x264-video)\n"); } fprintf(stderr, " --x264-preset x264 quality preset (default " X264_DEFAULT_PRESET ")\n"); fprintf(stderr, " --x264-tune x264 tuning (default " X264_DEFAULT_TUNE ", can be blank)\n"); @@ -167,6 +175,15 @@ void usage(Program program) fprintf(stderr, " --x264-vbv-max-bitrate x264 local max bitrate (in kilobit/sec per --vbv-bufsize,\n"); fprintf(stderr, " 0 = no limit, default: same as --x264-bitrate, i.e., CBR)\n"); fprintf(stderr, " --x264-param=NAME[,VALUE] set any x264 parameter, for fine tuning\n"); + if (program == PROGRAM_NAGERU) { + fprintf(stderr, " --x264-separate-disk-preset x264 quality preset (default " X264_DEFAULT_PRESET ")\n"); + fprintf(stderr, " --x264-separate-disk-tune x264 tuning (default " X264_DEFAULT_TUNE ", can be blank)\n"); + fprintf(stderr, " --x264-separate-disk-bitrate x264 bitrate (in kilobit/sec, default %d)\n", + DEFAULT_X264_OUTPUT_BIT_RATE); + fprintf(stderr, " --x264-separate-disk-crf=VALUE quality-based VBR (-12 to 51), \n"); + fprintf(stderr, " incompatible with --x264-separate-disk-bitrate\n"); + fprintf(stderr, " --x264-separate-disk-param=NAME[,VALUE] set any x264 parameter, for fine tuning\n"); + } fprintf(stderr, " --http-mux=NAME mux to use for HTTP streams (default " DEFAULT_STREAM_MUX_NAME ")\n"); fprintf(stderr, " --http-audio-codec=NAME audio codec to use for HTTP streams\n"); fprintf(stderr, " (default is to use the same as for the recording)\n"); @@ -254,6 +271,7 @@ void parse_flags(Program program, int argc, char * const argv[]) { "http-uncompressed-video", no_argument, 0, OPTION_HTTP_UNCOMPRESSED_VIDEO }, { "http-x264-video", no_argument, 0, OPTION_HTTP_X264_VIDEO }, { "record-x264-video", no_argument, 0, OPTION_RECORD_X264_VIDEO }, + { "separate-x264-disk-encode", no_argument, 0, OPTION_SEPARATE_X264_DISK_ENCODE }, { "x264-preset", required_argument, 0, OPTION_X264_PRESET }, { "x264-tune", required_argument, 0, OPTION_X264_TUNE }, { "x264-speedcontrol", no_argument, 0, OPTION_X264_SPEEDCONTROL }, @@ -263,6 +281,11 @@ void parse_flags(Program program, int argc, char * const argv[]) { "x264-vbv-bufsize", required_argument, 0, OPTION_X264_VBV_BUFSIZE }, { "x264-vbv-max-bitrate", required_argument, 0, OPTION_X264_VBV_MAX_BITRATE }, { "x264-param", required_argument, 0, OPTION_X264_PARAM }, + { "x264-separate-disk-preset", required_argument, 0, OPTION_X264_SEPARATE_DISK_PRESET }, + { "x264-separate-disk-tune", required_argument, 0, OPTION_X264_SEPARATE_DISK_TUNE }, + { "x264-separate-disk-bitrate", required_argument, 0, OPTION_X264_SEPARATE_DISK_BITRATE }, + { "x264-separate-disk-crf", required_argument, 0, OPTION_X264_SEPARATE_DISK_CRF }, + { "x264-separate-disk-param", required_argument, 0, OPTION_X264_SEPARATE_DISK_PARAM }, { "http-mux", required_argument, 0, OPTION_HTTP_MUX }, { "http-audio-codec", required_argument, 0, OPTION_HTTP_AUDIO_CODEC }, { "http-audio-bitrate", required_argument, 0, OPTION_HTTP_AUDIO_BITRATE }, @@ -413,6 +436,11 @@ void parse_flags(Program program, int argc, char * const argv[]) global_flags.x264_video_to_disk = true; global_flags.x264_video_to_http = true; break; + case OPTION_SEPARATE_X264_DISK_ENCODE: + global_flags.x264_video_to_disk = true; + global_flags.x264_video_to_http = true; + global_flags.x264_separate_disk_encode = true; + break; case OPTION_X264_PRESET: global_flags.x264_preset = optarg; break; @@ -440,6 +468,21 @@ void parse_flags(Program program, int argc, char * const argv[]) case OPTION_X264_PARAM: global_flags.x264_extra_param.push_back(optarg); break; + case OPTION_X264_SEPARATE_DISK_PRESET: + global_flags.x264_separate_disk_preset = optarg; + break; + case OPTION_X264_SEPARATE_DISK_TUNE: + global_flags.x264_separate_disk_tune = optarg; + break; + case OPTION_X264_SEPARATE_DISK_BITRATE: + global_flags.x264_separate_disk_bitrate = atoi(optarg); + break; + case OPTION_X264_SEPARATE_DISK_CRF: + global_flags.x264_separate_disk_crf = atof(optarg); + break; + case OPTION_X264_SEPARATE_DISK_PARAM: + global_flags.x264_separate_disk_extra_param.push_back(optarg); + break; case OPTION_FLAT_AUDIO: // If --flat-audio is given, turn off everything that messes with the sound, // except the final makeup gain. @@ -630,6 +673,9 @@ void parse_flags(Program program, int argc, char * const argv[]) } else if (global_flags.x264_preset.empty()) { global_flags.x264_preset = X264_DEFAULT_PRESET; } + if (global_flags.x264_separate_disk_preset.empty()) { + global_flags.x264_separate_disk_preset = X264_DEFAULT_PRESET; + } if (!theme_dirs.empty()) { global_flags.theme_dirs = theme_dirs; } @@ -713,6 +759,15 @@ void parse_flags(Program program, int argc, char * const argv[]) global_flags.x264_bitrate = DEFAULT_X264_OUTPUT_BIT_RATE; } + if (!isinf(global_flags.x264_separate_disk_crf)) { // CRF mode is selected. + if (global_flags.x264_separate_disk_bitrate != -1) { + fprintf(stderr, "ERROR: --x264-separate-disk-bitrate and --x264-separate-disk-crf are mutually incompatible.\n"); + exit(1); + } + } else if (global_flags.x264_separate_disk_bitrate == -1) { + global_flags.x264_separate_disk_bitrate = DEFAULT_X264_OUTPUT_BIT_RATE; + } + if (!card_to_mjpeg_stream_export_set) { // Fill in the default mapping (export all cards, in order). for (unsigned card_idx = 0; card_idx < unsigned(global_flags.max_num_cards); ++card_idx) { diff --git a/nageru/flags.h b/nageru/flags.h index dc9c585..9b9976c 100644 --- a/nageru/flags.h +++ b/nageru/flags.h @@ -19,6 +19,7 @@ struct Flags { bool uncompressed_video_to_http = false; bool x264_video_to_http = false; bool x264_video_to_disk = false; // Disables Quick Sync entirely. Implies x264_video_to_http == true. + bool x264_separate_disk_encode = false; // Disables Quick Sync entirely. Implies x264_video_to_disk == true. std::vector theme_dirs { ".", PREFIX "/share/nageru" }; std::string recording_dir = "."; std::string theme_filename = "theme.lua"; @@ -42,6 +43,13 @@ struct Flags { int x264_vbv_max_bitrate = -1; // In kilobits. 0 = no limit, -1 = same as (CBR). int x264_vbv_buffer_size = -1; // In kilobits. 0 = one-frame VBV, -1 = same as (one-second VBV). std::vector x264_extra_param; // In “key[,value]” format. + + std::string x264_separate_disk_preset; // Empty will be overridden by X264_DEFAULT_PRESET, unless speedcontrol is set. + std::string x264_separate_disk_tune = X264_DEFAULT_TUNE; + int x264_separate_disk_bitrate = -1; + float x264_separate_disk_crf = HUGE_VAL; + std::vector x264_separate_disk_extra_param; // In “key[,value]” format. + std::string v4l_output_device; // Empty if none. bool enable_alsa_output = true; std::map default_stream_mapping; diff --git a/nageru/kaeru.cpp b/nageru/kaeru.cpp index b1b08f7..1a63bb0 100644 --- a/nageru/kaeru.cpp +++ b/nageru/kaeru.cpp @@ -230,7 +230,7 @@ int main(int argc, char *argv[]) audio_encoder.reset(new AudioEncoder(global_flags.stream_audio_codec_name, global_flags.stream_audio_codec_bitrate, oformat)); } - unique_ptr x264_encoder(new X264Encoder(oformat)); + unique_ptr x264_encoder(new X264Encoder(oformat, /*use_separate_disk_params=*/false)); unique_ptr http_mux = create_mux(&httpd, oformat, x264_encoder.get(), audio_encoder.get()); if (global_flags.transcode_audio) { audio_encoder->add_mux(http_mux.get()); diff --git a/nageru/quicksync_encoder.cpp b/nageru/quicksync_encoder.cpp index 992bf8f..eb144ce 100644 --- a/nageru/quicksync_encoder.cpp +++ b/nageru/quicksync_encoder.cpp @@ -1435,8 +1435,8 @@ void QuickSyncEncoderImpl::release_gl_resources() has_released_gl_resources = true; } -QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder, DiskSpaceEstimator *disk_space_estimator) - : current_storage_frame(0), resource_pool(resource_pool), surface(surface), x264_encoder(x264_encoder), frame_width(width), frame_height(height), disk_space_estimator(disk_space_estimator) +QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *http_encoder, X264Encoder *disk_encoder, DiskSpaceEstimator *disk_space_estimator) + : current_storage_frame(0), resource_pool(resource_pool), surface(surface), x264_http_encoder(http_encoder), x264_disk_encoder(disk_encoder), frame_width(width), frame_height(height), disk_space_estimator(disk_space_estimator) { file_audio_encoder.reset(new AudioEncoder(AUDIO_OUTPUT_CODEC_NAME, DEFAULT_AUDIO_OUTPUT_BIT_RATE, oformat)); open_output_file(filename); @@ -1448,9 +1448,14 @@ QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, Resource //print_input(); if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) { - assert(x264_encoder != nullptr); + assert(x264_http_encoder != nullptr); } else { - assert(x264_encoder == nullptr); + assert(x264_http_encoder == nullptr); + } + if (global_flags.x264_separate_disk_encode) { + assert(x264_disk_encoder != nullptr); + } else { + assert(x264_disk_encoder == nullptr); } enable_zerocopy_if_possible(); @@ -1731,7 +1736,7 @@ void QuickSyncEncoderImpl::open_output_file(const std::string &filename) string video_extradata; // FIXME: See other comment about global headers. if (global_flags.x264_video_to_disk) { - video_extradata = x264_encoder->get_global_headers(); + video_extradata = x264_disk_encoder->get_global_headers(); } current_file_mux_metrics.reset(); @@ -1747,7 +1752,7 @@ void QuickSyncEncoderImpl::open_output_file(const std::string &filename) metric_current_file_start_time_seconds = get_timestamp_for_metrics(); if (global_flags.x264_video_to_disk) { - x264_encoder->add_mux(file_mux.get()); + x264_disk_encoder->add_mux(file_mux.get()); } } @@ -1910,7 +1915,10 @@ void QuickSyncEncoderImpl::pass_frame(QuickSyncEncoderImpl::PendingFrame frame, if (global_flags.uncompressed_video_to_http) { add_packet_for_uncompressed_frame(pts, duration, data); } else if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) { - x264_encoder->add_frame(pts, duration, frame.ycbcr_coefficients, data, received_ts); + x264_http_encoder->add_frame(pts, duration, frame.ycbcr_coefficients, data, received_ts); + } + if (global_flags.x264_separate_disk_encode) { + x264_disk_encoder->add_frame(pts, duration, frame.ycbcr_coefficients, data, received_ts); } if (v4l_output != nullptr) { @@ -2012,8 +2020,8 @@ void QuickSyncEncoderImpl::encode_frame(QuickSyncEncoderImpl::PendingFrame frame } // Proxy object. -QuickSyncEncoder::QuickSyncEncoder(const std::string &filename, ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder, DiskSpaceEstimator *disk_space_estimator) - : impl(new QuickSyncEncoderImpl(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder, disk_space_estimator)) {} +QuickSyncEncoder::QuickSyncEncoder(const std::string &filename, ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *http_encoder, X264Encoder *disk_encoder, DiskSpaceEstimator *disk_space_estimator) + : impl(new QuickSyncEncoderImpl(filename, resource_pool, surface, va_display, width, height, oformat, http_encoder, disk_encoder, disk_space_estimator)) {} // Must be defined here because unique_ptr<> destructor needs to know the impl. QuickSyncEncoder::~QuickSyncEncoder() {} diff --git a/nageru/quicksync_encoder.h b/nageru/quicksync_encoder.h index 4f71f90..e4594de 100644 --- a/nageru/quicksync_encoder.h +++ b/nageru/quicksync_encoder.h @@ -63,7 +63,7 @@ class ResourcePool; // This class is _not_ thread-safe, except where mentioned. class QuickSyncEncoder { public: - QuickSyncEncoder(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder, DiskSpaceEstimator *disk_space_estimator); + QuickSyncEncoder(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *http_encoder, X264Encoder *disk_encoder, DiskSpaceEstimator *disk_space_estimator); ~QuickSyncEncoder(); void set_stream_mux(Mux *mux); // Does not take ownership. Must be called unless x264 is used for the stream. diff --git a/nageru/quicksync_encoder_impl.h b/nageru/quicksync_encoder_impl.h index 53f88ec..c4a99fb 100644 --- a/nageru/quicksync_encoder_impl.h +++ b/nageru/quicksync_encoder_impl.h @@ -43,7 +43,7 @@ class X264Encoder; class QuickSyncEncoderImpl { public: - QuickSyncEncoderImpl(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder, DiskSpaceEstimator *disk_space_estimator); + QuickSyncEncoderImpl(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *http_encoder, X264Encoder *disk_encoder, DiskSpaceEstimator *disk_space_estimator); ~QuickSyncEncoderImpl(); void add_audio(int64_t pts, std::vector audio); bool is_zerocopy() const; @@ -171,7 +171,8 @@ private: std::mutex file_audio_encoder_mutex; std::unique_ptr file_audio_encoder; - X264Encoder *x264_encoder; // nullptr if not using x264. + X264Encoder *x264_http_encoder; // nullptr if not using x264. + X264Encoder *x264_disk_encoder; std::unique_ptr v4l_output; // nullptr if not using V4L2 output. Mux* stream_mux = nullptr; // To HTTP. diff --git a/nageru/video_encoder.cpp b/nageru/video_encoder.cpp index 2ac606e..8138e76 100644 --- a/nageru/video_encoder.cpp +++ b/nageru/video_encoder.cpp @@ -59,11 +59,17 @@ VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const stream_audio_encoder.reset(new AudioEncoder(global_flags.stream_audio_codec_name, global_flags.stream_audio_codec_bitrate, oformat)); } if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) { - x264_encoder.reset(new X264Encoder(oformat)); + x264_encoder.reset(new X264Encoder(oformat, /*use_separate_disk_params=*/false)); + } + X264Encoder *http_encoder = x264_encoder.get(); + X264Encoder *disk_encoder = x264_encoder.get(); + if (global_flags.x264_separate_disk_encode) { + x264_disk_encoder.reset(new X264Encoder(oformat, /*use_separate_disk_params=*/true)); + disk_encoder = x264_disk_encoder.get(); } string filename = generate_local_dump_filename(/*frame=*/0); - quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder.get(), disk_space_estimator)); + quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, http_encoder, disk_encoder, disk_space_estimator)); open_output_stream(); stream_audio_encoder->add_mux(stream_mux.get()); @@ -77,6 +83,7 @@ VideoEncoder::~VideoEncoder() { quicksync_encoder->shutdown(); x264_encoder.reset(nullptr); + x264_disk_encoder.reset(nullptr); quicksync_encoder->close_file(); quicksync_encoder.reset(nullptr); while (quicksync_encoders_in_shutdown.load() > 0) { @@ -100,12 +107,17 @@ void VideoEncoder::do_cut(int frame) lock_guard lock1(qs_mu, adopt_lock), lock2(qs_audio_mu, adopt_lock); QuickSyncEncoder *old_encoder = quicksync_encoder.release(); // When we go C++14, we can use move capture instead. X264Encoder *old_x264_encoder = nullptr; + X264Encoder *old_x264_disk_encoder = nullptr; if (global_flags.x264_video_to_disk) { old_x264_encoder = x264_encoder.release(); } - thread([old_encoder, old_x264_encoder, this]{ + if (global_flags.x264_separate_disk_encode) { + old_x264_disk_encoder = x264_disk_encoder.release(); + } + thread([old_encoder, old_x264_encoder, old_x264_disk_encoder, this]{ old_encoder->shutdown(); delete old_x264_encoder; + delete old_x264_disk_encoder; old_encoder->close_file(); stream_mux->unplug(); @@ -116,7 +128,8 @@ void VideoEncoder::do_cut(int frame) }).detach(); if (global_flags.x264_video_to_disk) { - x264_encoder.reset(new X264Encoder(oformat)); + x264_encoder.reset(new X264Encoder(oformat, /*use_separate_disk_params=*/false)); + assert(global_flags.x264_video_to_http); if (global_flags.x264_video_to_http) { x264_encoder->add_mux(stream_mux.get()); } @@ -124,8 +137,14 @@ void VideoEncoder::do_cut(int frame) x264_encoder->change_bitrate(overriding_bitrate); } } + X264Encoder *http_encoder = x264_encoder.get(); + X264Encoder *disk_encoder = x264_encoder.get(); + if (global_flags.x264_separate_disk_encode) { + x264_disk_encoder.reset(new X264Encoder(oformat, /*use_separate_disk_params=*/true)); + disk_encoder = x264_disk_encoder.get(); + } - quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder.get(), disk_space_estimator)); + quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, http_encoder, disk_encoder, disk_space_estimator)); quicksync_encoder->set_stream_mux(stream_mux.get()); } diff --git a/nageru/video_encoder.h b/nageru/video_encoder.h index aed485b..e0a926b 100644 --- a/nageru/video_encoder.h +++ b/nageru/video_encoder.h @@ -93,6 +93,7 @@ private: std::unique_ptr stream_mux; // To HTTP. std::unique_ptr stream_audio_encoder; std::unique_ptr x264_encoder; // nullptr if not using x264. + std::unique_ptr x264_disk_encoder; // nullptr if not using x264, or if not having separate disk encodes. std::string stream_mux_header; MuxMetrics stream_mux_metrics; diff --git a/nageru/x264_encoder.cpp b/nageru/x264_encoder.cpp index d938393..23a1359 100644 --- a/nageru/x264_encoder.cpp +++ b/nageru/x264_encoder.cpp @@ -43,7 +43,17 @@ atomic metric_x264_output_frames_p{0}; atomic metric_x264_output_frames_b{0}; Histogram metric_x264_crf; LatencyHistogram x264_latency_histogram; -once_flag x264_metrics_inited; + +atomic metric_x264_disk_queued_frames{0}; +atomic metric_x264_disk_max_queued_frames{X264_QUEUE_LENGTH}; +atomic metric_x264_disk_dropped_frames{0}; +atomic metric_x264_disk_output_frames_i{0}; +atomic metric_x264_disk_output_frames_p{0}; +atomic metric_x264_disk_output_frames_b{0}; +Histogram metric_x264_disk_crf; +LatencyHistogram x264_disk_latency_histogram; + +once_flag x264_metrics_inited, x264_disk_metrics_inited; void update_vbv_settings(x264_param_t *param) { @@ -64,22 +74,38 @@ void update_vbv_settings(x264_param_t *param) } // namespace -X264Encoder::X264Encoder(AVOutputFormat *oformat) +X264Encoder::X264Encoder(AVOutputFormat *oformat, bool use_separate_disk_params) : wants_global_headers(oformat->flags & AVFMT_GLOBALHEADER), + use_separate_disk_params(use_separate_disk_params), dyn(load_x264_for_bit_depth(global_flags.x264_bit_depth)) { - call_once(x264_metrics_inited, [](){ - global_metrics.add("x264_queued_frames", &metric_x264_queued_frames, Metrics::TYPE_GAUGE); - global_metrics.add("x264_max_queued_frames", &metric_x264_max_queued_frames, Metrics::TYPE_GAUGE); - global_metrics.add("x264_dropped_frames", &metric_x264_dropped_frames); - global_metrics.add("x264_output_frames", {{ "type", "i" }}, &metric_x264_output_frames_i); - global_metrics.add("x264_output_frames", {{ "type", "p" }}, &metric_x264_output_frames_p); - global_metrics.add("x264_output_frames", {{ "type", "b" }}, &metric_x264_output_frames_b); - - metric_x264_crf.init_uniform(50); - global_metrics.add("x264_crf", &metric_x264_crf); - x264_latency_histogram.init("x264"); - }); + if (use_separate_disk_params) { + call_once(x264_disk_metrics_inited, []{ + global_metrics.add("x264_queued_frames", {{ "encode", "separate_disk" }}, &metric_x264_disk_queued_frames, Metrics::TYPE_GAUGE); + global_metrics.add("x264_max_queued_frames", {{ "encode", "separate_disk" }}, &metric_x264_disk_max_queued_frames, Metrics::TYPE_GAUGE); + global_metrics.add("x264_dropped_frames", {{ "encode", "separate_disk" }}, &metric_x264_disk_dropped_frames); + global_metrics.add("x264_output_frames", {{ "encode", "separate_disk" }, { "type", "i" }}, &metric_x264_disk_output_frames_i); + global_metrics.add("x264_output_frames", {{ "encode", "separate_disk" }, { "type", "p" }}, &metric_x264_disk_output_frames_p); + global_metrics.add("x264_output_frames", {{ "encode", "separate_disk" }, { "type", "b" }}, &metric_x264_disk_output_frames_b); + + metric_x264_disk_crf.init_uniform(50); + global_metrics.add("x264_crf", {{ "encode", "separate_disk" }}, &metric_x264_disk_crf); + x264_disk_latency_histogram.init("x264_disk"); + }); + } else { + call_once(use_separate_disk_params ? x264_disk_metrics_inited : x264_metrics_inited, []{ + global_metrics.add("x264_queued_frames", {{ "encode", "regular" }}, &metric_x264_queued_frames, Metrics::TYPE_GAUGE); + global_metrics.add("x264_max_queued_frames", {{ "encode", "regular" }}, &metric_x264_max_queued_frames, Metrics::TYPE_GAUGE); + global_metrics.add("x264_dropped_frames", {{ "encode", "regular" }}, &metric_x264_dropped_frames); + global_metrics.add("x264_output_frames", {{ "encode", "regular" }, { "type", "i" }}, &metric_x264_output_frames_i); + global_metrics.add("x264_output_frames", {{ "encode", "regular" }, { "type", "p" }}, &metric_x264_output_frames_p); + global_metrics.add("x264_output_frames", {{ "encode", "regular" }, { "type", "b" }}, &metric_x264_output_frames_b); + + metric_x264_crf.init_uniform(50); + global_metrics.add("x264_crf", {{ "encode", "regular" }}, &metric_x264_crf); + x264_latency_histogram.init("x264"); + }); + } size_t bytes_per_pixel = global_flags.x264_bit_depth > 8 ? 2 : 1; frame_pool.reset(new uint8_t[global_flags.width * global_flags.height * 2 * bytes_per_pixel * X264_QUEUE_LENGTH]); @@ -112,8 +138,13 @@ void X264Encoder::add_frame(int64_t pts, int64_t duration, YCbCrLumaCoefficients { lock_guard lock(mu); if (free_frames.empty()) { - fprintf(stderr, "WARNING: x264 queue full, dropping frame with pts %" PRId64 "\n", pts); - ++metric_x264_dropped_frames; + if (use_separate_disk_params) { + fprintf(stderr, "WARNING: x264 queue full (disk encoder), dropping frame with pts %" PRId64 "\n", pts); + ++metric_x264_disk_dropped_frames; + } else { + fprintf(stderr, "WARNING: x264 queue full, dropping frame with pts %" PRId64 "\n", pts); + ++metric_x264_dropped_frames; + } return; } @@ -128,14 +159,22 @@ void X264Encoder::add_frame(int64_t pts, int64_t duration, YCbCrLumaCoefficients lock_guard lock(mu); queued_frames.push(qf); queued_frames_nonempty.notify_all(); - metric_x264_queued_frames = queued_frames.size(); + if (use_separate_disk_params) { + metric_x264_disk_queued_frames = queued_frames.size(); + } else { + metric_x264_queued_frames = queued_frames.size(); + } } } void X264Encoder::init_x264() { x264_param_t param; - dyn.x264_param_default_preset(¶m, global_flags.x264_preset.c_str(), global_flags.x264_tune.c_str()); + if (use_separate_disk_params) { + dyn.x264_param_default_preset(¶m, global_flags.x264_separate_disk_preset.c_str(), global_flags.x264_separate_disk_tune.c_str()); + } else { + dyn.x264_param_default_preset(¶m, global_flags.x264_preset.c_str(), global_flags.x264_tune.c_str()); + } param.i_width = global_flags.width; param.i_height = global_flags.height; @@ -147,7 +186,7 @@ void X264Encoder::init_x264() param.i_timebase_num = 1; param.i_timebase_den = TIMEBASE; param.i_keyint_max = 50; // About one second. - if (global_flags.x264_speedcontrol) { + if (!use_separate_disk_params && global_flags.x264_speedcontrol) { param.i_frame_reference = 16; // Because speedcontrol is never allowed to change this above what we set at start. } #if X264_BUILD >= 153 @@ -165,14 +204,18 @@ void X264Encoder::init_x264() param.vui.i_colmatrix = 6; // BT.601/SMPTE 170M. } - if (!isinf(global_flags.x264_crf)) { + const double crf = use_separate_disk_params ? global_flags.x264_separate_disk_crf : global_flags.x264_crf; + const int bitrate = use_separate_disk_params ? global_flags.x264_separate_disk_bitrate : global_flags.x264_bitrate; + if (!isinf(crf)) { param.rc.i_rc_method = X264_RC_CRF; - param.rc.f_rf_constant = global_flags.x264_crf; + param.rc.f_rf_constant = crf; } else { param.rc.i_rc_method = X264_RC_ABR; - param.rc.i_bitrate = global_flags.x264_bitrate; + param.rc.i_bitrate = bitrate; + } + if (!use_separate_disk_params) { + update_vbv_settings(¶m); } - update_vbv_settings(¶m); if (param.rc.i_vbv_max_bitrate > 0) { // If the user wants VBV control to cap the max rate, it is // also reasonable to assume that they are fine with the stream @@ -205,7 +248,8 @@ void X264Encoder::init_x264() // be on the safe side. Shouldn't affect quality in any meaningful way. param.rc.i_qp_min = 5; - for (const string &str : global_flags.x264_extra_param) { + const vector &extra_param = use_separate_disk_params ? global_flags.x264_separate_disk_extra_param : global_flags.x264_extra_param; + for (const string &str : extra_param) { const size_t pos = str.find(','); if (pos == string::npos) { if (dyn.x264_param_parse(¶m, str.c_str(), nullptr) != 0) { @@ -235,7 +279,7 @@ void X264Encoder::init_x264() abort(); } - if (global_flags.x264_speedcontrol) { + if (!use_separate_disk_params && global_flags.x264_speedcontrol) { speed_control.reset(new X264SpeedControl(x264, /*f_speed=*/1.0f, X264_QUEUE_LENGTH, /*f_buffer_init=*/1.0f)); } @@ -284,7 +328,11 @@ void X264Encoder::encoder_thread_func() qf.data = nullptr; } - metric_x264_queued_frames = queued_frames.size(); + if (use_separate_disk_params) { + metric_x264_disk_queued_frames = queued_frames.size(); + } else { + metric_x264_queued_frames = queued_frames.size(); + } frames_left = !queued_frames.empty(); } @@ -360,15 +408,27 @@ void X264Encoder::encode_frame(X264Encoder::QueuedFrame qf) if (num_nal == 0) return; - if (IS_X264_TYPE_I(pic.i_type)) { - ++metric_x264_output_frames_i; - } else if (IS_X264_TYPE_B(pic.i_type)) { - ++metric_x264_output_frames_b; + if (use_separate_disk_params) { + if (IS_X264_TYPE_I(pic.i_type)) { + ++metric_x264_disk_output_frames_i; + } else if (IS_X264_TYPE_B(pic.i_type)) { + ++metric_x264_disk_output_frames_b; + } else { + ++metric_x264_disk_output_frames_p; + } + + metric_x264_disk_crf.count_event(pic.prop.f_crf_avg); } else { - ++metric_x264_output_frames_p; - } + if (IS_X264_TYPE_I(pic.i_type)) { + ++metric_x264_output_frames_i; + } else if (IS_X264_TYPE_B(pic.i_type)) { + ++metric_x264_output_frames_b; + } else { + ++metric_x264_output_frames_p; + } - metric_x264_crf.count_event(pic.prop.f_crf_avg); + metric_x264_crf.count_event(pic.prop.f_crf_avg); + } if (frames_being_encoded.count(pic.i_pts)) { ReceivedTimestamps received_ts = frames_being_encoded[pic.i_pts]; diff --git a/nageru/x264_encoder.h b/nageru/x264_encoder.h index 7b87517..9995c3f 100644 --- a/nageru/x264_encoder.h +++ b/nageru/x264_encoder.h @@ -42,7 +42,7 @@ class X264SpeedControl; class X264Encoder { public: - X264Encoder(AVOutputFormat *oformat); // Does not take ownership. + X264Encoder(AVOutputFormat *oformat, bool use_separate_disk_params); // Does not take ownership. // Called after the last frame. Will block; once this returns, // the last data is flushed. @@ -86,7 +86,8 @@ private: std::unique_ptr frame_pool; std::vector muxes; - bool wants_global_headers; + const bool wants_global_headers; + const bool use_separate_disk_params; std::string global_headers; std::string buffered_sei; // Will be output before first frame, if any.