221 lines
8.5 KiB
C++
221 lines
8.5 KiB
C++
#pragma once
|
|
|
|
#include <flutter/encodable_value.h>
|
|
#include <flutter/method_channel.h>
|
|
#include <flutter/method_result.h>
|
|
#include <flutter/texture_registrar.h>
|
|
#include <d3d11.h>
|
|
#include <mfapi.h>
|
|
#include <mfcaptureengine.h>
|
|
#include <mfidl.h>
|
|
#include <wrl/client.h>
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#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<Camera> {
|
|
public:
|
|
using PlatformTaskPoster =
|
|
std::function<void(std::function<void()>, const char* tag)>;
|
|
|
|
Camera(int camera_id, flutter::TextureRegistrar* texture_registrar,
|
|
flutter::MethodChannel<flutter::EncodableValue>* channel,
|
|
CameraConfig config,
|
|
PlatformTaskPoster platform_task_poster);
|
|
~Camera();
|
|
|
|
int64_t RegisterTexture();
|
|
|
|
void Initialize(
|
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
|
|
|
void TakePicture(
|
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
|
|
|
void StartVideoRecording(
|
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
|
void StopVideoRecording(
|
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> 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<void()> 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<flutter::EncodableValue>* channel_;
|
|
PlatformTaskPoster platform_task_poster_;
|
|
std::shared_ptr<CameraTexture> 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<IMFCaptureEngine> capture_engine_;
|
|
ComPtr<IMFCapturePreviewSink> preview_sink_;
|
|
ComPtr<ID3D11Device> dx11_device_;
|
|
ComPtr<IMFDXGIDeviceManager> dxgi_device_manager_;
|
|
UINT dx_device_reset_token_ = 0;
|
|
|
|
// Negotiated media types (set in FindBaseMediaTypes before preview starts).
|
|
ComPtr<IMFMediaType> base_preview_media_type_;
|
|
ComPtr<IMFMediaType> 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<RecordHandler> record_handler_;
|
|
std::wstring current_record_path_;
|
|
std::atomic<bool> is_recording_{false};
|
|
int active_record_bitrate_ = 0;
|
|
|
|
// ── Preview / frame state ───────────────────────────────────────────────
|
|
std::atomic<bool> first_frame_received_{false};
|
|
std::atomic<bool> preview_paused_{false};
|
|
std::atomic<bool> image_streaming_{false};
|
|
|
|
// ── Latest frame for photo capture (natural BGRA) ─────────────────────
|
|
std::vector<uint8_t> latest_frame_;
|
|
std::mutex latest_frame_mutex_;
|
|
|
|
// ── Per-frame working buffer ────────────────────────────────────────────
|
|
std::vector<uint8_t> packed_frame_;
|
|
|
|
// ── Camera state ────────────────────────────────────────────────────────
|
|
CameraState state_ = CameraState::kCreated;
|
|
mutable std::mutex state_mutex_;
|
|
|
|
// ── Pending async MethodResults ─────────────────────────────────────────
|
|
mutable std::mutex pending_mutex_;
|
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
|
|
pending_init_;
|
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
|
|
pending_start_record_;
|
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
|
|
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<uint8_t> 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<bool> 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<std::function<void()>> dispose_callbacks_;
|
|
};
|
|
|
|
|
|
|