--- /dev/null
+#ifndef _CEF_ENCODER_ADAPTER
+#define _CEF_ENCODER_ADAPTER 1
+
+#include <stddef.h>
+#include <stdint.h>
+#include <bmusb/bmusb.h>
+#include <chrono>
+#include <optional>
+#include <mutex>
+#include <memory>
+
+#include "nageru/quittable_sleeper.h"
+#include "shared/ffmpeg_raii.h"
+
+extern "C" {
+#include <libavutil/rational.h>
+#include <libswscale/swscale.h>
+#include <libavutil/pixfmt.h>
+}
+
+class X264Encoder;
+class AudioEncoder;
+
+// For use in Kaeru, where we don't have a full mixer; converts the video data
+// from BGRA to NV12 (as CEF cannot produce NV12), and also deals with the fact
+// that CEF doesn't produce a steady stream of frames (see comments
+// on duplicate_frame_if_needed()).
+class CEFEncoderAdapter {
+public:
+ // Does not take ownership of the encoders.
+ CEFEncoderAdapter(unsigned width, unsigned height, X264Encoder *x264_encoder, AudioEncoder *audio_encoder)
+ : nv12_data(new uint8_t[width * height * 2]),
+ sws_ctx(sws_getContext(width, height, AV_PIX_FMT_BGRA,
+ width, height, AV_PIX_FMT_NV12,
+ SWS_BICUBIC, nullptr, nullptr, nullptr)),
+ x264_encoder(x264_encoder),
+ audio_encoder(audio_encoder) {}
+
+ void video_frame_callback(uint16_t timecode,
+ bmusb::FrameAllocator::Frame video_frame, size_t video_offset, bmusb::VideoFormat video_format,
+ bmusb::FrameAllocator::Frame audio_frame, size_t audio_offset, bmusb::AudioFormat audio_format);
+ void duplicate_frame_if_needed(QuittableSleeper *should_quit);
+
+private:
+ std::optional<std::chrono::steady_clock::time_point> get_last_frame() const;
+
+ mutable std::mutex mu; // Protects all data members.
+ std::unique_ptr<uint8_t[]> nv12_data;
+ SwsContextWithDeleter sws_ctx;
+ std::chrono::steady_clock::time_point start;
+ std::chrono::steady_clock::time_point last_frame;
+ bmusb::VideoFormat last_video_format;
+ bmusb::AudioFormat last_audio_format;
+ bool first_frame = true;
+ X264Encoder *x264_encoder;
+ AudioEncoder *audio_encoder;
+ static constexpr AVRational video_timebase{ 1, 1000000 };
+};
+
+#endif