]> git.sesse.net Git - nageru/blob - x264_encoder.h
Fix a thread race that would sometimes cause x264 streaming to go awry.
[nageru] / x264_encoder.h
1 // A wrapper around x264, to encode video in higher quality than Quick Sync
2 // can give us. We maintain a queue of uncompressed Y'CbCr frames (of 50 frames,
3 // so a little under 100 MB at 720p), then have a separate thread pull out
4 // those threads as fast as we can to give it to x264 for encoding.
5 //
6 // TODO: We use x264's “speedcontrol” patch if available, so that quality is
7 // automatically scaled up or down to content and available CPU time.
8 //
9 // The encoding threads are niced down because mixing is more important than
10 // encoding; if we lose frames in mixing, we'll lose frames to disk _and_
11 // to the stream, as where if we lose frames in encoding, we'll lose frames
12 // to the stream only, so the latter is strictly better. More importantly,
13 // this allows speedcontrol (when implemented) to do its thing without
14 // disturbing the mixer.
15
16 #ifndef _X264ENCODE_H
17 #define _X264ENCODE_H 1
18
19 #include <sched.h>
20 #include <stdint.h>
21 #include <x264.h>
22 #include <atomic>
23 #include <condition_variable>
24 #include <memory>
25 #include <mutex>
26 #include <queue>
27 #include <string>
28 #include <thread>
29
30 extern "C" {
31 #include <libavformat/avformat.h>
32 }
33
34 class Mux;
35 class X264SpeedControl;
36
37 class X264Encoder {
38 public:
39         X264Encoder(AVOutputFormat *oformat);  // Does not take ownership.
40
41         // Called after the last frame. Will block; once this returns,
42         // the last data is flushed.
43         ~X264Encoder();
44
45         // Must be called before first frame. Does not take ownership.
46         void set_mux(Mux *mux) { this->mux = mux; }
47
48         // <data> is taken to be raw NV12 data of WIDTHxHEIGHT resolution.
49         // Does not block.
50         void add_frame(int64_t pts, int64_t duration, const uint8_t *data);
51
52         std::string get_global_headers() const {
53                 while (!x264_init_done) {
54                         sched_yield();
55                 }
56                 return global_headers;
57         }
58
59         void change_bitrate(unsigned rate_kbit) {
60                 new_bitrate_kbit = rate_kbit;
61         }
62
63 private:
64         struct QueuedFrame {
65                 int64_t pts, duration;
66                 uint8_t *data;
67         };
68         void encoder_thread_func();
69         void init_x264();
70         void encode_frame(QueuedFrame qf);
71
72         // One big memory chunk of all 50 (or whatever) frames, allocated in
73         // the constructor. All data functions just use pointers into this
74         // pool.
75         std::unique_ptr<uint8_t[]> frame_pool;
76
77         Mux *mux = nullptr;
78         bool wants_global_headers;
79
80         std::string global_headers;
81         std::string buffered_sei;  // Will be output before first frame, if any.
82
83         std::thread encoder_thread;
84         std::atomic<bool> x264_init_done{false};
85         std::atomic<bool> should_quit{false};
86         x264_t *x264;
87         std::unique_ptr<X264SpeedControl> speed_control;
88
89         std::atomic<unsigned> new_bitrate_kbit{0};  // 0 for no change.
90
91         // Protects everything below it.
92         std::mutex mu;
93
94         // Frames that are not being encoded or waiting to be encoded,
95         // so that add_frame() can use new ones.
96         std::queue<uint8_t *> free_frames;
97
98         // Frames that are waiting to be encoded (ie., add_frame() has been
99         // called, but they are not picked up for encoding yet).
100         std::queue<QueuedFrame> queued_frames;
101
102         // Whenever the state of <queued_frames> changes.
103         std::condition_variable queued_frames_nonempty;
104 };
105
106 #endif  // !defined(_X264ENCODE_H)