From 692036dadf96f229f864c7f3cdcd851fe66dd119 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Wed, 4 Dec 2024 15:03:03 +0200 Subject: [PATCH 01/16] Revert "[camera_windows] Revert: Support image streams on Windows platform (#7951)" This reverts commit b3a5e33f1cf39c62e187cb3060814bae4e2efff2. --- .../camera_windows/lib/camera_windows.dart | 58 +++++ .../camera_windows/lib/src/messages.g.dart | 50 ++++ .../camera_windows/lib/type_conversion.dart | 25 ++ .../camera_windows/pigeons/messages.dart | 8 + .../camera/camera_windows/windows/camera.h | 2 + .../camera_windows/windows/camera_plugin.cpp | 75 ++++++ .../camera_windows/windows/camera_plugin.h | 8 + .../windows/capture_controller.cpp | 39 +++ .../windows/capture_controller.h | 15 +- .../camera_windows/windows/messages.g.cpp | 72 ++++++ .../camera_windows/windows/messages.g.h | 8 + .../windows/test/camera_plugin_test.cpp | 226 ++++++++++++++++++ .../camera_windows/windows/test/mocks.h | 19 ++ 13 files changed, 604 insertions(+), 1 deletion(-) create mode 100644 packages/camera/camera_windows/lib/type_conversion.dart diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 523b41b6975..bdbfc17dcf8 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; import 'src/messages.g.dart'; +import 'type_conversion.dart'; /// An implementation of [CameraPlatform] for Windows. class CameraWindows extends CameraPlatform { @@ -32,6 +33,12 @@ class CameraWindows extends CameraPlatform { final Map hostCameraHandlers = {}; + // The stream to receive frames from the native code. + StreamSubscription? _platformImageStreamSubscription; + + // The stream for vending frames to platform interface clients. + StreamController? _frameStreamController; + /// The controller that broadcasts events coming from handleCameraMethodCall /// /// It is a `broadcast` because multiple controllers will connect to @@ -234,6 +241,57 @@ class CameraWindows extends CameraPlatform { 'resumeVideoRecording() is not supported due to Win32 API limitations.'); } + @override + Stream onStreamedFrameAvailable(int cameraId, + {CameraImageStreamOptions? options}) { + _installStreamController( + onListen: () => _onFrameStreamListen(cameraId), + onCancel: () => _onFrameStreamCancel(cameraId)); + return _frameStreamController!.stream; + } + + StreamController _installStreamController( + {void Function()? onListen, void Function()? onCancel}) { + _frameStreamController = StreamController( + onListen: onListen ?? () {}, + onPause: _onFrameStreamPauseResume, + onResume: _onFrameStreamPauseResume, + onCancel: onCancel ?? () {}, + ); + return _frameStreamController!; + } + + void _onFrameStreamListen(int cameraId) { + _startPlatformStream(cameraId); + } + + Future _startPlatformStream(int cameraId) async { + _startStreamListener(); + await _hostApi.startImageStream(cameraId); + } + + void _startStreamListener() { + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera_android/imageStream'); + _platformImageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { + _frameStreamController! + .add(cameraImageFromPlatformData(imageData as Map)); + }); + } + + FutureOr _onFrameStreamCancel(int cameraId) async { + await _hostApi.stopImageStream(cameraId); + await _platformImageStreamSubscription?.cancel(); + _platformImageStreamSubscription = null; + _frameStreamController = null; + } + + void _onFrameStreamPauseResume() { + throw CameraException('InvalidCall', + 'Pause and resume are not supported for onStreamedFrameAvailable'); + } + @override Future setFlashMode(int cameraId, FlashMode mode) async { // TODO(jokerttu): Implement flash mode support, https://github.com/flutter/flutter/issues/97537. diff --git a/packages/camera/camera_windows/lib/src/messages.g.dart b/packages/camera/camera_windows/lib/src/messages.g.dart index a351d6d9cee..9930549183b 100644 --- a/packages/camera/camera_windows/lib/src/messages.g.dart +++ b/packages/camera/camera_windows/lib/src/messages.g.dart @@ -362,6 +362,56 @@ class CameraApi { } } + /// Starts the image stream for the given camera. + Future startImageStream(int cameraId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([cameraId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Stops the image stream for the given camera. + Future stopImageStream(int cameraId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_windows.CameraApi.stopImageStream$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([cameraId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + /// Starts the preview stream for the given camera. Future pausePreview(int cameraId) async { final String pigeonVar_channelName = diff --git a/packages/camera/camera_windows/lib/type_conversion.dart b/packages/camera/camera_windows/lib/type_conversion.dart new file mode 100644 index 00000000000..ed24a752341 --- /dev/null +++ b/packages/camera/camera_windows/lib/type_conversion.dart @@ -0,0 +1,25 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; + +/// Converts method channel call [data] for `receivedImageStreamData` to a +/// [CameraImageData]. +CameraImageData cameraImageFromPlatformData(Map data) { + return CameraImageData( + format: const CameraImageFormat(ImageFormatGroup.bgra8888, raw: 0), + height: data['height'] as int, + width: data['width'] as int, + lensAperture: data['lensAperture'] as double?, + sensorExposureTime: data['sensorExposureTime'] as int?, + sensorSensitivity: data['sensorSensitivity'] as double?, + planes: [ + CameraImagePlane( + bytes: data['data'] as Uint8List, + bytesPerRow: (data['width'] as int) * 4, + ) + ]); +} diff --git a/packages/camera/camera_windows/pigeons/messages.dart b/packages/camera/camera_windows/pigeons/messages.dart index cb70021a0ef..d0970af423a 100644 --- a/packages/camera/camera_windows/pigeons/messages.dart +++ b/packages/camera/camera_windows/pigeons/messages.dart @@ -70,6 +70,14 @@ abstract class CameraApi { @async String stopVideoRecording(int cameraId); + /// Starts the image stream for the given camera. + @async + void startImageStream(int cameraId); + + /// Stops the image stream for the given camera. + @async + void stopImageStream(int cameraId); + /// Starts the preview stream for the given camera. @async void pausePreview(int cameraId); diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 44e9fcde588..2a44ad2ccf6 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -22,6 +22,8 @@ enum class PendingResultType { kTakePicture, kStartRecord, kStopRecord, + kStartStream, + kStopStream, kPausePreview, kResumePreview, }; diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 089aa28c722..4b2abe39d6a 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -4,6 +4,8 @@ #include "camera_plugin.h" +#include +#include #include #include #include @@ -32,6 +34,10 @@ namespace { const std::string kPictureCaptureExtension = "jpeg"; const std::string kVideoCaptureExtension = "mp4"; +constexpr char kFrameEventChannelName[] = + "plugins.flutter.io/camera_android/imageStream"; + +std::unique_ptr> event_sink; // Builds CaptureDeviceInfo object from given device holding device name and id. std::unique_ptr GetDeviceInfo(IMFActivate* device) { @@ -116,12 +122,34 @@ std::optional GetFilePathForVideo() { } } // namespace +// a setter for the event sink helpful for testing. +void CameraPlugin::SetEventSink( + std::unique_ptr> events) { + event_sink = std::move(events); +} + // static void CameraPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows* registrar) { std::unique_ptr plugin = std::make_unique( registrar->texture_registrar(), registrar->messenger()); + auto frameEventchannel = std::make_unique>( + registrar->messenger(), kFrameEventChannelName, + &flutter::StandardMethodCodec::GetInstance()); + + auto event_channel_handler = + std::make_unique>( + [plugin = plugin.get()](auto arguments, auto events) { + plugin->SetEventSink(std::move(events)); + return nullptr; + }, + [](auto arguments) { + event_sink.reset(); + return nullptr; + }); + frameEventchannel->SetStreamHandler(std::move(event_channel_handler)); + CameraApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); @@ -341,6 +369,53 @@ void CameraPlugin::StopVideoRecording( } } +void CameraPlugin::StartImageStream( + int64_t camera_id, + std::function reply)> result) { + // check if request already exists + Camera* camera = GetCameraByCameraId(camera_id); + if (!camera) { + return result(FlutterError("camera_error", "Camera not created")); + } + if (camera->HasPendingResultByType(PendingResultType::kStartStream)) { + return result( + FlutterError("camera_error", "Pending start stream request exists")); + } + + if (!event_sink) { + return result(FlutterError("camera_error", + "Unable to make event channel from windows")); + } + + if (camera->AddPendingVoidResult(PendingResultType::kStartStream, + std::move(result))) { + CaptureController* cc = camera->GetCaptureController(); + assert(cc); + cc->StartImageStream(std::move(event_sink)); + } +} + +void CameraPlugin::StopImageStream( + int64_t camera_id, + std::function reply)> result) { + // check if request already exists + Camera* camera = GetCameraByCameraId(camera_id); + if (!camera) { + return result(FlutterError("camera_error", "Camera not created")); + } + if (camera->HasPendingResultByType(PendingResultType::kStopStream)) { + return result( + FlutterError("camera_error", "Pending stop stream request exists")); + } + + if (camera->AddPendingVoidResult(PendingResultType::kStopStream, + std::move(result))) { + CaptureController* cc = camera->GetCaptureController(); + assert(cc); + cc->StopImageStream(); + } +} + void CameraPlugin::TakePicture( int64_t camera_id, std::function reply)> result) { auto camera = GetCameraByCameraId(camera_id); diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 422f5c92b36..f77711402bb 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -31,6 +31,8 @@ class CameraPlugin : public flutter::Plugin, public CameraApi, public VideoCaptureDeviceEnumerator { public: + void SetEventSink( + std::unique_ptr> events); static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); CameraPlugin(flutter::TextureRegistrar* texture_registrar, @@ -68,6 +70,12 @@ class CameraPlugin : public flutter::Plugin, void StopVideoRecording( int64_t camera_id, std::function reply)> result) override; + void StartImageStream( + int64_t camera_id, + std::function reply)> result) override; + void StopImageStream( + int64_t camera_id, + std::function reply)> result) override; void TakePicture( int64_t camera_id, std::function reply)> result) override; diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 44675daebec..87c2290e335 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -5,11 +5,14 @@ #include "capture_controller.h" #include +#include +#include #include #include #include #include +#include #include "com_heap_ptr.h" #include "photo_handler.h" @@ -555,6 +558,16 @@ void CaptureControllerImpl::StopRecord() { "Failed to stop video recording"); } } +void CaptureControllerImpl::StartImageStream( + std::unique_ptr> sink) { + assert(capture_controller_listener_); + image_stream_sink_ = std::move(sink); +} + +void CaptureControllerImpl::StopImageStream() { + assert(capture_controller_listener_); + image_stream_sink_.reset(); +} // Starts capturing preview frames using preview handler // After first frame is captured, OnPreviewStarted is called @@ -863,6 +876,32 @@ bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer, if (!texture_handler_) { return false; } + if (image_stream_sink_) { + // Convert the buffer data to a std::vector. + std::vector buffer_data(buffer, buffer + data_length); + + // Ensure preview_frame_height_ and preview_frame_width_ are of supported + // types. + int preview_frame_height = static_cast(preview_frame_height_); + int preview_frame_width = static_cast(preview_frame_width_); + + // Create a map to hold the buffer data and data length. + flutter::EncodableMap data_map; + data_map[flutter::EncodableValue("data")] = + flutter::EncodableValue(buffer_data); + data_map[flutter::EncodableValue("height")] = + flutter::EncodableValue(preview_frame_height); + data_map[flutter::EncodableValue("width")] = + flutter::EncodableValue(preview_frame_width); + data_map[flutter::EncodableValue("length")] = + flutter::EncodableValue(static_cast(data_length)); + + // Wrap the map in a flutter::EncodableValue. + flutter::EncodableValue encoded_value(data_map); + + // Send the encoded value through the image_stream_sink_. + image_stream_sink_->Success(encoded_value); + } return texture_handler_->UpdateBuffer(buffer, data_length); } diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 2280cb3b93f..42e8ab2547a 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -6,6 +6,7 @@ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_H_ #include +#include #include #include #include @@ -92,6 +93,13 @@ class CaptureController { // Stops the current video recording. virtual void StopRecord() = 0; + // Starts image streaming. + virtual void StartImageStream( + std::unique_ptr> sink) = 0; + + // Stops the current image streaming. + virtual void StopImageStream() = 0; + // Captures a still photo. virtual void TakePicture(const std::string& file_path) = 0; }; @@ -125,6 +133,10 @@ class CaptureControllerImpl : public CaptureController, void ResumePreview() override; void StartRecord(const std::string& file_path) override; void StopRecord() override; + void StartImageStream( + std::unique_ptr> sink) + override; + void StopImageStream() override; void TakePicture(const std::string& file_path) override; // CaptureEngineObserver @@ -219,8 +231,9 @@ class CaptureControllerImpl : public CaptureController, std::unique_ptr preview_handler_; std::unique_ptr photo_handler_; std::unique_ptr texture_handler_; + std::unique_ptr> + image_stream_sink_; CaptureControllerListener* capture_controller_listener_; - std::string video_device_id_; CaptureEngineState capture_engine_state_ = CaptureEngineState::kNotInitialized; diff --git a/packages/camera/camera_windows/windows/messages.g.cpp b/packages/camera/camera_windows/windows/messages.g.cpp index 541ca97d018..724d4203748 100644 --- a/packages/camera/camera_windows/windows/messages.g.cpp +++ b/packages/camera/camera_windows/windows/messages.g.cpp @@ -498,6 +498,78 @@ void CameraApi::SetUp(flutter::BinaryMessenger* binary_messenger, channel.SetMessageHandler(nullptr); } } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.camera_windows.CameraApi.startImageStream" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_camera_id_arg = args.at(0); + if (encodable_camera_id_arg.IsNull()) { + reply(WrapError("camera_id_arg unexpectedly null.")); + return; + } + const int64_t camera_id_arg = encodable_camera_id_arg.LongValue(); + api->StartImageStream( + camera_id_arg, [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.camera_windows.CameraApi.stopImageStream" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_camera_id_arg = args.at(0); + if (encodable_camera_id_arg.IsNull()) { + reply(WrapError("camera_id_arg unexpectedly null.")); + return; + } + const int64_t camera_id_arg = encodable_camera_id_arg.LongValue(); + api->StopImageStream( + camera_id_arg, [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } { BasicMessageChannel<> channel( binary_messenger, diff --git a/packages/camera/camera_windows/windows/messages.g.h b/packages/camera/camera_windows/windows/messages.g.h index a85c692e5b0..9dcb6fab7d1 100644 --- a/packages/camera/camera_windows/windows/messages.g.h +++ b/packages/camera/camera_windows/windows/messages.g.h @@ -189,6 +189,14 @@ class CameraApi { virtual void StopVideoRecording( int64_t camera_id, std::function reply)> result) = 0; + // Starts the image stream for the given camera. + virtual void StartImageStream( + int64_t camera_id, + std::function reply)> result) = 0; + // Stops the image stream for the given camera. + virtual void StopImageStream( + int64_t camera_id, + std::function reply)> result) = 0; // Starts the preview stream for the given camera. virtual void PausePreview( int64_t camera_id, diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp index 2680ae26c0e..6bd59dbde8a 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -351,6 +351,232 @@ TEST(CameraPlugin, InitializeHandlerCallStartPreview) { EXPECT_TRUE(result_called); } +TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) { + int64_t mock_camera_id = 1234; + + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + + std::unique_ptr capture_controller = + std::make_unique(); + + EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) + .Times(1) + .WillOnce([cam = camera.get()](int64_t camera_id) { + return cam->camera_id_ == camera_id; + }); + + EXPECT_CALL(*camera, + HasPendingResultByType(Eq(PendingResultType::kStartStream))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, + AddPendingVoidResult(Eq(PendingResultType::kStartStream), _)) + .Times(1) + .WillOnce([cam = camera.get()]( + PendingResultType type, + std::function)> result) { + cam->pending_void_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_void_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, StartImageStream) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_void_result_); + return cam->pending_void_result_(std::nullopt); + }); + + camera->camera_id_ = mock_camera_id; + camera->capture_controller_ = std::move(capture_controller); + + MockCameraPlugin plugin(std::make_unique().get(), + std::make_unique().get(), + std::make_unique()); + + // Add mocked camera to plugins camera list. + plugin.AddCamera(std::move(camera)); + + // Set the event sink to a mocked event sink. + auto mock_event_sink = std::make_unique(); + plugin.SetEventSink(std::move(mock_event_sink)); + + bool result_called = false; + std::function)> start_image_stream_result = + [&result_called](std::optional reply) { + EXPECT_FALSE(result_called); // Ensure only one reply call. + result_called = true; + EXPECT_FALSE(reply); + }; + + plugin.StartImageStream(mock_camera_id, std::move(start_image_stream_result)); + + EXPECT_TRUE(result_called); +} + +TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) { + int64_t mock_camera_id = 1234; + int64_t missing_camera_id = 5678; + + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + + std::unique_ptr capture_controller = + std::make_unique(); + + EXPECT_CALL(*camera, HasCameraId) + .Times(1) + .WillOnce([cam = camera.get()](int64_t camera_id) { + return cam->camera_id_ == camera_id; + }); + + EXPECT_CALL(*camera, HasPendingResultByType).Times(0); + EXPECT_CALL(*camera, AddPendingVoidResult).Times(0); + EXPECT_CALL(*camera, GetCaptureController).Times(0); + EXPECT_CALL(*capture_controller, StartImageStream).Times(0); + + camera->camera_id_ = mock_camera_id; + + MockCameraPlugin plugin(std::make_unique().get(), + std::make_unique().get(), + std::make_unique()); + + // Add mocked camera to plugins camera list. + plugin.AddCamera(std::move(camera)); + + bool result_called = false; + std::function)> start_image_stream_result = + [&result_called](std::optional reply) { + EXPECT_FALSE(result_called); // Ensure only one reply call. + result_called = true; + EXPECT_TRUE(reply); + }; + + plugin.StartImageStream(missing_camera_id, + std::move(start_image_stream_result)); + + EXPECT_TRUE(result_called); +} + +TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) { + int64_t mock_camera_id = 1234; + + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + + std::unique_ptr capture_controller = + std::make_unique(); + + EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) + .Times(1) + .WillOnce([cam = camera.get()](int64_t camera_id) { + return cam->camera_id_ == camera_id; + }); + + EXPECT_CALL(*camera, + HasPendingResultByType(Eq(PendingResultType::kStopStream))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, + AddPendingVoidResult(Eq(PendingResultType::kStopStream), _)) + .Times(1) + .WillOnce([cam = camera.get()]( + PendingResultType type, + std::function)> result) { + cam->pending_void_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_void_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, StopImageStream) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_void_result_); + return cam->pending_void_result_(std::nullopt); + }); + + camera->camera_id_ = mock_camera_id; + camera->capture_controller_ = std::move(capture_controller); + + MockCameraPlugin plugin(std::make_unique().get(), + std::make_unique().get(), + std::make_unique()); + + // Add mocked camera to plugins camera list. + plugin.AddCamera(std::move(camera)); + + bool result_called = false; + std::function)> stop_image_stream_result = + [&result_called](std::optional reply) { + EXPECT_FALSE(result_called); // Ensure only one reply call. + result_called = true; + EXPECT_FALSE(reply); + }; + + plugin.StopImageStream(mock_camera_id, std::move(stop_image_stream_result)); + + EXPECT_TRUE(result_called); +} + +TEST(CameraPlugin, StopImageStreamHandlerErrorOnInvalidCameraId) { + int64_t mock_camera_id = 1234; + int64_t missing_camera_id = 5678; + + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + + std::unique_ptr capture_controller = + std::make_unique(); + + EXPECT_CALL(*camera, HasCameraId) + .Times(1) + .WillOnce([cam = camera.get()](int64_t camera_id) { + return cam->camera_id_ == camera_id; + }); + + EXPECT_CALL(*camera, HasPendingResultByType).Times(0); + EXPECT_CALL(*camera, AddPendingVoidResult).Times(0); + EXPECT_CALL(*camera, GetCaptureController).Times(0); + EXPECT_CALL(*capture_controller, StopImageStream).Times(0); + + camera->camera_id_ = mock_camera_id; + + MockCameraPlugin plugin(std::make_unique().get(), + std::make_unique().get(), + std::make_unique()); + + // Add mocked camera to plugins camera list. + plugin.AddCamera(std::move(camera)); + + bool result_called = false; + std::function)> stop_image_stream_result = + [&result_called](std::optional reply) { + EXPECT_FALSE(result_called); // Ensure only one reply call. + result_called = true; + EXPECT_TRUE(reply); + }; + + plugin.StopImageStream(missing_camera_id, + std::move(stop_image_stream_result)); + + EXPECT_TRUE(result_called); +} + TEST(CameraPlugin, InitializeHandlerErrorOnInvalidCameraId) { int64_t mock_camera_id = 1234; int64_t missing_camera_id = 5678; diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 4a05ef82296..8c279589f8c 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -248,6 +248,11 @@ class MockCaptureController : public CaptureController { MOCK_METHOD(void, PausePreview, (), (override)); MOCK_METHOD(void, StartRecord, (const std::string& file_path), (override)); MOCK_METHOD(void, StopRecord, (), (override)); + MOCK_METHOD( + void, StartImageStream, + (std::unique_ptr> sink), + (override)); + MOCK_METHOD(void, StopImageStream, (), (override)); MOCK_METHOD(void, TakePicture, (const std::string& file_path), (override)); }; @@ -1021,6 +1026,9 @@ class MockCaptureEngine : public IMFCaptureEngine { MOCK_METHOD(HRESULT, StartPreview, ()); MOCK_METHOD(HRESULT, StopPreview, ()); MOCK_METHOD(HRESULT, StartRecord, ()); + MOCK_METHOD(HRESULT, StartImageStream, ()); + MOCK_METHOD(HRESULT, StopImageStream, ()); + MOCK_METHOD(HRESULT, StopRecord, (BOOL finalize, BOOL flushUnprocessedSamples)); MOCK_METHOD(HRESULT, TakePhoto, ()); @@ -1068,6 +1076,17 @@ class MockCaptureEngine : public IMFCaptureEngine { volatile ULONG ref_ = 0; bool initialized_ = false; }; +// Mock class for flutter::EventSink +class MockEventSink : public flutter::EventSink { + public: + MOCK_METHOD(void, SuccessInternal, (const flutter::EncodableValue* event), + (override)); + MOCK_METHOD(void, ErrorInternal, + (const std::string& error_code, const std::string& error_message, + const flutter::EncodableValue* error_details), + (override)); + MOCK_METHOD(void, EndOfStreamInternal, (), (override)); +}; #define MOCK_DEVICE_ID "mock_device_id" #define MOCK_CAMERA_NAME "mock_camera_name <" MOCK_DEVICE_ID ">" From 56bd250c3e479f71e3f19d3e9adb3fe0c2554e25 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Wed, 4 Dec 2024 15:03:03 +0200 Subject: [PATCH 02/16] Make `{Start,Stop}ImageStream` synchronous The logic is synchronous, so there is no need to have the `@async` annotation here. Addresses: https://github.com/flutter/packages/pull/7067#discussion_r1693930321 --- .../camera_windows/lib/src/messages.g.dart | 2 +- .../camera_windows/pigeons/messages.dart | 2 - .../camera/camera_windows/windows/camera.h | 2 - .../camera_windows/windows/camera_plugin.cpp | 48 +++------ .../camera_windows/windows/camera_plugin.h | 8 +- .../camera_windows/windows/messages.g.cpp | 40 ++++--- .../camera_windows/windows/messages.g.h | 10 +- .../windows/test/camera_plugin_test.cpp | 100 ++---------------- 8 files changed, 51 insertions(+), 161 deletions(-) diff --git a/packages/camera/camera_windows/lib/src/messages.g.dart b/packages/camera/camera_windows/lib/src/messages.g.dart index 9930549183b..c91d28ceac0 100644 --- a/packages/camera/camera_windows/lib/src/messages.g.dart +++ b/packages/camera/camera_windows/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.0), do not edit directly. +// Autogenerated from Pigeon (v22.6.2), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers diff --git a/packages/camera/camera_windows/pigeons/messages.dart b/packages/camera/camera_windows/pigeons/messages.dart index d0970af423a..94d8af9230c 100644 --- a/packages/camera/camera_windows/pigeons/messages.dart +++ b/packages/camera/camera_windows/pigeons/messages.dart @@ -71,11 +71,9 @@ abstract class CameraApi { String stopVideoRecording(int cameraId); /// Starts the image stream for the given camera. - @async void startImageStream(int cameraId); /// Stops the image stream for the given camera. - @async void stopImageStream(int cameraId); /// Starts the preview stream for the given camera. diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 2a44ad2ccf6..44e9fcde588 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -22,8 +22,6 @@ enum class PendingResultType { kTakePicture, kStartRecord, kStopRecord, - kStartStream, - kStopStream, kPausePreview, kResumePreview, }; diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 4b2abe39d6a..fbfe13acc4c 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -369,51 +369,35 @@ void CameraPlugin::StopVideoRecording( } } -void CameraPlugin::StartImageStream( - int64_t camera_id, - std::function reply)> result) { - // check if request already exists +std::optional CameraPlugin::StartImageStream(int64_t camera_id) { Camera* camera = GetCameraByCameraId(camera_id); if (!camera) { - return result(FlutterError("camera_error", "Camera not created")); - } - if (camera->HasPendingResultByType(PendingResultType::kStartStream)) { - return result( - FlutterError("camera_error", "Pending start stream request exists")); + return FlutterError("camera_error", "Camera not created"); } if (!event_sink) { - return result(FlutterError("camera_error", - "Unable to make event channel from windows")); + return FlutterError("camera_error", + "Unable to make event channel from windows"); } - if (camera->AddPendingVoidResult(PendingResultType::kStartStream, - std::move(result))) { - CaptureController* cc = camera->GetCaptureController(); - assert(cc); - cc->StartImageStream(std::move(event_sink)); - } + CaptureController* cc = camera->GetCaptureController(); + assert(cc); + cc->StartImageStream(std::move(event_sink)); + + return std::nullopt; } -void CameraPlugin::StopImageStream( - int64_t camera_id, - std::function reply)> result) { - // check if request already exists +std::optional CameraPlugin::StopImageStream(int64_t camera_id) { Camera* camera = GetCameraByCameraId(camera_id); if (!camera) { - return result(FlutterError("camera_error", "Camera not created")); - } - if (camera->HasPendingResultByType(PendingResultType::kStopStream)) { - return result( - FlutterError("camera_error", "Pending stop stream request exists")); + return FlutterError("camera_error", "Camera not created"); } - if (camera->AddPendingVoidResult(PendingResultType::kStopStream, - std::move(result))) { - CaptureController* cc = camera->GetCaptureController(); - assert(cc); - cc->StopImageStream(); - } + CaptureController* cc = camera->GetCaptureController(); + assert(cc); + cc->StopImageStream(); + + return std::nullopt; } void CameraPlugin::TakePicture( diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index f77711402bb..d22049613b3 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -70,12 +70,8 @@ class CameraPlugin : public flutter::Plugin, void StopVideoRecording( int64_t camera_id, std::function reply)> result) override; - void StartImageStream( - int64_t camera_id, - std::function reply)> result) override; - void StopImageStream( - int64_t camera_id, - std::function reply)> result) override; + std::optional StartImageStream(int64_t camera_id) override; + std::optional StopImageStream(int64_t camera_id) override; void TakePicture( int64_t camera_id, std::function reply)> result) override; diff --git a/packages/camera/camera_windows/windows/messages.g.cpp b/packages/camera/camera_windows/windows/messages.g.cpp index 724d4203748..d503a449cef 100644 --- a/packages/camera/camera_windows/windows/messages.g.cpp +++ b/packages/camera/camera_windows/windows/messages.g.cpp @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.0), do not edit directly. +// Autogenerated from Pigeon (v22.6.2), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -516,16 +516,15 @@ void CameraApi::SetUp(flutter::BinaryMessenger* binary_messenger, return; } const int64_t camera_id_arg = encodable_camera_id_arg.LongValue(); - api->StartImageStream( - camera_id_arg, [reply](std::optional&& output) { - if (output.has_value()) { - reply(WrapError(output.value())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue()); - reply(EncodableValue(std::move(wrapped))); - }); + std::optional output = + api->StartImageStream(camera_id_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -552,16 +551,15 @@ void CameraApi::SetUp(flutter::BinaryMessenger* binary_messenger, return; } const int64_t camera_id_arg = encodable_camera_id_arg.LongValue(); - api->StopImageStream( - camera_id_arg, [reply](std::optional&& output) { - if (output.has_value()) { - reply(WrapError(output.value())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue()); - reply(EncodableValue(std::move(wrapped))); - }); + std::optional output = + api->StopImageStream(camera_id_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } diff --git a/packages/camera/camera_windows/windows/messages.g.h b/packages/camera/camera_windows/windows/messages.g.h index 9dcb6fab7d1..621466fe031 100644 --- a/packages/camera/camera_windows/windows/messages.g.h +++ b/packages/camera/camera_windows/windows/messages.g.h @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.0), do not edit directly. +// Autogenerated from Pigeon (v22.6.2), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_MESSAGES_G_H_ @@ -190,13 +190,9 @@ class CameraApi { int64_t camera_id, std::function reply)> result) = 0; // Starts the image stream for the given camera. - virtual void StartImageStream( - int64_t camera_id, - std::function reply)> result) = 0; + virtual std::optional StartImageStream(int64_t camera_id) = 0; // Stops the image stream for the given camera. - virtual void StopImageStream( - int64_t camera_id, - std::function reply)> result) = 0; + virtual std::optional StopImageStream(int64_t camera_id) = 0; // Starts the preview stream for the given camera. virtual void PausePreview( int64_t camera_id, diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp index 6bd59dbde8a..e2ecd42d313 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -29,6 +29,8 @@ using ::testing::EndsWith; using ::testing::Eq; using ::testing::Pointee; using ::testing::Return; +using ::testing::Optional; +using ::testing::Property; void MockInitCamera(MockCamera* camera, bool success) { EXPECT_CALL(*camera, @@ -366,34 +368,14 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) { return cam->camera_id_ == camera_id; }); - EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::kStartStream))) - .Times(1) - .WillOnce(Return(false)); - - EXPECT_CALL(*camera, - AddPendingVoidResult(Eq(PendingResultType::kStartStream), _)) - .Times(1) - .WillOnce([cam = camera.get()]( - PendingResultType type, - std::function)> result) { - cam->pending_void_result_ = std::move(result); - return true; - }); - EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { - assert(cam->pending_void_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, StartImageStream) - .Times(1) - .WillOnce([cam = camera.get()]() { - assert(cam->pending_void_result_); - return cam->pending_void_result_(std::nullopt); - }); + .Times(1); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); @@ -402,24 +384,14 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list. - plugin.AddCamera(std::move(camera)); - // Set the event sink to a mocked event sink. auto mock_event_sink = std::make_unique(); plugin.SetEventSink(std::move(mock_event_sink)); - bool result_called = false; - std::function)> start_image_stream_result = - [&result_called](std::optional reply) { - EXPECT_FALSE(result_called); // Ensure only one reply call. - result_called = true; - EXPECT_FALSE(reply); - }; - - plugin.StartImageStream(mock_camera_id, std::move(start_image_stream_result)); + // Add mocked camera to plugins camera list. + plugin.AddCamera(std::move(camera)); - EXPECT_TRUE(result_called); + EXPECT_EQ(plugin.StartImageStream(mock_camera_id), std::nullopt); } TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) { @@ -452,18 +424,7 @@ TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) { // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); - bool result_called = false; - std::function)> start_image_stream_result = - [&result_called](std::optional reply) { - EXPECT_FALSE(result_called); // Ensure only one reply call. - result_called = true; - EXPECT_TRUE(reply); - }; - - plugin.StartImageStream(missing_camera_id, - std::move(start_image_stream_result)); - - EXPECT_TRUE(result_called); + EXPECT_THAT(plugin.StartImageStream(missing_camera_id), Optional(Property("code", &FlutterError::code, "camera_error"))); } TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) { @@ -481,34 +442,14 @@ TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) { return cam->camera_id_ == camera_id; }); - EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::kStopStream))) - .Times(1) - .WillOnce(Return(false)); - - EXPECT_CALL(*camera, - AddPendingVoidResult(Eq(PendingResultType::kStopStream), _)) - .Times(1) - .WillOnce([cam = camera.get()]( - PendingResultType type, - std::function)> result) { - cam->pending_void_result_ = std::move(result); - return true; - }); - EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { - assert(cam->pending_void_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, StopImageStream) - .Times(1) - .WillOnce([cam = camera.get()]() { - assert(cam->pending_void_result_); - return cam->pending_void_result_(std::nullopt); - }); + .Times(1); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); @@ -520,17 +461,7 @@ TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) { // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); - bool result_called = false; - std::function)> stop_image_stream_result = - [&result_called](std::optional reply) { - EXPECT_FALSE(result_called); // Ensure only one reply call. - result_called = true; - EXPECT_FALSE(reply); - }; - - plugin.StopImageStream(mock_camera_id, std::move(stop_image_stream_result)); - - EXPECT_TRUE(result_called); + EXPECT_EQ(plugin.StopImageStream(mock_camera_id), std::nullopt); } TEST(CameraPlugin, StopImageStreamHandlerErrorOnInvalidCameraId) { @@ -563,18 +494,7 @@ TEST(CameraPlugin, StopImageStreamHandlerErrorOnInvalidCameraId) { // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); - bool result_called = false; - std::function)> stop_image_stream_result = - [&result_called](std::optional reply) { - EXPECT_FALSE(result_called); // Ensure only one reply call. - result_called = true; - EXPECT_TRUE(reply); - }; - - plugin.StopImageStream(missing_camera_id, - std::move(stop_image_stream_result)); - - EXPECT_TRUE(result_called); + EXPECT_THAT(plugin.StopImageStream(missing_camera_id), Optional(Property("code", &FlutterError::code, "camera_error"))); } TEST(CameraPlugin, InitializeHandlerErrorOnInvalidCameraId) { From 5cad20d7f7d9741b72727eff12ced0a67fa6abfe Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Tue, 11 Feb 2025 18:29:15 +0200 Subject: [PATCH 03/16] Make `type_conversion` private --- packages/camera/camera_windows/lib/camera_windows.dart | 2 +- .../camera/camera_windows/lib/{ => src}/type_conversion.dart | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/camera/camera_windows/lib/{ => src}/type_conversion.dart (100%) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index bdbfc17dcf8..b1f09f1a933 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -11,7 +11,7 @@ import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; import 'src/messages.g.dart'; -import 'type_conversion.dart'; +import 'src/type_conversion.dart'; /// An implementation of [CameraPlatform] for Windows. class CameraWindows extends CameraPlatform { diff --git a/packages/camera/camera_windows/lib/type_conversion.dart b/packages/camera/camera_windows/lib/src/type_conversion.dart similarity index 100% rename from packages/camera/camera_windows/lib/type_conversion.dart rename to packages/camera/camera_windows/lib/src/type_conversion.dart From 8962bc1336b253ef8e25985a2a5c3de4e7ef08d7 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Wed, 4 Dec 2024 15:03:03 +0200 Subject: [PATCH 04/16] Submit image stream frames from a platform thread When image stream frames are being received from Media Foundation, it appears to use a separate thread when calling the `OnSample` callback, which results in this error from: The 'plugins.flutter.io/camera_windows/imageStream' channel sent a message from native to Flutter on a non-platform thread. Platform channel messages must be sent on the platform thread. Failure to do so may result in data loss or crashes, and must be fixed in the plugin or application code creating that channel. In order to ensure that we are only ever using the `EventChannel` from a platform thread, create a `TaskRunner` that is backed by a hidden window to submit the frames to the image stream. Based on the suggestion in this comment: https://github.com/flutter/flutter/issues/134346#issuecomment-2141023146 --- .../camera_windows/windows/CMakeLists.txt | 4 + .../camera/camera_windows/windows/camera.cpp | 10 +- .../camera/camera_windows/windows/camera.h | 10 +- .../camera_windows/windows/camera_plugin.cpp | 8 +- .../camera_windows/windows/camera_plugin.h | 2 + .../windows/capture_controller.cpp | 11 +- .../windows/capture_controller.h | 16 ++- .../camera_windows/windows/task_runner.h | 24 ++++ .../windows/task_runner_window.cpp | 105 ++++++++++++++ .../windows/task_runner_window.h | 61 ++++++++ .../windows/test/camera_plugin_test.cpp | 3 +- .../windows/test/camera_test.cpp | 18 +-- .../windows/test/capture_controller_test.cpp | 131 +++++++++++++----- .../camera_windows/windows/test/mocks.h | 12 +- .../windows/test/task_runner_window_test.cpp | 34 +++++ .../camera_windows/windows/unique_hwnd.h | 28 ++++ 16 files changed, 413 insertions(+), 64 deletions(-) create mode 100644 packages/camera/camera_windows/windows/task_runner.h create mode 100644 packages/camera/camera_windows/windows/task_runner_window.cpp create mode 100644 packages/camera/camera_windows/windows/task_runner_window.h create mode 100644 packages/camera/camera_windows/windows/test/task_runner_window_test.cpp create mode 100644 packages/camera/camera_windows/windows/unique_hwnd.h diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index e209a22ec0f..ef607578de3 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -33,6 +33,9 @@ list(APPEND PLUGIN_SOURCES "texture_handler.h" "texture_handler.cpp" "com_heap_ptr.h" + "task_runner.h" + "task_runner_window.h" + "task_runner_window.cpp" ) add_library(${PLUGIN_NAME} SHARED @@ -84,6 +87,7 @@ add_executable(${TEST_RUNNER} test/camera_plugin_test.cpp test/camera_test.cpp test/capture_controller_test.cpp + test/task_runner_window_test.cpp ${PLUGIN_SOURCES} ) apply_standard_settings(${TEST_RUNNER}) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index 13bf3b3efae..aa25c907a5f 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -39,24 +39,26 @@ CameraImpl::~CameraImpl() { bool CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, - const PlatformMediaSettings& media_settings) { + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner) { auto capture_controller_factory = std::make_unique(); return InitCamera(std::move(capture_controller_factory), texture_registrar, - messenger, media_settings); + messenger, media_settings, task_runner); } bool CameraImpl::InitCamera( std::unique_ptr capture_controller_factory, flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, - const PlatformMediaSettings& media_settings) { + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner) { assert(!device_id_.empty()); messenger_ = messenger; capture_controller_ = capture_controller_factory->CreateCaptureController(this); return capture_controller_->InitCaptureDevice(texture_registrar, device_id_, - media_settings); + media_settings, task_runner); } bool CameraImpl::AddPendingVoidResult( diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 44e9fcde588..5bb25dc60e9 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -85,7 +85,8 @@ class Camera : public CaptureControllerListener { // Returns false if initialization fails. virtual bool InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, - const PlatformMediaSettings& media_settings) = 0; + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner) = 0; }; // Concrete implementation of the |Camera| interface. @@ -151,7 +152,8 @@ class CameraImpl : public Camera { } bool InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, - const PlatformMediaSettings& media_settings) override; + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner) override; // Initializes the camera and its associated capture controller. // @@ -163,7 +165,8 @@ class CameraImpl : public Camera { std::unique_ptr capture_controller_factory, flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, - const PlatformMediaSettings& media_settings); + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner); private: // A generic type for any pending asyncronous result. @@ -230,6 +233,7 @@ class CameraImpl : public Camera { std::unique_ptr capture_controller_; std::unique_ptr event_api_; flutter::BinaryMessenger* messenger_ = nullptr; + std::shared_ptr task_runner_; int64_t camera_id_ = -1; std::string device_id_; }; diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index fbfe13acc4c..c3eb02a7db3 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -24,6 +24,7 @@ #include "com_heap_ptr.h" #include "messages.g.h" #include "string_utils.h" +#include "task_runner_window.h" namespace camera_windows { using flutter::EncodableList; @@ -159,7 +160,8 @@ CameraPlugin::CameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger) : texture_registrar_(texture_registrar), messenger_(messenger), - camera_factory_(std::make_unique()) {} + camera_factory_(std::make_unique()), + task_runner_(std::make_shared()) {} CameraPlugin::CameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, @@ -251,8 +253,8 @@ void CameraPlugin::Create(const std::string& camera_name, if (camera->AddPendingIntResult(PendingResultType::kCreateCamera, std::move(result))) { - bool initialized = - camera->InitCamera(texture_registrar_, messenger_, settings); + bool initialized = camera->InitCamera(texture_registrar_, messenger_, + settings, task_runner_); if (initialized) { cameras_.push_back(std::move(camera)); } diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index d22049613b3..144f2434aae 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -16,6 +16,7 @@ #include "capture_controller.h" #include "capture_controller_listener.h" #include "messages.g.h" +#include "task_runner.h" namespace camera_windows { using flutter::MethodResult; @@ -97,6 +98,7 @@ class CameraPlugin : public flutter::Plugin, flutter::TextureRegistrar* texture_registrar_; flutter::BinaryMessenger* messenger_; std::vector> cameras_; + std::shared_ptr task_runner_; friend class camera_windows::test::MockCameraPlugin; }; diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 87c2290e335..4e330ad782b 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -303,7 +303,8 @@ void CaptureControllerImpl::ResetCaptureController() { bool CaptureControllerImpl::InitCaptureDevice( flutter::TextureRegistrar* texture_registrar, const std::string& device_id, - const PlatformMediaSettings& media_settings) { + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner) { assert(capture_controller_listener_); if (IsInitialized()) { @@ -320,6 +321,7 @@ bool CaptureControllerImpl::InitCaptureDevice( media_settings_ = media_settings; texture_registrar_ = texture_registrar; video_device_id_ = device_id; + task_runner_ = task_runner; // MFStartup must be called before using Media Foundation. if (!media_foundation_started_) { @@ -900,7 +902,12 @@ bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer, flutter::EncodableValue encoded_value(data_map); // Send the encoded value through the image_stream_sink_. - image_stream_sink_->Success(encoded_value); + std::weak_ptr weak_sink = image_stream_sink_; + task_runner_->EnqueueTask([weak_sink, encoded_value]() { + std::shared_ptr> sink = + weak_sink.lock(); + if (sink) sink->Success(encoded_value); + }); } return texture_handler_->UpdateBuffer(buffer, data_length); } diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 42e8ab2547a..b3ad7e83b87 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -25,6 +25,7 @@ #include "photo_handler.h" #include "preview_handler.h" #include "record_handler.h" +#include "task_runner.h" #include "texture_handler.h" namespace camera_windows { @@ -68,9 +69,12 @@ class CaptureController { // device_id: A string that holds information of camera device id to // be captured. // media_settings: Settings controlling capture behavior. - virtual bool InitCaptureDevice( - TextureRegistrar* texture_registrar, const std::string& device_id, - const PlatformMediaSettings& media_settings) = 0; + // task_runner: A task runner for posting image frames via a platform + // thread. + virtual bool InitCaptureDevice(TextureRegistrar* texture_registrar, + const std::string& device_id, + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner) = 0; // Returns preview frame width virtual uint32_t GetPreviewWidth() const = 0; @@ -125,7 +129,8 @@ class CaptureControllerImpl : public CaptureController, // CaptureController bool InitCaptureDevice(TextureRegistrar* texture_registrar, const std::string& device_id, - const PlatformMediaSettings& media_settings) override; + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner) override; uint32_t GetPreviewWidth() const override { return preview_frame_width_; } uint32_t GetPreviewHeight() const override { return preview_frame_height_; } void StartPreview() override; @@ -231,7 +236,8 @@ class CaptureControllerImpl : public CaptureController, std::unique_ptr preview_handler_; std::unique_ptr photo_handler_; std::unique_ptr texture_handler_; - std::unique_ptr> + std::shared_ptr task_runner_; + std::shared_ptr> image_stream_sink_; CaptureControllerListener* capture_controller_listener_; std::string video_device_id_; diff --git a/packages/camera/camera_windows/windows/task_runner.h b/packages/camera/camera_windows/windows/task_runner.h new file mode 100644 index 00000000000..fd51991d808 --- /dev/null +++ b/packages/camera/camera_windows/windows/task_runner.h @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TASK_RUNNER_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TASK_RUNNER_H_ + +#include + +namespace camera_windows { + +/// A closure that can be run as a task. +using TaskClosure = std::function; + +/// A task queue for scheduling functions to be executed in a different thread. +class TaskRunner { + public: + /// Schedule a function/closure to be executed as soon as possible. + virtual void EnqueueTask(TaskClosure task) = 0; +}; + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TASK_RUNNER_H_ diff --git a/packages/camera/camera_windows/windows/task_runner_window.cpp b/packages/camera/camera_windows/windows/task_runner_window.cpp new file mode 100644 index 00000000000..2783a1e1332 --- /dev/null +++ b/packages/camera/camera_windows/windows/task_runner_window.cpp @@ -0,0 +1,105 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "task_runner_window.h" + +#include +#include + +namespace camera_windows { + +TaskRunnerWindow::TaskRunnerWindow() { + WNDCLASS window_class = RegisterWindowClass(); + window_handle_.reset(CreateWindowEx(0, window_class.lpszClassName, L"", 0, 0, + 0, 0, 0, HWND_MESSAGE, nullptr, + window_class.hInstance, nullptr)); + + if (window_handle_) { + SetWindowLongPtr(window_handle_.get(), GWLP_USERDATA, + reinterpret_cast(this)); + } else { + auto error = GetLastError(); + LPWSTR message = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), 0, NULL); + OutputDebugString(message); + LocalFree(message); + } +} + +TaskRunnerWindow::~TaskRunnerWindow() { + UnregisterClass(window_class_name_.c_str(), nullptr); +} + +void TaskRunnerWindow::EnqueueTask(TaskClosure task) { + { + std::lock_guard lock(tasks_mutex_); + tasks_.push(task); + } + if (!PostMessage(window_handle_.get(), WM_NULL, 0, 0)) { + DWORD error_code = GetLastError(); + std::cerr << "Failed to post message to main thread; error_code: " + << error_code << std::endl; + } +} + +void TaskRunnerWindow::ProcessTasks() { + // Even though it would usually be sufficient to process only a single task + // whenever a message is received, if the message queue happens to be full, + // there may have been fewer messages received than tasks in the queue. + for (;;) { + TaskClosure task; + { + std::lock_guard lock(tasks_mutex_); + if (tasks_.empty()) break; + task = tasks_.front(); + tasks_.pop(); + } + task(); + } +} + +WNDCLASS TaskRunnerWindow::RegisterWindowClass() { + window_class_name_ = L"FlutterPluginCameraWindowsTaskRunnerWindow"; + + WNDCLASS window_class{}; + window_class.hCursor = nullptr; + window_class.lpszClassName = window_class_name_.c_str(); + window_class.style = 0; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = nullptr; + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = WndProc; + RegisterClass(&window_class); + return window_class; +} + +LRESULT +TaskRunnerWindow::HandleMessage(UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_NULL: + ProcessTasks(); + return 0; + } + return DefWindowProcW(window_handle_.get(), message, wparam, lparam); +} + +LRESULT TaskRunnerWindow::WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (auto* that = reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA))) { + return that->HandleMessage(message, wparam, lparam); + } else { + return DefWindowProc(window, message, wparam, lparam); + } +} + +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/task_runner_window.h b/packages/camera/camera_windows/windows/task_runner_window.h new file mode 100644 index 00000000000..1ab220ffe0d --- /dev/null +++ b/packages/camera/camera_windows/windows/task_runner_window.h @@ -0,0 +1,61 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TASK_RUNNER_WINDOW_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TASK_RUNNER_WINDOW_H_ + +#include + +#include +#include +#include +#include +#include + +#include "task_runner.h" +#include "unique_hwnd.h" + +namespace camera_windows { + +/// Hidden HWND responsible for processing camera tasks on main thread. +/// Adapted from Flutter Engine, see: +/// https://github.com/flutter/flutter/issues/134346#issuecomment-2141023146 +/// and: +/// https://github.com/flutter/engine/blob/d7c0bcfe7a30408b0722c9d47d8b0b1e4cdb9c81/shell/platform/windows/task_runner_window.h +class TaskRunnerWindow : public TaskRunner { + public: + virtual void EnqueueTask(TaskClosure task); + + /// Creates a hidden window and registers a callback that runs + /// enqueued tasks whenever a message is received. + TaskRunnerWindow(); + + /// Destroys the hidden window. + ~TaskRunnerWindow(); + + private: + void ProcessTasks(); + + WNDCLASS RegisterWindowClass(); + + LRESULT + HandleMessage(UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept; + + static LRESULT CALLBACK WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + UniqueHWND window_handle_; + std::wstring window_class_name_; + std::mutex tasks_mutex_; + std::queue tasks_; + + // Prevent copying. + TaskRunnerWindow(TaskRunnerWindow const&) = delete; + TaskRunnerWindow& operator=(TaskRunnerWindow const&) = delete; +}; +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TASK_RUNNER_WINDOW_H_ diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp index e2ecd42d313..06626a2c35c 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -54,7 +54,8 @@ void MockInitCamera(MockCamera* camera, bool success) { .Times(1) .WillOnce([camera, success](flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, - const PlatformMediaSettings& media_settings) { + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner) { assert(camera->pending_int_result_); if (success) { camera->pending_int_result_(1); diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp index 3aa199345c2..19f87ce130c 100644 --- a/packages/camera/camera_windows/windows/test/camera_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -52,10 +52,11 @@ TEST(Camera, InitCameraCreatesCaptureController) { PlatformMediaSettings media_settings(PlatformResolutionPreset::kMax, false); // Init camera with mock capture controller factory - bool result = camera->InitCamera( - std::move(capture_controller_factory), - std::make_unique().get(), - std::make_unique().get(), media_settings); + bool result = + camera->InitCamera(std::move(capture_controller_factory), + std::make_unique().get(), + std::make_unique().get(), + media_settings, std::make_shared()); EXPECT_TRUE(result); EXPECT_TRUE(camera->GetCaptureController() != nullptr); } @@ -84,10 +85,11 @@ TEST(Camera, InitCameraReportsFailure) { PlatformMediaSettings media_settings(PlatformResolutionPreset::kMax, false); // Init camera with mock capture controller factory - bool result = camera->InitCamera( - std::move(capture_controller_factory), - std::make_unique().get(), - std::make_unique().get(), media_settings); + bool result = + camera->InitCamera(std::move(capture_controller_factory), + std::make_unique().get(), + std::make_unique().get(), + media_settings, std::make_shared()); EXPECT_FALSE(result); EXPECT_TRUE(camera->GetCaptureController() != nullptr); } diff --git a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp index 98e775845a6..9bc6fa448da 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -33,6 +33,7 @@ void MockInitCaptureController( CaptureControllerImpl* capture_controller, MockTextureRegistrar* texture_registrar, MockCaptureEngine* engine, MockCamera* camera, int64_t mock_texture_id, + std::shared_ptr task_runner, const PlatformMediaSettings media_settings = PlatformMediaSettings(PlatformResolutionPreset::kMax, true)) { ComPtr video_source = new MockMediaSource(); @@ -62,7 +63,7 @@ void MockInitCaptureController( EXPECT_CALL(*engine, Initialize).Times(1); bool result = capture_controller->InitCaptureDevice( - texture_registrar, MOCK_DEVICE_ID, media_settings); + texture_registrar, MOCK_DEVICE_ID, media_settings, task_runner); EXPECT_TRUE(result); @@ -237,12 +238,14 @@ TEST(CaptureController, std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Init capture controller with mocks and tests MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); capture_controller = nullptr; camera = nullptr; @@ -258,19 +261,21 @@ TEST(CaptureController, InitCaptureEngineCanOnlyBeCalledOnce) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Init capture controller once with mocks and tests MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); // Init capture controller a second time. EXPECT_CALL(*camera, OnCreateCaptureEngineFailed).Times(1); bool result = capture_controller->InitCaptureDevice( texture_registrar.get(), MOCK_DEVICE_ID, - PlatformMediaSettings(PlatformResolutionPreset::kMax, true)); + PlatformMediaSettings(PlatformResolutionPreset::kMax, true), task_runner); EXPECT_FALSE(result); @@ -288,6 +293,7 @@ TEST(CaptureController, InitCaptureEngineReportsFailure) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); ComPtr video_source = new MockMediaSource(); ComPtr audio_source = new MockMediaSource(); @@ -312,7 +318,7 @@ TEST(CaptureController, InitCaptureEngineReportsFailure) { bool result = capture_controller->InitCaptureDevice( texture_registrar.get(), MOCK_DEVICE_ID, - PlatformMediaSettings(PlatformResolutionPreset::kMax, true)); + PlatformMediaSettings(PlatformResolutionPreset::kMax, true), task_runner); EXPECT_FALSE(result); EXPECT_FALSE(engine->initialized_); @@ -331,6 +337,7 @@ TEST(CaptureController, InitCaptureEngineReportsAccessDenied) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); ComPtr video_source = new MockMediaSource(); ComPtr audio_source = new MockMediaSource(); @@ -357,7 +364,7 @@ TEST(CaptureController, InitCaptureEngineReportsAccessDenied) { bool result = capture_controller->InitCaptureDevice( texture_registrar.get(), MOCK_DEVICE_ID, - PlatformMediaSettings(PlatformResolutionPreset::kMax, true)); + PlatformMediaSettings(PlatformResolutionPreset::kMax, true), task_runner); EXPECT_FALSE(result); EXPECT_FALSE(engine->initialized_); @@ -376,11 +383,13 @@ TEST(CaptureController, ReportsInitializedErrorEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); EXPECT_CALL(*camera, OnCreateCaptureEngineFailed( Eq(CameraResult::kError), @@ -405,11 +414,13 @@ TEST(CaptureController, ReportsInitializedAccessDeniedEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); EXPECT_CALL(*camera, OnCreateCaptureEngineFailed( Eq(CameraResult::kAccessDenied), @@ -434,11 +445,12 @@ TEST(CaptureController, ReportsCaptureEngineErrorEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); - + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); EXPECT_CALL(*(camera.get()), OnCaptureError(Eq(CameraResult::kError), Eq("Unspecified error"))) @@ -461,11 +473,12 @@ TEST(CaptureController, ReportsCaptureEngineAccessDeniedEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); - + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); EXPECT_CALL(*(camera.get()), OnCaptureError(Eq(CameraResult::kAccessDenied), Eq("Access is denied."))) @@ -488,12 +501,13 @@ TEST(CaptureController, StartPreviewStartsProcessingSamples) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); - + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr preview_sink = new MockCapturePreviewSink(); @@ -575,12 +589,14 @@ TEST(CaptureController, ReportsStartPreviewError) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); @@ -616,12 +632,14 @@ TEST(CaptureController, IgnoresStartPreviewErrorEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); EXPECT_CALL(*camera, OnStartPreviewFailed).Times(0); EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded).Times(0); @@ -643,12 +661,14 @@ TEST(CaptureController, ReportsStartPreviewAccessDenied) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); @@ -682,12 +702,14 @@ TEST(CaptureController, StartRecordSuccess) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -736,6 +758,7 @@ TEST(CaptureController, StartRecordWithSettingsSuccess) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; @@ -751,7 +774,7 @@ TEST(CaptureController, StartRecordWithSettingsSuccess) { // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id, - media_settings); + task_runner, media_settings); ComPtr capture_source = new MockCaptureSource(); @@ -823,12 +846,14 @@ TEST(CaptureController, ReportsStartRecordError) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -864,12 +889,14 @@ TEST(CaptureController, ReportsStartRecordAccessDenied) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -905,12 +932,14 @@ TEST(CaptureController, ReportsStartRecordErrorEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -970,12 +999,14 @@ TEST(CaptureController, ReportsStartRecordAccessDeniedEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1035,12 +1066,14 @@ TEST(CaptureController, StopRecordSuccess) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1080,12 +1113,14 @@ TEST(CaptureController, ReportsStopRecordError) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1124,12 +1159,14 @@ TEST(CaptureController, ReportsStopRecordAccessDenied) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1168,12 +1205,14 @@ TEST(CaptureController, ReportsStopRecordErrorEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1209,12 +1248,14 @@ TEST(CaptureController, ReportsStopRecordAccessDeniedEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1250,12 +1291,14 @@ TEST(CaptureController, TakePictureSuccess) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1292,12 +1335,14 @@ TEST(CaptureController, ReportsTakePictureError) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1334,12 +1379,14 @@ TEST(CaptureController, ReportsTakePictureAccessDenied) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1378,12 +1425,14 @@ TEST(CaptureController, ReportsPhotoTakenErrorEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1423,12 +1472,14 @@ TEST(CaptureController, ReportsPhotoTakenAccessDeniedEvent) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr capture_source = new MockCaptureSource(); @@ -1468,12 +1519,14 @@ TEST(CaptureController, PauseResumePreviewSuccess) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); ComPtr preview_sink = new MockCapturePreviewSink(); @@ -1505,11 +1558,14 @@ TEST(CaptureController, PausePreviewFailsIfPreviewNotStarted) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); + int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); // Pause preview fails if not started EXPECT_CALL(*camera, OnPausePreviewFailed(Eq(CameraResult::kError), @@ -1532,11 +1588,14 @@ TEST(CaptureController, ResumePreviewFailsIfPreviewNotStarted) { std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); + auto task_runner = std::make_shared(); + int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + engine.Get(), camera.get(), mock_texture_id, + task_runner); // Resume preview fails if not started. EXPECT_CALL(*camera, OnResumePreviewFailed(Eq(CameraResult::kError), diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 8c279589f8c..95457f45893 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -203,7 +203,8 @@ class MockCamera : public Camera { MOCK_METHOD(bool, InitCamera, (flutter::TextureRegistrar * texture_registrar, flutter::BinaryMessenger* messenger, - const PlatformMediaSettings& media_settings), + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner), (override)); std::unique_ptr capture_controller_; @@ -236,7 +237,8 @@ class MockCaptureController : public CaptureController { MOCK_METHOD(bool, InitCaptureDevice, (flutter::TextureRegistrar * texture_registrar, const std::string& device_id, - const PlatformMediaSettings& media_settings), + const PlatformMediaSettings& media_settings, + std::shared_ptr task_runner), (override)); MOCK_METHOD(uint32_t, GetPreviewWidth, (), (const override)); @@ -1076,6 +1078,12 @@ class MockCaptureEngine : public IMFCaptureEngine { volatile ULONG ref_ = 0; bool initialized_ = false; }; + +class MockTaskRunner : public TaskRunner { + public: + MOCK_METHOD(void, EnqueueTask, (TaskClosure), (override)); +}; + // Mock class for flutter::EventSink class MockEventSink : public flutter::EventSink { public: diff --git a/packages/camera/camera_windows/windows/test/task_runner_window_test.cpp b/packages/camera/camera_windows/windows/test/task_runner_window_test.cpp new file mode 100644 index 00000000000..67382bd8b76 --- /dev/null +++ b/packages/camera/camera_windows/windows/test/task_runner_window_test.cpp @@ -0,0 +1,34 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "task_runner_window.h" + +#include + +#include + +namespace camera_windows { +namespace test { + +static void ProcessOneMessage() { + MSG msg; + GetMessage(&msg, nullptr, 0, 0); + TranslateMessage(&msg); + DispatchMessage(&msg); +} + +TEST(TaskRunnerWindow, EnqueuedTaskIsExecuted) { + TaskRunnerWindow task_runner; + + volatile bool task_completed = false; + + task_runner.EnqueueTask([&task_completed]() { task_completed = true; }); + + ProcessOneMessage(); + + EXPECT_TRUE(task_completed); +} + +} // namespace test +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/unique_hwnd.h b/packages/camera/camera_windows/windows/unique_hwnd.h new file mode 100644 index 00000000000..231c523bab7 --- /dev/null +++ b/packages/camera/camera_windows/windows/unique_hwnd.h @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_UNIQUE_HWND_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_UNIQUE_HWND_H_ + +#include + +#include + +namespace camera_windows { + +struct UniqueHWNDDeleter { + typedef HWND pointer; + + void operator()(HWND hwnd) { + if (hwnd) { + DestroyWindow(hwnd); + } + } +}; + +typedef std::unique_ptr UniqueHWND; + +} // namespace camera_windows + +#endif PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_UNIQUE_HWND_H_ From 9ba784150185e97099ce25895714f03d31b02df3 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Wed, 4 Dec 2024 15:03:04 +0200 Subject: [PATCH 05/16] Use a camera-specific `EventChannel` Instead of a global `EventSink` that is re-used for each stream, create a dedicated `EventChannel` for each capture stream. The channel gets an unique name like `plugins.flutter.io/camera_android/imageStream/` for each camera instance. Addresses: https://github.com/flutter/packages/pull/7067#discussion_r1820651281 --- .../camera_windows/lib/camera_windows.dart | 14 +++-- .../camera_windows/windows/camera_plugin.cpp | 54 ++++++++----------- .../camera_windows/windows/camera_plugin.h | 2 - .../windows/test/camera_plugin_test.cpp | 30 +++++------ 4 files changed, 44 insertions(+), 56 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index b1f09f1a933..ee2743c60ce 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -133,6 +133,7 @@ class CameraWindows extends CameraPlatform { @override Future dispose(int cameraId) async { + await _stopPlatformStream(cameraId); await _hostApi.dispose(cameraId); // Destroy method channel after camera is disposed to be able to handle last messages. @@ -266,13 +267,14 @@ class CameraWindows extends CameraPlatform { } Future _startPlatformStream(int cameraId) async { - _startStreamListener(); await _hostApi.startImageStream(cameraId); + _startStreamListener(cameraId); } - void _startStreamListener() { - const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera_android/imageStream'); + void _startStreamListener(int cameraId) { + final eventChannelName = + 'plugins.flutter.io/camera_windows/imageStream/$cameraId'; + final EventChannel cameraEventChannel = EventChannel(eventChannelName); _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { _frameStreamController! @@ -281,6 +283,10 @@ class CameraWindows extends CameraPlatform { } FutureOr _onFrameStreamCancel(int cameraId) async { + await _stopPlatformStream(cameraId); + } + + Future _stopPlatformStream(int cameraId) async { await _hostApi.stopImageStream(cameraId); await _platformImageStreamSubscription?.cancel(); _platformImageStreamSubscription = null; diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index c3eb02a7db3..f727670b8cd 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "capture_device_info.h" #include "com_heap_ptr.h" @@ -35,10 +36,6 @@ namespace { const std::string kPictureCaptureExtension = "jpeg"; const std::string kVideoCaptureExtension = "mp4"; -constexpr char kFrameEventChannelName[] = - "plugins.flutter.io/camera_android/imageStream"; - -std::unique_ptr> event_sink; // Builds CaptureDeviceInfo object from given device holding device name and id. std::unique_ptr GetDeviceInfo(IMFActivate* device) { @@ -123,34 +120,12 @@ std::optional GetFilePathForVideo() { } } // namespace -// a setter for the event sink helpful for testing. -void CameraPlugin::SetEventSink( - std::unique_ptr> events) { - event_sink = std::move(events); -} - // static void CameraPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows* registrar) { std::unique_ptr plugin = std::make_unique( registrar->texture_registrar(), registrar->messenger()); - auto frameEventchannel = std::make_unique>( - registrar->messenger(), kFrameEventChannelName, - &flutter::StandardMethodCodec::GetInstance()); - - auto event_channel_handler = - std::make_unique>( - [plugin = plugin.get()](auto arguments, auto events) { - plugin->SetEventSink(std::move(events)); - return nullptr; - }, - [](auto arguments) { - event_sink.reset(); - return nullptr; - }); - frameEventchannel->SetStreamHandler(std::move(event_channel_handler)); - CameraApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); @@ -377,14 +352,29 @@ std::optional CameraPlugin::StartImageStream(int64_t camera_id) { return FlutterError("camera_error", "Camera not created"); } - if (!event_sink) { - return FlutterError("camera_error", - "Unable to make event channel from windows"); - } - CaptureController* cc = camera->GetCaptureController(); assert(cc); - cc->StartImageStream(std::move(event_sink)); + + std::ostringstream event_channel_name; + event_channel_name << "plugins.flutter.io/camera_windows/imageStream/" + << camera_id; + + auto frame_event_channel = + flutter::EventChannel(messenger_, event_channel_name.str(), + &flutter::StandardMethodCodec::GetInstance()); + + auto event_channel_handler = + std::make_unique>( + [cc](auto arguments, auto events) { + cc->StartImageStream(std::move(events)); + return nullptr; + }, + [cc](auto arguments) { + cc->StopImageStream(); + return nullptr; + }); + + frame_event_channel.SetStreamHandler(std::move(event_channel_handler)); return std::nullopt; } diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 144f2434aae..3f901e3f017 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -32,8 +32,6 @@ class CameraPlugin : public flutter::Plugin, public CameraApi, public VideoCaptureDeviceEnumerator { public: - void SetEventSink( - std::unique_ptr> events); static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); CameraPlugin(flutter::TextureRegistrar* texture_registrar, diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp index 06626a2c35c..4509a888cce 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -27,10 +27,10 @@ using ::testing::_; using ::testing::DoAll; using ::testing::EndsWith; using ::testing::Eq; -using ::testing::Pointee; -using ::testing::Return; using ::testing::Optional; +using ::testing::Pointee; using ::testing::Property; +using ::testing::Return; void MockInitCamera(MockCamera* camera, bool success) { EXPECT_CALL(*camera, @@ -371,12 +371,10 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) { EXPECT_CALL(*camera, GetCaptureController) .Times(1) - .WillOnce([cam = camera.get()]() { - return cam->capture_controller_.get(); - }); + .WillOnce( + [cam = camera.get()]() { return cam->capture_controller_.get(); }); - EXPECT_CALL(*capture_controller, StartImageStream) - .Times(1); + EXPECT_CALL(*capture_controller, StartImageStream).Times(1); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); @@ -385,10 +383,6 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) { std::make_unique().get(), std::make_unique()); - // Set the event sink to a mocked event sink. - auto mock_event_sink = std::make_unique(); - plugin.SetEventSink(std::move(mock_event_sink)); - // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); @@ -425,7 +419,8 @@ TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) { // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); - EXPECT_THAT(plugin.StartImageStream(missing_camera_id), Optional(Property("code", &FlutterError::code, "camera_error"))); + EXPECT_THAT(plugin.StartImageStream(missing_camera_id), + Optional(Property("code", &FlutterError::code, "camera_error"))); } TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) { @@ -445,12 +440,10 @@ TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) { EXPECT_CALL(*camera, GetCaptureController) .Times(1) - .WillOnce([cam = camera.get()]() { - return cam->capture_controller_.get(); - }); + .WillOnce( + [cam = camera.get()]() { return cam->capture_controller_.get(); }); - EXPECT_CALL(*capture_controller, StopImageStream) - .Times(1); + EXPECT_CALL(*capture_controller, StopImageStream).Times(1); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); @@ -495,7 +488,8 @@ TEST(CameraPlugin, StopImageStreamHandlerErrorOnInvalidCameraId) { // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); - EXPECT_THAT(plugin.StopImageStream(missing_camera_id), Optional(Property("code", &FlutterError::code, "camera_error"))); + EXPECT_THAT(plugin.StopImageStream(missing_camera_id), + Optional(Property("code", &FlutterError::code, "camera_error"))); } TEST(CameraPlugin, InitializeHandlerErrorOnInvalidCameraId) { From 641e18b0bf8b25d0be9bb9eeda452a1668c6b1f5 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Wed, 4 Dec 2024 15:03:04 +0200 Subject: [PATCH 06/16] Avoid a copy in `UpdateBuffer` --- .../windows/capture_controller.cpp | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 4e330ad782b..bd36ee12bc9 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -875,38 +875,30 @@ void CaptureControllerImpl::OnRecordStopped(CameraResult result, // Implements CaptureEngineObserver::UpdateBuffer. bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer, uint32_t data_length) { + using flutter::EncodableValue, flutter::EncodableMap; + if (!texture_handler_) { return false; } if (image_stream_sink_) { - // Convert the buffer data to a std::vector. - std::vector buffer_data(buffer, buffer + data_length); - - // Ensure preview_frame_height_ and preview_frame_width_ are of supported - // types. - int preview_frame_height = static_cast(preview_frame_height_); - int preview_frame_width = static_cast(preview_frame_width_); - - // Create a map to hold the buffer data and data length. - flutter::EncodableMap data_map; - data_map[flutter::EncodableValue("data")] = - flutter::EncodableValue(buffer_data); - data_map[flutter::EncodableValue("height")] = - flutter::EncodableValue(preview_frame_height); - data_map[flutter::EncodableValue("width")] = - flutter::EncodableValue(preview_frame_width); - data_map[flutter::EncodableValue("length")] = - flutter::EncodableValue(static_cast(data_length)); - - // Wrap the map in a flutter::EncodableValue. - flutter::EncodableValue encoded_value(data_map); - - // Send the encoded value through the image_stream_sink_. - std::weak_ptr weak_sink = image_stream_sink_; - task_runner_->EnqueueTask([weak_sink, encoded_value]() { + // Encode the frame into a map that can be sent through the `EventSink`. + EncodableMap encoded_frame = { + {EncodableValue("data"), + EncodableValue(std::vector(buffer, buffer + data_length))}, + {EncodableValue("height"), + EncodableValue(static_cast(preview_frame_height_))}, + {EncodableValue("width"), + EncodableValue(static_cast(preview_frame_width_))}, + {EncodableValue("length"), + EncodableValue(static_cast(data_length))}}; + + task_runner_->EnqueueTask([weak_sink = std::weak_ptr(image_stream_sink_), + encoded_frame = std::move(encoded_frame)]() { std::shared_ptr> sink = weak_sink.lock(); - if (sink) sink->Success(encoded_value); + if (sink) { + sink->Success(encoded_frame); + } }); } return texture_handler_->UpdateBuffer(buffer, data_length); From 5f9627f2287cd6a9145c46921d7d2b950ecd0ba2 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Wed, 4 Dec 2024 15:03:04 +0200 Subject: [PATCH 07/16] Simplify `onStreamedFrameAvailable` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don’t really need to keep track of the subscription and controller in instance variables. --- .../camera_windows/lib/camera_windows.dart | 72 +++++-------------- .../camera_windows/lib/src/messages.g.dart | 10 ++- .../camera_windows/pigeons/messages.dart | 3 +- .../test/camera_windows_test.mocks.dart | 9 +-- .../camera_windows/windows/camera_plugin.cpp | 9 ++- .../camera_windows/windows/camera_plugin.h | 2 +- .../windows/capture_controller.h | 6 ++ .../camera_windows/windows/messages.g.cpp | 8 +-- .../camera_windows/windows/messages.g.h | 3 +- .../windows/test/camera_plugin_test.cpp | 19 +++-- .../camera_windows/windows/test/mocks.h | 1 + 11 files changed, 68 insertions(+), 74 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index ee2743c60ce..174a552b794 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -33,12 +33,6 @@ class CameraWindows extends CameraPlatform { final Map hostCameraHandlers = {}; - // The stream to receive frames from the native code. - StreamSubscription? _platformImageStreamSubscription; - - // The stream for vending frames to platform interface clients. - StreamController? _frameStreamController; - /// The controller that broadcasts events coming from handleCameraMethodCall /// /// It is a `broadcast` because multiple controllers will connect to @@ -133,7 +127,6 @@ class CameraWindows extends CameraPlatform { @override Future dispose(int cameraId) async { - await _stopPlatformStream(cameraId); await _hostApi.dispose(cameraId); // Destroy method channel after camera is disposed to be able to handle last messages. @@ -245,52 +238,25 @@ class CameraWindows extends CameraPlatform { @override Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { - _installStreamController( - onListen: () => _onFrameStreamListen(cameraId), - onCancel: () => _onFrameStreamCancel(cameraId)); - return _frameStreamController!.stream; - } - - StreamController _installStreamController( - {void Function()? onListen, void Function()? onCancel}) { - _frameStreamController = StreamController( - onListen: onListen ?? () {}, - onPause: _onFrameStreamPauseResume, - onResume: _onFrameStreamPauseResume, - onCancel: onCancel ?? () {}, - ); - return _frameStreamController!; - } - - void _onFrameStreamListen(int cameraId) { - _startPlatformStream(cameraId); - } - - Future _startPlatformStream(int cameraId) async { - await _hostApi.startImageStream(cameraId); - _startStreamListener(cameraId); - } - - void _startStreamListener(int cameraId) { - final eventChannelName = - 'plugins.flutter.io/camera_windows/imageStream/$cameraId'; - final EventChannel cameraEventChannel = EventChannel(eventChannelName); - _platformImageStreamSubscription = - cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { - _frameStreamController! - .add(cameraImageFromPlatformData(imageData as Map)); - }); - } - - FutureOr _onFrameStreamCancel(int cameraId) async { - await _stopPlatformStream(cameraId); - } - - Future _stopPlatformStream(int cameraId) async { - await _hostApi.stopImageStream(cameraId); - await _platformImageStreamSubscription?.cancel(); - _platformImageStreamSubscription = null; - _frameStreamController = null; + late StreamController controller; + + controller = StreamController( + onListen: () async { + final String eventChannelName = + await _hostApi.startImageStream(cameraId); + final EventChannel imageStreamChannel = + EventChannel(eventChannelName); + imageStreamChannel.receiveBroadcastStream().listen((dynamic image) => + controller.add( + cameraImageFromPlatformData(image as Map))); + }, + onPause: _onFrameStreamPauseResume, + onResume: _onFrameStreamPauseResume, + onCancel: () async { + await _hostApi.stopImageStream(cameraId); + }); + + return controller.stream; } void _onFrameStreamPauseResume() { diff --git a/packages/camera/camera_windows/lib/src/messages.g.dart b/packages/camera/camera_windows/lib/src/messages.g.dart index c91d28ceac0..0f1f3fb7d22 100644 --- a/packages/camera/camera_windows/lib/src/messages.g.dart +++ b/packages/camera/camera_windows/lib/src/messages.g.dart @@ -363,7 +363,8 @@ class CameraApi { } /// Starts the image stream for the given camera. - Future startImageStream(int cameraId) async { + /// Returns the name of the [EventChannel] used to deliver the images. + Future startImageStream(int cameraId) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = @@ -382,8 +383,13 @@ class CameraApi { message: pigeonVar_replyList[1] as String?, details: pigeonVar_replyList[2], ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); } else { - return; + return (pigeonVar_replyList[0] as String?)!; } } diff --git a/packages/camera/camera_windows/pigeons/messages.dart b/packages/camera/camera_windows/pigeons/messages.dart index 94d8af9230c..bdcf180ce50 100644 --- a/packages/camera/camera_windows/pigeons/messages.dart +++ b/packages/camera/camera_windows/pigeons/messages.dart @@ -71,7 +71,8 @@ abstract class CameraApi { String stopVideoRecording(int cameraId); /// Starts the image stream for the given camera. - void startImageStream(int cameraId); + /// Returns the name of the [EventChannel] used to deliver the images. + String startImageStream(int cameraId); /// Stops the image stream for the given camera. void stopImageStream(int cameraId); diff --git a/packages/camera/camera_windows/test/camera_windows_test.mocks.dart b/packages/camera/camera_windows/test/camera_windows_test.mocks.dart index 7e22e173918..301c4405aaa 100644 --- a/packages/camera/camera_windows/test/camera_windows_test.mocks.dart +++ b/packages/camera/camera_windows/test/camera_windows_test.mocks.dart @@ -166,14 +166,15 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi { ) as _i4.Future); @override - _i4.Future startImageStream(int? cameraId) => (super.noSuchMethod( + _i4.Future startImageStream(int? cameraId) => (super.noSuchMethod( Invocation.method( #startImageStream, [cameraId], ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i4.Future.value('imageStream/$cameraId'), + returnValueForMissingStub: + _i4.Future.value('imageStream/$cameraId'), + ) as _i4.Future); @override _i4.Future stopImageStream(int? cameraId) => (super.noSuchMethod( diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index f727670b8cd..8c3efd33679 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -346,7 +346,7 @@ void CameraPlugin::StopVideoRecording( } } -std::optional CameraPlugin::StartImageStream(int64_t camera_id) { +ErrorOr CameraPlugin::StartImageStream(int64_t camera_id) { Camera* camera = GetCameraByCameraId(camera_id); if (!camera) { return FlutterError("camera_error", "Camera not created"); @@ -355,6 +355,11 @@ std::optional CameraPlugin::StartImageStream(int64_t camera_id) { CaptureController* cc = camera->GetCaptureController(); assert(cc); + if (cc->IsStreaming()) { + return FlutterError("camera_error", + "Images from camera are already streaming"); + } + std::ostringstream event_channel_name; event_channel_name << "plugins.flutter.io/camera_windows/imageStream/" << camera_id; @@ -376,7 +381,7 @@ std::optional CameraPlugin::StartImageStream(int64_t camera_id) { frame_event_channel.SetStreamHandler(std::move(event_channel_handler)); - return std::nullopt; + return event_channel_name.str(); } std::optional CameraPlugin::StopImageStream(int64_t camera_id) { diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 3f901e3f017..ab2f42cdc80 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -69,7 +69,7 @@ class CameraPlugin : public flutter::Plugin, void StopVideoRecording( int64_t camera_id, std::function reply)> result) override; - std::optional StartImageStream(int64_t camera_id) override; + ErrorOr StartImageStream(int64_t camera_id) override; std::optional StopImageStream(int64_t camera_id) override; void TakePicture( int64_t camera_id, diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index b3ad7e83b87..ec35a3574f9 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -104,6 +104,9 @@ class CaptureController { // Stops the current image streaming. virtual void StopImageStream() = 0; + // Returns true if an image stream is currently running. + virtual bool IsStreaming() const = 0; + // Captures a still photo. virtual void TakePicture(const std::string& file_path) = 0; }; @@ -142,6 +145,9 @@ class CaptureControllerImpl : public CaptureController, std::unique_ptr> sink) override; void StopImageStream() override; + bool IsStreaming() const override { + return static_cast(image_stream_sink_); + } void TakePicture(const std::string& file_path) override; // CaptureEngineObserver diff --git a/packages/camera/camera_windows/windows/messages.g.cpp b/packages/camera/camera_windows/windows/messages.g.cpp index d503a449cef..f55d42a8b00 100644 --- a/packages/camera/camera_windows/windows/messages.g.cpp +++ b/packages/camera/camera_windows/windows/messages.g.cpp @@ -516,14 +516,14 @@ void CameraApi::SetUp(flutter::BinaryMessenger* binary_messenger, return; } const int64_t camera_id_arg = encodable_camera_id_arg.LongValue(); - std::optional output = + ErrorOr output = api->StartImageStream(camera_id_arg); - if (output.has_value()) { - reply(WrapError(output.value())); + if (output.has_error()) { + reply(WrapError(output.error())); return; } EncodableList wrapped; - wrapped.push_back(EncodableValue()); + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); diff --git a/packages/camera/camera_windows/windows/messages.g.h b/packages/camera/camera_windows/windows/messages.g.h index 621466fe031..0dd4c36fedc 100644 --- a/packages/camera/camera_windows/windows/messages.g.h +++ b/packages/camera/camera_windows/windows/messages.g.h @@ -190,7 +190,8 @@ class CameraApi { int64_t camera_id, std::function reply)> result) = 0; // Starts the image stream for the given camera. - virtual std::optional StartImageStream(int64_t camera_id) = 0; + // Returns the name of the [EventChannel] used to deliver the images. + virtual ErrorOr StartImageStream(int64_t camera_id) = 0; // Stops the image stream for the given camera. virtual std::optional StopImageStream(int64_t camera_id) = 0; // Starts the preview stream for the given camera. diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp index 4509a888cce..64dbfe8bee4 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -363,6 +363,11 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) { std::unique_ptr capture_controller = std::make_unique(); + std::unique_ptr texture_registrar = + std::make_unique(); + std::unique_ptr messenger = + std::make_unique(); + EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { @@ -374,19 +379,21 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) { .WillOnce( [cam = camera.get()]() { return cam->capture_controller_.get(); }); - EXPECT_CALL(*capture_controller, StartImageStream).Times(1); + EXPECT_CALL(*capture_controller, IsStreaming).WillRepeatedly(Return(false)); + + EXPECT_CALL(*messenger, SetMessageHandler).Times(1); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); - MockCameraPlugin plugin(std::make_unique().get(), - std::make_unique().get(), + MockCameraPlugin plugin(texture_registrar.get(), messenger.get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); - EXPECT_EQ(plugin.StartImageStream(mock_camera_id), std::nullopt); + auto result = plugin.StartImageStream(mock_camera_id); + EXPECT_FALSE(result.has_error()); } TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) { @@ -419,8 +426,8 @@ TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) { // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); - EXPECT_THAT(plugin.StartImageStream(missing_camera_id), - Optional(Property("code", &FlutterError::code, "camera_error"))); + auto result = plugin.StartImageStream(missing_camera_id); + EXPECT_TRUE(result.has_error()); } TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) { diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 95457f45893..1fd646e8453 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -255,6 +255,7 @@ class MockCaptureController : public CaptureController { (std::unique_ptr> sink), (override)); MOCK_METHOD(void, StopImageStream, (), (override)); + MOCK_METHOD(bool, IsStreaming, (), (const override)); MOCK_METHOD(void, TakePicture, (const std::string& file_path), (override)); }; From 8a062a09d763d7a65291f05ecba7add92ecaf250 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Thu, 5 Dec 2024 13:07:50 +0200 Subject: [PATCH 08/16] Remove the `stopImageStream` method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Given that subscribing to the `EventChannel` tells the capture controller to start the image stream and cancelling the subscription tells it to stop, we don’t actually need a separate method in `CameraPlatform` for stopping the stream. --- .../camera_windows/lib/camera_windows.dart | 8 ++- .../camera_windows/lib/src/messages.g.dart | 26 +------ .../camera_windows/pigeons/messages.dart | 4 +- .../test/camera_windows_test.mocks.dart | 10 --- .../camera_windows/windows/camera_plugin.cpp | 13 ---- .../camera_windows/windows/camera_plugin.h | 1 - .../camera_windows/windows/messages.g.cpp | 35 ---------- .../camera_windows/windows/messages.g.h | 3 +- .../windows/test/camera_plugin_test.cpp | 69 ------------------- 9 files changed, 8 insertions(+), 161 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 174a552b794..8983d570617 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -239,6 +239,7 @@ class CameraWindows extends CameraPlatform { Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { late StreamController controller; + StreamSubscription? subscription; controller = StreamController( onListen: () async { @@ -246,14 +247,15 @@ class CameraWindows extends CameraPlatform { await _hostApi.startImageStream(cameraId); final EventChannel imageStreamChannel = EventChannel(eventChannelName); - imageStreamChannel.receiveBroadcastStream().listen((dynamic image) => - controller.add( + subscription = imageStreamChannel.receiveBroadcastStream().listen( + (dynamic image) => controller.add( cameraImageFromPlatformData(image as Map))); }, onPause: _onFrameStreamPauseResume, onResume: _onFrameStreamPauseResume, onCancel: () async { - await _hostApi.stopImageStream(cameraId); + // Cancelling the subscription stops the image capture on the native side. + await subscription?.cancel(); }); return controller.stream; diff --git a/packages/camera/camera_windows/lib/src/messages.g.dart b/packages/camera/camera_windows/lib/src/messages.g.dart index 0f1f3fb7d22..49b5078fb67 100644 --- a/packages/camera/camera_windows/lib/src/messages.g.dart +++ b/packages/camera/camera_windows/lib/src/messages.g.dart @@ -364,6 +364,7 @@ class CameraApi { /// Starts the image stream for the given camera. /// Returns the name of the [EventChannel] used to deliver the images. + /// Cancelling the subscription to the channel stops the capture. Future startImageStream(int cameraId) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$pigeonVar_messageChannelSuffix'; @@ -393,31 +394,6 @@ class CameraApi { } } - /// Stops the image stream for the given camera. - Future stopImageStream(int cameraId) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.camera_windows.CameraApi.stopImageStream$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([cameraId]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - /// Starts the preview stream for the given camera. Future pausePreview(int cameraId) async { final String pigeonVar_channelName = diff --git a/packages/camera/camera_windows/pigeons/messages.dart b/packages/camera/camera_windows/pigeons/messages.dart index bdcf180ce50..de6489b11d3 100644 --- a/packages/camera/camera_windows/pigeons/messages.dart +++ b/packages/camera/camera_windows/pigeons/messages.dart @@ -72,11 +72,9 @@ abstract class CameraApi { /// Starts the image stream for the given camera. /// Returns the name of the [EventChannel] used to deliver the images. + /// Cancelling the subscription to the channel stops the capture. String startImageStream(int cameraId); - /// Stops the image stream for the given camera. - void stopImageStream(int cameraId); - /// Starts the preview stream for the given camera. @async void pausePreview(int cameraId); diff --git a/packages/camera/camera_windows/test/camera_windows_test.mocks.dart b/packages/camera/camera_windows/test/camera_windows_test.mocks.dart index 301c4405aaa..a7f873fb427 100644 --- a/packages/camera/camera_windows/test/camera_windows_test.mocks.dart +++ b/packages/camera/camera_windows/test/camera_windows_test.mocks.dart @@ -176,16 +176,6 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi { _i4.Future.value('imageStream/$cameraId'), ) as _i4.Future); - @override - _i4.Future stopImageStream(int? cameraId) => (super.noSuchMethod( - Invocation.method( - #stopImageStream, - [cameraId], - ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); - @override _i4.Future pausePreview(int? cameraId) => (super.noSuchMethod( Invocation.method( diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 8c3efd33679..bf1afde5f95 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -384,19 +384,6 @@ ErrorOr CameraPlugin::StartImageStream(int64_t camera_id) { return event_channel_name.str(); } -std::optional CameraPlugin::StopImageStream(int64_t camera_id) { - Camera* camera = GetCameraByCameraId(camera_id); - if (!camera) { - return FlutterError("camera_error", "Camera not created"); - } - - CaptureController* cc = camera->GetCaptureController(); - assert(cc); - cc->StopImageStream(); - - return std::nullopt; -} - void CameraPlugin::TakePicture( int64_t camera_id, std::function reply)> result) { auto camera = GetCameraByCameraId(camera_id); diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index ab2f42cdc80..6fa15c496a5 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -70,7 +70,6 @@ class CameraPlugin : public flutter::Plugin, int64_t camera_id, std::function reply)> result) override; ErrorOr StartImageStream(int64_t camera_id) override; - std::optional StopImageStream(int64_t camera_id) override; void TakePicture( int64_t camera_id, std::function reply)> result) override; diff --git a/packages/camera/camera_windows/windows/messages.g.cpp b/packages/camera/camera_windows/windows/messages.g.cpp index f55d42a8b00..befa503aad8 100644 --- a/packages/camera/camera_windows/windows/messages.g.cpp +++ b/packages/camera/camera_windows/windows/messages.g.cpp @@ -533,41 +533,6 @@ void CameraApi::SetUp(flutter::BinaryMessenger* binary_messenger, channel.SetMessageHandler(nullptr); } } - { - BasicMessageChannel<> channel( - binary_messenger, - "dev.flutter.pigeon.camera_windows.CameraApi.stopImageStream" + - prepended_suffix, - &GetCodec()); - if (api != nullptr) { - channel.SetMessageHandler( - [api](const EncodableValue& message, - const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_camera_id_arg = args.at(0); - if (encodable_camera_id_arg.IsNull()) { - reply(WrapError("camera_id_arg unexpectedly null.")); - return; - } - const int64_t camera_id_arg = encodable_camera_id_arg.LongValue(); - std::optional output = - api->StopImageStream(camera_id_arg); - if (output.has_value()) { - reply(WrapError(output.value())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue()); - reply(EncodableValue(std::move(wrapped))); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); - } else { - channel.SetMessageHandler(nullptr); - } - } { BasicMessageChannel<> channel( binary_messenger, diff --git a/packages/camera/camera_windows/windows/messages.g.h b/packages/camera/camera_windows/windows/messages.g.h index 0dd4c36fedc..ace0f17a8ff 100644 --- a/packages/camera/camera_windows/windows/messages.g.h +++ b/packages/camera/camera_windows/windows/messages.g.h @@ -191,9 +191,8 @@ class CameraApi { std::function reply)> result) = 0; // Starts the image stream for the given camera. // Returns the name of the [EventChannel] used to deliver the images. + // Cancelling the subscription to the channel stops the capture. virtual ErrorOr StartImageStream(int64_t camera_id) = 0; - // Stops the image stream for the given camera. - virtual std::optional StopImageStream(int64_t camera_id) = 0; // Starts the preview stream for the given camera. virtual void PausePreview( int64_t camera_id, diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp index 64dbfe8bee4..b9b1f1354e9 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -430,75 +430,6 @@ TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) { EXPECT_TRUE(result.has_error()); } -TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) { - int64_t mock_camera_id = 1234; - - std::unique_ptr camera = - std::make_unique(MOCK_DEVICE_ID); - - std::unique_ptr capture_controller = - std::make_unique(); - - EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) - .Times(1) - .WillOnce([cam = camera.get()](int64_t camera_id) { - return cam->camera_id_ == camera_id; - }); - - EXPECT_CALL(*camera, GetCaptureController) - .Times(1) - .WillOnce( - [cam = camera.get()]() { return cam->capture_controller_.get(); }); - - EXPECT_CALL(*capture_controller, StopImageStream).Times(1); - - camera->camera_id_ = mock_camera_id; - camera->capture_controller_ = std::move(capture_controller); - - MockCameraPlugin plugin(std::make_unique().get(), - std::make_unique().get(), - std::make_unique()); - - // Add mocked camera to plugins camera list. - plugin.AddCamera(std::move(camera)); - - EXPECT_EQ(plugin.StopImageStream(mock_camera_id), std::nullopt); -} - -TEST(CameraPlugin, StopImageStreamHandlerErrorOnInvalidCameraId) { - int64_t mock_camera_id = 1234; - int64_t missing_camera_id = 5678; - - std::unique_ptr camera = - std::make_unique(MOCK_DEVICE_ID); - - std::unique_ptr capture_controller = - std::make_unique(); - - EXPECT_CALL(*camera, HasCameraId) - .Times(1) - .WillOnce([cam = camera.get()](int64_t camera_id) { - return cam->camera_id_ == camera_id; - }); - - EXPECT_CALL(*camera, HasPendingResultByType).Times(0); - EXPECT_CALL(*camera, AddPendingVoidResult).Times(0); - EXPECT_CALL(*camera, GetCaptureController).Times(0); - EXPECT_CALL(*capture_controller, StopImageStream).Times(0); - - camera->camera_id_ = mock_camera_id; - - MockCameraPlugin plugin(std::make_unique().get(), - std::make_unique().get(), - std::make_unique()); - - // Add mocked camera to plugins camera list. - plugin.AddCamera(std::move(camera)); - - EXPECT_THAT(plugin.StopImageStream(missing_camera_id), - Optional(Property("code", &FlutterError::code, "camera_error"))); -} - TEST(CameraPlugin, InitializeHandlerErrorOnInvalidCameraId) { int64_t mock_camera_id = 1234; int64_t missing_camera_id = 5678; From 3510ae4df16430d85adb9d218f4076838e502f58 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Thu, 5 Dec 2024 10:59:06 +0200 Subject: [PATCH 09/16] Allow image streaming on camera_windows --- packages/camera/camera_windows/CHANGELOG.md | 1 + packages/camera/camera_windows/lib/camera_windows.dart | 3 +++ packages/camera/camera_windows/pubspec.yaml | 2 +- packages/camera/camera_windows/test/camera_windows_test.dart | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md index 7b0cca7134b..3961d8fac56 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.2.6+2 +* Restores support for streaming frames. * Fixes compile errors under strict standards mode. * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 8983d570617..56b29e77d2a 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -235,6 +235,9 @@ class CameraWindows extends CameraPlatform { 'resumeVideoRecording() is not supported due to Win32 API limitations.'); } + @override + bool supportsImageStreaming() => true; + @override Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index d66f36f2550..e49fe44e4ec 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -17,7 +17,7 @@ flutter: dartPluginClass: CameraWindows dependencies: - camera_platform_interface: ^2.6.0 + camera_platform_interface: ^2.9.0 cross_file: ^0.3.1 flutter: sdk: flutter diff --git a/packages/camera/camera_windows/test/camera_windows_test.dart b/packages/camera/camera_windows/test/camera_windows_test.dart index 7d21af5f782..3732d7b6b07 100644 --- a/packages/camera/camera_windows/test/camera_windows_test.dart +++ b/packages/camera/camera_windows/test/camera_windows_test.dart @@ -181,6 +181,11 @@ void main() { verify(mockApi.dispose(captureAny)); expect(verification.captured[0], cameraId); }); + + test('Should report support for image streaming', () async { + final CameraWindows plugin = CameraWindows(api: MockCameraApi()); + expect(plugin.supportsImageStreaming(), true); + }); }); group('Event Tests', () { From d94312f1baac6d898ef192db6a93e91bbad3ea59 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Thu, 16 Jan 2025 19:26:32 +0200 Subject: [PATCH 10/16] Bump version to 0.2.7 --- packages/camera/camera_windows/CHANGELOG.md | 2 +- packages/camera/camera_windows/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md index 3961d8fac56..344e1c8ac9c 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.2.6+2 +## 0.2.7 * Restores support for streaming frames. * Fixes compile errors under strict standards mode. diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index e49fe44e4ec..f53e6d602a5 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_windows description: A Flutter plugin for getting information about and controlling the camera on Windows. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.2.6+2 +version: 0.2.7 environment: sdk: ^3.4.0 From 9326ab23c50c438d66f89b6e850a556f18382c64 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Thu, 20 Feb 2025 21:11:00 +0200 Subject: [PATCH 11/16] Add more test cases for image streaming --- .../test/camera_windows_test.dart | 11 ++++ .../windows/test/capture_controller_test.cpp | 64 +++++++++++++++++++ .../camera_windows/windows/test/mocks.h | 5 ++ 3 files changed, 80 insertions(+) diff --git a/packages/camera/camera_windows/test/camera_windows_test.dart b/packages/camera/camera_windows/test/camera_windows_test.dart index 3732d7b6b07..ccbfcc1c61f 100644 --- a/packages/camera/camera_windows/test/camera_windows_test.dart +++ b/packages/camera/camera_windows/test/camera_windows_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_windows/camera_windows.dart'; @@ -553,6 +555,15 @@ void main() { verify(mockApi.resumePreview(captureAny)); expect(verification.captured[0], cameraId); }); + + test('Should start the image stream when it is subscribed', () async { + final Stream stream = + plugin.onStreamedFrameAvailable(cameraId); + + await stream.listen((CameraImageData frame) {}).cancel(); + + verify(mockApi.startImageStream(cameraId)); + }); }); }); } diff --git a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp index 9bc6fa448da..821910b83e9 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -1610,5 +1610,69 @@ TEST(CaptureController, ResumePreviewFailsIfPreviewNotStarted) { camera = nullptr; } +TEST(CaptureController, FrameIsDeliveredAfterStartImageStream) { + ComPtr engine = new MockCaptureEngine(); + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr capture_controller = + std::make_unique(camera.get()); + std::unique_ptr texture_registrar = + std::make_unique(); + auto task_runner = std::make_shared(); + + int64_t mock_texture_id = 1234; + + // Initialize capture controller to be able to start preview + MockInitCaptureController(capture_controller.get(), texture_registrar.get(), + engine.Get(), camera.get(), mock_texture_id, + task_runner); + + auto sink = std::make_unique(); + + // Expect the sink to receive a frame after UpdateBuffer. + EXPECT_CALL(*sink, SuccessInternal(_)); + + capture_controller->StartImageStream(std::move(sink)); + capture_controller->UpdateBuffer(nullptr, 0); + + capture_controller = nullptr; + texture_registrar = nullptr; + engine = nullptr; + camera = nullptr; +} + +TEST(CaptureController, NoFramesAreDeliveredAfterStopImageStream) { + ComPtr engine = new MockCaptureEngine(); + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr capture_controller = + std::make_unique(camera.get()); + std::unique_ptr texture_registrar = + std::make_unique(); + auto task_runner = std::make_shared(); + + int64_t mock_texture_id = 1234; + + // Initialize capture controller to be able to start preview + MockInitCaptureController(capture_controller.get(), texture_registrar.get(), + engine.Get(), camera.get(), mock_texture_id, + task_runner); + + auto sink = std::make_unique(); + + EXPECT_CALL(*sink, SuccessInternal(_)).Times(0); + + capture_controller->StartImageStream(std::move(sink)); + capture_controller->StopImageStream(); + + // Stream is stopped, the sink should not receive any frames. + capture_controller->UpdateBuffer(nullptr, 0); + + capture_controller = nullptr; + texture_registrar = nullptr; + engine = nullptr; + camera = nullptr; +} + } // namespace test } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 1fd646e8453..8d040992808 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -1085,6 +1085,11 @@ class MockTaskRunner : public TaskRunner { MOCK_METHOD(void, EnqueueTask, (TaskClosure), (override)); }; +class ImmediateTaskRunner : public TaskRunner { + public: + virtual void EnqueueTask(TaskClosure task) override { task(); } +}; + // Mock class for flutter::EventSink class MockEventSink : public flutter::EventSink { public: From e892d701b06388bfb1f91e80c6547e9372ec75d1 Mon Sep 17 00:00:00 2001 From: Jordan English Date: Fri, 8 Aug 2025 11:45:31 +0930 Subject: [PATCH 12/16] Added PlatformFrameData in messages.dart --- .../camera_windows/lib/src/messages.g.dart | 44 ++++++++++++++++++- .../camera_windows/pigeons/messages.dart | 15 +++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_windows/lib/src/messages.g.dart b/packages/camera/camera_windows/lib/src/messages.g.dart index 49b5078fb67..0d815c7c95e 100644 --- a/packages/camera/camera_windows/lib/src/messages.g.dart +++ b/packages/camera/camera_windows/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.2), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -108,6 +108,43 @@ class PlatformSize { } } +/// A representation of frame data from the camera preview stream. +class PlatformFrameData { + PlatformFrameData({ + required this.data, + required this.width, + required this.height, + required this.length, + }); + + List data; + + int width; + + int height; + + int length; + + Object encode() { + return [ + data, + width, + height, + length, + ]; + } + + static PlatformFrameData decode(Object result) { + result as List; + return PlatformFrameData( + data: (result[0] as List?)!.cast(), + width: result[1]! as int, + height: result[2]! as int, + length: result[3]! as int, + ); + } +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -124,6 +161,9 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is PlatformSize) { buffer.putUint8(131); writeValue(buffer, value.encode()); + } else if (value is PlatformFrameData) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -139,6 +179,8 @@ class _PigeonCodec extends StandardMessageCodec { return PlatformMediaSettings.decode(readValue(buffer)!); case 131: return PlatformSize.decode(readValue(buffer)!); + case 132: + return PlatformFrameData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } diff --git a/packages/camera/camera_windows/pigeons/messages.dart b/packages/camera/camera_windows/pigeons/messages.dart index de6489b11d3..f8eb1fde2c9 100644 --- a/packages/camera/camera_windows/pigeons/messages.dart +++ b/packages/camera/camera_windows/pigeons/messages.dart @@ -40,6 +40,21 @@ class PlatformSize { final double height; } +/// A representation of frame data from the camera preview stream. +class PlatformFrameData { + PlatformFrameData({ + required this.data, + required this.width, + required this.height, + required this.length, + }); + + final List data; + final int width; + final int height; + final int length; +} + @HostApi() abstract class CameraApi { /// Returns the names of all of the available capture devices. From a1536a4e1bc18b616df7c0de7bfdcb8e92b382f9 Mon Sep 17 00:00:00 2001 From: Jordan English Date: Fri, 8 Aug 2025 11:50:27 +0930 Subject: [PATCH 13/16] Replaced EncodableMap with PlatformFrameData from pigeon --- .../windows/capture_controller.cpp | 28 ++++++---- .../camera_windows/windows/messages.g.cpp | 55 ++++++++++++++++++- .../camera_windows/windows/messages.g.h | 36 +++++++++++- 3 files changed, 107 insertions(+), 12 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index bd36ee12bc9..14cddfa9bb1 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -15,6 +15,7 @@ #include #include "com_heap_ptr.h" +#include "messages.g.h" #include "photo_handler.h" #include "preview_handler.h" #include "record_handler.h" @@ -881,16 +882,23 @@ bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer, return false; } if (image_stream_sink_) { - // Encode the frame into a map that can be sent through the `EventSink`. - EncodableMap encoded_frame = { - {EncodableValue("data"), - EncodableValue(std::vector(buffer, buffer + data_length))}, - {EncodableValue("height"), - EncodableValue(static_cast(preview_frame_height_))}, - {EncodableValue("width"), - EncodableValue(static_cast(preview_frame_width_))}, - {EncodableValue("length"), - EncodableValue(static_cast(data_length))}}; + // Create a strongly-typed frame data object using Pigeon + flutter::EncodableList data_list; + std::vector buffer_data(buffer, buffer + data_length); + for (uint8_t byte : buffer_data) { + data_list.push_back(flutter::EncodableValue(static_cast(byte))); + } + + PlatformFrameData frame_data(data_list, + static_cast(preview_frame_width_), + static_cast(preview_frame_height_), + static_cast(data_length)); + + // Use CameraApi::GetCodec() to properly encode the Pigeon-defined class + // This ensures the frame data is serialized using the same codec as other + // CameraApi messages + flutter::EncodableValue encoded_frame = + CameraApi::GetCodec().EncodeMessage(frame_data.ToEncodableList()); task_runner_->EnqueueTask([weak_sink = std::weak_ptr(image_stream_sink_), encoded_frame = std::move(encoded_frame)]() { diff --git a/packages/camera/camera_windows/windows/messages.g.cpp b/packages/camera/camera_windows/windows/messages.g.cpp index befa503aad8..a6cddd5b125 100644 --- a/packages/camera/camera_windows/windows/messages.g.cpp +++ b/packages/camera/camera_windows/windows/messages.g.cpp @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.2), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -167,6 +167,48 @@ PlatformSize PlatformSize::FromEncodableList(const EncodableList& list) { return decoded; } +// PlatformFrameData + +PlatformFrameData::PlatformFrameData(const EncodableList& data, int64_t width, + int64_t height, int64_t length) + : data_(data), width_(width), height_(height), length_(length) {} + +const EncodableList& PlatformFrameData::data() const { return data_; } + +void PlatformFrameData::set_data(const EncodableList& value_arg) { + data_ = value_arg; +} + +int64_t PlatformFrameData::width() const { return width_; } + +void PlatformFrameData::set_width(int64_t value_arg) { width_ = value_arg; } + +int64_t PlatformFrameData::height() const { return height_; } + +void PlatformFrameData::set_height(int64_t value_arg) { height_ = value_arg; } + +int64_t PlatformFrameData::length() const { return length_; } + +void PlatformFrameData::set_length(int64_t value_arg) { length_ = value_arg; } + +EncodableList PlatformFrameData::ToEncodableList() const { + EncodableList list; + list.reserve(4); + list.push_back(EncodableValue(data_)); + list.push_back(EncodableValue(width_)); + list.push_back(EncodableValue(height_)); + list.push_back(EncodableValue(length_)); + return list; +} + +PlatformFrameData PlatformFrameData::FromEncodableList( + const EncodableList& list) { + PlatformFrameData decoded( + std::get(list[0]), std::get(list[1]), + std::get(list[2]), std::get(list[3])); + return decoded; +} + PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {} EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( @@ -189,6 +231,10 @@ EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( return CustomEncodableValue(PlatformSize::FromEncodableList( std::get(ReadValue(stream)))); } + case 132: { + return CustomEncodableValue(PlatformFrameData::FromEncodableList( + std::get(ReadValue(stream)))); + } default: return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } @@ -221,6 +267,13 @@ void PigeonInternalCodecSerializer::WriteValue( stream); return; } + if (custom_value->type() == typeid(PlatformFrameData)) { + stream->WriteByte(132); + WriteValue(EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } } flutter::StandardCodecSerializer::WriteValue(value, stream); } diff --git a/packages/camera/camera_windows/windows/messages.g.h b/packages/camera/camera_windows/windows/messages.g.h index ace0f17a8ff..462947ca48d 100644 --- a/packages/camera/camera_windows/windows/messages.g.h +++ b/packages/camera/camera_windows/windows/messages.g.h @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.2), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_MESSAGES_G_H_ @@ -140,6 +140,40 @@ class PlatformSize { double height_; }; +// A representation of frame data from the camera preview stream. +// +// Generated class from Pigeon that represents data sent in messages. +class PlatformFrameData { + public: + // Constructs an object setting all fields. + explicit PlatformFrameData(const flutter::EncodableList& data, int64_t width, + int64_t height, int64_t length); + + const flutter::EncodableList& data() const; + void set_data(const flutter::EncodableList& value_arg); + + int64_t width() const; + void set_width(int64_t value_arg); + + int64_t height() const; + void set_height(int64_t value_arg); + + int64_t length() const; + void set_length(int64_t value_arg); + + private: + static PlatformFrameData FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class CameraApi; + friend class CameraEventApi; + friend class PigeonInternalCodecSerializer; + flutter::EncodableList data_; + int64_t width_; + int64_t height_; + int64_t length_; +}; + class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { public: PigeonInternalCodecSerializer(); From 8de827c85928da50e4483d604f9621da3f396997 Mon Sep 17 00:00:00 2001 From: Jordan English Date: Fri, 8 Aug 2025 11:50:38 +0930 Subject: [PATCH 14/16] Replaced shared_ptr with unique_ptr --- packages/camera/camera_windows/windows/camera_plugin.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 6fa15c496a5..6a2eb98b1f2 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -95,7 +95,7 @@ class CameraPlugin : public flutter::Plugin, flutter::TextureRegistrar* texture_registrar_; flutter::BinaryMessenger* messenger_; std::vector> cameras_; - std::shared_ptr task_runner_; + std::unique_ptr task_runner_; friend class camera_windows::test::MockCameraPlugin; }; From 64eb3b688aa61fe624038beda66b9cb6eb8c3218 Mon Sep 17 00:00:00 2001 From: jordanenglish-overlay Date: Mon, 11 Aug 2025 08:52:43 +0930 Subject: [PATCH 15/16] Properly did changelog this time (this is why you shouldnt make assumptions) --- packages/camera/camera_windows/CHANGELOG.md | 3 ++- packages/camera/camera_windows/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md index f8e51cf381d..ffb03962340 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.2.7+1 +* Restores Support for streaming frames. * Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. ## 0.2.6+2 diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index be0c085ab63..f937c5f5be9 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_windows description: A Flutter plugin for getting information about and controlling the camera on Windows. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.2.7 +version: 0.2.7+1 environment: sdk: ^3.6.0 From cadabc6cb9057a9cf61fef401bb96c3167735dfc Mon Sep 17 00:00:00 2001 From: jordanenglish-overlay Date: Mon, 11 Aug 2025 09:57:50 +0930 Subject: [PATCH 16/16] Replaced shared_ptr TaskRunner with unique_ptr TaskRunner --- packages/camera/camera_windows/windows/camera.cpp | 4 ++-- packages/camera/camera_windows/windows/camera.h | 8 ++++---- packages/camera/camera_windows/windows/camera_plugin.cpp | 2 +- .../camera/camera_windows/windows/capture_controller.cpp | 2 +- .../camera/camera_windows/windows/capture_controller.h | 6 +++--- .../camera_windows/windows/test/camera_plugin_test.cpp | 2 +- .../windows/test/capture_controller_test.cpp | 2 +- packages/camera/camera_windows/windows/test/mocks.h | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index aa25c907a5f..b6d0e7ffc5f 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -40,7 +40,7 @@ CameraImpl::~CameraImpl() { bool CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner) { + std::unique_ptr task_runner) { auto capture_controller_factory = std::make_unique(); return InitCamera(std::move(capture_controller_factory), texture_registrar, @@ -52,7 +52,7 @@ bool CameraImpl::InitCamera( flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner) { + std::unique_ptr task_runner) { assert(!device_id_.empty()); messenger_ = messenger; capture_controller_ = diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 5bb25dc60e9..7b4198703b0 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -86,7 +86,7 @@ class Camera : public CaptureControllerListener { virtual bool InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner) = 0; + std::unique_ptr task_runner) = 0; }; // Concrete implementation of the |Camera| interface. @@ -153,7 +153,7 @@ class CameraImpl : public Camera { bool InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner) override; + std::unique_ptr task_runner) override; // Initializes the camera and its associated capture controller. // @@ -166,7 +166,7 @@ class CameraImpl : public Camera { flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner); + std::unique_ptr task_runner); private: // A generic type for any pending asyncronous result. @@ -233,7 +233,7 @@ class CameraImpl : public Camera { std::unique_ptr capture_controller_; std::unique_ptr event_api_; flutter::BinaryMessenger* messenger_ = nullptr; - std::shared_ptr task_runner_; + std::unique_ptr task_runner_; int64_t camera_id_ = -1; std::string device_id_; }; diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index bf1afde5f95..d939139e471 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -136,7 +136,7 @@ CameraPlugin::CameraPlugin(flutter::TextureRegistrar* texture_registrar, : texture_registrar_(texture_registrar), messenger_(messenger), camera_factory_(std::make_unique()), - task_runner_(std::make_shared()) {} + task_runner_(std::make_unique()) {} CameraPlugin::CameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 14cddfa9bb1..85f79a9e43b 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -305,7 +305,7 @@ void CaptureControllerImpl::ResetCaptureController() { bool CaptureControllerImpl::InitCaptureDevice( flutter::TextureRegistrar* texture_registrar, const std::string& device_id, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner) { + std::unique_ptr task_runner) { assert(capture_controller_listener_); if (IsInitialized()) { diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index ec35a3574f9..497e973f15e 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -74,7 +74,7 @@ class CaptureController { virtual bool InitCaptureDevice(TextureRegistrar* texture_registrar, const std::string& device_id, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner) = 0; + std::unique_ptr task_runner) = 0; // Returns preview frame width virtual uint32_t GetPreviewWidth() const = 0; @@ -133,7 +133,7 @@ class CaptureControllerImpl : public CaptureController, bool InitCaptureDevice(TextureRegistrar* texture_registrar, const std::string& device_id, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner) override; + std::unique_ptr task_runner) override; uint32_t GetPreviewWidth() const override { return preview_frame_width_; } uint32_t GetPreviewHeight() const override { return preview_frame_height_; } void StartPreview() override; @@ -242,7 +242,7 @@ class CaptureControllerImpl : public CaptureController, std::unique_ptr preview_handler_; std::unique_ptr photo_handler_; std::unique_ptr texture_handler_; - std::shared_ptr task_runner_; + std::unique_ptr task_runner_; std::shared_ptr> image_stream_sink_; CaptureControllerListener* capture_controller_listener_; diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp index b9b1f1354e9..0b2c2c392a7 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -55,7 +55,7 @@ void MockInitCamera(MockCamera* camera, bool success) { .WillOnce([camera, success](flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner) { + std::unique_ptr task_runner) { assert(camera->pending_int_result_); if (success) { camera->pending_int_result_(1); diff --git a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp index 821910b83e9..67ce473689d 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -33,7 +33,7 @@ void MockInitCaptureController( CaptureControllerImpl* capture_controller, MockTextureRegistrar* texture_registrar, MockCaptureEngine* engine, MockCamera* camera, int64_t mock_texture_id, - std::shared_ptr task_runner, + std::unique_ptr task_runner, const PlatformMediaSettings media_settings = PlatformMediaSettings(PlatformResolutionPreset::kMax, true)) { ComPtr video_source = new MockMediaSource(); diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 8d040992808..745b744d74f 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -204,7 +204,7 @@ class MockCamera : public Camera { (flutter::TextureRegistrar * texture_registrar, flutter::BinaryMessenger* messenger, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner), + std::unique_ptr task_runner), (override)); std::unique_ptr capture_controller_; @@ -238,7 +238,7 @@ class MockCaptureController : public CaptureController { (flutter::TextureRegistrar * texture_registrar, const std::string& device_id, const PlatformMediaSettings& media_settings, - std::shared_ptr task_runner), + std::unique_ptr task_runner), (override)); MOCK_METHOD(uint32_t, GetPreviewWidth, (), (const override));