MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
IOManager.cpp
Go to the documentation of this file.
1#include "IOManager.hpp"
2
6
8
15
19
22
26
27#include "EXRWriter.hpp"
28#include "ImageExport.hpp"
29#include "ModelReader.hpp"
30#include "STBImageWriter.hpp"
31
33
34extern "C" {
35#include <libavdevice/avdevice.h>
36}
37
38namespace MayaFlux::IO {
39
40namespace {
41
42 TextureResolver make_default_resolver(const std::string& filepath)
43 {
44 const auto base_dir = std::filesystem::path(filepath).parent_path();
45 return [base_dir](const std::string& raw) -> std::shared_ptr<Core::VKImage> {
46 auto tex_path = FileReader::resolve_path((base_dir / raw).generic_string());
47 return IO::ImageReader::load_texture(tex_path);
48 };
49 }
50
51}
52
53IOManager::IOManager(Core::GlobalStreamInfo& stream_info, uint32_t frame_rate, const std::shared_ptr<Buffers::BufferManager>& buffer_manager)
54 : m_stream_info(stream_info)
55 , m_frame_rate(frame_rate)
56 , m_buffer_manager(buffer_manager)
57{
58 m_io_service = std::make_shared<Registry::Service::IOService>();
59
60 m_io_service->request_decode = [this](uint64_t reader_id) {
61 dispatch_decode_request(reader_id);
62 };
63
66 [svc = m_io_service]() -> void* { return svc.get(); });
67
68 m_io_service->request_frame = [this](uint64_t reader_id) {
69 dispatch_frame_request(reader_id);
70 };
71
74
76}
77
79{
80 {
81 std::vector<uint32_t> ids;
82 {
83 std::lock_guard lock(m_audio_captures_mutex);
84 ids.reserve(m_audio_captures.size());
85 for (const auto& [id, _] : m_audio_captures)
86 ids.push_back(id);
87 }
88 for (auto id : ids)
89 stop_capture(id);
90 }
91
92 for (auto& w : m_writers) {
93 if (w->is_open()) {
94 auto fut = w->close();
95 fut.wait();
96 }
97 }
98
101
102 m_io_service.reset();
103
104 {
105 std::unique_lock lock(m_readers_mutex);
106 m_video_readers.clear();
107 }
108
109 {
110 std::unique_lock lock(m_camera_mutex);
111 m_camera_readers.clear();
112 }
113
114 {
115 std::unique_lock lock(m_buffers_mutex);
116 m_video_buffers.clear();
117 m_audio_buffers.clear();
118 }
119
121}
122
123std::shared_ptr<Kakshya::VideoFileContainer>
124IOManager::load_video(const std::string& filepath)
125{
126 return load_video(filepath, {}).video;
127}
128
130IOManager::load_video(const std::string& filepath, LoadConfig config)
131{
132 auto reader = std::make_shared<IO::VideoFileReader>();
133
134 if (!reader->can_read(filepath)) {
136 "Cannot read video file: {}", filepath);
137 return {};
138 }
139
140 reader->set_video_options(config.video_options);
141 reader->set_audio_options(config.audio_options);
142 reader->set_target_dimensions(config.target_width, config.target_height);
143
145 reader->set_target_sample_rate(m_stream_info.sample_rate);
146 }
147
148 if (!reader->open(filepath, config.file_options)) {
150 "Failed to open video file: {}", reader->get_last_error());
151 return {};
152 }
153
154 const uint64_t reader_id = register_video_reader(reader);
155 reader->setup_io_service(reader_id);
156
157 auto video_container = std::dynamic_pointer_cast<Kakshya::VideoFileContainer>(reader->create_container());
158
159 if (!video_container) {
161 "Failed to create video container from: {}", filepath);
162 release_video_reader(reader_id);
163 return {};
164 }
165
166 if (!reader->load_into_container(video_container)) {
168 "Failed to load video data: {}", reader->get_last_error());
169 release_video_reader(reader_id);
170 return {};
171 }
172
173 configure_frame_processor(video_container);
174
175 VideoLoadResult result;
176 result.video = video_container;
177
179 auto audio_container = reader->get_audio_container();
180 if (audio_container) {
181 configure_audio_processor(audio_container);
182 result.audio = audio_container;
183 m_extracted_audio[video_container] = audio_container;
184 } else {
186 "No audio track found in: {}", filepath);
187 }
188 }
189
191 "Loaded video: {}", filepath);
192
193 return result;
194}
195
196uint64_t IOManager::register_video_reader(std::shared_ptr<IO::VideoFileReader> reader)
197{
198 if (!reader) {
200 "IOManager::register_video_reader called with null reader");
201 return 0;
202 }
203
204 const uint64_t id = m_next_reader_id.fetch_add(1, std::memory_order_relaxed);
205 reader->set_reader_id(id);
206
207 {
208 std::unique_lock lock(m_readers_mutex);
209 m_video_readers.emplace(id, std::move(reader));
210 }
211
213 "IOManager: registered VideoFileReader id={}", id);
214
215 return id;
216}
217
218void IOManager::release_video_reader(uint64_t reader_id)
219{
220 std::unique_lock lock(m_readers_mutex);
221 auto it = m_video_readers.find(reader_id);
222
223 if (it == m_video_readers.end()) {
225 "IOManager::release_video_reader: unknown id={}", reader_id);
226 return;
227 }
228
229 m_video_readers.erase(it);
230
232 "IOManager: released VideoFileReader id={}", reader_id);
233}
234
236{
237 std::shared_lock lock(m_readers_mutex);
238 auto it = m_video_readers.find(reader_id);
239
240 if (it == m_video_readers.end()) {
242 "IOManager: dispatch_decode_request unknown reader_id={}", reader_id);
243 return;
244 }
245
246 it->second->signal_decode();
247}
248
249void IOManager::dispatch_frame_request(uint64_t reader_id)
250{
251 std::shared_lock lock(m_camera_mutex);
252 auto it = m_camera_readers.find(reader_id);
253
254 if (it == m_camera_readers.end()) {
256 "IOManager: dispatch_frame_request unknown reader_id={}", reader_id);
257 return;
258 }
259
260 it->second->pull_frame_all();
261}
262
263std::shared_ptr<Kakshya::SoundFileContainer> IOManager::load_audio(const std::string& filepath, LoadConfig config)
264{
265 auto reader = std::make_shared<IO::SoundFileReader>();
266
267 if (!reader->can_read(filepath)) {
268 MF_ERROR(Journal::Component::API, Journal::Context::FileIO, "Cannot read file: {}", filepath);
269 return nullptr;
270 }
271
272 reader->set_target_sample_rate(m_stream_info.sample_rate);
273 reader->set_audio_options(config.audio_options);
274
275 if (!reader->open(filepath, config.file_options)) {
276 MF_ERROR(Journal::Component::API, Journal::Context::FileIO, "Failed to open file: {}", reader->get_last_error());
277 return nullptr;
278 }
279
280 auto container = reader->create_container();
281 auto sound_container = std::dynamic_pointer_cast<Kakshya::SoundFileContainer>(container);
282 if (!sound_container) {
283 MF_ERROR(Journal::Component::API, Journal::Context::Runtime, "Failed to create sound container");
284 return nullptr;
285 }
286
287 if (!reader->load_into_container(sound_container)) {
288 MF_ERROR(Journal::Component::API, Journal::Context::Runtime, "Failed to load audio data: {}", reader->get_last_error());
289 return nullptr;
290 }
291
292 configure_audio_processor(sound_container);
293
294 m_audio_readers.push_back(std::move(reader));
295
296 return sound_container;
297}
298
299std::shared_ptr<Kakshya::DynamicSoundStream> IOManager::load_audio_bounded(
300 const std::string& filepath,
301 uint64_t max_frames,
302 bool truncate)
303{
304 auto reader = std::make_shared<IO::SoundFileReader>();
305
306 if (!reader->can_read(filepath)) {
308 "IOManager::load_bounded: unsupported format '{}'", filepath);
309 return nullptr;
310 }
311
312 reader->set_target_sample_rate(m_stream_info.sample_rate);
313
314 auto stream = reader->load_bounded(filepath, max_frames, truncate);
315 if (!stream) {
317 "IOManager::load_bounded: failed for '{}'", filepath);
318 return nullptr;
319 }
320
321 stream->set_memory_layout(Kakshya::MemoryLayout::ROW_MAJOR);
322
323 m_audio_readers.push_back(std::move(reader));
324
325 return stream;
326}
327
328// ─────────────────────────────────────────────────────────────────────────
329
330std::shared_ptr<SoundFileWriter>
331IOManager::create_writer(const std::string& filepath,
332 uint32_t channels,
333 uint32_t sample_rate,
334 AVCodecID codec_id)
335{
336 auto writer = std::make_shared<SoundFileWriter>();
337 if (!writer->open(filepath, channels, sample_rate, codec_id)) {
339 "create_writer: open failed for '{}': {}", filepath, writer->last_error());
340 return nullptr;
341 }
342
343 {
344 std::lock_guard lock(m_save_tasks_mutex);
345 m_writers.push_back(writer);
346 }
347 return writer;
348}
349
350void IOManager::write(const std::shared_ptr<Kakshya::SoundStreamContainer>& container,
351 const std::string& filepath,
352 AVCodecID codec_id)
353{
354 if (!container) {
356 "write: null container");
357 return;
358 }
359
360 auto writer = std::make_shared<SoundFileWriter>();
361 if (!writer->open(filepath,
362 container->get_num_channels(),
363 container->get_sample_rate(),
364 codec_id)) {
366 "write: open failed for '{}': {}", filepath, writer->last_error());
367 return;
368 }
369
370 writer->write(container->get_data());
371 auto fut = writer->close();
372
373 std::lock_guard lock(m_save_tasks_mutex);
374 m_save_tasks.push_back(std::move(fut));
375 std::erase_if(m_save_tasks, [](std::future<bool>& f) {
376 return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
377 });
378}
379
380uint32_t IOManager::capture_output(const std::string& filepath, AVCodecID codec_id)
381{
382
386 }
387
389 error<std::runtime_error>(Journal::Component::IO, Journal::Context::FileIO, std::source_location::current(),
390 "capture_output: AudioBackendService unavailable");
391 }
392
393 auto ct = std::make_shared<Kakshya::AudioOutputContainer>(m_stream_info);
394 ct->create_default_processor();
395
396 auto writer = std::make_shared<SoundFileWriter>();
397 if (!writer->open(filepath,
400 codec_id)) {
402 "capture_output: writer open failed for '{}': {}", filepath, writer->last_error());
403 return 0;
404 }
405
407 [ct, writer](const double*, uint32_t) {
408 ct->process_default();
409 const auto& vt = ct->get_processed_data();
410 if (!vt.empty())
411 writer->write(vt);
412 });
413
414 {
415 std::lock_guard lock(m_save_tasks_mutex);
416 m_writers.push_back(writer);
417 }
418
419 std::lock_guard lock(m_audio_captures_mutex);
420 m_audio_captures.emplace(obs_id, AudioCaptureState { .container = ct, .writer = writer, .observer_id = obs_id });
421 return obs_id;
422}
423
424void IOManager::stop_capture(uint32_t capture_id)
425{
426 AudioCaptureState state;
427 {
428 std::lock_guard lock(m_audio_captures_mutex);
429 auto it = m_audio_captures.find(capture_id);
430 if (it == m_audio_captures.end()) {
432 "stop_capture: unknown capture_id={}", capture_id);
433 return;
434 }
435 state = std::move(it->second);
436 m_audio_captures.erase(it);
437 }
438
441 if (svc)
443
444 auto fut = state.writer->close();
445
446 std::lock_guard lock(m_save_tasks_mutex);
447 m_save_tasks.push_back(std::move(fut));
448 std::erase_if(m_save_tasks, [](std::future<bool>& f) {
449 return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
450 });
451}
452
453std::vector<uint32_t> IOManager::get_audio_capture_ids() const
454{
455 std::lock_guard lock(m_audio_captures_mutex);
456 std::vector<uint32_t> ids;
457 ids.reserve(m_audio_captures.size());
458 for (const auto& [id, _] : m_audio_captures)
459 ids.push_back(id);
460 return ids;
461}
462
463std::shared_ptr<VideoFileWriter>
464IOManager::create_writer(const std::string& filepath,
465 uint32_t width,
466 uint32_t height,
467 double frame_rate,
468 AVPixelFormat src_pixel_format,
469 AVCodecID codec_id)
470{
471 auto writer = std::make_shared<VideoFileWriter>();
472 if (!writer->open(filepath, width, height, frame_rate, src_pixel_format, codec_id)) {
474 "create_writer: open failed for '{}': {}", filepath, writer->last_error());
475 return nullptr;
476 }
477 return writer;
478}
479
481 const std::shared_ptr<Core::Window>& window,
482 const std::string& filepath,
483 double frame_rate,
484 AVCodecID codec_id)
485{
486 if (!window) {
488 "capture_window: null window");
489 return 0;
490 }
491
492 auto writer = std::make_shared<VideoFileWriter>();
493 if (!writer->record(window, filepath, frame_rate, codec_id)) {
495 "capture_window: record failed for '{}': {}", filepath, writer->last_error());
496 return 0;
497 }
498
499 const uint32_t id = m_next_video_capture_id.fetch_add(1, std::memory_order_relaxed);
500
501 {
502 std::lock_guard lock(m_video_captures_mutex);
503 m_video_captures.emplace(id, VideoCaptureState { .writer = writer, .capture_id = id });
504 m_video_writers.push_back(writer);
505 }
506
507 return id;
508}
509
510void IOManager::stop_capture(const std::shared_ptr<Core::Window>& window)
511{
512 if (!window) {
514 "stop_capture(window): null window");
515 return;
516 }
517
518 uint32_t found_id = 0;
519 {
520 std::lock_guard lock(m_video_captures_mutex);
521 for (const auto& [id, state] : m_video_captures) {
522 if (state.writer->capture_window() == window) {
523 found_id = id;
524 break;
525 }
526 }
527 }
528
529 if (found_id == 0) {
531 "stop_capture(window): no active capture for window '{}'",
532 window->get_create_info().title);
533 return;
534 }
535
536 stop_capture(found_id);
537}
538
539std::vector<uint32_t> IOManager::get_video_capture_ids() const
540{
541 std::lock_guard lock(m_video_captures_mutex);
542 std::vector<uint32_t> ids;
543 ids.reserve(m_video_captures.size());
544 for (const auto& [id, _] : m_video_captures)
545 ids.push_back(id);
546 return ids;
547}
548
549std::vector<std::shared_ptr<VideoFileWriter>> IOManager::get_video_writers() const
550{
551 std::lock_guard lock(m_video_captures_mutex);
552 return m_video_writers;
553}
554
555std::shared_ptr<Kakshya::CameraContainer>
557{
558 static std::once_flag s_avdevice_init;
559 std::call_once(s_avdevice_init, [] { avdevice_register_all(); });
560
561 auto reader = std::make_shared<CameraReader>();
562
563 if (!reader->open(config)) {
565 "open_camera: failed — {}", reader->last_error());
566 return nullptr;
567 }
568
569 auto container = reader->create_container();
570 if (!container) {
572 "open_camera: failed to create container — {}",
573 reader->last_error());
574 return nullptr;
575 }
576
577 container->create_default_processor();
578
579 uint64_t rid = m_next_reader_id.fetch_add(1, std::memory_order_relaxed);
580 reader->set_container(container);
581
582 {
583 std::unique_lock lock(m_camera_mutex);
584 m_camera_readers[rid] = reader;
585 }
586
587 container->setup_io(rid);
588 container->mark_ready_for_processing(true);
589
591 "open_camera: reader_id={} device='{}' {}x{} @{:.1f}fps",
592 rid, config.device_name,
593 reader->width(), reader->height(), reader->frame_rate());
594
595 return container;
596}
597
598std::shared_ptr<Buffers::TextureBuffer>
599IOManager::load_image(const std::string& filepath)
600{
601 auto reader = std::make_shared<IO::ImageReader>();
602
603 if (!reader->open(filepath)) {
605 "Failed to open image: {}", filepath);
606 return nullptr;
607 }
608
609 auto texture_buffer = reader->create_texture_buffer();
610
611 if (!texture_buffer) {
613 "Failed to create texture buffer from: {}", filepath);
614 return nullptr;
615 }
616
617 m_image_readers.push_back(std::move(reader));
618
620 "Loaded image: {} ({}x{})",
621 std::filesystem::path(filepath).filename().string(),
622 texture_buffer->get_width(),
623 texture_buffer->get_height());
624
625 return texture_buffer;
626}
627
628std::vector<std::shared_ptr<Buffers::MeshBuffer>>
629IOManager::load_mesh(const std::string& filepath, TextureResolver resolver)
630{
631 auto reader = std::make_shared<ModelReader>();
632
633 if (!reader->can_read(filepath)) {
635 "IOManager::load_mesh: unsupported format '{}'", filepath);
636 return {};
637 }
638
639 if (!reader->open(filepath)) {
641 "IOManager::load_mesh: failed to open '{}' — {}",
642 filepath, reader->get_last_error());
643 return {};
644 }
645
646 if (!resolver)
647 resolver = make_default_resolver(filepath);
648
649 auto buffers = reader->create_mesh_buffers(resolver);
650 reader->close();
651
652 if (buffers.empty()) {
654 "IOManager::load_mesh: no meshes in '{}'", filepath);
655 return {};
656 }
657
659 "IOManager::load_mesh: {} mesh(es) from '{}'",
660 buffers.size(),
661 std::filesystem::path(filepath).filename().string());
662
663 return buffers;
664}
665
666std::shared_ptr<Nodes::Network::MeshNetwork>
667IOManager::load_mesh_network(const std::string& filepath, TextureResolver resolver)
668{
669 auto reader = std::make_shared<ModelReader>();
670
671 if (!reader->can_read(filepath)) {
673 "IOManager::load_mesh_network: unsupported format '{}'", filepath);
674 return nullptr;
675 }
676
677 if (!reader->open(filepath)) {
679 "IOManager::load_mesh_network: failed to open '{}' — {}",
680 filepath, reader->get_last_error());
681 return nullptr;
682 }
683
684 if (!resolver)
685 resolver = make_default_resolver(filepath);
686
687 auto net = reader->create_mesh_network(resolver);
688 reader->close();
689
690 if (!net) {
692 "IOManager::load_mesh_network: no network from '{}'", filepath);
693 return nullptr;
694 }
695
697 "IOManager::load_mesh_network: {} slots from '{}'",
698 net->slot_count(),
699 std::filesystem::path(filepath).filename().string());
700
701 return net;
702}
703
705 const std::shared_ptr<Kakshya::VideoFileContainer>& container)
706{
707 auto existing = std::dynamic_pointer_cast<Kakshya::FrameAccessProcessor>(
708 container->get_default_processor());
709
710 if (existing) {
711 existing->set_global_fps(m_frame_rate);
712 existing->set_auto_advance(true);
714 "Configured existing FrameAccessProcessor");
715 } else {
716 auto processor = std::make_shared<Kakshya::FrameAccessProcessor>();
717 processor->set_global_fps(m_frame_rate);
718 processor->set_auto_advance(true);
719 container->set_default_processor(processor);
721 "Created and set FrameAccessProcessor");
722 }
723}
724
726 const std::shared_ptr<Kakshya::SoundFileContainer>& container)
727{
728 container->set_memory_layout(Kakshya::MemoryLayout::ROW_MAJOR);
729
730 const std::vector<uint64_t> output_shape = {
732 container->get_num_channels()
733 };
734
735 auto existing = std::dynamic_pointer_cast<Kakshya::ContiguousAccessProcessor>(
736 container->get_default_processor());
737
738 if (existing) {
739 existing->set_output_size(output_shape);
740 existing->set_auto_advance(true);
742 "Configured existing ContiguousAccessProcessor");
743 } else {
744 auto processor = std::make_shared<Kakshya::ContiguousAccessProcessor>();
745 processor->set_output_size(output_shape);
746 processor->set_auto_advance(true);
747 container->set_default_processor(processor);
749 "Created and set ContiguousAccessProcessor");
750 }
751}
752
753std::shared_ptr<Buffers::VideoContainerBuffer>
755 const std::shared_ptr<Kakshya::VideoFileContainer>& container)
756{
757 if (!container) {
759 "hook_video_container_to_buffer: null container");
760 return nullptr;
761 }
762
763 auto stream_container = std::dynamic_pointer_cast<Kakshya::StreamContainer>(container);
764
765 if (!stream_container) {
767 "hook_video_container_to_buffer: container is not a VideoStreamContainer");
768 return nullptr;
769 }
770
771 auto video_buffer = m_buffer_manager->create_graphics_buffer<Buffers::VideoContainerBuffer>(
773 stream_container);
774
775 {
776 std::unique_lock lock(m_buffers_mutex);
777 m_video_buffers[container] = video_buffer;
778 }
779
781 "Hooked VideoFileContainer to VideoContainerBuffer ({}x{})",
782 video_buffer->get_width(), video_buffer->get_height());
783
784 return video_buffer;
785}
786
787std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>>
789 const std::shared_ptr<Kakshya::SoundFileContainer>& container)
790{
791 if (!container) {
793 "hook_audio_container_to_buffers: null container");
794 return {};
795 }
796
797 uint32_t num_channels = container->get_num_channels();
798 std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>> created_buffers;
799
800 MF_TRACE(
803 "Setting up audio playback for {} channels...",
804 num_channels);
805
806 for (uint32_t channel = 0; channel < num_channels; ++channel) {
807 auto container_buffer = m_buffer_manager->create_audio_buffer<MayaFlux::Buffers::SoundContainerBuffer>(
809 channel,
810 container,
811 channel);
812
813 container_buffer->initialize();
814
815 created_buffers.push_back(std::move(container_buffer));
816
817 MF_INFO(
820 "✓ Created buffer for channel {}",
821 channel);
822 }
823
824 m_audio_buffers[container] = created_buffers;
825
826 return created_buffers;
827}
828
829std::shared_ptr<Buffers::VideoContainerBuffer>
831 const std::shared_ptr<Kakshya::CameraContainer>& container)
832{
833 if (!container) {
835 "hook_camera_to_buffer: null container");
836 return nullptr;
837 }
838
839 auto stream_container = std::dynamic_pointer_cast<Kakshya::StreamContainer>(container);
840 if (!stream_container) {
842 "hook_camera_to_buffer: container is not a StreamContainer");
843 return nullptr;
844 }
845
846 auto video_buffer = m_buffer_manager->create_graphics_buffer<Buffers::VideoContainerBuffer>(
848 stream_container);
849
850 if (!video_buffer) {
852 "hook_camera_to_buffer: failed to create VideoContainerBuffer");
853 return nullptr;
854 }
855
856 {
857 std::unique_lock lock(m_camera_mutex);
858 m_camera_buffers[container] = video_buffer;
859 }
860
862 "Hooked CameraContainer to VideoContainerBuffer ({}x{})",
863 video_buffer->get_width(), video_buffer->get_height());
864
865 return video_buffer;
866}
867
868std::shared_ptr<Buffers::VideoContainerBuffer>
870 const std::shared_ptr<Kakshya::VideoFileContainer>& container) const
871{
872 std::shared_lock lock(m_buffers_mutex);
873 auto it = m_video_buffers.find(container);
874 return it != m_video_buffers.end() ? it->second : nullptr;
875}
876
877std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>>
879 const std::shared_ptr<Kakshya::SoundFileContainer>& container) const
880{
881 std::shared_lock lock(m_buffers_mutex);
882 auto it = m_audio_buffers.find(container);
883 return it != m_audio_buffers.end() ? it->second : std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>> {};
884}
885
886std::shared_ptr<Kakshya::SoundFileContainer>
887IOManager::get_extracted_audio(const std::shared_ptr<Kakshya::VideoFileContainer>& container) const
888{
889 std::shared_lock lock(m_buffers_mutex);
890 auto it = m_extracted_audio.find(container);
891 return it != m_extracted_audio.end() ? it->second : nullptr;
892}
893
894std::shared_ptr<Buffers::VideoContainerBuffer>
896 const std::shared_ptr<Kakshya::CameraContainer>& container) const
897{
898 std::shared_lock lock(m_camera_mutex);
899 auto it = m_camera_buffers.find(container);
900 return it != m_camera_buffers.end() ? it->second : nullptr;
901}
902
904 const std::shared_ptr<Core::VKImage>& image,
905 const std::string& filepath,
906 const IO::ImageWriteOptions& options)
907{
908 if (!image) {
910 "save_image: null image");
911 return false;
912 }
913
914 auto fut = std::async(std::launch::async,
915 [image, filepath, options]() -> bool {
916 auto data = IO::download_image(image);
917 if (!data) {
919 "save_image task: download failed for '{}'", filepath);
920 return false;
921 }
922
923 auto writer = IO::ImageWriterRegistry::instance().create_writer(filepath);
924 if (!writer) {
926 "save_image task: no writer registered for '{}'", filepath);
927 return false;
928 }
929
930 const bool ok = writer->write(filepath, *data, options);
931 if (!ok) {
933 "save_image task: writer failed for '{}': {}",
934 filepath, writer->get_last_error());
935 } else {
937 "save_image task: wrote '{}'", filepath);
938 }
939 return ok;
940 });
941
942 std::lock_guard lock(m_save_tasks_mutex);
943 m_save_tasks.push_back(std::move(fut));
944
945 std::erase_if(m_save_tasks, [](std::future<bool>& f) {
946 return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
947 });
948
949 return true;
950}
951
953 const std::shared_ptr<Buffers::TextureBuffer>& buffer,
954 const std::string& filepath,
955 const IO::ImageWriteOptions& options)
956{
957 if (!buffer) {
959 "save_image: null buffer");
960 return false;
961 }
962 auto image = buffer->get_gpu_texture();
963 if (!image) {
965 "save_image: buffer has no GPU texture");
966 return false;
967 }
968 return save_image(image, filepath, options);
969}
970
972 const std::shared_ptr<Buffers::TextBuffer>& buffer,
973 const std::string& filepath,
974 const IO::ImageWriteOptions& options)
975{
976 return save_image(
977 std::static_pointer_cast<Buffers::TextureBuffer>(buffer),
978 filepath,
979 options);
980}
981
983 IO::ImageData data,
984 const std::string& filepath,
985 const IO::ImageWriteOptions& options)
986{
987 auto fut = std::async(std::launch::async,
988 [data = std::move(data),
989 filepath,
990 options]() -> bool {
991 auto writer = IO::ImageWriterRegistry::instance().create_writer(filepath);
992 if (!writer) {
994 "save_image task: no writer registered for '{}'", filepath);
995 return false;
996 }
997 const bool ok = writer->write(filepath, data, options);
998 if (!ok) {
1000 "save_image task: writer failed for '{}': {}",
1001 filepath, writer->get_last_error());
1002 } else {
1004 "save_image task: wrote '{}'", filepath);
1005 }
1006 return ok;
1007 });
1008
1009 std::lock_guard lock(m_save_tasks_mutex);
1010 m_save_tasks.push_back(std::move(fut));
1011
1012 std::erase_if(m_save_tasks, [](std::future<bool>& f) {
1013 return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
1014 });
1015
1016 return true;
1017}
1018
1020{
1021 std::vector<std::future<bool>> tasks;
1022 {
1023 std::lock_guard lock(m_save_tasks_mutex);
1024 tasks.swap(m_save_tasks);
1025 }
1026 for (auto& f : tasks) {
1027 f.wait();
1028 }
1029}
1030
1031std::vector<uint64_t> IOManager::get_video_reader_ids() const
1032{
1033 std::shared_lock lock(m_readers_mutex);
1034 std::vector<uint64_t> ids;
1035 ids.reserve(m_video_readers.size());
1036 for (const auto& [id, _] : m_video_readers)
1037 ids.push_back(id);
1038 return ids;
1039}
1040
1041std::shared_ptr<VideoFileReader> IOManager::get_video_reader(uint64_t id) const
1042{
1043 std::shared_lock lock(m_readers_mutex);
1044 auto it = m_video_readers.find(id);
1045 return it != m_video_readers.end() ? it->second : nullptr;
1046}
1047
1048std::vector<uint64_t> IOManager::get_camera_reader_ids() const
1049{
1050 std::shared_lock lock(m_camera_mutex);
1051 std::vector<uint64_t> ids;
1052 ids.reserve(m_camera_readers.size());
1053 for (const auto& [id, _] : m_camera_readers)
1054 ids.push_back(id);
1055 return ids;
1056}
1057
1058std::shared_ptr<CameraReader> IOManager::get_camera_reader(uint64_t id) const
1059{
1060 std::shared_lock lock(m_camera_mutex);
1061 auto it = m_camera_readers.find(id);
1062 return it != m_camera_readers.end() ? it->second : nullptr;
1063}
1064
1065} // namespace MayaFlux::IO
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_TRACE(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
Core::GlobalStreamInfo stream
Definition Config.cpp:34
IO::ImageData image
Definition Decoder.cpp:57
uint32_t width
Definition Decoder.cpp:59
Cycle Behavior: The for_cycles(N) configuration controls how many times the capture operation execute...
void initialize()
Initialize the buffer after construction.
AudioBuffer implementation backed by a StreamContainer.
TextureBuffer implementation backed by a VideoStreamContainer.
static void register_with_registry()
Register this writer with the ImageWriterRegistry.
Definition EXRWriter.cpp:61
static std::string resolve_path(const std::string &filepath)
Resolve a filepath against the project source root if not found as-is.
std::unordered_map< std::shared_ptr< Kakshya::VideoFileContainer >, std::shared_ptr< Kakshya::SoundFileContainer > > m_extracted_audio
std::shared_ptr< Kakshya::VideoFileContainer > load_video(const std::string &filepath)
Load a video file into a VideoFileContainer.
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::shared_ptr< SoundFileWriter > create_writer(const std::string &filepath, uint32_t channels, uint32_t sample_rate=48000, AVCodecID codec_id=AV_CODEC_ID_NONE)
Construct an open SoundFileWriter the caller drives manually.
std::shared_ptr< CameraReader > get_camera_reader(uint64_t id) const
Returns the camera reader assigned to the given ID, or nullptr.
std::unordered_map< std::shared_ptr< Kakshya::SoundFileContainer >, std::vector< std::shared_ptr< Buffers::SoundContainerBuffer > > > m_audio_buffers
bool save_image(const std::shared_ptr< Core::VKImage > &image, const std::string &filepath, const IO::ImageWriteOptions &options={})
Save image data to disk asynchronously.
std::shared_ptr< Buffers::TextureBuffer > load_image(const std::string &filepath)
Load an image file into a TextureBuffer.
std::unordered_map< uint32_t, AudioCaptureState > m_audio_captures
std::vector< uint64_t > get_video_reader_ids() const
Returns all active video reader IDs.
std::shared_ptr< Kakshya::CameraContainer > open_camera(const CameraConfig &config)
Open a camera device and create a CameraContainer.
Registry::Service::AudioBackendService * m_audio_backend_service
std::mutex m_save_tasks_mutex
std::vector< std::shared_ptr< VideoFileWriter > > m_video_writers
std::vector< std::shared_ptr< Buffers::SoundContainerBuffer > > hook_audio_container_to_buffers(const std::shared_ptr< Kakshya::SoundFileContainer > &container)
Wire a SoundFileContainer to the audio buffer system.
std::vector< std::future< bool > > m_save_tasks
std::shared_ptr< Nodes::Network::MeshNetwork > load_mesh_network(const std::string &filepath, TextureResolver resolver=nullptr)
Load a 3D model file as a MeshNetwork.
Core::GlobalStreamInfo & m_stream_info
std::vector< uint32_t > get_video_capture_ids() const
Returns all active video capture IDs (capture_window()).
void dispatch_frame_request(uint64_t reader_id)
IOService::request_frame target — shared-lock lookup + pull_frame_all().
std::vector< uint64_t > get_camera_reader_ids() const
Returns all active camera reader IDs.
std::shared_ptr< Kakshya::DynamicSoundStream > load_audio_bounded(const std::string &filepath, uint64_t max_frames=0, bool truncate=false)
Load an audio file into a fully resident, size-bounded DynamicSoundStream.
uint32_t capture_window(const std::shared_ptr< Core::Window > &window, const std::string &filepath, double frame_rate, AVCodecID codec_id=AV_CODEC_ID_NONE)
Begin continuous capture of a window's rendered frames to a file.
void configure_frame_processor(const std::shared_ptr< Kakshya::VideoFileContainer > &container)
std::vector< std::shared_ptr< SoundFileReader > > m_audio_readers
std::vector< std::shared_ptr< Buffers::SoundContainerBuffer > > get_audio_buffers(const std::shared_ptr< Kakshya::SoundFileContainer > &container) const
Retrieve the SoundContainerBuffers created for a container.
std::vector< std::shared_ptr< VideoFileWriter > > get_video_writers() const
Returns all VideoFileWriters created via create_writer().
void stop_capture(uint32_t capture_id)
Stop a running capture and finalise the file.
std::vector< std::shared_ptr< SoundFileWriter > > m_writers
std::shared_mutex m_camera_mutex
std::vector< std::shared_ptr< Buffers::MeshBuffer > > load_mesh(const std::string &filepath, TextureResolver resolver=nullptr)
Load all meshes from a 3D model file into MeshBuffer instances.
std::shared_ptr< Buffers::VideoContainerBuffer > hook_video_container_to_buffer(const std::shared_ptr< Kakshya::VideoFileContainer > &container)
Wire a VideoFileContainer to the graphics buffer system.
std::mutex m_video_captures_mutex
std::vector< uint32_t > get_audio_capture_ids() const
Returns all active audio capture IDs.
void configure_audio_processor(const std::shared_ptr< Kakshya::SoundFileContainer > &container)
std::shared_ptr< VideoFileReader > get_video_reader(uint64_t id) const
Returns the video reader assigned to the given ID, or nullptr.
std::shared_ptr< Kakshya::SoundFileContainer > get_extracted_audio(const std::shared_ptr< Kakshya::VideoFileContainer > &container) const
Retrieve the SoundFileContainer extracted from a video file.
std::shared_ptr< Buffers::VideoContainerBuffer > get_video_buffer(const std::shared_ptr< Kakshya::VideoFileContainer > &container) const
Retrieve the VideoContainerBuffer created for a container.
std::shared_ptr< Buffers::VideoContainerBuffer > get_camera_buffer(const std::shared_ptr< Kakshya::CameraContainer > &container) const
Retrieve the VideoContainerBuffer created for a camera container.
void release_video_reader(uint64_t reader_id)
Release ownership of the reader identified by reader_id.
void dispatch_decode_request(uint64_t reader_id)
IOService::request_decode target — shared-lock lookup + signal_decode().
std::shared_mutex m_buffers_mutex
uint32_t capture_output(const std::string &filepath, AVCodecID codec_id=AV_CODEC_ID_NONE)
Begin continuous capture of live audio output to a file.
void write(const std::shared_ptr< Kakshya::SoundStreamContainer > &container, const std::string &filepath, AVCodecID codec_id=AV_CODEC_ID_NONE)
Write a SoundStreamContainer to file in one shot.
uint64_t register_video_reader(std::shared_ptr< VideoFileReader > reader)
Assign a globally unique reader_id and take ownership of a reader.
void wait_for_pending_saves()
Wait for all in-flight save operations to complete.
std::atomic< uint32_t > m_next_video_capture_id
std::shared_ptr< Buffers::VideoContainerBuffer > hook_camera_to_buffer(const std::shared_ptr< Kakshya::CameraContainer > &container)
Wire a CameraContainer to the graphics buffer system.
~IOManager()
Unregisters IOService, releases all owned readers, clears stored buffers.
Definition IOManager.cpp:78
std::shared_ptr< Buffers::BufferManager > m_buffer_manager
std::unordered_map< uint32_t, VideoCaptureState > m_video_captures
std::atomic< uint64_t > m_next_reader_id
std::vector< std::shared_ptr< ImageReader > > m_image_readers
IOManager(Core::GlobalStreamInfo &stream_info, uint32_t frame_rate, const std::shared_ptr< Buffers::BufferManager > &buffer_manager)
Construct IOManager and register the IOService into BackendRegistry.
Definition IOManager.cpp:53
std::shared_ptr< Registry::Service::IOService > m_io_service
std::shared_mutex m_readers_mutex
std::mutex m_audio_captures_mutex
std::unordered_map< uint64_t, std::shared_ptr< VideoFileReader > > m_video_readers
std::shared_ptr< Kakshya::SoundFileContainer > load_audio(const std::string &filepath, LoadConfig config={})
Load an audio file into a SoundFileContainer.
std::unordered_map< std::shared_ptr< Kakshya::VideoFileContainer >, std::shared_ptr< Buffers::VideoContainerBuffer > > m_video_buffers
static std::shared_ptr< Core::VKImage > load_texture(const std::string &path)
Load image directly into GPU texture (static utility)
std::unique_ptr< ImageWriter > create_writer(const std::string &filepath) const
static ImageWriterRegistry & instance()
static void register_with_registry()
Register this writer with the ImageWriterRegistry.
Interface * get_service()
Query for a backend service.
void register_service(ServiceFactory factory)
Register a backend service capability.
static BackendRegistry & instance()
Get the global registry instance.
void unregister_service()
Unregister a service.
@ AUDIO_BACKEND
Standard audio processing backend configuration.
@ GRAPHICS_BACKEND
Standard graphics processing backend configuration.
std::function< std::shared_ptr< Core::VKImage >(const std::string &)> TextureResolver
Callable that maps a raw material texture path to a GPU image.
Definition Depot.hpp:25
std::optional< ImageData > download_image(const std::shared_ptr< Core::VKImage > &image)
Download pixel data from a GPU-resident VKImage into host ImageData.
@ ContainerProcessing
Container operations (Kakshya - file/stream/region processing)
@ BufferManagement
Buffer Management (Buffers::BufferManager, creating buffers)
@ FileIO
Filesystem I/O operations.
@ Init
Engine/subsystem initialization.
@ AsyncIO
Async I/O operations ( network, streaming)
@ Runtime
General runtime operations (default fallback)
@ Core
Core engine, backend, subsystems.
@ IO
Networking, file handling, streaming.
@ API
MayaFlux/API Wrapper and convenience functions.
@ ROW_MAJOR
C/C++ style (last dimension varies fastest)
uint32_t channels
Number of discrete channels in this set.
uint32_t buffer_size
Number of samples per processing block.
uint32_t sample_rate
Number of samples processed per second (Hz)
ChannelConfig output
Configuration for output signal channels.
Comprehensive configuration for digital audio stream processing.
std::string device_name
Platform device string.
Platform-specific FFmpeg input format string for camera devices.
std::shared_ptr< Kakshya::AudioOutputContainer > container
std::shared_ptr< SoundFileWriter > writer
std::shared_ptr< VideoFileWriter > writer
Raw image data loaded from file.
Configuration for image writing.
FileReadOptions file_options
Definition IOManager.hpp:50
AudioReadOptions audio_options
Definition IOManager.hpp:51
VideoReadOptions video_options
Definition IOManager.hpp:52
std::shared_ptr< Kakshya::SoundFileContainer > audio
Definition IOManager.hpp:67
std::shared_ptr< Kakshya::VideoFileContainer > video
Definition IOManager.hpp:66
Result of load_video() when audio extraction is requested via VideoReadOptions::EXTRACT_AUDIO.
Definition IOManager.hpp:65
std::function< uint32_t(std::function< void(const double *, uint32_t)>)> register_output_observer
Register a per-cycle output observer.
std::function< void(uint32_t)> unregister_output_observer
Unregister a previously registered observer.
Backend audio subsystem service interface.
std::function< void(uint64_t reader_id)> request_frame
Request the identified camera reader to pull the next frame.
Definition IOService.hpp:44
Backend IO streaming service interface.
Definition IOService.hpp:18