12#include <libavcodec/avcodec.h>
13#include <libavformat/avformat.h>
14#include <libswscale/swscale.h>
30 .get_service<Registry::Service::IOService>()) {
32 m_io_service = std::make_shared<Registry::Service::IOService>();
33 m_io_service->request_decode = [
this](uint64_t reader_id) {
60 static const std::vector<std::string> exts = {
61 "mp4",
"mkv",
"avi",
"mov",
"webm",
"flv",
"wmv",
"m4v",
"ts",
"mts"
63 auto dot = filepath.rfind(
'.');
64 if (dot == std::string::npos)
66 std::string ext = filepath.substr(dot + 1);
67 std::ranges::transform(ext, ext.begin(), ::tolower);
68 return std::ranges::find(exts, ext) != exts.end();
79 auto demux = std::make_shared<FFmpegDemuxContext>();
80 if (!demux->open(resolved)) {
85 auto video = std::make_shared<VideoStreamContext>();
91 std::shared_ptr<AudioStreamContext> audio;
95 audio = std::make_shared<AudioStreamContext>();
98 "VideoFileReader: no audio stream found or audio open failed");
182 const std::shared_ptr<FFmpegDemuxContext>& demux,
183 const std::shared_ptr<VideoStreamContext>& video)
const
187 demux->extract_container_metadata(meta);
188 video->extract_stream_metadata(*demux, meta);
195 const std::shared_ptr<FFmpegDemuxContext>& demux,
196 const std::shared_ptr<VideoStreamContext>& video)
const
198 std::vector<FileRegion> regions;
200 auto chapters = demux->extract_chapter_regions();
201 regions.insert(regions.end(),
202 std::make_move_iterator(chapters.begin()),
203 std::make_move_iterator(chapters.end()));
205 auto keyframes = video->extract_keyframe_regions(*demux);
206 regions.insert(regions.end(),
207 std::make_move_iterator(keyframes.begin()),
208 std::make_move_iterator(keyframes.end()));
226 "VideoFileReader::read_all() is not supported; "
227 "use create_container() + load_into_container()");
234 "VideoFileReader::read_region() is not supported; "
235 "use the container API to access regions");
250 return std::make_shared<Kakshya::VideoFileContainer>();
254 std::shared_ptr<Kakshya::SignalSourceContainer> container)
261 auto vc = std::dynamic_pointer_cast<Kakshya::VideoFileContainer>(container);
263 set_error(
"Container is not a VideoFileContainer");
267 std::shared_ptr<VideoStreamContext> video;
268 std::shared_ptr<AudioStreamContext> audio;
269 std::shared_ptr<FFmpegDemuxContext> demux;
283 vc->set_source_format(
m_demux->format_context->iformat->name);
285 const uint64_t total = video->total_frames;
287 set_error(
"Video stream reports 0 frames");
291 const uint32_t ring_cap = std::min(
293 static_cast<uint32_t
>(total));
299 vc->setup_ring(total, ring_cap,
300 video->out_width, video->out_height,
301 video->out_bytes_per_pixel, video->frame_rate,
305 static_cast<size_t>(video->out_linesize) * video->out_height);
309 && demux->find_best_stream(AVMEDIA_TYPE_AUDIO) >= 0;
311 if (want_audio && audio && audio->is_valid()) {
314 demux->seek(audio->stream_index, 0);
315 audio->flush_codec();
316 audio->drain_resampler_init();
329 "VideoFileReader: audio load failed: {}",
334 "VideoFileReader: open_from_demux failed: {}",
340 demux->seek(video->stream_index, 0);
341 video->flush_codec();
348 const uint64_t preload = std::min(
349 static_cast<uint64_t
>(ring_cap),
355 set_error(
"Failed to decode any frames during preload");
360 "VideoFileReader: preloaded {}/{} frames ({}x{}, {:.1f} fps, ring={})",
362 video->out_width, video->out_height,
363 video->frame_rate, ring_cap);
367 for (
const auto& [name, group] : region_groups)
368 vc->add_region_group(group);
370 vc->create_default_processor();
371 vc->mark_ready_for_processing(
true);
386 const size_t required =
static_cast<size_t>(
m_video->out_linesize) *
m_video->out_height;
391 const int packed_stride =
static_cast<int>(
394 uint64_t decoded = 0;
396 AVPacket* pkt = av_packet_alloc();
397 AVFrame* frame = av_frame_alloc();
398 if (!pkt || !frame) {
399 av_packet_free(&pkt);
400 av_frame_free(&frame);
404 uint8_t* sws_dst[1] = {
m_sws_buf.data() };
405 int sws_stride[1] = {
m_video->out_linesize };
407 auto write_frame_to_ring = [&]() ->
bool {
416 sws_scale(
m_video->sws_context,
417 frame->data, frame->linesize,
418 0,
static_cast<int>(
m_video->height),
419 sws_dst, sws_stride);
421 if (
m_video->out_linesize == packed_stride) {
422 std::memcpy(dest,
m_sws_buf.data(), frame_bytes);
424 for (uint32_t row = 0; row <
m_video->out_height; ++row) {
426 dest +
static_cast<size_t>(row) * packed_stride,
428 static_cast<size_t>(packed_stride));
436 av_frame_unref(frame);
440 while (decoded < batch_size) {
441 int ret = av_read_frame(
m_demux->format_context, pkt);
444 if (ret == AVERROR_EOF) {
445 avcodec_send_packet(
m_video->codec_context,
nullptr);
449 }
else if (pkt->stream_index !=
m_video->stream_index) {
450 av_packet_unref(pkt);
453 ret = avcodec_send_packet(
m_video->codec_context, pkt);
454 av_packet_unref(pkt);
455 if (ret < 0 && ret != AVERROR(EAGAIN))
459 while (decoded < batch_size) {
460 ret = avcodec_receive_frame(
m_video->codec_context, frame);
461 if (ret == AVERROR(EAGAIN))
463 if (ret == AVERROR_EOF)
466 av_frame_unref(frame);
470 if (!write_frame_to_ring())
474 if (ret == AVERROR_EOF)
479 av_packet_free(&pkt);
480 av_frame_free(&frame);
516 "VideoFileReader: decode thread — container expired");
521 const uint64_t total = vc->get_total_source_frames();
522 const uint32_t ring_cap = vc->get_ring_capacity();
529 const uint64_t read_pos = vc->get_read_position()[0];
534 const uint64_t buffered = (head > read_pos) ? (head - read_pos) : 0;
536 if (buffered >=
static_cast<uint64_t
>(ring_cap)) {
538 m_decode_cv.wait_for(lock, std::chrono::milliseconds(50), [&] {
542 const uint64_t rp = vc->get_read_position()[0];
543 const uint64_t ahead = (
h > rp) ? (
h - rp) : 0;
544 return ahead <= static_cast<uint64_t>(ring_cap - threshold);
549 const uint64_t want =
static_cast<uint64_t
>(ring_cap) - buffered;
550 const uint64_t capped = std::min(want, total - head);
551 const uint64_t batch = std::min(capped,
577 const uint64_t target_frame =
position[0];
581 std::shared_ptr<VideoStreamContext> video;
582 std::shared_ptr<FFmpegDemuxContext> demux;
586 set_error(
"Cannot seek: reader not open");
602 vc->invalidate_ring();
603 vc->set_read_position({ target_frame });
605 const uint64_t total = vc->get_total_source_frames();
606 const uint64_t batch = std::min(
608 total > target_frame ? total - target_frame : 0UL);
619 const std::shared_ptr<FFmpegDemuxContext>& demux,
620 const std::shared_ptr<VideoStreamContext>& video,
621 uint64_t frame_position)
623 if (frame_position > video->total_frames)
624 frame_position = video->total_frames;
626 if (video->frame_rate <= 0.0) {
627 set_error(
"Invalid frame rate for seeking");
631 AVStream* stream = demux->get_stream(video->stream_index);
637 double target_seconds =
static_cast<double>(frame_position) / video->frame_rate;
638 auto ts =
static_cast<int64_t
>(target_seconds / av_q2d(stream->time_base));
640 if (!demux->seek(video->stream_index, ts)) {
645 video->flush_codec();
667 return { 0, 0, 0, 0 };
678 return {
"mp4",
"mkv",
"avi",
"mov",
"webm",
"flv",
"wmv",
"m4v",
"ts",
"mts" };
696 "VideoFileReader: {}", msg);
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
static std::string resolve_path(const std::string &filepath)
Resolve a filepath against the project source root if not found as-is.
static std::unordered_map< std::string, Kakshya::RegionGroup > regions_to_groups(const std::vector< FileRegion > ®ions)
Convert file regions to region groups.
std::string get_last_error() const override
Get the last error message encountered by the reader.
bool load_into_container(std::shared_ptr< Kakshya::SignalSourceContainer > container) override
Load file data into an existing SignalSourceContainer.
bool open_from_demux(std::shared_ptr< FFmpegDemuxContext > demux, std::shared_ptr< AudioStreamContext > audio, const std::string &filepath, FileReadOptions options=FileReadOptions::ALL)
Open an audio stream from an already-constructed demux and stream context.
void set_audio_options(AudioReadOptions options)
Set audio-specific read options.
std::shared_ptr< Kakshya::SignalSourceContainer > create_container() override
Create a SignalSourceContainer for this file.
void set_target_sample_rate(uint32_t sample_rate)
Set the target sample rate for resampling.
FFmpeg-based audio file reader for MayaFlux.
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
AudioReadOptions m_audio_options
void start_decode_thread()
uint32_t m_decode_batch_size
std::vector< FileRegion > m_cached_regions
std::atomic< bool > m_decode_stop
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
VideoReadOptions m_video_options
std::vector< uint8_t > m_sws_buf
One-frame sws scratch buffer (padded linesize, reused by decode thread).
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.
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.
uint32_t m_target_sample_rate
FileReadOptions m_options
bool is_open() const override
Check if a file is currently open.
std::shared_mutex m_context_mutex
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 decode_thread_func()
std::shared_ptr< VideoStreamContext > m_video
std::optional< FileMetadata > get_metadata() const override
Get metadata from the open file.
std::mutex m_metadata_mutex
std::shared_ptr< AudioStreamContext > m_audio
void close() override
Close the currently open file.
std::optional< FileMetadata > m_cached_metadata
void stop_decode_thread()
std::shared_ptr< Kakshya::SignalSourceContainer > create_container() override
Create and initialize a container from the file.
std::shared_ptr< Kakshya::SoundFileContainer > m_audio_container
uint32_t m_refill_threshold
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 signal_decode()
Non-blocking signal to the background decode thread.
std::thread m_decode_thread
~VideoFileReader() override
size_t get_num_dimensions() const override
Get the dimensionality of the file data.
std::mutex m_decode_mutex
std::vector< Kakshya::DataVariant > read_region(const FileRegion ®ion) 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.
File-backed video container — semantic marker over VideoStreamContainer.
uint8_t * mutable_slot_ptr(uint64_t frame_index)
Mutable pointer into m_data[0] for the decode thread to write into.
size_t get_frame_byte_size() const
Get the total byte size of one frame (width * height * channels).
uint64_t get_total_source_frames() const
void commit_frame(uint64_t frame_index)
Publish a decoded frame.
void register_service(ServiceFactory factory)
Register a backend service capability.
static BackendRegistry & instance()
Get the global registry instance.
void unregister_service()
Unregister a service.
@ 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)
@ NONE
No special options.
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
Generic region descriptor for any file type.
Backend IO streaming service interface.