#ifndef CAMERA_H_ #define CAMERA_H_ #include #include #include #include #include #include #include "camera_texture.h" #include "device_enumerator.h" #include "record_handler.h" enum class CameraBackend { kV4L2, // Traditional /dev/video* + v4l2src kPipeWire, // Portal-authorized pipewiresrc }; enum class CameraState { kCreated, kInitializing, kRunning, kPaused, kDisposing, kDisposed, }; // Alias for the image-stream callback function pointer type. using ImageStreamCallback = void (*)(int32_t); struct CameraConfig { std::string device_path; int resolution_preset; bool enable_audio; int target_width; int target_height; int target_fps; int target_bitrate; int audio_bitrate = 0; CameraBackend backend = CameraBackend::kV4L2; int pw_fd = -1; // PipeWire remote fd (only used when backend == kPipeWire) }; class Camera { public: Camera(int camera_id, FlTextureRegistrar* texture_registrar, FlMethodChannel* method_channel, const CameraConfig& config); ~Camera(); int camera_id() const { return camera_id_; } int64_t texture_id() const { return texture_id_; } CameraState state() const { return state_; } // Allocates the texture and registers it. Must be called before Initialize. // Returns the texture_id on success, -1 on failure. int64_t RegisterTexture(); // Builds and starts the GStreamer pipeline. Responds to |method_call| // asynchronously once the first frame arrives or an error/timeout occurs. void Initialize(FlMethodCall* method_call); // Captures a still image and saves it to a temporary JPEG file. // Responds to |method_call| with the file path or an error. void TakePicture(FlMethodCall* method_call); // Pauses/resumes the live preview. void PausePreview(); void ResumePreview(); // Starts video recording (silent, no audio). void StartVideoRecording(FlMethodCall* method_call); // Stops video recording and returns the file path. void StopVideoRecording(FlMethodCall* method_call); // Starts/stops sending raw frame data to Dart via method channel. void StartImageStream(); void StopImageStream(); // FFI image stream access. void* GetImageStreamBuffer() const { return image_stream_buffer_; } void RegisterImageStreamCallback(void (*callback)(int32_t)); void UnregisterImageStreamCallback(); // Toggles horizontal mirroring on the live video feed. void SetMirror(bool mirrored); // Tears down the pipeline and releases all resources. void Dispose(); private: bool BuildPipeline(GError** error); void RespondToPendingInit(bool success, const char* error_message); // GStreamer callbacks (static with user_data = Camera*). static GstFlowReturn OnNewSample(GstAppSink* sink, gpointer user_data); static gboolean OnBusMessage(GstBus* bus, GstMessage* msg, gpointer user_data); static gboolean OnInitTimeout(gpointer user_data); // Sends an error event to Dart via the method channel. void SendError(const std::string& description); int camera_id_; int64_t texture_id_; // state_ is written from the GStreamer streaming thread (OnNewSample) and // read/written from the main thread. Must be atomic. (C-2) std::atomic state_; CameraConfig config_; FlTextureRegistrar* texture_registrar_; // Not owned. FlMethodChannel* method_channel_; // Not owned. CameraTexture* texture_; // Owned (GObject ref). GstElement* pipeline_; GstElement* tee_; // For branching preview + recording. GstElement* appsink_; GstElement* videoflip_; // Named element in pipeline for mirror toggle. guint bus_watch_id_; guint init_timeout_id_; std::unique_ptr record_handler_; // Pending async initialization, stores the FlMethodCall until first frame. // Only accessed from the main thread (set in Initialize, cleared in // RespondToPendingInit which is always dispatched via g_idle_add to main). FlMethodCall* pending_init_call_; // Written from the GStreamer streaming thread; read from main thread. (C-2) std::atomic first_frame_received_; // Read from GStreamer streaming thread, written from main thread. (C-3) std::atomic preview_paused_; std::atomic image_streaming_; // FFI image stream shared buffer. // NOTE: The |ready| field acts as a release/acquire flag between the // GStreamer thread (writer) and Dart (reader). The native side MUST issue a // std::atomic_thread_fence(release) before writing ready=1, ensuring all // pixel writes are visible before Dart observes ready==1. (C-5) struct ImageStreamBuffer { int64_t sequence; int32_t width; int32_t height; int32_t bytes_per_row; int32_t format; // 0=BGRA, 1=RGBA int32_t ready; // 1=Dart may read, 0=native writing int32_t _pad; uint8_t pixels[]; // flexible array member }; ImageStreamBuffer* image_stream_buffer_ = nullptr; size_t image_stream_buffer_size_ = 0; // Written from the main thread, read from the GStreamer streaming thread. // Must be atomic to avoid data races and torn reads. (C-4) std::atomic image_stream_callback_{nullptr}; int64_t image_stream_sequence_ = 0; // Written from the GStreamer streaming thread on first frame, read from the // main thread in StartVideoRecording. Must be atomic. (H-2) std::atomic actual_width_; std::atomic actual_height_; }; #endif // CAMERA_H_