]> git.sesse.net Git - nageru/blobdiff - x264_encoder.cpp
Add a switch to print video latency.
[nageru] / x264_encoder.cpp
index 5017e7357913ce5d33c74efc95e262e5889fcd69..fcd20e7aa519b33345a86b08728177bddf515b8b 100644 (file)
@@ -1,25 +1,52 @@
+#include "x264_encoder.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <x264.h>
+#include <cstdint>
 
 #include "defs.h"
 #include "flags.h"
 #include "mux.h"
+#include "print_latency.h"
 #include "timebase.h"
-#include "x264_encoder.h"
 #include "x264_speed_control.h"
 
 extern "C" {
+#include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 }
 
 using namespace std;
+using namespace std::chrono;
+
+namespace {
+
+void update_vbv_settings(x264_param_t *param)
+{
+       if (global_flags.x264_vbv_buffer_size < 0) {
+               param->rc.i_vbv_buffer_size = param->rc.i_bitrate;  // One-second VBV.
+       } else {
+               param->rc.i_vbv_buffer_size = global_flags.x264_vbv_buffer_size;
+       }
+       if (global_flags.x264_vbv_max_bitrate < 0) {
+               param->rc.i_vbv_max_bitrate = param->rc.i_bitrate;  // CBR.
+       } else {
+               param->rc.i_vbv_max_bitrate = global_flags.x264_vbv_max_bitrate;
+       }
+}
+
+}  // namespace
 
 X264Encoder::X264Encoder(AVOutputFormat *oformat)
        : wants_global_headers(oformat->flags & AVFMT_GLOBALHEADER)
 {
-       frame_pool.reset(new uint8_t[WIDTH * HEIGHT * 2 * X264_QUEUE_LENGTH]);
+       frame_pool.reset(new uint8_t[global_flags.width * global_flags.height * 2 * X264_QUEUE_LENGTH]);
        for (unsigned i = 0; i < X264_QUEUE_LENGTH; ++i) {
-               free_frames.push(frame_pool.get() + i * (WIDTH * HEIGHT * 2));
+               free_frames.push(frame_pool.get() + i * (global_flags.width * global_flags.height * 2));
        }
        encoder_thread = thread(&X264Encoder::encoder_thread_func, this);
 }
@@ -31,11 +58,12 @@ X264Encoder::~X264Encoder()
        encoder_thread.join();
 }
 
-void X264Encoder::add_frame(int64_t pts, int64_t duration, const uint8_t *data)
+void X264Encoder::add_frame(int64_t pts, int64_t duration, const uint8_t *data, const ReceivedTimestamps &received_ts)
 {
        QueuedFrame qf;
        qf.pts = pts;
        qf.duration = duration;
+       qf.received_ts = received_ts;
 
        {
                lock_guard<mutex> lock(mu);
@@ -48,7 +76,7 @@ void X264Encoder::add_frame(int64_t pts, int64_t duration, const uint8_t *data)
                free_frames.pop();
        }
 
-       memcpy(qf.data, data, WIDTH * HEIGHT * 2);
+       memcpy(qf.data, data, global_flags.width * global_flags.height * 2);
 
        {
                lock_guard<mutex> lock(mu);
@@ -62,8 +90,8 @@ void X264Encoder::init_x264()
        x264_param_t param;
        x264_param_default_preset(&param, global_flags.x264_preset.c_str(), global_flags.x264_tune.c_str());
 
-       param.i_width = WIDTH;
-       param.i_height = HEIGHT;
+       param.i_width = global_flags.width;
+       param.i_height = global_flags.height;
        param.i_csp = X264_CSP_NV12;
        param.b_vfr_input = 1;
        param.i_timebase_num = 1;
@@ -83,16 +111,7 @@ void X264Encoder::init_x264()
 
        param.rc.i_rc_method = X264_RC_ABR;
        param.rc.i_bitrate = global_flags.x264_bitrate;
-       if (global_flags.x264_vbv_buffer_size < 0) {
-               param.rc.i_vbv_buffer_size = param.rc.i_bitrate;  // One-second VBV.
-       } else {
-               param.rc.i_vbv_buffer_size = global_flags.x264_vbv_buffer_size;
-       }
-       if (global_flags.x264_vbv_max_bitrate < 0) {
-               param.rc.i_vbv_max_bitrate = param.rc.i_bitrate;  // CBR.
-       } else {
-               param.rc.i_vbv_max_bitrate = global_flags.x264_vbv_max_bitrate;
-       }
+       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
@@ -100,7 +119,7 @@ void X264Encoder::init_x264()
                // content; the obvious and extreme example being a static
                // black picture.
                //
-               // One would think it's fine to have low-complexity codec use
+               // One would think it's fine to have low-complexity content use
                // less bitrate, but it seems to cause problems in practice;
                // e.g. VLC seems to often drop the stream (similar to a buffer
                // underrun) in such cases, but only when streaming from Nageru,
@@ -174,8 +193,12 @@ void X264Encoder::init_x264()
 
 void X264Encoder::encoder_thread_func()
 {
-       nice(5);  // Note that x264 further nices some of its threads.
+       if (nice(5) == -1) {  // Note that x264 further nices some of its threads.
+               perror("nice()");
+               // No exit; it's not fatal.
+       }
        init_x264();
+       x264_init_done = true;
 
        bool frames_left;
 
@@ -226,12 +249,31 @@ void X264Encoder::encode_frame(X264Encoder::QueuedFrame qf)
                pic.img.i_csp = X264_CSP_NV12;
                pic.img.i_plane = 2;
                pic.img.plane[0] = qf.data;
-               pic.img.i_stride[0] = WIDTH;
-               pic.img.plane[1] = qf.data + WIDTH * HEIGHT;
-               pic.img.i_stride[1] = WIDTH / 2 * sizeof(uint16_t);
+               pic.img.i_stride[0] = global_flags.width;
+               pic.img.plane[1] = qf.data + global_flags.width * global_flags.height;
+               pic.img.i_stride[1] = global_flags.width / 2 * sizeof(uint16_t);
                pic.opaque = reinterpret_cast<void *>(intptr_t(qf.duration));
 
                input_pic = &pic;
+
+               frames_being_encoded[qf.pts] = qf.received_ts;
+       }
+
+       // See if we have a new bitrate to change to.
+       unsigned new_rate = new_bitrate_kbit.exchange(0);  // Read and clear.
+       if (new_rate != 0) {
+               if (speed_control) {
+                       speed_control->set_config_override_function([new_rate](x264_param_t *param) {
+                               param->rc.i_bitrate = new_rate;
+                               update_vbv_settings(param);
+                       });
+               } else {
+                       x264_param_t param;
+                       x264_encoder_parameters(x264, &param);
+                       param.rc.i_bitrate = new_rate;
+                       update_vbv_settings(&param);
+                       x264_encoder_reconfig(x264, &param);
+               }
        }
 
        if (speed_control) {
@@ -242,6 +284,20 @@ void X264Encoder::encode_frame(X264Encoder::QueuedFrame qf)
                speed_control->after_frame();
        }
 
+       if (num_nal == 0) return;
+
+       if (frames_being_encoded.count(pic.i_pts)) {
+               ReceivedTimestamps received_ts = frames_being_encoded[pic.i_pts];
+               frames_being_encoded.erase(pic.i_pts);
+
+               static int frameno = 0;
+               print_latency("Current x264 latency (video inputs → network mux):",
+                       received_ts, (pic.i_type == X264_TYPE_B || pic.i_type == X264_TYPE_BREF),
+                       &frameno);
+       } else {
+               assert(false);
+       }
+
        // We really need one AVPacket for the entire frame, it seems,
        // so combine it all.
        size_t num_bytes = buffered_sei.size();