MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
FFmpegDemuxContext.cpp
Go to the documentation of this file.
2
3extern "C" {
4#include <libavcodec/avcodec.h>
5#include <libavdevice/avdevice.h>
6#include <libavformat/avformat.h>
7#include <libavutil/opt.h>
8}
9
10namespace MayaFlux::IO {
11
12// =========================================================================
13// Static members
14// =========================================================================
15
16std::atomic<bool> FFmpegDemuxContext::s_ffmpeg_initialized { false };
18
19// =========================================================================
20// Lifecycle
21// =========================================================================
22
27
29{
30 std::lock_guard<std::mutex> lock(s_ffmpeg_init_mutex);
31 if (!s_ffmpeg_initialized.exchange(true)) {
32 av_log_set_level(AV_LOG_WARNING);
33 }
34}
35
36bool FFmpegDemuxContext::open(const std::string& filepath)
37{
38 close();
40
41 if (avformat_open_input(&format_context, filepath.c_str(), nullptr, nullptr) < 0) {
42 m_last_error = "avformat_open_input failed: " + filepath;
43 return false;
44 }
45
46 if (avformat_find_stream_info(format_context, nullptr) < 0) {
47 avformat_close_input(&format_context);
48 m_last_error = "avformat_find_stream_info failed: " + filepath;
49 return false;
50 }
51
52 return true;
53}
54
56{
57 if (format_context) {
58 avformat_close_input(&format_context);
59 format_context = nullptr;
60 }
61 m_last_error.clear();
62}
63
64bool FFmpegDemuxContext::open_device(const std::string& device_name,
65 const std::string& format_name,
66 AVDictionary** options)
67{
68 close();
70
71 const AVInputFormat* fmt = av_find_input_format(format_name.c_str());
72 if (!fmt) {
73 m_last_error = "av_find_input_format failed for: " + format_name;
74 return false;
75 }
76
77 int ret = avformat_open_input(&format_context,
78 device_name.c_str(), fmt, options);
79 if (ret < 0) {
80 char errbuf[AV_ERROR_MAX_STRING_SIZE];
81 av_strerror(ret, errbuf, sizeof(errbuf));
82 m_last_error = "avformat_open_input failed for device: "
83 + device_name + " (" + errbuf + ")";
84 return false;
85 }
86
87 if (avformat_find_stream_info(format_context, nullptr) < 0) {
88 avformat_close_input(&format_context);
89 m_last_error = "avformat_find_stream_info failed for device: " + device_name;
90 return false;
91 }
92
93 return true;
94}
95
96// =========================================================================
97// Stream discovery
98// =========================================================================
99
100int FFmpegDemuxContext::find_best_stream(int media_type, const void** out_codec) const
101{
102 if (!format_context)
103 return -1;
104
105 const AVCodec* codec = nullptr;
106 int idx = av_find_best_stream(
108 static_cast<AVMediaType>(media_type),
109 -1, -1,
110 &codec,
111 0);
112
113 if (out_codec)
114 *out_codec = codec;
115
116 return idx;
117}
118
119AVStream* FFmpegDemuxContext::get_stream(int index) const
120{
121 if (!format_context || index < 0 || static_cast<unsigned>(index) >= format_context->nb_streams)
122 return nullptr;
123 return format_context->streams[index];
124}
125
127{
128 return format_context ? format_context->nb_streams : 0U;
129}
130
131// =========================================================================
132// Seeking
133// =========================================================================
134
135bool FFmpegDemuxContext::seek(int stream_index, int64_t timestamp)
136{
137 if (!format_context)
138 return false;
139
140 int ret = av_seek_frame(format_context, stream_index, timestamp, AVSEEK_FLAG_BACKWARD);
141 if (ret < 0) {
142 m_last_error = "av_seek_frame failed";
143 return false;
144 }
145 return true;
146}
147
149{
150 // No-op at format level; codec flush is the stream context's responsibility.
151 // Provided here so callers have a symmetric flush call site.
152}
153
154// =========================================================================
155// Metadata / regions
156// =========================================================================
157
159{
160 if (!format_context)
161 return;
162
163 out.format = format_context->iformat->name;
164 out.mime_type = format_context->iformat->mime_type
165 ? format_context->iformat->mime_type
166 : "application/" + std::string(format_context->iformat->name);
167
168 if (format_context->duration != AV_NOPTS_VALUE)
169 out.attributes["duration_seconds"] = (double)format_context->duration / static_cast<double>(AV_TIME_BASE);
170
171 out.attributes["bit_rate"] = static_cast<int64_t>(format_context->bit_rate);
172
173 AVDictionaryEntry* tag = nullptr;
174 while ((tag = av_dict_get(format_context->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
175 out.attributes[std::string("tag_") + tag->key] = std::string(tag->value);
176}
177
178std::vector<FileRegion> FFmpegDemuxContext::extract_chapter_regions() const
179{
180 std::vector<FileRegion> regions;
181 if (!format_context)
182 return regions;
183
184 for (unsigned i = 0; i < format_context->nb_chapters; ++i) {
185 const AVChapter* ch = format_context->chapters[i];
186 FileRegion r;
187 r.type = "chapter";
188 r.name = "chapter_" + std::to_string(i);
189
190 AVDictionaryEntry* title = av_dict_get(ch->metadata, "title", nullptr, 0);
191 if (title)
192 r.name = title->value;
193
194 double tb = av_q2d(ch->time_base);
195 r.start_coordinates = { static_cast<uint64_t>(ch->start * tb * 1000) };
196 r.end_coordinates = { static_cast<uint64_t>(ch->end * tb * 1000) };
197 r.attributes["chapter_index"] = static_cast<int>(i);
198
199 regions.push_back(std::move(r));
200 }
201 return regions;
202}
203
205{
206 if (!format_context || format_context->duration == AV_NOPTS_VALUE)
207 return 0.0;
208 return static_cast<double>(format_context->duration) / static_cast<double>(AV_TIME_BASE);
209}
210
211} // namespace MayaFlux::IO
void flush()
Flush the demuxer's internal read buffers.
bool open(const std::string &filepath)
Open a media file and probe stream information.
AVFormatContext * format_context
Owned; freed in destructor.
void close()
Close the format context and release all demux resources.
static void init_ffmpeg()
Initialise FFmpeg logging level once per process.
bool open_device(const std::string &device_name, const std::string &format_name, AVDictionary **options=nullptr)
Open an FFmpeg device input (camera, screen capture, etc.).
int find_best_stream(int media_type, const void **out_codec=nullptr) const
Find the best stream of the requested media type.
unsigned int stream_count() const
Number of streams in the container.
AVStream * get_stream(int index) const
Access a stream by index.
static std::atomic< bool > s_ffmpeg_initialized
bool seek(int stream_index, int64_t timestamp)
Seek to the nearest keyframe at or before the given timestamp.
std::vector< FileRegion > extract_chapter_regions() const
Extract chapter information as FileRegion entries.
void extract_container_metadata(FileMetadata &out_metadata) const
Extract container-level metadata tags into a FileMetadata attributes map.
double duration_seconds() const
Total container duration in seconds, or 0 if unknown.
std::unordered_map< std::string, std::any > attributes
Type-specific metadata stored as key-value pairs (e.g., sample rate, channels)
std::string format
File format identifier (e.g., "wav", "mp3", "hdf5")
std::string mime_type
MIME type if applicable (e.g., "audio/wav")
Generic metadata structure for any file type.
std::vector< uint64_t > start_coordinates
N-dimensional start position (e.g., frame, x, y)
std::string name
Human-readable name for the region.
std::string type
Region type identifier (e.g., "cue", "scene", "block")
std::unordered_map< std::string, std::any > attributes
Region-specific metadata.
std::vector< uint64_t > end_coordinates
N-dimensional end position (inclusive)
Generic region descriptor for any file type.