MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
CameraReader.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <condition_variable>
7
8namespace MayaFlux::Kakshya {
9class CameraContainer;
10}
11
13class IOService;
14}
15
16namespace MayaFlux::IO {
17
18/**
19 * @brief Platform-specific FFmpeg input format string for camera devices.
20 */
21#if defined(MAYAFLUX_PLATFORM_LINUX)
22inline constexpr std::string_view CAMERA_FORMAT = "v4l2";
23#elif defined(MAYAFLUX_PLATFORM_MACOS)
24inline constexpr std::string_view CAMERA_FORMAT = "avfoundation";
25#elif defined(MAYAFLUX_PLATFORM_WINDOWS)
26inline constexpr std::string_view CAMERA_FORMAT = "dshow";
27#endif
28
29/**
30 * @struct CameraConfig
31 * @brief Configuration for opening a camera device via FFmpeg.
32 *
33 * Platform device string conventions:
34 * - Linux: "/dev/video0", "/dev/video1", …
35 * - macOS: "0" (AVFoundation device index) or "FaceTime HD Camera"
36 * - Windows: "video=Integrated Camera" (DirectShow filter name)
37 *
38 * Resolution and frame rate values are hints passed to the device driver
39 * via AVDictionary options. The device may negotiate different parameters;
40 * the actual negotiated values are available from CameraReader after open().
41 */
42struct MAYAFLUX_API CameraConfig {
43 std::string device_name; ///< Platform device string.
44 uint32_t target_width { 1920 }; ///< Requested width in pixels.
45 uint32_t target_height { 1080 }; ///< Requested height in pixels.
46 double target_fps { 30.0 }; ///< Hint only; device may ignore.
47 std::string format_override; ///< Leave empty to use CAMERA_FORMAT for current platform.
48};
49
50/**
51 * @class CameraReader
52 * @brief FFmpeg device reader for live camera input with background decode.
53 *
54 * Owns the FFmpeg demux and video codec contexts for a single camera device.
55 * Decodes frames on a dedicated thread signalled by IOService::request_frame,
56 * writing RGBA pixels directly into CameraContainer::mutable_frame_ptr() and
57 * marking the container READY. The graphics thread is never blocked by device
58 * I/O.
59 *
60 * Two integration paths:
61 * - Managed: IOManager::open_camera() handles registration, reader_id
62 * assignment, container wiring, and avdevice initialisation.
63 * - Standalone: open() → create_container() → setup_io_service(id) →
64 * set_container() → close(). Caller is responsible for
65 * avdevice_register_all() before open().
66 *
67 * Unlike VideoFileReader there is no ring buffer, no seek, and no batch
68 * decode — the device is a live unbounded source. One frame is pulled per
69 * process cycle, demand-driven by CameraContainer::process_default().
70 */
71class MAYAFLUX_API CameraReader {
72public:
75
76 CameraReader(const CameraReader&) = delete;
80
81 /**
82 * @brief Open a camera device using the supplied config.
83 * @param config Device name, resolution hint, fps hint, format override.
84 * @return True on success.
85 */
86 [[nodiscard]] bool open(const CameraConfig& config);
87
88 /**
89 * @brief Release codec, demux, and scratch buffer resources.
90 */
91 void close();
92
93 /**
94 * @brief True if the device is open and codec is ready.
95 */
96 [[nodiscard]] bool is_open() const;
97
98 /**
99 * @brief Create a CameraContainer sized to the negotiated device resolution.
100 * @return Initialised container with slot-0 allocated, ready for pull_frame().
101 */
102 [[nodiscard]] std::shared_ptr<Kakshya::CameraContainer> create_container() const;
103
104 /**
105 * @brief Decode one frame from the device into the container's m_data[0].
106 *
107 * Pumps packets until one decoded frame is available, converts to RGBA
108 * via swscale into container->mutable_frame_ptr(), then calls
109 * container->mark_ready_for_processing(true). Returns false on EAGAIN
110 * (no frame yet) — not an error, just no data available this cycle.
111 *
112 * The caller is responsible for invoking this once per graphics cycle,
113 * before the container's process_default() is triggered by downstream
114 * consumers. Typical integration: register as a graphics pre_process_hook
115 * or call explicitly before buffer processing.
116 *
117 * @param container Target CameraContainer.
118 * @return True if a new frame was written, false if no frame was available.
119 */
120 bool pull_frame(const std::shared_ptr<Kakshya::CameraContainer>& container);
121
122 /** @brief Negotiated output width in pixels. */
123 [[nodiscard]] uint32_t width() const;
124
125 /** @brief Negotiated output height in pixels. */
126 [[nodiscard]] uint32_t height() const;
127
128 /** @brief Negotiated frame rate in fps. */
129 [[nodiscard]] double frame_rate() const;
130
131 /**
132 * @brief Store a weak reference to the container for IOService dispatch.
133 *
134 * Called by IOManager::open_camera() after create_container(). Enables
135 * pull_frame_all() to resolve the container without an explicit argument.
136 *
137 * @param container CameraContainer created by this reader.
138 */
139 void set_container(const std::shared_ptr<Kakshya::CameraContainer>& container);
140
141 /**
142 * @brief Signal the background decode thread to pull one frame.
143 *
144 * Non-blocking. Called by IOManager::dispatch_frame_request() or the
145 * standalone IOService lambda. The decode thread wakes, calls pull_frame(),
146 * writes pixels into the container, marks it READY, then sleeps until the
147 * next signal. Safe to call from any thread.
148 */
149 void pull_frame_all();
150
151 /** @brief Last error string, empty if no error. */
152 [[nodiscard]] const std::string& last_error() const;
153
154 /**
155 * @brief Setup an IOService for this reader with the given reader_id.
156 * @param reader_id Globally unique ID assigned to this reader.
157 *
158 * This is method is called when working outside of IOManager for self registration.
159 * IOManager::open_camera() handles this automatically for managed readers.
160 */
161 void setup_io_service(uint64_t reader_id);
162
163private:
164 std::shared_ptr<FFmpegDemuxContext> m_demux;
165 std::shared_ptr<VideoStreamContext> m_video;
166 mutable std::shared_mutex m_ctx_mutex;
167 std::vector<uint8_t> m_sws_buf;
168 mutable std::string m_last_error;
169 std::weak_ptr<Kakshya::CameraContainer> m_container_ref;
170
171 std::shared_ptr<Registry::Service::IOService> m_standalone_service;
172 uint64_t m_standalone_reader_id {};
173 bool m_owns_service {};
174 bool m_scaler_ready {};
175
176 std::thread m_decode_thread;
177 std::mutex m_decode_mutex;
178 std::condition_variable m_decode_cv;
179 std::atomic<bool> m_decode_stop { false };
180 std::atomic<bool> m_decode_active { false };
181 std::atomic<bool> m_frame_requested { false };
182
183 void start_decode_thread();
184 void stop_decode_thread();
185 void decode_thread_func();
186};
187
188} // namespace MayaFlux::IO
CameraReader(const CameraReader &)=delete
CameraReader & operator=(const CameraReader &)=delete
std::shared_ptr< Registry::Service::IOService > m_standalone_service
std::vector< uint8_t > m_sws_buf
std::shared_mutex m_ctx_mutex
CameraReader & operator=(CameraReader &&)=delete
std::weak_ptr< Kakshya::CameraContainer > m_container_ref
std::shared_ptr< VideoStreamContext > m_video
std::condition_variable m_decode_cv
std::shared_ptr< FFmpegDemuxContext > m_demux
CameraReader(CameraReader &&)=delete
FFmpeg device reader for live camera input with background decode.
auto create_container(Args &&... args) -> std::shared_ptr< ContainerType >
creates a new container of the specified type
Definition Depot.hpp:41
std::string device_name
Platform device string.
std::string format_override
Leave empty to use CAMERA_FORMAT for current platform.
Platform-specific FFmpeg input format string for camera devices.