MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VideoStreamContext.hpp
Go to the documentation of this file.
1#pragma once
2
5
6extern "C" {
7struct AVCodecContext;
8struct SwsContext;
9struct AVFrame;
10}
11
12namespace MayaFlux::IO {
13
14/**
15 * @class VideoStreamContext
16 * @brief RAII owner of one video stream's codec and pixel-format scaler state.
17 *
18 * Encapsulates all video-stream-specific FFmpeg objects:
19 * - AVCodecContext for the selected video stream
20 * - SwsContext for pixel-format conversion and optional rescaling
21 * - Cached video parameters: width, height, frame_rate, total_frames, pixel_format
22 *
23 * Does NOT own AVFormatContext — that belongs to FFmpegDemuxContext.
24 * Packet reading is always delegated to the demuxer's format_context;
25 * this context only decodes and converts packets it receives.
26 *
27 * The default output pixel format is AV_PIX_FMT_RGBA (4 bytes per pixel),
28 * chosen for direct compatibility with Vulkan's VK_FORMAT_R8G8B8A8_UNORM
29 * and the MayaFlux TextureBuffer / VKImage pipeline. For HDR workflows
30 * or compute-shader ingestion, callers can request AV_PIX_FMT_RGBAF32
31 * or other formats via the target_format parameter.
32 *
33 * Destruction order (enforced in destructor):
34 * sws_context → codec_context
35 * The associated FFmpegDemuxContext must outlive this object.
36 */
37class MAYAFLUX_API VideoStreamContext {
38public:
39 VideoStreamContext() = default;
41
46
47 // =========================================================================
48 // Lifecycle
49 // =========================================================================
50
51 /**
52 * @brief Open the video stream from an already-probed demux context.
53 *
54 * Finds the best video stream, allocates and opens the codec context,
55 * caches video parameters, and initialises the SwsContext for conversion
56 * to the target pixel format (default AV_PIX_FMT_RGBA).
57 *
58 * @param demux Open demux context (must outlive this object).
59 * @param target_width Output width in pixels; 0 = keep source width.
60 * @param target_height Output height in pixels; 0 = keep source height.
61 * @param target_format Target AVPixelFormat; negative = AV_PIX_FMT_RGBA.
62 * @return True on success.
63 */
64 bool open(const FFmpegDemuxContext& demux,
65 uint32_t target_width = 0,
66 uint32_t target_height = 0,
67 int target_format = -1);
68
69 /**
70 * @brief Open codec only, without initialising the SwsContext scaler.
71 *
72 * Intended for live capture devices (dshow, v4l2, avfoundation) where
73 * pix_fmt is AV_PIX_FMT_NONE until the first decoded frame arrives and
74 * sws_getContext therefore cannot be called at open time.
75 *
76 * After this call is_codec_valid() returns true; is_valid() returns false
77 * until rebuild_scaler_from_frame() has been called successfully.
78 *
79 * @param demux Open demux context (must outlive this object).
80 * @param target_width Desired output width (0 = use frame width).
81 * @param target_height Desired output height (0 = use frame height).
82 * @param target_format Desired AVPixelFormat (negative = AV_PIX_FMT_RGBA).
83 * @return True if codec was opened successfully.
84 */
85 [[nodiscard]] bool open_device(const FFmpegDemuxContext& demux,
86 uint32_t target_width = 0,
87 uint32_t target_height = 0,
88 int target_format = -1);
89
90 /**
91 * @brief True if the codec context is open and ready to receive packets.
92 * Does NOT require the SwsContext scaler to be initialised.
93 * Use this check in live-capture paths instead of is_valid().
94 */
95 [[nodiscard]] bool is_codec_valid() const
96 {
97 return codec_context && stream_index >= 0;
98 }
99
100 /**
101 * @brief Release codec and scaler resources.
102 * Safe to call multiple times.
103 */
104 void close();
105
106 /**
107 * @brief True if the codec and scaler are ready for decoding.
108 */
109 [[nodiscard]] bool is_valid() const
110 {
111 return codec_context && sws_context && stream_index >= 0;
112 }
113
114 /**
115 * @brief Rebuild the SwsContext using the pixel format resolved from a live
116 * decoded frame.
117 *
118 * dshow and similar capture devices on Windows leave pix_fmt as
119 * AV_PIX_FMT_NONE until the first decoded frame arrives. Call this once
120 * from the camera's frame-receive loop on the very first valid AVFrame to
121 * finalise the scaler before calling sws_scale.
122 *
123 * @param frame The first successfully decoded AVFrame.
124 * @param target_width Desired output width (0 = keep frame width).
125 * @param target_height Desired output height (0 = keep frame height).
126 * @param target_format Desired AVPixelFormat (negative = AV_PIX_FMT_RGBA).
127 * @return True if the scaler was (re)initialised successfully.
128 */
129 [[nodiscard]] bool rebuild_scaler_from_frame(
130 const AVFrame* frame,
131 uint32_t target_width = 0,
132 uint32_t target_height = 0,
133 int target_format = -1);
134
135 // =========================================================================
136 // Codec flush
137 // =========================================================================
138
139 /**
140 * @brief Flush codec internal buffers (call after a seek).
141 */
142 void flush_codec();
143
144 // =========================================================================
145 // Stream-level metadata extraction
146 // =========================================================================
147
148 /**
149 * @brief Populate stream-specific fields into an existing FileMetadata.
150 *
151 * Appends codec name, pixel format, dimensions, frame rate, bit_rate, etc.
152 *
153 * @param demux The demux context that owns the format_context.
154 * @param out Metadata struct to append into.
155 */
156 void extract_stream_metadata(const FFmpegDemuxContext& demux, FileMetadata& out) const;
157
158 /**
159 * @brief Extract keyframe positions as FileRegion entries.
160 * @param demux The demux context.
161 * @return Vector of FileRegion with type="keyframe".
162 */
163 [[nodiscard]] std::vector<FileRegion> extract_keyframe_regions(const FFmpegDemuxContext& demux) const;
164
165 // =========================================================================
166 // Error
167 // =========================================================================
168
169 [[nodiscard]] const std::string& last_error() const { return m_last_error; }
170
171 // =========================================================================
172 // Owned handles — accessible to VideoFileReader for decode loops
173 // =========================================================================
174
175 AVCodecContext* codec_context = nullptr; ///< Owned; freed in destructor.
176 SwsContext* sws_context = nullptr; ///< Owned; freed in destructor.
177
178 int stream_index = -1;
179 uint64_t total_frames {};
180 uint32_t width {}; ///< Source width in pixels.
181 uint32_t height {}; ///< Source height in pixels.
182 uint32_t out_width {}; ///< Output width after scaling.
183 uint32_t out_height {}; ///< Output height after scaling.
184 double frame_rate {}; ///< Average frame rate (fps).
185 int src_pixel_format = -1; ///< Source AVPixelFormat.
186 int out_pixel_format = -1; ///< Output AVPixelFormat.
187 uint32_t out_bytes_per_pixel = 4; ///< Bytes per pixel in output format.
188 int out_linesize {}; ///< Output row stride in bytes.
189
190 uint32_t target_width {}; ///< Requested output width (0 = source).
191 uint32_t target_height {}; ///< Requested output height (0 = source).
192 int target_format = -1; ///< Requested AVPixelFormat (negative = RGBA).
193
194private:
195 std::string m_last_error;
196
197 /**
198 * @brief Allocate and initialise the SwsContext for pixel format conversion.
199 * @param target_width Desired output width (0 = source width).
200 * @param target_height Desired output height (0 = source height).
201 * @param target_format Desired AVPixelFormat (negative = AV_PIX_FMT_RGBA).
202 * @return True on success.
203 */
204 bool setup_scaler(uint32_t target_width, uint32_t target_height, int target_format);
205};
206
207/**
208 * @brief Map an AVPixelFormat to the Portal ImageFormat backing it.
209 *
210 * Covers the formats a container can store. Returns nullopt for anything
211 * without a direct equivalent, including planar YUV and packed 24-bit
212 * layouts that swscale must convert before a container sees them.
213 *
214 * @param av_pixel_format AVPixelFormat as int; negative means RGBA.
215 */
216[[nodiscard]] std::optional<Portal::Graphics::ImageFormat> to_image_format(int av_pixel_format);
217
218} // namespace MayaFlux::IO
uint32_t width
uint32_t height
RAII owner of a single AVFormatContext and associated demux state.
VideoStreamContext(const VideoStreamContext &)=delete
const std::string & last_error() const
VideoStreamContext(VideoStreamContext &&)=delete
VideoStreamContext & operator=(VideoStreamContext &&)=delete
VideoStreamContext & operator=(const VideoStreamContext &)=delete
bool is_codec_valid() const
True if the codec context is open and ready to receive packets.
bool is_valid() const
True if the codec and scaler are ready for decoding.
RAII owner of one video stream's codec and pixel-format scaler state.
std::optional< Portal::Graphics::ImageFormat > to_image_format(int av_pixel_format)
Map an AVPixelFormat to the Portal ImageFormat backing it.
Generic metadata structure for any file type.