MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VideoFileWriter.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <future>
6
7extern "C" {
8#include <libavcodec/avcodec.h>
9#include <libavutil/pixfmt.h>
10}
11
12namespace MayaFlux::Core {
13class Window;
14}
15
16namespace MayaFlux::Kakshya {
17class TextureContainer;
18class VideoStreamContainer;
19}
20
21namespace MayaFlux::Buffers {
22class TextureBuffer;
23}
24
25namespace MayaFlux::IO {
26
27/**
28 * @class VideoFileWriter
29 * @brief Asynchronous video file encoder with a lock-free work queue.
30 *
31 * Accepts pixel data from multiple source types and encodes it to a file on a
32 * dedicated worker thread. The public API is fully non-blocking: every write
33 * call copies the pixel data, posts a work item to an SPSC queue, and returns
34 * immediately. The worker thread owns all FFmpeg state and is the only thread
35 * that touches it.
36 *
37 * Supported input paths:
38 * - record() / stop_recording(): swapchain readback via DisplayService frame
39 * observer; encoder opened lazily on first frame.
40 * - Raw packed uint8_t pixels (span): caller supplies dimensions matching open().
41 * - TextureContainer: reads pixel_bytes(layer); dimensions from container.
42 * - VideoStreamContainer / CameraContainer: reads get_frame_pixels(); always RGBA.
43 * - TextureBuffer: CPU pixels via get_pixel_data() when present, otherwise
44 * GPU download via download_data_async() on the worker thread.
45 *
46 * Source dimensions may differ from the encoder output dimensions; SwsContext
47 * rescales automatically. The format passed to open() must match the source
48 * pixel layout for all write() calls on that instance.
49 *
50 * close() returns future<bool>; caller must .get() before process exit or the
51 * container trailer will not be written.
52 */
53class MAYAFLUX_API VideoFileWriter {
54public:
57
62
63 // =========================================================================
64 // Lifecycle
65 // =========================================================================
66
67 bool open(const std::string& filepath,
68 uint32_t width,
69 uint32_t height,
70 double frame_rate,
71 AVPixelFormat src_pixel_format,
72 AVCodecID explicit_codec = AV_CODEC_ID_NONE);
73
74 std::future<bool> close();
75
76 [[nodiscard]] bool is_open() const { return m_open.load(std::memory_order_acquire); }
77
78 // =========================================================================
79 // Screen capture
80 // =========================================================================
81
82 bool record(const std::shared_ptr<Core::Window>& window,
83 const std::string& filepath,
84 double frame_rate,
85 AVCodecID codec_id = AV_CODEC_ID_NONE);
86
87 std::future<bool> stop_recording();
88
89 [[nodiscard]] bool is_recording() const
90 {
91 return m_observer_id.load(std::memory_order_acquire) != 0;
92 }
93
94 [[nodiscard]] std::shared_ptr<Core::Window> capture_window() const { return m_capture_window; }
95
96 // =========================================================================
97 // Write
98 // =========================================================================
99
100 void write(const uint8_t* pixels, size_t size);
101 void write(std::span<const uint8_t> pixels);
102
103 void write(const std::shared_ptr<Kakshya::TextureContainer>& container,
104 uint32_t layer = 0);
105
106 void write(const std::shared_ptr<Kakshya::VideoStreamContainer>& container,
107 uint64_t frame_index = 0);
108
109 void write(const std::shared_ptr<Buffers::TextureBuffer>& buffer);
110
111 // =========================================================================
112 // Error
113 // =========================================================================
114
115 [[nodiscard]] std::string last_error() const;
116
117private:
118 struct RawFrame {
119 std::vector<uint8_t> pixels;
120 uint32_t width {};
121 uint32_t height {};
122 };
123
124 struct DownloadCmd {
125 std::shared_ptr<Buffers::TextureBuffer> buffer;
126 };
127
128 struct CloseCmd { };
129
130 using WorkItem = std::variant<RawFrame, DownloadCmd, CloseCmd>;
131
132 static constexpr size_t k_queue_capacity = 512;
133 std::unique_ptr<Memory::LockFreeQueue<WorkItem, k_queue_capacity>> m_queue;
134
135 std::thread m_worker;
136 std::atomic<bool> m_open { false };
137 std::atomic<bool> m_closing { false };
138 std::promise<bool> m_close_promise;
139
140 mutable std::mutex m_error_mutex;
141 std::string m_last_error;
142
143 uint32_t m_width {};
144 uint32_t m_height {};
145 AVPixelFormat m_src_fmt { AV_PIX_FMT_BGRA };
146
147 std::shared_ptr<Core::Window> m_capture_window;
148 std::atomic<uint32_t> m_observer_id { 0 };
149 std::atomic<bool> m_capture_opened { false };
151 double m_capture_frame_rate {};
152 AVCodecID m_capture_codec_id { AV_CODEC_ID_NONE };
153 bool m_capture_did_enable { false };
154
155 void worker_loop(const std::string& filepath,
156 uint32_t width,
157 uint32_t height,
158 double frame_rate,
159 AVPixelFormat src_fmt,
160 AVCodecID codec_id);
161
162 void set_error(std::string msg);
163 bool post(const WorkItem& item);
164};
165
166} // namespace MayaFlux::IO
uint32_t width
Definition Decoder.cpp:59
const std::vector< float > * pixels
Definition Decoder.cpp:58
VideoFileWriter(const VideoFileWriter &)=delete
std::shared_ptr< Core::Window > m_capture_window
std::variant< RawFrame, DownloadCmd, CloseCmd > WorkItem
VideoFileWriter & operator=(VideoFileWriter &&)=delete
VideoFileWriter & operator=(const VideoFileWriter &)=delete
std::shared_ptr< Core::Window > capture_window() const
std::unique_ptr< Memory::LockFreeQueue< WorkItem, k_queue_capacity > > m_queue
VideoFileWriter(VideoFileWriter &&)=delete
std::promise< bool > m_close_promise
Asynchronous video file encoder with a lock-free work queue.
std::shared_ptr< Buffers::TextureBuffer > buffer