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();
78 auto demux = std::make_shared<FFmpegDemuxContext>();
79 if (!demux->open(filepath)) {
84 auto video = std::make_shared<VideoStreamContext>();
90 std::shared_ptr<AudioStreamContext> audio;
94 audio = std::make_shared<AudioStreamContext>();
97 "VideoFileReader: no audio stream found or audio open failed");
181 const std::shared_ptr<FFmpegDemuxContext>& demux,
182 const std::shared_ptr<VideoStreamContext>& video)
const
186 demux->extract_container_metadata(meta);
187 video->extract_stream_metadata(*demux, meta);
194 const std::shared_ptr<FFmpegDemuxContext>& demux,
195 const std::shared_ptr<VideoStreamContext>& video)
const
197 std::vector<FileRegion> regions;
199 auto chapters = demux->extract_chapter_regions();
200 regions.insert(regions.end(),
201 std::make_move_iterator(chapters.begin()),
202 std::make_move_iterator(chapters.end()));
204 auto keyframes = video->extract_keyframe_regions(*demux);
205 regions.insert(regions.end(),
206 std::make_move_iterator(keyframes.begin()),
207 std::make_move_iterator(keyframes.end()));
225 "VideoFileReader::read_all() is not supported; "
226 "use create_container() + load_into_container()");
233 "VideoFileReader::read_region() is not supported; "
234 "use the container API to access regions");
249 return std::make_shared<Kakshya::VideoFileContainer>();
253 std::shared_ptr<Kakshya::SignalSourceContainer> container)
260 auto vc = std::dynamic_pointer_cast<Kakshya::VideoFileContainer>(container);
262 set_error(
"Container is not a VideoFileContainer");
266 std::shared_ptr<VideoStreamContext> video;
267 std::shared_ptr<AudioStreamContext> audio;
268 std::shared_ptr<FFmpegDemuxContext> demux;
280 const uint64_t total = video->total_frames;
282 set_error(
"Video stream reports 0 frames");
286 const uint32_t ring_cap = std::min(
288 static_cast<uint32_t
>(total));
294 vc->setup_ring(total, ring_cap,
295 video->out_width, video->out_height,
296 video->out_bytes_per_pixel, video->frame_rate,
300 static_cast<size_t>(video->out_linesize) * video->out_height);
304 && demux->find_best_stream(AVMEDIA_TYPE_AUDIO) >= 0;
306 if (want_audio && audio && audio->is_valid()) {
309 demux->seek(audio->stream_index, 0);
310 audio->flush_codec();
311 audio->drain_resampler_init();
324 "VideoFileReader: audio load failed: {}",
329 "VideoFileReader: open_from_demux failed: {}",
335 demux->seek(video->stream_index, 0);
336 video->flush_codec();
343 const uint64_t preload = std::min(
344 static_cast<uint64_t
>(ring_cap),
350 set_error(
"Failed to decode any frames during preload");
355 "VideoFileReader: preloaded {}/{} frames ({}x{}, {:.1f} fps, ring={})",
357 video->out_width, video->out_height,
358 video->frame_rate, ring_cap);
362 for (
const auto& [name, group] : region_groups)
363 vc->add_region_group(group);
365 vc->create_default_processor();
366 vc->mark_ready_for_processing(
true);
381 const size_t required =
static_cast<size_t>(
m_video->out_linesize) *
m_video->out_height;
386 const int packed_stride =
static_cast<int>(
389 uint64_t decoded = 0;
391 AVPacket* pkt = av_packet_alloc();
392 AVFrame* frame = av_frame_alloc();
393 if (!pkt || !frame) {
394 av_packet_free(&pkt);
395 av_frame_free(&frame);
399 uint8_t* sws_dst[1] = {
m_sws_buf.data() };
400 int sws_stride[1] = {
m_video->out_linesize };
402 auto write_frame_to_ring = [&]() ->
bool {
411 sws_scale(
m_video->sws_context,
412 frame->data, frame->linesize,
413 0,
static_cast<int>(
m_video->height),
414 sws_dst, sws_stride);
416 if (
m_video->out_linesize == packed_stride) {
417 std::memcpy(dest,
m_sws_buf.data(), frame_bytes);
419 for (uint32_t row = 0; row <
m_video->out_height; ++row) {
421 dest +
static_cast<size_t>(row) * packed_stride,
423 static_cast<size_t>(packed_stride));
431 av_frame_unref(frame);
435 while (decoded < batch_size) {
436 int ret = av_read_frame(
m_demux->format_context, pkt);
439 if (ret == AVERROR_EOF) {
440 avcodec_send_packet(
m_video->codec_context,
nullptr);
444 }
else if (pkt->stream_index !=
m_video->stream_index) {
445 av_packet_unref(pkt);
448 ret = avcodec_send_packet(
m_video->codec_context, pkt);
449 av_packet_unref(pkt);
450 if (ret < 0 && ret != AVERROR(EAGAIN))
454 while (decoded < batch_size) {
455 ret = avcodec_receive_frame(
m_video->codec_context, frame);
456 if (ret == AVERROR(EAGAIN))
458 if (ret == AVERROR_EOF)
461 av_frame_unref(frame);
465 if (!write_frame_to_ring())
469 if (ret == AVERROR_EOF)
474 av_packet_free(&pkt);
475 av_frame_free(&frame);
511 "VideoFileReader: decode thread — container expired");
516 const uint64_t total = vc->get_total_source_frames();
517 const uint32_t ring_cap = vc->get_ring_capacity();
524 const uint64_t read_pos = vc->get_read_position()[0];
529 const uint64_t buffered = (head > read_pos) ? (head - read_pos) : 0;
531 if (buffered >=
static_cast<uint64_t
>(ring_cap)) {
533 m_decode_cv.wait_for(lock, std::chrono::milliseconds(50), [&] {
536 const uint64_t h =
m_decode_head.load(std::memory_order_acquire);
537 const uint64_t rp = vc->get_read_position()[0];
538 const uint64_t ahead = (h > rp) ? (h - rp) : 0;
539 return ahead <= static_cast<uint64_t>(ring_cap - threshold);
544 const uint64_t want =
static_cast<uint64_t
>(ring_cap) - buffered;
545 const uint64_t capped = std::min(want, total - head);
546 const uint64_t batch = std::min(capped,
569 if (position.empty())
572 const uint64_t target_frame = position[0];
576 std::shared_ptr<VideoStreamContext> video;
577 std::shared_ptr<FFmpegDemuxContext> demux;
581 set_error(
"Cannot seek: reader not open");
597 vc->invalidate_ring();
598 vc->set_read_position({ target_frame });
600 const uint64_t total = vc->get_total_source_frames();
601 const uint64_t batch = std::min(
603 total > target_frame ? total - target_frame : 0UL);
614 const std::shared_ptr<FFmpegDemuxContext>& demux,
615 const std::shared_ptr<VideoStreamContext>& video,
616 uint64_t frame_position)
618 if (frame_position > video->total_frames)
619 frame_position = video->total_frames;
621 if (video->frame_rate <= 0.0) {
622 set_error(
"Invalid frame rate for seeking");
626 AVStream* stream = demux->get_stream(video->stream_index);
632 double target_seconds =
static_cast<double>(frame_position) / video->frame_rate;
633 auto ts =
static_cast<int64_t
>(target_seconds / av_q2d(stream->time_base));
635 if (!demux->seek(video->stream_index, ts)) {
640 video->flush_codec();
662 return { 0, 0, 0, 0 };
673 return {
"mp4",
"mkv",
"avi",
"mov",
"webm",
"flv",
"wmv",
"m4v",
"ts",
"mts" };
691 "VideoFileReader: {}", msg);
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
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.