MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
CameraReader.cpp
Go to the documentation of this file.
1#include "CameraReader.hpp"
2
4
7
9
10extern "C" {
11#include <libavcodec/avcodec.h>
12#include <libavdevice/avdevice.h>
13#include <libavformat/avformat.h>
14#include <libavutil/dict.h>
15#include <libavutil/frame.h>
16#include <libavutil/imgutils.h>
17#include <libswscale/swscale.h>
18}
19
20namespace MayaFlux::IO {
21
22// std::once_flag CameraReader::s_avdevice_init;
23
25 : m_demux(std::make_shared<FFmpegDemuxContext>())
26 , m_video(std::make_shared<VideoStreamContext>())
27{
28}
29
30void CameraReader::setup_io_service(uint64_t reader_id)
31{
32 m_standalone_reader_id = reader_id;
33
35 .get_service<Registry::Service::IOService>()) {
36
37 m_standalone_service = std::make_shared<Registry::Service::IOService>();
38 m_standalone_service->request_frame = [this](uint64_t reader_id) {
39 if (reader_id == m_standalone_reader_id)
41 };
42
45 [this]() -> void* { return m_standalone_service.get(); });
46
47 m_owns_service = true;
48 }
49}
50
60
62{
63 close();
64
65 const std::string fmt_name = config.format_override.empty()
66 ? std::string(CAMERA_FORMAT)
67 : config.format_override;
68
69 AVDictionary* opts = nullptr;
70
71 std::string fps_str = std::to_string(static_cast<int>(config.target_fps));
72 av_dict_set(&opts, "framerate", fps_str.c_str(), 0);
73
74 std::string size_str = std::to_string(config.target_width)
75 + "x"
76 + std::to_string(config.target_height);
77
78 av_dict_set(&opts, "video_size", size_str.c_str(), 0);
79
80 if (!m_demux->open_device(config.device_name, fmt_name, &opts)) {
81 m_last_error = "Device open failed: " + m_demux->last_error();
83 "CameraReader::open — {}", m_last_error);
84 return false;
85 }
86
88
89 if (!m_video->open_device(*m_demux,
90 config.target_width,
91 config.target_height,
92 config.pixel_format)) {
93 m_last_error = "Video stream open failed: " + m_video->last_error();
95 "CameraReader::open — {}", m_last_error);
96 m_demux->close();
97 return false;
98 }
99
100 m_sws_buf.clear();
101 m_scaler_ready = false;
102
104 "CameraReader: opened '{}' via {} — {}x{} @{:.1f}fps (scaler deferred)",
105 config.device_name, fmt_name,
106 m_video->width, m_video->height, m_video->frame_rate);
107
108 return true;
109}
110
112{
114
115 std::unique_lock lock(m_ctx_mutex);
116 m_video->close();
117 m_demux->close();
118 m_scaler_ready = false;
119 m_sws_buf.clear();
120 m_sws_buf.shrink_to_fit();
121 m_last_error.clear();
122}
123
125{
126 std::shared_lock lock(m_ctx_mutex);
127 return m_demux->is_open() && m_video->is_codec_valid();
128}
129
130std::shared_ptr<Kakshya::CameraContainer>
132{
133 std::shared_lock lock(m_ctx_mutex);
134 if (!m_video->is_codec_valid()) {
135 m_last_error = "Cannot create container: reader not open";
136 return nullptr;
137 }
138
140 if (!fmt) {
141 m_last_error = "Requested pixel format has no ImageFormat equivalent";
142 return nullptr;
143 }
144
145 return std::make_shared<Kakshya::CameraContainer>(
146 m_video->out_width,
147 m_video->out_height,
148 *fmt,
149 m_video->frame_rate);
150}
151
153 const std::shared_ptr<Kakshya::CameraContainer>& container)
154{
155 if (!container)
156 return false;
157
158 std::shared_lock lock(m_ctx_mutex);
159 if (!m_video->is_codec_valid())
160 return false;
161
162 uint8_t* dest = container->mutable_frame_ptr();
163 if (!dest)
164 return false;
165
166 AVPacket* pkt = av_packet_alloc();
167 if (!pkt)
168 return false;
169
170 AVFrame* frame = av_frame_alloc();
171 if (!frame) {
172 av_packet_free(&pkt);
173 return false;
174 }
175
176 bool got_frame = false;
177
178 while (!got_frame) {
179 int ret = av_read_frame(m_demux->format_context, pkt);
180 if (ret < 0)
181 break;
182
183 if (pkt->stream_index != m_video->stream_index) {
184 av_packet_unref(pkt);
185 continue;
186 }
187
188 ret = avcodec_send_packet(m_video->codec_context, pkt);
189 av_packet_unref(pkt);
190
191 if (ret < 0 && ret != AVERROR(EAGAIN))
192 break;
193
194 ret = avcodec_receive_frame(m_video->codec_context, frame);
195 if (ret == AVERROR(EAGAIN))
196 continue;
197 if (ret < 0)
198 break;
199
200 if (!m_scaler_ready) {
201 if (!m_video->rebuild_scaler_from_frame(
202 frame,
203 static_cast<uint32_t>(frame->width),
204 static_cast<uint32_t>(frame->height))) {
206 "CameraReader: scaler init failed: {}",
207 m_video->last_error());
208 break;
209 }
210 const size_t buf_bytes = static_cast<size_t>(m_video->out_linesize) * m_video->out_height;
211 m_sws_buf.assign(buf_bytes, 0);
212 m_scaler_ready = true;
213 }
214
215 uint8_t* sws_dst[1] = { m_sws_buf.data() };
216 int sws_stride[1] = { m_video->out_linesize };
217
218 sws_scale(m_video->sws_context,
219 frame->data, frame->linesize,
220 0, static_cast<int>(m_video->height),
221 sws_dst, sws_stride);
222
223 const int packed_stride = static_cast<int>(m_video->out_width * m_video->out_bytes_per_pixel);
224
225 if (m_video->out_linesize == packed_stride) {
226 std::memcpy(dest, m_sws_buf.data(),
227 static_cast<size_t>(packed_stride) * m_video->out_height);
228 } else {
229 for (uint32_t row = 0; row < m_video->out_height; ++row) {
230 std::memcpy(
231 dest + static_cast<size_t>(row) * packed_stride,
232 m_sws_buf.data() + static_cast<size_t>(row) * m_video->out_linesize,
233 static_cast<size_t>(packed_stride));
234 }
235 }
236
237 container->mark_ready_for_processing(true);
238 got_frame = true;
239 }
240
241 av_frame_free(&frame);
242 av_packet_free(&pkt);
243
244 return got_frame;
245}
246
247uint32_t CameraReader::width() const
248{
249 std::shared_lock lock(m_ctx_mutex);
250 return m_video->out_width;
251}
252
253uint32_t CameraReader::height() const
254{
255 std::shared_lock lock(m_ctx_mutex);
256 return m_video->out_height;
257}
258
260{
261 std::shared_lock lock(m_ctx_mutex);
262 return m_video->frame_rate;
263}
264
266 const std::shared_ptr<Kakshya::CameraContainer>& container)
267{
268 m_container_ref = container;
270}
271
273{
274 {
275 std::lock_guard lock(m_decode_mutex);
276 m_frame_requested.store(true, std::memory_order_relaxed);
277 }
278 m_decode_cv.notify_one();
279}
280
281const std::string& CameraReader::last_error() const
282{
283 return m_last_error;
284}
285
287{
289
290 m_decode_stop.store(false);
291 m_decode_active.store(true);
293}
294
296{
297 if (!m_decode_active.load())
298 return;
299
300 m_decode_stop.store(true);
301 m_decode_cv.notify_all();
302
303 if (m_decode_thread.joinable())
304 m_decode_thread.join();
305
306 m_decode_active.store(false);
307}
308
310{
311 while (!m_decode_stop.load()) {
312 {
313 std::unique_lock lock(m_decode_mutex);
314 m_decode_cv.wait(lock, [this] {
315 return m_decode_stop.load()
316 || m_frame_requested.load(std::memory_order_relaxed);
317 });
318 }
319
320 if (m_decode_stop.load())
321 break;
322
323 m_frame_requested.store(false, std::memory_order_relaxed);
324
325 auto container = m_container_ref.lock();
326 if (container)
327 pull_frame(container);
328 }
329
330 m_decode_active.store(false);
331}
332
333} // namespace MayaFlux::IO
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
std::atomic< bool > m_decode_stop
uint32_t height() const
Negotiated output height in pixels.
std::atomic< bool > m_decode_active
void setup_io_service(uint64_t reader_id)
Setup an IOService for this reader with the given reader_id.
int m_requested_pixel_format
Pixel format requested at open() time, as an AVPixelFormat int.
void set_container(const std::shared_ptr< Kakshya::CameraContainer > &container)
Store a weak reference to the container for IOService dispatch.
std::atomic< bool > m_frame_requested
const std::string & last_error() const
Last error string, empty if no error.
std::shared_ptr< Registry::Service::IOService > m_standalone_service
std::vector< uint8_t > m_sws_buf
std::shared_mutex m_ctx_mutex
void close()
Release codec, demux, and scratch buffer resources.
bool is_open() const
True if the device is open and codec is ready.
std::weak_ptr< Kakshya::CameraContainer > m_container_ref
std::shared_ptr< VideoStreamContext > m_video
bool open(const CameraConfig &config)
Open a camera device using the supplied config.
std::condition_variable m_decode_cv
std::shared_ptr< FFmpegDemuxContext > m_demux
void pull_frame_all()
Signal the background decode thread to pull one frame.
bool pull_frame(const std::shared_ptr< Kakshya::CameraContainer > &container)
Decode one frame from the device into the container's m_data[0].
double frame_rate() const
Negotiated frame rate in fps.
std::shared_ptr< Kakshya::CameraContainer > create_container() const
Create a CameraContainer sized to the negotiated device resolution.
uint32_t width() const
Negotiated output width in pixels.
RAII owner of a single AVFormatContext and associated demux state.
RAII owner of one video stream's codec and pixel-format scaler state.
void register_service(ServiceFactory factory)
Register a backend service capability.
static BackendRegistry & instance()
Get the global registry instance.
void unregister_service()
Unregister a service.
std::optional< Portal::Graphics::ImageFormat > to_image_format(int av_pixel_format)
Map an AVPixelFormat to the Portal ImageFormat backing it.
@ FileIO
Filesystem I/O operations.
@ Runtime
General runtime operations (default fallback)
@ IO
Networking, file handling, streaming.
int pixel_format
Target AVPixelFormat as int; negative selects AV_PIX_FMT_RGBA.
std::string device_name
Platform device string.
std::string format_override
Leave empty to use CAMERA_FORMAT for current platform.
uint32_t target_width
Requested width in pixels.
uint32_t target_height
Requested height in pixels.
double target_fps
Hint only; device may ignore.
Platform-specific FFmpeg input format string for camera devices.
Backend IO streaming service interface.
Definition IOService.hpp:18