MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
IOManager.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "CameraReader.hpp"
4#include "ImageWriter.hpp"
5#include "VideoFileReader.hpp"
6
7#include <future>
8
9namespace MayaFlux::Core {
10class VKImage;
11}
12
14class MeshNetwork;
15}
16
17namespace MayaFlux::Kakshya {
18class SignalSourceContainer;
19class VideoFileContainer;
20class SoundFileContainer;
21class CameraContainer;
22}
23
24namespace MayaFlux::Buffers {
25class VideoContainerBuffer;
26class SoundContainerBuffer;
27class TextureBuffer;
28class TextBuffer;
29class MeshBuffer;
30class BufferManager;
31}
32
34class IOService;
35}
36
37namespace MayaFlux::IO {
38
39using TextureResolver = std::function<std::shared_ptr<Core::VKImage>(const std::string& path)>;
40
41class ImageReader;
42class ModelReader;
43
51
52/**
53 * @struct VideoLoadResult
54 * @brief Result of load_video() when audio extraction is requested via
55 * VideoReadOptions::EXTRACT_AUDIO.
56 *
57 * @c audio is nullptr if the file has no audio track or EXTRACT_AUDIO was
58 * not set in the options passed to load_video().
59 */
61 std::shared_ptr<Kakshya::VideoFileContainer> video;
62 std::shared_ptr<Kakshya::SoundFileContainer> audio;
63};
64
65/**
66 * @class IOManager
67 * @brief Optional orchestration layer for IO reader lifetime and IOService dispatch.
68 *
69 * MayaFlux IO readers are fully self-sufficient — VideoFileReader, SoundFileReader,
70 * and ImageReader can all be used standalone without this class. IOManager exists
71 * to solve problems that only arise at scale:
72 *
73 * - Globally unique reader_ids across concurrent VideoFileReader instances, so
74 * IOService::request_decode routes to the correct decode thread.
75 * - A single IOService registration backed by a keyed dispatch map, rather than
76 * each reader overwriting the previous registration.
77 * - Shared ownership of VideoFileReader instances whose background decode thread
78 * must outlive the scope in which they were created.
79 * - High-level load/hook functions that collapse the full open→load→processor
80 * setup→buffer wiring sequence into single calls, mirroring what Depot does
81 * for audio and eliminating the boilerplate currently required for video.
82 *
83 * VideoFileReader detects the IOService registration generically via BackendRegistry
84 * and skips its own registration — no IOManager-specific knowledge in the reader.
85 *
86 * Levels of integration (all valid):
87 * 1. VideoFileReader standalone, single stream — self-registers, self-ids.
88 * 2. IOManager constructed directly — multi-stream safe, no Engine required.
89 * 3. Engine constructs IOManager in Init() — fully orchestrated, zero user wiring.
90 */
91class MAYAFLUX_API IOManager {
92public:
93 /**
94 * @brief Construct IOManager and register the IOService into BackendRegistry.
95 *
96 * Must be constructed before any VideoFileReader::load_into_container() call
97 * that should participate in managed dispatch.
98 */
99 IOManager(uint64_t sample_rate, uint32_t buffer_size, uint32_t frame_rate, const std::shared_ptr<Buffers::BufferManager>& buffer_manager);
100
101 /**
102 * @brief Unregisters IOService, releases all owned readers, clears stored buffers.
103 */
104 ~IOManager();
105
106 IOManager(const IOManager&) = delete;
107 IOManager& operator=(const IOManager&) = delete;
108 IOManager(IOManager&&) = delete;
110
111 // ─────────────────────────────────────────────────────────────────────────
112 // Video — load
113 // ─────────────────────────────────────────────────────────────────────────
114
115 /**
116 * @brief Load a video file into a VideoFileContainer.
117 *
118 * Performs can_read check, opens the
119 * file with metadata extraction, creates the container, registers the reader
120 * with IOManager (assigning a globally unique reader_id), calls
121 * setup_io_service(), loads into container, and configures the default
122 * FrameAccessProcessor with auto_advance enabled.
123 *
124 * Audio is NOT extracted. Use load_video(path, options) for that.
125 *
126 * @param filepath Path to the video file.
127 * @return Loaded VideoFileContainer, or nullptr on failure.
128 */
129 [[nodiscard]] std::shared_ptr<Kakshya::VideoFileContainer> load_video(const std::string& filepath);
130
131 /**
132 * @brief Load a video file into a VideoFileContainer.
133 *
134 * Performs can_read check, opens with
135 * metadata and region extraction, registers the reader (assigning a globally
136 * unique reader_id), calls setup_io_service(), loads into container, and
137 * configures the default FrameAccessProcessor with auto_advance enabled.
138 *
139 * Passing VideoReadOptions::EXTRACT_AUDIO also extracts the embedded
140 * SoundFileContainer, configures its ContiguousAccessProcessor identically
141 * to load_audio_file() in Depot, and populates VideoLoadResult::audio.
142 *
143 * @param filepath Path to the video file.
144 * @param config LoadConfig struct containing options and target dimensions.
145 * @return VideoLoadResult containing video container and optional audio container.
146 */
147 [[nodiscard]] VideoLoadResult load_video(
148 const std::string& filepath, LoadConfig config);
149
150 // ─────────────────────────────────────────────────────────────────────────
151 // Video — hook
152 // ─────────────────────────────────────────────────────────────────────────
153
154 /**
155 * @brief Wire a VideoFileContainer to the graphics buffer system.
156 *
157 * Creates a VideoContainerBuffer via BufferManager with GRAPHICS_BACKEND token,
158 * stores it keyed by container pointer, and returns it.
159 *
160 * @param container Loaded VideoFileContainer to wire.
161 * @return Created VideoContainerBuffer, or nullptr on failure.
162 */
163 [[nodiscard]] std::shared_ptr<Buffers::VideoContainerBuffer>
164 hook_video_container_to_buffer(
165 const std::shared_ptr<Kakshya::VideoFileContainer>& container);
166
167 // ─────────────────────────────────────────────────────────────────────────
168 // Video — retrieve
169 // ─────────────────────────────────────────────────────────────────────────
170
171 /**
172 * @brief Retrieve the VideoContainerBuffer created for a container.
173 * @param container Container previously passed to hook_video_container_to_buffer().
174 * @return Stored buffer, or nullptr if not found.
175 */
176 [[nodiscard]] std::shared_ptr<Buffers::VideoContainerBuffer>
177 get_video_buffer(
178 const std::shared_ptr<Kakshya::VideoFileContainer>& container) const;
179
180 // ─────────────────────────────────────────────────────────────────────────
181 // Audio — load
182 // ─────────────────────────────────────────────────────────────────────────
183
184 /**
185 * @brief Load an audio file into a SoundFileContainer.
186 *
187 * Performs can_read check, opens the file, creates and populates the
188 * container, and configures the default ContiguousAccessProcessor
189 * with auto_advance enabled.
190 *
191 * @param filepath Path to the audio file.
192 * @param config LoadConfig struct containing audio read options.
193 * @return Loaded SoundFileContainer, or nullptr on failure.
194 */
195 [[nodiscard]] std::shared_ptr<Kakshya::SoundFileContainer> load_audio(const std::string& filepath, LoadConfig config = {});
196
197 /**
198 * @brief Load an audio file into a fully resident, size-bounded DynamicSoundStream.
199 *
200 * Applies the engine sample rate, ROW_MAJOR layout, and the engine buffer
201 * size as the CursorAccessProcessor block size. The result is ready for use
202 * with StreamSliceProcessor without any further configuration.
203 *
204 * @param filepath Path to the audio file.
205 * @param max_frames Upper bound on frame count. 0 defaults to 5 s at the
206 * engine sample rate inside SoundFileReader::load_bounded.
207 * @param truncate If true, silently truncate files exceeding max_frames.
208 * @return Configured DynamicSoundStream, or nullptr on failure.
209 */
210 [[nodiscard]] std::shared_ptr<Kakshya::DynamicSoundStream> load_audio_bounded(
211 const std::string& filepath,
212 uint64_t max_frames = 0,
213 bool truncate = false);
214
215 // ─────────────────────────────────────────────────────────────────────────
216 // Audio — hook
217 // ─────────────────────────────────────────────────────────────────────────
218
219 /**
220 * @brief Wire a SoundFileContainer to the audio buffer system.
221 *
222 * Creates per-channel SoundContainerBuffers with AUDIO_BACKEND token,
223 * stores them keyed by container pointer, and returns them.
224 *
225 * @param container Loaded SoundFileContainer to wire.
226 * @return Per-channel SoundContainerBuffers, or empty on failure.
227 */
228 [[nodiscard]] std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>>
229 hook_audio_container_to_buffers(
230 const std::shared_ptr<Kakshya::SoundFileContainer>& container);
231
232 // ─────────────────────────────────────────────────────────────────────────
233 // Audio — retrieve
234 // ─────────────────────────────────────────────────────────────────────────
235
236 /**
237 * @brief Retrieve the SoundContainerBuffers created for a container.
238 * @param container Container previously passed to hook_audio_container_to_buffers().
239 * @return Stored buffer vector, or empty if not found.
240 */
241 [[nodiscard]] std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>>
242 get_audio_buffers(
243 const std::shared_ptr<Kakshya::SoundFileContainer>& container) const;
244
245 /**
246 * @brief Retrieve the SoundFileContainer extracted from a video file.
247 * @param container VideoFileContainer whose audio was extracted during load_video().
248 * @return Extracted SoundFileContainer, or nullptr if not found.
249 */
250 [[nodiscard]] std::shared_ptr<Kakshya::SoundFileContainer>
251 get_extracted_audio(const std::shared_ptr<Kakshya::VideoFileContainer>& container) const;
252
253 // ─────────────────────────────────────────────────────────────────────────
254 // Camera — open
255 // ─────────────────────────────────────────────────────────────────────────
256
257 /**
258 * @brief Open a camera device and create a CameraContainer.
259 *
260 * Constructs a CameraReader, opens the device, creates the container,
261 * configures its FrameAccessProcessor (auto_advance disabled), assigns
262 * a globally unique reader_id, registers the reader for IOService
263 * dispatch, and wires the container's IOService callback via
264 * CameraContainer::setup_io().
265 *
266 * After this call, the normal processing pipeline drives frame pulls:
267 * CameraContainer::process_default() → IOService::request_frame(reader_id)
268 * → dispatch_frame_request() → CameraReader::pull_frame_all()
269 * → decode thread → pull_frame() → mutable_frame_ptr() write → READY.
270 *
271 * @param config Device and resolution configuration.
272 * @return Opened CameraContainer, or nullptr on failure.
273 */
274 [[nodiscard]] std::shared_ptr<Kakshya::CameraContainer>
275 open_camera(const CameraConfig& config);
276
277 // ─────────────────────────────────────────────────────────────────────────
278 // Camera — hook
279 // ─────────────────────────────────────────────────────────────────────────
280
281 /**
282 * @brief Wire a CameraContainer to the graphics buffer system.
283 *
284 * Creates a VideoContainerBuffer via BufferManager with GRAPHICS_BACKEND
285 * token, stores it keyed by container pointer, and returns it. Identical
286 * to hook_video_container_to_buffer() but accepts CameraContainer.
287 *
288 * @param container Opened CameraContainer from open_camera().
289 * @return Created VideoContainerBuffer, or nullptr on failure.
290 */
291 [[nodiscard]] std::shared_ptr<Buffers::VideoContainerBuffer>
292 hook_camera_to_buffer(const std::shared_ptr<Kakshya::CameraContainer>& container);
293
294 // ─────────────────────────────────────────────────────────────────────────
295 // Camera — retrieve
296 // ─────────────────────────────────────────────────────────────────────────
297
298 /**
299 * @brief Retrieve the VideoContainerBuffer created for a camera container.
300 */
301 [[nodiscard]] std::shared_ptr<Buffers::VideoContainerBuffer>
302 get_camera_buffer(const std::shared_ptr<Kakshya::CameraContainer>& container) const;
303
304 // ─────────────────────────────────────────────────────────────────────────
305 // Video reader management
306 // ─────────────────────────────────────────────────────────────────────────
307
308 /**
309 * @brief Assign a globally unique reader_id and take ownership of a reader.
310 *
311 * Called internally by load_video(). Advanced users who open a
312 * VideoFileReader manually can call this before load_into_container() to
313 * participate in managed dispatch and lifetime tracking.
314 *
315 * Calls reader->set_reader_id(id) before returning.
316 *
317 * @param reader Fully-opened VideoFileReader.
318 * @return Globally unique reader_id assigned to this reader.
319 */
320 [[nodiscard]] uint64_t register_video_reader(std::shared_ptr<VideoFileReader> reader);
321
322 /**
323 * @brief Release ownership of the reader identified by reader_id.
324 *
325 * When the last shared_ptr to the reader is released its decode thread
326 * is joined. Safe to call from a container state-change callback.
327 * No-op with a warning if reader_id is unknown.
328 *
329 * @param reader_id Id returned by register_video_reader().
330 */
331 void release_video_reader(uint64_t reader_id);
332
333 // ─────────────────────────────────────────────────────────────────────────
334 // Image — load
335 // ─────────────────────────────────────────────────────────────────────────
336
337 /**
338 * @brief Load an image file into a TextureBuffer.
339 *
340 * Opens the file via ImageReader, decodes to RGBA8, and creates a
341 * TextureBuffer containing the pixel data. The reader is retained
342 * for the lifetime of IOManager.
343 *
344 * @param filepath Path to the image file (PNG, JPG, BMP, TGA, etc.).
345 * @return TextureBuffer with pixel data, or nullptr on failure.
346 */
347 [[nodiscard]] std::shared_ptr<Buffers::TextureBuffer>
348 load_image(const std::string& filepath);
349
350 // ─────────────────────────────────────────────────────────────────────────
351 // Mesh — load
352 // ─────────────────────────────────────────────────────────────────────────
353
354 /**
355 * @brief Load all meshes from a 3D model file into MeshBuffer instances.
356 *
357 * Opens the file via ModelReader, extracts all aiMesh entries as MeshData,
358 * constructs one MeshBuffer per mesh, calls setup_processors() on each,
359 * and returns them. setup_rendering() is left to the caller.
360 *
361 * If resolver is null, the default resolver is used: paths resolved
362 * relative to the model file's directory via ImageReader::load_texture.
363 * Unresolvable textures are logged and skipped.
364 *
365 * @param filepath Path to the model file (glTF, FBX, OBJ, PLY, etc.).
366 * @param resolver Optional texture resolver.
367 * @return One MeshBuffer per mesh in scene order, or empty on failure.
368 */
369 [[nodiscard]] std::vector<std::shared_ptr<Buffers::MeshBuffer>>
370 load_mesh(
371 const std::string& filepath,
372 TextureResolver resolver = nullptr);
373
374 /**
375 * @brief Load a 3D model file as a MeshNetwork.
376 *
377 * One MeshSlot per aiMesh. Per-slot diffuse textures resolved via resolver.
378 * Null resolver uses the default: ImageReader::load_texture relative to the
379 * model file's directory.
380 *
381 * @param filepath Path to the model file.
382 * @param resolver Optional texture resolver.
383 * @return Populated MeshNetwork, or nullptr on failure.
384 */
385 [[nodiscard]] std::shared_ptr<Nodes::Network::MeshNetwork>
386 load_mesh_network(
387 const std::string& filepath,
388 TextureResolver resolver = nullptr);
389
390 // ─────────────────────────────────────────────────────────────────────────
391 // Image — save
392 // ─────────────────────────────────────────────────────────────────────────
393
394 /**
395 * @brief Save image data to disk asynchronously.
396 *
397 * The source is downloaded from the GPU on the calling thread (must have
398 * command queue access) and the encode + disk flush is dispatched to a
399 * worker. Returns once the download completes and the encode task is
400 * queued; encode/flush failures are logged but do not block the caller.
401 *
402 * For synchronous semantics (e.g. shutdown flush, tests) use the
403 * IO::save_image / IO::save_texture_buffer free functions in
404 * ImageExport.hpp.
405 *
406 * The extension of @p filepath selects the writer via
407 * ImageWriterRegistry. Format-variant compatibility is the writer's
408 * responsibility (PNG wants uint8, EXR wants float, etc.).
409 *
410 * @return True if the download succeeded and the encode task was queued.
411 */
412 bool save_image(
413 const std::shared_ptr<Core::VKImage>& image,
414 const std::string& filepath,
415 const IO::ImageWriteOptions& options = {});
416
417 bool save_image(
418 const std::shared_ptr<Buffers::TextureBuffer>& buffer,
419 const std::string& filepath,
420 const IO::ImageWriteOptions& options = {});
421
422 bool save_image(
423 const std::shared_ptr<Buffers::TextBuffer>& buffer,
424 const std::string& filepath,
425 const IO::ImageWriteOptions& options = {});
426
427 /**
428 * @brief Save an already-downloaded ImageData to disk asynchronously.
429 *
430 * For callers that have an ImageData in hand (e.g. from
431 * IO::download_image) and want to avoid the GPU download step. Purely
432 * CPU-bound from here, so no thread restrictions on the caller.
433 */
434 bool save_image(
435 IO::ImageData data,
436 const std::string& filepath,
437 const IO::ImageWriteOptions& options = {});
438
439 /**
440 * @brief Wait for all in-flight save operations to complete.
441 *
442 * Called automatically by the destructor. Invoke explicitly to flush
443 * pending saves before a checkpoint.
444 */
445 void wait_for_pending_saves();
446
447private:
449
451
452 uint32_t m_frame_rate;
453
454 /**
455 * @brief IOService::request_decode target — shared-lock lookup + signal_decode().
456 * Non-blocking. Safe from any thread.
457 */
458 void dispatch_decode_request(uint64_t reader_id);
459
460 /**
461 * @brief IOService::request_frame target — shared-lock lookup + pull_frame_all().
462 * Non-blocking. Safe from any thread.
463 */
464 void dispatch_frame_request(uint64_t reader_id);
465
466 void configure_frame_processor(
467 const std::shared_ptr<Kakshya::VideoFileContainer>& container);
468
469 void configure_audio_processor(
470 const std::shared_ptr<Kakshya::SoundFileContainer>& container);
471
472 // ── readers ──────────────────────────────────────────────────────
473
474 std::atomic<uint64_t> m_next_reader_id { 1 };
475 mutable std::shared_mutex m_readers_mutex;
476 mutable std::shared_mutex m_camera_mutex;
477 std::unordered_map<uint64_t, std::shared_ptr<VideoFileReader>> m_video_readers;
478 std::unordered_map<uint64_t, std::shared_ptr<CameraReader>> m_camera_readers;
479
480 std::vector<std::shared_ptr<SoundFileReader>> m_audio_readers;
481
482 std::vector<std::shared_ptr<ImageReader>> m_image_readers;
483
484 std::vector<std::shared_ptr<ModelReader>> m_model_readers;
485
486 // ── Stored buffers ─────────────────────────────────────────────────────
487
488 mutable std::shared_mutex m_buffers_mutex;
490 std::vector<std::future<bool>> m_save_tasks;
491
492 std::unordered_map<
493 std::shared_ptr<Kakshya::VideoFileContainer>,
494 std::shared_ptr<Buffers::VideoContainerBuffer>>
496
497 std::unordered_map<
498 std::shared_ptr<Kakshya::CameraContainer>,
499 std::shared_ptr<Buffers::VideoContainerBuffer>>
501
502 std::unordered_map<
503 std::shared_ptr<Kakshya::VideoFileContainer>,
504 std::shared_ptr<Kakshya::SoundFileContainer>>
506
507 std::unordered_map<
508 std::shared_ptr<Kakshya::SoundFileContainer>,
509 std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>>>
511
512 std::shared_ptr<Buffers::BufferManager> m_buffer_manager;
513
514 // ── IOService ──────────────────────────────────────────────────────────
515
516 std::shared_ptr<Registry::Service::IOService> m_io_service;
517};
518
519} // namespace MayaFlux::IO
IO::ImageData image
std::unordered_map< std::shared_ptr< Kakshya::VideoFileContainer >, std::shared_ptr< Kakshya::SoundFileContainer > > m_extracted_audio
std::unordered_map< std::shared_ptr< Kakshya::CameraContainer >, std::shared_ptr< Buffers::VideoContainerBuffer > > m_camera_buffers
std::unordered_map< uint64_t, std::shared_ptr< CameraReader > > m_camera_readers
std::unordered_map< std::shared_ptr< Kakshya::SoundFileContainer >, std::vector< std::shared_ptr< Buffers::SoundContainerBuffer > > > m_audio_buffers
IOManager(const IOManager &)=delete
std::mutex m_save_tasks_mutex
std::vector< std::future< bool > > m_save_tasks
std::vector< std::shared_ptr< SoundFileReader > > m_audio_readers
IOManager & operator=(IOManager &&)=delete
std::shared_mutex m_camera_mutex
std::vector< std::shared_ptr< ModelReader > > m_model_readers
IOManager(IOManager &&)=delete
std::shared_mutex m_buffers_mutex
std::shared_ptr< Buffers::BufferManager > m_buffer_manager
std::vector< std::shared_ptr< ImageReader > > m_image_readers
std::shared_ptr< Registry::Service::IOService > m_io_service
std::shared_mutex m_readers_mutex
std::unordered_map< uint64_t, std::shared_ptr< VideoFileReader > > m_video_readers
IOManager & operator=(const IOManager &)=delete
std::unordered_map< std::shared_ptr< Kakshya::VideoFileContainer >, std::shared_ptr< Buffers::VideoContainerBuffer > > m_video_buffers
Optional orchestration layer for IO reader lifetime and IOService dispatch.
Definition IOManager.hpp:91
bool save_image(const std::shared_ptr< Core::VKImage > &image, const std::string &filepath, const ImageWriteOptions &options)
Save a VKImage directly to disk via the ImageWriter registry.
AudioReadOptions
Audio-specific reading options.
@ DEINTERLEAVE
Output planar (per-channel) doubles instead of interleaved.
FileReadOptions
Generic options for file reading behavior.
@ EXTRACT_METADATA
Extract file metadata.
@ EXTRACT_REGIONS
Extract semantic regions (format-specific)
VideoReadOptions
Video-specific reading options.
std::function< std::shared_ptr< Core::VKImage >(const std::string &path)> TextureResolver
Callable that maps a raw material texture path to a GPU image.
Definition Creator.hpp:17
Platform-specific FFmpeg input format string for camera devices.
Configuration for image writing.
FileReadOptions file_options
Definition IOManager.hpp:45
AudioReadOptions audio_options
Definition IOManager.hpp:46
VideoReadOptions video_options
Definition IOManager.hpp:47
std::shared_ptr< Kakshya::SoundFileContainer > audio
Definition IOManager.hpp:62
std::shared_ptr< Kakshya::VideoFileContainer > video
Definition IOManager.hpp:61
Result of load_video() when audio extraction is requested via VideoReadOptions::EXTRACT_AUDIO.
Definition IOManager.hpp:60