MayaFlux 0.2.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 {
134 m_target_width = width;
135 m_target_height = height;
136 }
137 void set_target_sample_rate(uint32_t sample_rate) { m_target_sample_rate = sample_rate; }
139
140 /**
141 * @brief After load_into_container(), retrieve the audio container if
142 * EXTRACT_AUDIO was set.
143 */
144 [[nodiscard]] std::shared_ptr<Kakshya::SoundFileContainer> get_audio_container() const
145 {
146 return m_audio_container;
147 }
148
149 //=========================================================================
150 // IOManager integration
151 //=========================================================================
152
153 /**
154 * @brief Assign an externally-managed reader_id before load_into_container().
155 *
156 * When IOManager is present it assigns a globally unique id and calls this
157 * before load_into_container(). If never called, load_into_container()
158 * generates a local id from an instance-private atomic — safe for standalone
159 * single-reader use, but not unique across concurrent readers.
160 *
161 * @param id Opaque id assigned by the orchestration layer.
162 */
163 void set_reader_id(uint64_t id) { m_reader_id = id; }
164
165 /**
166 * @brief Returns the reader_id active for this instance.
167 *
168 * Valid after set_reader_id() or after load_into_container() has been called.
169 */
170 [[nodiscard]] uint64_t get_reader_id() const { return m_reader_id; }
171
172 /**
173 * @brief Non-blocking signal to the background decode thread.
174 *
175 * Called by IOManager::dispatch_decode_request() when it owns the IOService
176 * registration. In standalone mode the IOService lambda calls this directly.
177 * Must never block.
178 */
179 void signal_decode();
180
181 /**
182 * @brief Internal setup for IOService integration. Called by load_into_container()
183 * if no IOService is registered yet. Can also be called manually
184 * to pre-register before load_into_container().
185 *
186 * In standalone mode, the reader generates its own reader_id and registers
187 * an IOService lambda that calls signal_decode() when a decode request is
188 * dispatched with the matching reader_id.
189 *
190 * When IOManager is present, it assigns a globally unique reader_id and
191 * calls the second overload of setup_io_service() with the shared IOService
192 * instance it manages. The reader then registers a lambda that calls
193 * signal_decode() when a decode request is dispatched with the matching id.
194 */
195 void setup_io_service(uint64_t reader_id = 0);
196
197 /**
198 * @brief Overload for IOManager-managed IOService. Registers a lambda that
199 * calls signal_decode() when a decode request is dispatched with the
200 * matching reader_id.
201 *
202 * The caller retains ownership of the shared IOService instance and must ensure
203 * it remains valid for the lifetime of this reader.
204 */
205 void setup_io_service(const std::shared_ptr<Registry::Service::IOService>& io_service, uint64_t reader_id);
206
207private:
208 // =========================================================================
209 // FFmpeg state
210 // =========================================================================
211
212 std::shared_ptr<FFmpegDemuxContext> m_demux;
213 std::shared_ptr<VideoStreamContext> m_video;
214 std::shared_ptr<AudioStreamContext> m_audio;
215 mutable std::shared_mutex m_context_mutex;
216
217 std::string m_filepath;
221
222 uint32_t m_target_width { 0 };
223 uint32_t m_target_height { 0 };
224 uint32_t m_target_sample_rate { 0 };
225
226 mutable std::string m_last_error;
227 mutable std::mutex m_error_mutex;
228 mutable std::optional<FileMetadata> m_cached_metadata;
229 mutable std::vector<FileRegion> m_cached_regions;
230 mutable std::mutex m_metadata_mutex;
231
232 std::shared_ptr<Kakshya::SoundFileContainer> m_audio_container;
233
234 // =========================================================================
235 // Streaming decode state
236 // =========================================================================
237
238 uint32_t m_ring_capacity { 32 };
239 uint32_t m_decode_batch_size { 8 };
240 uint32_t m_refill_threshold { 0 };
241 uint64_t m_reader_id { 0 };
243
244 std::weak_ptr<Kakshya::VideoFileContainer> m_container_ref;
245
246 std::thread m_decode_thread;
247 std::mutex m_decode_mutex;
248 std::condition_variable m_decode_cv;
249 std::atomic<bool> m_decode_stop { false };
250 std::atomic<bool> m_decode_active { false };
251 std::atomic<uint64_t> m_decode_head { 0 };
252
253 std::shared_ptr<Registry::Service::IOService> m_io_service;
254
255 /// @brief One-frame sws scratch buffer (padded linesize, reused by decode thread).
256 std::vector<uint8_t> m_sws_buf;
257
258 // =========================================================================
259 // Decode thread
260 // =========================================================================
261
262 void start_decode_thread();
263 void stop_decode_thread();
264 void decode_thread_func();
265
266 /**
267 * @brief Decode up to batch_size frames starting at m_decode_head.
268 * Pumps packets in a loop, draining multiple frames per packet
269 * batch. Returns the number of frames actually decoded.
270 */
271 uint64_t decode_batch(Kakshya::VideoFileContainer& vc, uint64_t batch_size);
272
273 // =========================================================================
274 // Shared helpers
275 // =========================================================================
276
277 bool seek_internal(const std::shared_ptr<FFmpegDemuxContext>& demux,
278 const std::shared_ptr<VideoStreamContext>& video,
279 uint64_t frame_position);
280
281 void build_metadata(const std::shared_ptr<FFmpegDemuxContext>& demux,
282 const std::shared_ptr<VideoStreamContext>& video) const;
283
284 void build_regions(const std::shared_ptr<FFmpegDemuxContext>& demux,
285 const std::shared_ptr<VideoStreamContext>& video) const;
286
287 void set_error(const std::string& msg) const;
288 void clear_error() const;
289};
290
291} // namespace MayaFlux::IO
size_t a
size_t b
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.
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.