]> git.sesse.net Git - nageru/blobdiff - nageru/av1_encoder.h
Support AV1 streaming over HTTP, via SVT-AV1.
[nageru] / nageru / av1_encoder.h
diff --git a/nageru/av1_encoder.h b/nageru/av1_encoder.h
new file mode 100644 (file)
index 0000000..3431595
--- /dev/null
@@ -0,0 +1,120 @@
+// An AV1 encoder using the SVT-AV1 encoder library. (libaom does not seem
+// to be suitable for real-time streaming as of 2022, as it is not using
+// threads particularly efficiently.) AV1 is a newer format than H.264,
+// obviously both for better and for worse: Higher coding efficiency
+// (with sufficient amount of cores, SVT-AV1 is even better than x264
+// at higher frame rates), but generally smaller ecosystem, no speed
+// control, less sophisticated rate control, etc..
+//
+// We don't support storing AV1 to disk currently, only to HTTP.
+// We also don't support hardware AV1 encoding, as hardware supporting it
+// is very rare currently.
+//
+// Since SVT-AV1 does not support VFR, you need to declare the frame rate
+// up-front, using the --av1-framerate flag. If the given frame rate is
+// too high (ie., you are producing frames too slowly), rate control will get
+// confused and use too little bitrate. If it is too low, Nageru will need to
+// drop frames before input to the encoder.
+
+#ifndef _AV1_ENCODER_H
+#define _AV1_ENCODER_H 1
+
+#include <sched.h>
+#include <stdint.h>
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+extern "C" {
+#include <libavformat/avformat.h>
+}
+
+#include <movit/image_format.h>
+
+#include "defs.h"
+#include "shared/metrics.h"
+#include "print_latency.h"
+#include "video_codec_interface.h"
+
+class Mux;
+struct EbBufferHeaderType;
+struct EbComponentType;
+
+class AV1Encoder : public VideoCodecInterface {
+public:
+       AV1Encoder(const AVOutputFormat *oformat);  // Does not take ownership.
+
+       // Called after the last frame. Will block; once this returns,
+       // the last data is flushed.
+       ~AV1Encoder() override;
+
+       // Must be called before first frame. Does not take ownership.
+       void add_mux(Mux *mux) override { muxes.push_back(mux); }
+
+       // <data> is taken to be raw NV12 data of WIDTHxHEIGHT resolution.
+       // Does not block.
+       void add_frame(int64_t pts, int64_t duration, movit::YCbCrLumaCoefficients ycbcr_coefficients, const uint8_t *data, const ReceivedTimestamps &received_ts) override;
+
+       std::string get_global_headers() const override {
+               while (!av1_init_done) {
+                       sched_yield();
+               }
+               return global_headers;
+       }
+
+private:
+       struct QueuedFrame {
+               int64_t pts, duration;
+               movit::YCbCrLumaCoefficients ycbcr_coefficients;
+               uint8_t *data;
+               ReceivedTimestamps received_ts;
+       };
+       void encoder_thread_func();
+       void init_av1();
+       void encode_frame(QueuedFrame qf);
+       void process_packet(EbBufferHeaderType *buf);  // Takes ownership.
+
+       // One big memory chunk of all 50 (or whatever) frames, allocated in
+       // the constructor. All data functions just use pointers into this
+       // pool.
+       std::unique_ptr<uint8_t[]> frame_pool;
+
+       std::vector<Mux *> muxes;
+       const bool wants_global_headers;
+
+       std::string global_headers;
+
+       std::thread encoder_thread;
+       std::atomic<bool> av1_init_done{false};
+       std::atomic<bool> should_quit{false};
+       EbComponentType *encoder;
+
+       int64_t last_pts = -1;
+
+       // Protects everything below it.
+       std::mutex mu;
+
+       // Frames that are not being encoded or waiting to be encoded,
+       // so that add_frame() can use new ones.
+       // TODO: Do we actually need this queue, or is SVT-AV1's queue
+       // non-blocking so that we can drop it?
+       std::queue<uint8_t *> free_frames;
+
+       // Frames that are waiting to be encoded (ie., add_frame() has been
+       // called, but they are not picked up for encoding yet).
+       std::queue<QueuedFrame> queued_frames;
+
+       // Whenever the state of <queued_frames> changes.
+       std::condition_variable queued_frames_nonempty;
+
+       // Key is the pts of the frame.
+       std::unordered_map<int64_t, ReceivedTimestamps> frames_being_encoded;
+};
+
+#endif  // !defined(_AV1_ENCODER_H)