]> git.sesse.net Git - nageru/commitdiff
Support sending a separate x264 encode to disk.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 26 Sep 2021 18:36:48 +0000 (20:36 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 26 Sep 2021 18:36:48 +0000 (20:36 +0200)
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.

nageru/flags.cpp
nageru/flags.h
nageru/kaeru.cpp
nageru/quicksync_encoder.cpp
nageru/quicksync_encoder.h
nageru/quicksync_encoder_impl.h
nageru/video_encoder.cpp
nageru/video_encoder.h
nageru/x264_encoder.cpp
nageru/x264_encoder.h

index 769263bb771adfb55667f39170d6dd4492100005..ed35062427ec01330bc26fa7bcf4cbfaf9a0ff23 100644 (file)
@@ -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) {
index dc9c585d0efb5ae718687110b0d9c76683919345..9b9976c5db7c572027c7ad314e5196b3d9f644ab 100644 (file)
@@ -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<std::string> 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 <x264_bitrate> (CBR).
        int x264_vbv_buffer_size = -1;  // In kilobits. 0 = one-frame VBV, -1 = same as <x264_bitrate> (one-second VBV).
        std::vector<std::string> 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<std::string> x264_separate_disk_extra_param;  // In “key[,value]” format.
+
        std::string v4l_output_device;  // Empty if none.
        bool enable_alsa_output = true;
        std::map<int, int> default_stream_mapping;
index b1b08f74465db6e418fd6a8e495800991a072514..1a63bb04337ca56841033b9628908c39b7083f8f 100644 (file)
@@ -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<X264Encoder> x264_encoder(new X264Encoder(oformat));
+       unique_ptr<X264Encoder> x264_encoder(new X264Encoder(oformat, /*use_separate_disk_params=*/false));
        unique_ptr<Mux> http_mux = create_mux(&httpd, oformat, x264_encoder.get(), audio_encoder.get());
        if (global_flags.transcode_audio) {
                audio_encoder->add_mux(http_mux.get());
index 992bf8f83e037e3bf8df40b02df23ede0fbcd2a3..eb144ceb2512f70044e5ae55d9b17b6e77ee4135 100644 (file)
@@ -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() {}
index 4f71f90067d3d8349f7f7ad11fe163c2b670dcf5..e4594de218cb013d746a67a5681fcea1b0132470 100644 (file)
@@ -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.
index 53f88ecd4be8c9fe98d51cfeaf72eebb34fe4afe..c4a99fb97f8874b24e0362d9469bafc92792a678 100644 (file)
@@ -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<float> audio);
        bool is_zerocopy() const;
@@ -171,7 +171,8 @@ private:
        std::mutex file_audio_encoder_mutex;
        std::unique_ptr<AudioEncoder> 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<V4LOutput> v4l_output;  // nullptr if not using V4L2 output.
 
        Mux* stream_mux = nullptr;  // To HTTP.
index 2ac606ec5aea8674f1a31cc2ea763134d75deea4..8138e76a42c99a7cf984d030ea7241423d99599d 100644 (file)
@@ -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<mutex> 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());
 }
 
index aed485b5eeaf42f8ac7ffbaad09dcbbbf3fc2296..e0a926bbef811a8a0b63d083506925b8642b0309 100644 (file)
@@ -93,6 +93,7 @@ private:
        std::unique_ptr<Mux> stream_mux;  // To HTTP.
        std::unique_ptr<AudioEncoder> stream_audio_encoder;
        std::unique_ptr<X264Encoder> x264_encoder;  // nullptr if not using x264.
+       std::unique_ptr<X264Encoder> x264_disk_encoder;  // nullptr if not using x264, or if not having separate disk encodes.
 
        std::string stream_mux_header;
        MuxMetrics stream_mux_metrics;
index d938393598d637365ba4d1e8e843c8878cdf311c..23a1359d70fc6083307dd456a1cd53889c30c290 100644 (file)
@@ -43,7 +43,17 @@ atomic<int64_t> metric_x264_output_frames_p{0};
 atomic<int64_t> metric_x264_output_frames_b{0};
 Histogram metric_x264_crf;
 LatencyHistogram x264_latency_histogram;
-once_flag x264_metrics_inited;
+
+atomic<int64_t> metric_x264_disk_queued_frames{0};
+atomic<int64_t> metric_x264_disk_max_queued_frames{X264_QUEUE_LENGTH};
+atomic<int64_t> metric_x264_disk_dropped_frames{0};
+atomic<int64_t> metric_x264_disk_output_frames_i{0};
+atomic<int64_t> metric_x264_disk_output_frames_p{0};
+atomic<int64_t> 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<mutex> 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<mutex> 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(&param, global_flags.x264_preset.c_str(), global_flags.x264_tune.c_str());
+       if (use_separate_disk_params) {
+               dyn.x264_param_default_preset(&param, global_flags.x264_separate_disk_preset.c_str(), global_flags.x264_separate_disk_tune.c_str());
+       } else {
+               dyn.x264_param_default_preset(&param, 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(&param);
        }
-       update_vbv_settings(&param);
        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<string> &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(&param, 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];
index 7b8751715250d5f34147f7f11d9a8d83df409037..9995c3f370452ba300b97d34e952f5c60f514a51 100644 (file)
@@ -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<uint8_t[]> frame_pool;
 
        std::vector<Mux *> 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.