MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VideoFileReader.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "SoundFileReader.hpp"
4
8
9#include <condition_variable>
10
11namespace MayaFlux::Kakshya {
12class VideoFileContainer;
13class SoundFileContainer;
14}
15
17struct IOService;
18}
19
20namespace MayaFlux::IO {
21
22/// @brief Video-specific reading options.
23enum class VideoReadOptions : uint32_t {
24 NONE = 0,
25 EXTRACT_AUDIO = 1 << 0,
26 ALL = 0xFFFFFFFF
27};
28
30{
31 return static_cast<VideoReadOptions>(static_cast<uint32_t>(a) | static_cast<uint32_t>(b));
32}
33
35{
36 return static_cast<VideoReadOptions>(static_cast<uint32_t>(a) & static_cast<uint32_t>(b));
37}
38
39/**
40 * @class VideoFileReader
41 * @brief Streaming FFmpeg-based video file reader with background decode.
42 *
43 * The reader is NOT transient — it stays alive alongside its container and
44 * owns the FFmpeg decode contexts plus a background decode thread. The
45 * lifecycle is:
46 *
47 * open() → create_container() → load_into_container() → [playback] → close()
48 *
49 * load_into_container() sets up the container's ring buffer, synchronously
50 * decodes the first batch (so frame 0 is available immediately), then starts
51 * a background thread that batch-decodes ahead of the consumer read head.
52 *
53 * When the consumer (FrameAccessProcessor → VideoStreamReader) advances past
54 * ring_capacity - refill_threshold decoded frames, the decode thread
55 * automatically refills with the next decode_batch_size frames.
56 *
57 * Seek invalidates the ring, repositions the demuxer, synchronously decodes
58 * the first batch at the new position, then restarts background decoding.
59 *
60 * Audio extraction (EXTRACT_AUDIO) is delegated to SoundFileReader as before.
61 */
63public:
65 ~VideoFileReader() override;
66
67 bool can_read(const std::string& filepath) const override;
68
69 bool open(const std::string& filepath, FileReadOptions options = FileReadOptions::ALL) override;
70 void close() override;
71 [[nodiscard]] bool is_open() const override;
72
73 [[nodiscard]] std::optional<FileMetadata> get_metadata() const override;
74 [[nodiscard]] std::vector<FileRegion> get_regions() const override;
75
76 std::vector<Kakshya::DataVariant> read_all() override;
77 std::vector<Kakshya::DataVariant> read_region(const FileRegion& region) override;
78
79 std::shared_ptr<Kakshya::SignalSourceContainer> create_container() override;
80 bool load_into_container(std::shared_ptr<Kakshya::SignalSourceContainer> container) override;
81
82 [[nodiscard]] std::vector<uint64_t> get_read_position() const override;
83 bool seek(const std::vector<uint64_t>& position) override;
84
85 [[nodiscard]] std::vector<std::string> get_supported_extensions() const override;
86
87 [[nodiscard]] std::type_index get_data_type() const override { return typeid(std::vector<uint8_t>); }
88 [[nodiscard]] std::type_index get_container_type() const override;
89
90 [[nodiscard]] std::string get_last_error() const override;
91 [[nodiscard]] bool supports_streaming() const override { return true; }
92 [[nodiscard]] uint64_t get_preferred_chunk_size() const override { return m_decode_batch_size; }
93 [[nodiscard]] size_t get_num_dimensions() const override;
94 [[nodiscard]] std::vector<uint64_t> get_dimension_sizes() const override;
95
96 // =========================================================================
97 // Streaming configuration
98 // =========================================================================
99
100 /**
101 * @brief Set the number of decoded frame slots in the ring buffer.
102 * Default: 32. Must be a power of 2 (enforced by FixedStorage).
103 * Must be called before load_into_container().
104 */
105 void set_ring_capacity(uint32_t n)
106 {
107 n = std::max(4U, n);
108 uint32_t p = 1;
109 while (p < n)
110 p <<= 1;
111 m_ring_capacity = p;
112 }
113
114 /**
115 * @brief Set the number of frames decoded per batch by the background thread.
116 * Default: 8.
117 */
118 void set_decode_batch_size(uint32_t n) { m_decode_batch_size = std::max(1U, n); }
119
120 /**
121 * @brief Start refilling when fewer than this many frames remain ahead of
122 * the consumer read head. Default: ring_capacity / 4.
123 * A value of 0 means auto-compute as ring_capacity / 4.
124 */
125 void set_refill_threshold(uint32_t n) { m_refill_threshold = n; }
126
127 // =========================================================================
128 // Video-specific configuration
129 // =========================================================================
130
132 void set_target_dimensions(uint32_t width, uint32_t height)
133 {
136 }
137 void set_target_sample_rate(uint32_t sample_rate) { m_target_sample_rate = sample_rate; }
139
140 /**
141 * @brief Request an output pixel format for the scaler.
142 *
143 * Must be called before open(). Negative selects AV_PIX_FMT_RGBA.
144 * Multi-planar targets are rejected by the scaler; the read path
145 * writes a single packed plane.
146 */
147 void set_target_format(int av_pixel_format) { m_target_format = av_pixel_format; }
148
149 /**
150 * @brief After load_into_container(), retrieve the audio container if
151 * EXTRACT_AUDIO was set.
152 */
153 [[nodiscard]] std::shared_ptr<Kakshya::SoundFileContainer> get_audio_container() const
154 {
155 return m_audio_container;
156 }
157
158 //=========================================================================
159 // IOManager integration
160 //=========================================================================
161
162 /**
163 * @brief Assign an externally-managed reader_id before load_into_container().
164 *
165 * When IOManager is present it assigns a globally unique id and calls this
166 * before load_into_container(). If never called, load_into_container()
167 * generates a local id from an instance-private atomic — safe for standalone
168 * single-reader use, but not unique across concurrent readers.
169 *
170 * @param id Opaque id assigned by the orchestration layer.
171 */
172 void set_reader_id(uint64_t id) { m_reader_id = id; }
173
174 /**
175 * @brief Returns the reader_id active for this instance.
176 *
177 * Valid after set_reader_id() or after load_into_container() has been called.
178 */
179 [[nodiscard]] uint64_t get_reader_id() const { return m_reader_id; }
180
181 /**
182 * @brief Non-blocking signal to the background decode thread.
183 *
184 * Called by IOManager::dispatch_decode_request() when it owns the IOService
185 * registration. In standalone mode the IOService lambda calls this directly.
186 * Must never block.
187 */
188 void signal_decode();
189
190 /**
191 * @brief Internal setup for IOService integration. Called by load_into_container()
192 * if no IOService is registered yet. Can also be called manually
193 * to pre-register before load_into_container().
194 *
195 * In standalone mode, the reader generates its own reader_id and registers
196 * an IOService lambda that calls signal_decode() when a decode request is
197 * dispatched with the matching reader_id.
198 *
199 * When IOManager is present, it assigns a globally unique reader_id and
200 * calls the second overload of setup_io_service() with the shared IOService
201 * instance it manages. The reader then registers a lambda that calls
202 * signal_decode() when a decode request is dispatched with the matching id.
203 */
204 void setup_io_service(uint64_t reader_id = 0);
205
206 /**
207 * @brief Overload for IOManager-managed IOService. Registers a lambda that
208 * calls signal_decode() when a decode request is dispatched with the
209 * matching reader_id.
210 *
211 * The caller retains ownership of the shared IOService instance and must ensure
212 * it remains valid for the lifetime of this reader.
213 */
214 void setup_io_service(const std::shared_ptr<Registry::Service::IOService>& io_service, uint64_t reader_id);
215
216private:
217 // =========================================================================
218 // FFmpeg state
219 // =========================================================================
220
221 std::shared_ptr<FFmpegDemuxContext> m_demux;
222 std::shared_ptr<VideoStreamContext> m_video;
223 std::shared_ptr<AudioStreamContext> m_audio;
224 mutable std::shared_mutex m_context_mutex;
225
226 std::string m_filepath;
230
231 uint32_t m_target_width { 0 };
232 uint32_t m_target_height { 0 };
233 uint32_t m_target_sample_rate { 0 };
234
235 mutable std::string m_last_error;
236 mutable std::mutex m_error_mutex;
237 mutable std::optional<FileMetadata> m_cached_metadata;
238 mutable std::vector<FileRegion> m_cached_regions;
239 mutable std::mutex m_metadata_mutex;
240
241 std::shared_ptr<Kakshya::SoundFileContainer> m_audio_container;
242
243 // =========================================================================
244 // Streaming decode state
245 // =========================================================================
246
247 uint32_t m_ring_capacity { 32 };
248 uint32_t m_decode_batch_size { 8 };
249 uint32_t m_refill_threshold { 0 };
250 uint64_t m_reader_id { 0 };
252
253 std::weak_ptr<Kakshya::VideoFileContainer> m_container_ref;
254
255 std::thread m_decode_thread;
256 std::mutex m_decode_mutex;
257 std::condition_variable m_decode_cv;
258 std::atomic<bool> m_decode_stop { false };
259 std::atomic<bool> m_decode_active { false };
260 std::atomic<uint64_t> m_decode_head { 0 };
261 int m_target_format { -1 };
262
263 std::shared_ptr<Registry::Service::IOService> m_io_service;
264
265 /// @brief One-frame sws scratch buffer (padded linesize, reused by decode thread).
266 std::vector<uint8_t> m_sws_buf;
267
268 // =========================================================================
269 // Decode thread
270 // =========================================================================
271
272 void start_decode_thread();
273 void stop_decode_thread();
274 void decode_thread_func();
275
276 /**
277 * @brief Decode up to batch_size frames starting at m_decode_head.
278 * Pumps packets in a loop, draining multiple frames per packet
279 * batch. Returns the number of frames actually decoded.
280 */
281 uint64_t decode_batch(Kakshya::VideoFileContainer& vc, uint64_t batch_size);
282
283 // =========================================================================
284 // Shared helpers
285 // =========================================================================
286
287 bool seek_internal(const std::shared_ptr<FFmpegDemuxContext>& demux,
288 const std::shared_ptr<VideoStreamContext>& video,
289 uint64_t frame_position);
290
291 void build_metadata(const std::shared_ptr<FFmpegDemuxContext>& demux,
292 const std::shared_ptr<VideoStreamContext>& video) const;
293
294 void build_regions(const std::shared_ptr<FFmpegDemuxContext>& demux,
295 const std::shared_ptr<VideoStreamContext>& video) const;
296
297 void set_error(const std::string& msg) const;
298 void clear_error() const;
299};
300
301} // namespace MayaFlux::IO
size_t a
size_t b
uint32_t width
uint32_t height
Abstract interface for reading various file formats into containers.
std::condition_variable m_decode_cv
uint64_t decode_batch(Kakshya::VideoFileContainer &vc, uint64_t batch_size)
Decode up to batch_size frames starting at m_decode_head.
void set_error(const std::string &msg) const
std::vector< FileRegion > m_cached_regions
void set_ring_capacity(uint32_t n)
Set the number of decoded frame slots in the ring buffer.
void set_decode_batch_size(uint32_t n)
Set the number of frames decoded per batch by the background thread.
std::vector< FileRegion > get_regions() const override
Get semantic regions from the file.
void build_metadata(const std::shared_ptr< FFmpegDemuxContext > &demux, const std::shared_ptr< VideoStreamContext > &video) const
void set_target_dimensions(uint32_t width, uint32_t height)
std::vector< uint8_t > m_sws_buf
One-frame sws scratch buffer (padded linesize, reused by decode thread).
std::type_index get_data_type() const override
Get the data type this reader produces.
void build_regions(const std::shared_ptr< FFmpegDemuxContext > &demux, const std::shared_ptr< VideoStreamContext > &video) const
bool open(const std::string &filepath, FileReadOptions options=FileReadOptions::ALL) override
Open a file for reading.
uint64_t get_preferred_chunk_size() const override
Get the preferred chunk size for streaming.
uint64_t get_reader_id() const
Returns the reader_id active for this instance.
std::vector< Kakshya::DataVariant > read_all() override
Read all data from the file into memory.
std::weak_ptr< Kakshya::VideoFileContainer > m_container_ref
std::shared_ptr< FFmpegDemuxContext > m_demux
std::shared_ptr< Registry::Service::IOService > m_io_service
bool seek(const std::vector< uint64_t > &position) override
Seek to a specific position in the file.
std::vector< std::string > get_supported_extensions() const override
Get supported file extensions for this reader.
std::atomic< bool > m_decode_active
bool seek_internal(const std::shared_ptr< FFmpegDemuxContext > &demux, const std::shared_ptr< VideoStreamContext > &video, uint64_t frame_position)
bool load_into_container(std::shared_ptr< Kakshya::SignalSourceContainer > container) override
Load file data into an existing container.
bool is_open() const override
Check if a file is currently open.
void set_refill_threshold(uint32_t n)
Start refilling when fewer than this many frames remain ahead of the consumer read head.
std::string get_last_error() const override
Get the last error message.
std::vector< uint64_t > get_read_position() const override
Get current read position in primary dimension.
void set_audio_options(AudioReadOptions options)
std::shared_ptr< VideoStreamContext > m_video
std::optional< FileMetadata > get_metadata() const override
Get metadata from the open file.
void set_reader_id(uint64_t id)
Assign an externally-managed reader_id before load_into_container().
void set_target_sample_rate(uint32_t sample_rate)
std::shared_ptr< AudioStreamContext > m_audio
void close() override
Close the currently open file.
std::optional< FileMetadata > m_cached_metadata
std::shared_ptr< Kakshya::SignalSourceContainer > create_container() override
Create and initialize a container from the file.
std::shared_ptr< Kakshya::SoundFileContainer > get_audio_container() const
After load_into_container(), retrieve the audio container if EXTRACT_AUDIO was set.
std::shared_ptr< Kakshya::SoundFileContainer > m_audio_container
void setup_io_service(uint64_t reader_id=0)
Internal setup for IOService integration.
std::atomic< uint64_t > m_decode_head
bool can_read(const std::string &filepath) const override
Check if a file can be read by this reader.
std::type_index get_container_type() const override
Get the container type this reader creates.
void set_target_format(int av_pixel_format)
Request an output pixel format for the scaler.
bool supports_streaming() const override
Check if streaming is supported for the current file.
void signal_decode()
Non-blocking signal to the background decode thread.
void set_video_options(VideoReadOptions options)
size_t get_num_dimensions() const override
Get the dimensionality of the file data.
std::vector< Kakshya::DataVariant > read_region(const FileRegion &region) override
Read a specific region of data.
std::vector< uint64_t > get_dimension_sizes() const override
Get size of each dimension in the file data.
Streaming FFmpeg-based video file reader with background decode.
File-backed video container — semantic marker over VideoStreamContainer.
AudioReadOptions
Audio-specific reading options.
FileReadOptions
Generic options for file reading behavior.
@ ALL
All options enabled.
@ NONE
No special options.
FileReadOptions operator&(FileReadOptions a, FileReadOptions b)
VideoReadOptions
Video-specific reading options.
FileReadOptions operator|(FileReadOptions a, FileReadOptions b)
Generic region descriptor for any file type.