#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "camera_texture.h" #include "record_handler.h" using Microsoft::WRL::ComPtr; enum class CameraState { kCreated, kInitializing, kRunning, kPaused, kDisposing, kDisposed, }; struct CameraConfig { std::wstring symbolic_link; int resolution_preset = 4; // 0=low … 4=max bool enable_audio = false; int target_fps = 30; int target_bitrate = 0; // <=0 means use dynamic default ladder. int audio_bitrate = 0; }; class Camera : public std::enable_shared_from_this { public: using PlatformTaskPoster = std::function, const char* tag)>; Camera(int camera_id, flutter::TextureRegistrar* texture_registrar, flutter::MethodChannel* channel, CameraConfig config, PlatformTaskPoster platform_task_poster); ~Camera(); int64_t RegisterTexture(); void Initialize( std::unique_ptr> result); void TakePicture( std::unique_ptr> result); void StartVideoRecording( std::unique_ptr> result); void StopVideoRecording( std::unique_ptr> result); void StartImageStream(); void StopImageStream(); // FFI image stream access. void* GetImageStreamBuffer(); void RegisterImageStreamCallback(void (*callback)(int32_t)); void UnregisterImageStreamCallback(); void PausePreview(); void ResumePreview(); void DisposeAsync(std::function on_done); void Dispose(); bool IsDisposedOrDisposing() const; // Called from COM callbacks, must be public. void OnEngineEvent(IMFMediaEvent* event); void OnPreviewSample(IMFSample* sample); private: uint32_t MaxPreviewHeightForPreset() const; uint32_t MaxRecordHeightForPreset() const; int ComputeDefaultBitrate(int width, int height, int fps) const; HRESULT CreateCaptureEngine(); HRESULT FindBaseMediaTypes(); HRESULT StartPreviewInternal(); void CompleteInit(bool success, const std::string& error, int width = 0, int height = 0); void FailAllPendingResults(const std::string& error); void DisposeInternal(); void SendError(const std::string& description); int InitElapsedMs() const; static void FlipHorizontal(uint8_t* data, int width, int height); static void SwapRBChannels(uint8_t* data, int width, int height); void PostImageStreamFrame(const uint8_t* data, int width, int height); void ImageStreamLoop(); // ── Identity ──────────────────────────────────────────────────────────── int camera_id_; int64_t texture_id_ = -1; CameraConfig config_; flutter::TextureRegistrar* texture_registrar_; flutter::MethodChannel* channel_; PlatformTaskPoster platform_task_poster_; std::shared_ptr texture_; // Guards texture_ against concurrent teardown: preview samples arrive on an // MF callback thread and touch texture_, while DisposeInternal frees it on // the dispose thread. See CRASH.md. std::mutex texture_mutex_; // ── Capture engine + D3D11 ───────────────────────────────────────────── ComPtr capture_engine_; ComPtr preview_sink_; ComPtr dx11_device_; ComPtr dxgi_device_manager_; UINT dx_device_reset_token_ = 0; // Negotiated media types (set in FindBaseMediaTypes before preview starts). ComPtr base_preview_media_type_; ComPtr base_capture_media_type_; int preview_width_ = 0; int preview_height_ = 0; int record_width_ = 0; int record_height_ = 0; int record_fps_ = 0; // ── Recording ────────────────────────────────────────────────────────── std::unique_ptr record_handler_; std::wstring current_record_path_; std::atomic is_recording_{false}; int active_record_bitrate_ = 0; // ── Preview / frame state ─────────────────────────────────────────────── std::atomic first_frame_received_{false}; std::atomic preview_paused_{false}; std::atomic image_streaming_{false}; // ── Latest frame for photo capture (natural BGRA) ───────────────────── std::vector latest_frame_; std::mutex latest_frame_mutex_; // ── Per-frame working buffer ──────────────────────────────────────────── std::vector packed_frame_; // ── Camera state ──────────────────────────────────────────────────────── CameraState state_ = CameraState::kCreated; mutable std::mutex state_mutex_; // ── Pending async MethodResults ───────────────────────────────────────── mutable std::mutex pending_mutex_; std::unique_ptr> pending_init_; std::unique_ptr> pending_start_record_; std::unique_ptr> pending_stop_record_; // ── Init timing (diagnostics) ─────────────────────────────────────────── std::chrono::steady_clock::time_point create_start_{}; // ── Initialisation timeout ────────────────────────────────────────────── std::thread init_timeout_thread_; std::mutex init_timeout_cancel_mutex_; std::condition_variable init_timeout_cancel_cv_; bool init_timeout_cancelled_ = false; // ── Image stream delivery ─────────────────────────────────────────────── 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[1]; }; ImageStreamBuffer* image_stream_buffer_ = nullptr; size_t image_stream_buffer_size_ = 0; void (*image_stream_callback_)(int32_t) = nullptr; int64_t image_stream_sequence_ = 0; std::mutex image_stream_ffi_mutex_; struct ImageStreamSlot { std::vector data; int width = 0; int height = 0; bool dirty = false; }; std::mutex image_stream_mutex_; std::condition_variable image_stream_cv_; ImageStreamSlot image_stream_slot_; std::thread image_stream_thread_; std::atomic image_stream_running_{false}; std::thread image_stream_join_thread_; std::mutex image_stream_thread_mutex_; // ── Async dispose ─────────────────────────────────────────────────────── std::thread dispose_thread_; std::mutex dispose_mutex_; std::vector> dispose_callbacks_; };