]> git.sesse.net Git - nageru/blobdiff - x264_speed_control.h
Implement x264 speedcontrol.
[nageru] / x264_speed_control.h
diff --git a/x264_speed_control.h b/x264_speed_control.h
new file mode 100644 (file)
index 0000000..b498826
--- /dev/null
@@ -0,0 +1,116 @@
+// The x264 speed control tries to encode video at maximum possible quality
+// without skipping frames (at the expense of higher encoding latency and
+// less even output rates, although VBV is still respected). It does this
+// by continuously (every frame) changing the x264 quality settings such that
+// it uses maximum amount of CPU, but no more.
+//
+// Speed control works by maintaining a queue of frames, with the confusing
+// nomenclature “full” meaning that there are no queues in the frame.
+// (Conversely, if the queue is “empty” and a new frame comes in, we need to
+// drop that frame.) It tries to keep the buffer 3/4 “full” by using a table
+// of measured relative speeds for the different presets, and choosing one that it
+// thinks will return the buffer to that state over time. However, since
+// different frames take different times to encode regardless of preset, it
+// also tries to maintain a running average of how long the typical frame will
+// take to encode at the fastest preset (the so-called “complexity”), by dividing
+// the actual time by the relative time for the preset used.
+//
+// Frame timings is a complex topic in its own sright, since usually, multiple
+// frames are encoded in parallel. X264SpeedControl only supports the timing
+// method that the original patch calls “alternate timing”; one simply measures
+// the time the last x264_encoder_encode() call took. (The other alternative given
+// is to measure the time between successive x264_encoder_encode() calls.)
+// Unless using the zerocopy presets (which activate slice threading), the function
+// actually returns not when the given frame is done encoding, but when one a few
+// frames back is done encoding. So it doesn't actually measure the time of any
+// given one frame, but it measures something correlated to it, at least as long as
+// you are near 100% CPU utilization (ie., the encoded frame doesn't linger in the
+// buffers already when x264_encoder_encode() is called).
+//
+// The code has a long history; it was originally part of Avail Media's x264
+// branch, used in their encoder appliances, and then a snapshot of that was
+// released. (Given that x264 is licensed under GPLv2 or newer, this means that
+// we can also treat the patch as GPLv2 or newer if we want, which we do.
+// As far as I know, it is copyright Avail Media, although no specific copyright
+// notice was posted on the patch.)
+//
+// From there, it was incorporated in OBE's x264 tree (x264-obe) and some bugs
+// were fixed. I started working on it for the purposes of Nageru, fixing various
+// issues, adding VFR support and redoing the timings entirely based on more
+// modern presets (the patch was made before several important x264 features,
+// such as weighted P-frames). Finally, I took it out of x264 and put it into
+// Nageru (it does not actually use any hooks into the codec itself), so that
+// one does not need to patch x264 to use it in Nageru. It still could do with
+// some cleanup, but it's much, much better than just using a static preset.
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <math.h>
+
+extern "C" {
+#include "x264.h"
+}
+
+class X264SpeedControl {
+public:
+       // x264: Encoding object we are using; must be opened. Assumed to be
+       //    set to the "faster" preset, and with 16 reference frames.
+       // f_speed: Relative encoding speed, usually 1.0.
+       // i_buffer_size: Number of frames in the buffer.
+       // f_buffer_init: Relative fullness of buffer at start
+       //    (0.0 = assumed to be <i_buffer_size> frames in buffer,
+       //     1.0 = no frames in buffer)
+       X264SpeedControl(x264_t *x264, float f_speed, int i_buffer_size, float f_buffer_init);
+       ~X264SpeedControl();
+
+       // You need to call before_frame() immediately before each call to
+       // x264_encoder_encode(), and after_frame() immediately after.
+       //
+       // new_buffer_fill: Buffer fullness, in microseconds (_not_ a relative
+       //   number, unlike f_buffer_init in the constructor).
+       // new_buffer_size: If > 0, new number of frames in the buffer,
+       //   ie. the buffer size has changed. (It is harmless to set this
+       //   even if the buffer hasn't actually changed.)
+       // f_uspf: If > 0, new microseconds per frame, ie. the frame rate has
+       //   changed. (Of course, with VFR, it can be impossible to truly know
+       //   the frame rate of the coming frames, but it is a reasonable
+       //   assumption that the next second or so is likely to be the same
+       //   frame rate as the last frame.)
+       void before_frame(float new_buffer_fill, int new_buffer_size, float f_uspf);
+       void after_frame();
+
+private:
+       void set_buffer_size(int new_buffer_size);
+       int dither_preset(float f);
+       void apply_preset(int new_preset);
+       int64_t mdate();  // Current time in microseconds.
+
+       // Not owned by us.
+       x264_t *x264;
+
+       float f_speed;
+
+       // all times are in usec
+       int64_t timestamp;   // when was speedcontrol last invoked
+       int64_t cpu_time_last_frame = 0;    // time spent encoding the previous frame
+       int64_t buffer_size; // assumed application-side buffer of frames to be streamed (measured in microseconds),
+       int64_t buffer_fill; //   where full = we don't have to hurry
+       int64_t compensation_period; // how quickly we try to return to the target buffer fullness
+       float uspf;          // microseconds per frame
+       int preset = -1;     // which setting was used in the previous frame
+       float cplx_num = 3e3;  // rolling average of estimated spf for preset #0. FIXME estimate initial complexity
+       float cplx_den = .1;
+       float cplx_decay;
+       float dither = 0.0f;
+
+       bool first = true;
+       bool buffer_complete = false;
+
+       struct
+       {
+               int64_t min_buffer, max_buffer;
+               double avg_preset;
+               int den;
+       } stat;
+};