From 19c5f431a3efbc2cae1d58b83e7dfb777833dbe3 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 5 Jan 2022 10:23:05 +0200 Subject: [PATCH 01/93] [camera_windows] Initial plugin implementation --- packages/camera/camera_windows/.gitignore | 7 + packages/camera/camera_windows/.metadata | 10 + packages/camera/camera_windows/CHANGELOG.md | 3 + packages/camera/camera_windows/LICENSE | 25 + packages/camera/camera_windows/README.md | 54 + .../camera/camera_windows/example/.gitignore | 46 + .../camera/camera_windows/example/.metadata | 10 + .../camera/camera_windows/example/README.md | 16 + .../camera_windows/example/lib/main.dart | 283 ++++ .../camera_windows/example/pubspec.yaml | 31 + .../camera_windows/example/windows/.gitignore | 17 + .../example/windows/CMakeLists.txt | 100 ++ .../example/windows/flutter/CMakeLists.txt | 103 ++ .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 17 + .../example/windows/runner/CMakeLists.txt | 18 + .../example/windows/runner/Runner.rc | 121 ++ .../example/windows/runner/flutter_window.cpp | 61 + .../example/windows/runner/flutter_window.h | 33 + .../example/windows/runner/main.cpp | 43 + .../example/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 20 + .../example/windows/runner/utils.cpp | 64 + .../example/windows/runner/utils.h | 19 + .../example/windows/runner/win32_window.cpp | 245 ++++ .../example/windows/runner/win32_window.h | 98 ++ .../camera_windows/lib/camera_windows.dart | 418 ++++++ .../camera_windows/lib/src/utils/utils.dart | 18 + packages/camera/camera_windows/pubspec.yaml | 27 + .../camera/camera_windows/windows/.gitignore | 17 + .../camera_windows/windows/CMakeLists.txt | 89 ++ .../camera/camera_windows/windows/camera.cpp | 266 ++++ .../camera/camera_windows/windows/camera.h | 154 +++ .../camera_windows/windows/camera_plugin.cpp | 592 +++++++++ .../camera_windows/windows/camera_plugin.h | 93 ++ .../camera_windows/windows/camera_windows.cpp | 16 + .../windows/capture_controller.cpp | 1135 +++++++++++++++++ .../windows/capture_controller.h | 260 ++++ .../windows/capture_controller_listener.h | 44 + .../windows/capture_engine_listener.cpp | 134 ++ .../windows/capture_engine_listener.h | 68 + .../camera_windows/windows/device_info.cpp | 29 + .../camera_windows/windows/device_info.h | 25 + .../include/camera_windows/camera_windows.h | 27 + .../camera_windows/windows/string_utils.cpp | 60 + .../camera_windows/windows/string_utils.h | 22 + .../windows/test/camera_plugin_test.cpp | 1008 +++++++++++++++ .../windows/test/camera_test.cpp | 332 +++++ .../camera_windows/windows/test/mocks.h | 252 ++++ 50 files changed, 6561 insertions(+) create mode 100644 packages/camera/camera_windows/.gitignore create mode 100644 packages/camera/camera_windows/.metadata create mode 100644 packages/camera/camera_windows/CHANGELOG.md create mode 100644 packages/camera/camera_windows/LICENSE create mode 100644 packages/camera/camera_windows/README.md create mode 100644 packages/camera/camera_windows/example/.gitignore create mode 100644 packages/camera/camera_windows/example/.metadata create mode 100644 packages/camera/camera_windows/example/README.md create mode 100644 packages/camera/camera_windows/example/lib/main.dart create mode 100644 packages/camera/camera_windows/example/pubspec.yaml create mode 100644 packages/camera/camera_windows/example/windows/.gitignore create mode 100644 packages/camera/camera_windows/example/windows/CMakeLists.txt create mode 100644 packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt create mode 100644 packages/camera/camera_windows/example/windows/flutter/generated_plugin_registrant.h create mode 100644 packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake create mode 100644 packages/camera/camera_windows/example/windows/runner/CMakeLists.txt create mode 100644 packages/camera/camera_windows/example/windows/runner/Runner.rc create mode 100644 packages/camera/camera_windows/example/windows/runner/flutter_window.cpp create mode 100644 packages/camera/camera_windows/example/windows/runner/flutter_window.h create mode 100644 packages/camera/camera_windows/example/windows/runner/main.cpp create mode 100644 packages/camera/camera_windows/example/windows/runner/resource.h create mode 100644 packages/camera/camera_windows/example/windows/runner/resources/app_icon.ico create mode 100644 packages/camera/camera_windows/example/windows/runner/runner.exe.manifest create mode 100644 packages/camera/camera_windows/example/windows/runner/utils.cpp create mode 100644 packages/camera/camera_windows/example/windows/runner/utils.h create mode 100644 packages/camera/camera_windows/example/windows/runner/win32_window.cpp create mode 100644 packages/camera/camera_windows/example/windows/runner/win32_window.h create mode 100644 packages/camera/camera_windows/lib/camera_windows.dart create mode 100644 packages/camera/camera_windows/lib/src/utils/utils.dart create mode 100644 packages/camera/camera_windows/pubspec.yaml create mode 100644 packages/camera/camera_windows/windows/.gitignore create mode 100644 packages/camera/camera_windows/windows/CMakeLists.txt create mode 100644 packages/camera/camera_windows/windows/camera.cpp create mode 100644 packages/camera/camera_windows/windows/camera.h create mode 100644 packages/camera/camera_windows/windows/camera_plugin.cpp create mode 100644 packages/camera/camera_windows/windows/camera_plugin.h create mode 100644 packages/camera/camera_windows/windows/camera_windows.cpp create mode 100644 packages/camera/camera_windows/windows/capture_controller.cpp create mode 100644 packages/camera/camera_windows/windows/capture_controller.h create mode 100644 packages/camera/camera_windows/windows/capture_controller_listener.h create mode 100644 packages/camera/camera_windows/windows/capture_engine_listener.cpp create mode 100644 packages/camera/camera_windows/windows/capture_engine_listener.h create mode 100644 packages/camera/camera_windows/windows/device_info.cpp create mode 100644 packages/camera/camera_windows/windows/device_info.h create mode 100644 packages/camera/camera_windows/windows/include/camera_windows/camera_windows.h create mode 100644 packages/camera/camera_windows/windows/string_utils.cpp create mode 100644 packages/camera/camera_windows/windows/string_utils.h create mode 100644 packages/camera/camera_windows/windows/test/camera_plugin_test.cpp create mode 100644 packages/camera/camera_windows/windows/test/camera_test.cpp create mode 100644 packages/camera/camera_windows/windows/test/mocks.h diff --git a/packages/camera/camera_windows/.gitignore b/packages/camera/camera_windows/.gitignore new file mode 100644 index 000000000000..e9dc58d3d6e2 --- /dev/null +++ b/packages/camera/camera_windows/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/packages/camera/camera_windows/.metadata b/packages/camera/camera_windows/.metadata new file mode 100644 index 000000000000..5bed5265e818 --- /dev/null +++ b/packages/camera/camera_windows/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 18116933e77adc82f80866c928266a5b4f1ed645 + channel: stable + +project_type: plugin diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md new file mode 100644 index 000000000000..29f648fd17f1 --- /dev/null +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release diff --git a/packages/camera/camera_windows/LICENSE b/packages/camera/camera_windows/LICENSE new file mode 100644 index 000000000000..c6823b81eb84 --- /dev/null +++ b/packages/camera/camera_windows/LICENSE @@ -0,0 +1,25 @@ +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/camera/camera_windows/README.md b/packages/camera/camera_windows/README.md new file mode 100644 index 000000000000..944b4df0ec29 --- /dev/null +++ b/packages/camera/camera_windows/README.md @@ -0,0 +1,54 @@ +# Camera Windows Plugin + +The windows implementation of [`camera`][camera]. + +*Note*: This plugin is under development. See [missing implementation](#missing-implementation). + +## Usage + +### Depend on the package + +This package is not [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), +which means you need to add `camera_windows` separately to your project dependencies to use it with [`camera`][camera] plugin. + +## Example + +Find the example in the [`camera windows` package](https://pub.dev/packages/camera_windows#example). + +## Limitations on the windows platform + +### Device orientation + +The device orientation is not detected for cameras yet. See [missing implementation](#missing-implementation) + +- `CameraPlatform.onDeviceOrientationChanged` stream returns always one item: `DeviceOrientation.landscapeRight` + +### Taking a picture + +Captured pictures are saved to default `Pictures` folder. +This folder cannot be changed at the moment. + +### Video recording + +Captures videos are saved to default `Videos` folder. +This folder cannot be changed at the moment. + +Recording video do not work if preview is not started. + +A video is recorded in following video MIME type: `video/mp4` + +Pausing and resuming the video is not supported at the moment. + +## Missing implementation + +The windows implementation of [`camera`][camera] is missing the following features: +- Exposure mode, point and offset +- Focus mode and point +- Sensor orientation +- Image format group +- Streaming of frames +- Video record pause and resume +- Support for multiple simultanious camera captures. + + +[camera]: https://pub.dev/packages/camera_windows diff --git a/packages/camera/camera_windows/example/.gitignore b/packages/camera/camera_windows/example/.gitignore new file mode 100644 index 000000000000..0fa6b675c0a5 --- /dev/null +++ b/packages/camera/camera_windows/example/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/camera/camera_windows/example/.metadata b/packages/camera/camera_windows/example/.metadata new file mode 100644 index 000000000000..a5584fc372d9 --- /dev/null +++ b/packages/camera/camera_windows/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 18116933e77adc82f80866c928266a5b4f1ed645 + channel: stable + +project_type: app diff --git a/packages/camera/camera_windows/example/README.md b/packages/camera/camera_windows/example/README.md new file mode 100644 index 000000000000..b9d5b32796c1 --- /dev/null +++ b/packages/camera/camera_windows/example/README.md @@ -0,0 +1,16 @@ +# camera_windows_example + +Demonstrates how to use the camera_windows plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart new file mode 100644 index 000000000000..eddea342105f --- /dev/null +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -0,0 +1,283 @@ +import 'dart:async'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void main() { + runApp(MyApp()); +} + +/// App for testing +class MyApp extends StatefulWidget { + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String _cameraInfo = 'Unknown'; + List _cameras = []; + int _cameraId = -1; + bool _initialized = false; + bool _recording = false; + bool _recordingTimed = false; + bool _recordAudio = true; + Size? _previewSize; + + @override + void initState() { + super.initState(); + WidgetsFlutterBinding.ensureInitialized(); + getAvailableCameras(); + } + + // Fetches list of available cameras from camera_windows plugin + Future getAvailableCameras() async { + String cameraInfo; + List cameras = []; + + try { + cameras = await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + cameraInfo = 'No available cameras'; + } else { + cameraInfo = 'Found camera: ${cameras.first.name}'; + } + } on PlatformException catch (e) { + cameraInfo = 'Failed to get cameras: ${e.code}: ${e.message}'; + } + + if (!mounted) { + return; + } + + setState(() { + _cameras = cameras; + _cameraInfo = cameraInfo; + }); + } + + /// Initializes the camera on the device. + Future initializeFirstCamera() async { + assert(_cameras.isNotEmpty); + assert(!_initialized); + try { + final Completer _initializeCompleter = + Completer(); + + final CameraDescription camera = _cameras.first; + + final int cameraId = await CameraPlatform.instance.createCamera( + camera, + ResolutionPreset.veryHigh, + enableAudio: _recordAudio, + ); + + unawaited( + CameraPlatform.instance + .onCameraInitialized(cameraId) + .first + .then((CameraInitializedEvent event) { + _initializeCompleter.complete(event); + }), + ); + + await CameraPlatform.instance.initializeCamera( + cameraId, + imageFormatGroup: ImageFormatGroup.unknown, + ); + + _previewSize = await _initializeCompleter.future.then( + (CameraInitializedEvent event) => Size( + event.previewWidth, + event.previewHeight, + ), + ); + + setState(() { + _initialized = true; + _cameraId = cameraId; + _cameraInfo = 'Capturing camera: ${camera.name}'; + }); + } on PlatformException catch (e) { + setState(() { + _initialized = false; + _cameraId = -1; + _cameraInfo = 'Failed to initialize camera: ${e.code}: ${e.message}'; + }); + } + } + + Future disposeCurrentCamera() async { + assert(_cameraId > 0); + assert(_initialized); + try { + await CameraPlatform.instance.dispose(_cameraId); + setState(() { + _initialized = false; + _cameraId = -1; + _cameraInfo = 'Camera disposed'; + _previewSize = null; + _recording = false; + _recordingTimed = false; + }); + getAvailableCameras(); + } on PlatformException catch (e) { + setState(() { + _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.message}'; + }); + } + } + + Widget buildPreview() { + return CameraPlatform.instance.buildPreview(_cameraId); + } + + Future takePicture() async { + final XFile _file = await CameraPlatform.instance.takePicture(_cameraId); + if (!await launch('file:${_file.path}')) { + throw 'Could not open file: "${_file.path}"'; + } + } + + Future recordTimed(int seconds) async { + if (_initialized && _cameraId > 0 && !_recordingTimed) { + CameraPlatform.instance + .onVideoRecordedEvent(_cameraId) + .first + .then((VideoRecordedEvent event) async { + if (mounted) { + setState(() { + _recordingTimed = false; + }); + if (!await launch('file:${event.file.path}')) { + throw 'Could not open file: "${event.file.path}"'; + } + } + }); + + await CameraPlatform.instance.startVideoRecording( + _cameraId, + maxVideoDuration: Duration(seconds: seconds), + ); + + setState(() { + _recordingTimed = true; + }); + } + } + + Future toggleRecord() async { + if (_initialized && _cameraId > 0) { + if (!_recording) { + await CameraPlatform.instance.startVideoRecording(_cameraId); + } else { + final XFile _file = + await CameraPlatform.instance.stopVideoRecording(_cameraId); + + if (!await launch('file:${_file.path}')) { + throw 'Could not open file: "${_file.path}"'; + } + } + setState(() { + _recording = !_recording; + }); + } + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: ListView( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 5, + horizontal: 10, + ), + child: Text(_cameraInfo), + ), + if (_cameras.isNotEmpty) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: _initialized + ? disposeCurrentCamera + : initializeFirstCamera, + child: + Text(_initialized ? 'Dispose camera' : 'Create camera'), + ), + const SizedBox(width: 5), + ElevatedButton( + onPressed: _initialized ? takePicture : null, + child: const Text('Take picture'), + ), + const SizedBox(width: 5), + ElevatedButton( + onPressed: (_initialized && !_recordingTimed) + ? toggleRecord + : null, + child: Text( + _recording ? 'Stop recording' : 'Record Video', + ), + ), + const SizedBox(width: 5), + ElevatedButton( + onPressed: (_initialized && !_recording && !_recordingTimed) + ? () => recordTimed(5) + : null, + child: const Text( + 'Record 5 seconds', + ), + ), + const SizedBox(width: 20), + const Text( + 'Audio:', + ), + Switch( + value: _recordAudio, + onChanged: !_initialized + ? (bool state) => setState(() { + _recordAudio = state; + }) + : null, + ), + ], + ), + const SizedBox(height: 5), + if (_initialized && _cameraId > 0 && _previewSize != null) + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + child: Align( + alignment: Alignment.center, + child: Container( + constraints: const BoxConstraints( + maxHeight: 500, + ), + child: AspectRatio( + aspectRatio: _previewSize!.width / _previewSize!.height, + child: buildPreview(), + ), + ), + ), + ), + if (_previewSize != null) + Center( + child: Text( + 'Preview size: ${_previewSize!.width.toStringAsFixed(0)}x${_previewSize!.height.toStringAsFixed(0)}', + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml new file mode 100644 index 000000000000..b634ca42ffb5 --- /dev/null +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -0,0 +1,31 @@ +name: camera_windows_example +description: Demonstrates how to use the camera_windows plugin. +publish_to: 'none' + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.5.3" + +dependencies: + flutter: + sdk: flutter + + camera_windows: + # When depending on this package from a real application you should use: + # camera_windows: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + camera_platform_interface: ^2.1.1 + url_launcher: ^6.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + pedantic: ^1.10.0 + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/packages/camera/camera_windows/example/windows/.gitignore b/packages/camera/camera_windows/example/windows/.gitignore new file mode 100644 index 000000000000..d492d0d98c8f --- /dev/null +++ b/packages/camera/camera_windows/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/camera/camera_windows/example/windows/CMakeLists.txt b/packages/camera/camera_windows/example/windows/CMakeLists.txt new file mode 100644 index 000000000000..13229f1340a0 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/CMakeLists.txt @@ -0,0 +1,100 @@ +cmake_minimum_required(VERSION 3.15) +project(camera_windows_example LANGUAGES CXX) + +set(BINARY_NAME "camera_windows_example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() + +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Enable the test target. +set(include_camera_windows_tests TRUE) +# Provide an alias for the test target using the name expected by repo tooling. +add_custom_target(unit_tests DEPENDS camera_windows_test) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt b/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000000..b02c5485c957 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.15) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/camera/camera_windows/example/windows/flutter/generated_plugin_registrant.h b/packages/camera/camera_windows/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000000..dc139d85a931 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake b/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..67b11087b9f1 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,17 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + camera_windows + url_launcher_windows +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/camera/camera_windows/example/windows/runner/CMakeLists.txt b/packages/camera/camera_windows/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000000..0551532991f0 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.15) +project(runner LANGUAGES CXX) + +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +apply_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/camera/camera_windows/example/windows/runner/Runner.rc b/packages/camera/camera_windows/example/windows/runner/Runner.rc new file mode 100644 index 000000000000..f1cfa4391ebd --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "Demonstrates how to use the camera_windows plugin." "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "camera_windows_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "camera_windows_example.exe" "\0" + VALUE "ProductName", "camera_windows_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/camera/camera_windows/example/windows/runner/flutter_window.cpp b/packages/camera/camera_windows/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000000..b43b9095ea3a --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/camera/camera_windows/example/windows/runner/flutter_window.h b/packages/camera/camera_windows/example/windows/runner/flutter_window.h new file mode 100644 index 000000000000..6da0652f05f2 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/camera/camera_windows/example/windows/runner/main.cpp b/packages/camera/camera_windows/example/windows/runner/main.cpp new file mode 100644 index 000000000000..b7a7d4016155 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"camera_windows_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/camera/camera_windows/example/windows/runner/resource.h b/packages/camera/camera_windows/example/windows/runner/resource.h new file mode 100644 index 000000000000..66a65d1e4a79 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/camera/camera_windows/example/windows/runner/resources/app_icon.ico b/packages/camera/camera_windows/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 zcmeHQc|26z|35SKE&G-*mXah&B~fFkXr)DEO&hIfqby^T&>|8^_Ub8Vp#`BLl3lbZ zvPO!8k!2X>cg~Elr=IVxo~J*a`+9wR=A83c-k-DFd(XM&UI1VKCqM@V;DDtJ09WB} zRaHKiW(GT00brH|0EeTeKVbpbGZg?nK6-j827q-+NFM34gXjqWxJ*a#{b_apGN<-L_m3#8Z26atkEn& ze87Bvv^6vVmM+p+cQ~{u%=NJF>#(d;8{7Q{^rWKWNtf14H}>#&y7$lqmY6xmZryI& z($uy?c5-+cPnt2%)R&(KIWEXww>Cnz{OUpT>W$CbO$h1= z#4BPMkFG1Y)x}Ui+WXr?Z!w!t_hjRq8qTaWpu}FH{MsHlU{>;08goVLm{V<&`itk~ zE_Ys=D(hjiy+5=?=$HGii=Y5)jMe9|wWoD_K07(}edAxh`~LBorOJ!Cf@f{_gNCC| z%{*04ViE!#>@hc1t5bb+NO>ncf@@Dv01K!NxH$3Eg1%)|wLyMDF8^d44lV!_Sr}iEWefOaL z8f?ud3Q%Sen39u|%00W<#!E=-RpGa+H8}{ulxVl4mwpjaU+%2pzmi{3HM)%8vb*~-M9rPUAfGCSos8GUXp02|o~0BTV2l#`>>aFV&_P$ejS;nGwSVP8 zMbOaG7<7eKD>c12VdGH;?2@q7535sa7MN*L@&!m?L`ASG%boY7(&L5imY#EQ$KrBB z4@_tfP5m50(T--qv1BJcD&aiH#b-QC>8#7Fx@3yXlonJI#aEIi=8&ChiVpc#N=5le zM*?rDIdcpawoc5kizv$GEjnveyrp3sY>+5_R5;>`>erS%JolimF=A^EIsAK zsPoVyyUHCgf0aYr&alx`<)eb6Be$m&`JYSuBu=p8j%QlNNp$-5C{b4#RubPb|CAIS zGE=9OFLP7?Hgc{?k45)84biT0k&-C6C%Q}aI~q<(7BL`C#<6HyxaR%!dFx7*o^laG z=!GBF^cwK$IA(sn9y6>60Rw{mYRYkp%$jH z*xQM~+bp)G$_RhtFPYx2HTsWk80+p(uqv9@I9)y{b$7NK53rYL$ezbmRjdXS?V}fj zWxX_feWoLFNm3MG7pMUuFPs$qrQWO9!l2B(SIuy2}S|lHNbHzoE+M2|Zxhjq9+Ws8c{*}x^VAib7SbxJ*Q3EnY5lgI9 z=U^f3IW6T=TWaVj+2N%K3<%Un;CF(wUp`TC&Y|ZjyFu6co^uqDDB#EP?DV5v_dw~E zIRK*BoY9y-G_ToU2V_XCX4nJ32~`czdjT!zwme zGgJ0nOk3U4@IE5JwtM}pwimLjk{ln^*4HMU%Fl4~n(cnsLB}Ja-jUM>xIB%aY;Nq8 z)Fp8dv1tkqKanv<68o@cN|%thj$+f;zGSO7H#b+eMAV8xH$hLggtt?O?;oYEgbq@= zV(u9bbd12^%;?nyk6&$GPI%|+<_mEpJGNfl*`!KV;VfmZWw{n{rnZ51?}FDh8we_L z8OI9nE31skDqJ5Oa_ybn7|5@ui>aC`s34p4ZEu6-s!%{uU45$Zd1=p$^^dZBh zu<*pDDPLW+c>iWO$&Z_*{VSQKg7=YEpS3PssPn1U!lSm6eZIho*{@&20e4Y_lRklKDTUCKI%o4Pc<|G^Xgu$J^Q|B87U;`c1zGwf^-zH*VQ^x+i^OUWE0yd z;{FJq)2w!%`x7yg@>uGFFf-XJl4H`YtUG%0slGKOlXV`q?RP>AEWg#x!b{0RicxGhS!3$p7 zij;{gm!_u@D4$Ox%>>bPtLJ> zwKtYz?T_DR1jN>DkkfGU^<#6sGz|~p*I{y`aZ>^Di#TC|Z!7j_O1=Wo8thuit?WxR zh9_S>kw^{V^|g}HRUF=dcq>?q(pHxw!8rx4dC6vbQVmIhmICF#zU!HkHpQ>9S%Uo( zMw{eC+`&pb=GZRou|3;Po1}m46H6NGd$t<2mQh}kaK-WFfmj_66_17BX0|j-E2fe3Jat}ijpc53 zJV$$;PC<5aW`{*^Z6e5##^`Ed#a0nwJDT#Qq~^e8^JTA=z^Kl>La|(UQ!bI@#ge{Dzz@61p-I)kc2?ZxFt^QQ}f%ldLjO*GPj(5)V9IyuUakJX=~GnTgZ4$5!3E=V#t`yOG4U z(gphZB6u2zsj=qNFLYShhg$}lNpO`P9xOSnO*$@@UdMYES*{jJVj|9z-}F^riksLK zbsU+4-{281P9e2UjY6tse^&a)WM1MFw;p#_dHhWI7p&U*9TR0zKdVuQed%6{otTsq z$f~S!;wg#Bd9kez=Br{m|66Wv z#g1xMup<0)H;c2ZO6su_ii&m8j&+jJz4iKnGZ&wxoQX|5a>v&_e#6WA!MB_4asTxLRGQCC5cI(em z%$ZfeqP>!*q5kU>a+BO&ln=4Jm>Ef(QE8o&RgLkk%2}4Tf}U%IFP&uS7}&|Q-)`5< z+e>;s#4cJ-z%&-^&!xsYx777Wt(wZY9(3(avmr|gRe4cD+a8&!LY`1^T?7x{E<=kdY9NYw>A;FtTvQ=Y&1M%lyZPl$ss1oY^Sl8we}n}Aob#6 zl4jERwnt9BlSoWb@3HxYgga(752Vu6Y)k4yk9u~Kw>cA5&LHcrvn1Y-HoIuFWg~}4 zEw4bR`mXZQIyOAzo)FYqg?$5W<;^+XX%Uz61{-L6@eP|lLH%|w?g=rFc;OvEW;^qh z&iYXGhVt(G-q<+_j}CTbPS_=K>RKN0&;dubh0NxJyDOHFF;<1k!{k#7b{|Qok9hac z;gHz}6>H6C6RnB`Tt#oaSrX0p-j-oRJ;_WvS-qS--P*8}V943RT6kou-G=A+7QPGQ z!ze^UGxtW3FC0$|(lY9^L!Lx^?Q8cny(rR`es5U;-xBhphF%_WNu|aO<+e9%6LuZq zt(0PoagJG<%hyuf;te}n+qIl_Ej;czWdc{LX^pS>77s9t*2b4s5dvP_!L^3cwlc)E!(!kGrg~FescVT zZCLeua3f4;d;Tk4iXzt}g}O@nlK3?_o91_~@UMIl?@77Qc$IAlLE95#Z=TES>2E%z zxUKpK{_HvGF;5%Q7n&vA?`{%8ohlYT_?(3A$cZSi)MvIJygXD}TS-3UwyUxGLGiJP znblO~G|*uA^|ac8E-w#}uBtg|s_~s&t>-g0X%zIZ@;o_wNMr_;{KDg^O=rg`fhDZu zFp(VKd1Edj%F zWHPl+)FGj%J1BO3bOHVfH^3d1F{)*PL&sRX`~(-Zy3&9UQX)Z;c51tvaI2E*E7!)q zcz|{vpK7bjxix(k&6=OEIBJC!9lTkUbgg?4-yE{9+pFS)$Ar@vrIf`D0Bnsed(Cf? zObt2CJ>BKOl>q8PyFO6w)+6Iz`LW%T5^R`U_NIW0r1dWv6OY=TVF?N=EfA(k(~7VBW(S;Tu5m4Lg8emDG-(mOSSs=M9Q&N8jc^Y4&9RqIsk(yO_P(mcCr}rCs%1MW1VBrn=0-oQN(Xj!k%iKV zb%ricBF3G4S1;+8lzg5PbZ|$Se$)I=PwiK=cDpHYdov2QO1_a-*dL4KUi|g&oh>(* zq$<`dQ^fat`+VW?m)?_KLn&mp^-@d=&7yGDt<=XwZZC=1scwxO2^RRI7n@g-1o8ps z)&+et_~)vr8aIF1VY1Qrq~Xe``KJrQSnAZ{CSq3yP;V*JC;mmCT6oRLSs7=GA?@6g zUooM}@tKtx(^|aKK8vbaHlUQqwE0}>j&~YlN3H#vKGm@u)xxS?n9XrOWUfCRa< z`20Fld2f&;gg7zpo{Adh+mqNntMc-D$N^yWZAZRI+u1T1zWHPxk{+?vcS1D>08>@6 zLhE@`gt1Y9mAK6Z4p|u(5I%EkfU7rKFSM=E4?VG9tI;a*@?6!ey{lzN5=Y-!$WFSe z&2dtO>^0@V4WRc#L&P%R(?@KfSblMS+N+?xUN$u3K4Ys%OmEh+tq}fnU}i>6YHM?< zlnL2gl~sF!j!Y4E;j3eIU-lfa`RsOL*Tt<%EFC0gPzoHfNWAfKFIKZN8}w~(Yi~=q z>=VNLO2|CjkxP}RkutxjV#4fWYR1KNrPYq5ha9Wl+u>ipsk*I(HS@iLnmGH9MFlTU zaFZ*KSR0px>o+pL7BbhB2EC1%PJ{67_ z#kY&#O4@P=OV#-79y_W>Gv2dxL*@G7%LksNSqgId9v;2xJ zrh8uR!F-eU$NMx@S*+sk=C~Dxr9Qn7TfWnTupuHKuQ$;gGiBcU>GF5sWx(~4IP3`f zWE;YFO*?jGwYh%C3X<>RKHC-DZ!*r;cIr}GLOno^3U4tFSSoJp%oHPiSa%nh=Zgn% z14+8v@ygy0>UgEN1bczD6wK45%M>psM)y^)IfG*>3ItX|TzV*0i%@>L(VN!zdKb8S?Qf7BhjNpziA zR}?={-eu>9JDcl*R=OP9B8N$IcCETXah9SUDhr{yrld{G;PnCWRsPD7!eOOFBTWUQ=LrA_~)mFf&!zJX!Oc-_=kT<}m|K52 z)M=G#;p;Rdb@~h5D{q^K;^fX-m5V}L%!wVC2iZ1uu401Ll}#rocTeK|7FAeBRhNdQ zCc2d^aQnQp=MpOmak60N$OgS}a;p(l9CL`o4r(e-nN}mQ?M&isv-P&d$!8|1D1I(3-z!wi zTgoo)*Mv`gC?~bm?S|@}I|m-E2yqPEvYybiD5azInexpK8?9q*$9Yy9-t%5jU8~ym zgZDx>!@ujQ=|HJnwp^wv-FdD{RtzO9SnyfB{mH_(c!jHL*$>0o-(h(eqe*ZwF6Lvu z{7rkk%PEqaA>o+f{H02tzZ@TWy&su?VNw43! z-X+rN`6llvpUms3ZiSt)JMeztB~>9{J8SPmYs&qohxdYFi!ra8KR$35Zp9oR)eFC4 zE;P31#3V)n`w$fZ|4X-|%MX`xZDM~gJyl2W;O$H25*=+1S#%|53>|LyH za@yh+;325%Gq3;J&a)?%7X%t@WXcWL*BaaR*7UEZad4I8iDt7^R_Fd`XeUo256;sAo2F!HcIQKk;h})QxEsPE5BcKc7WyerTchgKmrfRX z!x#H_%cL#B9TWAqkA4I$R^8{%do3Y*&(;WFmJ zU7Dih{t1<{($VtJRl9|&EB?|cJ)xse!;}>6mSO$o5XIx@V|AA8ZcoD88ZM?C*;{|f zZVmf94_l1OmaICt`2sTyG!$^UeTHx9YuUP!omj(r|7zpm5475|yXI=rR>>fteLI+| z)MoiGho0oEt=*J(;?VY0QzwCqw@cVm?d7Y!z0A@u#H?sCJ*ecvyhj& z-F77lO;SH^dmf?L>3i>?Z*U}Em4ZYV_CjgfvzYsRZ+1B!Uo6H6mbS<-FFL`ytqvb& zE7+)2ahv-~dz(Hs+f})z{*4|{)b=2!RZK;PWwOnO=hG7xG`JU5>bAvUbdYd_CjvtHBHgtGdlO+s^9ca^Bv3`t@VRX2_AD$Ckg36OcQRF zXD6QtGfHdw*hx~V(MV-;;ZZF#dJ-piEF+s27z4X1qi5$!o~xBnvf=uopcn7ftfsZc zy@(PuOk`4GL_n(H9(E2)VUjqRCk9kR?w)v@xO6Jm_Mx})&WGEl=GS0#)0FAq^J*o! zAClhvoTsNP*-b~rN{8Yym3g{01}Ep^^Omf=SKqvN?{Q*C4HNNAcrowIa^mf+3PRy! z*_G-|3i8a;+q;iP@~Of_$(vtFkB8yOyWt2*K)vAn9El>=D;A$CEx6b*XF@4y_6M+2 zpeW`RHoI_p(B{%(&jTHI->hmNmZjHUj<@;7w0mx3&koy!2$@cfX{sN19Y}euYJFn& z1?)+?HCkD0MRI$~uB2UWri})0bru_B;klFdwsLc!ne4YUE;t41JqfG# zZJq6%vbsdx!wYeE<~?>o4V`A3?lN%MnKQ`z=uUivQN^vzJ|C;sdQ37Qn?;lpzg})y z)_2~rUdH}zNwX;Tp0tJ78+&I=IwOQ-fl30R79O8@?Ub8IIA(6I`yHn%lARVL`%b8+ z4$8D-|MZZWxc_)vu6@VZN!HsI$*2NOV&uMxBNzIbRgy%ob_ zhwEH{J9r$!dEix9XM7n&c{S(h>nGm?el;gaX0@|QnzFD@bne`el^CO$yXC?BDJ|Qg z+y$GRoR`?ST1z^e*>;!IS@5Ovb7*RlN>BV_UC!7E_F;N#ky%1J{+iixp(dUJj93aK zzHNN>R-oN7>kykHClPnoPTIj7zc6KM(Pnlb(|s??)SMb)4!sMHU^-ntJwY5Big7xv zb1Ew`Xj;|D2kzGja*C$eS44(d&RMU~c_Y14V9_TLTz0J#uHlsx`S6{nhsA0dWZ#cG zJ?`fO50E>*X4TQLv#nl%3GOk*UkAgt=IY+u0LNXqeln3Z zv$~&Li`ZJOKkFuS)dJRA>)b_Da%Q~axwA_8zNK{BH{#}#m}zGcuckz}riDE-z_Ms> zR8-EqAMcfyGJCtvTpaUVQtajhUS%c@Yj}&6Zz;-M7MZzqv3kA7{SuW$oW#=0az2wQ zg-WG@Vb4|D`pl~Il54N7Hmsauc_ne-a!o5#j3WaBBh@Wuefb!QJIOn5;d)%A#s+5% zuD$H=VNux9bE-}1&bcYGZ+>1Fo;3Z@e&zX^n!?JK*adSbONm$XW9z;Q^L>9U!}Toj2WdafJ%oL#h|yWWwyAGxzfrAWdDTtaKl zK4`5tDpPg5>z$MNv=X0LZ0d6l%D{(D8oT@+w0?ce$DZ6pv>{1&Ok67Ix1 zH}3=IEhPJEhItCC8E=`T`N5(k?G=B4+xzZ?<4!~ ze~z6Wk9!CHTI(0rLJ4{JU?E-puc;xusR?>G?;4vt;q~iI9=kDL=z0Rr%O$vU`30X$ zDZRFyZ`(omOy@u|i6h;wtJlP;+}$|Ak|k2dea7n?U1*$T!sXqqOjq^NxLPMmk~&qI zYg0W?yK8T(6+Ea+$YyspKK?kP$+B`~t3^Pib_`!6xCs32!i@pqXfFV6PmBIR<-QW= zN8L{pt0Vap0x`Gzn#E@zh@H)0FfVfA_Iu4fjYZ+umO1LXIbVc$pY+E234u)ttcrl$ z>s92z4vT%n6cMb>=XT6;l0+9e(|CZG)$@C7t7Z7Ez@a)h)!hyuV&B5K%%)P5?Lk|C zZZSVzdXp{@OXSP0hoU-gF8s8Um(#xzjP2Vem zec#-^JqTa&Y#QJ>-FBxd7tf`XB6e^JPUgagB8iBSEps;92KG`!#mvVcPQ5yNC-GEG zTiHEDYfH+0O15}r^+ z#jxj=@x8iNHWALe!P3R67TwmhItn**0JwnzSV2O&KE8KcT+0hWH^OPD1pwiuyx=b@ zNf5Jh0{9X)8;~Es)$t@%(3!OnbY+`@?i{mGX7Yy}8T_*0a6g;kaFPq;*=px5EhO{Cp%1kI<0?*|h8v!6WnO3cCJRF2-CRrU3JiLJnj@6;L)!0kWYAc_}F{2P))3HmCrz zQ&N&gE70;`!6*eJ4^1IR{f6j4(-l&X!tjHxkbHA^Zhrnhr9g{exN|xrS`5Pq=#Xf& zG%P=#ra-TyVFfgW%cZo5OSIwFL9WtXAlFOa+ubmI5t*3=g#Y zF%;70p5;{ZeFL}&}yOY1N1*Q;*<(kTB!7vM$QokF)yr2FlIU@$Ph58$Bz z0J?xQG=MlS4L6jA22eS42g|9*9pX@$#*sUeM(z+t?hr@r5J&D1rx}2pW&m*_`VDCW zUYY@v-;bAO0HqoAgbbiGGC<=ryf96}3pouhy3XJrX+!!u*O_>Si38V{uJmQ&USptX zKp#l(?>%^7;2%h(q@YWS#9;a!JhKlkR#Vd)ERILlgu!Hr@jA@V;sk4BJ-H#p*4EqC zDGjC*tl=@3Oi6)Bn^QwFpul18fpkbpg0+peH$xyPBqb%`$OUhPKyWb32o7clB*9Z< zN=i~NLjavrLtwgJ01bufP+>p-jR2I95|TpmKpQL2!oV>g(4RvS2pK4*ou%m(h6r3A zX#s&`9LU1ZG&;{CkOK!4fLDTnBys`M!vuz>Q&9OZ0hGQl!~!jSDg|~s*w52opC{sB ze|Cf2luD(*G13LcOAGA!s2FjSK8&IE5#W%J25w!vM0^VyQM!t)inj&RTiJ!wXzFgz z3^IqzB7I0L$llljsGq})thBy9UOyjtFO_*hYM_sgcMk>44jeH0V1FDyELc{S1F-;A zS;T^k^~4biG&V*Irq}O;e}j$$+E_#G?HKIn05iP3j|87TkGK~SqG!-KBg5+mN(aLm z8ybhIM`%C19UX$H$KY6JgXbY$0AT%rEpHC;u`rQ$Y=rxUdsc5*Kvc8jaYaO$^)cI6){P6K0r)I6DY4Wr4&B zLQUBraey#0HV|&c4v7PVo3n$zHj99(TZO^3?Ly%C4nYvJTL9eLBLHsM3WKKD>5!B` zQ=BsR3aR6PD(Fa>327E2HAu5TM~Wusc!)>~(gM)+3~m;92Jd;FnSib=M5d6;;5{%R zb4V7DEJ0V!CP-F*oU?gkc>ksUtAYP&V4ND5J>J2^jt*vcFflQWCrB&fLdT%O59PVJ zhid#toR=FNgD!q3&r8#wEBr`!wzvQu5zX?Q>nlSJ4i@WC*CN*-xU66F^V5crWevQ9gsq$I@z1o(a=k7LL~ z7m_~`o;_Ozha1$8Q}{WBehvAlO4EL60y5}8GDrZ< zXh&F}71JbW2A~8KfEWj&UWV#4+Z4p`b{uAj4&WC zha`}X@3~+Iz^WRlOHU&KngK>#j}+_o@LdBC1H-`gT+krWX3-;!)6?{FBp~%20a}FL zFP9%Emqcwa#(`=G>BBZ0qZDQhmZKJg_g8<=bBFKWr!dyg(YkpE+|R*SGpDVU!+VlU zFC54^DLv}`qa%49T>nNiA9Q7Ips#!Xx90tCU2gvK`(F+GPcL=J^>No{)~we#o@&mUb6c$ zCc*<|NJBk-#+{j9xkQ&ujB zI~`#kN~7W!f*-}wkG~Ld!JqZ@tK}eeSnsS5J1fMFXm|`LJx&}5`@dK3W^7#Wnm+_P zBZkp&j1fa2Y=eIjJ0}gh85jt43kaIXXv?xmo@eHrka!Z|vQv12HN#+!I5E z`(fbuW>gFiJL|uXJ!vKt#z3e3HlVdboH7;e#i3(2<)Fg-I@BR!qY#eof3MFZ&*Y@l zI|KJf&ge@p2Dq09Vu$$Qxb7!}{m-iRk@!)%KL)txi3;~Z4Pb}u@GsW;ELiWeG9V51 znX#}B&4Y2E7-H=OpNE@q{%hFLxwIpBF2t{vPREa8_{linXT;#1vMRWjOzLOP$-hf( z>=?$0;~~PnkqY;~K{EM6Vo-T(0K{A0}VUGmu*hR z{tw3hvBN%N3G3Yw`X5Te+F{J`(3w1s3-+1EbnFQKcrgrX1Jqvs@ADGe%M0s$EbK$$ zK)=y=upBc6SjGYAACCcI=Y*6Fi8_jgwZlLxD26fnQfJmb8^gHRN5(TemhX@0e=vr> zg`W}6U>x6VhoA3DqsGGD9uL1DhB3!OXO=k}59TqD@(0Nb{)Ut_luTioK_>7wjc!5C zIr@w}b`Fez3)0wQfKl&bae7;PcTA7%?f2xucM0G)wt_KO!Ewx>F~;=BI0j=Fb4>pp zv}0R^xM4eti~+^+gE$6b81p(kwzuDti(-K9bc|?+pJEl@H+jSYuxZQV8rl8 zjp@M{#%qItIUFN~KcO9Hed*`$5A-2~pAo~K&<-Q+`9`$CK>rzqAI4w~$F%vs9s{~x zg4BP%Gy*@m?;D6=SRX?888Q6peF@_4Z->8wAH~Cn!R$|Hhq2cIzFYqT_+cDourHbY z0qroxJnrZ4Gh+Ay+F`_c%+KRT>y3qw{)89?=hJ@=KO=@ep)aBJ$c!JHfBMJpsP*3G za7|)VJJ8B;4?n{~ldJF7%jmb`-ftIvNd~ekoufG(`K(3=LNc;HBY& z(lp#q8XAD#cIf}k49zX_i`*fO+#!zKA&%T3j@%)R+#yag067CU%yUEe47>wzGU8^` z1EXFT^@I!{J!F8!X?S6ph8J=gUi5tl93*W>7}_uR<2N2~e}FaG?}KPyugQ=-OGEZs z!GBoyYY+H*ANn4?Z)X4l+7H%`17i5~zRlRIX?t)6_eu=g2Q`3WBhxSUeea+M-S?RL zX9oBGKn%a!H+*hx4d2(I!gsi+@SQK%<{X22M~2tMulJoa)0*+z9=-YO+;DFEm5eE1U9b^B(Z}2^9!Qk`!A$wUE z7$Ar5?NRg2&G!AZqnmE64eh^Anss3i!{}%6@Et+4rr!=}!SBF8eZ2*J3ujCWbl;3; z48H~goPSv(8X61fKKdpP!Z7$88NL^Z?j`!^*I?-P4X^pMxyWz~@$(UeAcTSDd(`vO z{~rc;9|GfMJcApU3k}22a!&)k4{CU!e_ny^Y3cO;tOvOMKEyWz!vG(Kp*;hB?d|R3`2X~=5a6#^o5@qn?J-bI8Ppip{-yG z!k|VcGsq!jF~}7DMr49Wap-s&>o=U^T0!Lcy}!(bhtYsPQy z4|EJe{12QL#=c(suQ89Mhw9<`bui%nx7Nep`C&*M3~vMEACmcRYYRGtANq$F%zh&V zc)cEVeHz*Z1N)L7k-(k3np#{GcDh2Q@ya0YHl*n7fl*ZPAsbU-a94MYYtA#&!c`xGIaV;yzsmrjfieTEtqB_WgZp2*NplHx=$O{M~2#i_vJ{ps-NgK zQsxKK_CBM2PP_je+Xft`(vYfXXgIUr{=PA=7a8`2EHk)Ym2QKIforz# tySWtj{oF3N9@_;i*Fv5S)9x^z=nlWP>jpp-9)52ZmLVA=i*%6g{{fxOO~wEK literal 0 HcmV?d00001 diff --git a/packages/camera/camera_windows/example/windows/runner/runner.exe.manifest b/packages/camera/camera_windows/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000000..c977c4a42589 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/camera/camera_windows/example/windows/runner/utils.cpp b/packages/camera/camera_windows/example/windows/runner/utils.cpp new file mode 100644 index 000000000000..d19bdbbcc322 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/camera/camera_windows/example/windows/runner/utils.h b/packages/camera/camera_windows/example/windows/runner/utils.h new file mode 100644 index 000000000000..3879d5475579 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/camera/camera_windows/example/windows/runner/win32_window.cpp b/packages/camera/camera_windows/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000000..c10f08dc7da6 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/packages/camera/camera_windows/example/windows/runner/win32_window.h b/packages/camera/camera_windows/example/windows/runner/win32_window.h new file mode 100644 index 000000000000..17ba431125b4 --- /dev/null +++ b/packages/camera/camera_windows/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart new file mode 100644 index 000000000000..61538d726476 --- /dev/null +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -0,0 +1,418 @@ +// 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:async'; +import 'dart:math'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_windows/src/utils/utils.dart'; +import 'package:cross_file/cross_file.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; +import 'package:stream_transform/stream_transform.dart'; + +/// An implementation of [CameraPlatform] that uses method channels. +class CameraWindows extends CameraPlatform { + /// The method channel used to interact with the native platform. + final MethodChannel _channel = + const MethodChannel('plugins.flutter.io/camera'); + + /// Registers the Windows implementation of CameraPlatform. + static void registerWith() { + CameraPlatform.instance = CameraWindows(); + } + + final Map _channels = {}; + + /// The controller we need to broadcast the different events coming + /// from handleMethodCall, specific to camera events. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + final StreamController cameraEventStreamController = + StreamController.broadcast(); + + Stream _cameraEvents(int cameraId) => + cameraEventStreamController.stream + .where((CameraEvent event) => event.cameraId == cameraId); + + @override + Future> availableCameras() async { + try { + final List>? cameras = await _channel + .invokeListMethod>('availableCameras'); + + if (cameras == null) { + return []; + } + + return cameras.map((Map camera) { + return CameraDescription( + name: camera['name'] as String, + lensDirection: + parseCameraLensDirection(camera['lensFacing'] as String), + sensorOrientation: camera['sensorOrientation'] as int, + ); + }).toList(); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future createCamera( + CameraDescription cameraDescription, + ResolutionPreset? resolutionPreset, { + bool enableAudio = false, + }) async { + try { + final Map? reply = await _channel + .invokeMapMethod('create', { + 'cameraName': cameraDescription.name, + 'resolutionPreset': resolutionPreset != null + ? _serializeResolutionPreset(resolutionPreset) + : null, + 'enableAudio': enableAudio, + }); + + if (reply == null) { + throw CameraException('System', 'Cannot create camera'); + } + + return reply['cameraId']! as int; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future initializeCamera( + int cameraId, { + ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, + }) async { + final int requestedCameraId = cameraId; + + //Create channel for camera events + _channels.putIfAbsent(requestedCameraId, () { + final MethodChannel channel = + MethodChannel('flutter.io/cameraPlugin/camera$requestedCameraId'); + channel.setMethodCallHandler( + (MethodCall call) => handleCameraMethodCall(call, requestedCameraId), + ); + return channel; + }); + + final Map? reply = + await _channel.invokeMapMethod( + 'initialize', + { + 'cameraId': requestedCameraId, + }, + ); + + if (reply != null && + reply.containsKey('previewWidth') && + reply.containsKey('previewHeight')) { + cameraEventStreamController.add( + CameraInitializedEvent( + requestedCameraId, + reply['previewWidth']!, + reply['previewHeight']!, + ExposureMode.auto, + false, + FocusMode.auto, + false, + ), + ); + } else { + throw CameraException( + 'INITIALIZATION_FAILED', + 'The platform "$defaultTargetPlatform" did not return valid data when reporting success. The platform should always return a valid data or report an error.', + ); + } + } + + @override + Future dispose(int cameraId) async { + if (_channels.containsKey(cameraId)) { + final MethodChannel? cameraChannel = _channels[cameraId]; + cameraChannel?.setMethodCallHandler(null); + _channels.remove(cameraId); + } + + await _channel.invokeMethod( + 'dispose', + {'cameraId': cameraId}, + ); + } + + @override + Stream onCameraInitialized(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraResolutionChanged(int cameraId) { + //Windows camera plugin does not support resolution changed events + return const Stream.empty(); + } + + @override + Stream onCameraClosing(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraError(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onVideoRecordedEvent(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onDeviceOrientationChanged() { + //Windows camera plugin does not support capture orientations + //Force device orientation to landscape (by default camera plugin uses portraitUp orientation) + return Stream.value( + DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight), + ); + } + + @override + Future lockCaptureOrientation( + int cameraId, + DeviceOrientation orientation, + ) async { + //Windows camera plugin does not support capture orientation locking + } + + @override + Future unlockCaptureOrientation(int cameraId) async { + //Windows camera plugin does not support capture orientation locking + } + + @override + Future takePicture(int cameraId) async { + final String? path = await _channel.invokeMethod( + 'takePicture', + {'cameraId': cameraId}, + ); + + if (path == null) { + throw CameraException( + 'INVALID_PATH', + 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', + ); + } + + return XFile(path); + } + + @override + Future prepareForVideoRecording() => + _channel.invokeMethod('prepareForVideoRecording'); + + @override + Future startVideoRecording( + int cameraId, { + Duration? maxVideoDuration, + }) async { + await _channel.invokeMethod( + 'startVideoRecording', + { + 'cameraId': cameraId, + 'maxVideoDuration': maxVideoDuration?.inMilliseconds, + }, + ); + } + + @override + Future stopVideoRecording(int cameraId) async { + final String? path = await _channel.invokeMethod( + 'stopVideoRecording', + {'cameraId': cameraId}, + ); + + if (path == null) { + throw CameraException( + 'INVALID_PATH', + 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', + ); + } + + return XFile(path); + } + + @override + Future pauseVideoRecording(int cameraId) async { + //Video recording cannot be paused on windows + } + + @override + Future resumeVideoRecording(int cameraId) async { + //Video recording cannot be paused on windows + } + + @override + Future setFlashMode(int cameraId, FlashMode mode) async { + //Windows camera plugin does not support setFlashMode yet + } + + @override + Future setExposureMode(int cameraId, ExposureMode mode) async { + //Windows camera plugin does not support setExposureMode yet + } + + @override + Future setExposurePoint(int cameraId, Point? point) async { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + + //Windows camera plugin does not support setExposurePoint yet + } + + @override + Future getMinExposureOffset(int cameraId) async { + //Windows camera plugin does not support getMinExposureOffset yet + return 0.0; + } + + @override + Future getMaxExposureOffset(int cameraId) async { + //Windows camera plugin does not support getMaxExposureOffset yet + return 0.0; + } + + @override + Future getExposureOffsetStepSize(int cameraId) async { + //Windows camera plugin does not support getExposureOffsetStepSize yet + return 1.0; + } + + @override + Future setExposureOffset(int cameraId, double offset) async { + //Windows camera plugin does not support setExposureOffset yet + return 0.0; + } + + @override + Future setFocusMode(int cameraId, FocusMode mode) async { + //Windows camera plugin does not support focus modes yet + } + + @override + Future setFocusPoint(int cameraId, Point? point) async { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + + //Windows camera plugin does not support focus points yet + } + + @override + Future getMaxZoomLevel(int cameraId) async { + //Windows camera plugin does not support zoom levels yet + return 1.0; + } + + @override + Future getMinZoomLevel(int cameraId) async { + //Windows camera plugin does not support zoom levels yet + return 1.0; + } + + @override + Future setZoomLevel(int cameraId, double zoom) async { + //Windows camera plugin does not support zoom levels yet + } + + @override + Future pausePreview(int cameraId) async { + await _channel.invokeMethod( + 'pausePreview', + {'cameraId': cameraId}, + ); + } + + @override + Future resumePreview(int cameraId) async { + await _channel.invokeMethod( + 'resumePreview', + {'cameraId': cameraId}, + ); + } + + @override + Widget buildPreview(int cameraId) { + return Texture(textureId: cameraId); + } + + /// Returns the resolution preset as a String. + String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { + switch (resolutionPreset) { + case ResolutionPreset.max: + return 'max'; + case ResolutionPreset.ultraHigh: + return 'ultraHigh'; + case ResolutionPreset.veryHigh: + return 'veryHigh'; + case ResolutionPreset.high: + return 'high'; + case ResolutionPreset.medium: + return 'medium'; + case ResolutionPreset.low: + return 'low'; + default: + throw ArgumentError('Unknown ResolutionPreset value'); + } + } + + /// Converts messages received from the native platform into camera events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + Future handleCameraMethodCall(MethodCall call, int cameraId) async { + switch (call.method) { + case 'camera_closing': + cameraEventStreamController.add( + CameraClosingEvent( + cameraId, + ), + ); + break; + case 'video_recorded': + //This is called if maxVideoDuration was given on record start + cameraEventStreamController.add( + VideoRecordedEvent( + cameraId, + XFile(call.arguments['path'] as String), + call.arguments['maxVideoDuration'] != null + ? Duration( + milliseconds: call.arguments['maxVideoDuration'] as int, + ) + : null, + ), + ); + break; + case 'error': + cameraEventStreamController.add( + CameraErrorEvent( + cameraId, + call.arguments['description'] as String, + ), + ); + break; + default: + throw MissingPluginException(); + } + } +} diff --git a/packages/camera/camera_windows/lib/src/utils/utils.dart b/packages/camera/camera_windows/lib/src/utils/utils.dart new file mode 100644 index 000000000000..b98e20e85a20 --- /dev/null +++ b/packages/camera/camera_windows/lib/src/utils/utils.dart @@ -0,0 +1,18 @@ +// 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 'package:camera_platform_interface/camera_platform_interface.dart'; + +/// Parses a string into a corresponding CameraLensDirection. +CameraLensDirection parseCameraLensDirection(String string) { + switch (string) { + case 'front': + return CameraLensDirection.front; + case 'back': + return CameraLensDirection.back; + case 'external': + return CameraLensDirection.external; + } + throw ArgumentError('Unknown CameraLensDirection value'); +} diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml new file mode 100644 index 000000000000..1b25fa05e8bb --- /dev/null +++ b/packages/camera/camera_windows/pubspec.yaml @@ -0,0 +1,27 @@ +name: camera_windows +description: A Flutter plugin for getting information about and controlling the camera on Windows. +version: 0.0.1 +homepage: TBD +publish_to: 'none' + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.5.3" + +flutter: + plugin: + implements: camera + platforms: + windows: + pluginClass: CameraWindows + dartPluginClass: CameraWindows + +dependencies: + camera_platform_interface: ^2.1.0 + flutter: + sdk: flutter + +dev_dependencies: + flutter_lints: ^1.0.0 + flutter_test: + sdk: flutter diff --git a/packages/camera/camera_windows/windows/.gitignore b/packages/camera/camera_windows/windows/.gitignore new file mode 100644 index 000000000000..b3eb2be169a5 --- /dev/null +++ b/packages/camera/camera_windows/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt new file mode 100644 index 000000000000..d493b7b253a0 --- /dev/null +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 3.15) +set(PROJECT_NAME "camera_windows") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "${PROJECT_NAME}_plugin") + +list(APPEND PLUGIN_SOURCES + "camera_plugin.h" + "camera_plugin.cpp" + "camera.h" + "camera.cpp" + "capture_controller.h" + "capture_controller.cpp" + "capture_controller_listener.h" + "capture_engine_listener.h" + "capture_engine_listener.cpp" + "string_utils.h" + "string_utils.cpp" + "device_info.h" + "device_info.cpp" +) + +add_library(${PLUGIN_NAME} SHARED + "camera_windows.cpp" + "include/camera_windows/camera_windows.h" + ${PLUGIN_SOURCES} +) + +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) +target_link_libraries(${PLUGIN_NAME} PRIVATE mf mfplat mfreadwrite mfuuid d3d11) + +# List of absolute paths to libraries that should be bundled with the plugin +set(camera_windows_bundled_libraries + "" + PARENT_SCOPE +) + + +# === Tests === + +if (${include_${PROJECT_NAME}_tests}) +set(TEST_RUNNER "${PROJECT_NAME}_test") +enable_testing() +# TODO(stuartmorgan): Consider using a single shared, pre-checked-in googletest +# instance rather than downloading for each plugin. This approach makes sense +# for a template, but not for a monorepo with many plugins. +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/release-1.11.0.zip +) +# Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +# Disable install commands for gtest so it doesn't end up in the bundle. +set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) + +FetchContent_MakeAvailable(googletest) + +# The plugin's C API is not very useful for unit testing, so build the sources +# directly into the test binary rather than using the DLL. +add_executable(${TEST_RUNNER} + test/mocks.h + test/camera_plugin_test.cpp + test/camera_test.cpp + ${PLUGIN_SOURCES} +) +apply_standard_settings(${TEST_RUNNER}) +target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) +target_link_libraries(${TEST_RUNNER} PRIVATE mf mfplat mfreadwrite mfuuid d3d11) +target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) + +# flutter_wrapper_plugin has link dependencies on the Flutter DLL. +add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${FLUTTER_LIBRARY}" $ +) + +include(GoogleTest) +gtest_discover_tests(${TEST_RUNNER}) +endif() diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp new file mode 100644 index 000000000000..aa65090cbc03 --- /dev/null +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -0,0 +1,266 @@ +// 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 "camera.h" + +namespace camera_windows { +using flutter::EncodableList; +using flutter::EncodableMap; +using flutter::EncodableValue; + +namespace { +// Camera channel events +const char kCameraMethodChannelBaseName[] = "flutter.io/cameraPlugin/camera"; +const char kVideoRecordedEvent[] = "video_recorded"; + +// Helper function for creating messaging channel for camera +std::unique_ptr> BuildChannelForCamera( + flutter::BinaryMessenger *messenger, int64_t camera_id) { + auto channel_name = + std::string(kCameraMethodChannelBaseName) + std::to_string(camera_id); + return std::make_unique>( + messenger, channel_name, &flutter::StandardMethodCodec::GetInstance()); +} + +} // namespace + +CameraImpl::CameraImpl(const std::string &device_id) + : device_id_(device_id), + capture_controller_(nullptr), + messenger_(nullptr), + camera_id_(-1), + Camera(device_id) {} +CameraImpl::~CameraImpl() { + capture_controller_ = nullptr; + ClearPendingResults(); +} + +void CameraImpl::InitCamera(flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger, + bool enable_audio, + ResolutionPreset resolution_preset) { + auto capture_controller_factory = + std::make_unique(); + InitCamera(std::move(capture_controller_factory), texture_registrar, + messenger, enable_audio, resolution_preset); +} + +void CameraImpl::InitCamera( + std::unique_ptr capture_controller_factory, + flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger, bool enable_audio, + ResolutionPreset resolution_preset) { + assert(!device_id_.empty()); + messenger_ = messenger; + capture_controller_ = + capture_controller_factory->CreateCaptureController(this); + capture_controller_->CreateCaptureDevice(texture_registrar, device_id_, + enable_audio, resolution_preset); +} + +// Adds pending result to the pending_results map. +// If result already exists, call result error handler +bool CameraImpl::AddPendingResult( + PendingResultType type, std::unique_ptr> result) { + assert(result); + + auto it = pending_results_.find(type); + if (it != pending_results_.end()) { + result->Error("Duplicate request", "Method handler already called"); + return false; + } + + pending_results_.insert(std::make_pair(type, std::move(result))); + return true; +} + +std::unique_ptr> CameraImpl::GetPendingResultByType( + PendingResultType type) { + if (pending_results_.empty()) { + return nullptr; + } + auto it = pending_results_.find(type); + if (it == pending_results_.end()) { + return nullptr; + } + auto result = std::move(it->second); + pending_results_.erase(it); + return result; +} + +bool CameraImpl::HasPendingResultByType(PendingResultType type) { + if (pending_results_.empty()) { + return nullptr; + } + auto it = pending_results_.find(type); + if (it == pending_results_.end()) { + return false; + } + return it->second != nullptr; +} + +void CameraImpl::ClearPendingResultByType(PendingResultType type) { + auto pending_result = GetPendingResultByType(type); + if (pending_result) { + pending_result->Error("Plugin disposed", + "Plugin disposed before request was handled"); + } +} + +void CameraImpl::ClearPendingResults() { + ClearPendingResultByType(PendingResultType::CREATE_CAMERA); + ClearPendingResultByType(PendingResultType::INITIALIZE); + ClearPendingResultByType(PendingResultType::PAUSE_PREVIEW); + ClearPendingResultByType(PendingResultType::RESUME_PREVIEW); + ClearPendingResultByType(PendingResultType::START_RECORD); + ClearPendingResultByType(PendingResultType::STOP_RECORD); + ClearPendingResultByType(PendingResultType::TAKE_PICTURE); +} + +// TODO: Create common base handler function for alll success and error cases +// below From CaptureControllerListener +void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { + // Use texture id as camera id + camera_id_ = texture_id; + auto pending_result = + GetPendingResultByType(PendingResultType::CREATE_CAMERA); + if (pending_result) { + pending_result->Success(EncodableMap( + {{EncodableValue("cameraId"), EncodableValue(texture_id)}})); + } +} + +// From CaptureControllerListener +void CameraImpl::OnCreateCaptureEngineFailed(const std::string &error) { + auto pending_result = + GetPendingResultByType(PendingResultType::CREATE_CAMERA); + if (pending_result) { + pending_result->Error("Failed to create camera", error); + } +} + +// From CaptureControllerListener +void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { + auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); + if (pending_result) { + pending_result->Success(EncodableValue(EncodableMap({ + {EncodableValue("previewWidth"), EncodableValue((float)width)}, + {EncodableValue("previewHeight"), EncodableValue((float)height)}, + }))); + } +}; + +// From CaptureControllerListener +void CameraImpl::OnStartPreviewFailed(const std::string &error) { + auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); + if (pending_result) { + pending_result->Error("Failed to initialize", error); + } +}; + +// From CaptureControllerListener +void CameraImpl::OnResumePreviewSucceeded() { + auto pending_result = + GetPendingResultByType(PendingResultType::RESUME_PREVIEW); + if (pending_result) { + pending_result->Success(); + } +} + +// From CaptureControllerListener +void CameraImpl::OnResumePreviewFailed(const std::string &error) { + auto pending_result = + GetPendingResultByType(PendingResultType::RESUME_PREVIEW); + if (pending_result) { + pending_result->Error("Failed to resume preview", error); + } +} + +// From CaptureControllerListener +void CameraImpl::OnPausePreviewSucceeded() { + auto pending_result = + GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); + if (pending_result) { + pending_result->Success(); + } +} + +// From CaptureControllerListener +void CameraImpl::OnPausePreviewFailed(const std::string &error) { + auto pending_result = + GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); + if (pending_result) { + pending_result->Error("Failed to pause preview", error); + } +} + +// From CaptureControllerListener +void CameraImpl::OnStartRecordSucceeded() { + auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); + if (pending_result) { + pending_result->Success(); + } +}; + +// From CaptureControllerListener +void CameraImpl::OnStartRecordFailed(const std::string &error) { + auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); + if (pending_result) { + pending_result->Error("Failed to start recording", error); + } +}; + +// From CaptureControllerListener +void CameraImpl::OnStopRecordSucceeded(const std::string &filepath) { + auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); + if (pending_result) { + pending_result->Success(EncodableValue(filepath)); + } +}; + +// From CaptureControllerListener +void CameraImpl::OnStopRecordFailed(const std::string &error) { + auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); + if (pending_result) { + pending_result->Error("Failed to stop recording", error); + } +}; + +// From CaptureControllerListener +void CameraImpl::OnPictureSuccess(const std::string &filepath) { + auto pending_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); + if (pending_result) { + pending_result->Success(EncodableValue(filepath)); + } +}; + +// From CaptureControllerListener +void CameraImpl::OnPictureFailed(const std::string &error) { + auto pending_take_picture_result = + GetPendingResultByType(PendingResultType::TAKE_PICTURE); + if (pending_take_picture_result) { + pending_take_picture_result->Error("Failed to take picture", error); + } +}; + +// From CaptureControllerListener +void CameraImpl::OnVideoRecordedSuccess(const std::string &filepath, + int64_t video_duration) { + if (messenger_ && camera_id_ >= 0) { + auto channel = BuildChannelForCamera(messenger_, camera_id_); + + std::unique_ptr message_data = + std::make_unique( + EncodableMap({{EncodableValue("path"), EncodableValue(filepath)}, + {EncodableValue("maxVideoDuration"), + EncodableValue(video_duration)}})); + + channel->InvokeMethod(kVideoRecordedEvent, std::move(message_data)); + } +} + +// From CaptureControllerListener +void CameraImpl::OnVideoRecordedFailed(const std::string &error){}; + +} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h new file mode 100644 index 000000000000..7010b6a85fbb --- /dev/null +++ b/packages/camera/camera_windows/windows/camera.h @@ -0,0 +1,154 @@ +// 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_CAMERA_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_H_ + +#include +#include + +#include + +#include "capture_controller.h" + +namespace camera_windows { + +using flutter::EncodableMap; +using flutter::MethodResult; + +enum PendingResultType { + CREATE_CAMERA, + INITIALIZE, + TAKE_PICTURE, + START_RECORD, + STOP_RECORD, + PAUSE_PREVIEW, + RESUME_PREVIEW, +}; + +class Camera : public CaptureControllerListener { + public: + Camera(const std::string &device_id){}; + virtual ~Camera() = default; + + // Disallow copy and move. + Camera(const Camera &) = delete; + Camera &operator=(const Camera &) = delete; + + virtual bool HasDeviceId(std::string &device_id) = 0; + virtual bool HasCameraId(int64_t camera_id) = 0; + + virtual bool AddPendingResult(PendingResultType type, + std::unique_ptr> result) = 0; + virtual bool HasPendingResultByType(PendingResultType type) = 0; + + virtual camera_windows::CaptureController *GetCaptureController() = 0; + + virtual void InitCamera(flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger, + bool enable_audio, + ResolutionPreset resolution_preset) = 0; +}; + +class CameraImpl : public Camera { + public: + CameraImpl(const std::string &device_id); + virtual ~CameraImpl(); + + // Disallow copy and move. + CameraImpl(const CameraImpl &) = delete; + CameraImpl &operator=(const CameraImpl &) = delete; + + // From CaptureControllerListener + void OnCreateCaptureEngineSucceeded(int64_t texture_id) override; + void OnCreateCaptureEngineFailed(const std::string &error) override; + void OnStartPreviewSucceeded(int32_t width, int32_t height) override; + void OnStartPreviewFailed(const std::string &error) override; + void OnPausePreviewSucceeded() override; + void OnPausePreviewFailed(const std::string &error) override; + void OnResumePreviewSucceeded() override; + void OnResumePreviewFailed(const std::string &error) override; + void OnStartRecordSucceeded() override; + void OnStartRecordFailed(const std::string &error) override; + void OnStopRecordSucceeded(const std::string &filepath) override; + void OnStopRecordFailed(const std::string &error) override; + void OnPictureSuccess(const std::string &filepath) override; + void OnPictureFailed(const std::string &error) override; + void OnVideoRecordedSuccess(const std::string &filepath, + int64_t video_duration) override; + void OnVideoRecordedFailed(const std::string &error) override; + + // From Camera + + bool HasDeviceId(std::string &device_id) override { + return device_id_ == device_id; + }; + + bool HasCameraId(int64_t camera_id) override { + return camera_id_ == camera_id; + }; + + bool AddPendingResult(PendingResultType type, + std::unique_ptr> result) override; + + bool HasPendingResultByType(PendingResultType type) override; + + camera_windows::CaptureController *GetCaptureController() override { + return capture_controller_.get(); + }; + + void InitCamera(flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger, bool enable_audio, + ResolutionPreset resolution_preset) override; + + void InitCamera( + std::unique_ptr capture_controller_factory, + flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger, bool enable_audio, + ResolutionPreset resolution_preset); + + private: + std::unique_ptr capture_controller_; + flutter::BinaryMessenger *messenger_; + int64_t camera_id_; + std::string device_id_; + + // Pending results + std::map>> pending_results_; + std::unique_ptr> GetPendingResultByType( + PendingResultType type); + void ClearPendingResultByType(PendingResultType type); + void ClearPendingResults(); +}; + +class CameraFactory { + public: + CameraFactory(){}; + virtual ~CameraFactory() = default; + + // Disallow copy and move. + CameraFactory(const CameraFactory &) = delete; + CameraFactory &operator=(const CameraFactory &) = delete; + + virtual std::unique_ptr CreateCamera( + const std::string &device_id) = 0; +}; + +class CameraFactoryImpl : public CameraFactory { + public: + CameraFactoryImpl(){}; + virtual ~CameraFactoryImpl() = default; + + // Disallow copy and move. + CameraFactoryImpl(const CameraFactoryImpl &) = delete; + CameraFactoryImpl &operator=(const CameraFactoryImpl &) = delete; + + std::unique_ptr CreateCamera(const std::string &device_id) override { + return std::make_unique(device_id); + }; +}; + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_H_ diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp new file mode 100644 index 000000000000..7131c5d71248 --- /dev/null +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -0,0 +1,592 @@ +// 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 "camera_plugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "device_info.h" +#include "string_utils.h" + +namespace camera_windows { +using flutter::EncodableList; +using flutter::EncodableMap; +using flutter::EncodableValue; + +namespace { + +// Channel events +const char kChannelName[] = "plugins.flutter.io/camera"; + +const char kAvailableCamerasMethod[] = "availableCameras"; +const char kCreateMethod[] = "create"; +const char kInitializeMethod[] = "initialize"; +const char kTakePictureMethod[] = "takePicture"; +const char kStartVideoRecordingMethod[] = "startVideoRecording"; +const char kStopVideoRecordingMethod[] = "stopVideoRecording"; +const char kPausePreview[] = "pausePreview"; +const char kResumePreview[] = "resumePreview"; +const char kDisposeMethod[] = "dispose"; + +const char kCameraNameKey[] = "cameraName"; +const char kResolutionPresetKey[] = "resolutionPreset"; +const char kEnableAudioKey[] = "enableAudio"; + +const char kCameraIdKey[] = "cameraId"; +const char kMaxVideoDurationKey[] = "maxVideoDuration"; + +const char kResolutionPresetValueLow[] = "low"; +const char kResolutionPresetValueMedium[] = "medium"; +const char kResolutionPresetValueHigh[] = "high"; +const char kResolutionPresetValueVeryHigh[] = "veryHigh"; +const char kResolutionPresetValueUltraHigh[] = "ultraHigh"; +const char kResolutionPresetValueMax[] = "max"; + +const std::string kPictureCaptureExtension = "jpeg"; +const std::string kVideoCaptureExtension = "mp4"; + +// Looks for |key| in |map|, returning the associated value if it is present, or +// a nullptr if not. +const EncodableValue *ValueOrNull(const EncodableMap &map, const char *key) { + auto it = map.find(EncodableValue(key)); + if (it == map.end()) { + return nullptr; + } + return &(it->second); +} + +// Parses resolution preset argument to enum value +ResolutionPreset ParseResolutionPreset(const std::string &resolution_preset) { + if (resolution_preset.compare(kResolutionPresetValueLow) == 0) { + return ResolutionPreset::RESOLUTION_PRESET_LOW; + } else if (resolution_preset.compare(kResolutionPresetValueMedium) == 0) { + return ResolutionPreset::RESOLUTION_PRESET_MEDIUM; + } else if (resolution_preset.compare(kResolutionPresetValueHigh) == 0) { + return ResolutionPreset::RESOLUTION_PRESET_HIGH; + } else if (resolution_preset.compare(kResolutionPresetValueVeryHigh) == 0) { + return ResolutionPreset::RESOLUTION_PRESET_VERY_HIGH; + } else if (resolution_preset.compare(kResolutionPresetValueUltraHigh) == 0) { + return ResolutionPreset::RESOLUTION_PRESET_ULTRA_HIGH; + } else if (resolution_preset.compare(kResolutionPresetValueMax) == 0) { + return ResolutionPreset::RESOLUTION_PRESET_MAX; + } + return ResolutionPreset::RESOLUTION_PRESET_AUTO; +} + +bool HasCurrentTextureId(int64_t current_camera_id, const EncodableMap &args) { + const auto *camera_id = + std::get_if(ValueOrNull(args, kCameraIdKey)); + + if (!camera_id) { + return false; + } + return current_camera_id == *camera_id; +} + +std::unique_ptr GetDeviceInfo(IMFActivate *device) { + assert(device); + auto device_info = std::make_unique(); + wchar_t *name; + UINT32 name_size; + + HRESULT hr = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, + &name, &name_size); + if (SUCCEEDED(hr)) { + wchar_t *id; + UINT32 id_size; + hr = device->GetAllocatedString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, &id_size); + + if (SUCCEEDED(hr)) { + device_info->display_name = Utf8FromUtf16(std::wstring(name, name_size)); + device_info->device_id = Utf8FromUtf16(std::wstring(id, id_size)); + } + + ::CoTaskMemFree(id); + } + + ::CoTaskMemFree(name); + return device_info; +} + +std::string GetCurrentTimeString() { + std::chrono::system_clock::duration now = + std::chrono::system_clock::now().time_since_epoch(); + + auto s = std::chrono::duration_cast(now).count(); + auto ms = std::chrono::duration_cast(now).count(); + + struct tm newtime; + localtime_s(&newtime, &s); + + std::string time_start = ""; + time_start.resize(80); + size_t len = + strftime(&time_start[0], time_start.size(), "%Y_%m%d_%H%M%S_", &newtime); + if (len > 0) { + time_start.resize(len); + } + + // Add milliseconds + return time_start + std::to_string(ms - s * 1000); +} + +bool GetFilePathForPicture(std::string &filename) { + wchar_t *known_folder_path = nullptr; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_Pictures, KF_FLAG_CREATE, nullptr, + &known_folder_path); + + if (SUCCEEDED(hr)) { + std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); + + filename = path + "\\" + "PhotoCapture_" + GetCurrentTimeString() + "." + + kPictureCaptureExtension; + } + + return SUCCEEDED(hr); +} + +bool GetFilePathForVideo(std::string &filename) { + wchar_t *known_folder_path = nullptr; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_Videos, KF_FLAG_CREATE, nullptr, + &known_folder_path); + + if (SUCCEEDED(hr)) { + std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); + + filename = path + "\\" + "VideoCapture_" + GetCurrentTimeString() + "." + + kVideoCaptureExtension; + } + + return SUCCEEDED(hr); +} +} // namespace + +// static +void CameraPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows *registrar) { + auto channel = std::make_unique>( + registrar->messenger(), kChannelName, + &flutter::StandardMethodCodec::GetInstance()); + + std::unique_ptr plugin = std::make_unique( + registrar->texture_registrar(), registrar->messenger()); + + channel->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto &call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + registrar->AddPlugin(std::move(plugin)); +} + +CameraPlugin::CameraPlugin(flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger) + : texture_registrar_(texture_registrar), + messenger_(messenger), + camera_factory_(std::make_unique()) {} + +CameraPlugin::CameraPlugin(flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger, + std::unique_ptr camera_factory) + : texture_registrar_(texture_registrar), + messenger_(messenger), + camera_factory_(std::move(camera_factory)) {} + +CameraPlugin::~CameraPlugin() {} + +void CameraPlugin::HandleMethodCall( + const flutter::MethodCall<> &method_call, + std::unique_ptr> result) { + const std::string &method_name = method_call.method_name(); + + if (method_name.compare(kAvailableCamerasMethod) == 0) { + return AvailableCamerasMethodHandler(std::move(result)); + } else if (method_name.compare(kCreateMethod) == 0) { + const auto *arguments = + std::get_if(method_call.arguments()); + assert(arguments); + + return CreateMethodHandler(*arguments, std::move(result)); + } else if (method_name.compare(kInitializeMethod) == 0) { + const auto *arguments = + std::get_if(method_call.arguments()); + assert(arguments); + + return this->InitializeMethodHandler(*arguments, std::move(result)); + } else if (method_name.compare(kTakePictureMethod) == 0) { + const auto *arguments = + std::get_if(method_call.arguments()); + assert(arguments); + + return TakePictureMethodHandler(*arguments, std::move(result)); + } else if (method_name.compare(kStartVideoRecordingMethod) == 0) { + const auto *arguments = + std::get_if(method_call.arguments()); + assert(arguments); + + return StartVideoRecordingMethodHandler(*arguments, std::move(result)); + } else if (method_name.compare(kStopVideoRecordingMethod) == 0) { + const auto *arguments = + std::get_if(method_call.arguments()); + assert(arguments); + + return StopVideoRecordingMethodHandler(*arguments, std::move(result)); + } else if (method_name.compare(kPausePreview) == 0) { + const auto *arguments = + std::get_if(method_call.arguments()); + assert(arguments); + + return PausePreviewMethodHandler(*arguments, std::move(result)); + } else if (method_name.compare(kResumePreview) == 0) { + const auto *arguments = + std::get_if(method_call.arguments()); + assert(arguments); + + return ResumePreviewMethodHandler(*arguments, std::move(result)); + } else if (method_name.compare(kDisposeMethod) == 0) { + const auto *arguments = + std::get_if(method_call.arguments()); + assert(arguments); + + return DisposeMethodHandler(*arguments, std::move(result)); + } else { + result->NotImplemented(); + } +} + +void CameraPlugin::AvailableCamerasMethodHandler( + std::unique_ptr> result) { + // Enumerate devices. + IMFActivate **devices; + UINT32 count = 0; + if (!this->EnumerateVideoCaptureDeviceSources(&devices, &count)) { + result->Error("System error", "Failed to get available cameras"); + // No need to free devices here, cos allocation failed + return; + } + + if (count == 0) { + result->Success(EncodableValue(EncodableList())); + CoTaskMemFree(devices); + return; + } + + // Format found devices to the response + EncodableList devices_list; + for (UINT32 i = 0; i < count; ++i) { + auto device_info = GetDeviceInfo(devices[i]); + auto deviceName = GetUniqueDeviceName(std::move(device_info)); + + // TODO: get lens facing info and sensor orientation from devices + devices_list.push_back(EncodableMap({ + {EncodableValue("name"), EncodableValue(deviceName)}, + {EncodableValue("lensFacing"), EncodableValue("front")}, + {EncodableValue("sensorOrientation"), EncodableValue(0)}, + })); + } + + CoTaskMemFree(devices); + result->Success(std::move(EncodableValue(devices_list))); +} + +bool CameraPlugin::EnumerateVideoCaptureDeviceSources(IMFActivate ***devices, + UINT32 *count) { + return CaptureControllerImpl::EnumerateVideoCaptureDeviceSources(devices, + count); +} + +// Loops through cameras and returns camera with matching device_id or nullptr +Camera *CameraPlugin::GetCameraByDeviceId(std::string &device_id) { + for (auto it = begin(cameras_); it != end(cameras_); ++it) { + if ((*it)->HasDeviceId(device_id)) { + return it->get(); + } + } + return nullptr; +} + +// Loops through cameras and returns camera with matching camera_id or nullptr +Camera *CameraPlugin::GetCameraByCameraId(int64_t camera_id) { + for (auto it = begin(cameras_); it != end(cameras_); ++it) { + if ((*it)->HasCameraId(camera_id)) { + return it->get(); + } + } + return nullptr; +} + +void CameraPlugin::DisposeCameraByCameraId(int64_t camera_id) { + for (auto it = begin(cameras_); it != end(cameras_); ++it) { + if ((*it)->HasCameraId(camera_id)) { + cameras_.erase(it); + return; + } + } +} + +// Creates and initializes capture controller +// and MFCaptureEngine for requested device +void CameraPlugin::CreateMethodHandler( + const EncodableMap &args, std::unique_ptr> result) { + // Parse enableAudio argument + const auto *enable_audio = + std::get_if(ValueOrNull(args, kEnableAudioKey)); + if (!enable_audio) { + return result->Error("Argument error", + std::string(kEnableAudioKey) + " argument missing"); + } + + // Parse cameraName argument + const auto *camera_name = + std::get_if(ValueOrNull(args, kCameraNameKey)); + if (!camera_name) { + return result->Error("Argument error", + std::string(kCameraNameKey) + " argument missing"); + } + auto device_info = ParseDeviceInfoFromCameraName(*camera_name); + + if (!device_info) { + return result->Error( + "Camera error", "Cannot parse argument " + std::string(kCameraNameKey)); + } + + if (GetCameraByDeviceId(device_info->device_id)) { + return result->Error( + "Camera error", + "Camera with given " + std::string(*camera_name) + " already exists"); + } + + std::unique_ptr camera = + camera_factory_->CreateCamera(device_info->device_id); + + if (camera->HasPendingResultByType(PendingResultType::CREATE_CAMERA)) { + // This should never happen + return result->Error("Failed to create camera", + "Pending camera creation already exists"); + } + + if (camera->AddPendingResult(PendingResultType::CREATE_CAMERA, + std::move(result))) { + // Parse resolutionPreset argument + const auto *resolution_preset_argument = + std::get_if(ValueOrNull(args, kResolutionPresetKey)); + ResolutionPreset resolution_preset; + if (resolution_preset_argument) { + resolution_preset = ParseResolutionPreset(*resolution_preset_argument); + } else { + resolution_preset = ResolutionPreset::RESOLUTION_PRESET_AUTO; + } + + camera->InitCamera(texture_registrar_, messenger_, *enable_audio, + resolution_preset); + cameras_.push_back(std::move(camera)); + } +} + +void CameraPlugin::InitializeMethodHandler( + const EncodableMap &args, std::unique_ptr> result) { + auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + if (!camera_id) { + return result->Error("Argument error", + std::string(kCameraIdKey) + " missing"); + } + + auto camera = GetCameraByCameraId(*camera_id); + if (!camera) { + return result->Error("Camera not created", "Please create camera first"); + } + + if (camera->HasPendingResultByType(PendingResultType::INITIALIZE)) { + return result->Error("Failed to initialize", + "Initialize method already called"); + } + + if (camera->AddPendingResult(PendingResultType::INITIALIZE, + std::move(result))) { + auto cc = camera->GetCaptureController(); + assert(cc); + cc->StartPreview(); + } +} + +void CameraPlugin::PausePreviewMethodHandler( + const EncodableMap &args, std::unique_ptr> result) { + auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + if (!camera_id) { + return result->Error("Argument error", + std::string(kCameraIdKey) + " missing"); + } + + auto camera = GetCameraByCameraId(*camera_id); + if (!camera) { + return result->Error("Camera not created", "Please create camera first"); + } + + if (camera->HasPendingResultByType(PendingResultType::PAUSE_PREVIEW)) { + return result->Error("Failed to initialize", + "Pause preview method already called"); + } + + if (camera->AddPendingResult(PendingResultType::PAUSE_PREVIEW, + std::move(result))) { + // Capture engine does not really have pause feature... + // so preview is stopped instead. + auto cc = camera->GetCaptureController(); + assert(cc); + cc->PausePreview(); + } +} + +void CameraPlugin::ResumePreviewMethodHandler( + const EncodableMap &args, std::unique_ptr> result) { + auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + if (!camera_id) { + return result->Error("Argument error", + std::string(kCameraIdKey) + " missing"); + } + + auto camera = GetCameraByCameraId(*camera_id); + if (!camera) { + return result->Error("Camera not created", "Please create camera first"); + } + + if (camera->HasPendingResultByType(PendingResultType::RESUME_PREVIEW)) { + return result->Error("Failed to initialize", + "Resume preview method already called"); + } + + if (camera->AddPendingResult(PendingResultType::RESUME_PREVIEW, + std::move(result))) { + // Capture engine does not really have pause feature... + // so preview is started instead + auto cc = camera->GetCaptureController(); + assert(cc); + cc->ResumePreview(); + } +} + +void CameraPlugin::StartVideoRecordingMethodHandler( + const EncodableMap &args, std::unique_ptr> result) { + auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + if (!camera_id) { + return result->Error("Argument error", + std::string(kCameraIdKey) + " missing"); + } + + auto camera = GetCameraByCameraId(*camera_id); + if (!camera) { + return result->Error("Camera not created", "Please create camera first"); + } + + if (camera->HasPendingResultByType(PendingResultType::START_RECORD)) { + return result->Error("Failed to start video recording", + "Video recording starting already"); + } + + // Get max video duration + int64_t max_video_duration_ms = -1; + auto requested_max_video_duration_ms = + std::get_if(ValueOrNull(args, kMaxVideoDurationKey)); + + if (requested_max_video_duration_ms != nullptr) { + max_video_duration_ms = *requested_max_video_duration_ms; + } + + std::string path; + if (GetFilePathForVideo(path)) { + if (camera->AddPendingResult(PendingResultType::START_RECORD, + std::move(result))) { + auto str_path = std::string(path); + auto cc = camera->GetCaptureController(); + assert(cc); + cc->StartRecord(str_path, max_video_duration_ms); + } + } else { + return result->Error("System error", + "Failed to get path for video capture"); + } +} + +void CameraPlugin::StopVideoRecordingMethodHandler( + const EncodableMap &args, std::unique_ptr> result) { + auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + if (!camera_id) { + return result->Error("Argument error", + std::string(kCameraIdKey) + " missing"); + } + + auto camera = GetCameraByCameraId(*camera_id); + if (!camera) { + return result->Error("Camera not created", "Please create camera first"); + } + + if (camera->HasPendingResultByType(PendingResultType::STOP_RECORD)) { + return result->Error("Failed to stop video recording", + "Video recording stopping already"); + } + + if (camera->AddPendingResult(PendingResultType::STOP_RECORD, + std::move(result))) { + auto cc = camera->GetCaptureController(); + assert(cc); + cc->StopRecord(); + } +} + +void CameraPlugin::TakePictureMethodHandler( + const EncodableMap &args, std::unique_ptr> result) { + auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + if (!camera_id) { + return result->Error("Argument error", + std::string(kCameraIdKey) + " missing"); + } + + auto camera = GetCameraByCameraId(*camera_id); + if (!camera) { + return result->Error("Camera not created", "Please create camera first"); + } + + if (camera->HasPendingResultByType(PendingResultType::TAKE_PICTURE)) { + return result->Error("Taking picture failed", "Picture already requested"); + } + + std::string path; + if (GetFilePathForPicture(path)) { + if (camera->AddPendingResult(PendingResultType::TAKE_PICTURE, + std::move(result))) { + auto cc = camera->GetCaptureController(); + assert(cc); + cc->TakePicture(path); + } + } else { + return result->Error("Taking picture failed", "Failed to get proper path"); + } +} + +void CameraPlugin::DisposeMethodHandler( + const EncodableMap &args, std::unique_ptr> result) { + auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + if (!camera_id) { + return result->Error("Argument error", + std::string(kCameraIdKey) + " missing"); + } + + DisposeCameraByCameraId(*camera_id); + result->Success(); +} + +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h new file mode 100644 index 000000000000..4c04229f40d5 --- /dev/null +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -0,0 +1,93 @@ +// 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_CAMERA_PLUGIN_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_PLUGIN_H_ + +#include +#include +#include +#include + +#include + +#include "camera.h" +#include "capture_controller.h" +#include "capture_controller_listener.h" + +namespace camera_windows { +using flutter::MethodResult; + +class CameraPlugin : public flutter::Plugin, + public VideoCaptureDeviceEnumerator { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); + + CameraPlugin(flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger); + + // Creates a plugin instance with the given CameraFactory instance. + // Exists for unit testing with mock implementations. + CameraPlugin(flutter::TextureRegistrar *texture_registrar, + flutter::BinaryMessenger *messenger, + std::unique_ptr camera_factory); + + virtual ~CameraPlugin(); + + // Disallow copy and move. + CameraPlugin(const CameraPlugin &) = delete; + CameraPlugin &operator=(const CameraPlugin &) = delete; + + // Called when a method is called on plugin channel; + void HandleMethodCall(const flutter::MethodCall<> &method_call, + std::unique_ptr> result); + + protected: + std::vector> cameras_; + + Camera *GetCameraByDeviceId(std::string &device_id); + Camera *GetCameraByCameraId(int64_t camera_id); + void DisposeCameraByCameraId(int64_t camera_id); + + bool EnumerateVideoCaptureDeviceSources(IMFActivate ***devices, + UINT32 *count) override; + + private: + std::unique_ptr camera_factory_; + flutter::TextureRegistrar *texture_registrar_; + flutter::BinaryMessenger *messenger_; + + // Method handlers + + void AvailableCamerasMethodHandler( + std::unique_ptr> result); + + void CreateMethodHandler(const EncodableMap &args, + std::unique_ptr> result); + + void InitializeMethodHandler(const EncodableMap &args, + std::unique_ptr> result); + + void TakePictureMethodHandler(const EncodableMap &args, + std::unique_ptr> result); + + void StartVideoRecordingMethodHandler(const EncodableMap &args, + std::unique_ptr> result); + + void StopVideoRecordingMethodHandler(const EncodableMap &args, + std::unique_ptr> result); + + void ResumePreviewMethodHandler(const EncodableMap &args, + std::unique_ptr> result); + + void PausePreviewMethodHandler(const EncodableMap &args, + std::unique_ptr> result); + + void DisposeMethodHandler(const EncodableMap &args, + std::unique_ptr> result); +}; + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_PLUGIN_H_ diff --git a/packages/camera/camera_windows/windows/camera_windows.cpp b/packages/camera/camera_windows/windows/camera_windows.cpp new file mode 100644 index 000000000000..2d6b781af59f --- /dev/null +++ b/packages/camera/camera_windows/windows/camera_windows.cpp @@ -0,0 +1,16 @@ +// 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 "include/camera_windows/camera_windows.h" + +#include + +#include "camera_plugin.h" + +void CameraWindowsRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + camera_windows::CameraPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp new file mode 100644 index 000000000000..7a7276540bc7 --- /dev/null +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -0,0 +1,1135 @@ +// 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 "capture_controller.h" + +#include + +#include + +#include "string_utils.h" + +namespace camera_windows { + +struct FlutterDesktop_Pixel { + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + BYTE a = 0; +}; + +struct MFVideoFormat_RGB32_Pixel { + BYTE b = 0; + BYTE g = 0; + BYTE r = 0; + BYTE x = 0; +}; + +CaptureControllerImpl::CaptureControllerImpl( + CaptureControllerListener *listener) + : capture_controller_listener_(listener), CaptureController(){}; + +CaptureControllerImpl::~CaptureControllerImpl() { + ResetCaptureEngineState(); + capture_controller_listener_ = nullptr; +}; + +// static +bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( + IMFActivate ***devices, UINT32 *count) { + IMFAttributes *attributes = nullptr; + + HRESULT hr = MFCreateAttributes(&attributes, 1); + + if (SUCCEEDED(hr)) { + hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + } + + if (SUCCEEDED(hr)) { + hr = MFEnumDeviceSources(attributes, devices, count); + } + + Release(&attributes); + return SUCCEEDED(hr); +} + +HRESULT BuildMediaTypeForVideoPreview(IMFMediaType *src_media_type, + IMFMediaType **preview_media_type) { + Release(preview_media_type); + IMFMediaType *new_media_type = nullptr; + + HRESULT hr = MFCreateMediaType(&new_media_type); + + // First clone everything from original media type + if (SUCCEEDED(hr)) { + hr = src_media_type->CopyAllItems(new_media_type); + } + + if (SUCCEEDED(hr)) { + // Change subtype to requested + hr = new_media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); + } + + if (SUCCEEDED(hr)) { + hr = new_media_type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + } + + if (SUCCEEDED(hr)) { + *preview_media_type = new_media_type; + (*preview_media_type)->AddRef(); + } + + Release(&new_media_type); + return hr; +} + +// Creates media type for photo capture for jpeg images +HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, + IMFMediaType **photo_media_type, + GUID image_format) { + Release(photo_media_type); + IMFMediaType *new_media_type = nullptr; + + HRESULT hr = MFCreateMediaType(&new_media_type); + + // First clone everything from original media type + if (SUCCEEDED(hr)) { + hr = src_media_type->CopyAllItems(new_media_type); + } + + if (SUCCEEDED(hr)) { + hr = new_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Image); + } + + if (SUCCEEDED(hr)) { + hr = new_media_type->SetGUID(MF_MT_SUBTYPE, image_format); + } + + if (SUCCEEDED(hr)) { + *photo_media_type = new_media_type; + (*photo_media_type)->AddRef(); + } + + Release(&new_media_type); + return hr; +} + +// Creates media type for video capture +HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, + IMFMediaType **video_record_media_type, + GUID capture_format) { + Release(video_record_media_type); + IMFMediaType *new_media_type = nullptr; + + HRESULT hr = MFCreateMediaType(&new_media_type); + + // First clone everything from original media type + if (SUCCEEDED(hr)) { + hr = src_media_type->CopyAllItems(new_media_type); + } + + if (SUCCEEDED(hr)) { + hr = new_media_type->SetGUID(MF_MT_SUBTYPE, capture_format); + } + + if (SUCCEEDED(hr)) { + *video_record_media_type = new_media_type; + (*video_record_media_type)->AddRef(); + } + + Release(&new_media_type); + return hr; +} + +// Queries interface object from collection +template +HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, + Q **ppObj) { + IUnknown *pUnk; + HRESULT hr = pCollection->GetElement(index, &pUnk); + if (SUCCEEDED(hr)) { + hr = pUnk->QueryInterface(IID_PPV_ARGS(ppObj)); + pUnk->Release(); + } + return hr; +} + +HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { + Release(audio_record_media_type); + + IMFAttributes *audio_output_attributes = nullptr; + IMFCollection *available_output_types = nullptr; + IMFMediaType *src_media_type = nullptr; + IMFMediaType *new_media_type = nullptr; + DWORD mt_count = 0; + + HRESULT hr = MFCreateAttributes(&audio_output_attributes, 1); + + if (SUCCEEDED(hr)) { + // Enumerate only low latency audio outputs + hr = audio_output_attributes->SetUINT32(MF_LOW_LATENCY, TRUE); + } + + if (SUCCEEDED(hr)) { + DWORD mft_flags = (MFT_ENUM_FLAG_ALL & (~MFT_ENUM_FLAG_FIELDOFUSE)) | + MFT_ENUM_FLAG_SORTANDFILTER; + + hr = MFTranscodeGetAudioOutputAvailableTypes(MFAudioFormat_AAC, mft_flags, + audio_output_attributes, + &available_output_types); + } + + if (SUCCEEDED(hr)) { + hr = GetCollectionObject(available_output_types, 0, &src_media_type); + } + + if (SUCCEEDED(hr)) { + hr = available_output_types->GetElementCount(&mt_count); + } + + if (mt_count == 0) { + // No sources found + hr = E_FAIL; + } + + // Create new media type to copy original media type to + if (SUCCEEDED(hr)) { + hr = MFCreateMediaType(&new_media_type); + } + + if (SUCCEEDED(hr)) { + hr = src_media_type->CopyAllItems(new_media_type); + } + + if (SUCCEEDED(hr)) { + // Point target media type to new media type + *audio_record_media_type = new_media_type; + (*audio_record_media_type)->AddRef(); + } + + Release(&audio_output_attributes); + Release(&available_output_types); + Release(&src_media_type); + Release(&new_media_type); + + return hr; +} + +// Uses first audio source to capture audio. Enumerating audio sources via +// platform interface is not supported. +HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { + this->audio_source_ = nullptr; + IMFActivate **devices = nullptr; + UINT32 count = 0; + + IMFAttributes *attributes = nullptr; + HRESULT hr = MFCreateAttributes(&attributes, 1); + + if (SUCCEEDED(hr)) { + hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID); + } + + if (SUCCEEDED(hr)) { + hr = MFEnumDeviceSources(attributes, &devices, &count); + } + + Release(&attributes); + + if (SUCCEEDED(hr) && count > 0) { + wchar_t *audio_device_id; + UINT32 audio_device_id_size; + + // Use first audio device + hr = devices[0]->GetAllocatedString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID, &audio_device_id, + &audio_device_id_size); + + if (SUCCEEDED(hr)) { + IMFAttributes *audio_capture_source_attributes = nullptr; + hr = MFCreateAttributes(&audio_capture_source_attributes, 2); + + if (SUCCEEDED(hr)) { + hr = audio_capture_source_attributes->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID); + } + + if (SUCCEEDED(hr)) { + hr = audio_capture_source_attributes->SetString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID, + audio_device_id); + } + + if (SUCCEEDED(hr)) { + hr = MFCreateDeviceSource(audio_capture_source_attributes, + &this->audio_source_); + } + Release(&audio_capture_source_attributes); + } + + ::CoTaskMemFree(audio_device_id); + } + + CoTaskMemFree(devices); + + return hr; +} + +HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( + const std::string &video_device_id) { + this->video_source_ = nullptr; + + IMFAttributes *video_capture_source_attributes = nullptr; + + HRESULT hr = MFCreateAttributes(&video_capture_source_attributes, 2); + + if (SUCCEEDED(hr)) { + hr = video_capture_source_attributes->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + } + + if (SUCCEEDED(hr)) { + hr = video_capture_source_attributes->SetString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, + Utf16FromUtf8(video_device_id).c_str()); + } + + if (SUCCEEDED(hr)) { + hr = MFCreateDeviceSource(video_capture_source_attributes, + &this->video_source_); + } + + Release(&video_capture_source_attributes); + + return hr; +} + +// Create DX11 Device and D3D Manager +// TODO: Check if DX12 device can be used with flutter: +// Separate CreateD3DManagerWithDX12Device functionality +// can be written if needed +// D3D_FEATURE_LEVEL min_feature_level = D3D_FEATURE_LEVEL_9_1; +// D3D12CreateDevice(nullptr,min_feature_level,...); +HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { + HRESULT hr = S_OK; + /* + // Captures selected feature level + D3D_FEATURE_LEVEL feature_level; + + // List of allowed feature levels + static const D3D_FEATURE_LEVEL allowed_feature_levels[] = { + D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1}; + + hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT, + allowed_feature_levels, + ARRAYSIZE(allowed_feature_levels), D3D11_SDK_VERSION, + &dx11_device_, &feature_level, nullptr ); + */ + + hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT, nullptr, 0, + D3D11_SDK_VERSION, &dx11_device_, nullptr, nullptr); + + if (SUCCEEDED(hr)) { + // Enable multithread protection + ID3D10Multithread *multi_thread; + hr = dx11_device_->QueryInterface(IID_PPV_ARGS(&multi_thread)); + if (SUCCEEDED(hr)) { + multi_thread->SetMultithreadProtected(TRUE); + } + Release(&multi_thread); + } + + if (SUCCEEDED(hr)) { + hr = MFCreateDXGIDeviceManager(&dx_device_reset_token_, + &dxgi_device_manager_); + } + + if (SUCCEEDED(hr)) { + hr = + dxgi_device_manager_->ResetDevice(dx11_device_, dx_device_reset_token_); + } + + return hr; +} + +HRESULT CaptureControllerImpl::CreateCaptureEngine( + const std::string &video_device_id) { + HRESULT hr = S_OK; + IMFAttributes *attributes = nullptr; + IMFCaptureEngineClassFactory *capture_engine_factory = nullptr; + + if (!capture_engine_callback_handler_) { + capture_engine_callback_handler_ = new CaptureEngineListener(this); + capture_engine_callback_handler_->AddRef(); + } + + if (SUCCEEDED(hr)) { + hr = CreateD3DManagerWithDX11Device(); + } + + if (SUCCEEDED(hr)) { + hr = MFCreateAttributes(&attributes, 2); + } + + if (SUCCEEDED(hr)) { + hr = attributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, + dxgi_device_manager_); + } + + if (SUCCEEDED(hr)) { + hr = attributes->SetUINT32(MF_CAPTURE_ENGINE_USE_VIDEO_DEVICE_ONLY, + !enable_audio_record_); + } + + if (SUCCEEDED(hr)) { + hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&capture_engine_factory)); + } + + if (SUCCEEDED(hr)) { + // Create CaptureEngine. + hr = capture_engine_factory->CreateInstance(CLSID_MFCaptureEngine, + IID_PPV_ARGS(&capture_engine_)); + } + + if (SUCCEEDED(hr)) { + hr = CreateVideoCaptureSourceForDevice(video_device_id); + } + + if (enable_audio_record_) { + if (SUCCEEDED(hr)) { + hr = CreateDefaultAudioCaptureSource(); + } + } + + if (SUCCEEDED(hr)) { + hr = capture_engine_->Initialize(capture_engine_callback_handler_, + attributes, audio_source_, video_source_); + } + + return hr; +} + +void CaptureControllerImpl::ResetCaptureEngineState() { + initialized_ = false; + if (previewing_) { + StopPreview(); + } + + if (recording_type_ == RecordingType::RECORDING_TYPE_CONTINUOUS) { + StopRecord(); + } else if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { + StopTimedRecord(); + } + + // States + capture_engine_initialization_pending_ = false; + preview_pending_ = false; + previewing_ = false; + record_start_pending_ = false; + record_stop_pending_ = false; + recording_ = false; + pending_image_capture_ = false; + max_video_duration_ms_ = -1; + recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; + record_start_timestamp_us_ = -1; + recording_duration_us_ = 0; + max_video_duration_ms_ = -1; + + // Preview + Release(&preview_sink_); + preview_frame_width_ = 0; + preview_frame_height_ = 0; + + // Photo / Record + Release(&photo_sink_); + Release(&record_sink_); + capture_frame_width_ = 0; + capture_frame_height_ = 0; + + // CaptureEngine + Release(&capture_engine_callback_handler_); + Release(&capture_engine_); + + Release(&audio_source_); + Release(&video_source_); + + Release(&base_preview_media_type); + Release(&base_capture_media_type); + + if (dxgi_device_manager_) { + dxgi_device_manager_->ResetDevice(dx11_device_, dx_device_reset_token_); + } + Release(&dxgi_device_manager_); + Release(&dx11_device_); + + // Texture + if (texture_registrar_ && texture_id_ > -1) { + texture_registrar_->UnregisterTexture(texture_id_); + } + texture_ = nullptr; +} + +uint8_t *CaptureControllerImpl::GetSourceBuffer(uint32_t current_length) { + if (this->source_buffer_data_ == nullptr || + this->source_buffer_size_ != current_length) { + // Update source buffer size + this->source_buffer_data_ = nullptr; + this->source_buffer_data_ = std::make_unique(current_length); + this->source_buffer_size_ = current_length; + } + return this->source_buffer_data_.get(); +} + +void CaptureControllerImpl::OnBufferUpdate() { + if (this->texture_registrar_ && this->texture_id_ >= 0) { + this->texture_registrar_->MarkTextureFrameAvailable(this->texture_id_); + } +} + +void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { + // Check if max_video_duration_ms is passed + if (recording_ && recording_type_ == RecordingType::RECORDING_TYPE_TIMED && + max_video_duration_ms_ > 0) { + if (record_start_timestamp_us_ < 0) { + record_start_timestamp_us_ = capture_time_us; + } + + recording_duration_us_ = (capture_time_us - record_start_timestamp_us_); + + if (!record_stop_pending_ && + recording_duration_us_ >= + (static_cast(max_video_duration_ms_) * 1000)) { + StopTimedRecord(); + } + } +} + +void CaptureControllerImpl::CreateCaptureDevice( + flutter::TextureRegistrar *texture_registrar, const std::string &device_id, + bool enable_audio, ResolutionPreset resolution_preset) { + assert(capture_controller_listener_); + + if (initialized_ && texture_id_ >= 0) { + return capture_controller_listener_->OnCreateCaptureEngineFailed( + "Capture device already initialized"); + } else if (capture_engine_initialization_pending_) { + return capture_controller_listener_->OnCreateCaptureEngineFailed( + "Capture device already initializing"); + } + + // Reset current capture engine state before creating new capture engine; + ResetCaptureEngineState(); + + capture_engine_initialization_pending_ = true; + resolution_preset_ = resolution_preset; + enable_audio_record_ = enable_audio; + texture_registrar_ = texture_registrar; + + HRESULT hr = CreateCaptureEngine(device_id); + + if (FAILED(hr)) { + capture_controller_listener_->OnCreateCaptureEngineFailed( + "Failed to create camera"); + ResetCaptureEngineState(); + return; + } +} + +const FlutterDesktopPixelBuffer * +CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, + size_t target_height) { + if (this->source_buffer_data_ && this->source_buffer_size_ > 0 && + this->preview_frame_width_ > 0 && this->preview_frame_height_ > 0) { + uint32_t pixels_total = + this->preview_frame_width_ * this->preview_frame_height_; + dest_buffer_ = std::make_unique(pixels_total * 4); + + MFVideoFormat_RGB32_Pixel *src = + (MFVideoFormat_RGB32_Pixel *)this->source_buffer_data_.get(); + FlutterDesktop_Pixel *dst = (FlutterDesktop_Pixel *)dest_buffer_.get(); + + for (uint32_t i = 0; i < pixels_total; i++) { + dst[i].r = src[i].r; + dst[i].g = src[i].g; + dst[i].b = src[i].b; + dst[i].a = 255; + } + + this->flutter_desktop_pixel_buffer_.buffer = dest_buffer_.get(); + this->flutter_desktop_pixel_buffer_.width = this->preview_frame_width_; + this->flutter_desktop_pixel_buffer_.height = this->preview_frame_height_; + return &this->flutter_desktop_pixel_buffer_; + } + return nullptr; +} + +void CaptureControllerImpl::TakePicture(const std::string filepath) { + assert(capture_controller_listener_); + + if (!initialized_) { + return capture_controller_listener_->OnPictureFailed("Not initialized"); + } + + if (pending_image_capture_) { + return capture_controller_listener_->OnPictureFailed( + "Already capturing image"); + } + + HRESULT hr = InitPhotoSink(filepath); + + if (SUCCEEDED(hr)) { + // Request new photo + pending_picture_path_ = filepath; + pending_image_capture_ = true; + hr = capture_engine_->TakePhoto(); + } + + if (FAILED(hr)) { + pending_image_capture_ = false; + pending_picture_path_ = std::string(); + return capture_controller_listener_->OnPictureFailed( + "Failed to take picture"); + } +} + +void CaptureControllerImpl::OnPicture(bool success) { + if (capture_controller_listener_) { + if (success && !pending_picture_path_.empty()) { + capture_controller_listener_->OnPictureSuccess(pending_picture_path_); + } else { + capture_controller_listener_->OnPictureFailed("Failed to take picture"); + } + } + pending_image_capture_ = false; + pending_picture_path_ = std::string(); +} + +void CaptureControllerImpl::OnCaptureEngineInitialized(bool success) { + if (capture_controller_listener_) { + // Create flutter desktop pixelbuffer texture; + texture_ = + std::make_unique(flutter::PixelBufferTexture( + [this](size_t width, + size_t height) -> const FlutterDesktopPixelBuffer * { + return this->ConvertPixelBufferForFlutter(width, height); + })); + + auto new_texture_id = texture_registrar_->RegisterTexture(texture_.get()); + + if (new_texture_id >= 0) { + texture_id_ = new_texture_id; + capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id_); + initialized_ = true; + } else { + initialized_ = false; + } + } + capture_engine_initialization_pending_ = false; +} + +void CaptureControllerImpl::OnCaptureEngineError() { + // TODO: detect error type and update state depending of error type, also send + // other than capture engine creation errors to separate error handler + if (capture_controller_listener_) { + capture_controller_listener_->OnCreateCaptureEngineFailed( + "Error while capturing device"); + } + + initialized_ = false; + capture_engine_initialization_pending_ = false; +} + +void CaptureControllerImpl::OnPreviewStarted(bool success) { + if (capture_controller_listener_) { + if (success && preview_frame_width_ > 0 && preview_frame_height_ > 0) { + capture_controller_listener_->OnStartPreviewSucceeded( + preview_frame_width_, preview_frame_height_); + } else { + capture_controller_listener_->OnStartPreviewFailed( + "Failed to start preview"); + } + } + + // update state + preview_pending_ = false; + previewing_ = success; +}; + +void CaptureControllerImpl::OnPreviewStopped(bool success) { + // update state + previewing_ = false; +}; + +void CaptureControllerImpl::OnRecordStarted(bool success) { + if (capture_controller_listener_) { + if (success) { + capture_controller_listener_->OnStartRecordSucceeded(); + } else { + capture_controller_listener_->OnStartRecordFailed( + "Failed to start recording"); + } + } + + // update state + record_start_pending_ = false; + recording_ = success; +}; + +void CaptureControllerImpl::OnRecordStopped(bool success) { + if (capture_controller_listener_) { + if (recording_type_ == RecordingType::RECORDING_TYPE_CONTINUOUS) { + if (success && !pending_record_path_.empty()) { + capture_controller_listener_->OnStopRecordSucceeded( + pending_record_path_); + } else { + capture_controller_listener_->OnStopRecordFailed( + "Failed to record video"); + } + } else if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { + if (success && !pending_record_path_.empty()) { + capture_controller_listener_->OnVideoRecordedSuccess( + pending_record_path_, (recording_duration_us_ / 1000)); + + } else { + capture_controller_listener_->OnVideoRecordedFailed( + "Failed to record video"); + } + } + } + + // update state + recording_ = false; + record_stop_pending_ = false; + recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; + pending_record_path_ = std::string(); +} + +void CaptureControllerImpl::StartRecord(const std::string &filepath, + int64_t max_video_duration_ms) { + assert(capture_controller_listener_); + if (!initialized_) { + return capture_controller_listener_->OnStartRecordFailed( + "Capture not initialized"); + } else if (recording_) { + return capture_controller_listener_->OnStartRecordFailed( + "Already recording"); + } else if (record_start_pending_) { + return capture_controller_listener_->OnStartRecordFailed( + "Start record already requested"); + } + + HRESULT hr = InitRecordSink(filepath); + + if (SUCCEEDED(hr)) { + recording_type_ = max_video_duration_ms < 0 + ? RecordingType::RECORDING_TYPE_CONTINUOUS + : RecordingType::RECORDING_TYPE_TIMED; + max_video_duration_ms_ = max_video_duration_ms; + record_start_timestamp_us_ = -1; + recording_duration_us_ = 0; + pending_record_path_ = filepath; + record_start_pending_ = true; + + // Request to start recording. + // Check MF_CAPTURE_ENGINE_RECORD_STARTED event with CaptureEngineListener + hr = capture_engine_->StartRecord(); + } + + if (FAILED(hr)) { + record_start_pending_ = false; + recording_ = false; + return capture_controller_listener_->OnStartRecordFailed( + "Failed to initialize video recording"); + } +} + +void CaptureControllerImpl::StopRecord() { + assert(capture_controller_listener_); + + if (!initialized_) { + return capture_controller_listener_->OnStopRecordFailed( + "Capture not initialized"); + } else if (!recording_ && !record_start_pending_) { + return capture_controller_listener_->OnStopRecordFailed("Not recording"); + } else if (record_stop_pending_) { + return capture_controller_listener_->OnStopRecordFailed( + "Stop already requested"); + } + + // Request to stop recording. + // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event with CaptureEngineListener + record_stop_pending_ = true; + HRESULT hr = capture_engine_->StopRecord(true, false); + + if (FAILED(hr)) { + record_stop_pending_ = false; + recording_ = false; + return capture_controller_listener_->OnStopRecordFailed( + "Failed to stop recording"); + } +} + +void CaptureControllerImpl::StopTimedRecord() { + assert(capture_controller_listener_); + if (!recording_ && record_stop_pending_ && + recording_type_ != RecordingType::RECORDING_TYPE_TIMED) { + return; + } + + // Request to stop recording. + // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event with CaptureEngineListener + record_stop_pending_ = true; + HRESULT hr = capture_engine_->StopRecord(true, false); + + if (FAILED(hr)) { + record_stop_pending_ = false; + recording_ = false; + return capture_controller_listener_->OnVideoRecordedFailed( + "Failed to record video"); + } +} + +uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { + switch (resolution_preset_) { + case RESOLUTION_PRESET_LOW: + return 240; + break; + case RESOLUTION_PRESET_MEDIUM: + return 480; + break; + case RESOLUTION_PRESET_HIGH: + return 720; + break; + case RESOLUTION_PRESET_VERY_HIGH: + return 1080; + break; + case RESOLUTION_PRESET_ULTRA_HIGH: + return 2160; + break; + case RESOLUTION_PRESET_AUTO: + default: + // no limit + return 0xffffffff; + break; + } +} + +HRESULT CaptureControllerImpl::FindBaseMediaTypes() { + if (!initialized_) { + return E_FAIL; + } + + IMFCaptureSource *source = nullptr; + HRESULT hr = capture_engine_->GetSource(&source); + + if (SUCCEEDED(hr)) { + IMFMediaType *media_type = nullptr; + uint32_t max_height = GetMaxPreviewHeight(); + + // Loop native media types + for (int i = 0;; i++) { + // Release media type if exists from previous loop; + Release(&media_type); + + if (FAILED(source->GetAvailableDeviceMediaType( + (DWORD) + MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, + i, &media_type))) { + break; + } + + uint32_t frame_width; + uint32_t frame_height; + if (SUCCEEDED(MFGetAttributeSize(media_type, MF_MT_FRAME_SIZE, + &frame_width, &frame_height))) { + // Update media type for photo and record capture + if (capture_frame_width_ < frame_width || + capture_frame_height_ < frame_height) { + // Release old base type if allocated + Release(&base_capture_media_type); + + base_capture_media_type = media_type; + base_capture_media_type->AddRef(); + + capture_frame_width_ = frame_width; + capture_frame_height_ = frame_height; + } + + // Update media type for preview + if (frame_height <= max_height && + (preview_frame_width_ < frame_width || + preview_frame_height_ < frame_height)) { + // Release old base type if allocated + Release(&base_preview_media_type); + + base_preview_media_type = media_type; + base_preview_media_type->AddRef(); + + preview_frame_width_ = frame_width; + preview_frame_height_ = frame_height; + } + } + } + Release(&media_type); + + if (base_preview_media_type && base_capture_media_type) { + hr = S_OK; + } else { + hr = E_FAIL; + } + } + + Release(&source); + return hr; +} + +HRESULT CaptureControllerImpl::InitPreviewSink() { + if (!initialized_) { + return E_FAIL; + } + + HRESULT hr = S_OK; + if (preview_sink_) { + return hr; + } + + IMFMediaType *preview_media_type = nullptr; + IMFCaptureSink *capture_sink = nullptr; + + // Get sink with preview type; + hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, + &capture_sink); + + if (SUCCEEDED(hr)) { + hr = capture_sink->QueryInterface(IID_PPV_ARGS(&preview_sink_)); + } + + if (SUCCEEDED(hr) && !base_preview_media_type) { + hr = FindBaseMediaTypes(); + } + + if (SUCCEEDED(hr)) { + hr = BuildMediaTypeForVideoPreview(base_preview_media_type, + &preview_media_type); + } + + if (SUCCEEDED(hr)) { + DWORD preview_sink_stream_index; + hr = preview_sink_->AddStream( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, + preview_media_type, nullptr, &preview_sink_stream_index); + + if (SUCCEEDED(hr)) { + hr = preview_sink_->SetSampleCallback(preview_sink_stream_index, + capture_engine_callback_handler_); + } + } + + if (FAILED(hr)) { + Release(&preview_sink_); + } + + Release(&capture_sink); + return hr; +} + +HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { + HRESULT hr = S_OK; + + if (photo_sink_) { + // If photo sink already exists, only update output filename + hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); + + if (FAILED(hr)) { + Release(&photo_sink_); + } + + return hr; + } + + IMFMediaType *photo_media_type = nullptr; + IMFCaptureSink *capture_sink = nullptr; + + // Get sink with photo type; + hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, + &capture_sink); + + if (SUCCEEDED(hr)) { + hr = capture_sink->QueryInterface(IID_PPV_ARGS(&photo_sink_)); + } + + if (SUCCEEDED(hr) && !base_capture_media_type) { + hr = FindBaseMediaTypes(); + } + + if (SUCCEEDED(hr)) { + hr = BuildMediaTypeForPhotoCapture( + base_capture_media_type, &photo_media_type, GUID_ContainerFormatJpeg); + } + + if (SUCCEEDED(hr)) { + // Remove existing streams if available + hr = photo_sink_->RemoveAllStreams(); + } + + if (SUCCEEDED(hr)) { + DWORD dwSinkStreamIndex; + hr = photo_sink_->AddStream( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, + photo_media_type, nullptr, &dwSinkStreamIndex); + } + + if (SUCCEEDED(hr)) { + hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); + } + + if (FAILED(hr)) { + Release(&photo_sink_); + } + + Release(&capture_sink); + Release(&photo_media_type); + + return hr; +} + +HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { + HRESULT hr = S_OK; + + if (record_sink_) { + // If record sink already exists, only update output filename + hr = record_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); + + if (FAILED(hr)) { + Release(&record_sink_); + } + + return hr; + } + + IMFMediaType *video_record_media_type = nullptr; + IMFCaptureSink *capture_sink = nullptr; + + // Get sink with record type; + hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, + &capture_sink); + + if (SUCCEEDED(hr)) { + hr = capture_sink->QueryInterface(IID_PPV_ARGS(&record_sink_)); + } + + if (SUCCEEDED(hr) && !base_capture_media_type) { + hr = FindBaseMediaTypes(); + } + + if (SUCCEEDED(hr)) { + // Remove existing streams if available + hr = record_sink_->RemoveAllStreams(); + } + + if (SUCCEEDED(hr)) { + hr = BuildMediaTypeForVideoCapture( + base_capture_media_type, &video_record_media_type, MFVideoFormat_H264); + } + + if (SUCCEEDED(hr)) { + DWORD video_record_sink_stream_index; + hr = record_sink_->AddStream( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, + video_record_media_type, nullptr, &video_record_sink_stream_index); + } + + IMFMediaType *audio_record_media_type = nullptr; + if (SUCCEEDED(hr) && enable_audio_record_) { + HRESULT audio_capture_hr = S_OK; + audio_capture_hr = BuildMediaTypeForAudioCapture(&audio_record_media_type); + + if (SUCCEEDED(audio_capture_hr)) { + DWORD audio_record_sink_stream_index; + hr = record_sink_->AddStream( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_AUDIO, + audio_record_media_type, nullptr, &audio_record_sink_stream_index); + } + } + + if (SUCCEEDED(hr)) { + hr = record_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); + } + + if (FAILED(hr)) { + Release(&record_sink_); + } + + Release(&capture_sink); + Release(&video_record_media_type); + Release(&audio_record_media_type); + return hr; +} + +void CaptureControllerImpl::StartPreview() { + assert(capture_controller_listener_); + + if (!initialized_ || previewing_) { + return OnPreviewStarted(false); + } + + HRESULT hr = InitPreviewSink(); + + if (SUCCEEDED(hr)) { + preview_pending_ = true; + + // Request to start preview. + // Check MF_CAPTURE_ENGINE_PREVIEW_STARTED event with CaptureEngineListener + hr = capture_engine_->StartPreview(); + } + + if (FAILED(hr)) { + return OnPreviewStarted(false); + } +} + +void CaptureControllerImpl::StopPreview() { + assert(capture_controller_listener_); + + if (!initialized_) { + return capture_controller_listener_->OnPausePreviewFailed( + "Capture not initialized"); + } else if (!previewing_ && !preview_pending_) { + return capture_controller_listener_->OnPausePreviewFailed("Not previewing"); + } + + // Request to stop preview. + // Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event with CaptureEngineListener + HRESULT hr = capture_engine_->StopPreview(); + + if (FAILED(hr)) { + capture_controller_listener_->OnPausePreviewFailed( + "Failed to stop previewing"); + }; +} + +void CaptureControllerImpl::PausePreview() { + if (!previewing_) { + preview_paused_ = false; + return capture_controller_listener_->OnPausePreviewFailed( + "Preview not started"); + } + preview_paused_ = true; + capture_controller_listener_->OnPausePreviewSucceeded(); +} + +void CaptureControllerImpl::ResumePreview() { + preview_paused_ = false; + capture_controller_listener_->OnResumePreviewSucceeded(); +} +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h new file mode 100644 index 000000000000..b71172de32f1 --- /dev/null +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -0,0 +1,260 @@ +// 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_CAPTURE_CONTROLLER_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_H_ + +#include +//#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "capture_controller_listener.h" +#include "capture_engine_listener.h" + +namespace camera_windows { + +enum ResolutionPreset { + /// AUTO + RESOLUTION_PRESET_AUTO, + + /// 240p (320x240) + RESOLUTION_PRESET_LOW, + + /// 480p (720x480) + RESOLUTION_PRESET_MEDIUM, + + /// 720p (1280x720) + RESOLUTION_PRESET_HIGH, + + /// 1080p (1920x1080) + RESOLUTION_PRESET_VERY_HIGH, + + /// 2160p (4096x2160) + RESOLUTION_PRESET_ULTRA_HIGH, + + /// The highest resolution available. + RESOLUTION_PRESET_MAX, +}; + +enum RecordingType { + RECORDING_TYPE_NOT_SET, + RECORDING_TYPE_CONTINUOUS, + RECORDING_TYPE_TIMED +}; + +template +void Release(T** ppT) { + static_assert(std::is_base_of::value, + "T must inherit from IUnknown"); + if (*ppT) { + (*ppT)->Release(); + *ppT = NULL; + } +} + +class VideoCaptureDeviceEnumerator { + protected: + virtual bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, + UINT32* count) = 0; +}; + +class CaptureController { + public: + CaptureController(){}; + virtual ~CaptureController() = default; + + // Disallow copy and move. + CaptureController(const CaptureController&) = delete; + CaptureController& operator=(const CaptureController&) = delete; + + virtual void CreateCaptureDevice(flutter::TextureRegistrar* texture_registrar, + const std::string& device_id, + bool enable_audio, + ResolutionPreset resolution_preset) = 0; + + virtual int64_t GetTextureId() = 0; + virtual uint32_t GetPreviewWidth() = 0; + virtual uint32_t GetPreviewHeight() = 0; + + // Actions + virtual void StartPreview() = 0; + virtual void StopPreview() = 0; + virtual void PausePreview() = 0; + virtual void ResumePreview() = 0; + virtual void StartRecord(const std::string& filepath, + int64_t max_video_duration_ms) = 0; + virtual void StopRecord() = 0; + virtual void TakePicture(const std::string filepath) = 0; +}; + +class CaptureControllerImpl : public CaptureController, + public CaptureEngineObserver { + public: + static bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, + UINT32* count); + + CaptureControllerImpl(CaptureControllerListener* listener); + virtual ~CaptureControllerImpl(); + + bool IsInitialized() { return initialized_; } + bool IsPreviewing() { return previewing_; } + + void CreateCaptureDevice(flutter::TextureRegistrar* texture_registrar, + const std::string& device_id, bool enable_audio, + ResolutionPreset resolution_preset) override; + int64_t GetTextureId() override { return texture_id_; } + uint32_t GetPreviewWidth() override { return preview_frame_width_; } + uint32_t GetPreviewHeight() override { return preview_frame_height_; } + + void StartPreview() override; + void StopPreview() override; + void PausePreview() override; + void ResumePreview() override; + void StartRecord(const std::string& filepath, + int64_t max_video_duration_ms) override; + void StopRecord() override; + void TakePicture(const std::string filepath) override; + + // Handlers for CaptureEngineListener events + // From CaptureEngineObserver + bool IsReadyForEvents() override { + return initialized_ || capture_engine_initialization_pending_; + }; + bool IsReadyForSample() override { + return initialized_ && previewing_ && !preview_paused_; + } + void OnCaptureEngineInitialized(bool success) override; + void OnCaptureEngineError() override; + void OnPicture(bool success) override; + void OnPreviewStarted(bool success) override; + void OnPreviewStopped(bool success) override; + void OnRecordStarted(bool success) override; + void OnRecordStopped(bool success) override; + + uint8_t* GetSourceBuffer(uint32_t current_length) override; + void OnBufferUpdate() override; + void UpdateCaptureTime(uint64_t capture_time) override; + + private: + CaptureControllerListener* capture_controller_listener_ = nullptr; + bool initialized_ = false; + bool enable_audio_record_ = false; + + ResolutionPreset resolution_preset_ = + ResolutionPreset::RESOLUTION_PRESET_MEDIUM; + + // CaptureEngine objects + bool capture_engine_initialization_pending_ = false; + IMFCaptureEngine* capture_engine_ = nullptr; + CaptureEngineListener* capture_engine_callback_handler_ = nullptr; + + IMFDXGIDeviceManager* dxgi_device_manager_ = nullptr; + ID3D11Device* dx11_device_ = nullptr; + // ID3D12Device* dx12_device_ = nullptr; + UINT dx_device_reset_token_ = 0; + + // Sources + IMFMediaSource* video_source_ = nullptr; + IMFMediaSource* audio_source_ = nullptr; + + // Texture + int64_t texture_id_ = -1; + flutter::TextureRegistrar* texture_registrar_ = nullptr; + std::unique_ptr texture_; + + // TODO: add release_callback and clear buffer after each frame + FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; + uint32_t source_buffer_size_ = 0; + std::unique_ptr source_buffer_data_ = nullptr; + std::unique_ptr dest_buffer_ = nullptr; + uint32_t bytes_per_pixel_ = 4; // MFVideoFormat_RGB32 + + // Preview + bool preview_paused_ = false; + bool preview_pending_ = false; + bool previewing_ = false; + uint32_t preview_frame_width_ = 0; + uint32_t preview_frame_height_ = 0; + IMFMediaType* base_preview_media_type = nullptr; + IMFCapturePreviewSink* preview_sink_ = nullptr; + + // Photo / Record + bool pending_image_capture_ = false; + bool record_start_pending_ = false; + bool record_stop_pending_ = false; + bool recording_ = false; + int64_t record_start_timestamp_us_ = -1; + uint64_t recording_duration_us_ = 0; + int64_t max_video_duration_ms_ = -1; + + uint32_t capture_frame_width_ = 0; + uint32_t capture_frame_height_ = 0; + IMFMediaType* base_capture_media_type = nullptr; + IMFCapturePhotoSink* photo_sink_ = nullptr; + IMFCaptureRecordSink* record_sink_ = nullptr; + std::string pending_picture_path_ = ""; + std::string pending_record_path_ = ""; + + RecordingType recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; + + void ResetCaptureEngineState(); + uint32_t GetMaxPreviewHeight(); + HRESULT CreateDefaultAudioCaptureSource(); + HRESULT CreateVideoCaptureSourceForDevice(const std::string& video_device_id); + HRESULT CreateD3DManagerWithDX11Device(); + + HRESULT CreateCaptureEngine(const std::string& video_device_id); + + HRESULT FindBaseMediaTypes(); + HRESULT InitPreviewSink(); + HRESULT InitPhotoSink(const std::string& filepath); + HRESULT InitRecordSink(const std::string& filepath); + + void StopTimedRecord(); + + const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, + size_t height); +}; + +class CaptureControllerFactory { + public: + CaptureControllerFactory(){}; + virtual ~CaptureControllerFactory() = default; + + // Disallow copy and move. + CaptureControllerFactory(const CaptureControllerFactory&) = delete; + CaptureControllerFactory& operator=(const CaptureControllerFactory&) = delete; + + virtual std::unique_ptr CreateCaptureController( + CaptureControllerListener* listener) = 0; +}; + +class CaptureControllerFactoryImpl : public CaptureControllerFactory { + public: + CaptureControllerFactoryImpl(){}; + virtual ~CaptureControllerFactoryImpl() = default; + + // Disallow copy and move. + CaptureControllerFactoryImpl(const CaptureControllerFactoryImpl&) = delete; + CaptureControllerFactoryImpl& operator=(const CaptureControllerFactoryImpl&) = + delete; + + std::unique_ptr CreateCaptureController( + CaptureControllerListener* listener) override { + return std::make_unique(listener); + }; +}; + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_H_ diff --git a/packages/camera/camera_windows/windows/capture_controller_listener.h b/packages/camera/camera_windows/windows/capture_controller_listener.h new file mode 100644 index 000000000000..98be7d47e2c9 --- /dev/null +++ b/packages/camera/camera_windows/windows/capture_controller_listener.h @@ -0,0 +1,44 @@ +// 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_CAPTURE_CONTROLLER_LISTENER_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_LISTENER_H_ + +#include + +namespace camera_windows { + +class CaptureControllerListener { + public: + virtual ~CaptureControllerListener() = default; + + virtual void OnCreateCaptureEngineSucceeded(int64_t texture_id) = 0; + virtual void OnCreateCaptureEngineFailed(const std::string& error) = 0; + + virtual void OnStartPreviewSucceeded(int32_t width, int32_t height) = 0; + virtual void OnStartPreviewFailed(const std::string& error) = 0; + + virtual void OnResumePreviewSucceeded() = 0; + virtual void OnResumePreviewFailed(const std::string& error) = 0; + + virtual void OnPausePreviewSucceeded() = 0; + virtual void OnPausePreviewFailed(const std::string& error) = 0; + + virtual void OnStartRecordSucceeded() = 0; + virtual void OnStartRecordFailed(const std::string& error) = 0; + + virtual void OnStopRecordSucceeded(const std::string& filepath) = 0; + virtual void OnStopRecordFailed(const std::string& error) = 0; + + virtual void OnPictureSuccess(const std::string& filepath) = 0; + virtual void OnPictureFailed(const std::string& error) = 0; + + virtual void OnVideoRecordedSuccess(const std::string& filepath, + int64_t video_duration) = 0; + virtual void OnVideoRecordedFailed(const std::string& error) = 0; +}; + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_LISTENER_H_ diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp new file mode 100644 index 000000000000..4d1053b0d6c8 --- /dev/null +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -0,0 +1,134 @@ + +// 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 "capture_engine_listener.h" + +#include + +namespace camera_windows { + +// Method from IUnknown +STDMETHODIMP_(ULONG) CaptureEngineListener::AddRef() { + return InterlockedIncrement(&ref_); +} + +// Method from IUnknown +STDMETHODIMP_(ULONG) +CaptureEngineListener::Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; + } + return ref; +} + +// Method from IUnknown +STDMETHODIMP_(HRESULT) +CaptureEngineListener::QueryInterface(const IID &riid, void **ppv) { + HRESULT hr = E_NOINTERFACE; + *ppv = nullptr; + + if (riid == IID_IMFCaptureEngineOnEventCallback) { + *ppv = static_cast(this); + ((IUnknown *)*ppv)->AddRef(); + hr = S_OK; + } else if (riid == IID_IMFCaptureEngineOnSampleCallback) { + *ppv = static_cast(this); + ((IUnknown *)*ppv)->AddRef(); + hr = S_OK; + } + + return hr; +} + +STDMETHODIMP CaptureEngineListener::OnEvent(IMFMediaEvent *event) { + HRESULT event_hr; + HRESULT hr = event->GetStatus(&event_hr); + + if (!observer_->IsReadyForEvents()) { + // TODO: call observer_->OnCaptureEngineError() + // with proper error message + return event_hr; + } + + if (SUCCEEDED(hr)) { + GUID extended_type_guid; + hr = event->GetExtendedType(&extended_type_guid); + if (SUCCEEDED(hr)) { + if (extended_type_guid == MF_CAPTURE_ENGINE_ERROR) { + observer_->OnCaptureEngineError(); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_INITIALIZED) { + observer_->OnCaptureEngineInitialized(SUCCEEDED(event_hr)); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) { + observer_->OnPreviewStarted(SUCCEEDED(event_hr)); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) { + observer_->OnPreviewStopped(SUCCEEDED(event_hr)); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STARTED) { + observer_->OnRecordStarted(SUCCEEDED(event_hr)); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STOPPED) { + observer_->OnRecordStopped(SUCCEEDED(event_hr)); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_PHOTO_TAKEN) { + observer_->OnPicture(SUCCEEDED(event_hr)); + } + } + } + + // TODO: pass this error directly to the handlers? + if (FAILED(event_hr)) { + std::string message = std::system_category().message(event_hr); + + printf("Got capture event error: %s\n", message.c_str()); + fflush(stdout); + } + + return event_hr; +} + +// Method from IMFCaptureEngineOnSampleCallback +HRESULT CaptureEngineListener::OnSample(IMFSample *sample) { + HRESULT hr = S_OK; + + if (this->observer_ == nullptr || !this->observer_->IsReadyForSample()) { + // No texture target available or not previewing, just return status + return hr; + } + + if (SUCCEEDED(hr) && sample) { + IMFMediaBuffer *buffer = nullptr; + hr = sample->ConvertToContiguousBuffer(&buffer); + + // Draw the frame. + if (SUCCEEDED(hr)) { + DWORD max_length = 0; + DWORD current_length = 0; + uint8_t *data; + if (SUCCEEDED(buffer->Lock(&data, &max_length, ¤t_length))) { + uint8_t *src_buffer = this->observer_->GetSourceBuffer(current_length); + if (src_buffer) { + CopyMemory(src_buffer, data, current_length); + } + } + hr = buffer->Unlock(); + if (SUCCEEDED(hr)) { + this->observer_->OnBufferUpdate(); + } + } + + LONGLONG raw_time_stamp = 0; + // Receives the presentation time, in 100-nanosecond units + sample->GetSampleTime(&raw_time_stamp); + + // Report time in microseconds + this->observer_->UpdateCaptureTime( + static_cast(raw_time_stamp / 10)); + + if (buffer) { + buffer->Release(); + buffer = nullptr; + } + } + return hr; +} +} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h new file mode 100644 index 000000000000..d7eb25378be9 --- /dev/null +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -0,0 +1,68 @@ +// 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_CAPTURE_ENGINE_LISTENER_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_ENGINE_LISTENER_H_ + +#include + +#include + +namespace camera_windows { + +class CaptureEngineObserver { + public: + virtual ~CaptureEngineObserver() = default; + + virtual bool IsReadyForEvents() = 0; + virtual bool IsReadyForSample() = 0; + + // Event functions + // TODO: Instead of separate functions for each event, + // just have OnEvent handler; + virtual void OnCaptureEngineInitialized(bool success) = 0; + virtual void OnCaptureEngineError() = 0; + virtual void OnPicture(bool success) = 0; + virtual void OnPreviewStarted(bool success) = 0; + virtual void OnPreviewStopped(bool success) = 0; + virtual void OnRecordStarted(bool success) = 0; + virtual void OnRecordStopped(bool success) = 0; + + // Sample functions + virtual uint8_t* GetSourceBuffer(uint32_t current_length) = 0; + virtual void OnBufferUpdate() = 0; + virtual void UpdateCaptureTime(uint64_t capture_time) = 0; +}; + +class CaptureEngineListener : public IMFCaptureEngineOnSampleCallback, + public IMFCaptureEngineOnEventCallback { + public: + CaptureEngineListener(CaptureEngineObserver* observer) + : ref_(1), observer_(observer) {} + + ~CaptureEngineListener(){}; + + // Disallow copy and move. + CaptureEngineListener(const CaptureEngineListener&) = delete; + CaptureEngineListener& operator=(const CaptureEngineListener&) = delete; + + // IUnknown + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv); + + // IMFCaptureEngineOnEventCallback + STDMETHODIMP OnEvent(IMFMediaEvent* pEvent); + + // IMFCaptureEngineOnSampleCallback + STDMETHODIMP_(HRESULT) OnSample(IMFSample* pSample); + + private: + CaptureEngineObserver* observer_; + volatile ULONG ref_; +}; + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_ENGINE_LISTENER_H_ diff --git a/packages/camera/camera_windows/windows/device_info.cpp b/packages/camera/camera_windows/windows/device_info.cpp new file mode 100644 index 000000000000..8c7546bbdbc4 --- /dev/null +++ b/packages/camera/camera_windows/windows/device_info.cpp @@ -0,0 +1,29 @@ +// 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 "device_info.h" + +#include +#include + +namespace camera_windows { +std::string GetUniqueDeviceName( + std::unique_ptr device_info) { + return device_info->display_name + " <" + device_info->device_id + ">"; +} + +std::unique_ptr ParseDeviceInfoFromCameraName( + const std::string &camera_name) { + size_t delimeter_index = camera_name.rfind(' ', camera_name.length()); + if (delimeter_index != std::string::npos) { + auto deviceInfo = std::make_unique(); + deviceInfo->display_name = camera_name.substr(0, delimeter_index); + deviceInfo->device_id = camera_name.substr( + delimeter_index + 2, camera_name.length() - delimeter_index - 3); + return deviceInfo; + } + + return nullptr; +} +} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/device_info.h b/packages/camera/camera_windows/windows/device_info.h new file mode 100644 index 000000000000..904c3cb35b2a --- /dev/null +++ b/packages/camera/camera_windows/windows/device_info.h @@ -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. + +#ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ + +#include +#include + +namespace camera_windows { + +struct CaptureDeviceInfo { + std::string display_name; + std::string device_id; +}; + +std::string GetUniqueDeviceName(std::unique_ptr device_info); + +std::unique_ptr ParseDeviceInfoFromCameraName( + const std::string &device_name); + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/include/camera_windows/camera_windows.h b/packages/camera/camera_windows/windows/include/camera_windows/camera_windows.h new file mode 100644 index 000000000000..b1e28b8aa8df --- /dev/null +++ b/packages/camera/camera_windows/windows/include/camera_windows/camera_windows.h @@ -0,0 +1,27 @@ +// 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_INCLUDE_CAMERA_WINDOWS_CAMERA_WINDOWS_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_INCLUDE_CAMERA_WINDOWS_CAMERA_WINDOWS_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void CameraWindowsRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_INCLUDE_CAMERA_WINDOWS_CAMERA_WINDOWS_H_ diff --git a/packages/camera/camera_windows/windows/string_utils.cpp b/packages/camera/camera_windows/windows/string_utils.cpp new file mode 100644 index 000000000000..eda147d81448 --- /dev/null +++ b/packages/camera/camera_windows/windows/string_utils.cpp @@ -0,0 +1,60 @@ +// 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 "string_utils.h" + +#include +#include + +#include + +namespace camera_windows { + +// Converts the given UTF-16 string to UTF-8. +std::string Utf8FromUtf16(const std::wstring &utf16_string) { + if (utf16_string.empty()) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), + static_cast(utf16_string.length()), nullptr, 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), + static_cast(utf16_string.length()), utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} + +// Converts the given UTF-8 string to UTF-16. +std::wstring Utf16FromUtf8(const std::string &utf8_string) { + if (utf8_string.empty()) { + return std::wstring(); + } + int target_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + static_cast(utf8_string.length()), nullptr, 0); + if (target_length == 0) { + return std::wstring(); + } + std::wstring utf16_string; + utf16_string.resize(target_length); + int converted_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + static_cast(utf8_string.length()), + utf16_string.data(), target_length); + if (converted_length == 0) { + return std::wstring(); + } + return utf16_string; +} + +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/string_utils.h b/packages/camera/camera_windows/windows/string_utils.h new file mode 100644 index 000000000000..348b0376db62 --- /dev/null +++ b/packages/camera/camera_windows/windows/string_utils.h @@ -0,0 +1,22 @@ +// 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_STRING_UTILS_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_STRING_UTILS_H_ + +#include + +#include + +namespace camera_windows { + +// Converts the given UTF-16 string to UTF-8. +std::string Utf8FromUtf16(const std::wstring &utf16_string); + +// Converts the given UTF-8 string to UTF-16. +std::wstring Utf16FromUtf8(const std::string &utf8_string); + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_STRING_UTILS_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 new file mode 100644 index 000000000000..991f3c2a8454 --- /dev/null +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -0,0 +1,1008 @@ +// 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mocks.h" + +namespace camera_windows { +namespace test { + +using flutter::EncodableMap; +using flutter::EncodableValue; +using ::testing::_; +using ::testing::DoAll; +using ::testing::EndsWith; +using ::testing::Eq; +using ::testing::Pointee; +using ::testing::Return; + +TEST(CameraPlugin, AvailableCamerasHandlerSuccessIfNoCameras) { + std::unique_ptr texture_registrar_ = + std::make_unique(); + std::unique_ptr messenger_ = + std::make_unique(); + std::unique_ptr camera_factory_ = + std::make_unique(); + std::unique_ptr result = + std::make_unique(); + + MockCameraPlugin plugin(texture_registrar_.get(), messenger_.get(), + std::move(camera_factory_)); + + EXPECT_CALL(plugin, EnumerateVideoCaptureDeviceSources) + .Times(1) + .WillOnce([](IMFActivate*** devices, UINT32* count) { + *count = 0U; + *devices = static_cast( + CoTaskMemAlloc(sizeof(IMFActivate*) * (*count))); + return true; + }); + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL(*result, SuccessInternal).Times(1); + + plugin.HandleMethodCall( + flutter::MethodCall("availableCameras", + std::make_unique()), + std::move(result)); +} + +TEST(CameraPlugin, AvailableCamerasHandlerErrorIfFailsToEnumerateDevices) { + std::unique_ptr texture_registrar_ = + std::make_unique(); + std::unique_ptr messenger_ = + std::make_unique(); + std::unique_ptr camera_factory_ = + std::make_unique(); + std::unique_ptr result = + std::make_unique(); + + MockCameraPlugin plugin(texture_registrar_.get(), messenger_.get(), + std::move(camera_factory_)); + + EXPECT_CALL(plugin, EnumerateVideoCaptureDeviceSources) + .Times(1) + .WillOnce([](IMFActivate*** devices, UINT32* count) { return false; }); + + EXPECT_CALL(*result, ErrorInternal).Times(1); + EXPECT_CALL(*result, SuccessInternal).Times(0); + + plugin.HandleMethodCall( + flutter::MethodCall("availableCameras", + std::make_unique()), + std::move(result)); +} + +TEST(CameraPlugin, CreateHandlerCallsInitCamera) { + std::unique_ptr result = + std::make_unique(); + std::unique_ptr texture_registrar_ = + std::make_unique(); + std::unique_ptr messenger_ = + std::make_unique(); + std::unique_ptr camera_factory_ = + std::make_unique(); + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + + EXPECT_CALL(*camera, + HasPendingResultByType(Eq(PendingResultType::CREATE_CAMERA))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, + AddPendingResult(Eq(PendingResultType::CREATE_CAMERA), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + EXPECT_CALL(*camera, InitCamera) + .Times(1) + .WillOnce([cam = camera.get()]( + flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, bool enable_audio, + ResolutionPreset resolution_preset) { + assert(cam->pending_result_); + return cam->pending_result_->Success(EncodableValue(1)); + }); + + // Move mocked camera to the factory to be passed + // for plugin with CreateCamera function + camera_factory_->pending_camera_ = std::move(camera); + + EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID)); + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(1)))); + + CameraPlugin plugin(texture_registrar_.get(), messenger_.get(), + std::move(camera_factory_)); + EncodableMap args = { + {EncodableValue("cameraName"), EncodableValue(MOCK_CAMERA_NAME)}, + {EncodableValue("resolutionPreset"), EncodableValue(nullptr)}, + {EncodableValue("enableAudio"), EncodableValue(true)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("create", + std::make_unique(EncodableMap(args))), + std::move(result)); +} + +TEST(CameraPlugin, CreateHandlerErrorOnInvalidDeviceId) { + std::unique_ptr result = + std::make_unique(); + std::unique_ptr texture_registrar_ = + std::make_unique(); + std::unique_ptr messenger_ = + std::make_unique(); + std::unique_ptr camera_factory_ = + std::make_unique(); + + CameraPlugin plugin(texture_registrar_.get(), messenger_.get(), + std::move(camera_factory_)); + EncodableMap args = { + {EncodableValue("cameraName"), EncodableValue(MOCK_INVALID_CAMERA_NAME)}, + {EncodableValue("resolutionPreset"), EncodableValue(nullptr)}, + {EncodableValue("enableAudio"), EncodableValue(true)}, + }; + + EXPECT_CALL(*result, ErrorInternal).Times(1); + + plugin.HandleMethodCall( + flutter::MethodCall("create", + std::make_unique(EncodableMap(args))), + std::move(result)); +} + +TEST(CameraPlugin, CreateHandlerErrorOnExistingDeviceId) { + std::unique_ptr first_create_result = + std::make_unique(); + std::unique_ptr second_create_result = + std::make_unique(); + std::unique_ptr texture_registrar_ = + std::make_unique(); + std::unique_ptr messenger_ = + std::make_unique(); + std::unique_ptr camera_factory_ = + std::make_unique(); + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + + EXPECT_CALL(*camera, + HasPendingResultByType(Eq(PendingResultType::CREATE_CAMERA))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, + AddPendingResult(Eq(PendingResultType::CREATE_CAMERA), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + EXPECT_CALL(*camera, InitCamera) + .Times(1) + .WillOnce([cam = camera.get()]( + flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, bool enable_audio, + ResolutionPreset resolution_preset) { + assert(cam->pending_result_); + return cam->pending_result_->Success(EncodableValue(1)); + }); + + EXPECT_CALL(*camera, HasDeviceId(Eq(MOCK_DEVICE_ID))) + .Times(1) + .WillOnce([cam = camera.get()](std::string& device_id) { + return cam->device_id_ == device_id; + }); + + // Move mocked camera to the factory to be passed + // for plugin with CreateCamera function + camera_factory_->pending_camera_ = std::move(camera); + + EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID)); + + EXPECT_CALL(*first_create_result, ErrorInternal).Times(0); + EXPECT_CALL(*first_create_result, + SuccessInternal(Pointee(EncodableValue(1)))); + + CameraPlugin plugin(texture_registrar_.get(), messenger_.get(), + std::move(camera_factory_)); + EncodableMap args = { + {EncodableValue("cameraName"), EncodableValue(MOCK_CAMERA_NAME)}, + {EncodableValue("resolutionPreset"), EncodableValue(nullptr)}, + {EncodableValue("enableAudio"), EncodableValue(true)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("create", + std::make_unique(EncodableMap(args))), + std::move(first_create_result)); + + EXPECT_CALL(*second_create_result, ErrorInternal).Times(1); + EXPECT_CALL(*second_create_result, SuccessInternal).Times(0); + + plugin.HandleMethodCall( + flutter::MethodCall("create", + std::make_unique(EncodableMap(args))), + std::move(second_create_result)); +} + +TEST(CameraPlugin, InitializeHandlerCallStartPreview) { + int64_t mock_camera_id = 1234; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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::INITIALIZE))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::INITIALIZE), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, StartPreview()) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->pending_result_->Success(); + }); + + 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_CALL(*initialize_result, ErrorInternal).Times(0); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("initialize", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, InitializeHandlerErrorOnInvalidCameraId) { + int64_t mock_camera_id = 1234; + int64_t missing_camera_id = 5678; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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, AddPendingResult).Times(0); + EXPECT_CALL(*camera, GetCaptureController).Times(0); + EXPECT_CALL(*capture_controller, StartPreview).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_CALL(*initialize_result, ErrorInternal).Times(1); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("initialize", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, TakePictureHandlerCallsTakePictureWithPath) { + int64_t mock_camera_id = 1234; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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::TAKE_PICTURE))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::TAKE_PICTURE), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, TakePicture(EndsWith(".jpeg"))) + .Times(1) + .WillOnce([cam = camera.get()](const std::string filepath) { + assert(cam->pending_result_); + return cam->pending_result_->Success(); + }); + + 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_CALL(*initialize_result, ErrorInternal).Times(0); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("takePicture", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, TakePictureHandlerErrorOnInvalidCameraId) { + int64_t mock_camera_id = 1234; + int64_t missing_camera_id = 5678; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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, AddPendingResult).Times(0); + EXPECT_CALL(*camera, GetCaptureController).Times(0); + EXPECT_CALL(*capture_controller, TakePicture).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_CALL(*initialize_result, ErrorInternal).Times(1); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("takePicture", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, StartVideoRecordingHandlerCallsStartRecordWithPath) { + int64_t mock_camera_id = 1234; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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::START_RECORD))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::START_RECORD), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, StartRecord(EndsWith(".mp4"), -1)) + .Times(1) + .WillOnce([cam = camera.get()](const std::string filepath, + int64_t max_video_duration_ms) { + assert(cam->pending_result_); + return cam->pending_result_->Success(); + }); + + 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_CALL(*initialize_result, ErrorInternal).Times(0); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("startVideoRecording", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, + StartVideoRecordingHandlerCallsStartRecordWithPathAndCaptureDuration) { + int64_t mock_camera_id = 1234; + int32_t mock_video_duration = 100000; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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::START_RECORD))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::START_RECORD), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, + StartRecord(EndsWith(".mp4"), Eq(mock_video_duration))) + .Times(1) + .WillOnce([cam = camera.get()](const std::string filepath, + int64_t max_video_duration_ms) { + assert(cam->pending_result_); + return cam->pending_result_->Success(); + }); + + 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_CALL(*initialize_result, ErrorInternal).Times(0); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, + {EncodableValue("maxVideoDuration"), EncodableValue(mock_video_duration)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("startVideoRecording", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, StartVideoRecordingHandlerErrorOnInvalidCameraId) { + int64_t mock_camera_id = 1234; + int64_t missing_camera_id = 5678; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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, AddPendingResult).Times(0); + EXPECT_CALL(*camera, GetCaptureController).Times(0); + EXPECT_CALL(*capture_controller, StartRecord(_, -1)).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_CALL(*initialize_result, ErrorInternal).Times(1); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("startVideoRecording", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, StopVideoRecordingHandlerCallsStopRecord) { + int64_t mock_camera_id = 1234; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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::STOP_RECORD))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::STOP_RECORD), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, StopRecord) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->pending_result_->Success(); + }); + + 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_CALL(*initialize_result, ErrorInternal).Times(0); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("stopVideoRecording", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, StopVideoRecordingHandlerErrorOnInvalidCameraId) { + int64_t mock_camera_id = 1234; + int64_t missing_camera_id = 5678; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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, AddPendingResult).Times(0); + EXPECT_CALL(*camera, GetCaptureController).Times(0); + EXPECT_CALL(*capture_controller, StopRecord).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_CALL(*initialize_result, ErrorInternal).Times(1); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("stopVideoRecording", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, ResumePreviewHandlerCallsResumePreview) { + int64_t mock_camera_id = 1234; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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::RESUME_PREVIEW))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, + AddPendingResult(Eq(PendingResultType::RESUME_PREVIEW), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, ResumePreview) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->pending_result_->Success(); + }); + + 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_CALL(*initialize_result, ErrorInternal).Times(0); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("resumePreview", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, ResumePreviewHandlerErrorOnInvalidCameraId) { + int64_t mock_camera_id = 1234; + int64_t missing_camera_id = 5678; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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, AddPendingResult).Times(0); + EXPECT_CALL(*camera, GetCaptureController).Times(0); + EXPECT_CALL(*capture_controller, ResumePreview).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_CALL(*initialize_result, ErrorInternal).Times(1); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("resumePreview", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, PausePreviewHandlerCallsPausePreview) { + int64_t mock_camera_id = 1234; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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::PAUSE_PREVIEW))) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*camera, + AddPendingResult(Eq(PendingResultType::PAUSE_PREVIEW), _)) + .Times(1) + .WillOnce([cam = camera.get()](PendingResultType type, + std::unique_ptr> result) { + cam->pending_result_ = std::move(result); + return true; + }); + + EXPECT_CALL(*camera, GetCaptureController) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->capture_controller_.get(); + }); + + EXPECT_CALL(*capture_controller, PausePreview) + .Times(1) + .WillOnce([cam = camera.get()]() { + assert(cam->pending_result_); + return cam->pending_result_->Success(); + }); + + 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_CALL(*initialize_result, ErrorInternal).Times(0); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("pausePreview", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +TEST(CameraPlugin, PausePreviewHandlerErrorOnInvalidCameraId) { + int64_t mock_camera_id = 1234; + int64_t missing_camera_id = 5678; + + std::unique_ptr initialize_result = + std::make_unique(); + + 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, AddPendingResult).Times(0); + EXPECT_CALL(*camera, GetCaptureController).Times(0); + EXPECT_CALL(*capture_controller, PausePreview).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_CALL(*initialize_result, ErrorInternal).Times(1); + EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); + + EncodableMap args = { + {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, + }; + + plugin.HandleMethodCall( + flutter::MethodCall("pausePreview", + std::make_unique(EncodableMap(args))), + std::move(initialize_result)); +} + +} // namespace test +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp new file mode 100644 index 000000000000..01d47fb6eb05 --- /dev/null +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -0,0 +1,332 @@ +// 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mocks.h" + +namespace camera_windows { +namespace test { + +TEST(Camera, InitCameraCreatesCaptureController) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr capture_controller_factory = + std::make_unique(); + + EXPECT_CALL(*capture_controller_factory, CreateCaptureController) + .Times(1) + .WillOnce( + []() { return std::make_unique>(); }); + + EXPECT_TRUE(camera->GetCaptureController() == nullptr); + + // Init camera with mock capture controller factory + camera->InitCamera(std::move(capture_controller_factory), + std::make_unique().get(), + std::make_unique().get(), false, + ResolutionPreset::RESOLUTION_PRESET_AUTO); + + EXPECT_TRUE(camera->GetCaptureController() != nullptr); +} + +TEST(Camera, AddPendingResultReturnsErrorForDuplicates) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr first_pending_result = + std::make_unique(); + std::unique_ptr second_pending_result = + std::make_unique(); + + EXPECT_CALL(*first_pending_result, ErrorInternal).Times(0); + EXPECT_CALL(*first_pending_result, SuccessInternal); + EXPECT_CALL(*second_pending_result, ErrorInternal).Times(1); + + camera->AddPendingResult(PendingResultType::CREATE_CAMERA, + std::move(first_pending_result)); + + // This should fail + camera->AddPendingResult(PendingResultType::CREATE_CAMERA, + std::move(second_pending_result)); + + // Mark pending result as succeeded + camera->OnCreateCaptureEngineSucceeded(0); +} + +TEST(Camera, OnCreateCaptureEngineSucceededReturnsCameraId) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + const int64_t texture_id = 12345; + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL( + *result, + SuccessInternal(Pointee(EncodableValue(EncodableMap( + {{EncodableValue("cameraId"), EncodableValue(texture_id)}}))))); + + camera->AddPendingResult(PendingResultType::CREATE_CAMERA, std::move(result)); + + camera->OnCreateCaptureEngineSucceeded(texture_id); +} + +TEST(Camera, OnCreateCaptureEngineFailedReturnsError) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string error_text = "error_text"; + + EXPECT_CALL(*result, SuccessInternal).Times(0); + EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); + + camera->AddPendingResult(PendingResultType::CREATE_CAMERA, std::move(result)); + + camera->OnCreateCaptureEngineFailed(error_text); +} + +TEST(Camera, OnStartPreviewSucceededReturnsFrameSize) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + const int32_t width = 123; + const int32_t height = 456; + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL( + *result, + SuccessInternal(Pointee(EncodableValue(EncodableMap({ + {EncodableValue("previewWidth"), EncodableValue((float)width)}, + {EncodableValue("previewHeight"), EncodableValue((float)height)}, + }))))); + + camera->AddPendingResult(PendingResultType::INITIALIZE, std::move(result)); + + camera->OnStartPreviewSucceeded(width, height); +} + +TEST(Camera, OnStartPreviewFailedReturnsError) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string error_text = "error_text"; + + EXPECT_CALL(*result, SuccessInternal).Times(0); + EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); + + camera->AddPendingResult(PendingResultType::INITIALIZE, std::move(result)); + + camera->OnStartPreviewFailed(error_text); +} + +TEST(Camera, OnPausePreviewSucceededReturnsSuccess) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL(*result, SuccessInternal(nullptr)); + + camera->AddPendingResult(PendingResultType::PAUSE_PREVIEW, std::move(result)); + + camera->OnPausePreviewSucceeded(); +} + +TEST(Camera, OnPausePreviewFailedReturnsError) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string error_text = "error_text"; + + EXPECT_CALL(*result, SuccessInternal).Times(0); + EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); + + camera->AddPendingResult(PendingResultType::PAUSE_PREVIEW, std::move(result)); + + camera->OnPausePreviewFailed(error_text); +} + +TEST(Camera, OnResumePreviewSucceededReturnsSuccess) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL(*result, SuccessInternal(nullptr)); + + camera->AddPendingResult(PendingResultType::RESUME_PREVIEW, + std::move(result)); + + camera->OnResumePreviewSucceeded(); +} + +TEST(Camera, OnResumePreviewFailedReturnsError) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string error_text = "error_text"; + + EXPECT_CALL(*result, SuccessInternal).Times(0); + EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); + + camera->AddPendingResult(PendingResultType::RESUME_PREVIEW, + std::move(result)); + + camera->OnResumePreviewFailed(error_text); +} + +TEST(Camera, OnStartRecordSucceededReturnsSuccess) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL(*result, SuccessInternal(nullptr)); + + camera->AddPendingResult(PendingResultType::START_RECORD, std::move(result)); + + camera->OnStartRecordSucceeded(); +} + +TEST(Camera, OnStartRecordFailedReturnsError) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string error_text = "error_text"; + + EXPECT_CALL(*result, SuccessInternal).Times(0); + EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); + + camera->AddPendingResult(PendingResultType::START_RECORD, std::move(result)); + + camera->OnStartRecordFailed(error_text); +} + +TEST(Camera, OnStopRecordSucceededReturnsSuccess) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string filepath = "C:\temp\filename.mp4"; + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(filepath)))); + + camera->AddPendingResult(PendingResultType::STOP_RECORD, std::move(result)); + + camera->OnStopRecordSucceeded(filepath); +} + +TEST(Camera, OnStopRecordFailedReturnsError) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string error_text = "error_text"; + + EXPECT_CALL(*result, SuccessInternal).Times(0); + EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); + + camera->AddPendingResult(PendingResultType::STOP_RECORD, std::move(result)); + + camera->OnStopRecordFailed(error_text); +} + +TEST(Camera, OnPictureSuccessReturnsSuccess) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string filepath = "C:\temp\filename.jpeg"; + + EXPECT_CALL(*result, ErrorInternal).Times(0); + EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(filepath)))); + + camera->AddPendingResult(PendingResultType::TAKE_PICTURE, std::move(result)); + + camera->OnPictureSuccess(filepath); +} + +TEST(Camera, OnPictureFailedReturnsError) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr result = + std::make_unique(); + + std::string error_text = "error_text"; + + EXPECT_CALL(*result, SuccessInternal).Times(0); + EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); + + camera->AddPendingResult(PendingResultType::TAKE_PICTURE, std::move(result)); + + camera->OnPictureFailed(error_text); +} + +TEST(Camera, OnVideoRecordedSuccessInvokesCameraChannelEvent) { + std::unique_ptr camera = + std::make_unique(MOCK_DEVICE_ID); + std::unique_ptr capture_controller_factory = + std::make_unique(); + + std::unique_ptr binary_messenger = + std::make_unique(); + + std::string filepath = "C:\temp\filename.mp4"; + int64_t camera_id = 12345; + std::string camera_channel = + std::string("flutter.io/cameraPlugin/camera") + std::to_string(camera_id); + int64_t video_duration = 1000000; + + EXPECT_CALL(*capture_controller_factory, CreateCaptureController) + .Times(1) + .WillOnce( + []() { return std::make_unique>(); }); + + // TODO: test binary content + EXPECT_CALL(*binary_messenger, Send(Eq(camera_channel), _, _, _)).Times(1); + + // Init camera with mock capture controller factory + camera->InitCamera(std::move(capture_controller_factory), + std::make_unique().get(), + binary_messenger.get(), false, + ResolutionPreset::RESOLUTION_PRESET_AUTO); + + // Pass camera id for camera + camera->OnCreateCaptureEngineSucceeded(camera_id); + + camera->OnVideoRecordedSuccess(filepath, video_duration); +} + +} // 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 new file mode 100644 index 000000000000..fc0d41fa0db4 --- /dev/null +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -0,0 +1,252 @@ +// 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_TEST_MOCKS_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEST_MOCKS_H_ + +#include +#include +#include +#include +#include +#include + +#include "camera.h" +#include "camera_plugin.h" + +namespace camera_windows { +namespace test { + +namespace { + +using flutter::EncodableMap; +using flutter::EncodableValue; +using ::testing::_; +using ::testing::ByMove; +using ::testing::DoAll; +using ::testing::EndsWith; +using ::testing::Eq; +using ::testing::NiceMock; +using ::testing::Pointee; +using ::testing::Return; +using ::testing::SetArgPointee; + +class MockMethodResult : public flutter::MethodResult<> { + public: + MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result), + (override)); + MOCK_METHOD(void, ErrorInternal, + (const std::string& error_code, const std::string& error_message, + const EncodableValue* details), + (override)); + MOCK_METHOD(void, NotImplementedInternal, (), (override)); +}; + +class MockBinaryMessenger : public flutter::BinaryMessenger { + public: + MOCK_METHOD(void, Send, + (const std::string& channel, const uint8_t* message, + size_t message_size, flutter::BinaryReply reply), + (const)); + + MOCK_METHOD(void, SetMessageHandler, + (const std::string& channel, + flutter::BinaryMessageHandler handler), + ()); +}; + +class MockTextureRegistrar : public flutter::TextureRegistrar { + public: + MockTextureRegistrar() { + // TODO: create separate fake implementation + ON_CALL(*this, RegisterTexture) + .WillByDefault([this](flutter::TextureVariant* texture) -> int64_t { + this->texture_id = 1000; + return this->texture_id; + }); + ON_CALL(*this, UnregisterTexture) + .WillByDefault([this](int64_t tid) -> bool { + if (tid == this->texture_id) { + this->texture_id = -1; + return true; + } + return false; + }); + ON_CALL(*this, MarkTextureFrameAvailable) + .WillByDefault([this](int64_t tid) -> bool { + if (tid == this->texture_id) { + return true; + } + return false; + }); + } + MOCK_METHOD(int64_t, RegisterTexture, (flutter::TextureVariant * texture), + (override)); + + MOCK_METHOD(bool, UnregisterTexture, (int64_t), (override)); + MOCK_METHOD(bool, MarkTextureFrameAvailable, (int64_t), (override)); + int64_t texture_id = -1; +}; + +class MockCameraFactory : public CameraFactory { + public: + MockCameraFactory() { + ON_CALL(*this, CreateCamera).WillByDefault([this]() { + assert(this->pending_camera_); + return std::move(this->pending_camera_); + }); + } + + MOCK_METHOD(std::unique_ptr, CreateCamera, + (const std::string& device_id), (override)); + + std::unique_ptr pending_camera_; +}; + +class MockCamera : public Camera { + public: + MockCamera(const std::string& device_id) + : device_id_(device_id), Camera(device_id){}; + ~MockCamera() = default; + + MockCamera(const MockCamera&) = delete; + MockCamera& operator=(const MockCamera&) = delete; + + MOCK_METHOD(void, OnCreateCaptureEngineSucceeded, (int64_t texture_id), + (override)); + MOCK_METHOD(std::unique_ptr>, GetPendingResultByType, + (PendingResultType type)); + MOCK_METHOD(void, OnCreateCaptureEngineFailed, (const std::string& error), + (override)); + + MOCK_METHOD(void, OnStartPreviewSucceeded, (int32_t width, int32_t height), + (override)); + MOCK_METHOD(void, OnStartPreviewFailed, (const std::string& error), + (override)); + + MOCK_METHOD(void, OnResumePreviewSucceeded, (), (override)); + MOCK_METHOD(void, OnResumePreviewFailed, (const std::string& error), + (override)); + + MOCK_METHOD(void, OnPausePreviewSucceeded, (), (override)); + MOCK_METHOD(void, OnPausePreviewFailed, (const std::string& error), + (override)); + + MOCK_METHOD(void, OnStartRecordSucceeded, (), (override)); + MOCK_METHOD(void, OnStartRecordFailed, (const std::string& error), + (override)); + + MOCK_METHOD(void, OnStopRecordSucceeded, (const std::string& filepath), + (override)); + MOCK_METHOD(void, OnStopRecordFailed, (const std::string& error), (override)); + + MOCK_METHOD(void, OnPictureSuccess, (const std::string& filepath), + (override)); + MOCK_METHOD(void, OnPictureFailed, (const std::string& error), (override)); + + MOCK_METHOD(void, OnVideoRecordedSuccess, + (const std::string& filepath, int64_t video_duration), + (override)); + MOCK_METHOD(void, OnVideoRecordedFailed, (const std::string& error), + (override)); + + MOCK_METHOD(bool, HasDeviceId, (std::string & device_id), (override)); + MOCK_METHOD(bool, HasCameraId, (int64_t camera_id), (override)); + + MOCK_METHOD(bool, AddPendingResult, + (PendingResultType type, std::unique_ptr> result), + (override)); + MOCK_METHOD(bool, HasPendingResultByType, (PendingResultType type), + (override)); + + MOCK_METHOD(camera_windows::CaptureController*, GetCaptureController, (), + (override)); + + MOCK_METHOD(void, InitCamera, + (flutter::TextureRegistrar * texture_registrar, + flutter::BinaryMessenger* messenger, bool enable_audio, + ResolutionPreset resolution_preset), + (override)); + + std::unique_ptr capture_controller_; + std::unique_ptr> pending_result_; + std::string device_id_; + int64_t camera_id_ = -1; +}; + +class MockCaptureControllerFactory : public CaptureControllerFactory { + public: + MockCaptureControllerFactory(){}; + virtual ~MockCaptureControllerFactory() = default; + + // Disallow copy and move. + MockCaptureControllerFactory(const MockCaptureControllerFactory&) = delete; + MockCaptureControllerFactory& operator=(const MockCaptureControllerFactory&) = + delete; + + MOCK_METHOD(std::unique_ptr, CreateCaptureController, + (CaptureControllerListener * listener), (override)); +}; + +class MockCaptureController : public CaptureController { + public: + MOCK_METHOD(void, CreateCaptureDevice, + (flutter::TextureRegistrar * texture_registrar, + const std::string& device_id, bool enable_audio, + ResolutionPreset resolution_preset), + (override)); + + MOCK_METHOD(int64_t, GetTextureId, (), (override)); + MOCK_METHOD(uint32_t, GetPreviewWidth, (), (override)); + MOCK_METHOD(uint32_t, GetPreviewHeight, (), (override)); + + // Actions + MOCK_METHOD(void, StartPreview, (), (override)); + MOCK_METHOD(void, StopPreview, (), (override)); + MOCK_METHOD(void, ResumePreview, (), (override)); + MOCK_METHOD(void, PausePreview, (), (override)); + MOCK_METHOD(void, StartRecord, + (const std::string& filepath, int64_t max_video_duration_ms), + (override)); + MOCK_METHOD(void, StopRecord, (), (override)); + MOCK_METHOD(void, TakePicture, (const std::string filepath), (override)); +}; + +// MockCameraPlugin extends CameraPlugin behaviour a bit to allow adding cameras +// without creating them first with create message handler and mocking static +// system calls +class MockCameraPlugin : public CameraPlugin { + public: + MockCameraPlugin(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger) + : CameraPlugin(texture_registrar, messenger){}; + + // Creates a plugin instance with the given CameraFactory instance. + // Exists for unit testing with mock implementations. + MockCameraPlugin(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, + std::unique_ptr camera_factory) + : CameraPlugin(texture_registrar, messenger, std::move(camera_factory)){}; + + MockCameraPlugin(const MockCameraPlugin&) = delete; + MockCameraPlugin& operator=(const MockCameraPlugin&) = delete; + + MOCK_METHOD(bool, EnumerateVideoCaptureDeviceSources, + (IMFActivate * **devices, UINT32* count), (override)); + + // Helper to add camera without creating it via CameraFactory for testing + // purposes + void AddCamera(std::unique_ptr camera) { + cameras_.push_back(std::move(camera)); + } +}; + +#define MOCK_DEVICE_ID "mock_device_id" +#define MOCK_CAMERA_NAME "mock_camera_name <" MOCK_DEVICE_ID ">" +#define MOCK_INVALID_CAMERA_NAME "invalid_camera_name" +} // namespace +} // namespace test +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEST_MOCKS_H_ From dbd214d0a4ab9bb5fc603dfb9125a9746472e39b Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 5 Jan 2022 10:31:11 +0200 Subject: [PATCH 02/93] [camera_windows] Improve README --- packages/camera/camera_windows/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_windows/README.md b/packages/camera/camera_windows/README.md index 944b4df0ec29..34d541771dab 100644 --- a/packages/camera/camera_windows/README.md +++ b/packages/camera/camera_windows/README.md @@ -8,12 +8,12 @@ The windows implementation of [`camera`][camera]. ### Depend on the package -This package is not [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), -which means you need to add `camera_windows` separately to your project dependencies to use it with [`camera`][camera] plugin. +This package is not an [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin) implementation of the [`camera`][camera] plugin yet, so you'll need to +[add it explicitly](https://pub.dev/packages/camera_windows/install) ## Example -Find the example in the [`camera windows` package](https://pub.dev/packages/camera_windows#example). +Find the example in the [camera windows package](https://pub.dev/packages/camera_windows#example). ## Limitations on the windows platform @@ -48,7 +48,6 @@ The windows implementation of [`camera`][camera] is missing the following featur - Image format group - Streaming of frames - Video record pause and resume -- Support for multiple simultanious camera captures. -[camera]: https://pub.dev/packages/camera_windows +[camera]: https://pub.dev/packages/camera From 893740500e6b3fcca4132b492a83bfd5c102bf61 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 5 Jan 2022 10:40:10 +0200 Subject: [PATCH 03/93] [camera_windows] Update pubspec files --- packages/camera/camera_windows/example/pubspec.yaml | 7 +++---- packages/camera/camera_windows/pubspec.yaml | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index b634ca42ffb5..5cad664dfff9 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -7,9 +7,7 @@ environment: flutter: ">=2.5.3" dependencies: - flutter: - sdk: flutter - + camera_platform_interface: ^2.1.1 camera_windows: # When depending on this package from a real application you should use: # camera_windows: ^x.y.z @@ -17,7 +15,8 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - camera_platform_interface: ^2.1.1 + flutter: + sdk: flutter url_launcher: ^6.0.0 dev_dependencies: diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 1b25fa05e8bb..682d7118a096 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -1,7 +1,10 @@ name: camera_windows description: A Flutter plugin for getting information about and controlling the camera on Windows. version: 0.0.1 -homepage: TBD +# TODO: Update next lines to match proper repository and issue_tracker url +#repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera_windows +#issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 +# TODO: Remove next line after plugin is ready to be published publish_to: 'none' environment: From 420e6c837b8846096a4bbb0ecc5f217db25ca316 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 5 Jan 2022 14:37:29 +0200 Subject: [PATCH 04/93] [camera_windows] Add integration tests --- .../example/integration_test/camera_test.dart | 96 +++++++++++++++++++ .../example/test_driver/integration_test.dart | 7 ++ 2 files changed, 103 insertions(+) create mode 100644 packages/camera/camera_windows/example/integration_test/camera_test.dart create mode 100644 packages/camera/camera_windows/example/test_driver/integration_test.dart diff --git a/packages/camera/camera_windows/example/integration_test/camera_test.dart b/packages/camera/camera_windows/example/integration_test/camera_test.dart new file mode 100644 index 000000000000..9565ae1aa5b8 --- /dev/null +++ b/packages/camera/camera_windows/example/integration_test/camera_test.dart @@ -0,0 +1,96 @@ +// 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:async'; +import 'package:async/async.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // TODO(jokerttu): write rest of the integration tests after camera support is available on windows test framework or native plugin code is mockable + + group('initializeCamera', () { + testWidgets('throws exception if camera is not created', + (WidgetTester _) async { + final CameraPlatform camera = CameraPlatform.instance; + + expect(() async => await camera.initializeCamera(1234), + throwsA(isA())); + }); + }); + + group('takePicture', () { + testWidgets('throws exception if camera is not created', + (WidgetTester _) async { + final CameraPlatform camera = CameraPlatform.instance; + + expect(() async => await camera.takePicture(1234), + throwsA(isA())); + }); + }); + + group('startVideoRecording', () { + testWidgets('throws exception if camera is not created', + (WidgetTester _) async { + final CameraPlatform camera = CameraPlatform.instance; + + expect(() async => await camera.startVideoRecording(1234), + throwsA(isA())); + }); + }); + + group('stopVideoRecording', () { + testWidgets('throws exception if camera is not created', + (WidgetTester _) async { + final CameraPlatform camera = CameraPlatform.instance; + + expect(() async => await camera.stopVideoRecording(1234), + throwsA(isA())); + }); + }); + + group('pausePreview', () { + testWidgets('throws exception if camera is not created', + (WidgetTester _) async { + final CameraPlatform camera = CameraPlatform.instance; + + expect(() async => await camera.pausePreview(1234), + throwsA(isA())); + }); + }); + + group('resumePreview', () { + testWidgets('throws exception if camera is not created', + (WidgetTester _) async { + final CameraPlatform camera = CameraPlatform.instance; + + expect(() async => await camera.resumePreview(1234), + throwsA(isA())); + }); + }); + + group('onDeviceOrientationChanged', () { + testWidgets('emits the initial DeviceOrientationChangedEvent', + (WidgetTester _) async { + final Stream eventStream = + CameraPlatform.instance.onDeviceOrientationChanged(); + + final StreamQueue streamQueue = + StreamQueue(eventStream); + + expect( + await streamQueue.next, + equals( + DeviceOrientationChangedEvent( + DeviceOrientation.landscapeRight, + ), + ), + ); + }); + }); +} diff --git a/packages/camera/camera_windows/example/test_driver/integration_test.dart b/packages/camera/camera_windows/example/test_driver/integration_test.dart new file mode 100644 index 000000000000..4f10f2a522f3 --- /dev/null +++ b/packages/camera/camera_windows/example/test_driver/integration_test.dart @@ -0,0 +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. + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); From 49e9222e30fc95657bbe8a04d990721ad3b22a85 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 5 Jan 2022 14:38:11 +0200 Subject: [PATCH 05/93] [camera_windows] Fix formatting for windows runner --- .../example/windows/runner/main.cpp | 3 +-- .../example/windows/runner/resource.h | 10 +++++----- .../example/windows/runner/utils.cpp | 11 +++++------ .../example/windows/runner/win32_window.cpp | 18 +++++------------- .../example/windows/runner/win32_window.h | 9 +++------ 5 files changed, 19 insertions(+), 32 deletions(-) diff --git a/packages/camera/camera_windows/example/windows/runner/main.cpp b/packages/camera/camera_windows/example/windows/runner/main.cpp index b7a7d4016155..568d53954c20 100644 --- a/packages/camera/camera_windows/example/windows/runner/main.cpp +++ b/packages/camera/camera_windows/example/windows/runner/main.cpp @@ -19,8 +19,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, flutter::DartProject project(L"data"); - std::vector command_line_arguments = - GetCommandLineArguments(); + std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); diff --git a/packages/camera/camera_windows/example/windows/runner/resource.h b/packages/camera/camera_windows/example/windows/runner/resource.h index 66a65d1e4a79..d5d958dc4257 100644 --- a/packages/camera/camera_windows/example/windows/runner/resource.h +++ b/packages/camera/camera_windows/example/windows/runner/resource.h @@ -2,15 +2,15 @@ // Microsoft Visual C++ generated include file. // Used by Runner.rc // -#define IDI_APP_ICON 101 +#define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/packages/camera/camera_windows/example/windows/runner/utils.cpp b/packages/camera/camera_windows/example/windows/runner/utils.cpp index d19bdbbcc322..afa363b236e3 100644 --- a/packages/camera/camera_windows/example/windows/runner/utils.cpp +++ b/packages/camera/camera_windows/example/windows/runner/utils.cpp @@ -9,7 +9,7 @@ void CreateAndAttachConsole() { if (::AllocConsole()) { - FILE *unused; + FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } @@ -45,17 +45,16 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } - int target_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr); + int target_length = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, + nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, utf8_string.data(), + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); diff --git a/packages/camera/camera_windows/example/windows/runner/win32_window.cpp b/packages/camera/camera_windows/example/windows/runner/win32_window.cpp index c10f08dc7da6..44091b3f3c91 100644 --- a/packages/camera/camera_windows/example/windows/runner/win32_window.cpp +++ b/packages/camera/camera_windows/example/windows/runner/win32_window.cpp @@ -93,17 +93,14 @@ void WindowClassRegistrar::UnregisterWindowClass() { class_registered_ = false; } -Win32Window::Win32Window() { - ++g_active_window_count; -} +Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } -bool Win32Window::CreateAndShow(const std::wstring& title, - const Point& origin, +bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); @@ -130,8 +127,7 @@ bool Win32Window::CreateAndShow(const std::wstring& title, } // static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, +LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { @@ -150,9 +146,7 @@ LRESULT CALLBACK Win32Window::WndProc(HWND const window, } LRESULT -Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, +Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: @@ -227,9 +221,7 @@ RECT Win32Window::GetClientArea() { return frame; } -HWND Win32Window::GetHandle() { - return window_handle_; -} +HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; diff --git a/packages/camera/camera_windows/example/windows/runner/win32_window.h b/packages/camera/camera_windows/example/windows/runner/win32_window.h index 17ba431125b4..4ae64a12b465 100644 --- a/packages/camera/camera_windows/example/windows/runner/win32_window.h +++ b/packages/camera/camera_windows/example/windows/runner/win32_window.h @@ -34,8 +34,7 @@ class Win32Window { // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. - bool CreateAndShow(const std::wstring& title, - const Point& origin, + bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. @@ -58,8 +57,7 @@ class Win32Window { // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, - UINT const message, + virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; @@ -78,8 +76,7 @@ class Win32Window { // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, - UINT const message, + static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; From f1883524b18470cae62c86981e088b030b951025 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 5 Jan 2022 15:18:54 +0200 Subject: [PATCH 06/93] [camera_windows] Add missing license blocks --- packages/camera/camera_windows/example/lib/main.dart | 4 ++++ .../camera_windows/example/windows/runner/flutter_window.cpp | 4 ++++ .../camera_windows/example/windows/runner/flutter_window.h | 4 ++++ .../camera/camera_windows/example/windows/runner/main.cpp | 4 ++++ .../camera/camera_windows/example/windows/runner/utils.cpp | 4 ++++ packages/camera/camera_windows/example/windows/runner/utils.h | 4 ++++ .../camera_windows/example/windows/runner/win32_window.cpp | 4 ++++ .../camera_windows/example/windows/runner/win32_window.h | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index eddea342105f..d21357a5acda 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -1,3 +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. + import 'dart:async'; import 'package:camera_platform_interface/camera_platform_interface.dart'; diff --git a/packages/camera/camera_windows/example/windows/runner/flutter_window.cpp b/packages/camera/camera_windows/example/windows/runner/flutter_window.cpp index b43b9095ea3a..8254bd9ff3c1 100644 --- a/packages/camera/camera_windows/example/windows/runner/flutter_window.cpp +++ b/packages/camera/camera_windows/example/windows/runner/flutter_window.cpp @@ -1,3 +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. + #include "flutter_window.h" #include diff --git a/packages/camera/camera_windows/example/windows/runner/flutter_window.h b/packages/camera/camera_windows/example/windows/runner/flutter_window.h index 6da0652f05f2..f1fc669093d0 100644 --- a/packages/camera/camera_windows/example/windows/runner/flutter_window.h +++ b/packages/camera/camera_windows/example/windows/runner/flutter_window.h @@ -1,3 +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. + #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/camera/camera_windows/example/windows/runner/main.cpp b/packages/camera/camera_windows/example/windows/runner/main.cpp index 568d53954c20..2ec184ba841b 100644 --- a/packages/camera/camera_windows/example/windows/runner/main.cpp +++ b/packages/camera/camera_windows/example/windows/runner/main.cpp @@ -1,3 +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. + #include #include #include diff --git a/packages/camera/camera_windows/example/windows/runner/utils.cpp b/packages/camera/camera_windows/example/windows/runner/utils.cpp index afa363b236e3..fb7e945a63b7 100644 --- a/packages/camera/camera_windows/example/windows/runner/utils.cpp +++ b/packages/camera/camera_windows/example/windows/runner/utils.cpp @@ -1,3 +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. + #include "utils.h" #include diff --git a/packages/camera/camera_windows/example/windows/runner/utils.h b/packages/camera/camera_windows/example/windows/runner/utils.h index 3879d5475579..bd81e1e02338 100644 --- a/packages/camera/camera_windows/example/windows/runner/utils.h +++ b/packages/camera/camera_windows/example/windows/runner/utils.h @@ -1,3 +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. + #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ diff --git a/packages/camera/camera_windows/example/windows/runner/win32_window.cpp b/packages/camera/camera_windows/example/windows/runner/win32_window.cpp index 44091b3f3c91..85aa3614e8ad 100644 --- a/packages/camera/camera_windows/example/windows/runner/win32_window.cpp +++ b/packages/camera/camera_windows/example/windows/runner/win32_window.cpp @@ -1,3 +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. + #include "win32_window.h" #include diff --git a/packages/camera/camera_windows/example/windows/runner/win32_window.h b/packages/camera/camera_windows/example/windows/runner/win32_window.h index 4ae64a12b465..d2a730052223 100644 --- a/packages/camera/camera_windows/example/windows/runner/win32_window.h +++ b/packages/camera/camera_windows/example/windows/runner/win32_window.h @@ -1,3 +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. + #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ From 01478ed62cb64052e64d1fbfe038f6963530f447 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 5 Jan 2022 15:38:32 +0200 Subject: [PATCH 07/93] [camera_windows] Update camera_platform_interface --- .../camera_windows/example/integration_test/camera_test.dart | 2 +- packages/camera/camera_windows/example/pubspec.yaml | 2 +- packages/camera/camera_windows/lib/camera_windows.dart | 2 +- packages/camera/camera_windows/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera_windows/example/integration_test/camera_test.dart b/packages/camera/camera_windows/example/integration_test/camera_test.dart index 9565ae1aa5b8..3a6539a88f33 100644 --- a/packages/camera/camera_windows/example/integration_test/camera_test.dart +++ b/packages/camera/camera_windows/example/integration_test/camera_test.dart @@ -86,7 +86,7 @@ void main() { expect( await streamQueue.next, equals( - DeviceOrientationChangedEvent( + const DeviceOrientationChangedEvent( DeviceOrientation.landscapeRight, ), ), diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index 5cad664dfff9..e8d3b641395c 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -7,7 +7,7 @@ environment: flutter: ">=2.5.3" dependencies: - camera_platform_interface: ^2.1.1 + camera_platform_interface: ^2.1.2 camera_windows: # When depending on this package from a real application you should use: # camera_windows: ^x.y.z diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 61538d726476..2cbd7bcfd0a3 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -183,7 +183,7 @@ class CameraWindows extends CameraPlatform { //Windows camera plugin does not support capture orientations //Force device orientation to landscape (by default camera plugin uses portraitUp orientation) return Stream.value( - DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight), + const DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight), ); } diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 682d7118a096..de4f174de49d 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -20,7 +20,7 @@ flutter: dartPluginClass: CameraWindows dependencies: - camera_platform_interface: ^2.1.0 + camera_platform_interface: ^2.1.2 flutter: sdk: flutter From ccc8ca0a8356e7bec8ef50174c13ec8be9a00a41 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 11 Jan 2022 20:45:19 +0200 Subject: [PATCH 08/93] [camera_windows] Use smart pointers for IUnknown classes --- .../windows/capture_controller.cpp | 281 ++++++++---------- .../windows/capture_controller.h | 34 +-- 2 files changed, 138 insertions(+), 177 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 7a7276540bc7..824f7b857043 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -5,6 +5,7 @@ #include "capture_controller.h" #include +#include #include @@ -12,6 +13,8 @@ namespace camera_windows { +using Microsoft::WRL::ComPtr; + struct FlutterDesktop_Pixel { BYTE r = 0; BYTE g = 0; @@ -38,7 +41,7 @@ CaptureControllerImpl::~CaptureControllerImpl() { // static bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( IMFActivate ***devices, UINT32 *count) { - IMFAttributes *attributes = nullptr; + ComPtr attributes; HRESULT hr = MFCreateAttributes(&attributes, 1); @@ -48,23 +51,22 @@ bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( } if (SUCCEEDED(hr)) { - hr = MFEnumDeviceSources(attributes, devices, count); + hr = MFEnumDeviceSources(attributes.Get(), devices, count); } - Release(&attributes); return SUCCEEDED(hr); } HRESULT BuildMediaTypeForVideoPreview(IMFMediaType *src_media_type, IMFMediaType **preview_media_type) { - Release(preview_media_type); - IMFMediaType *new_media_type = nullptr; + assert(src_media_type); + ComPtr new_media_type; HRESULT hr = MFCreateMediaType(&new_media_type); // First clone everything from original media type if (SUCCEEDED(hr)) { - hr = src_media_type->CopyAllItems(new_media_type); + hr = src_media_type->CopyAllItems(new_media_type.Get()); } if (SUCCEEDED(hr)) { @@ -77,11 +79,9 @@ HRESULT BuildMediaTypeForVideoPreview(IMFMediaType *src_media_type, } if (SUCCEEDED(hr)) { - *preview_media_type = new_media_type; - (*preview_media_type)->AddRef(); + new_media_type.CopyTo(preview_media_type); } - Release(&new_media_type); return hr; } @@ -89,14 +89,14 @@ HRESULT BuildMediaTypeForVideoPreview(IMFMediaType *src_media_type, HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, IMFMediaType **photo_media_type, GUID image_format) { - Release(photo_media_type); - IMFMediaType *new_media_type = nullptr; + assert(src_media_type); + ComPtr new_media_type; HRESULT hr = MFCreateMediaType(&new_media_type); // First clone everything from original media type if (SUCCEEDED(hr)) { - hr = src_media_type->CopyAllItems(new_media_type); + hr = src_media_type->CopyAllItems(new_media_type.Get()); } if (SUCCEEDED(hr)) { @@ -108,11 +108,9 @@ HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, } if (SUCCEEDED(hr)) { - *photo_media_type = new_media_type; - (*photo_media_type)->AddRef(); + new_media_type.CopyTo(photo_media_type); } - Release(&new_media_type); return hr; } @@ -120,14 +118,14 @@ HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, IMFMediaType **video_record_media_type, GUID capture_format) { - Release(video_record_media_type); - IMFMediaType *new_media_type = nullptr; + assert(src_media_type); + ComPtr new_media_type; HRESULT hr = MFCreateMediaType(&new_media_type); // First clone everything from original media type if (SUCCEEDED(hr)) { - hr = src_media_type->CopyAllItems(new_media_type); + hr = src_media_type->CopyAllItems(new_media_type.Get()); } if (SUCCEEDED(hr)) { @@ -135,16 +133,26 @@ HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, } if (SUCCEEDED(hr)) { - *video_record_media_type = new_media_type; - (*video_record_media_type)->AddRef(); + new_media_type.CopyTo(video_record_media_type); } - Release(&new_media_type); return hr; } // Queries interface object from collection template +HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, + Q **ppObj) { + ComPtr pUnk; + HRESULT hr = pCollection->GetElement(index, pUnk.GetAddressOf()); + if (SUCCEEDED(hr)) { + hr = pUnk->QueryInterface(IID_PPV_ARGS(ppObj)); + } + return hr; +} + +/* +template HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, Q **ppObj) { IUnknown *pUnk; @@ -154,15 +162,13 @@ HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, pUnk->Release(); } return hr; -} +}*/ HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { - Release(audio_record_media_type); - - IMFAttributes *audio_output_attributes = nullptr; - IMFCollection *available_output_types = nullptr; - IMFMediaType *src_media_type = nullptr; - IMFMediaType *new_media_type = nullptr; + ComPtr audio_output_attributes; + ComPtr src_media_type; + ComPtr new_media_type; + ComPtr available_output_types; DWORD mt_count = 0; HRESULT hr = MFCreateAttributes(&audio_output_attributes, 1); @@ -176,13 +182,14 @@ HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { DWORD mft_flags = (MFT_ENUM_FLAG_ALL & (~MFT_ENUM_FLAG_FIELDOFUSE)) | MFT_ENUM_FLAG_SORTANDFILTER; - hr = MFTranscodeGetAudioOutputAvailableTypes(MFAudioFormat_AAC, mft_flags, - audio_output_attributes, - &available_output_types); + hr = MFTranscodeGetAudioOutputAvailableTypes( + MFAudioFormat_AAC, mft_flags, audio_output_attributes.Get(), + available_output_types.GetAddressOf()); } if (SUCCEEDED(hr)) { - hr = GetCollectionObject(available_output_types, 0, &src_media_type); + hr = GetCollectionObject(available_output_types.Get(), 0, + src_media_type.GetAddressOf()); } if (SUCCEEDED(hr)) { @@ -200,31 +207,24 @@ HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { } if (SUCCEEDED(hr)) { - hr = src_media_type->CopyAllItems(new_media_type); + hr = src_media_type->CopyAllItems(new_media_type.Get()); } if (SUCCEEDED(hr)) { - // Point target media type to new media type - *audio_record_media_type = new_media_type; - (*audio_record_media_type)->AddRef(); + new_media_type.CopyTo(audio_record_media_type); } - Release(&audio_output_attributes); - Release(&available_output_types); - Release(&src_media_type); - Release(&new_media_type); - return hr; } // Uses first audio source to capture audio. Enumerating audio sources via // platform interface is not supported. HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { - this->audio_source_ = nullptr; + audio_source_ = nullptr; IMFActivate **devices = nullptr; UINT32 count = 0; - IMFAttributes *attributes = nullptr; + ComPtr attributes; HRESULT hr = MFCreateAttributes(&attributes, 1); if (SUCCEEDED(hr)) { @@ -233,11 +233,9 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { } if (SUCCEEDED(hr)) { - hr = MFEnumDeviceSources(attributes, &devices, &count); + hr = MFEnumDeviceSources(attributes.Get(), &devices, &count); } - Release(&attributes); - if (SUCCEEDED(hr) && count > 0) { wchar_t *audio_device_id; UINT32 audio_device_id_size; @@ -248,7 +246,7 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { &audio_device_id_size); if (SUCCEEDED(hr)) { - IMFAttributes *audio_capture_source_attributes = nullptr; + ComPtr audio_capture_source_attributes; hr = MFCreateAttributes(&audio_capture_source_attributes, 2); if (SUCCEEDED(hr)) { @@ -264,13 +262,12 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { } if (SUCCEEDED(hr)) { - hr = MFCreateDeviceSource(audio_capture_source_attributes, - &this->audio_source_); + hr = MFCreateDeviceSource(audio_capture_source_attributes.Get(), + audio_source_.GetAddressOf()); } - Release(&audio_capture_source_attributes); } - ::CoTaskMemFree(audio_device_id); + CoTaskMemFree(audio_device_id); } CoTaskMemFree(devices); @@ -280,9 +277,9 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( const std::string &video_device_id) { - this->video_source_ = nullptr; + video_source_ = nullptr; - IMFAttributes *video_capture_source_attributes = nullptr; + ComPtr video_capture_source_attributes; HRESULT hr = MFCreateAttributes(&video_capture_source_attributes, 2); @@ -299,62 +296,40 @@ HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( } if (SUCCEEDED(hr)) { - hr = MFCreateDeviceSource(video_capture_source_attributes, - &this->video_source_); + hr = MFCreateDeviceSource(video_capture_source_attributes.Get(), + video_source_.GetAddressOf()); } - Release(&video_capture_source_attributes); - return hr; } // Create DX11 Device and D3D Manager -// TODO: Check if DX12 device can be used with flutter: +// TODO: If DX12 device can be used with flutter: // Separate CreateD3DManagerWithDX12Device functionality // can be written if needed -// D3D_FEATURE_LEVEL min_feature_level = D3D_FEATURE_LEVEL_9_1; -// D3D12CreateDevice(nullptr,min_feature_level,...); HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { HRESULT hr = S_OK; - /* - // Captures selected feature level - D3D_FEATURE_LEVEL feature_level; - - // List of allowed feature levels - static const D3D_FEATURE_LEVEL allowed_feature_levels[] = { - D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1}; - - hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, - D3D11_CREATE_DEVICE_VIDEO_SUPPORT, - allowed_feature_levels, - ARRAYSIZE(allowed_feature_levels), D3D11_SDK_VERSION, - &dx11_device_, &feature_level, nullptr ); - */ - hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, &dx11_device_, nullptr, nullptr); if (SUCCEEDED(hr)) { // Enable multithread protection - ID3D10Multithread *multi_thread; - hr = dx11_device_->QueryInterface(IID_PPV_ARGS(&multi_thread)); + ComPtr multi_thread; + hr = dx11_device_.As(&multi_thread); if (SUCCEEDED(hr)) { multi_thread->SetMultithreadProtected(TRUE); } - Release(&multi_thread); } if (SUCCEEDED(hr)) { hr = MFCreateDXGIDeviceManager(&dx_device_reset_token_, - &dxgi_device_manager_); + dxgi_device_manager_.GetAddressOf()); } if (SUCCEEDED(hr)) { - hr = - dxgi_device_manager_->ResetDevice(dx11_device_, dx_device_reset_token_); + hr = dxgi_device_manager_->ResetDevice(dx11_device_.Get(), + dx_device_reset_token_); } return hr; @@ -367,8 +342,8 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine( IMFCaptureEngineClassFactory *capture_engine_factory = nullptr; if (!capture_engine_callback_handler_) { - capture_engine_callback_handler_ = new CaptureEngineListener(this); - capture_engine_callback_handler_->AddRef(); + capture_engine_callback_handler_ = + ComPtr(new CaptureEngineListener(this)); } if (SUCCEEDED(hr)) { @@ -381,7 +356,7 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine( if (SUCCEEDED(hr)) { hr = attributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, - dxgi_device_manager_); + dxgi_device_manager_.Get()); } if (SUCCEEDED(hr)) { @@ -412,8 +387,9 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine( } if (SUCCEEDED(hr)) { - hr = capture_engine_->Initialize(capture_engine_callback_handler_, - attributes, audio_source_, video_source_); + hr = capture_engine_->Initialize(capture_engine_callback_handler_.Get(), + attributes, audio_source_.Get(), + video_source_.Get()); } return hr; @@ -446,31 +422,32 @@ void CaptureControllerImpl::ResetCaptureEngineState() { max_video_duration_ms_ = -1; // Preview - Release(&preview_sink_); + preview_sink_ = nullptr; preview_frame_width_ = 0; preview_frame_height_ = 0; // Photo / Record - Release(&photo_sink_); - Release(&record_sink_); + photo_sink_ = nullptr; + record_sink_ = nullptr; capture_frame_width_ = 0; capture_frame_height_ = 0; // CaptureEngine - Release(&capture_engine_callback_handler_); - Release(&capture_engine_); + capture_engine_callback_handler_ = nullptr; + capture_engine_ = nullptr; - Release(&audio_source_); - Release(&video_source_); + audio_source_ = nullptr; + video_source_ = nullptr; - Release(&base_preview_media_type); - Release(&base_capture_media_type); + base_preview_media_type_ = nullptr; + base_capture_media_type_ = nullptr; if (dxgi_device_manager_) { - dxgi_device_manager_->ResetDevice(dx11_device_, dx_device_reset_token_); + dxgi_device_manager_->ResetDevice(dx11_device_.Get(), + dx_device_reset_token_); } - Release(&dxgi_device_manager_); - Release(&dx11_device_); + dxgi_device_manager_ = nullptr; + dx11_device_ = nullptr; // Texture if (texture_registrar_ && texture_id_ > -1) { @@ -829,37 +806,33 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { return E_FAIL; } - IMFCaptureSource *source = nullptr; + ComPtr source; HRESULT hr = capture_engine_->GetSource(&source); if (SUCCEEDED(hr)) { - IMFMediaType *media_type = nullptr; + ComPtr media_type; uint32_t max_height = GetMaxPreviewHeight(); // Loop native media types for (int i = 0;; i++) { // Release media type if exists from previous loop; - Release(&media_type); + media_type = nullptr; if (FAILED(source->GetAvailableDeviceMediaType( (DWORD) MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, - i, &media_type))) { + i, media_type.GetAddressOf()))) { break; } uint32_t frame_width; uint32_t frame_height; - if (SUCCEEDED(MFGetAttributeSize(media_type, MF_MT_FRAME_SIZE, + if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, &frame_width, &frame_height))) { // Update media type for photo and record capture if (capture_frame_width_ < frame_width || capture_frame_height_ < frame_height) { - // Release old base type if allocated - Release(&base_capture_media_type); - - base_capture_media_type = media_type; - base_capture_media_type->AddRef(); + base_capture_media_type_ = media_type; capture_frame_width_ = frame_width; capture_frame_height_ = frame_height; @@ -869,27 +842,21 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { if (frame_height <= max_height && (preview_frame_width_ < frame_width || preview_frame_height_ < frame_height)) { - // Release old base type if allocated - Release(&base_preview_media_type); - - base_preview_media_type = media_type; - base_preview_media_type->AddRef(); + base_preview_media_type_ = media_type; preview_frame_width_ = frame_width; preview_frame_height_ = frame_height; } } } - Release(&media_type); - if (base_preview_media_type && base_capture_media_type) { + if (base_preview_media_type_ && base_capture_media_type_) { hr = S_OK; } else { hr = E_FAIL; } } - Release(&source); return hr; } @@ -903,43 +870,46 @@ HRESULT CaptureControllerImpl::InitPreviewSink() { return hr; } - IMFMediaType *preview_media_type = nullptr; - IMFCaptureSink *capture_sink = nullptr; + ComPtr preview_media_type; + ComPtr capture_sink; // Get sink with preview type; hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, &capture_sink); if (SUCCEEDED(hr)) { - hr = capture_sink->QueryInterface(IID_PPV_ARGS(&preview_sink_)); + hr = capture_sink.As(&preview_sink_); + } + + if (SUCCEEDED(hr)) { + hr = preview_sink_->RemoveAllStreams(); } - if (SUCCEEDED(hr) && !base_preview_media_type) { + if (SUCCEEDED(hr) && !base_preview_media_type_) { hr = FindBaseMediaTypes(); } if (SUCCEEDED(hr)) { - hr = BuildMediaTypeForVideoPreview(base_preview_media_type, - &preview_media_type); + hr = BuildMediaTypeForVideoPreview(base_preview_media_type_.Get(), + preview_media_type.GetAddressOf()); } if (SUCCEEDED(hr)) { DWORD preview_sink_stream_index; hr = preview_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, - preview_media_type, nullptr, &preview_sink_stream_index); + preview_media_type.Get(), nullptr, &preview_sink_stream_index); if (SUCCEEDED(hr)) { - hr = preview_sink_->SetSampleCallback(preview_sink_stream_index, - capture_engine_callback_handler_); + hr = preview_sink_->SetSampleCallback( + preview_sink_stream_index, capture_engine_callback_handler_.Get()); } } if (FAILED(hr)) { - Release(&preview_sink_); + preview_sink_ = nullptr; } - Release(&capture_sink); return hr; } @@ -951,30 +921,31 @@ HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); if (FAILED(hr)) { - Release(&photo_sink_); + photo_sink_ = nullptr; } return hr; } - IMFMediaType *photo_media_type = nullptr; - IMFCaptureSink *capture_sink = nullptr; + ComPtr photo_media_type; + ComPtr capture_sink; // Get sink with photo type; hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, &capture_sink); if (SUCCEEDED(hr)) { - hr = capture_sink->QueryInterface(IID_PPV_ARGS(&photo_sink_)); + hr = capture_sink.As(&photo_sink_); } - if (SUCCEEDED(hr) && !base_capture_media_type) { + if (SUCCEEDED(hr) && !base_capture_media_type_) { hr = FindBaseMediaTypes(); } if (SUCCEEDED(hr)) { - hr = BuildMediaTypeForPhotoCapture( - base_capture_media_type, &photo_media_type, GUID_ContainerFormatJpeg); + hr = BuildMediaTypeForPhotoCapture(base_capture_media_type_.Get(), + photo_media_type.GetAddressOf(), + GUID_ContainerFormatJpeg); } if (SUCCEEDED(hr)) { @@ -986,7 +957,7 @@ HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { DWORD dwSinkStreamIndex; hr = photo_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, - photo_media_type, nullptr, &dwSinkStreamIndex); + photo_media_type.Get(), nullptr, &dwSinkStreamIndex); } if (SUCCEEDED(hr)) { @@ -994,12 +965,9 @@ HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { } if (FAILED(hr)) { - Release(&photo_sink_); + photo_sink_ = nullptr; } - Release(&capture_sink); - Release(&photo_media_type); - return hr; } @@ -1011,24 +979,24 @@ HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { hr = record_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); if (FAILED(hr)) { - Release(&record_sink_); + record_sink_ = nullptr; } return hr; } - IMFMediaType *video_record_media_type = nullptr; - IMFCaptureSink *capture_sink = nullptr; + ComPtr video_record_media_type; + ComPtr capture_sink; // Get sink with record type; hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, &capture_sink); if (SUCCEEDED(hr)) { - hr = capture_sink->QueryInterface(IID_PPV_ARGS(&record_sink_)); + hr = capture_sink.As(&record_sink_); } - if (SUCCEEDED(hr) && !base_capture_media_type) { + if (SUCCEEDED(hr) && !base_capture_media_type_) { hr = FindBaseMediaTypes(); } @@ -1038,27 +1006,31 @@ HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { } if (SUCCEEDED(hr)) { - hr = BuildMediaTypeForVideoCapture( - base_capture_media_type, &video_record_media_type, MFVideoFormat_H264); + hr = BuildMediaTypeForVideoCapture(base_capture_media_type_.Get(), + video_record_media_type.GetAddressOf(), + MFVideoFormat_H264); } if (SUCCEEDED(hr)) { DWORD video_record_sink_stream_index; hr = record_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, - video_record_media_type, nullptr, &video_record_sink_stream_index); + video_record_media_type.Get(), nullptr, + &video_record_sink_stream_index); } - IMFMediaType *audio_record_media_type = nullptr; + ComPtr audio_record_media_type; if (SUCCEEDED(hr) && enable_audio_record_) { HRESULT audio_capture_hr = S_OK; - audio_capture_hr = BuildMediaTypeForAudioCapture(&audio_record_media_type); + audio_capture_hr = + BuildMediaTypeForAudioCapture(audio_record_media_type.GetAddressOf()); if (SUCCEEDED(audio_capture_hr)) { DWORD audio_record_sink_stream_index; hr = record_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_AUDIO, - audio_record_media_type, nullptr, &audio_record_sink_stream_index); + audio_record_media_type.Get(), nullptr, + &audio_record_sink_stream_index); } } @@ -1067,12 +1039,9 @@ HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { } if (FAILED(hr)) { - Release(&record_sink_); + record_sink_ = nullptr; } - Release(&capture_sink); - Release(&video_record_media_type); - Release(&audio_record_media_type); return hr; } diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index b71172de32f1..724eef4a2ede 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,7 @@ #include "capture_engine_listener.h" namespace camera_windows { +using Microsoft::WRL::ComPtr; enum ResolutionPreset { /// AUTO @@ -52,16 +54,6 @@ enum RecordingType { RECORDING_TYPE_TIMED }; -template -void Release(T** ppT) { - static_assert(std::is_base_of::value, - "T must inherit from IUnknown"); - if (*ppT) { - (*ppT)->Release(); - *ppT = NULL; - } -} - class VideoCaptureDeviceEnumerator { protected: virtual bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, @@ -155,17 +147,17 @@ class CaptureControllerImpl : public CaptureController, // CaptureEngine objects bool capture_engine_initialization_pending_ = false; - IMFCaptureEngine* capture_engine_ = nullptr; - CaptureEngineListener* capture_engine_callback_handler_ = nullptr; + ComPtr capture_engine_; + ComPtr capture_engine_callback_handler_; - IMFDXGIDeviceManager* dxgi_device_manager_ = nullptr; - ID3D11Device* dx11_device_ = nullptr; + ComPtr dxgi_device_manager_; + ComPtr dx11_device_; // ID3D12Device* dx12_device_ = nullptr; UINT dx_device_reset_token_ = 0; // Sources - IMFMediaSource* video_source_ = nullptr; - IMFMediaSource* audio_source_ = nullptr; + ComPtr video_source_; + ComPtr audio_source_; // Texture int64_t texture_id_ = -1; @@ -185,8 +177,8 @@ class CaptureControllerImpl : public CaptureController, bool previewing_ = false; uint32_t preview_frame_width_ = 0; uint32_t preview_frame_height_ = 0; - IMFMediaType* base_preview_media_type = nullptr; - IMFCapturePreviewSink* preview_sink_ = nullptr; + ComPtr base_preview_media_type_; + ComPtr preview_sink_; // Photo / Record bool pending_image_capture_ = false; @@ -199,9 +191,9 @@ class CaptureControllerImpl : public CaptureController, uint32_t capture_frame_width_ = 0; uint32_t capture_frame_height_ = 0; - IMFMediaType* base_capture_media_type = nullptr; - IMFCapturePhotoSink* photo_sink_ = nullptr; - IMFCaptureRecordSink* record_sink_ = nullptr; + ComPtr base_capture_media_type_; + ComPtr photo_sink_; + ComPtr record_sink_; std::string pending_picture_path_ = ""; std::string pending_record_path_ = ""; From 0ee296fdf3e04ef02f025e2e2cdea7d6338a28d1 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 12 Jan 2022 10:46:57 +0200 Subject: [PATCH 09/93] [camera_windows] Code cleanup, fix memory leak --- .../windows/capture_controller.cpp | 757 +++++++++--------- 1 file changed, 391 insertions(+), 366 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 824f7b857043..0ffdf7e1923a 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -64,13 +64,13 @@ HRESULT BuildMediaTypeForVideoPreview(IMFMediaType *src_media_type, HRESULT hr = MFCreateMediaType(&new_media_type); - // First clone everything from original media type if (SUCCEEDED(hr)) { + // Clones everything from original media type. hr = src_media_type->CopyAllItems(new_media_type.Get()); } if (SUCCEEDED(hr)) { - // Change subtype to requested + // Changes subtype to MFVideoFormat_RGB32. hr = new_media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); } @@ -85,7 +85,7 @@ HRESULT BuildMediaTypeForVideoPreview(IMFMediaType *src_media_type, return hr; } -// Creates media type for photo capture for jpeg images +// Initializes media type for photo capture for jpeg images. HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, IMFMediaType **photo_media_type, GUID image_format) { @@ -94,8 +94,8 @@ HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, HRESULT hr = MFCreateMediaType(&new_media_type); - // First clone everything from original media type if (SUCCEEDED(hr)) { + // Clones everything from original media type. hr = src_media_type->CopyAllItems(new_media_type.Get()); } @@ -114,7 +114,7 @@ HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, return hr; } -// Creates media type for video capture +// Initializes media type for video capture. HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, IMFMediaType **video_record_media_type, GUID capture_format) { @@ -123,8 +123,8 @@ HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, HRESULT hr = MFCreateMediaType(&new_media_type); - // First clone everything from original media type if (SUCCEEDED(hr)) { + // Clones everything from original media type. hr = src_media_type->CopyAllItems(new_media_type.Get()); } @@ -139,7 +139,7 @@ HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, return hr; } -// Queries interface object from collection +// Queries interface object from collection. template HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, Q **ppObj) { @@ -151,19 +151,7 @@ HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, return hr; } -/* -template -HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, - Q **ppObj) { - IUnknown *pUnk; - HRESULT hr = pCollection->GetElement(index, &pUnk); - if (SUCCEEDED(hr)) { - hr = pUnk->QueryInterface(IID_PPV_ARGS(ppObj)); - pUnk->Release(); - } - return hr; -}*/ - +// Initializes media type for audo capture. HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { ComPtr audio_output_attributes; ComPtr src_media_type; @@ -174,7 +162,7 @@ HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { HRESULT hr = MFCreateAttributes(&audio_output_attributes, 1); if (SUCCEEDED(hr)) { - // Enumerate only low latency audio outputs + // Enumerates only low latency audio outputs. hr = audio_output_attributes->SetUINT32(MF_LOW_LATENCY, TRUE); } @@ -197,12 +185,12 @@ HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { } if (mt_count == 0) { - // No sources found + // No sources found, mark process as failure. hr = E_FAIL; } - // Create new media type to copy original media type to if (SUCCEEDED(hr)) { + // Create new media type to copy original media type to. hr = MFCreateMediaType(&new_media_type); } @@ -217,8 +205,8 @@ HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { return hr; } -// Uses first audio source to capture audio. Enumerating audio sources via -// platform interface is not supported. +// Uses first audio source to capture audio. +// Note: Enumerating audio sources via platform interface is not supported. HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { audio_source_ = nullptr; IMFActivate **devices = nullptr; @@ -338,32 +326,13 @@ HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { HRESULT CaptureControllerImpl::CreateCaptureEngine( const std::string &video_device_id) { HRESULT hr = S_OK; - IMFAttributes *attributes = nullptr; - IMFCaptureEngineClassFactory *capture_engine_factory = nullptr; - - if (!capture_engine_callback_handler_) { - capture_engine_callback_handler_ = - ComPtr(new CaptureEngineListener(this)); - } + ComPtr attributes; + ComPtr capture_engine_factory; if (SUCCEEDED(hr)) { hr = CreateD3DManagerWithDX11Device(); } - if (SUCCEEDED(hr)) { - hr = MFCreateAttributes(&attributes, 2); - } - - if (SUCCEEDED(hr)) { - hr = attributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, - dxgi_device_manager_.Get()); - } - - if (SUCCEEDED(hr)) { - hr = attributes->SetUINT32(MF_CAPTURE_ENGINE_USE_VIDEO_DEVICE_ONLY, - !enable_audio_record_); - } - if (SUCCEEDED(hr)) { hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, nullptr, CLSCTX_INPROC_SERVER, @@ -371,7 +340,7 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine( } if (SUCCEEDED(hr)) { - // Create CaptureEngine. + // Creates CaptureEngine. hr = capture_engine_factory->CreateInstance(CLSID_MFCaptureEngine, IID_PPV_ARGS(&capture_engine_)); } @@ -386,9 +355,27 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine( } } + if (!capture_engine_callback_handler_) { + capture_engine_callback_handler_ = + ComPtr(new CaptureEngineListener(this)); + } + if (SUCCEEDED(hr)) { + hr = MFCreateAttributes(&attributes, 2); + } + + if (SUCCEEDED(hr)) { + hr = attributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, + dxgi_device_manager_.Get()); + } + + if (SUCCEEDED(hr)) { + hr = attributes->SetUINT32(MF_CAPTURE_ENGINE_USE_VIDEO_DEVICE_ONLY, + !enable_audio_record_); + } + if (SUCCEEDED(hr)) { hr = capture_engine_->Initialize(capture_engine_callback_handler_.Get(), - attributes, audio_source_.Get(), + attributes.Get(), audio_source_.Get(), video_source_.Get()); } @@ -456,41 +443,6 @@ void CaptureControllerImpl::ResetCaptureEngineState() { texture_ = nullptr; } -uint8_t *CaptureControllerImpl::GetSourceBuffer(uint32_t current_length) { - if (this->source_buffer_data_ == nullptr || - this->source_buffer_size_ != current_length) { - // Update source buffer size - this->source_buffer_data_ = nullptr; - this->source_buffer_data_ = std::make_unique(current_length); - this->source_buffer_size_ = current_length; - } - return this->source_buffer_data_.get(); -} - -void CaptureControllerImpl::OnBufferUpdate() { - if (this->texture_registrar_ && this->texture_id_ >= 0) { - this->texture_registrar_->MarkTextureFrameAvailable(this->texture_id_); - } -} - -void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { - // Check if max_video_duration_ms is passed - if (recording_ && recording_type_ == RecordingType::RECORDING_TYPE_TIMED && - max_video_duration_ms_ > 0) { - if (record_start_timestamp_us_ < 0) { - record_start_timestamp_us_ = capture_time_us; - } - - recording_duration_us_ = (capture_time_us - record_start_timestamp_us_); - - if (!record_stop_pending_ && - recording_duration_us_ >= - (static_cast(max_video_duration_ms_) * 1000)) { - StopTimedRecord(); - } - } -} - void CaptureControllerImpl::CreateCaptureDevice( flutter::TextureRegistrar *texture_registrar, const std::string &device_id, bool enable_audio, ResolutionPreset resolution_preset) { @@ -579,301 +531,104 @@ void CaptureControllerImpl::TakePicture(const std::string filepath) { } } -void CaptureControllerImpl::OnPicture(bool success) { - if (capture_controller_listener_) { - if (success && !pending_picture_path_.empty()) { - capture_controller_listener_->OnPictureSuccess(pending_picture_path_); - } else { - capture_controller_listener_->OnPictureFailed("Failed to take picture"); - } +uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { + switch (resolution_preset_) { + case RESOLUTION_PRESET_LOW: + return 240; + break; + case RESOLUTION_PRESET_MEDIUM: + return 480; + break; + case RESOLUTION_PRESET_HIGH: + return 720; + break; + case RESOLUTION_PRESET_VERY_HIGH: + return 1080; + break; + case RESOLUTION_PRESET_ULTRA_HIGH: + return 2160; + break; + case RESOLUTION_PRESET_AUTO: + default: + // no limit + return 0xffffffff; + break; } - pending_image_capture_ = false; - pending_picture_path_ = std::string(); } -void CaptureControllerImpl::OnCaptureEngineInitialized(bool success) { - if (capture_controller_listener_) { - // Create flutter desktop pixelbuffer texture; - texture_ = - std::make_unique(flutter::PixelBufferTexture( - [this](size_t width, - size_t height) -> const FlutterDesktopPixelBuffer * { - return this->ConvertPixelBufferForFlutter(width, height); - })); - - auto new_texture_id = texture_registrar_->RegisterTexture(texture_.get()); - - if (new_texture_id >= 0) { - texture_id_ = new_texture_id; - capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id_); - initialized_ = true; - } else { - initialized_ = false; - } +HRESULT CaptureControllerImpl::FindBaseMediaTypes() { + if (!initialized_) { + return E_FAIL; } - capture_engine_initialization_pending_ = false; -} -void CaptureControllerImpl::OnCaptureEngineError() { - // TODO: detect error type and update state depending of error type, also send - // other than capture engine creation errors to separate error handler - if (capture_controller_listener_) { - capture_controller_listener_->OnCreateCaptureEngineFailed( - "Error while capturing device"); - } + ComPtr source; + HRESULT hr = capture_engine_->GetSource(&source); - initialized_ = false; - capture_engine_initialization_pending_ = false; -} + if (SUCCEEDED(hr)) { + ComPtr media_type; + uint32_t max_height = GetMaxPreviewHeight(); -void CaptureControllerImpl::OnPreviewStarted(bool success) { - if (capture_controller_listener_) { - if (success && preview_frame_width_ > 0 && preview_frame_height_ > 0) { - capture_controller_listener_->OnStartPreviewSucceeded( - preview_frame_width_, preview_frame_height_); - } else { - capture_controller_listener_->OnStartPreviewFailed( - "Failed to start preview"); - } - } + // Loop native media types + for (int i = 0;; i++) { + // Release media type if exists from previous loop; + media_type = nullptr; - // update state - preview_pending_ = false; - previewing_ = success; -}; + if (FAILED(source->GetAvailableDeviceMediaType( + (DWORD) + MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, + i, media_type.GetAddressOf()))) { + break; + } -void CaptureControllerImpl::OnPreviewStopped(bool success) { - // update state - previewing_ = false; -}; + uint32_t frame_width; + uint32_t frame_height; + if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, + &frame_width, &frame_height))) { + // Update media type for photo and record capture + if (capture_frame_width_ < frame_width || + capture_frame_height_ < frame_height) { + base_capture_media_type_ = media_type; -void CaptureControllerImpl::OnRecordStarted(bool success) { - if (capture_controller_listener_) { - if (success) { - capture_controller_listener_->OnStartRecordSucceeded(); - } else { - capture_controller_listener_->OnStartRecordFailed( - "Failed to start recording"); - } - } + capture_frame_width_ = frame_width; + capture_frame_height_ = frame_height; + } - // update state - record_start_pending_ = false; - recording_ = success; -}; + // Update media type for preview + if (frame_height <= max_height && + (preview_frame_width_ < frame_width || + preview_frame_height_ < frame_height)) { + base_preview_media_type_ = media_type; -void CaptureControllerImpl::OnRecordStopped(bool success) { - if (capture_controller_listener_) { - if (recording_type_ == RecordingType::RECORDING_TYPE_CONTINUOUS) { - if (success && !pending_record_path_.empty()) { - capture_controller_listener_->OnStopRecordSucceeded( - pending_record_path_); - } else { - capture_controller_listener_->OnStopRecordFailed( - "Failed to record video"); + preview_frame_width_ = frame_width; + preview_frame_height_ = frame_height; + } } - } else if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { - if (success && !pending_record_path_.empty()) { - capture_controller_listener_->OnVideoRecordedSuccess( - pending_record_path_, (recording_duration_us_ / 1000)); + } - } else { - capture_controller_listener_->OnVideoRecordedFailed( - "Failed to record video"); - } + if (base_preview_media_type_ && base_capture_media_type_) { + hr = S_OK; + } else { + hr = E_FAIL; } } - // update state - recording_ = false; - record_stop_pending_ = false; - recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; - pending_record_path_ = std::string(); + return hr; } -void CaptureControllerImpl::StartRecord(const std::string &filepath, - int64_t max_video_duration_ms) { - assert(capture_controller_listener_); +HRESULT CaptureControllerImpl::InitPreviewSink() { if (!initialized_) { - return capture_controller_listener_->OnStartRecordFailed( - "Capture not initialized"); - } else if (recording_) { - return capture_controller_listener_->OnStartRecordFailed( - "Already recording"); - } else if (record_start_pending_) { - return capture_controller_listener_->OnStartRecordFailed( - "Start record already requested"); + return E_FAIL; } - HRESULT hr = InitRecordSink(filepath); - - if (SUCCEEDED(hr)) { - recording_type_ = max_video_duration_ms < 0 - ? RecordingType::RECORDING_TYPE_CONTINUOUS - : RecordingType::RECORDING_TYPE_TIMED; - max_video_duration_ms_ = max_video_duration_ms; - record_start_timestamp_us_ = -1; - recording_duration_us_ = 0; - pending_record_path_ = filepath; - record_start_pending_ = true; - - // Request to start recording. - // Check MF_CAPTURE_ENGINE_RECORD_STARTED event with CaptureEngineListener - hr = capture_engine_->StartRecord(); - } - - if (FAILED(hr)) { - record_start_pending_ = false; - recording_ = false; - return capture_controller_listener_->OnStartRecordFailed( - "Failed to initialize video recording"); - } -} - -void CaptureControllerImpl::StopRecord() { - assert(capture_controller_listener_); - - if (!initialized_) { - return capture_controller_listener_->OnStopRecordFailed( - "Capture not initialized"); - } else if (!recording_ && !record_start_pending_) { - return capture_controller_listener_->OnStopRecordFailed("Not recording"); - } else if (record_stop_pending_) { - return capture_controller_listener_->OnStopRecordFailed( - "Stop already requested"); - } - - // Request to stop recording. - // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event with CaptureEngineListener - record_stop_pending_ = true; - HRESULT hr = capture_engine_->StopRecord(true, false); - - if (FAILED(hr)) { - record_stop_pending_ = false; - recording_ = false; - return capture_controller_listener_->OnStopRecordFailed( - "Failed to stop recording"); - } -} - -void CaptureControllerImpl::StopTimedRecord() { - assert(capture_controller_listener_); - if (!recording_ && record_stop_pending_ && - recording_type_ != RecordingType::RECORDING_TYPE_TIMED) { - return; - } - - // Request to stop recording. - // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event with CaptureEngineListener - record_stop_pending_ = true; - HRESULT hr = capture_engine_->StopRecord(true, false); - - if (FAILED(hr)) { - record_stop_pending_ = false; - recording_ = false; - return capture_controller_listener_->OnVideoRecordedFailed( - "Failed to record video"); - } -} - -uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { - switch (resolution_preset_) { - case RESOLUTION_PRESET_LOW: - return 240; - break; - case RESOLUTION_PRESET_MEDIUM: - return 480; - break; - case RESOLUTION_PRESET_HIGH: - return 720; - break; - case RESOLUTION_PRESET_VERY_HIGH: - return 1080; - break; - case RESOLUTION_PRESET_ULTRA_HIGH: - return 2160; - break; - case RESOLUTION_PRESET_AUTO: - default: - // no limit - return 0xffffffff; - break; - } -} - -HRESULT CaptureControllerImpl::FindBaseMediaTypes() { - if (!initialized_) { - return E_FAIL; - } - - ComPtr source; - HRESULT hr = capture_engine_->GetSource(&source); - - if (SUCCEEDED(hr)) { - ComPtr media_type; - uint32_t max_height = GetMaxPreviewHeight(); - - // Loop native media types - for (int i = 0;; i++) { - // Release media type if exists from previous loop; - media_type = nullptr; - - if (FAILED(source->GetAvailableDeviceMediaType( - (DWORD) - MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, - i, media_type.GetAddressOf()))) { - break; - } - - uint32_t frame_width; - uint32_t frame_height; - if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, - &frame_width, &frame_height))) { - // Update media type for photo and record capture - if (capture_frame_width_ < frame_width || - capture_frame_height_ < frame_height) { - base_capture_media_type_ = media_type; - - capture_frame_width_ = frame_width; - capture_frame_height_ = frame_height; - } - - // Update media type for preview - if (frame_height <= max_height && - (preview_frame_width_ < frame_width || - preview_frame_height_ < frame_height)) { - base_preview_media_type_ = media_type; - - preview_frame_width_ = frame_width; - preview_frame_height_ = frame_height; - } - } - } - - if (base_preview_media_type_ && base_capture_media_type_) { - hr = S_OK; - } else { - hr = E_FAIL; - } - } - - return hr; -} - -HRESULT CaptureControllerImpl::InitPreviewSink() { - if (!initialized_) { - return E_FAIL; - } - - HRESULT hr = S_OK; - if (preview_sink_) { - return hr; - } + HRESULT hr = S_OK; + if (preview_sink_) { + return hr; + } ComPtr preview_media_type; ComPtr capture_sink; - // Get sink with preview type; + // Get sink with preview type. hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, &capture_sink); @@ -917,7 +672,7 @@ HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { HRESULT hr = S_OK; if (photo_sink_) { - // If photo sink already exists, only update output filename + // If photo sink already exists, only update output filename. hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); if (FAILED(hr)) { @@ -930,7 +685,7 @@ HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { ComPtr photo_media_type; ComPtr capture_sink; - // Get sink with photo type; + // Gets sink with photo type. hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, &capture_sink); @@ -949,7 +704,7 @@ HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { } if (SUCCEEDED(hr)) { - // Remove existing streams if available + // Removes existing streams if available. hr = photo_sink_->RemoveAllStreams(); } @@ -975,7 +730,7 @@ HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { HRESULT hr = S_OK; if (record_sink_) { - // If record sink already exists, only update output filename + // If record sink already exists, only update output filename. hr = record_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); if (FAILED(hr)) { @@ -988,7 +743,7 @@ HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { ComPtr video_record_media_type; ComPtr capture_sink; - // Get sink with record type; + // Gets sink with record type. hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, &capture_sink); @@ -1001,7 +756,7 @@ HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { } if (SUCCEEDED(hr)) { - // Remove existing streams if available + // Removes existing streams if available. hr = record_sink_->RemoveAllStreams(); } @@ -1045,6 +800,91 @@ HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { return hr; } +void CaptureControllerImpl::StartRecord(const std::string &filepath, + int64_t max_video_duration_ms) { + assert(capture_controller_listener_); + if (!initialized_) { + return capture_controller_listener_->OnStartRecordFailed( + "Capture not initialized"); + } else if (recording_) { + return capture_controller_listener_->OnStartRecordFailed( + "Already recording"); + } else if (record_start_pending_) { + return capture_controller_listener_->OnStartRecordFailed( + "Start record already requested"); + } + + HRESULT hr = InitRecordSink(filepath); + + if (SUCCEEDED(hr)) { + recording_type_ = max_video_duration_ms < 0 + ? RecordingType::RECORDING_TYPE_CONTINUOUS + : RecordingType::RECORDING_TYPE_TIMED; + max_video_duration_ms_ = max_video_duration_ms; + record_start_timestamp_us_ = -1; + recording_duration_us_ = 0; + pending_record_path_ = filepath; + record_start_pending_ = true; + + // Request to start recording. + // Check MF_CAPTURE_ENGINE_RECORD_STARTED event with CaptureEngineListener + hr = capture_engine_->StartRecord(); + } + + if (FAILED(hr)) { + record_start_pending_ = false; + recording_ = false; + return capture_controller_listener_->OnStartRecordFailed( + "Failed to initialize video recording"); + } +} + +void CaptureControllerImpl::StopRecord() { + assert(capture_controller_listener_); + + if (!initialized_) { + return capture_controller_listener_->OnStopRecordFailed( + "Capture not initialized"); + } else if (!recording_ && !record_start_pending_) { + return capture_controller_listener_->OnStopRecordFailed("Not recording"); + } else if (record_stop_pending_) { + return capture_controller_listener_->OnStopRecordFailed( + "Stop already requested"); + } + + // Request to stop recording. + // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event with CaptureEngineListener + record_stop_pending_ = true; + HRESULT hr = capture_engine_->StopRecord(true, false); + + if (FAILED(hr)) { + record_stop_pending_ = false; + recording_ = false; + return capture_controller_listener_->OnStopRecordFailed( + "Failed to stop recording"); + } +} + +void CaptureControllerImpl::StopTimedRecord() { + assert(capture_controller_listener_); + if (!recording_ && record_stop_pending_ && + recording_type_ != RecordingType::RECORDING_TYPE_TIMED) { + return; + } + + // Request to stop recording. + // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event with CaptureEngineListener + record_stop_pending_ = true; + HRESULT hr = capture_engine_->StopRecord(true, false); + + if (FAILED(hr)) { + record_stop_pending_ = false; + recording_ = false; + return capture_controller_listener_->OnVideoRecordedFailed( + "Failed to record video"); + } +} + void CaptureControllerImpl::StartPreview() { assert(capture_controller_listener_); @@ -1057,8 +897,7 @@ void CaptureControllerImpl::StartPreview() { if (SUCCEEDED(hr)) { preview_pending_ = true; - // Request to start preview. - // Check MF_CAPTURE_ENGINE_PREVIEW_STARTED event with CaptureEngineListener + // Requests to start preview. hr = capture_engine_->StartPreview(); } @@ -1077,8 +916,7 @@ void CaptureControllerImpl::StopPreview() { return capture_controller_listener_->OnPausePreviewFailed("Not previewing"); } - // Request to stop preview. - // Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event with CaptureEngineListener + // Requests to stop preview. HRESULT hr = capture_engine_->StopPreview(); if (FAILED(hr)) { @@ -1090,15 +928,202 @@ void CaptureControllerImpl::StopPreview() { void CaptureControllerImpl::PausePreview() { if (!previewing_) { preview_paused_ = false; - return capture_controller_listener_->OnPausePreviewFailed( - "Preview not started"); + + if (capture_controller_listener_) { + return capture_controller_listener_->OnPausePreviewFailed( + "Preview not started"); + } } preview_paused_ = true; - capture_controller_listener_->OnPausePreviewSucceeded(); + + if (capture_controller_listener_) { + capture_controller_listener_->OnPausePreviewSucceeded(); + } } void CaptureControllerImpl::ResumePreview() { preview_paused_ = false; - capture_controller_listener_->OnResumePreviewSucceeded(); + if (capture_controller_listener_) { + capture_controller_listener_->OnResumePreviewSucceeded(); + } +} + +// Handles Picture event and informs CaptureControllerListener. +// Called via IMFCaptureEngineOnEventCallback implementation. +// Implements CaptureEngineObserver::OnPicture. +void CaptureControllerImpl::OnPicture(bool success) { + if (capture_controller_listener_) { + if (success && !pending_picture_path_.empty()) { + capture_controller_listener_->OnPictureSuccess(pending_picture_path_); + } else { + capture_controller_listener_->OnPictureFailed("Failed to take picture"); + } + } + pending_image_capture_ = false; + pending_picture_path_ = std::string(); +} + +// Handles CaptureEngineInitialized event and informs CaptureControllerListener. +// Called via IMFCaptureEngineOnEventCallback implementation. +// Implements CaptureEngineObserver::OnCaptureEngineInitialized. +void CaptureControllerImpl::OnCaptureEngineInitialized(bool success) { + if (capture_controller_listener_) { + // Create flutter desktop pixelbuffer texture; + texture_ = + std::make_unique(flutter::PixelBufferTexture( + [this](size_t width, + size_t height) -> const FlutterDesktopPixelBuffer * { + return this->ConvertPixelBufferForFlutter(width, height); + })); + + auto new_texture_id = texture_registrar_->RegisterTexture(texture_.get()); + + if (new_texture_id >= 0) { + texture_id_ = new_texture_id; + capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id_); + initialized_ = true; + } else { + initialized_ = false; + } + } + capture_engine_initialization_pending_ = false; +} + +// Handles CaptureEngineError event and informs CaptureControllerListener. +// Called via IMFCaptureEngineOnEventCallback implementation. +// Implements CaptureEngineObserver::OnCaptureEngineError. +void CaptureControllerImpl::OnCaptureEngineError() { + // TODO: detect error type and update state depending of error type, also send + // other than capture engine creation errors to separate error handler + if (capture_controller_listener_) { + capture_controller_listener_->OnCreateCaptureEngineFailed( + "Error while capturing device"); + } + + initialized_ = false; + capture_engine_initialization_pending_ = false; +} + +// Handles PreviewStarted event and informs CaptureControllerListener. +// Called via IMFCaptureEngineOnEventCallback implementation. +// Implements CaptureEngineObserver::OnPreviewStarted. +void CaptureControllerImpl::OnPreviewStarted(bool success) { + if (capture_controller_listener_) { + if (success && preview_frame_width_ > 0 && preview_frame_height_ > 0) { + capture_controller_listener_->OnStartPreviewSucceeded( + preview_frame_width_, preview_frame_height_); + } else { + capture_controller_listener_->OnStartPreviewFailed( + "Failed to start preview"); + } + } + + // update state + preview_pending_ = false; + previewing_ = success; +}; + +// Handles PreviewStopped event. +// Called via IMFCaptureEngineOnEventCallback implementation. +// Implements CaptureEngineObserver::OnPreviewStopped. +void CaptureControllerImpl::OnPreviewStopped(bool success) { + // update state + previewing_ = false; +}; + +// Handles RecordStarted event and informs CaptureControllerListener. +// Called via IMFCaptureEngineOnEventCallback implementation. +// Implements CaptureEngineObserver::OnRecordStarted. +void CaptureControllerImpl::OnRecordStarted(bool success) { + if (capture_controller_listener_) { + if (success) { + capture_controller_listener_->OnStartRecordSucceeded(); + } else { + capture_controller_listener_->OnStartRecordFailed( + "Failed to start recording"); + } + } + + // update state + record_start_pending_ = false; + recording_ = success; +}; + +// Handles RecordStopped event and informs CaptureControllerListener. +// Called via IMFCaptureEngineOnEventCallback implementation. +// Implements CaptureEngineObserver::OnRecordStopped. +void CaptureControllerImpl::OnRecordStopped(bool success) { + if (capture_controller_listener_) { + if (recording_type_ == RecordingType::RECORDING_TYPE_CONTINUOUS) { + if (success && !pending_record_path_.empty()) { + capture_controller_listener_->OnStopRecordSucceeded( + pending_record_path_); + } else { + capture_controller_listener_->OnStopRecordFailed( + "Failed to record video"); + } + } else if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { + if (success && !pending_record_path_.empty()) { + capture_controller_listener_->OnVideoRecordedSuccess( + pending_record_path_, (recording_duration_us_ / 1000)); + + } else { + capture_controller_listener_->OnVideoRecordedFailed( + "Failed to record video"); + } + } + } + + // update state + recording_ = false; + record_stop_pending_ = false; + recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; + pending_record_path_ = std::string(); +} + +// Returns pointer to databuffer. +// Called via IMFCaptureEngineOnSampleCallback implementation. +// Implements CaptureEngineObserver::GetSourceBuffer. +uint8_t *CaptureControllerImpl::GetSourceBuffer(uint32_t current_length) { + if (this->source_buffer_data_ == nullptr || + this->source_buffer_size_ != current_length) { + // Update source buffer size. + this->source_buffer_data_ = nullptr; + this->source_buffer_data_ = std::make_unique(current_length); + this->source_buffer_size_ = current_length; + } + return this->source_buffer_data_.get(); +} + +// Marks texture frame available after buffer is updated. +// Called via IMFCaptureEngineOnSampleCallback implementation. +// Implements CaptureEngineObserver::OnBufferUpdate. +void CaptureControllerImpl::OnBufferUpdate() { + if (this->texture_registrar_ && this->texture_id_ >= 0) { + this->texture_registrar_->MarkTextureFrameAvailable(this->texture_id_); + } } + +// Handles capture time update from each processed frame. +// Stops timed recordings if requested recording duration has passed. +// Called via IMFCaptureEngineOnSampleCallback implementation. +// Implements CaptureEngineObserver::UpdateCaptureTime. +void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { + // Checks if max_video_duration_ms is passed. + if (recording_ && recording_type_ == RecordingType::RECORDING_TYPE_TIMED && + max_video_duration_ms_ > 0) { + if (record_start_timestamp_us_ < 0) { + record_start_timestamp_us_ = capture_time_us; + } + + recording_duration_us_ = (capture_time_us - record_start_timestamp_us_); + + if (!record_stop_pending_ && + recording_duration_us_ >= + (static_cast(max_video_duration_ms_) * 1000)) { + StopTimedRecord(); + } + } +} + } // namespace camera_windows From 566407799040a305472c3c8783c2d609a0c29b04 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 13 Jan 2022 12:55:23 +0200 Subject: [PATCH 10/93] [camera_windows] Improve event and error handling - Moved event handling to the capture_controller - When camera is detached - When camera is already in use --- .../camera_windows/example/lib/main.dart | 125 +++++++--- .../camera_windows/lib/camera_windows.dart | 82 ++++--- .../camera/camera_windows/windows/camera.cpp | 51 ++-- .../camera/camera_windows/windows/camera.h | 5 +- .../camera_windows/windows/camera_plugin.cpp | 68 +++--- .../windows/capture_controller.cpp | 219 ++++++++++++------ .../windows/capture_controller.h | 27 +-- .../windows/capture_controller_listener.h | 2 + .../windows/capture_engine_listener.cpp | 84 ++----- .../windows/capture_engine_listener.h | 13 +- .../camera_windows/windows/test/mocks.h | 1 + 11 files changed, 406 insertions(+), 271 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index d21357a5acda..0a01e11c3fe3 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/material.dart'; @@ -27,7 +28,9 @@ class _MyAppState extends State { bool _recording = false; bool _recordingTimed = false; bool _recordAudio = true; + bool _previewPaused = false; Size? _previewSize; + StreamSubscription? _errorStreamSubscription; @override void initState() { @@ -36,6 +39,14 @@ class _MyAppState extends State { getAvailableCameras(); } + @override + void dispose() { + disposeCurrentCamera(); + _errorStreamSubscription?.cancel(); + _errorStreamSubscription = null; + super.dispose(); + } + // Fetches list of available cameras from camera_windows plugin Future getAvailableCameras() async { String cameraInfo; @@ -66,26 +77,28 @@ class _MyAppState extends State { Future initializeFirstCamera() async { assert(_cameras.isNotEmpty); assert(!_initialized); + final Completer _initializeCompleter = + Completer(); + int cameraId = -1; try { - final Completer _initializeCompleter = - Completer(); - final CameraDescription camera = _cameras.first; - final int cameraId = await CameraPlatform.instance.createCamera( + cameraId = await CameraPlatform.instance.createCamera( camera, ResolutionPreset.veryHigh, enableAudio: _recordAudio, ); - unawaited( - CameraPlatform.instance - .onCameraInitialized(cameraId) - .first - .then((CameraInitializedEvent event) { - _initializeCompleter.complete(event); - }), - ); + _errorStreamSubscription?.cancel(); + _errorStreamSubscription = + CameraPlatform.instance.onCameraError(cameraId).listen(OnCameraError); + + unawaited(CameraPlatform.instance + .onCameraInitialized(cameraId) + .first + .then((CameraInitializedEvent event) { + _initializeCompleter.complete(event); + })); await CameraPlatform.instance.initializeCamera( cameraId, @@ -104,11 +117,24 @@ class _MyAppState extends State { _cameraId = cameraId; _cameraInfo = 'Capturing camera: ${camera.name}'; }); - } on PlatformException catch (e) { + } on CameraException catch (e) { + try { + if (cameraId >= 0) { + await CameraPlatform.instance.dispose(cameraId); + } + } on CameraException catch (e) { + debugPrint('Failed to dispose camera: ${e.code}: ${e.description}'); + } + //Reset state setState(() { _initialized = false; _cameraId = -1; - _cameraInfo = 'Failed to initialize camera: ${e.code}: ${e.message}'; + _cameraInfo = 'Camera disposed'; + _previewSize = null; + _recording = false; + _recordingTimed = false; + _cameraInfo = + 'Failed to initialize camera: ${e.code}: ${e.description}'; }); } } @@ -127,9 +153,9 @@ class _MyAppState extends State { _recordingTimed = false; }); getAvailableCameras(); - } on PlatformException catch (e) { + } on CameraException catch (e) { setState(() { - _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.message}'; + _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.description}'; }); } } @@ -143,6 +169,8 @@ class _MyAppState extends State { if (!await launch('file:${_file.path}')) { throw 'Could not open file: "${_file.path}"'; } + + showInSnackBar('Picture captured to: ${_file.path}'); } Future recordTimed(int seconds) async { @@ -158,6 +186,7 @@ class _MyAppState extends State { if (!await launch('file:${event.file.path}')) { throw 'Could not open file: "${event.file.path}"'; } + showInSnackBar('Video captured to: ${event.file.path}'); } }); @@ -183,6 +212,7 @@ class _MyAppState extends State { if (!await launch('file:${_file.path}')) { throw 'Could not open file: "${_file.path}"'; } + showInSnackBar('Video captured to: ${_file.path}'); } setState(() { _recording = !_recording; @@ -190,9 +220,36 @@ class _MyAppState extends State { } } + Future togglePreview() async { + if (_initialized && _cameraId > 0) { + if (!_previewPaused) { + await CameraPlatform.instance.pausePreview(_cameraId); + } else { + await CameraPlatform.instance.resumePreview(_cameraId); + } + setState(() { + _previewPaused = !_previewPaused; + }); + } + } + + void OnCameraError(CameraErrorEvent event) { + scaffoldMessengerKey.currentState + ?.showSnackBar(SnackBar(content: Text('Error: ${event.description}'))); + } + + void showInSnackBar(String message) { + scaffoldMessengerKey.currentState + ?.showSnackBar(SnackBar(content: Text(message))); + } + + final GlobalKey scaffoldMessengerKey = + GlobalKey(); + @override Widget build(BuildContext context) { return MaterialApp( + scaffoldMessengerKey: scaffoldMessengerKey, home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), @@ -206,10 +263,27 @@ class _MyAppState extends State { ), child: Text(_cameraInfo), ), + if (_cameras.isEmpty) + ElevatedButton( + onPressed: getAvailableCameras, + child: const Text('Re-check available cameras'), + ), if (_cameras.isNotEmpty) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ + const Text( + 'Audio:', + ), + Switch( + value: _recordAudio, + onChanged: !_initialized + ? (bool state) => setState(() { + _recordAudio = state; + }) + : null, + ), + const SizedBox(width: 20), ElevatedButton( onPressed: _initialized ? disposeCurrentCamera @@ -223,6 +297,13 @@ class _MyAppState extends State { child: const Text('Take picture'), ), const SizedBox(width: 5), + ElevatedButton( + onPressed: _initialized ? togglePreview : null, + child: Text( + _previewPaused ? 'Resume preview' : 'Pause preview', + ), + ), + const SizedBox(width: 5), ElevatedButton( onPressed: (_initialized && !_recordingTimed) ? toggleRecord @@ -240,18 +321,6 @@ class _MyAppState extends State { 'Record 5 seconds', ), ), - const SizedBox(width: 20), - const Text( - 'Audio:', - ), - Switch( - value: _recordAudio, - onChanged: !_initialized - ? (bool state) => setState(() { - _recordAudio = state; - }) - : null, - ), ], ), const SizedBox(height: 5), diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 2cbd7bcfd0a3..ccac20a1ac2c 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -108,13 +108,17 @@ class CameraWindows extends CameraPlatform { return channel; }); - final Map? reply = - await _channel.invokeMapMethod( - 'initialize', - { - 'cameraId': requestedCameraId, - }, - ); + final Map? reply; + try { + reply = await _channel.invokeMapMethod( + 'initialize', + { + 'cameraId': requestedCameraId, + }, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } if (reply != null && reply.containsKey('previewWidth') && @@ -146,10 +150,14 @@ class CameraWindows extends CameraPlatform { _channels.remove(cameraId); } - await _channel.invokeMethod( - 'dispose', - {'cameraId': cameraId}, - ); + try { + await _channel.invokeMethod( + 'dispose', + {'cameraId': cameraId}, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } } @override @@ -202,10 +210,15 @@ class CameraWindows extends CameraPlatform { @override Future takePicture(int cameraId) async { - final String? path = await _channel.invokeMethod( - 'takePicture', - {'cameraId': cameraId}, - ); + final String? path; + try { + path = await _channel.invokeMethod( + 'takePicture', + {'cameraId': cameraId}, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } if (path == null) { throw CameraException( @@ -218,29 +231,44 @@ class CameraWindows extends CameraPlatform { } @override - Future prepareForVideoRecording() => + Future prepareForVideoRecording() async { + try { _channel.invokeMethod('prepareForVideoRecording'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } @override Future startVideoRecording( int cameraId, { Duration? maxVideoDuration, }) async { - await _channel.invokeMethod( - 'startVideoRecording', - { - 'cameraId': cameraId, - 'maxVideoDuration': maxVideoDuration?.inMilliseconds, - }, - ); + try { + await _channel.invokeMethod( + 'startVideoRecording', + { + 'cameraId': cameraId, + 'maxVideoDuration': maxVideoDuration?.inMilliseconds, + }, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } } @override Future stopVideoRecording(int cameraId) async { - final String? path = await _channel.invokeMethod( - 'stopVideoRecording', - {'cameraId': cameraId}, - ); + final String? path; + + try { + path = await _channel.invokeMethod( + 'stopVideoRecording', + {'cameraId': cameraId}, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } if (path == null) { throw CameraException( diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index aa65090cbc03..ca5dd96e6735 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -13,6 +13,7 @@ namespace { // Camera channel events const char kCameraMethodChannelBaseName[] = "flutter.io/cameraPlugin/camera"; const char kVideoRecordedEvent[] = "video_recorded"; +const char kErrorEvent[] = "error"; // Helper function for creating messaging channel for camera std::unique_ptr> BuildChannelForCamera( @@ -33,7 +34,9 @@ CameraImpl::CameraImpl(const std::string &device_id) Camera(device_id) {} CameraImpl::~CameraImpl() { capture_controller_ = nullptr; - ClearPendingResults(); + + SendErrorForPendingResults("Plugin disposed", + "Plugin disposed before request was handled"); } void CameraImpl::InitCamera(flutter::TextureRegistrar *texture_registrar, @@ -100,22 +103,12 @@ bool CameraImpl::HasPendingResultByType(PendingResultType type) { return it->second != nullptr; } -void CameraImpl::ClearPendingResultByType(PendingResultType type) { - auto pending_result = GetPendingResultByType(type); - if (pending_result) { - pending_result->Error("Plugin disposed", - "Plugin disposed before request was handled"); +void CameraImpl::SendErrorForPendingResults(const std::string &error_id, + const std::string &descripion) { + for (const auto &pending_result : pending_results_) { + std::move(pending_result.second)->Error(error_id, descripion); } -} - -void CameraImpl::ClearPendingResults() { - ClearPendingResultByType(PendingResultType::CREATE_CAMERA); - ClearPendingResultByType(PendingResultType::INITIALIZE); - ClearPendingResultByType(PendingResultType::PAUSE_PREVIEW); - ClearPendingResultByType(PendingResultType::RESUME_PREVIEW); - ClearPendingResultByType(PendingResultType::START_RECORD); - ClearPendingResultByType(PendingResultType::STOP_RECORD); - ClearPendingResultByType(PendingResultType::TAKE_PICTURE); + pending_results_.clear(); } // TODO: Create common base handler function for alll success and error cases @@ -136,7 +129,7 @@ void CameraImpl::OnCreateCaptureEngineFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::CREATE_CAMERA); if (pending_result) { - pending_result->Error("Failed to create camera", error); + pending_result->Error("camera_error", error); } } @@ -155,7 +148,7 @@ void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { void CameraImpl::OnStartPreviewFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); if (pending_result) { - pending_result->Error("Failed to initialize", error); + pending_result->Error("camera_error", error); } }; @@ -173,7 +166,7 @@ void CameraImpl::OnResumePreviewFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::RESUME_PREVIEW); if (pending_result) { - pending_result->Error("Failed to resume preview", error); + pending_result->Error("camera_error", error); } } @@ -191,7 +184,7 @@ void CameraImpl::OnPausePreviewFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); if (pending_result) { - pending_result->Error("Failed to pause preview", error); + pending_result->Error("camera_error", error); } } @@ -207,7 +200,7 @@ void CameraImpl::OnStartRecordSucceeded() { void CameraImpl::OnStartRecordFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); if (pending_result) { - pending_result->Error("Failed to start recording", error); + pending_result->Error("camera_error", error); } }; @@ -223,7 +216,7 @@ void CameraImpl::OnStopRecordSucceeded(const std::string &filepath) { void CameraImpl::OnStopRecordFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); if (pending_result) { - pending_result->Error("Failed to stop recording", error); + pending_result->Error("camera_error", error); } }; @@ -240,7 +233,7 @@ void CameraImpl::OnPictureFailed(const std::string &error) { auto pending_take_picture_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); if (pending_take_picture_result) { - pending_take_picture_result->Error("Failed to take picture", error); + pending_take_picture_result->Error("camera_error", error); } }; @@ -263,4 +256,16 @@ void CameraImpl::OnVideoRecordedSuccess(const std::string &filepath, // From CaptureControllerListener void CameraImpl::OnVideoRecordedFailed(const std::string &error){}; +void CameraImpl::OnCaptureError(const std::string &error) { + if (messenger_ && camera_id_ >= 0) { + auto channel = BuildChannelForCamera(messenger_, camera_id_); + std::unique_ptr message_data = + std::make_unique(EncodableMap( + {{EncodableValue("description"), EncodableValue(error)}})); + channel->InvokeMethod(kErrorEvent, std::move(message_data)); + } + + SendErrorForPendingResults("capture_error", error); +} + } // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 7010b6a85fbb..913b94b0ecf7 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -78,6 +78,7 @@ class CameraImpl : public Camera { void OnVideoRecordedSuccess(const std::string &filepath, int64_t video_duration) override; void OnVideoRecordedFailed(const std::string &error) override; + void OnCaptureError(const std::string &error) override; // From Camera @@ -118,8 +119,8 @@ class CameraImpl : public Camera { std::map>> pending_results_; std::unique_ptr> GetPendingResultByType( PendingResultType type); - void ClearPendingResultByType(PendingResultType type); - void ClearPendingResults(); + void SendErrorForPendingResults(const std::string &error_id, + const std::string &descripion); }; class CameraFactory { diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 7131c5d71248..949ac35d26c7 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::CreateMethodHandler( const auto *enable_audio = std::get_if(ValueOrNull(args, kEnableAudioKey)); if (!enable_audio) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kEnableAudioKey) + " argument missing"); } @@ -354,20 +354,20 @@ void CameraPlugin::CreateMethodHandler( const auto *camera_name = std::get_if(ValueOrNull(args, kCameraNameKey)); if (!camera_name) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kCameraNameKey) + " argument missing"); } auto device_info = ParseDeviceInfoFromCameraName(*camera_name); if (!device_info) { return result->Error( - "Camera error", "Cannot parse argument " + std::string(kCameraNameKey)); + "camera_error", "Cannot parse argument " + std::string(kCameraNameKey)); } if (GetCameraByDeviceId(device_info->device_id)) { - return result->Error( - "Camera error", - "Camera with given " + std::string(*camera_name) + " already exists"); + return result->Error("camera_error", + "Camera with given device id already exists. Existing " + "camera must be disposed before creating it again."); } std::unique_ptr camera = @@ -375,8 +375,8 @@ void CameraPlugin::CreateMethodHandler( if (camera->HasPendingResultByType(PendingResultType::CREATE_CAMERA)) { // This should never happen - return result->Error("Failed to create camera", - "Pending camera creation already exists"); + return result->Error("camera_error", + "Pending camera creation request exists"); } if (camera->AddPendingResult(PendingResultType::CREATE_CAMERA, @@ -401,18 +401,18 @@ void CameraPlugin::InitializeMethodHandler( const EncodableMap &args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { - return result->Error("Camera not created", "Please create camera first"); + return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::INITIALIZE)) { - return result->Error("Failed to initialize", - "Initialize method already called"); + return result->Error("camera_error", + "Pending initialization request exists"); } if (camera->AddPendingResult(PendingResultType::INITIALIZE, @@ -427,18 +427,18 @@ void CameraPlugin::PausePreviewMethodHandler( const EncodableMap &args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { - return result->Error("Camera not created", "Please create camera first"); + return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::PAUSE_PREVIEW)) { - return result->Error("Failed to initialize", - "Pause preview method already called"); + return result->Error("camera_error", + "Pending pause preview request exists"); } if (camera->AddPendingResult(PendingResultType::PAUSE_PREVIEW, @@ -455,18 +455,18 @@ void CameraPlugin::ResumePreviewMethodHandler( const EncodableMap &args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { - return result->Error("Camera not created", "Please create camera first"); + return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::RESUME_PREVIEW)) { - return result->Error("Failed to initialize", - "Resume preview method already called"); + return result->Error("camera_error", + "Pending resume preview request exists"); } if (camera->AddPendingResult(PendingResultType::RESUME_PREVIEW, @@ -483,18 +483,18 @@ void CameraPlugin::StartVideoRecordingMethodHandler( const EncodableMap &args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { - return result->Error("Camera not created", "Please create camera first"); + return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::START_RECORD)) { - return result->Error("Failed to start video recording", - "Video recording starting already"); + return result->Error("camera_error", + "Pending start recording request exists"); } // Get max video duration @@ -516,7 +516,7 @@ void CameraPlugin::StartVideoRecordingMethodHandler( cc->StartRecord(str_path, max_video_duration_ms); } } else { - return result->Error("System error", + return result->Error("system_error", "Failed to get path for video capture"); } } @@ -525,18 +525,18 @@ void CameraPlugin::StopVideoRecordingMethodHandler( const EncodableMap &args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { - return result->Error("Camera not created", "Please create camera first"); + return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::STOP_RECORD)) { - return result->Error("Failed to stop video recording", - "Video recording stopping already"); + return result->Error("camera_error", + "Pending stop recording request exists"); } if (camera->AddPendingResult(PendingResultType::STOP_RECORD, @@ -551,17 +551,17 @@ void CameraPlugin::TakePictureMethodHandler( const EncodableMap &args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { - return result->Error("Camera not created", "Please create camera first"); + return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::TAKE_PICTURE)) { - return result->Error("Taking picture failed", "Picture already requested"); + return result->Error("camera_error", "Pending take picture request exists"); } std::string path; @@ -573,7 +573,7 @@ void CameraPlugin::TakePictureMethodHandler( cc->TakePicture(path); } } else { - return result->Error("Taking picture failed", "Failed to get proper path"); + return result->Error("system_error", "Failed to get capture path for picture"); } } @@ -581,7 +581,7 @@ void CameraPlugin::DisposeMethodHandler( const EncodableMap &args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { - return result->Error("Argument error", + return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 0ffdf7e1923a..831b534d3245 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "string_utils.h" @@ -34,7 +35,7 @@ CaptureControllerImpl::CaptureControllerImpl( : capture_controller_listener_(listener), CaptureController(){}; CaptureControllerImpl::~CaptureControllerImpl() { - ResetCaptureEngineState(); + ResetCaptureController(); capture_controller_listener_ = nullptr; }; @@ -295,6 +296,7 @@ HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( // TODO: If DX12 device can be used with flutter: // Separate CreateD3DManagerWithDX12Device functionality // can be written if needed +// TODO: Should shared ANGLE device be used? HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { HRESULT hr = S_OK; hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, @@ -323,30 +325,35 @@ HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { return hr; } -HRESULT CaptureControllerImpl::CreateCaptureEngine( - const std::string &video_device_id) { +HRESULT CaptureControllerImpl::CreateCaptureEngine() { + assert(!video_device_id_.empty()); + HRESULT hr = S_OK; ComPtr attributes; - ComPtr capture_engine_factory; - if (SUCCEEDED(hr)) { - hr = CreateD3DManagerWithDX11Device(); - } + // Creates capture engine only if not already initialized by test framework + if (!capture_engine_) { + ComPtr capture_engine_factory; - if (SUCCEEDED(hr)) { - hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&capture_engine_factory)); + if (SUCCEEDED(hr)) { + hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&capture_engine_factory)); + } + + if (SUCCEEDED(hr)) { + // Creates CaptureEngine. + hr = capture_engine_factory->CreateInstance( + CLSID_MFCaptureEngine, IID_PPV_ARGS(&capture_engine_)); + } } if (SUCCEEDED(hr)) { - // Creates CaptureEngine. - hr = capture_engine_factory->CreateInstance(CLSID_MFCaptureEngine, - IID_PPV_ARGS(&capture_engine_)); + hr = CreateD3DManagerWithDX11Device(); } if (SUCCEEDED(hr)) { - hr = CreateVideoCaptureSourceForDevice(video_device_id); + hr = CreateVideoCaptureSourceForDevice(video_device_id_); } if (enable_audio_record_) { @@ -382,7 +389,7 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine( return hr; } -void CaptureControllerImpl::ResetCaptureEngineState() { +void CaptureControllerImpl::ResetCaptureController() { initialized_ = false; if (previewing_) { StopPreview(); @@ -456,20 +463,18 @@ void CaptureControllerImpl::CreateCaptureDevice( "Capture device already initializing"); } - // Reset current capture engine state before creating new capture engine; - ResetCaptureEngineState(); - capture_engine_initialization_pending_ = true; resolution_preset_ = resolution_preset; enable_audio_record_ = enable_audio; texture_registrar_ = texture_registrar; + video_device_id_ = device_id; - HRESULT hr = CreateCaptureEngine(device_id); + HRESULT hr = CreateCaptureEngine(); if (FAILED(hr)) { capture_controller_listener_->OnCreateCaptureEngineFailed( "Failed to create camera"); - ResetCaptureEngineState(); + ResetCaptureController(); return; } } @@ -800,12 +805,14 @@ HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { return hr; } +// Starts recording. +// Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response process. void CaptureControllerImpl::StartRecord(const std::string &filepath, int64_t max_video_duration_ms) { assert(capture_controller_listener_); if (!initialized_) { return capture_controller_listener_->OnStartRecordFailed( - "Capture not initialized"); + "Camera not initialized. Camera should be disposed and reinitialized."); } else if (recording_) { return capture_controller_listener_->OnStartRecordFailed( "Already recording"); @@ -839,12 +846,14 @@ void CaptureControllerImpl::StartRecord(const std::string &filepath, } } +// Stops recording. +// Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process. void CaptureControllerImpl::StopRecord() { assert(capture_controller_listener_); if (!initialized_) { return capture_controller_listener_->OnStopRecordFailed( - "Capture not initialized"); + "Camera not initialized. Camera should be disposed and reinitialized."); } else if (!recording_ && !record_start_pending_) { return capture_controller_listener_->OnStopRecordFailed("Not recording"); } else if (record_stop_pending_) { @@ -853,7 +862,6 @@ void CaptureControllerImpl::StopRecord() { } // Request to stop recording. - // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event with CaptureEngineListener record_stop_pending_ = true; HRESULT hr = capture_engine_->StopRecord(true, false); @@ -865,6 +873,8 @@ void CaptureControllerImpl::StopRecord() { } } +// Stops timed recording. Called internally when requested time is passed. +// Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process. void CaptureControllerImpl::StopTimedRecord() { assert(capture_controller_listener_); if (!recording_ && record_stop_pending_ && @@ -885,11 +895,19 @@ void CaptureControllerImpl::StopTimedRecord() { } } +// Starts capturing preview frames using preview sink +// After first frame is captured, OnPreviewStarted is called void CaptureControllerImpl::StartPreview() { assert(capture_controller_listener_); - if (!initialized_ || previewing_) { - return OnPreviewStarted(false); + if (!initialized_) { + return OnPreviewStarted( + false, + "Camera not initialized. Camera should be disposed and reinitialized."); + } + if (previewing_) { + // Return success if preview already started + return OnPreviewStarted(true, ""); } HRESULT hr = InitPreviewSink(); @@ -902,10 +920,14 @@ void CaptureControllerImpl::StartPreview() { } if (FAILED(hr)) { - return OnPreviewStarted(false); + return OnPreviewStarted(false, "Failed to start preview"); } } +// Stops preview. If called, recording will not work either. +// Use PausePreview and ResumePreview methods to for +// pausing and resuming the preview. +// Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event handling for response process. void CaptureControllerImpl::StopPreview() { assert(capture_controller_listener_); @@ -925,6 +947,9 @@ void CaptureControllerImpl::StopPreview() { }; } +// Marks preview as paused. +// When preview is paused, captured frames are not processed for preview +// and flutter texture is not updated void CaptureControllerImpl::PausePreview() { if (!previewing_) { preview_paused_ = false; @@ -941,6 +966,9 @@ void CaptureControllerImpl::PausePreview() { } } +// Marks preview as not paused. +// When preview is not paused, captured frames are processed for preview +// and flutter texture is updated. void CaptureControllerImpl::ResumePreview() { preview_paused_ = false; if (capture_controller_listener_) { @@ -948,15 +976,60 @@ void CaptureControllerImpl::ResumePreview() { } } -// Handles Picture event and informs CaptureControllerListener. +// Handles capture engine events. // Called via IMFCaptureEngineOnEventCallback implementation. -// Implements CaptureEngineObserver::OnPicture. -void CaptureControllerImpl::OnPicture(bool success) { +// Implements CaptureEngineObserver::OnEvent. +void CaptureControllerImpl::OnEvent(IMFMediaEvent *event) { + if (!initialized_ && !capture_engine_initialization_pending_) { + return; + } + + HRESULT event_hr; + HRESULT hr = event->GetStatus(&event_hr); + + GUID extended_type_guid; + if (SUCCEEDED(hr)) { + hr = event->GetExtendedType(&extended_type_guid); + } + + if (SUCCEEDED(hr)) { + std::string error; + if (FAILED(event_hr)) { + error = std::system_category().message(event_hr); + } + + if (extended_type_guid == MF_CAPTURE_ENGINE_ERROR) { + OnCaptureEngineError(event_hr, error); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_INITIALIZED) { + OnCaptureEngineInitialized(SUCCEEDED(event_hr), error); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) { + // Preview is marked as started after first frame is captured. + // This is because, CaptureEngine might inform that preview is started + // even if error is thrown right after. + } else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) { + OnPreviewStopped(SUCCEEDED(event_hr), error); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STARTED) { + OnRecordStarted(SUCCEEDED(event_hr), error); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STOPPED) { + OnRecordStopped(SUCCEEDED(event_hr), error); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_PHOTO_TAKEN) { + OnPicture(SUCCEEDED(event_hr), error); + } else if (extended_type_guid == MF_CAPTURE_ENGINE_CAMERA_STREAM_BLOCKED) { + // TODO: Inform capture state to flutter. + } else if (extended_type_guid == + MF_CAPTURE_ENGINE_CAMERA_STREAM_UNBLOCKED) { + // TODO: Inform capture state to flutter. + } + } +} + +// Handles Picture event and informs CaptureControllerListener. +void CaptureControllerImpl::OnPicture(bool success, const std::string &error) { if (capture_controller_listener_) { if (success && !pending_picture_path_.empty()) { capture_controller_listener_->OnPictureSuccess(pending_picture_path_); } else { - capture_controller_listener_->OnPictureFailed("Failed to take picture"); + capture_controller_listener_->OnPictureFailed(error); } } pending_image_capture_ = false; @@ -964,9 +1037,8 @@ void CaptureControllerImpl::OnPicture(bool success) { } // Handles CaptureEngineInitialized event and informs CaptureControllerListener. -// Called via IMFCaptureEngineOnEventCallback implementation. -// Implements CaptureEngineObserver::OnCaptureEngineInitialized. -void CaptureControllerImpl::OnCaptureEngineInitialized(bool success) { +void CaptureControllerImpl::OnCaptureEngineInitialized( + bool success, const std::string &error) { if (capture_controller_listener_) { // Create flutter desktop pixelbuffer texture; texture_ = @@ -983,38 +1055,37 @@ void CaptureControllerImpl::OnCaptureEngineInitialized(bool success) { capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id_); initialized_ = true; } else { + texture_ = nullptr; + texture_id_ = -1; + capture_controller_listener_->OnCreateCaptureEngineFailed( + "Failed to create texture_id"); initialized_ = false; } } + capture_engine_initialization_pending_ = false; } // Handles CaptureEngineError event and informs CaptureControllerListener. -// Called via IMFCaptureEngineOnEventCallback implementation. -// Implements CaptureEngineObserver::OnCaptureEngineError. -void CaptureControllerImpl::OnCaptureEngineError() { - // TODO: detect error type and update state depending of error type, also send - // other than capture engine creation errors to separate error handler +void CaptureControllerImpl::OnCaptureEngineError(HRESULT hr, + const std::string &error) { if (capture_controller_listener_) { - capture_controller_listener_->OnCreateCaptureEngineFailed( - "Error while capturing device"); + capture_controller_listener_->OnCaptureError(error); } - initialized_ = false; - capture_engine_initialization_pending_ = false; + // TODO: If MF_CAPTURE_ENGINE_ERROR is returned, + // should capture controller be reinitialized automatically? } // Handles PreviewStarted event and informs CaptureControllerListener. -// Called via IMFCaptureEngineOnEventCallback implementation. -// Implements CaptureEngineObserver::OnPreviewStarted. -void CaptureControllerImpl::OnPreviewStarted(bool success) { +void CaptureControllerImpl::OnPreviewStarted(bool success, + const std::string &error) { if (capture_controller_listener_) { if (success && preview_frame_width_ > 0 && preview_frame_height_ > 0) { capture_controller_listener_->OnStartPreviewSucceeded( preview_frame_width_, preview_frame_height_); } else { - capture_controller_listener_->OnStartPreviewFailed( - "Failed to start preview"); + capture_controller_listener_->OnStartPreviewFailed(error); } } @@ -1024,23 +1095,20 @@ void CaptureControllerImpl::OnPreviewStarted(bool success) { }; // Handles PreviewStopped event. -// Called via IMFCaptureEngineOnEventCallback implementation. -// Implements CaptureEngineObserver::OnPreviewStopped. -void CaptureControllerImpl::OnPreviewStopped(bool success) { +void CaptureControllerImpl::OnPreviewStopped(bool success, + const std::string &error) { // update state previewing_ = false; }; // Handles RecordStarted event and informs CaptureControllerListener. -// Called via IMFCaptureEngineOnEventCallback implementation. -// Implements CaptureEngineObserver::OnRecordStarted. -void CaptureControllerImpl::OnRecordStarted(bool success) { +void CaptureControllerImpl::OnRecordStarted(bool success, + const std::string &error) { if (capture_controller_listener_) { if (success) { capture_controller_listener_->OnStartRecordSucceeded(); } else { - capture_controller_listener_->OnStartRecordFailed( - "Failed to start recording"); + capture_controller_listener_->OnStartRecordFailed(error); } } @@ -1050,26 +1118,24 @@ void CaptureControllerImpl::OnRecordStarted(bool success) { }; // Handles RecordStopped event and informs CaptureControllerListener. -// Called via IMFCaptureEngineOnEventCallback implementation. -// Implements CaptureEngineObserver::OnRecordStopped. -void CaptureControllerImpl::OnRecordStopped(bool success) { +void CaptureControllerImpl::OnRecordStopped(bool success, + const std::string &error) { if (capture_controller_listener_) { - if (recording_type_ == RecordingType::RECORDING_TYPE_CONTINUOUS) { - if (success && !pending_record_path_.empty()) { - capture_controller_listener_->OnStopRecordSucceeded( - pending_record_path_); - } else { - capture_controller_listener_->OnStopRecordFailed( - "Failed to record video"); - } - } else if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { + // Always call stop record handlers, + // to handle separate stop record request for timed records. + if (success && !pending_record_path_.empty()) { + capture_controller_listener_->OnStopRecordSucceeded(pending_record_path_); + } else { + capture_controller_listener_->OnStopRecordFailed(error); + } + + if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { if (success && !pending_record_path_.empty()) { capture_controller_listener_->OnVideoRecordedSuccess( pending_record_path_, (recording_duration_us_ / 1000)); } else { - capture_controller_listener_->OnVideoRecordedFailed( - "Failed to record video"); + capture_controller_listener_->OnVideoRecordedFailed(error); } } } @@ -1097,8 +1163,8 @@ uint8_t *CaptureControllerImpl::GetSourceBuffer(uint32_t current_length) { // Marks texture frame available after buffer is updated. // Called via IMFCaptureEngineOnSampleCallback implementation. -// Implements CaptureEngineObserver::OnBufferUpdate. -void CaptureControllerImpl::OnBufferUpdate() { +// Implements CaptureEngineObserver::OnBufferUpdated. +void CaptureControllerImpl::OnBufferUpdated() { if (this->texture_registrar_ && this->texture_id_ >= 0) { this->texture_registrar_->MarkTextureFrameAvailable(this->texture_id_); } @@ -1109,6 +1175,15 @@ void CaptureControllerImpl::OnBufferUpdate() { // Called via IMFCaptureEngineOnSampleCallback implementation. // Implements CaptureEngineObserver::UpdateCaptureTime. void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { + if (!initialized_) { + return; + } + + if (preview_pending_) { + // Informs that first frame is captured succeffully and preview has started. + OnPreviewStarted(true, ""); + } + // Checks if max_video_duration_ms is passed. if (recording_ && recording_type_ == RecordingType::RECORDING_TYPE_TIMED && max_video_duration_ms_ > 0) { diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 724eef4a2ede..a52596a97cd9 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -119,28 +119,21 @@ class CaptureControllerImpl : public CaptureController, // Handlers for CaptureEngineListener events // From CaptureEngineObserver - bool IsReadyForEvents() override { - return initialized_ || capture_engine_initialization_pending_; - }; bool IsReadyForSample() override { return initialized_ && previewing_ && !preview_paused_; } - void OnCaptureEngineInitialized(bool success) override; - void OnCaptureEngineError() override; - void OnPicture(bool success) override; - void OnPreviewStarted(bool success) override; - void OnPreviewStopped(bool success) override; - void OnRecordStarted(bool success) override; - void OnRecordStopped(bool success) override; + + void OnEvent(IMFMediaEvent* event) override; uint8_t* GetSourceBuffer(uint32_t current_length) override; - void OnBufferUpdate() override; + void OnBufferUpdated() override; void UpdateCaptureTime(uint64_t capture_time) override; private: CaptureControllerListener* capture_controller_listener_ = nullptr; bool initialized_ = false; bool enable_audio_record_ = false; + std::string video_device_id_; ResolutionPreset resolution_preset_ = ResolutionPreset::RESOLUTION_PRESET_MEDIUM; @@ -199,13 +192,13 @@ class CaptureControllerImpl : public CaptureController, RecordingType recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; - void ResetCaptureEngineState(); + void ResetCaptureController(); uint32_t GetMaxPreviewHeight(); HRESULT CreateDefaultAudioCaptureSource(); HRESULT CreateVideoCaptureSourceForDevice(const std::string& video_device_id); HRESULT CreateD3DManagerWithDX11Device(); - HRESULT CreateCaptureEngine(const std::string& video_device_id); + HRESULT CreateCaptureEngine(); HRESULT FindBaseMediaTypes(); HRESULT InitPreviewSink(); @@ -214,6 +207,14 @@ class CaptureControllerImpl : public CaptureController, void StopTimedRecord(); + void OnCaptureEngineInitialized(bool success, const std::string& error); + void OnCaptureEngineError(HRESULT hr, const std::string& error); + void OnPicture(bool success, const std::string& error); + void OnPreviewStarted(bool success, const std::string& error); + void OnPreviewStopped(bool success, const std::string& error); + void OnRecordStarted(bool success, const std::string& error); + void OnRecordStopped(bool success, const std::string& error); + const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, size_t height); }; diff --git a/packages/camera/camera_windows/windows/capture_controller_listener.h b/packages/camera/camera_windows/windows/capture_controller_listener.h index 98be7d47e2c9..ae4fce39c921 100644 --- a/packages/camera/camera_windows/windows/capture_controller_listener.h +++ b/packages/camera/camera_windows/windows/capture_controller_listener.h @@ -37,6 +37,8 @@ class CaptureControllerListener { virtual void OnVideoRecordedSuccess(const std::string& filepath, int64_t video_duration) = 0; virtual void OnVideoRecordedFailed(const std::string& error) = 0; + + virtual void OnCaptureError(const std::string& error) = 0; }; } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp index 4d1053b0d6c8..4bc4ceeb5118 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.cpp +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -5,10 +5,12 @@ #include "capture_engine_listener.h" -#include +#include namespace camera_windows { +using Microsoft::WRL::ComPtr; + // Method from IUnknown STDMETHODIMP_(ULONG) CaptureEngineListener::AddRef() { return InterlockedIncrement(&ref_); @@ -44,67 +46,40 @@ CaptureEngineListener::QueryInterface(const IID &riid, void **ppv) { } STDMETHODIMP CaptureEngineListener::OnEvent(IMFMediaEvent *event) { - HRESULT event_hr; - HRESULT hr = event->GetStatus(&event_hr); - - if (!observer_->IsReadyForEvents()) { - // TODO: call observer_->OnCaptureEngineError() - // with proper error message - return event_hr; - } - - if (SUCCEEDED(hr)) { - GUID extended_type_guid; - hr = event->GetExtendedType(&extended_type_guid); - if (SUCCEEDED(hr)) { - if (extended_type_guid == MF_CAPTURE_ENGINE_ERROR) { - observer_->OnCaptureEngineError(); - } else if (extended_type_guid == MF_CAPTURE_ENGINE_INITIALIZED) { - observer_->OnCaptureEngineInitialized(SUCCEEDED(event_hr)); - } else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) { - observer_->OnPreviewStarted(SUCCEEDED(event_hr)); - } else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) { - observer_->OnPreviewStopped(SUCCEEDED(event_hr)); - } else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STARTED) { - observer_->OnRecordStarted(SUCCEEDED(event_hr)); - } else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STOPPED) { - observer_->OnRecordStopped(SUCCEEDED(event_hr)); - } else if (extended_type_guid == MF_CAPTURE_ENGINE_PHOTO_TAKEN) { - observer_->OnPicture(SUCCEEDED(event_hr)); - } - } + if (observer_) { + observer_->OnEvent(event); } - - // TODO: pass this error directly to the handlers? - if (FAILED(event_hr)) { - std::string message = std::system_category().message(event_hr); - - printf("Got capture event error: %s\n", message.c_str()); - fflush(stdout); - } - - return event_hr; + return S_OK; } // Method from IMFCaptureEngineOnSampleCallback HRESULT CaptureEngineListener::OnSample(IMFSample *sample) { HRESULT hr = S_OK; - if (this->observer_ == nullptr || !this->observer_->IsReadyForSample()) { - // No texture target available or not previewing, just return status - return hr; - } + if (this->observer_ && sample) { + LONGLONG raw_time_stamp = 0; + // Receives the presentation time, in 100-nanosecond units + sample->GetSampleTime(&raw_time_stamp); + + // Report time in microseconds + this->observer_->UpdateCaptureTime( + static_cast(raw_time_stamp / 10)); + + if (!this->observer_->IsReadyForSample()) { + // No texture target available or not previewing, just return status + return hr; + } - if (SUCCEEDED(hr) && sample) { - IMFMediaBuffer *buffer = nullptr; + ComPtr buffer; hr = sample->ConvertToContiguousBuffer(&buffer); // Draw the frame. - if (SUCCEEDED(hr)) { + if (SUCCEEDED(hr) && buffer) { DWORD max_length = 0; DWORD current_length = 0; uint8_t *data; if (SUCCEEDED(buffer->Lock(&data, &max_length, ¤t_length))) { + // this->observer.OnFrame(); uint8_t *src_buffer = this->observer_->GetSourceBuffer(current_length); if (src_buffer) { CopyMemory(src_buffer, data, current_length); @@ -112,22 +87,9 @@ HRESULT CaptureEngineListener::OnSample(IMFSample *sample) { } hr = buffer->Unlock(); if (SUCCEEDED(hr)) { - this->observer_->OnBufferUpdate(); + this->observer_->OnBufferUpdated(); } } - - LONGLONG raw_time_stamp = 0; - // Receives the presentation time, in 100-nanosecond units - sample->GetSampleTime(&raw_time_stamp); - - // Report time in microseconds - this->observer_->UpdateCaptureTime( - static_cast(raw_time_stamp / 10)); - - if (buffer) { - buffer->Release(); - buffer = nullptr; - } } return hr; } diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index d7eb25378be9..7f74b33be656 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -15,23 +15,14 @@ class CaptureEngineObserver { public: virtual ~CaptureEngineObserver() = default; - virtual bool IsReadyForEvents() = 0; virtual bool IsReadyForSample() = 0; // Event functions - // TODO: Instead of separate functions for each event, - // just have OnEvent handler; - virtual void OnCaptureEngineInitialized(bool success) = 0; - virtual void OnCaptureEngineError() = 0; - virtual void OnPicture(bool success) = 0; - virtual void OnPreviewStarted(bool success) = 0; - virtual void OnPreviewStopped(bool success) = 0; - virtual void OnRecordStarted(bool success) = 0; - virtual void OnRecordStopped(bool success) = 0; + virtual void OnEvent(IMFMediaEvent *event) = 0; // Sample functions virtual uint8_t* GetSourceBuffer(uint32_t current_length) = 0; - virtual void OnBufferUpdate() = 0; + virtual void OnBufferUpdated() = 0; virtual void UpdateCaptureTime(uint64_t capture_time) = 0; }; diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index fc0d41fa0db4..2050b136a501 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -150,6 +150,7 @@ class MockCamera : public Camera { (override)); MOCK_METHOD(void, OnVideoRecordedFailed, (const std::string& error), (override)); + MOCK_METHOD(void, OnCaptureError, (const std::string& error), (override)); MOCK_METHOD(bool, HasDeviceId, (std::string & device_id), (override)); MOCK_METHOD(bool, HasCameraId, (int64_t camera_id), (override)); From 930a6b2f96dc3bdc26dce8c5bc0c05cd640807ed Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 13 Jan 2022 12:59:16 +0200 Subject: [PATCH 11/93] [camera_windows] Fix format --- packages/camera/camera_windows/windows/camera_plugin.cpp | 3 ++- .../camera/camera_windows/windows/capture_engine_listener.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 949ac35d26c7..a675603b41fd 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -573,7 +573,8 @@ void CameraPlugin::TakePictureMethodHandler( cc->TakePicture(path); } } else { - return result->Error("system_error", "Failed to get capture path for picture"); + return result->Error("system_error", + "Failed to get capture path for picture"); } } diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 7f74b33be656..628209436a03 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -18,7 +18,7 @@ class CaptureEngineObserver { virtual bool IsReadyForSample() = 0; // Event functions - virtual void OnEvent(IMFMediaEvent *event) = 0; + virtual void OnEvent(IMFMediaEvent* event) = 0; // Sample functions virtual uint8_t* GetSourceBuffer(uint32_t current_length) = 0; From 8a80e1c4a590ed531a90601178bebe7e0b1e787f Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 14 Jan 2022 12:11:46 +0200 Subject: [PATCH 12/93] [camera_windows] capture controller test mocks - Fix memory leaks found by tests --- .../camera_windows/windows/CMakeLists.txt | 1 + .../camera/camera_windows/windows/camera.cpp | 4 +- .../windows/capture_controller.cpp | 7 +- .../windows/capture_controller.h | 33 ++- .../windows/capture_engine_listener.cpp | 16 +- .../windows/capture_engine_listener.h | 2 +- .../camera_windows/windows/system_api.cpp | 7 + .../camera_windows/windows/system_api.h | 32 +++ .../windows/test/camera_plugin_test.cpp | 2 + .../windows/test/camera_test.cpp | 2 + .../windows/test/capture_controller_test.cpp | 84 +++++++ .../camera_windows/windows/test/mocks.h | 206 +++++++++++++++++- 12 files changed, 374 insertions(+), 22 deletions(-) create mode 100644 packages/camera/camera_windows/windows/system_api.cpp create mode 100644 packages/camera/camera_windows/windows/system_api.h create mode 100644 packages/camera/camera_windows/windows/test/capture_controller_test.cpp diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index d493b7b253a0..23bfbfecdc4d 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -70,6 +70,7 @@ add_executable(${TEST_RUNNER} test/mocks.h test/camera_plugin_test.cpp test/camera_test.cpp + test/capture_controller_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 ca5dd96e6735..70f125d1afa2 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -58,8 +58,8 @@ void CameraImpl::InitCamera( messenger_ = messenger; capture_controller_ = capture_controller_factory->CreateCaptureController(this); - capture_controller_->CreateCaptureDevice(texture_registrar, device_id_, - enable_audio, resolution_preset); + capture_controller_->InitCaptureDevice(texture_registrar, device_id_, + enable_audio, resolution_preset); } // Adds pending result to the pending_results map. diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 831b534d3245..412ef68940e6 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -352,11 +352,11 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine() { hr = CreateD3DManagerWithDX11Device(); } - if (SUCCEEDED(hr)) { + if (SUCCEEDED(hr) && !video_source_) { hr = CreateVideoCaptureSourceForDevice(video_device_id_); } - if (enable_audio_record_) { + if (enable_audio_record_ && !audio_source_) { if (SUCCEEDED(hr)) { hr = CreateDefaultAudioCaptureSource(); } @@ -366,6 +366,7 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine() { capture_engine_callback_handler_ = ComPtr(new CaptureEngineListener(this)); } + if (SUCCEEDED(hr)) { hr = MFCreateAttributes(&attributes, 2); } @@ -450,7 +451,7 @@ void CaptureControllerImpl::ResetCaptureController() { texture_ = nullptr; } -void CaptureControllerImpl::CreateCaptureDevice( +void CaptureControllerImpl::InitCaptureDevice( flutter::TextureRegistrar *texture_registrar, const std::string &device_id, bool enable_audio, ResolutionPreset resolution_preset) { assert(capture_controller_listener_); diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index a52596a97cd9..df4c3312d3eb 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -69,10 +69,10 @@ class CaptureController { CaptureController(const CaptureController&) = delete; CaptureController& operator=(const CaptureController&) = delete; - virtual void CreateCaptureDevice(flutter::TextureRegistrar* texture_registrar, - const std::string& device_id, - bool enable_audio, - ResolutionPreset resolution_preset) = 0; + virtual void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, + const std::string& device_id, + bool enable_audio, + ResolutionPreset resolution_preset) = 0; virtual int64_t GetTextureId() = 0; virtual uint32_t GetPreviewWidth() = 0; @@ -98,12 +98,16 @@ class CaptureControllerImpl : public CaptureController, CaptureControllerImpl(CaptureControllerListener* listener); virtual ~CaptureControllerImpl(); + // Disallow copy and move. + CaptureControllerImpl(const CaptureControllerImpl&) = delete; + CaptureControllerImpl& operator=(const CaptureControllerImpl&) = delete; + bool IsInitialized() { return initialized_; } bool IsPreviewing() { return previewing_; } - void CreateCaptureDevice(flutter::TextureRegistrar* texture_registrar, - const std::string& device_id, bool enable_audio, - ResolutionPreset resolution_preset) override; + void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, + const std::string& device_id, bool enable_audio, + ResolutionPreset resolution_preset) override; int64_t GetTextureId() override { return texture_id_; } uint32_t GetPreviewWidth() override { return preview_frame_width_; } uint32_t GetPreviewHeight() override { return preview_frame_height_; } @@ -129,6 +133,21 @@ class CaptureControllerImpl : public CaptureController, void OnBufferUpdated() override; void UpdateCaptureTime(uint64_t capture_time) override; + // Sets capture engine, for mocking purposes + void SetCaptureEngine(IMFCaptureEngine* capture_engine) { + capture_engine_ = capture_engine; + }; + + // Sets video source, for mocking purposes + void SetVideoSource(IMFMediaSource* video_source) { + video_source_ = video_source; + }; + + // Sets audio source, for mocking purposes + void SetAudioSource(IMFMediaSource* audio_source) { + audio_source_ = audio_source; + }; + private: CaptureControllerListener* capture_controller_listener_ = nullptr; bool initialized_ = false; diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp index 4bc4ceeb5118..2fa397f313a9 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.cpp +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -5,18 +5,19 @@ #include "capture_engine_listener.h" +#include #include namespace camera_windows { using Microsoft::WRL::ComPtr; -// Method from IUnknown +// IUnknown STDMETHODIMP_(ULONG) CaptureEngineListener::AddRef() { return InterlockedIncrement(&ref_); } -// Method from IUnknown +// IUnknown STDMETHODIMP_(ULONG) CaptureEngineListener::Release() { LONG ref = InterlockedDecrement(&ref_); @@ -26,23 +27,22 @@ CaptureEngineListener::Release() { return ref; } -// Method from IUnknown +// IUnknown STDMETHODIMP_(HRESULT) CaptureEngineListener::QueryInterface(const IID &riid, void **ppv) { - HRESULT hr = E_NOINTERFACE; *ppv = nullptr; if (riid == IID_IMFCaptureEngineOnEventCallback) { *ppv = static_cast(this); ((IUnknown *)*ppv)->AddRef(); - hr = S_OK; + return S_OK; } else if (riid == IID_IMFCaptureEngineOnSampleCallback) { *ppv = static_cast(this); ((IUnknown *)*ppv)->AddRef(); - hr = S_OK; + return S_OK; } - return hr; + return E_NOINTERFACE; } STDMETHODIMP CaptureEngineListener::OnEvent(IMFMediaEvent *event) { @@ -52,7 +52,7 @@ STDMETHODIMP CaptureEngineListener::OnEvent(IMFMediaEvent *event) { return S_OK; } -// Method from IMFCaptureEngineOnSampleCallback +// IMFCaptureEngineOnSampleCallback HRESULT CaptureEngineListener::OnSample(IMFSample *sample) { HRESULT hr = S_OK; diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 628209436a03..9aa8d66cdbc2 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -30,7 +30,7 @@ class CaptureEngineListener : public IMFCaptureEngineOnSampleCallback, public IMFCaptureEngineOnEventCallback { public: CaptureEngineListener(CaptureEngineObserver* observer) - : ref_(1), observer_(observer) {} + : ref_(0), observer_(observer) {} ~CaptureEngineListener(){}; diff --git a/packages/camera/camera_windows/windows/system_api.cpp b/packages/camera/camera_windows/windows/system_api.cpp new file mode 100644 index 000000000000..e0168cdad4c8 --- /dev/null +++ b/packages/camera/camera_windows/windows/system_api.cpp @@ -0,0 +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. + +#include "system_api.h" + +namespace camera_windows {} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/system_api.h b/packages/camera/camera_windows/windows/system_api.h new file mode 100644 index 000000000000..52b83e6a810f --- /dev/null +++ b/packages/camera/camera_windows/windows/system_api.h @@ -0,0 +1,32 @@ +// 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_SYSTEM_API_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_SYSTEM_API_H_ + +namespace camera_windows { + +class SystemApi { + public: + SystemApi(){}; + virtual ~SystemApi() = default; + + // Disallow copy and move. + SystemApi(const SystemApi&) = delete; + SystemApi& operator=(const SystemApi&) = delete; +}; + +class SystemApiImpl : public SystemApi { + public: + SystemApiImpl(){}; + virtual ~SystemApiImpl() = default; + + // Disallow copy and move. + SystemApiImpl(const SystemApiImpl&) = delete; + SystemApiImpl& operator=(const SystemApiImpl&) = delete; +}; + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_SYSTEM_API_H_ \ No newline at end of file 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 991f3c2a8454..e455fe8e2463 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "camera_plugin.h" + #include #include #include diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp index 01d47fb6eb05..a0e2697b2f63 100644 --- a/packages/camera/camera_windows/windows/test/camera_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "camera.h" + #include #include #include diff --git a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp new file mode 100644 index 000000000000..a7b6065eff0e --- /dev/null +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -0,0 +1,84 @@ +// 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 "capture_controller.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mocks.h" + +namespace camera_windows { + +namespace test { + +using Microsoft::WRL::ComPtr; +using ::testing::Eq; + +TEST(CaptureController, + InitCaptureEngineCallsCaptureControllerListenerWithTextureId) { + 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(); + + ComPtr video_source = new FakeMediaSource(); + ComPtr audio_source = new FakeMediaSource(); + + capture_controller->SetCaptureEngine( + reinterpret_cast(engine.Get())); + capture_controller->SetVideoSource( + reinterpret_cast(video_source.Get())); + capture_controller->SetAudioSource( + reinterpret_cast(audio_source.Get())); + + int64_t mock_texture_id = 1000; + + EXPECT_CALL(*texture_registrar, RegisterTexture) + .Times(1) + .WillOnce([reg = texture_registrar.get(), + mock_texture_id](flutter::TextureVariant* texture) -> int64_t { + EXPECT_TRUE(texture); + reg->texture_id = mock_texture_id; + return reg->texture_id; + }); + EXPECT_CALL(*texture_registrar, UnregisterTexture(Eq(mock_texture_id))) + .Times(1); + EXPECT_CALL(*camera, OnCreateCaptureEngineFailed).Times(0); + EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded(Eq(mock_texture_id))) + .Times(1); + EXPECT_CALL(*(engine.Get()), Initialize).Times(1); + + capture_controller->InitCaptureDevice( + texture_registrar.get(), MOCK_DEVICE_ID, true, + ResolutionPreset::RESOLUTION_PRESET_AUTO); + + // MockCaptureEngine::Initialize is called + EXPECT_TRUE(engine->initialized_); + + engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_INITIALIZED); + + capture_controller = nullptr; + camera = nullptr; + texture_registrar = nullptr; + video_source = nullptr; + audio_source = nullptr; + engine = 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 2050b136a501..3c3a60f7dccd 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -11,9 +11,13 @@ #include #include #include +#include #include "camera.h" #include "camera_plugin.h" +#include "capture_controller.h" +#include "capture_controller_listener.h" +#include "capture_engine_listener.h" namespace camera_windows { namespace test { @@ -62,6 +66,7 @@ class MockTextureRegistrar : public flutter::TextureRegistrar { // TODO: create separate fake implementation ON_CALL(*this, RegisterTexture) .WillByDefault([this](flutter::TextureVariant* texture) -> int64_t { + EXPECT_TRUE(texture); this->texture_id = 1000; return this->texture_id; }); @@ -192,7 +197,7 @@ class MockCaptureControllerFactory : public CaptureControllerFactory { class MockCaptureController : public CaptureController { public: - MOCK_METHOD(void, CreateCaptureDevice, + MOCK_METHOD(void, InitCaptureDevice, (flutter::TextureRegistrar * texture_registrar, const std::string& device_id, bool enable_audio, ResolutionPreset resolution_preset), @@ -243,6 +248,205 @@ class MockCameraPlugin : public CameraPlugin { } }; +class MockCaptureEngineListener : public IMFCaptureEngineOnSampleCallback, + public IMFCaptureEngineOnEventCallback { + MockCaptureEngineListener(CaptureEngineObserver* observer) + : ref_(1), observer_(observer) {} + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } + + // IUnknown + STDMETHODIMP_(ULONG) Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; + } + return ref; + } + + // IUnknown + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { + *ppv = nullptr; + + if (riid == IID_IMFCaptureEngineOnEventCallback) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } else if (riid == IID_IMFCaptureEngineOnSampleCallback) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + MOCK_METHOD(HRESULT, OnEvent, (IMFMediaEvent * pEvent)); + MOCK_METHOD(HRESULT, OnSample, (IMFSample * pSample)); + + CaptureEngineObserver* observer_; + volatile ULONG ref_; +}; +class MockCaptureControllerListener {}; + +// Uses IMFMediaSourceEx which has SetD3DManager method. +class FakeMediaSource : public IMFMediaSourceEx { + public: + FakeMediaSource() : ref_(0){}; + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } + + // IUnknown + STDMETHODIMP_(ULONG) Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; + } + return ref; + } + + // IUnknown + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { + *ppv = nullptr; + + if (riid == IID_IMFMediaSource) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + // IMFMediaSource + HRESULT GetCharacteristics(DWORD* dwCharacteristics) override { + return E_NOTIMPL; + } + // IMFMediaSource + HRESULT CreatePresentationDescriptor( + IMFPresentationDescriptor** presentationDescriptor) override { + return E_NOTIMPL; + } + // IMFMediaSource + HRESULT Start(IMFPresentationDescriptor* presentationDescriptor, + const GUID* guidTimeFormat, + const PROPVARIANT* varStartPosition) override { + return E_NOTIMPL; + } + // IMFMediaSource + HRESULT Stop(void) override { return E_NOTIMPL; } + // IMFMediaSource + HRESULT Pause(void) override { return E_NOTIMPL; } + // IMFMediaSource + HRESULT Shutdown(void) override { return E_NOTIMPL; } + + // IMFMediaEventGenerator + HRESULT GetEvent(DWORD dwFlags, IMFMediaEvent** event) override { + return E_NOTIMPL; + } + // IMFMediaEventGenerator + HRESULT BeginGetEvent(IMFAsyncCallback* callback, + IUnknown* unkState) override { + return E_NOTIMPL; + } + // IMFMediaEventGenerator + HRESULT EndGetEvent(IMFAsyncResult* result, IMFMediaEvent** event) override { + return E_NOTIMPL; + } + // IMFMediaEventGenerator + HRESULT QueueEvent(MediaEventType met, REFGUID guidExtendedType, + HRESULT hrStatus, const PROPVARIANT* value) override { + return E_NOTIMPL; + } + + // IMFMediaSourceEx + HRESULT GetSourceAttributes(IMFAttributes** attributes) { return E_NOTIMPL; } + // IMFMediaSourceEx + HRESULT GetStreamAttributes(DWORD stream_id, IMFAttributes** attributes) { + return E_NOTIMPL; + } + // IMFMediaSourceEx + HRESULT SetD3DManager(IUnknown* manager) { return S_OK; } + + private: + ~FakeMediaSource() = default; + volatile ULONG ref_; +}; + +class MockCaptureEngine : public IMFCaptureEngine { + public: + MockCaptureEngine() : ref_(0) { + ON_CALL(*this, Initialize) + .WillByDefault([this](IMFCaptureEngineOnEventCallback* callback, + IMFAttributes* attributes, IUnknown* audio_Source, + IUnknown* video_source) -> HRESULT { + EXPECT_TRUE(callback); + EXPECT_TRUE(attributes); + EXPECT_TRUE(video_source); + // audio_source is allowed to be nullptr; + callback_ = callback; + initialized_ = true; + return S_OK; + }); + }; + + virtual ~MockCaptureEngine() = default; + + MOCK_METHOD(HRESULT, Initialize, + (IMFCaptureEngineOnEventCallback * callback, + IMFAttributes* attributes, IUnknown* audio_Source, + IUnknown* video_source)); + MOCK_METHOD(HRESULT, StartPreview, ()); + MOCK_METHOD(HRESULT, StopPreview, ()); + MOCK_METHOD(HRESULT, StartRecord, ()); + MOCK_METHOD(HRESULT, StopRecord, + (BOOL finalize, BOOL flushUnprocessedSamples)); + MOCK_METHOD(HRESULT, TakePhoto, ()); + MOCK_METHOD(HRESULT, GetSink, + (MF_CAPTURE_ENGINE_SINK_TYPE type, IMFCaptureSink** sink)); + MOCK_METHOD(HRESULT, GetSource, (IMFCaptureSource * *ppSource)); + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } + + // IUnknown + STDMETHODIMP_(ULONG) Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; + } + return ref; + } + + // IUnknown + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { + *ppv = nullptr; + + if (riid == IID_IMFCaptureEngine) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + void CreateFakeEvent(HRESULT hrStatus, GUID event_type) { + EXPECT_TRUE(initialized_); + ComPtr event; + MFCreateMediaEvent(MEExtendedType, event_type, hrStatus, nullptr, &event); + if (callback_) { + callback_->OnEvent(event.Get()); + } + } + + ComPtr callback_; + volatile ULONG ref_; + bool initialized_ = false; +}; + #define MOCK_DEVICE_ID "mock_device_id" #define MOCK_CAMERA_NAME "mock_camera_name <" MOCK_DEVICE_ID ">" #define MOCK_INVALID_CAMERA_NAME "invalid_camera_name" From 8c651ec64916f5041f6daac72fe3e65dbcdf15ba Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 14 Jan 2022 12:21:40 +0200 Subject: [PATCH 13/93] [camera_windows] Fix intergration tests --- .../example/integration_test/camera_test.dart | 12 +++++----- .../camera_windows/lib/camera_windows.dart | 24 ++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/camera/camera_windows/example/integration_test/camera_test.dart b/packages/camera/camera_windows/example/integration_test/camera_test.dart index 3a6539a88f33..17e98e983b7f 100644 --- a/packages/camera/camera_windows/example/integration_test/camera_test.dart +++ b/packages/camera/camera_windows/example/integration_test/camera_test.dart @@ -20,7 +20,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.initializeCamera(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -30,7 +30,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.takePicture(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -40,7 +40,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.startVideoRecording(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -50,7 +50,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.stopVideoRecording(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -60,7 +60,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.pausePreview(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -70,7 +70,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.resumePreview(1234), - throwsA(isA())); + throwsA(isA())); }); }); diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index ccac20a1ac2c..c8c8460684d2 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -364,18 +364,26 @@ class CameraWindows extends CameraPlatform { @override Future pausePreview(int cameraId) async { - await _channel.invokeMethod( - 'pausePreview', - {'cameraId': cameraId}, - ); + try { + await _channel.invokeMethod( + 'pausePreview', + {'cameraId': cameraId}, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } } @override Future resumePreview(int cameraId) async { - await _channel.invokeMethod( - 'resumePreview', - {'cameraId': cameraId}, - ); + try { + await _channel.invokeMethod( + 'resumePreview', + {'cameraId': cameraId}, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } } @override From 1eca936f1b98b14afac3c66b65c63aa47069a9b5 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 14 Jan 2022 12:32:11 +0200 Subject: [PATCH 14/93] [camera_windows] fix example dart format --- packages/camera/camera_windows/example/lib/main.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index 0a01e11c3fe3..cf92019b8336 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/material.dart'; @@ -30,7 +29,7 @@ class _MyAppState extends State { bool _recordAudio = true; bool _previewPaused = false; Size? _previewSize; - StreamSubscription? _errorStreamSubscription; + StreamSubscription? _errorStreamSubscription; @override void initState() { @@ -91,7 +90,7 @@ class _MyAppState extends State { _errorStreamSubscription?.cancel(); _errorStreamSubscription = - CameraPlatform.instance.onCameraError(cameraId).listen(OnCameraError); + CameraPlatform.instance.onCameraError(cameraId).listen(onCameraError); unawaited(CameraPlatform.instance .onCameraInitialized(cameraId) @@ -233,7 +232,7 @@ class _MyAppState extends State { } } - void OnCameraError(CameraErrorEvent event) { + void onCameraError(CameraErrorEvent event) { scaffoldMessengerKey.currentState ?.showSnackBar(SnackBar(content: Text('Error: ${event.description}'))); } From 7c341725fc3cbf6c7ec65fd0448728fa1ce5481c Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 14 Jan 2022 12:33:43 +0200 Subject: [PATCH 15/93] [camera_windows] code cleanup --- .../camera_windows/windows/system_api.cpp | 7 ---- .../camera_windows/windows/system_api.h | 32 ------------------- 2 files changed, 39 deletions(-) delete mode 100644 packages/camera/camera_windows/windows/system_api.cpp delete mode 100644 packages/camera/camera_windows/windows/system_api.h diff --git a/packages/camera/camera_windows/windows/system_api.cpp b/packages/camera/camera_windows/windows/system_api.cpp deleted file mode 100644 index e0168cdad4c8..000000000000 --- a/packages/camera/camera_windows/windows/system_api.cpp +++ /dev/null @@ -1,7 +0,0 @@ -// 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 "system_api.h" - -namespace camera_windows {} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/system_api.h b/packages/camera/camera_windows/windows/system_api.h deleted file mode 100644 index 52b83e6a810f..000000000000 --- a/packages/camera/camera_windows/windows/system_api.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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_SYSTEM_API_H_ -#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_SYSTEM_API_H_ - -namespace camera_windows { - -class SystemApi { - public: - SystemApi(){}; - virtual ~SystemApi() = default; - - // Disallow copy and move. - SystemApi(const SystemApi&) = delete; - SystemApi& operator=(const SystemApi&) = delete; -}; - -class SystemApiImpl : public SystemApi { - public: - SystemApiImpl(){}; - virtual ~SystemApiImpl() = default; - - // Disallow copy and move. - SystemApiImpl(const SystemApiImpl&) = delete; - SystemApiImpl& operator=(const SystemApiImpl&) = delete; -}; - -} // namespace camera_windows - -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_SYSTEM_API_H_ \ No newline at end of file From 83795485037a00ea3b8187b0bc7715ee3f1f8f86 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Sat, 15 Jan 2022 22:05:10 +0200 Subject: [PATCH 16/93] [camera_windows] Add more test cases --- .../windows/capture_controller.cpp | 37 +- .../windows/capture_controller.h | 17 +- .../windows/capture_engine_listener.cpp | 3 +- .../windows/test/camera_test.cpp | 5 + .../windows/test/capture_controller_test.cpp | 206 +++++++- .../camera_windows/windows/test/mocks.h | 496 +++++++++++++++++- 6 files changed, 699 insertions(+), 65 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 412ef68940e6..265b15bb2dd9 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -16,20 +16,6 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -struct FlutterDesktop_Pixel { - BYTE r = 0; - BYTE g = 0; - BYTE b = 0; - BYTE a = 0; -}; - -struct MFVideoFormat_RGB32_Pixel { - BYTE b = 0; - BYTE g = 0; - BYTE r = 0; - BYTE x = 0; -}; - CaptureControllerImpl::CaptureControllerImpl( CaptureControllerListener *listener) : capture_controller_listener_(listener), CaptureController(){}; @@ -391,7 +377,6 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine() { } void CaptureControllerImpl::ResetCaptureController() { - initialized_ = false; if (previewing_) { StopPreview(); } @@ -403,6 +388,7 @@ void CaptureControllerImpl::ResetCaptureController() { } // States + initialized_ = false; capture_engine_initialization_pending_ = false; preview_pending_ = false; previewing_ = false; @@ -638,11 +624,11 @@ HRESULT CaptureControllerImpl::InitPreviewSink() { hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, &capture_sink); - if (SUCCEEDED(hr)) { + if (capture_sink && SUCCEEDED(hr)) { hr = capture_sink.As(&preview_sink_); } - if (SUCCEEDED(hr)) { + if (preview_sink_ && SUCCEEDED(hr)) { hr = preview_sink_->RemoveAllStreams(); } @@ -906,6 +892,7 @@ void CaptureControllerImpl::StartPreview() { false, "Camera not initialized. Camera should be disposed and reinitialized."); } + if (previewing_) { // Return success if preview already started return OnPreviewStarted(true, ""); @@ -925,27 +912,19 @@ void CaptureControllerImpl::StartPreview() { } } -// Stops preview. If called, recording will not work either. +// Stops preview. Called by destructor // Use PausePreview and ResumePreview methods to for // pausing and resuming the preview. // Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event handling for response process. void CaptureControllerImpl::StopPreview() { assert(capture_controller_listener_); - if (!initialized_) { - return capture_controller_listener_->OnPausePreviewFailed( - "Capture not initialized"); - } else if (!previewing_ && !preview_pending_) { - return capture_controller_listener_->OnPausePreviewFailed("Not previewing"); + if (!initialized_ && (!previewing_ && !preview_pending_)) { + return; } // Requests to stop preview. - HRESULT hr = capture_engine_->StopPreview(); - - if (FAILED(hr)) { - capture_controller_listener_->OnPausePreviewFailed( - "Failed to stop previewing"); - }; + capture_engine_->StopPreview(); } // Marks preview as paused. diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index df4c3312d3eb..fb2702b6992f 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -25,6 +25,20 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; +struct FlutterDesktop_Pixel { + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + BYTE a = 0; +}; + +struct MFVideoFormat_RGB32_Pixel { + BYTE b = 0; + BYTE g = 0; + BYTE r = 0; + BYTE x = 0; +}; + enum ResolutionPreset { /// AUTO RESOLUTION_PRESET_AUTO, @@ -80,7 +94,6 @@ class CaptureController { // Actions virtual void StartPreview() = 0; - virtual void StopPreview() = 0; virtual void PausePreview() = 0; virtual void ResumePreview() = 0; virtual void StartRecord(const std::string& filepath, @@ -113,7 +126,6 @@ class CaptureControllerImpl : public CaptureController, uint32_t GetPreviewHeight() override { return preview_frame_height_; } void StartPreview() override; - void StopPreview() override; void PausePreview() override; void ResumePreview() override; void StartRecord(const std::string& filepath, @@ -225,6 +237,7 @@ class CaptureControllerImpl : public CaptureController, HRESULT InitRecordSink(const std::string& filepath); void StopTimedRecord(); + void StopPreview(); void OnCaptureEngineInitialized(bool success, const std::string& error); void OnCaptureEngineError(HRESULT hr, const std::string& error); diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp index 2fa397f313a9..946743941ae6 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.cpp +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -79,10 +79,9 @@ HRESULT CaptureEngineListener::OnSample(IMFSample *sample) { DWORD current_length = 0; uint8_t *data; if (SUCCEEDED(buffer->Lock(&data, &max_length, ¤t_length))) { - // this->observer.OnFrame(); uint8_t *src_buffer = this->observer_->GetSourceBuffer(current_length); if (src_buffer) { - CopyMemory(src_buffer, data, current_length); + std::copy(data, data + current_length, src_buffer); } } hr = buffer->Unlock(); diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp index a0e2697b2f63..39e3ff3ff34c 100644 --- a/packages/camera/camera_windows/windows/test/camera_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -19,6 +19,11 @@ #include "mocks.h" namespace camera_windows { +using ::testing::_; +using ::testing::Eq; +using ::testing::NiceMock; +using ::testing::Pointee; + namespace test { TEST(Camera, InitCameraCreatesCaptureController) { 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 a7b6065eff0e..bc006fb15f18 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -24,10 +24,52 @@ namespace camera_windows { namespace test { using Microsoft::WRL::ComPtr; +using ::testing::_; using ::testing::Eq; +using ::testing::Return; + +void InitCaptureController(CaptureControllerImpl* capture_controller, + MockTextureRegistrar* texture_registrar, + MockCaptureEngine* engine, MockCamera* camera, + int64_t mock_texture_id) { + ComPtr video_source = new MockMediaSource(); + ComPtr audio_source = new MockMediaSource(); + + capture_controller->SetCaptureEngine( + reinterpret_cast(engine)); + capture_controller->SetVideoSource( + reinterpret_cast(video_source.Get())); + capture_controller->SetAudioSource( + reinterpret_cast(audio_source.Get())); + + EXPECT_CALL(*texture_registrar, RegisterTexture) + .Times(1) + .WillOnce([reg = texture_registrar, + mock_texture_id](flutter::TextureVariant* texture) -> int64_t { + EXPECT_TRUE(texture); + reg->texture_ = texture; + reg->texture_id_ = mock_texture_id; + return reg->texture_id_; + }); + EXPECT_CALL(*texture_registrar, UnregisterTexture(Eq(mock_texture_id))) + .Times(1); + EXPECT_CALL(*camera, OnCreateCaptureEngineFailed).Times(0); + EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded(Eq(mock_texture_id))) + .Times(1); + EXPECT_CALL(*engine, Initialize).Times(1); + + capture_controller->InitCaptureDevice( + texture_registrar, MOCK_DEVICE_ID, true, + ResolutionPreset::RESOLUTION_PRESET_AUTO); + + // MockCaptureEngine::Initialize is called + EXPECT_TRUE(engine->initialized_); + + engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_INITIALIZED); +} TEST(CaptureController, - InitCaptureEngineCallsCaptureControllerListenerWithTextureId) { + InitCaptureEngineCallsOnCreateCaptureEngineSucceededWithTextureId) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); @@ -36,8 +78,8 @@ TEST(CaptureController, std::unique_ptr texture_registrar = std::make_unique(); - ComPtr video_source = new FakeMediaSource(); - ComPtr audio_source = new FakeMediaSource(); + ComPtr video_source = new MockMediaSource(); + ComPtr audio_source = new MockMediaSource(); capture_controller->SetCaptureEngine( reinterpret_cast(engine.Get())); @@ -53,8 +95,9 @@ TEST(CaptureController, .WillOnce([reg = texture_registrar.get(), mock_texture_id](flutter::TextureVariant* texture) -> int64_t { EXPECT_TRUE(texture); - reg->texture_id = mock_texture_id; - return reg->texture_id; + reg->texture_ = texture; + reg->texture_id_ = mock_texture_id; + return reg->texture_id_; }); EXPECT_CALL(*texture_registrar, UnregisterTexture(Eq(mock_texture_id))) .Times(1); @@ -80,5 +123,158 @@ TEST(CaptureController, engine = nullptr; } +TEST(CaptureController, StartPreviewStartsProcessingSamples) { + 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(); + + uint64_t mock_texture_id = 1234; + // Init capture controller to be ready for testing + InitCaptureController(capture_controller.get(), texture_registrar.get(), + engine.Get(), camera.get(), mock_texture_id); + + ComPtr preview_sink = new MockCapturePreviewSink(); + ComPtr capture_source = new MockCaptureSource(); + + // Let's keep these small for mock texture data. Two pixels should be enough. + uint32_t mock_preview_width = 2; + uint32_t mock_preview_height = 1; + uint32_t pixels_total = mock_preview_width * mock_preview_height; + uint32_t pixel_size = 4; + + // Build mock texture + uint32_t mock_texture_data_size = pixels_total * pixel_size; + + std::unique_ptr mock_source_buffer = + std::make_unique(mock_texture_data_size); + + uint8_t mock_red_pixel = 0x11; + uint8_t mock_green_pixel = 0x22; + uint8_t mock_blue_pixel = 0x33; + MFVideoFormat_RGB32_Pixel* mock_source_buffer_data = + (MFVideoFormat_RGB32_Pixel*)mock_source_buffer.get(); + + for (uint32_t i = 0; i < pixels_total; i++) { + mock_source_buffer_data[i].r = mock_red_pixel; + mock_source_buffer_data[i].g = mock_green_pixel; + mock_source_buffer_data[i].b = mock_blue_pixel; + } + + EXPECT_CALL(*(engine.Get()), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _)) + .Times(1) + .WillOnce( + [src_sink = preview_sink.Get()](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, + IMFCaptureSink** target_sink) { + *target_sink = src_sink; + src_sink->AddRef(); + return S_OK; + }); + + EXPECT_CALL(*(preview_sink.Get()), RemoveAllStreams) + .Times(1) + .WillOnce(Return(S_OK)); + EXPECT_CALL(*(preview_sink.Get()), AddStream).Times(1).WillOnce(Return(S_OK)); + EXPECT_CALL(*(preview_sink.Get()), SetSampleCallback) + .Times(1) + .WillOnce([sink = preview_sink.Get()]( + DWORD dwStreamSinkIndex, + IMFCaptureEngineOnSampleCallback* pCallback) -> HRESULT { + sink->sample_callback_ = pCallback; + return S_OK; + }); + + EXPECT_CALL(*(engine.Get()), GetSource) + .Times(1) + .WillOnce([src_source = + capture_source.Get()](IMFCaptureSource** target_source) { + *target_source = src_source; + src_source->AddRef(); + return S_OK; + }); + + EXPECT_CALL( + *(capture_source.Get()), + GetAvailableDeviceMediaType( + Eq((DWORD) + MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW), + _, _)) + .WillRepeatedly([mock_preview_width, mock_preview_height]( + DWORD stream_index, DWORD media_type_index, + IMFMediaType** media_type) { + // We give only one media type to loop through + if (media_type_index != 0) return MF_E_NO_MORE_TYPES; + *media_type = + new FakeMediaType(MFMediaType_Video, MFVideoFormat_RGB32, + mock_preview_width, mock_preview_height); + (*media_type)->AddRef(); + return S_OK; + }); + + EXPECT_CALL(*(engine.Get()), StartPreview()).Times(1).WillOnce(Return(S_OK)); + + // Called by destructor + EXPECT_CALL(*(engine.Get()), StopPreview()).Times(1).WillOnce(Return(S_OK)); + + // Called after first processed sample + EXPECT_CALL(*camera, + OnStartPreviewSucceeded(mock_preview_width, mock_preview_height)) + .Times(1); + EXPECT_CALL(*camera, OnStartPreviewFailed).Times(0); + EXPECT_CALL(*texture_registrar, MarkTextureFrameAvailable(mock_texture_id)) + .Times(1); + + capture_controller->StartPreview(); + + EXPECT_EQ(capture_controller->GetPreviewHeight(), mock_preview_height); + EXPECT_EQ(capture_controller->GetPreviewWidth(), mock_preview_width); + + // Capture engine is now started and will first send event of started preview + engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_PREVIEW_STARTED); + + // SendFake sample + preview_sink->SendFakeSample(mock_source_buffer.get(), + mock_texture_data_size); + + // Test texture processing + EXPECT_TRUE(texture_registrar->texture_); + if (texture_registrar->texture_) { + auto pixel_buffer_texture = + std::get_if(texture_registrar->texture_); + EXPECT_TRUE(pixel_buffer_texture); + + if (pixel_buffer_texture) { + auto converted_buffer = + pixel_buffer_texture->CopyPixelBuffer((size_t)100, (size_t)100); + + EXPECT_TRUE(converted_buffer); + if (converted_buffer) { + EXPECT_EQ(converted_buffer->height, mock_preview_height); + EXPECT_EQ(converted_buffer->width, mock_preview_width); + + FlutterDesktop_Pixel* converted_buffer_data = + (FlutterDesktop_Pixel*)(converted_buffer->buffer); + + for (uint32_t i = 0; i < pixels_total; i++) { + EXPECT_EQ(converted_buffer_data[i].r, mock_red_pixel); + EXPECT_EQ(converted_buffer_data[i].g, mock_green_pixel); + EXPECT_EQ(converted_buffer_data[i].b, mock_blue_pixel); + } + } + converted_buffer = nullptr; + } + pixel_buffer_texture = nullptr; + } + + capture_controller = nullptr; + engine = nullptr; + camera = nullptr; + texture_registrar = nullptr; + mock_source_buffer = 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 3c3a60f7dccd..d7c62f37461a 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -27,14 +27,6 @@ namespace { using flutter::EncodableMap; using flutter::EncodableValue; using ::testing::_; -using ::testing::ByMove; -using ::testing::DoAll; -using ::testing::EndsWith; -using ::testing::Eq; -using ::testing::NiceMock; -using ::testing::Pointee; -using ::testing::Return; -using ::testing::SetArgPointee; class MockMethodResult : public flutter::MethodResult<> { public: @@ -62,36 +54,43 @@ class MockBinaryMessenger : public flutter::BinaryMessenger { class MockTextureRegistrar : public flutter::TextureRegistrar { public: - MockTextureRegistrar() { - // TODO: create separate fake implementation + MockTextureRegistrar() : texture_id_(-1), texture_(nullptr) { ON_CALL(*this, RegisterTexture) .WillByDefault([this](flutter::TextureVariant* texture) -> int64_t { EXPECT_TRUE(texture); - this->texture_id = 1000; - return this->texture_id; + this->texture_ = texture; + this->texture_id_ = 1000; + return this->texture_id_; }); + ON_CALL(*this, UnregisterTexture) .WillByDefault([this](int64_t tid) -> bool { - if (tid == this->texture_id) { - this->texture_id = -1; + if (tid == this->texture_id_) { + texture_ = nullptr; + this->texture_id_ = -1; return true; } return false; }); + ON_CALL(*this, MarkTextureFrameAvailable) .WillByDefault([this](int64_t tid) -> bool { - if (tid == this->texture_id) { + if (tid == this->texture_id_) { return true; } return false; }); } + + ~MockTextureRegistrar() { texture_ = nullptr; } + MOCK_METHOD(int64_t, RegisterTexture, (flutter::TextureVariant * texture), (override)); MOCK_METHOD(bool, UnregisterTexture, (int64_t), (override)); MOCK_METHOD(bool, MarkTextureFrameAvailable, (int64_t), (override)); - int64_t texture_id = -1; + int64_t texture_id_; + flutter::TextureVariant* texture_; }; class MockCameraFactory : public CameraFactory { @@ -209,7 +208,6 @@ class MockCaptureController : public CaptureController { // Actions MOCK_METHOD(void, StartPreview, (), (override)); - MOCK_METHOD(void, StopPreview, (), (override)); MOCK_METHOD(void, ResumePreview, (), (override)); MOCK_METHOD(void, PausePreview, (), (override)); MOCK_METHOD(void, StartRecord, @@ -284,16 +282,84 @@ class MockCaptureEngineListener : public IMFCaptureEngineOnSampleCallback, MOCK_METHOD(HRESULT, OnEvent, (IMFMediaEvent * pEvent)); MOCK_METHOD(HRESULT, OnSample, (IMFSample * pSample)); - CaptureEngineObserver* observer_; + + private: volatile ULONG ref_; }; class MockCaptureControllerListener {}; +class MockCaptureSource : public IMFCaptureSource { + public: + MockCaptureSource() : ref_(0){}; + ~MockCaptureSource() = default; + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } + + // IUnknown + STDMETHODIMP_(ULONG) Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; + } + return ref; + } + + // IUnknown + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { + *ppv = nullptr; + + if (riid == IID_IMFCaptureSource) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + MOCK_METHOD(HRESULT, GetCaptureDeviceSource, + (MF_CAPTURE_ENGINE_DEVICE_TYPE mfCaptureEngineDeviceType, + IMFMediaSource** ppMediaSource)); + MOCK_METHOD(HRESULT, GetCaptureDeviceActivate, + (MF_CAPTURE_ENGINE_DEVICE_TYPE mfCaptureEngineDeviceType, + IMFActivate** ppActivate)); + MOCK_METHOD(HRESULT, GetService, + (REFIID rguidService, REFIID riid, IUnknown** ppUnknown)); + MOCK_METHOD(HRESULT, AddEffect, + (DWORD dwSourceStreamIndex, IUnknown* pUnknown)); + + MOCK_METHOD(HRESULT, RemoveEffect, + (DWORD dwSourceStreamIndex, IUnknown* pUnknown)); + MOCK_METHOD(HRESULT, RemoveAllEffects, (DWORD dwSourceStreamIndex)); + MOCK_METHOD(HRESULT, GetAvailableDeviceMediaType, + (DWORD dwSourceStreamIndex, DWORD dwMediaTypeIndex, + IMFMediaType** ppMediaType)); + MOCK_METHOD(HRESULT, SetCurrentDeviceMediaType, + (DWORD dwSourceStreamIndex, IMFMediaType* pMediaType)); + MOCK_METHOD(HRESULT, GetCurrentDeviceMediaType, + (DWORD dwSourceStreamIndex, IMFMediaType** ppMediaType)); + MOCK_METHOD(HRESULT, GetDeviceStreamCount, (DWORD * pdwStreamCount)); + MOCK_METHOD(HRESULT, GetDeviceStreamCategory, + (DWORD dwSourceStreamIndex, + MF_CAPTURE_ENGINE_STREAM_CATEGORY* pStreamCategory)); + MOCK_METHOD(HRESULT, GetMirrorState, + (DWORD dwStreamIndex, BOOL* pfMirrorState)); + MOCK_METHOD(HRESULT, SetMirrorState, + (DWORD dwStreamIndex, BOOL fMirrorState)); + MOCK_METHOD(HRESULT, GetStreamIndexFromFriendlyName, + (UINT32 uifriendlyName, DWORD* pdwActualStreamIndex)); + + private: + volatile ULONG ref_; +}; + // Uses IMFMediaSourceEx which has SetD3DManager method. -class FakeMediaSource : public IMFMediaSourceEx { +class MockMediaSource : public IMFMediaSourceEx { public: - FakeMediaSource() : ref_(0){}; + MockMediaSource() : ref_(0){}; + ~MockMediaSource() = default; // IUnknown STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } @@ -371,22 +437,396 @@ class FakeMediaSource : public IMFMediaSourceEx { HRESULT SetD3DManager(IUnknown* manager) { return S_OK; } private: - ~FakeMediaSource() = default; volatile ULONG ref_; }; +// TODO: Implement fake IMFCapturePreviewSink instead +class MockCapturePreviewSink : public IMFCapturePreviewSink { + public: + // IMFCaptureSink + MOCK_METHOD(HRESULT, GetOutputMediaType, + (DWORD dwSinkStreamIndex, IMFMediaType** ppMediaType)); + + // IMFCaptureSink + MOCK_METHOD(HRESULT, GetService, + (DWORD dwSinkStreamIndex, REFGUID rguidService, REFIID riid, + IUnknown** ppUnknown)); + + // IMFCaptureSink + MOCK_METHOD(HRESULT, AddStream, + (DWORD dwSourceStreamIndex, IMFMediaType* pMediaType, + IMFAttributes* pAttributes, DWORD* pdwSinkStreamIndex)); + + // IMFCaptureSink + MOCK_METHOD(HRESULT, Prepare, ()); + + // IMFCaptureSink + MOCK_METHOD(HRESULT, RemoveAllStreams, ()); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, SetRenderHandle, (HANDLE handle)); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, SetRenderSurface, (IUnknown * pSurface)); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, UpdateVideo, + (const MFVideoNormalizedRect* pSrc, const RECT* pDst, + const COLORREF* pBorderClr)); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, SetSampleCallback, + (DWORD dwStreamSinkIndex, + IMFCaptureEngineOnSampleCallback* pCallback)); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, GetMirrorState, (BOOL * pfMirrorState)); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, SetMirrorState, (BOOL fMirrorState)); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, GetRotation, + (DWORD dwStreamIndex, DWORD* pdwRotationValue)); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, SetRotation, + (DWORD dwStreamIndex, DWORD dwRotationValue)); + + // IMFCapturePreviewSink + MOCK_METHOD(HRESULT, SetCustomSink, (IMFMediaSink * pMediaSink)); + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } + + // IUnknown + STDMETHODIMP_(ULONG) Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; + } + return ref; + } + + // IUnknown + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { + *ppv = nullptr; + + if (riid == IID_IMFCapturePreviewSink) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + void SendFakeSample(uint8_t* src_buffer, uint32_t size) { + assert(sample_callback_); + ComPtr sample; + ComPtr buffer; + HRESULT hr = MFCreateSample(&sample); + + if (SUCCEEDED(hr)) { + hr = MFCreateMemoryBuffer(size, &buffer); + } + + if (SUCCEEDED(hr)) { + uint8_t* target_data; + if (SUCCEEDED(buffer->Lock(&target_data, nullptr, nullptr))) { + std::copy(src_buffer, src_buffer + size, target_data); + } + hr = buffer->Unlock(); + } + + if (SUCCEEDED(hr)) { + hr = buffer->SetCurrentLength(size); + } + + if (SUCCEEDED(hr)) { + hr = sample->AddBuffer(buffer.Get()); + } + + if (SUCCEEDED(hr)) { + sample_callback_->OnSample(sample.Get()); + } + } + + ComPtr sample_callback_; + + private: + ~MockCapturePreviewSink() = default; + volatile ULONG ref_; +}; + +template +class FakeIMFAttributesBase : public T { + static_assert(std::is_base_of::value, + "I must inherit from IMFAttributes"); + + // IIMFAttributes + HRESULT GetItem(REFGUID guidKey, PROPVARIANT* pValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetItemType(REFGUID guidKey, MF_ATTRIBUTE_TYPE* pType) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT CompareItem(REFGUID guidKey, REFPROPVARIANT Value, + BOOL* pbResult) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT Compare(IMFAttributes* pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, + BOOL* pbResult) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetUINT32(REFGUID guidKey, UINT32* punValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetUINT64(REFGUID guidKey, UINT64* punValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetDouble(REFGUID guidKey, double* pfValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetGUID(REFGUID guidKey, GUID* pguidValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetStringLength(REFGUID guidKey, UINT32* pcchLength) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetString(REFGUID guidKey, LPWSTR pwszValue, UINT32 cchBufSize, + UINT32* pcchLength) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetAllocatedString(REFGUID guidKey, LPWSTR* ppwszValue, + UINT32* pcchLength) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetBlobSize(REFGUID guidKey, UINT32* pcbBlobSize) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetBlob(REFGUID guidKey, UINT8* pBuf, UINT32 cbBufSize, + UINT32* pcbBlobSize) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetAllocatedBlob(REFGUID guidKey, UINT8** ppBuf, + UINT32* pcbSize) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT GetUnknown(REFGUID guidKey, REFIID riid, + __RPC__deref_out_opt LPVOID* ppv) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT SetItem(REFGUID guidKey, REFPROPVARIANT Value) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT DeleteItem(REFGUID guidKey) override { return E_NOTIMPL; } + + // IIMFAttributes + HRESULT DeleteAllItems(void) override { return E_NOTIMPL; } + + // IIMFAttributes + HRESULT SetUINT32(REFGUID guidKey, UINT32 unValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT SetUINT64(REFGUID guidKey, UINT64 unValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT SetDouble(REFGUID guidKey, double fValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT SetGUID(REFGUID guidKey, REFGUID guidValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT SetString(REFGUID guidKey, LPCWSTR wszValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT SetBlob(REFGUID guidKey, const UINT8* pBuf, + UINT32 cbBufSize) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT SetUnknown(REFGUID guidKey, IUnknown* pUnknown) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT LockStore(void) override { return E_NOTIMPL; } + + // IIMFAttributes + HRESULT UnlockStore(void) override { return E_NOTIMPL; } + + // IIMFAttributes + HRESULT GetCount(UINT32* pcItems) override { return E_NOTIMPL; } + + // IIMFAttributes + HRESULT GetItemByIndex(UINT32 unIndex, GUID* pguidKey, + PROPVARIANT* pValue) override { + return E_NOTIMPL; + } + + // IIMFAttributes + HRESULT CopyAllItems(IMFAttributes* pDest) override { return E_NOTIMPL; } +}; + +class FakeMediaType : public FakeIMFAttributesBase { + public: + FakeMediaType(GUID major_type, GUID sub_type, int width, int height) + : ref_(0), + major_type_(major_type), + sub_type_(sub_type), + width_(width), + height_(height){}; + + // IMFAttributes + HRESULT GetUINT64(REFGUID key, UINT64* value) override { + if (key == MF_MT_FRAME_SIZE) { + *value = (int64_t)width_ << 32 | (int64_t)height_; + return S_OK; + } + return E_FAIL; + }; + + // IMFAttributes + HRESULT GetGUID(REFGUID key, GUID* value) override { + if (key == MF_MT_MAJOR_TYPE) { + *value = major_type_; + return S_OK; + } else if (key == MF_MT_SUBTYPE) { + *value = sub_type_; + return S_OK; + } + return E_FAIL; + } + + // IIMFAttributes + HRESULT CopyAllItems(IMFAttributes* pDest) override { + pDest->SetUINT64(MF_MT_FRAME_SIZE, + (int64_t)width_ << 32 | (int64_t)height_); + pDest->SetGUID(MF_MT_MAJOR_TYPE, major_type_); + pDest->SetGUID(MF_MT_SUBTYPE, sub_type_); + return S_OK; + } + + // IMFMediaType + HRESULT STDMETHODCALLTYPE GetMajorType(GUID* pguidMajorType) override { + return E_NOTIMPL; + }; + + // IMFMediaType + HRESULT STDMETHODCALLTYPE IsCompressedFormat(BOOL* pfCompressed) override { + return E_NOTIMPL; + } + + // IMFMediaType + HRESULT STDMETHODCALLTYPE IsEqual(IMFMediaType* pIMediaType, + DWORD* pdwFlags) override { + return E_NOTIMPL; + } + + // IMFMediaType + HRESULT STDMETHODCALLTYPE GetRepresentation( + GUID guidRepresentation, LPVOID* ppvRepresentation) override { + return E_NOTIMPL; + } + + // IMFMediaType + HRESULT STDMETHODCALLTYPE FreeRepresentation( + GUID guidRepresentation, LPVOID pvRepresentation) override { + return E_NOTIMPL; + } + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } + + // IUnknown + STDMETHODIMP_(ULONG) Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; + } + return ref; + } + + // IUnknown + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { + *ppv = nullptr; + + if (riid == IID_IMFMediaType) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + private: + ~FakeMediaType() = default; + volatile ULONG ref_; + const GUID major_type_; + const GUID sub_type_; + const int width_; + const int height_; +}; + class MockCaptureEngine : public IMFCaptureEngine { public: MockCaptureEngine() : ref_(0) { ON_CALL(*this, Initialize) .WillByDefault([this](IMFCaptureEngineOnEventCallback* callback, - IMFAttributes* attributes, IUnknown* audio_Source, - IUnknown* video_source) -> HRESULT { + IMFAttributes* attributes, IUnknown* audioSource, + IUnknown* videoSource) -> HRESULT { EXPECT_TRUE(callback); EXPECT_TRUE(attributes); - EXPECT_TRUE(video_source); - // audio_source is allowed to be nullptr; + EXPECT_TRUE(videoSource); + // audioSource is allowed to be nullptr; callback_ = callback; + videoSource_ = reinterpret_cast(videoSource); + audioSource_ = reinterpret_cast(audioSource); initialized_ = true; return S_OK; }); @@ -396,8 +836,8 @@ class MockCaptureEngine : public IMFCaptureEngine { MOCK_METHOD(HRESULT, Initialize, (IMFCaptureEngineOnEventCallback * callback, - IMFAttributes* attributes, IUnknown* audio_Source, - IUnknown* video_source)); + IMFAttributes* attributes, IUnknown* audioSource, + IUnknown* videoSource)); MOCK_METHOD(HRESULT, StartPreview, ()); MOCK_METHOD(HRESULT, StopPreview, ()); MOCK_METHOD(HRESULT, StartRecord, ()); @@ -443,6 +883,8 @@ class MockCaptureEngine : public IMFCaptureEngine { } ComPtr callback_; + ComPtr videoSource_; + ComPtr audioSource_; volatile ULONG ref_; bool initialized_ = false; }; From 1827226589c43d45ba8de403affc01e916febb8d Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 17 Jan 2022 14:42:49 +0200 Subject: [PATCH 17/93] [camera_windows] Add start record tests --- .../windows/test/capture_controller_test.cpp | 286 ++++++++++-------- .../camera_windows/windows/test/mocks.h | 169 ++++++++--- 2 files changed, 292 insertions(+), 163 deletions(-) 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 bc006fb15f18..a0440e19c1c9 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -18,6 +18,7 @@ #include #include "mocks.h" +#include "string_utils.h" namespace camera_windows { @@ -28,10 +29,10 @@ using ::testing::_; using ::testing::Eq; using ::testing::Return; -void InitCaptureController(CaptureControllerImpl* capture_controller, - MockTextureRegistrar* texture_registrar, - MockCaptureEngine* engine, MockCamera* camera, - int64_t mock_texture_id) { +void MockInitCaptureController(CaptureControllerImpl* capture_controller, + MockTextureRegistrar* texture_registrar, + MockCaptureEngine* engine, MockCamera* camera, + int64_t mock_texture_id) { ComPtr video_source = new MockMediaSource(); ComPtr audio_source = new MockMediaSource(); @@ -68,6 +69,88 @@ void InitCaptureController(CaptureControllerImpl* capture_controller, engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_INITIALIZED); } +void MockStartPreview(CaptureControllerImpl* capture_controller, + MockCaptureSource* capture_source, + MockCapturePreviewSink* preview_sink, + MockTextureRegistrar* texture_registrar, + MockCaptureEngine* engine, MockCamera* camera, + std::unique_ptr mock_source_buffer, + uint32_t mock_source_buffer_size, + uint32_t mock_preview_width, uint32_t mock_preview_height, + int64_t mock_texture_id) { + EXPECT_CALL(*engine, GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _)) + .Times(1) + .WillOnce([src_sink = preview_sink](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, + IMFCaptureSink** target_sink) { + *target_sink = src_sink; + src_sink->AddRef(); + return S_OK; + }); + + EXPECT_CALL(*preview_sink, RemoveAllStreams).Times(1).WillOnce(Return(S_OK)); + EXPECT_CALL(*preview_sink, AddStream).Times(1).WillOnce(Return(S_OK)); + EXPECT_CALL(*preview_sink, SetSampleCallback) + .Times(1) + .WillOnce([sink = preview_sink]( + DWORD dwStreamSinkIndex, + IMFCaptureEngineOnSampleCallback* pCallback) -> HRESULT { + sink->sample_callback_ = pCallback; + return S_OK; + }); + + EXPECT_CALL(*engine, GetSource) + .Times(1) + .WillOnce( + [src_source = capture_source](IMFCaptureSource** target_source) { + *target_source = src_source; + src_source->AddRef(); + return S_OK; + }); + + EXPECT_CALL( + *capture_source, + GetAvailableDeviceMediaType( + Eq((DWORD) + MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW), + _, _)) + .WillRepeatedly([mock_preview_width, mock_preview_height]( + DWORD stream_index, DWORD media_type_index, + IMFMediaType** media_type) { + // We give only one media type to loop through + if (media_type_index != 0) return MF_E_NO_MORE_TYPES; + *media_type = + new FakeMediaType(MFMediaType_Video, MFVideoFormat_RGB32, + mock_preview_width, mock_preview_height); + (*media_type)->AddRef(); + return S_OK; + }); + + EXPECT_CALL(*engine, StartPreview()).Times(1).WillOnce(Return(S_OK)); + + // Called by destructor + EXPECT_CALL(*engine, StopPreview()).Times(1).WillOnce(Return(S_OK)); + + // Called after first processed sample + EXPECT_CALL(*camera, + OnStartPreviewSucceeded(mock_preview_width, mock_preview_height)) + .Times(1); + EXPECT_CALL(*camera, OnStartPreviewFailed).Times(0); + EXPECT_CALL(*texture_registrar, MarkTextureFrameAvailable(mock_texture_id)) + .Times(1); + + capture_controller->StartPreview(); + + EXPECT_EQ(capture_controller->GetPreviewHeight(), mock_preview_height); + EXPECT_EQ(capture_controller->GetPreviewWidth(), mock_preview_width); + + // Capture engine is now started and will first send event of started preview + engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_PREVIEW_STARTED); + + // SendFake sample + preview_sink->SendFakeSample(mock_source_buffer.get(), + mock_source_buffer_size); +} + TEST(CaptureController, InitCaptureEngineCallsOnCreateCaptureEngineSucceededWithTextureId) { ComPtr engine = new MockCaptureEngine(); @@ -78,48 +161,15 @@ TEST(CaptureController, std::unique_ptr texture_registrar = std::make_unique(); - ComPtr video_source = new MockMediaSource(); - ComPtr audio_source = new MockMediaSource(); - - capture_controller->SetCaptureEngine( - reinterpret_cast(engine.Get())); - capture_controller->SetVideoSource( - reinterpret_cast(video_source.Get())); - capture_controller->SetAudioSource( - reinterpret_cast(audio_source.Get())); - - int64_t mock_texture_id = 1000; - - EXPECT_CALL(*texture_registrar, RegisterTexture) - .Times(1) - .WillOnce([reg = texture_registrar.get(), - mock_texture_id](flutter::TextureVariant* texture) -> int64_t { - EXPECT_TRUE(texture); - reg->texture_ = texture; - reg->texture_id_ = mock_texture_id; - return reg->texture_id_; - }); - EXPECT_CALL(*texture_registrar, UnregisterTexture(Eq(mock_texture_id))) - .Times(1); - EXPECT_CALL(*camera, OnCreateCaptureEngineFailed).Times(0); - EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded(Eq(mock_texture_id))) - .Times(1); - EXPECT_CALL(*(engine.Get()), Initialize).Times(1); - - capture_controller->InitCaptureDevice( - texture_registrar.get(), MOCK_DEVICE_ID, true, - ResolutionPreset::RESOLUTION_PRESET_AUTO); - - // MockCaptureEngine::Initialize is called - EXPECT_TRUE(engine->initialized_); + uint64_t mock_texture_id = 1234; - engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_INITIALIZED); + // Init capture controller with mocks and tests + MockInitCaptureController(capture_controller.get(), texture_registrar.get(), + engine.Get(), camera.get(), mock_texture_id); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; - video_source = nullptr; - audio_source = nullptr; engine = nullptr; } @@ -133,9 +183,10 @@ TEST(CaptureController, StartPreviewStartsProcessingSamples) { std::make_unique(); uint64_t mock_texture_id = 1234; - // Init capture controller to be ready for testing - InitCaptureController(capture_controller.get(), texture_registrar.get(), - engine.Get(), camera.get(), mock_texture_id); + + // Initialize capture controller to be able to start preview + MockInitCaptureController(capture_controller.get(), texture_registrar.get(), + engine.Get(), camera.get(), mock_texture_id); ComPtr preview_sink = new MockCapturePreviewSink(); ComPtr capture_source = new MockCaptureSource(); @@ -164,80 +215,12 @@ TEST(CaptureController, StartPreviewStartsProcessingSamples) { mock_source_buffer_data[i].b = mock_blue_pixel; } - EXPECT_CALL(*(engine.Get()), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _)) - .Times(1) - .WillOnce( - [src_sink = preview_sink.Get()](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, - IMFCaptureSink** target_sink) { - *target_sink = src_sink; - src_sink->AddRef(); - return S_OK; - }); - - EXPECT_CALL(*(preview_sink.Get()), RemoveAllStreams) - .Times(1) - .WillOnce(Return(S_OK)); - EXPECT_CALL(*(preview_sink.Get()), AddStream).Times(1).WillOnce(Return(S_OK)); - EXPECT_CALL(*(preview_sink.Get()), SetSampleCallback) - .Times(1) - .WillOnce([sink = preview_sink.Get()]( - DWORD dwStreamSinkIndex, - IMFCaptureEngineOnSampleCallback* pCallback) -> HRESULT { - sink->sample_callback_ = pCallback; - return S_OK; - }); - - EXPECT_CALL(*(engine.Get()), GetSource) - .Times(1) - .WillOnce([src_source = - capture_source.Get()](IMFCaptureSource** target_source) { - *target_source = src_source; - src_source->AddRef(); - return S_OK; - }); - - EXPECT_CALL( - *(capture_source.Get()), - GetAvailableDeviceMediaType( - Eq((DWORD) - MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW), - _, _)) - .WillRepeatedly([mock_preview_width, mock_preview_height]( - DWORD stream_index, DWORD media_type_index, - IMFMediaType** media_type) { - // We give only one media type to loop through - if (media_type_index != 0) return MF_E_NO_MORE_TYPES; - *media_type = - new FakeMediaType(MFMediaType_Video, MFVideoFormat_RGB32, - mock_preview_width, mock_preview_height); - (*media_type)->AddRef(); - return S_OK; - }); - - EXPECT_CALL(*(engine.Get()), StartPreview()).Times(1).WillOnce(Return(S_OK)); - - // Called by destructor - EXPECT_CALL(*(engine.Get()), StopPreview()).Times(1).WillOnce(Return(S_OK)); - - // Called after first processed sample - EXPECT_CALL(*camera, - OnStartPreviewSucceeded(mock_preview_width, mock_preview_height)) - .Times(1); - EXPECT_CALL(*camera, OnStartPreviewFailed).Times(0); - EXPECT_CALL(*texture_registrar, MarkTextureFrameAvailable(mock_texture_id)) - .Times(1); - - capture_controller->StartPreview(); - - EXPECT_EQ(capture_controller->GetPreviewHeight(), mock_preview_height); - EXPECT_EQ(capture_controller->GetPreviewWidth(), mock_preview_width); - - // Capture engine is now started and will first send event of started preview - engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_PREVIEW_STARTED); - - // SendFake sample - preview_sink->SendFakeSample(mock_source_buffer.get(), - mock_texture_data_size); + // Start preview and run preview tests + MockStartPreview(capture_controller.get(), capture_source.Get(), + preview_sink.Get(), texture_registrar.get(), engine.Get(), + camera.get(), std::move(mock_source_buffer), + mock_texture_data_size, mock_preview_width, + mock_preview_height, mock_texture_id); // Test texture processing EXPECT_TRUE(texture_registrar->texture_); @@ -273,7 +256,72 @@ TEST(CaptureController, StartPreviewStartsProcessingSamples) { engine = nullptr; camera = nullptr; texture_registrar = nullptr; - mock_source_buffer = nullptr; +} + +TEST(CaptureController, StartRecordSuccess) { + 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(); + + uint64_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); + + ComPtr preview_sink = new MockCapturePreviewSink(); + ComPtr capture_source = new MockCaptureSource(); + + std::unique_ptr mock_source_buffer = + std::make_unique(0); + + // Start preview to be able to start record + MockStartPreview(capture_controller.get(), capture_source.Get(), + preview_sink.Get(), texture_registrar.get(), engine.Get(), + camera.get(), std::move(mock_source_buffer), 0, 1, 1, + mock_texture_id); + + // Start record + ComPtr record_sink = new MockCaptureRecordSink(); + std::string mock_path_to_file = "mock_path_to_file"; + EXPECT_CALL(*(engine.Get()), StartRecord()).Times(1).WillOnce(Return(S_OK)); + + EXPECT_CALL(*(engine.Get()), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, _)) + .Times(1) + .WillOnce( + [src_sink = record_sink.Get()](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, + IMFCaptureSink** target_sink) { + *target_sink = src_sink; + src_sink->AddRef(); + return S_OK; + }); + + EXPECT_CALL(*(record_sink.Get()), RemoveAllStreams) + .Times(1) + .WillOnce(Return(S_OK)); + EXPECT_CALL(*(record_sink.Get()), AddStream).Times(1).WillOnce(Return(S_OK)); + EXPECT_CALL(*(record_sink.Get()), SetOutputFileName) + .Times(1) + .WillOnce(Return(S_OK)); + + capture_controller->StartRecord(mock_path_to_file, -1); + + EXPECT_CALL(*camera, OnStartRecordSucceeded()).Times(1); + engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_RECORD_STARTED); + + // Called by destructor + EXPECT_CALL(*(engine.Get()), StopRecord(true, false)) + .Times(1) + .WillOnce(Return(S_OK)); + + capture_controller = nullptr; + engine = nullptr; + camera = nullptr; + texture_registrar = nullptr; } } // namespace test diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index d7c62f37461a..78bd33f97ec7 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -30,6 +30,8 @@ using ::testing::_; class MockMethodResult : public flutter::MethodResult<> { public: + ~MockMethodResult() = default; + MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result), (override)); MOCK_METHOD(void, ErrorInternal, @@ -41,6 +43,8 @@ class MockMethodResult : public flutter::MethodResult<> { class MockBinaryMessenger : public flutter::BinaryMessenger { public: + ~MockBinaryMessenger() = default; + MOCK_METHOD(void, Send, (const std::string& channel, const uint8_t* message, size_t message_size, flutter::BinaryReply reply), @@ -102,6 +106,12 @@ class MockCameraFactory : public CameraFactory { }); } + ~MockCameraFactory() = default; + + // Disallow copy and move. + MockCameraFactory(const MockCameraFactory&) = delete; + MockCameraFactory& operator=(const MockCameraFactory&) = delete; + MOCK_METHOD(std::unique_ptr, CreateCamera, (const std::string& device_id), (override)); @@ -112,8 +122,10 @@ class MockCamera : public Camera { public: MockCamera(const std::string& device_id) : device_id_(device_id), Camera(device_id){}; + ~MockCamera() = default; + // Disallow copy and move. MockCamera(const MockCamera&) = delete; MockCamera& operator=(const MockCamera&) = delete; @@ -196,6 +208,8 @@ class MockCaptureControllerFactory : public CaptureControllerFactory { class MockCaptureController : public CaptureController { public: + ~MockCaptureController() = default; + MOCK_METHOD(void, InitCaptureDevice, (flutter::TextureRegistrar * texture_registrar, const std::string& device_id, bool enable_audio, @@ -233,6 +247,9 @@ class MockCameraPlugin : public CameraPlugin { std::unique_ptr camera_factory) : CameraPlugin(texture_registrar, messenger, std::move(camera_factory)){}; + ~MockCameraPlugin() = default; + + // Disallow copy and move. MockCameraPlugin(const MockCameraPlugin&) = delete; MockCameraPlugin& operator=(const MockCameraPlugin&) = delete; @@ -246,49 +263,6 @@ class MockCameraPlugin : public CameraPlugin { } }; -class MockCaptureEngineListener : public IMFCaptureEngineOnSampleCallback, - public IMFCaptureEngineOnEventCallback { - MockCaptureEngineListener(CaptureEngineObserver* observer) - : ref_(1), observer_(observer) {} - - // IUnknown - STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } - - // IUnknown - STDMETHODIMP_(ULONG) Release() { - LONG ref = InterlockedDecrement(&ref_); - if (ref == 0) { - delete this; - } - return ref; - } - - // IUnknown - STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { - *ppv = nullptr; - - if (riid == IID_IMFCaptureEngineOnEventCallback) { - *ppv = static_cast(this); - ((IUnknown*)*ppv)->AddRef(); - return S_OK; - } else if (riid == IID_IMFCaptureEngineOnSampleCallback) { - *ppv = static_cast(this); - ((IUnknown*)*ppv)->AddRef(); - return S_OK; - } - - return E_NOINTERFACE; - } - - MOCK_METHOD(HRESULT, OnEvent, (IMFMediaEvent * pEvent)); - MOCK_METHOD(HRESULT, OnSample, (IMFSample * pSample)); - CaptureEngineObserver* observer_; - - private: - volatile ULONG ref_; -}; -class MockCaptureControllerListener {}; - class MockCaptureSource : public IMFCaptureSource { public: MockCaptureSource() : ref_(0){}; @@ -440,7 +414,6 @@ class MockMediaSource : public IMFMediaSourceEx { volatile ULONG ref_; }; -// TODO: Implement fake IMFCapturePreviewSink instead class MockCapturePreviewSink : public IMFCapturePreviewSink { public: // IMFCaptureSink @@ -559,6 +532,114 @@ class MockCapturePreviewSink : public IMFCapturePreviewSink { volatile ULONG ref_; }; +class MockCaptureRecordSink : public IMFCaptureRecordSink { + public: + // IMFCaptureSink + MOCK_METHOD(HRESULT, GetOutputMediaType, + (DWORD dwSinkStreamIndex, IMFMediaType** ppMediaType)); + + // IMFCaptureSink + MOCK_METHOD(HRESULT, GetService, + (DWORD dwSinkStreamIndex, REFGUID rguidService, REFIID riid, + IUnknown** ppUnknown)); + + // IMFCaptureSink + MOCK_METHOD(HRESULT, AddStream, + (DWORD dwSourceStreamIndex, IMFMediaType* pMediaType, + IMFAttributes* pAttributes, DWORD* pdwSinkStreamIndex)); + + // IMFCaptureSink + MOCK_METHOD(HRESULT, Prepare, ()); + + // IMFCaptureSink + MOCK_METHOD(HRESULT, RemoveAllStreams, ()); + + // IMFCaptureRecordSink + MOCK_METHOD(HRESULT, SetOutputByteStream, + (IMFByteStream * pByteStream, REFGUID guidContainerType)); + + // IMFCaptureRecordSink + MOCK_METHOD(HRESULT, SetOutputFileName, (LPCWSTR fileName)); + + // IMFCaptureRecordSink + MOCK_METHOD(HRESULT, SetSampleCallback, + (DWORD dwStreamSinkIndex, + IMFCaptureEngineOnSampleCallback* pCallback)); + + // IMFCaptureRecordSink + MOCK_METHOD(HRESULT, SetCustomSink, (IMFMediaSink * pMediaSink)); + + // IMFCaptureRecordSink + MOCK_METHOD(HRESULT, GetRotation, + (DWORD dwStreamIndex, DWORD* pdwRotationValue)); + + // IMFCaptureRecordSink + MOCK_METHOD(HRESULT, SetRotation, + (DWORD dwStreamIndex, DWORD dwRotationValue)); + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } + + // IUnknown + STDMETHODIMP_(ULONG) Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; + } + return ref; + } + + // IUnknown + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { + *ppv = nullptr; + + if (riid == IID_IMFCaptureRecordSink) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + void SendFakeSample(uint8_t* src_buffer, uint32_t size) { + assert(sample_callback_); + ComPtr sample; + ComPtr buffer; + HRESULT hr = MFCreateSample(&sample); + + if (SUCCEEDED(hr)) { + hr = MFCreateMemoryBuffer(size, &buffer); + } + + if (SUCCEEDED(hr)) { + uint8_t* target_data; + if (SUCCEEDED(buffer->Lock(&target_data, nullptr, nullptr))) { + std::copy(src_buffer, src_buffer + size, target_data); + } + hr = buffer->Unlock(); + } + + if (SUCCEEDED(hr)) { + hr = buffer->SetCurrentLength(size); + } + + if (SUCCEEDED(hr)) { + hr = sample->AddBuffer(buffer.Get()); + } + + if (SUCCEEDED(hr)) { + sample_callback_->OnSample(sample.Get()); + } + } + + ComPtr sample_callback_; + + private: + ~MockCaptureRecordSink() = default; + volatile ULONG ref_; +}; + template class FakeIMFAttributesBase : public T { static_assert(std::is_base_of::value, From b3912264faab24d931287038797153d20de85207 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 17 Jan 2022 17:54:33 +0200 Subject: [PATCH 18/93] [camera_windows] Add tests to rest of the capture_controller success paths --- .../camera/camera_windows/windows/camera.cpp | 8 +- .../camera/camera_windows/windows/camera.h | 8 +- .../windows/capture_controller.cpp | 16 +- .../windows/capture_controller_listener.h | 8 +- .../windows/test/camera_test.cpp | 12 +- .../windows/test/capture_controller_test.cpp | 194 ++++++++++++++++-- .../camera_windows/windows/test/mocks.h | 87 +++++--- 7 files changed, 260 insertions(+), 73 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index 70f125d1afa2..e196c6ec8316 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -221,7 +221,7 @@ void CameraImpl::OnStopRecordFailed(const std::string &error) { }; // From CaptureControllerListener -void CameraImpl::OnPictureSuccess(const std::string &filepath) { +void CameraImpl::OnTakePictureSucceeded(const std::string &filepath) { auto pending_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); if (pending_result) { pending_result->Success(EncodableValue(filepath)); @@ -229,7 +229,7 @@ void CameraImpl::OnPictureSuccess(const std::string &filepath) { }; // From CaptureControllerListener -void CameraImpl::OnPictureFailed(const std::string &error) { +void CameraImpl::OnTakePictureFailed(const std::string &error) { auto pending_take_picture_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); if (pending_take_picture_result) { @@ -238,7 +238,7 @@ void CameraImpl::OnPictureFailed(const std::string &error) { }; // From CaptureControllerListener -void CameraImpl::OnVideoRecordedSuccess(const std::string &filepath, +void CameraImpl::OnVideoRecordSucceeded(const std::string &filepath, int64_t video_duration) { if (messenger_ && camera_id_ >= 0) { auto channel = BuildChannelForCamera(messenger_, camera_id_); @@ -254,7 +254,7 @@ void CameraImpl::OnVideoRecordedSuccess(const std::string &filepath, } // From CaptureControllerListener -void CameraImpl::OnVideoRecordedFailed(const std::string &error){}; +void CameraImpl::OnVideoRecordFailed(const std::string &error){}; void CameraImpl::OnCaptureError(const std::string &error) { if (messenger_ && camera_id_ >= 0) { diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 913b94b0ecf7..ddec8da52a03 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -73,11 +73,11 @@ class CameraImpl : public Camera { void OnStartRecordFailed(const std::string &error) override; void OnStopRecordSucceeded(const std::string &filepath) override; void OnStopRecordFailed(const std::string &error) override; - void OnPictureSuccess(const std::string &filepath) override; - void OnPictureFailed(const std::string &error) override; - void OnVideoRecordedSuccess(const std::string &filepath, + void OnTakePictureSucceeded(const std::string &filepath) override; + void OnTakePictureFailed(const std::string &error) override; + void OnVideoRecordSucceeded(const std::string &filepath, int64_t video_duration) override; - void OnVideoRecordedFailed(const std::string &error) override; + void OnVideoRecordFailed(const std::string &error) override; void OnCaptureError(const std::string &error) override; // From Camera diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 265b15bb2dd9..add030622324 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -498,11 +498,11 @@ void CaptureControllerImpl::TakePicture(const std::string filepath) { assert(capture_controller_listener_); if (!initialized_) { - return capture_controller_listener_->OnPictureFailed("Not initialized"); + return capture_controller_listener_->OnTakePictureFailed("Not initialized"); } if (pending_image_capture_) { - return capture_controller_listener_->OnPictureFailed( + return capture_controller_listener_->OnTakePictureFailed( "Already capturing image"); } @@ -518,7 +518,7 @@ void CaptureControllerImpl::TakePicture(const std::string filepath) { if (FAILED(hr)) { pending_image_capture_ = false; pending_picture_path_ = std::string(); - return capture_controller_listener_->OnPictureFailed( + return capture_controller_listener_->OnTakePictureFailed( "Failed to take picture"); } } @@ -877,7 +877,7 @@ void CaptureControllerImpl::StopTimedRecord() { if (FAILED(hr)) { record_stop_pending_ = false; recording_ = false; - return capture_controller_listener_->OnVideoRecordedFailed( + return capture_controller_listener_->OnVideoRecordFailed( "Failed to record video"); } } @@ -1007,9 +1007,9 @@ void CaptureControllerImpl::OnEvent(IMFMediaEvent *event) { void CaptureControllerImpl::OnPicture(bool success, const std::string &error) { if (capture_controller_listener_) { if (success && !pending_picture_path_.empty()) { - capture_controller_listener_->OnPictureSuccess(pending_picture_path_); + capture_controller_listener_->OnTakePictureSucceeded(pending_picture_path_); } else { - capture_controller_listener_->OnPictureFailed(error); + capture_controller_listener_->OnTakePictureFailed(error); } } pending_image_capture_ = false; @@ -1111,11 +1111,11 @@ void CaptureControllerImpl::OnRecordStopped(bool success, if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { if (success && !pending_record_path_.empty()) { - capture_controller_listener_->OnVideoRecordedSuccess( + capture_controller_listener_->OnVideoRecordSucceeded( pending_record_path_, (recording_duration_us_ / 1000)); } else { - capture_controller_listener_->OnVideoRecordedFailed(error); + capture_controller_listener_->OnVideoRecordFailed(error); } } } diff --git a/packages/camera/camera_windows/windows/capture_controller_listener.h b/packages/camera/camera_windows/windows/capture_controller_listener.h index ae4fce39c921..50ca5f5bc926 100644 --- a/packages/camera/camera_windows/windows/capture_controller_listener.h +++ b/packages/camera/camera_windows/windows/capture_controller_listener.h @@ -31,12 +31,12 @@ class CaptureControllerListener { virtual void OnStopRecordSucceeded(const std::string& filepath) = 0; virtual void OnStopRecordFailed(const std::string& error) = 0; - virtual void OnPictureSuccess(const std::string& filepath) = 0; - virtual void OnPictureFailed(const std::string& error) = 0; + virtual void OnTakePictureSucceeded(const std::string& filepath) = 0; + virtual void OnTakePictureFailed(const std::string& error) = 0; - virtual void OnVideoRecordedSuccess(const std::string& filepath, + virtual void OnVideoRecordSucceeded(const std::string& filepath, int64_t video_duration) = 0; - virtual void OnVideoRecordedFailed(const std::string& error) = 0; + virtual void OnVideoRecordFailed(const std::string& error) = 0; virtual void OnCaptureError(const std::string& error) = 0; }; diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp index 39e3ff3ff34c..2276169f4a5f 100644 --- a/packages/camera/camera_windows/windows/test/camera_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -268,7 +268,7 @@ TEST(Camera, OnStopRecordFailedReturnsError) { camera->OnStopRecordFailed(error_text); } -TEST(Camera, OnPictureSuccessReturnsSuccess) { +TEST(Camera, OnTakePictureSucceededReturnsSuccess) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = @@ -281,10 +281,10 @@ TEST(Camera, OnPictureSuccessReturnsSuccess) { camera->AddPendingResult(PendingResultType::TAKE_PICTURE, std::move(result)); - camera->OnPictureSuccess(filepath); + camera->OnTakePictureSucceeded(filepath); } -TEST(Camera, OnPictureFailedReturnsError) { +TEST(Camera, OnTakePictureFailedReturnsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = @@ -297,10 +297,10 @@ TEST(Camera, OnPictureFailedReturnsError) { camera->AddPendingResult(PendingResultType::TAKE_PICTURE, std::move(result)); - camera->OnPictureFailed(error_text); + camera->OnTakePictureFailed(error_text); } -TEST(Camera, OnVideoRecordedSuccessInvokesCameraChannelEvent) { +TEST(Camera, OnVideoRecordSucceededInvokesCameraChannelEvent) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller_factory = @@ -332,7 +332,7 @@ TEST(Camera, OnVideoRecordedSuccessInvokesCameraChannelEvent) { // Pass camera id for camera camera->OnCreateCaptureEngineSucceeded(camera_id); - camera->OnVideoRecordedSuccess(filepath, video_duration); + camera->OnVideoRecordSucceeded(filepath, video_duration); } } // namespace test 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 a0440e19c1c9..388ae7137398 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -151,6 +151,31 @@ void MockStartPreview(CaptureControllerImpl* capture_controller, mock_source_buffer_size); } +void MockRecordStart(CaptureControllerImpl* capture_controller, + MockCaptureEngine* engine, + MockCaptureRecordSink* record_sink, MockCamera* camera, + const std::string& mock_path_to_video) { + EXPECT_CALL(*engine, StartRecord()).Times(1).WillOnce(Return(S_OK)); + + EXPECT_CALL(*engine, GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, _)) + .Times(1) + .WillOnce([src_sink = record_sink](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, + IMFCaptureSink** target_sink) { + *target_sink = src_sink; + src_sink->AddRef(); + return S_OK; + }); + + EXPECT_CALL(*record_sink, RemoveAllStreams).Times(1).WillOnce(Return(S_OK)); + EXPECT_CALL(*record_sink, AddStream).Times(1).WillOnce(Return(S_OK)); + EXPECT_CALL(*record_sink, SetOutputFileName).Times(1).WillOnce(Return(S_OK)); + + capture_controller->StartRecord(mock_path_to_video, -1); + + EXPECT_CALL(*camera, OnStartRecordSucceeded()).Times(1); + engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_RECORD_STARTED); +} + TEST(CaptureController, InitCaptureEngineCallsOnCreateCaptureEngineSucceededWithTextureId) { ComPtr engine = new MockCaptureEngine(); @@ -191,7 +216,8 @@ TEST(CaptureController, StartPreviewStartsProcessingSamples) { ComPtr preview_sink = new MockCapturePreviewSink(); ComPtr capture_source = new MockCaptureSource(); - // Let's keep these small for mock texture data. Two pixels should be enough. + // Let's keep these small for mock texture data. Two pixels should be + // enough. uint32_t mock_preview_width = 2; uint32_t mock_preview_height = 1; uint32_t pixels_total = mock_preview_width * mock_preview_height; @@ -287,41 +313,173 @@ TEST(CaptureController, StartRecordSuccess) { // Start record ComPtr record_sink = new MockCaptureRecordSink(); - std::string mock_path_to_file = "mock_path_to_file"; - EXPECT_CALL(*(engine.Get()), StartRecord()).Times(1).WillOnce(Return(S_OK)); + std::string mock_path_to_video = "mock_path_to_video"; + MockRecordStart(capture_controller.get(), engine.Get(), record_sink.Get(), + camera.get(), mock_path_to_video); + + // Called by destructor + EXPECT_CALL(*(engine.Get()), StopRecord(true, false)) + .Times(1) + .WillOnce(Return(S_OK)); + + capture_controller = nullptr; + texture_registrar = nullptr; + engine = nullptr; + camera = nullptr; + record_sink = nullptr; +} + +TEST(CaptureController, StopRecordSuccess) { + 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(); + + uint64_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); + + ComPtr preview_sink = new MockCapturePreviewSink(); + ComPtr capture_source = new MockCaptureSource(); + + std::unique_ptr mock_source_buffer = + std::make_unique(0); + + // Start preview to be able to start record + MockStartPreview(capture_controller.get(), capture_source.Get(), + preview_sink.Get(), texture_registrar.get(), engine.Get(), + camera.get(), std::move(mock_source_buffer), 0, 1, 1, + mock_texture_id); + + // Start record + ComPtr record_sink = new MockCaptureRecordSink(); + std::string mock_path_to_video = "mock_path_to_video"; + MockRecordStart(capture_controller.get(), engine.Get(), record_sink.Get(), + camera.get(), mock_path_to_video); - EXPECT_CALL(*(engine.Get()), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, _)) + // Request to stop record + EXPECT_CALL(*(engine.Get()), StopRecord(true, false)) + .Times(1) + .WillOnce(Return(S_OK)); + capture_controller->StopRecord(); + + // OnStopRecordSucceeded should be called with mocked file path + EXPECT_CALL(*camera, OnStopRecordSucceeded(Eq(mock_path_to_video))).Times(1); + engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_RECORD_STOPPED); + + capture_controller = nullptr; + texture_registrar = nullptr; + engine = nullptr; + camera = nullptr; + record_sink = nullptr; +} + +TEST(CaptureController, TakePictureSuccess) { + 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(); + + uint64_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); + + ComPtr preview_sink = new MockCapturePreviewSink(); + ComPtr capture_source = new MockCaptureSource(); + + std::unique_ptr mock_source_buffer = + std::make_unique(0); + + // Start preview to be able to start record + MockStartPreview(capture_controller.get(), capture_source.Get(), + preview_sink.Get(), texture_registrar.get(), engine.Get(), + camera.get(), std::move(mock_source_buffer), 0, 1, 1, + mock_texture_id); + + // Init photo sink tests + ComPtr photo_sink = new MockCapturePhotoSink(); + EXPECT_CALL(*(engine.Get()), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, _)) .Times(1) .WillOnce( - [src_sink = record_sink.Get()](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, - IMFCaptureSink** target_sink) { + [src_sink = photo_sink.Get()](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, + IMFCaptureSink** target_sink) { *target_sink = src_sink; src_sink->AddRef(); return S_OK; }); - - EXPECT_CALL(*(record_sink.Get()), RemoveAllStreams) + EXPECT_CALL(*(photo_sink.Get()), RemoveAllStreams) .Times(1) .WillOnce(Return(S_OK)); - EXPECT_CALL(*(record_sink.Get()), AddStream).Times(1).WillOnce(Return(S_OK)); - EXPECT_CALL(*(record_sink.Get()), SetOutputFileName) + EXPECT_CALL(*(photo_sink.Get()), AddStream).Times(1).WillOnce(Return(S_OK)); + EXPECT_CALL(*(photo_sink.Get()), SetOutputFileName) .Times(1) .WillOnce(Return(S_OK)); - capture_controller->StartRecord(mock_path_to_file, -1); + // Request photo + std::string mock_path_to_photo = "mock_path_to_photo"; + EXPECT_CALL(*(engine.Get()), TakePhoto()).Times(1).WillOnce(Return(S_OK)); + capture_controller->TakePicture(mock_path_to_photo); - EXPECT_CALL(*camera, OnStartRecordSucceeded()).Times(1); - engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_RECORD_STARTED); - - // Called by destructor - EXPECT_CALL(*(engine.Get()), StopRecord(true, false)) - .Times(1) - .WillOnce(Return(S_OK)); + // OnTakePictureSucceeded should be called with mocked file path + EXPECT_CALL(*camera, OnTakePictureSucceeded(Eq(mock_path_to_photo))).Times(1); + engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_PHOTO_TAKEN); capture_controller = nullptr; + texture_registrar = nullptr; engine = nullptr; camera = nullptr; + photo_sink = nullptr; +} + + +TEST(CaptureController, PauseResumePreviewSuccess) { + 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(); + + uint64_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); + + ComPtr preview_sink = new MockCapturePreviewSink(); + ComPtr capture_source = new MockCaptureSource(); + + std::unique_ptr mock_source_buffer = + std::make_unique(0); + + // Start preview to be able to start record + MockStartPreview(capture_controller.get(), capture_source.Get(), + preview_sink.Get(), texture_registrar.get(), engine.Get(), + camera.get(), std::move(mock_source_buffer), 0, 1, 1, + mock_texture_id); + + EXPECT_CALL(*camera, OnPausePreviewSucceeded()).Times(1); + capture_controller->PausePreview(); + + + EXPECT_CALL(*camera, OnResumePreviewSucceeded()).Times(1); + capture_controller->ResumePreview(); + + capture_controller = nullptr; texture_registrar = nullptr; + engine = nullptr; + camera = nullptr; } } // namespace test diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 78bd33f97ec7..f7018545535c 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -157,14 +157,14 @@ class MockCamera : public Camera { (override)); MOCK_METHOD(void, OnStopRecordFailed, (const std::string& error), (override)); - MOCK_METHOD(void, OnPictureSuccess, (const std::string& filepath), + MOCK_METHOD(void, OnTakePictureSucceeded, (const std::string& filepath), (override)); - MOCK_METHOD(void, OnPictureFailed, (const std::string& error), (override)); + MOCK_METHOD(void, OnTakePictureFailed, (const std::string& error), (override)); - MOCK_METHOD(void, OnVideoRecordedSuccess, + MOCK_METHOD(void, OnVideoRecordSucceeded, (const std::string& filepath, int64_t video_duration), (override)); - MOCK_METHOD(void, OnVideoRecordedFailed, (const std::string& error), + MOCK_METHOD(void, OnVideoRecordFailed, (const std::string& error), (override)); MOCK_METHOD(void, OnCaptureError, (const std::string& error), (override)); @@ -602,41 +602,70 @@ class MockCaptureRecordSink : public IMFCaptureRecordSink { return E_NOINTERFACE; } - void SendFakeSample(uint8_t* src_buffer, uint32_t size) { - assert(sample_callback_); - ComPtr sample; - ComPtr buffer; - HRESULT hr = MFCreateSample(&sample); + private: + ~MockCaptureRecordSink() = default; + volatile ULONG ref_; +}; - if (SUCCEEDED(hr)) { - hr = MFCreateMemoryBuffer(size, &buffer); - } +class MockCapturePhotoSink : public IMFCapturePhotoSink { + public: + // IMFCaptureSink + MOCK_METHOD(HRESULT, GetOutputMediaType, + (DWORD dwSinkStreamIndex, IMFMediaType** ppMediaType)); - if (SUCCEEDED(hr)) { - uint8_t* target_data; - if (SUCCEEDED(buffer->Lock(&target_data, nullptr, nullptr))) { - std::copy(src_buffer, src_buffer + size, target_data); - } - hr = buffer->Unlock(); - } + // IMFCaptureSink + MOCK_METHOD(HRESULT, GetService, + (DWORD dwSinkStreamIndex, REFGUID rguidService, REFIID riid, + IUnknown** ppUnknown)); - if (SUCCEEDED(hr)) { - hr = buffer->SetCurrentLength(size); - } + // IMFCaptureSink + MOCK_METHOD(HRESULT, AddStream, + (DWORD dwSourceStreamIndex, IMFMediaType* pMediaType, + IMFAttributes* pAttributes, DWORD* pdwSinkStreamIndex)); - if (SUCCEEDED(hr)) { - hr = sample->AddBuffer(buffer.Get()); - } + // IMFCaptureSink + MOCK_METHOD(HRESULT, Prepare, ()); - if (SUCCEEDED(hr)) { - sample_callback_->OnSample(sample.Get()); + // IMFCaptureSink + MOCK_METHOD(HRESULT, RemoveAllStreams, ()); + + // IMFCapturePhotoSink + MOCK_METHOD(HRESULT, SetOutputFileName, (LPCWSTR fileName)); + + // IMFCapturePhotoSink + MOCK_METHOD(HRESULT, SetSampleCallback, + (IMFCaptureEngineOnSampleCallback * pCallback)); + + // IMFCapturePhotoSink + MOCK_METHOD(HRESULT, SetOutputByteStream, (IMFByteStream * pByteStream)); + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } + + // IUnknown + STDMETHODIMP_(ULONG) Release() { + LONG ref = InterlockedDecrement(&ref_); + if (ref == 0) { + delete this; } + return ref; } - ComPtr sample_callback_; + // IUnknown + STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { + *ppv = nullptr; + + if (riid == IID_IMFCapturePhotoSink) { + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } private: - ~MockCaptureRecordSink() = default; + ~MockCapturePhotoSink() = default; volatile ULONG ref_; }; From 06b386558a8dfb8bf987a4e8f545e0ea43e35d59 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 17 Jan 2022 17:56:57 +0200 Subject: [PATCH 19/93] [camera_windows] Fix formatting --- packages/camera/camera_windows/windows/capture_controller.cpp | 3 ++- .../camera_windows/windows/test/capture_controller_test.cpp | 2 -- packages/camera/camera_windows/windows/test/mocks.h | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index add030622324..c68722f8225c 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -1007,7 +1007,8 @@ void CaptureControllerImpl::OnEvent(IMFMediaEvent *event) { void CaptureControllerImpl::OnPicture(bool success, const std::string &error) { if (capture_controller_listener_) { if (success && !pending_picture_path_.empty()) { - capture_controller_listener_->OnTakePictureSucceeded(pending_picture_path_); + capture_controller_listener_->OnTakePictureSucceeded( + pending_picture_path_); } else { capture_controller_listener_->OnTakePictureFailed(error); } 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 388ae7137398..491499d8a3f3 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -441,7 +441,6 @@ TEST(CaptureController, TakePictureSuccess) { photo_sink = nullptr; } - TEST(CaptureController, PauseResumePreviewSuccess) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = @@ -472,7 +471,6 @@ TEST(CaptureController, PauseResumePreviewSuccess) { EXPECT_CALL(*camera, OnPausePreviewSucceeded()).Times(1); capture_controller->PausePreview(); - EXPECT_CALL(*camera, OnResumePreviewSucceeded()).Times(1); capture_controller->ResumePreview(); diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index f7018545535c..542bb32ec43e 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -159,7 +159,8 @@ class MockCamera : public Camera { MOCK_METHOD(void, OnTakePictureSucceeded, (const std::string& filepath), (override)); - MOCK_METHOD(void, OnTakePictureFailed, (const std::string& error), (override)); + MOCK_METHOD(void, OnTakePictureFailed, (const std::string& error), + (override)); MOCK_METHOD(void, OnVideoRecordSucceeded, (const std::string& filepath, int64_t video_duration), From d50f9fdd1d751e9e7638ef2c9abd0eff87b055ba Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 13:11:17 +0200 Subject: [PATCH 20/93] [camera_windows] update readme.md --- packages/camera/camera_windows/README.md | 38 +++++++++++++----------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/camera/camera_windows/README.md b/packages/camera/camera_windows/README.md index 34d541771dab..9f549dfc3ad5 100644 --- a/packages/camera/camera_windows/README.md +++ b/packages/camera/camera_windows/README.md @@ -2,52 +2,54 @@ The windows implementation of [`camera`][camera]. -*Note*: This plugin is under development. See [missing implementation](#missing-implementation). +*Note*: This plugin is under development. +See [missing implementations and limitations](#limitations-on-the-windows-platform) ## Usage ### Depend on the package -This package is not an [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin) implementation of the [`camera`][camera] plugin yet, so you'll need to -[add it explicitly](https://pub.dev/packages/camera_windows/install) +This package is not an [endorsed][endorsed-federated-plugin] +implementation of the [`camera`][camera] plugin, so you'll need to +[add it explicitly][install] -## Example - -Find the example in the [camera windows package](https://pub.dev/packages/camera_windows#example). - -## Limitations on the windows platform +## Limitations on the Windows platform ### Device orientation -The device orientation is not detected for cameras yet. See [missing implementation](#missing-implementation) +The device orientation is not detected for cameras. -- `CameraPlatform.onDeviceOrientationChanged` stream returns always one item: `DeviceOrientation.landscapeRight` +- `CameraPlatform.onDeviceOrientationChanged` stream always +returns the following value: `DeviceOrientation.landscapeRight` ### Taking a picture Captured pictures are saved to default `Pictures` folder. This folder cannot be changed at the moment. -### Video recording +### Video recording Captures videos are saved to default `Videos` folder. This folder cannot be changed at the moment. -Recording video do not work if preview is not started. +Video recording does not work if preview is not started. +If preview is not drawn on the screen it is recommended to pause preview +to avoid unnecessary processing of the textures while recording. -A video is recorded in following video MIME type: `video/mp4` +Pausing and resuming the video recording is not supported. -Pausing and resuming the video is not supported at the moment. +### Other limitations -## Missing implementation +The windows implementation of [`camera`][camera] +is missing the following features: -The windows implementation of [`camera`][camera] is missing the following features: - Exposure mode, point and offset - Focus mode and point -- Sensor orientation - Image format group - Streaming of frames -- Video record pause and resume + [camera]: https://pub.dev/packages/camera +[endorsed-federated-plugin]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[install]: https://pub.dev/packages/camera_windows/install From 1fa0fcef371b401d22e0207f12e2863969438add Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 13:27:14 +0200 Subject: [PATCH 21/93] [camera_windows] remove todo from integration_tests --- .../example/integration_test/camera_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/example/integration_test/camera_test.dart b/packages/camera/camera_windows/example/integration_test/camera_test.dart index 17e98e983b7f..3b5a346545dc 100644 --- a/packages/camera/camera_windows/example/integration_test/camera_test.dart +++ b/packages/camera/camera_windows/example/integration_test/camera_test.dart @@ -9,11 +9,13 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +// Note that these integration tests do not currently cover +// all features and code paths, as they can only be tested if +// one or more cameras are available in the test environment. + void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - // TODO(jokerttu): write rest of the integration tests after camera support is available on windows test framework or native plugin code is mockable - group('initializeCamera', () { testWidgets('throws exception if camera is not created', (WidgetTester _) async { From 60023f479d3191d40e2ed7aff8bcf71edb24b751 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 13:27:51 +0200 Subject: [PATCH 22/93] [camera_windows] remove unnecessary library --- packages/camera/camera_windows/windows/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index 23bfbfecdc4d..9d1744ad819f 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -35,7 +35,7 @@ target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) -target_link_libraries(${PLUGIN_NAME} PRIVATE mf mfplat mfreadwrite mfuuid d3d11) +target_link_libraries(${PLUGIN_NAME} PRIVATE mf mfplat mfuuid d3d11) # List of absolute paths to libraries that should be bundled with the plugin set(camera_windows_bundled_libraries @@ -76,7 +76,7 @@ add_executable(${TEST_RUNNER} apply_standard_settings(${TEST_RUNNER}) target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) -target_link_libraries(${TEST_RUNNER} PRIVATE mf mfplat mfreadwrite mfuuid d3d11) +target_link_libraries(${TEST_RUNNER} PRIVATE mf mfplat mfuuid d3d11) target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) # flutter_wrapper_plugin has link dependencies on the Flutter DLL. From 8b4fe0c51c7fbeca36d74fcec656fcc02f57bfad Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 13:33:07 +0200 Subject: [PATCH 23/93] [camera_windows] return unimplemented errors pauseVideoRecording and resumeVideoRecording returns UnimplementedError --- packages/camera/camera_windows/lib/camera_windows.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index c8c8460684d2..f203a8e92031 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -282,12 +282,12 @@ class CameraWindows extends CameraPlatform { @override Future pauseVideoRecording(int cameraId) async { - //Video recording cannot be paused on windows + throw UnimplementedError('pauseVideoRecording() is not implemented.'); } @override Future resumeVideoRecording(int cameraId) async { - //Video recording cannot be paused on windows + throw UnimplementedError('resumeVideoRecording() is not implemented.'); } @override From 64fd233ea70971e1302e63b5acbd2a3212b1c0c7 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 13:37:45 +0200 Subject: [PATCH 24/93] [camera_windows] change minimum cmake version to 3.14 --- packages/camera/camera_windows/example/windows/CMakeLists.txt | 2 +- .../camera_windows/example/windows/flutter/CMakeLists.txt | 2 +- .../camera/camera_windows/example/windows/runner/CMakeLists.txt | 2 +- packages/camera/camera_windows/windows/CMakeLists.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera_windows/example/windows/CMakeLists.txt b/packages/camera/camera_windows/example/windows/CMakeLists.txt index 13229f1340a0..28757c79ca2f 100644 --- a/packages/camera/camera_windows/example/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/example/windows/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.14) project(camera_windows_example LANGUAGES CXX) set(BINARY_NAME "camera_windows_example") diff --git a/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt b/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt index b02c5485c957..b2e4bd8d658b 100644 --- a/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt +++ b/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") diff --git a/packages/camera/camera_windows/example/windows/runner/CMakeLists.txt b/packages/camera/camera_windows/example/windows/runner/CMakeLists.txt index 0551532991f0..adb2052b6050 100644 --- a/packages/camera/camera_windows/example/windows/runner/CMakeLists.txt +++ b/packages/camera/camera_windows/example/windows/runner/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index 9d1744ad819f..da6845196c61 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.14) set(PROJECT_NAME "camera_windows") project(${PROJECT_NAME} LANGUAGES CXX) From 7c0f3db345a99c3b469e07045695c61c8044c892 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 13:46:26 +0200 Subject: [PATCH 25/93] [camera_windows] remove url_launcher dependency Removed url_launcher dependency from example application --- packages/camera/camera_windows/example/lib/main.dart | 12 +----------- packages/camera/camera_windows/example/pubspec.yaml | 1 - .../example/windows/flutter/generated_plugins.cmake | 1 - 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index cf92019b8336..db6a28615ad3 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -7,7 +7,6 @@ import 'dart:async'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:url_launcher/url_launcher.dart'; void main() { runApp(MyApp()); @@ -165,10 +164,6 @@ class _MyAppState extends State { Future takePicture() async { final XFile _file = await CameraPlatform.instance.takePicture(_cameraId); - if (!await launch('file:${_file.path}')) { - throw 'Could not open file: "${_file.path}"'; - } - showInSnackBar('Picture captured to: ${_file.path}'); } @@ -182,9 +177,7 @@ class _MyAppState extends State { setState(() { _recordingTimed = false; }); - if (!await launch('file:${event.file.path}')) { - throw 'Could not open file: "${event.file.path}"'; - } + showInSnackBar('Video captured to: ${event.file.path}'); } }); @@ -208,9 +201,6 @@ class _MyAppState extends State { final XFile _file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - if (!await launch('file:${_file.path}')) { - throw 'Could not open file: "${_file.path}"'; - } showInSnackBar('Video captured to: ${_file.path}'); } setState(() { diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index e8d3b641395c..3fb00b2c77c3 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: path: ../ flutter: sdk: flutter - url_launcher: ^6.0.0 dev_dependencies: flutter_test: diff --git a/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake b/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake index 67b11087b9f1..62644e4539c0 100644 --- a/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake +++ b/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST camera_windows - url_launcher_windows ) set(PLUGIN_BUNDLED_LIBRARIES) From 6678a5a255df00abcf52ac3a4ca901b7816fbcf4 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 13:51:49 +0200 Subject: [PATCH 26/93] [camera_windows] removed boilerplate from example readme --- packages/camera/camera_windows/example/README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/camera/camera_windows/example/README.md b/packages/camera/camera_windows/example/README.md index b9d5b32796c1..ee7326472eaf 100644 --- a/packages/camera/camera_windows/example/README.md +++ b/packages/camera/camera_windows/example/README.md @@ -1,16 +1,3 @@ # camera_windows_example Demonstrates how to use the camera_windows plugin. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. From 14dedf286fb926385856087a36af745757c32d67 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 13:57:06 +0200 Subject: [PATCH 27/93] [camera_windows] return unimplemented error lockCaptureOrientation and unlockCaptureOrientation are not supported by windows platform at the moment --- packages/camera/camera_windows/lib/camera_windows.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index f203a8e92031..b54ff42e3675 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -200,12 +200,12 @@ class CameraWindows extends CameraPlatform { int cameraId, DeviceOrientation orientation, ) async { - //Windows camera plugin does not support capture orientation locking + throw UnimplementedError('lockCaptureOrientation() is not implemented.'); } @override Future unlockCaptureOrientation(int cameraId) async { - //Windows camera plugin does not support capture orientation locking + throw UnimplementedError('unlockCaptureOrientation() is not implemented.'); } @override From 103368689813e0d18ee059fa3cdac16fe0aa5eeb Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 14:15:47 +0200 Subject: [PATCH 28/93] [camera_windows] dart comments fixed --- .../camera_windows/lib/camera_windows.dart | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index b54ff42e3675..78625daa20f2 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -14,30 +14,31 @@ import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:stream_transform/stream_transform.dart'; -/// An implementation of [CameraPlatform] that uses method channels. +// An implementation of [CameraPlatform] for Windows. class CameraWindows extends CameraPlatform { - /// The method channel used to interact with the native platform. + // The method channel used to interact with the native platform. final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); - /// Registers the Windows implementation of CameraPlatform. + // Registers the Windows implementation of CameraPlatform. static void registerWith() { CameraPlatform.instance = CameraWindows(); } final Map _channels = {}; - /// The controller we need to broadcast the different events coming - /// from handleMethodCall, specific to camera events. - /// - /// It is a `broadcast` because multiple controllers will connect to - /// different stream views of this Controller. - /// This is only exposed for test purposes. It shouldn't be used by clients of - /// the plugin as it may break or change at any time. + // The controller we need to broadcast the different events coming + // from handleMethodCall. + // + // It is a `broadcast` because multiple controllers will connect to + // different stream views of this Controller. + // This is only exposed for test purposes. It shouldn't be used by clients of + // the plugin as it may break or change at any time. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); + // Returns a stream of camera events for the given [cameraId]. Stream _cameraEvents(int cameraId) => cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); @@ -98,7 +99,7 @@ class CameraWindows extends CameraPlatform { }) async { final int requestedCameraId = cameraId; - //Create channel for camera events + // Creates channel for camera events. _channels.putIfAbsent(requestedCameraId, () { final MethodChannel channel = MethodChannel('flutter.io/cameraPlugin/camera$requestedCameraId'); @@ -167,7 +168,7 @@ class CameraWindows extends CameraPlatform { @override Stream onCameraResolutionChanged(int cameraId) { - //Windows camera plugin does not support resolution changed events + // Windows camera plugin does not support resolution changed events. return const Stream.empty(); } @@ -188,8 +189,8 @@ class CameraWindows extends CameraPlatform { @override Stream onDeviceOrientationChanged() { - //Windows camera plugin does not support capture orientations - //Force device orientation to landscape (by default camera plugin uses portraitUp orientation) + // Windows camera plugin does not support capture orientations. + // Force device orientation to landscape as by default camera plugin uses portraitUp orientation. return Stream.value( const DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight), ); @@ -292,12 +293,12 @@ class CameraWindows extends CameraPlatform { @override Future setFlashMode(int cameraId, FlashMode mode) async { - //Windows camera plugin does not support setFlashMode yet + throw UnimplementedError('setFlashMode() is not implemented.'); } @override Future setExposureMode(int cameraId, ExposureMode mode) async { - //Windows camera plugin does not support setExposureMode yet + throw UnimplementedError('setExposureMode() is not implemented.'); } @override @@ -305,36 +306,40 @@ class CameraWindows extends CameraPlatform { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); - //Windows camera plugin does not support setExposurePoint yet + throw UnimplementedError('setExposurePoint() is not implemented.'); } @override Future getMinExposureOffset(int cameraId) async { - //Windows camera plugin does not support getMinExposureOffset yet + // Explosure offset is not supported by camera windows plugin yet. + // Default min offset value is returned. return 0.0; } @override Future getMaxExposureOffset(int cameraId) async { - //Windows camera plugin does not support getMaxExposureOffset yet + // Explosure offset is not supported by camera windows plugin yet. + // Default max offset value is returned. return 0.0; } @override Future getExposureOffsetStepSize(int cameraId) async { - //Windows camera plugin does not support getExposureOffsetStepSize yet + // Explosure offset is not supported by camera windows plugin yet. + // Default step value is returned. return 1.0; } @override Future setExposureOffset(int cameraId, double offset) async { - //Windows camera plugin does not support setExposureOffset yet + // Explosure offset is not supported by camera windows plugin yet. + // Default exposure offset value is returned as a response. return 0.0; } @override Future setFocusMode(int cameraId, FocusMode mode) async { - //Windows camera plugin does not support focus modes yet + throw UnimplementedError('setFocusMode() is not implemented.'); } @override @@ -342,24 +347,26 @@ class CameraWindows extends CameraPlatform { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); - //Windows camera plugin does not support focus points yet + throw UnimplementedError('setFocusPoint() is not implemented.'); } @override - Future getMaxZoomLevel(int cameraId) async { - //Windows camera plugin does not support zoom levels yet + Future getMinZoomLevel(int cameraId) async { + // Zoom level is not supported by camera windows plugin yet. + // Default min zoom level value is returned as a response. return 1.0; } @override - Future getMinZoomLevel(int cameraId) async { - //Windows camera plugin does not support zoom levels yet + Future getMaxZoomLevel(int cameraId) async { + // Zoom level is not supported by camera windows plugin yet. + // Default max zoom level value is returned as a response. return 1.0; } @override Future setZoomLevel(int cameraId, double zoom) async { - //Windows camera plugin does not support zoom levels yet + throw UnimplementedError('setZoomLevel() is not implemented.'); } @override @@ -391,7 +398,7 @@ class CameraWindows extends CameraPlatform { return Texture(textureId: cameraId); } - /// Returns the resolution preset as a String. + // Returns the resolution preset as a String. String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { case ResolutionPreset.max: @@ -411,10 +418,10 @@ class CameraWindows extends CameraPlatform { } } - /// Converts messages received from the native platform into camera events. - /// - /// This is only exposed for test purposes. It shouldn't be used by clients of - /// the plugin as it may break or change at any time. + // Converts messages received from the native platform into camera events. + // + // This is only exposed for test purposes. It shouldn't be used by clients of + // the plugin as it may break or change at any time. @visibleForTesting Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { @@ -426,7 +433,7 @@ class CameraWindows extends CameraPlatform { ); break; case 'video_recorded': - //This is called if maxVideoDuration was given on record start + // This is called if maxVideoDuration was given on record start. cameraEventStreamController.add( VideoRecordedEvent( cameraId, From 87d087017c2040b11a98d3cc7c14eae11a5baf56 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 14:16:51 +0200 Subject: [PATCH 29/93] [camera_windows] fix dart comments --- packages/camera/camera_windows/example/lib/main.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index db6a28615ad3..bee5ed6c4150 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -12,7 +12,7 @@ void main() { runApp(MyApp()); } -/// App for testing +// App for testing. class MyApp extends StatefulWidget { @override State createState() => _MyAppState(); @@ -45,7 +45,7 @@ class _MyAppState extends State { super.dispose(); } - // Fetches list of available cameras from camera_windows plugin + // Fetches list of available cameras from camera_windows plugin. Future getAvailableCameras() async { String cameraInfo; List cameras = []; @@ -123,7 +123,7 @@ class _MyAppState extends State { } on CameraException catch (e) { debugPrint('Failed to dispose camera: ${e.code}: ${e.description}'); } - //Reset state + // Reset state. setState(() { _initialized = false; _cameraId = -1; From 89a0911f5b2d0eb09e871540696412618354acc1 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 15:19:20 +0200 Subject: [PATCH 30/93] [camera_windows] rename pixel format structs --- .../camera/camera_windows/windows/capture_controller.cpp | 6 +++--- .../camera/camera_windows/windows/capture_controller.h | 4 ++-- .../windows/test/capture_controller_test.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index c68722f8225c..eaba959ffc85 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -475,9 +475,9 @@ CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, this->preview_frame_width_ * this->preview_frame_height_; dest_buffer_ = std::make_unique(pixels_total * 4); - MFVideoFormat_RGB32_Pixel *src = - (MFVideoFormat_RGB32_Pixel *)this->source_buffer_data_.get(); - FlutterDesktop_Pixel *dst = (FlutterDesktop_Pixel *)dest_buffer_.get(); + MFVideoFormatRGB32Pixel *src = + (MFVideoFormatRGB32Pixel *)this->source_buffer_data_.get(); + FlutterDesktopPixel *dst = (FlutterDesktopPixel *)dest_buffer_.get(); for (uint32_t i = 0; i < pixels_total; i++) { dst[i].r = src[i].r; diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index fb2702b6992f..a0c8966ff479 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -25,14 +25,14 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -struct FlutterDesktop_Pixel { +struct FlutterDesktopPixel { BYTE r = 0; BYTE g = 0; BYTE b = 0; BYTE a = 0; }; -struct MFVideoFormat_RGB32_Pixel { +struct MFVideoFormatRGB32Pixel { BYTE b = 0; BYTE g = 0; BYTE r = 0; 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 491499d8a3f3..887b50e96e96 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -232,8 +232,8 @@ TEST(CaptureController, StartPreviewStartsProcessingSamples) { uint8_t mock_red_pixel = 0x11; uint8_t mock_green_pixel = 0x22; uint8_t mock_blue_pixel = 0x33; - MFVideoFormat_RGB32_Pixel* mock_source_buffer_data = - (MFVideoFormat_RGB32_Pixel*)mock_source_buffer.get(); + MFVideoFormatRGB32Pixel* mock_source_buffer_data = + (MFVideoFormatRGB32Pixel*)mock_source_buffer.get(); for (uint32_t i = 0; i < pixels_total; i++) { mock_source_buffer_data[i].r = mock_red_pixel; @@ -264,8 +264,8 @@ TEST(CaptureController, StartPreviewStartsProcessingSamples) { EXPECT_EQ(converted_buffer->height, mock_preview_height); EXPECT_EQ(converted_buffer->width, mock_preview_width); - FlutterDesktop_Pixel* converted_buffer_data = - (FlutterDesktop_Pixel*)(converted_buffer->buffer); + FlutterDesktopPixel* converted_buffer_data = + (FlutterDesktopPixel*)(converted_buffer->buffer); for (uint32_t i = 0; i < pixels_total; i++) { EXPECT_EQ(converted_buffer_data[i].r, mock_red_pixel); From d13ef868483b1eccd49740c044ce6c496855eb41 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 15:35:57 +0200 Subject: [PATCH 31/93] [camera_windows] code cleanup - remove commented out include line --- packages/camera/camera_windows/windows/capture_controller.h | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index a0c8966ff479..d76ce0b62265 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -6,7 +6,6 @@ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_H_ #include -//#include #include #include #include From 0a137888c3a667857f8be71e77f377f5be35fee0 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 15:40:54 +0200 Subject: [PATCH 32/93] [camera_windows] rename plugin and camera channels --- .../camera_windows/lib/camera_windows.dart | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 78625daa20f2..24a765518d48 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -17,7 +17,7 @@ import 'package:stream_transform/stream_transform.dart'; // An implementation of [CameraPlatform] for Windows. class CameraWindows extends CameraPlatform { // The method channel used to interact with the native platform. - final MethodChannel _channel = + final MethodChannel _pluginChannel = const MethodChannel('plugins.flutter.io/camera'); // Registers the Windows implementation of CameraPlatform. @@ -25,7 +25,8 @@ class CameraWindows extends CameraPlatform { CameraPlatform.instance = CameraWindows(); } - final Map _channels = {}; + // Camera specific method channels to allow comminicating with specific cameras. + final Map _cameraChannels = {}; // The controller we need to broadcast the different events coming // from handleMethodCall. @@ -46,7 +47,7 @@ class CameraWindows extends CameraPlatform { @override Future> availableCameras() async { try { - final List>? cameras = await _channel + final List>? cameras = await _pluginChannel .invokeListMethod>('availableCameras'); if (cameras == null) { @@ -73,7 +74,7 @@ class CameraWindows extends CameraPlatform { bool enableAudio = false, }) async { try { - final Map? reply = await _channel + final Map? reply = await _pluginChannel .invokeMapMethod('create', { 'cameraName': cameraDescription.name, 'resolutionPreset': resolutionPreset != null @@ -100,7 +101,7 @@ class CameraWindows extends CameraPlatform { final int requestedCameraId = cameraId; // Creates channel for camera events. - _channels.putIfAbsent(requestedCameraId, () { + _cameraChannels.putIfAbsent(requestedCameraId, () { final MethodChannel channel = MethodChannel('flutter.io/cameraPlugin/camera$requestedCameraId'); channel.setMethodCallHandler( @@ -111,7 +112,7 @@ class CameraWindows extends CameraPlatform { final Map? reply; try { - reply = await _channel.invokeMapMethod( + reply = await _pluginChannel.invokeMapMethod( 'initialize', { 'cameraId': requestedCameraId, @@ -145,14 +146,14 @@ class CameraWindows extends CameraPlatform { @override Future dispose(int cameraId) async { - if (_channels.containsKey(cameraId)) { - final MethodChannel? cameraChannel = _channels[cameraId]; + if (_cameraChannels.containsKey(cameraId)) { + final MethodChannel? cameraChannel = _cameraChannels[cameraId]; cameraChannel?.setMethodCallHandler(null); - _channels.remove(cameraId); + _cameraChannels.remove(cameraId); } try { - await _channel.invokeMethod( + await _pluginChannel.invokeMethod( 'dispose', {'cameraId': cameraId}, ); @@ -213,7 +214,7 @@ class CameraWindows extends CameraPlatform { Future takePicture(int cameraId) async { final String? path; try { - path = await _channel.invokeMethod( + path = await _pluginChannel.invokeMethod( 'takePicture', {'cameraId': cameraId}, ); @@ -234,7 +235,7 @@ class CameraWindows extends CameraPlatform { @override Future prepareForVideoRecording() async { try { - _channel.invokeMethod('prepareForVideoRecording'); + _pluginChannel.invokeMethod('prepareForVideoRecording'); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -246,7 +247,7 @@ class CameraWindows extends CameraPlatform { Duration? maxVideoDuration, }) async { try { - await _channel.invokeMethod( + await _pluginChannel.invokeMethod( 'startVideoRecording', { 'cameraId': cameraId, @@ -263,7 +264,7 @@ class CameraWindows extends CameraPlatform { final String? path; try { - path = await _channel.invokeMethod( + path = await _pluginChannel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, ); @@ -372,7 +373,7 @@ class CameraWindows extends CameraPlatform { @override Future pausePreview(int cameraId) async { try { - await _channel.invokeMethod( + await _pluginChannel.invokeMethod( 'pausePreview', {'cameraId': cameraId}, ); @@ -384,7 +385,7 @@ class CameraWindows extends CameraPlatform { @override Future resumePreview(int cameraId) async { try { - await _channel.invokeMethod( + await _pluginChannel.invokeMethod( 'resumePreview', {'cameraId': cameraId}, ); From 9b97fd9e6f45846d4a5dc1a5d241e36208fb10ea Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 17:28:22 +0200 Subject: [PATCH 33/93] [camera_windows] change flutter version to >=2.5.0 --- packages/camera/camera_windows/example/pubspec.yaml | 2 +- packages/camera/camera_windows/pubspec.yaml | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index 3fb00b2c77c3..6cce239c317e 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.5.3" + flutter: ">=2.5.0" dependencies: camera_platform_interface: ^2.1.2 diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index de4f174de49d..8c900e6b4852 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -1,15 +1,12 @@ name: camera_windows description: A Flutter plugin for getting information about and controlling the camera on Windows. version: 0.0.1 -# TODO: Update next lines to match proper repository and issue_tracker url -#repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera_windows -#issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -# TODO: Remove next line after plugin is ready to be published -publish_to: 'none' +repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera_windows +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.5.3" + flutter: ">=2.5.0" flutter: plugin: From 44305fffe1bb3a18aa8b3ed1f3caf50ce30fd259 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 17:38:51 +0200 Subject: [PATCH 34/93] [camera_windows] Fix comment formatting --- .../camera/camera_windows/windows/camera.cpp | 37 +++++++++---------- .../camera/camera_windows/windows/camera.h | 7 ++-- .../camera_windows/windows/camera_plugin.cpp | 23 ++++-------- .../camera_windows/windows/camera_plugin.h | 3 +- .../windows/capture_controller.cpp | 20 +++++----- .../windows/capture_controller.h | 28 +++++++------- .../windows/capture_engine_listener.cpp | 6 +-- .../windows/test/camera_plugin_test.cpp | 30 +++++++-------- 8 files changed, 70 insertions(+), 84 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index e196c6ec8316..cb60f0031409 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -10,12 +10,12 @@ using flutter::EncodableMap; using flutter::EncodableValue; namespace { -// Camera channel events +// Camera channel events. const char kCameraMethodChannelBaseName[] = "flutter.io/cameraPlugin/camera"; const char kVideoRecordedEvent[] = "video_recorded"; const char kErrorEvent[] = "error"; -// Helper function for creating messaging channel for camera +// Helper function for creating messaging channel for camera. std::unique_ptr> BuildChannelForCamera( flutter::BinaryMessenger *messenger, int64_t camera_id) { auto channel_name = @@ -111,8 +111,7 @@ void CameraImpl::SendErrorForPendingResults(const std::string &error_id, pending_results_.clear(); } -// TODO: Create common base handler function for alll success and error cases -// below From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { // Use texture id as camera id camera_id_ = texture_id; @@ -124,7 +123,7 @@ void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { } } -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnCreateCaptureEngineFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::CREATE_CAMERA); @@ -133,7 +132,7 @@ void CameraImpl::OnCreateCaptureEngineFailed(const std::string &error) { } } -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); if (pending_result) { @@ -144,7 +143,7 @@ void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { } }; -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnStartPreviewFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); if (pending_result) { @@ -152,7 +151,7 @@ void CameraImpl::OnStartPreviewFailed(const std::string &error) { } }; -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnResumePreviewSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::RESUME_PREVIEW); @@ -161,7 +160,7 @@ void CameraImpl::OnResumePreviewSucceeded() { } } -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnResumePreviewFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::RESUME_PREVIEW); @@ -170,7 +169,7 @@ void CameraImpl::OnResumePreviewFailed(const std::string &error) { } } -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnPausePreviewSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); @@ -179,7 +178,7 @@ void CameraImpl::OnPausePreviewSucceeded() { } } -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnPausePreviewFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); @@ -188,7 +187,7 @@ void CameraImpl::OnPausePreviewFailed(const std::string &error) { } } -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnStartRecordSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); if (pending_result) { @@ -196,7 +195,7 @@ void CameraImpl::OnStartRecordSucceeded() { } }; -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnStartRecordFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); if (pending_result) { @@ -204,7 +203,7 @@ void CameraImpl::OnStartRecordFailed(const std::string &error) { } }; -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnStopRecordSucceeded(const std::string &filepath) { auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); if (pending_result) { @@ -212,7 +211,7 @@ void CameraImpl::OnStopRecordSucceeded(const std::string &filepath) { } }; -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnStopRecordFailed(const std::string &error) { auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); if (pending_result) { @@ -220,7 +219,7 @@ void CameraImpl::OnStopRecordFailed(const std::string &error) { } }; -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnTakePictureSucceeded(const std::string &filepath) { auto pending_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); if (pending_result) { @@ -228,7 +227,7 @@ void CameraImpl::OnTakePictureSucceeded(const std::string &filepath) { } }; -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnTakePictureFailed(const std::string &error) { auto pending_take_picture_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); @@ -237,7 +236,7 @@ void CameraImpl::OnTakePictureFailed(const std::string &error) { } }; -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnVideoRecordSucceeded(const std::string &filepath, int64_t video_duration) { if (messenger_ && camera_id_ >= 0) { @@ -253,7 +252,7 @@ void CameraImpl::OnVideoRecordSucceeded(const std::string &filepath, } } -// From CaptureControllerListener +// From CaptureControllerListener. void CameraImpl::OnVideoRecordFailed(const std::string &error){}; void CameraImpl::OnCaptureError(const std::string &error) { diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index ddec8da52a03..aea651b10623 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -60,7 +60,7 @@ class CameraImpl : public Camera { CameraImpl(const CameraImpl &) = delete; CameraImpl &operator=(const CameraImpl &) = delete; - // From CaptureControllerListener + // From CaptureControllerListener. void OnCreateCaptureEngineSucceeded(int64_t texture_id) override; void OnCreateCaptureEngineFailed(const std::string &error) override; void OnStartPreviewSucceeded(int32_t width, int32_t height) override; @@ -80,8 +80,7 @@ class CameraImpl : public Camera { void OnVideoRecordFailed(const std::string &error) override; void OnCaptureError(const std::string &error) override; - // From Camera - + // From Camera. bool HasDeviceId(std::string &device_id) override { return device_id_ == device_id; }; @@ -115,7 +114,7 @@ class CameraImpl : public Camera { int64_t camera_id_; std::string device_id_; - // Pending results + // Pending results. std::map>> pending_results_; std::unique_ptr> GetPendingResultByType( PendingResultType type); diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index a675603b41fd..96cfb1d025b6 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -275,7 +275,7 @@ void CameraPlugin::AvailableCamerasMethodHandler( UINT32 count = 0; if (!this->EnumerateVideoCaptureDeviceSources(&devices, &count)) { result->Error("System error", "Failed to get available cameras"); - // No need to free devices here, cos allocation failed + // No need to free devices here, cos allocation failed. return; } @@ -285,13 +285,12 @@ void CameraPlugin::AvailableCamerasMethodHandler( return; } - // Format found devices to the response + // Format found devices to the response. EncodableList devices_list; for (UINT32 i = 0; i < count; ++i) { auto device_info = GetDeviceInfo(devices[i]); auto deviceName = GetUniqueDeviceName(std::move(device_info)); - // TODO: get lens facing info and sensor orientation from devices devices_list.push_back(EncodableMap({ {EncodableValue("name"), EncodableValue(deviceName)}, {EncodableValue("lensFacing"), EncodableValue("front")}, @@ -309,7 +308,7 @@ bool CameraPlugin::EnumerateVideoCaptureDeviceSources(IMFActivate ***devices, count); } -// Loops through cameras and returns camera with matching device_id or nullptr +// Loops through cameras and returns camera with matching device_id or nullptr. Camera *CameraPlugin::GetCameraByDeviceId(std::string &device_id) { for (auto it = begin(cameras_); it != end(cameras_); ++it) { if ((*it)->HasDeviceId(device_id)) { @@ -319,7 +318,7 @@ Camera *CameraPlugin::GetCameraByDeviceId(std::string &device_id) { return nullptr; } -// Loops through cameras and returns camera with matching camera_id or nullptr +// Loops through cameras and returns camera with matching camera_id or nullptr. Camera *CameraPlugin::GetCameraByCameraId(int64_t camera_id) { for (auto it = begin(cameras_); it != end(cameras_); ++it) { if ((*it)->HasCameraId(camera_id)) { @@ -339,10 +338,10 @@ void CameraPlugin::DisposeCameraByCameraId(int64_t camera_id) { } // Creates and initializes capture controller -// and MFCaptureEngine for requested device +// and MFCaptureEngine for requested device. void CameraPlugin::CreateMethodHandler( const EncodableMap &args, std::unique_ptr> result) { - // Parse enableAudio argument + // Parse enableAudio argument. const auto *enable_audio = std::get_if(ValueOrNull(args, kEnableAudioKey)); if (!enable_audio) { @@ -350,7 +349,7 @@ void CameraPlugin::CreateMethodHandler( std::string(kEnableAudioKey) + " argument missing"); } - // Parse cameraName argument + // Parse cameraName argument. const auto *camera_name = std::get_if(ValueOrNull(args, kCameraNameKey)); if (!camera_name) { @@ -374,14 +373,13 @@ void CameraPlugin::CreateMethodHandler( camera_factory_->CreateCamera(device_info->device_id); if (camera->HasPendingResultByType(PendingResultType::CREATE_CAMERA)) { - // This should never happen return result->Error("camera_error", "Pending camera creation request exists"); } if (camera->AddPendingResult(PendingResultType::CREATE_CAMERA, std::move(result))) { - // Parse resolutionPreset argument + // Parse resolution preset argument. const auto *resolution_preset_argument = std::get_if(ValueOrNull(args, kResolutionPresetKey)); ResolutionPreset resolution_preset; @@ -443,8 +441,6 @@ void CameraPlugin::PausePreviewMethodHandler( if (camera->AddPendingResult(PendingResultType::PAUSE_PREVIEW, std::move(result))) { - // Capture engine does not really have pause feature... - // so preview is stopped instead. auto cc = camera->GetCaptureController(); assert(cc); cc->PausePreview(); @@ -471,8 +467,6 @@ void CameraPlugin::ResumePreviewMethodHandler( if (camera->AddPendingResult(PendingResultType::RESUME_PREVIEW, std::move(result))) { - // Capture engine does not really have pause feature... - // so preview is started instead auto cc = camera->GetCaptureController(); assert(cc); cc->ResumePreview(); @@ -497,7 +491,6 @@ void CameraPlugin::StartVideoRecordingMethodHandler( "Pending start recording request exists"); } - // Get max video duration int64_t max_video_duration_ms = -1; auto requested_max_video_duration_ms = std::get_if(ValueOrNull(args, kMaxVideoDurationKey)); diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 4c04229f40d5..41ce28fc0775 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -39,7 +39,7 @@ class CameraPlugin : public flutter::Plugin, CameraPlugin(const CameraPlugin &) = delete; CameraPlugin &operator=(const CameraPlugin &) = delete; - // Called when a method is called on plugin channel; + // Called when a method is called on plugin channel. void HandleMethodCall(const flutter::MethodCall<> &method_call, std::unique_ptr> result); @@ -59,7 +59,6 @@ class CameraPlugin : public flutter::Plugin, flutter::BinaryMessenger *messenger_; // Method handlers - void AvailableCamerasMethodHandler( std::unique_ptr> result); diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index eaba959ffc85..96b9114db7da 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -215,7 +215,7 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { wchar_t *audio_device_id; UINT32 audio_device_id_size; - // Use first audio device + // Use first audio device. hr = devices[0]->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID, &audio_device_id, &audio_device_id_size); @@ -279,10 +279,7 @@ HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( } // Create DX11 Device and D3D Manager -// TODO: If DX12 device can be used with flutter: -// Separate CreateD3DManagerWithDX12Device functionality -// can be written if needed -// TODO: Should shared ANGLE device be used? +// TODO: Use existing ANGLE device HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { HRESULT hr = S_OK; hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, @@ -486,6 +483,7 @@ CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, dst[i].a = 255; } + // TODO: add release_callback and clear dest_buffer after each frame. this->flutter_desktop_pixel_buffer_.buffer = dest_buffer_.get(); this->flutter_desktop_pixel_buffer_.width = this->preview_frame_width_; this->flutter_desktop_pixel_buffer_.height = this->preview_frame_height_; @@ -509,7 +507,7 @@ void CaptureControllerImpl::TakePicture(const std::string filepath) { HRESULT hr = InitPhotoSink(filepath); if (SUCCEEDED(hr)) { - // Request new photo + // Request new photo. pending_picture_path_ = filepath; pending_image_capture_ = true; hr = capture_engine_->TakePhoto(); @@ -542,7 +540,7 @@ uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { break; case RESOLUTION_PRESET_AUTO: default: - // no limit + // no limit. return 0xffffffff; break; } @@ -560,9 +558,9 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { ComPtr media_type; uint32_t max_height = GetMaxPreviewHeight(); - // Loop native media types + // Loop native media types. for (int i = 0;; i++) { - // Release media type if exists from previous loop; + // Release media type if exists from previous loop. media_type = nullptr; if (FAILED(source->GetAvailableDeviceMediaType( @@ -576,7 +574,7 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { uint32_t frame_height; if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, &frame_width, &frame_height))) { - // Update media type for photo and record capture + // Update media type for photo and record capture. if (capture_frame_width_ < frame_width || capture_frame_height_ < frame_height) { base_capture_media_type_ = media_type; @@ -585,7 +583,7 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { capture_frame_height_ = frame_height; } - // Update media type for preview + // Update media type for preview. if (frame_height <= max_height && (preview_frame_width_ < frame_width || preview_frame_height_ < frame_height)) { diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index d76ce0b62265..4e0575397bbb 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -39,25 +39,25 @@ struct MFVideoFormatRGB32Pixel { }; enum ResolutionPreset { - /// AUTO + // AUTO RESOLUTION_PRESET_AUTO, - /// 240p (320x240) + // 240p (320x240) RESOLUTION_PRESET_LOW, - /// 480p (720x480) + // 480p (720x480) RESOLUTION_PRESET_MEDIUM, - /// 720p (1280x720) + // 720p (1280x720) RESOLUTION_PRESET_HIGH, - /// 1080p (1920x1080) + // 1080p (1920x1080) RESOLUTION_PRESET_VERY_HIGH, - /// 2160p (4096x2160) + // 2160p (4096x2160) RESOLUTION_PRESET_ULTRA_HIGH, - /// The highest resolution available. + // The highest resolution available. RESOLUTION_PRESET_MAX, }; @@ -132,8 +132,8 @@ class CaptureControllerImpl : public CaptureController, void StopRecord() override; void TakePicture(const std::string filepath) override; - // Handlers for CaptureEngineListener events - // From CaptureEngineObserver + // Handlers for CaptureEngineListener events. + // From CaptureEngineObserver. bool IsReadyForSample() override { return initialized_ && previewing_ && !preview_paused_; } @@ -144,17 +144,17 @@ class CaptureControllerImpl : public CaptureController, void OnBufferUpdated() override; void UpdateCaptureTime(uint64_t capture_time) override; - // Sets capture engine, for mocking purposes + // Sets capture engine, for mocking purposes. void SetCaptureEngine(IMFCaptureEngine* capture_engine) { capture_engine_ = capture_engine; }; - // Sets video source, for mocking purposes + // Sets video source, for mocking purposes. void SetVideoSource(IMFMediaSource* video_source) { video_source_ = video_source; }; - // Sets audio source, for mocking purposes + // Sets audio source, for mocking purposes. void SetAudioSource(IMFMediaSource* audio_source) { audio_source_ = audio_source; }; @@ -175,7 +175,6 @@ class CaptureControllerImpl : public CaptureController, ComPtr dxgi_device_manager_; ComPtr dx11_device_; - // ID3D12Device* dx12_device_ = nullptr; UINT dx_device_reset_token_ = 0; // Sources @@ -187,12 +186,11 @@ class CaptureControllerImpl : public CaptureController, flutter::TextureRegistrar* texture_registrar_ = nullptr; std::unique_ptr texture_; - // TODO: add release_callback and clear buffer after each frame FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; uint32_t source_buffer_size_ = 0; std::unique_ptr source_buffer_data_ = nullptr; std::unique_ptr dest_buffer_ = nullptr; - uint32_t bytes_per_pixel_ = 4; // MFVideoFormat_RGB32 + uint32_t bytes_per_pixel_ = 4; // Preview bool preview_paused_ = false; diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp index 946743941ae6..219206ce0416 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.cpp +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -58,15 +58,15 @@ HRESULT CaptureEngineListener::OnSample(IMFSample *sample) { if (this->observer_ && sample) { LONGLONG raw_time_stamp = 0; - // Receives the presentation time, in 100-nanosecond units + // Receives the presentation time, in 100-nanosecond units. sample->GetSampleTime(&raw_time_stamp); - // Report time in microseconds + // Report time in microseconds. this->observer_->UpdateCaptureTime( static_cast(raw_time_stamp / 10)); if (!this->observer_->IsReadyForSample()) { - // No texture target available or not previewing, just return status + // No texture target available or not previewing, just return status. return hr; } 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 e455fe8e2463..96c720174e55 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -123,7 +123,7 @@ TEST(CameraPlugin, CreateHandlerCallsInitCamera) { }); // Move mocked camera to the factory to be passed - // for plugin with CreateCamera function + // for plugin with CreateCamera function. camera_factory_->pending_camera_ = std::move(camera); EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID)); @@ -215,7 +215,7 @@ TEST(CameraPlugin, CreateHandlerErrorOnExistingDeviceId) { }); // Move mocked camera to the factory to be passed - // for plugin with CreateCamera function + // for plugin with CreateCamera function. camera_factory_->pending_camera_ = std::move(camera); EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID)); @@ -298,7 +298,7 @@ TEST(CameraPlugin, InitializeHandlerCallStartPreview) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); @@ -344,7 +344,7 @@ TEST(CameraPlugin, InitializeHandlerErrorOnInvalidCameraId) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); @@ -412,7 +412,7 @@ TEST(CameraPlugin, TakePictureHandlerCallsTakePictureWithPath) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); @@ -458,7 +458,7 @@ TEST(CameraPlugin, TakePictureHandlerErrorOnInvalidCameraId) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); @@ -527,7 +527,7 @@ TEST(CameraPlugin, StartVideoRecordingHandlerCallsStartRecordWithPath) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); @@ -599,7 +599,7 @@ TEST(CameraPlugin, std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); @@ -646,7 +646,7 @@ TEST(CameraPlugin, StartVideoRecordingHandlerErrorOnInvalidCameraId) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); @@ -714,7 +714,7 @@ TEST(CameraPlugin, StopVideoRecordingHandlerCallsStopRecord) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); @@ -760,7 +760,7 @@ TEST(CameraPlugin, StopVideoRecordingHandlerErrorOnInvalidCameraId) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); @@ -829,7 +829,7 @@ TEST(CameraPlugin, ResumePreviewHandlerCallsResumePreview) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); @@ -875,7 +875,7 @@ TEST(CameraPlugin, ResumePreviewHandlerErrorOnInvalidCameraId) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); @@ -944,7 +944,7 @@ TEST(CameraPlugin, PausePreviewHandlerCallsPausePreview) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); @@ -990,7 +990,7 @@ TEST(CameraPlugin, PausePreviewHandlerErrorOnInvalidCameraId) { std::make_unique().get(), std::make_unique()); - // Add mocked camera to plugins camera list + // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); From 166698f03d949b47bf3f3401a088f38c744c5fe3 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 18:09:21 +0200 Subject: [PATCH 35/93] [camera_windows] format fixes --- .../camera_windows/lib/camera_windows.dart | 60 ++++++------------- .../camera/camera_windows/windows/camera.h | 3 - .../camera_windows/windows/camera_plugin.h | 9 --- 3 files changed, 18 insertions(+), 54 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 24a765518d48..e37379172eaa 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -16,20 +16,19 @@ import 'package:stream_transform/stream_transform.dart'; // An implementation of [CameraPlatform] for Windows. class CameraWindows extends CameraPlatform { - // The method channel used to interact with the native platform. - final MethodChannel _pluginChannel = - const MethodChannel('plugins.flutter.io/camera'); - // Registers the Windows implementation of CameraPlatform. static void registerWith() { CameraPlatform.instance = CameraWindows(); } + // The method channel used to interact with the native platform. + final MethodChannel _pluginChannel = + const MethodChannel('plugins.flutter.io/camera'); + // Camera specific method channels to allow comminicating with specific cameras. final Map _cameraChannels = {}; - // The controller we need to broadcast the different events coming - // from handleMethodCall. + // The controller that broadcasts events coming from handleCameraMethodCall // // It is a `broadcast` because multiple controllers will connect to // different stream views of this Controller. @@ -122,26 +121,17 @@ class CameraWindows extends CameraPlatform { throw CameraException(e.code, e.message); } - if (reply != null && - reply.containsKey('previewWidth') && - reply.containsKey('previewHeight')) { - cameraEventStreamController.add( - CameraInitializedEvent( - requestedCameraId, - reply['previewWidth']!, - reply['previewHeight']!, - ExposureMode.auto, - false, - FocusMode.auto, - false, - ), - ); - } else { - throw CameraException( - 'INITIALIZATION_FAILED', - 'The platform "$defaultTargetPlatform" did not return valid data when reporting success. The platform should always return a valid data or report an error.', - ); - } + cameraEventStreamController.add( + CameraInitializedEvent( + requestedCameraId, + reply!['previewWidth']!, + reply['previewHeight']!, + ExposureMode.auto, + false, + FocusMode.auto, + false, + ), + ); } @override @@ -222,14 +212,7 @@ class CameraWindows extends CameraPlatform { throw CameraException(e.code, e.message); } - if (path == null) { - throw CameraException( - 'INVALID_PATH', - 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', - ); - } - - return XFile(path); + return XFile(path!); } @override @@ -272,14 +255,7 @@ class CameraWindows extends CameraPlatform { throw CameraException(e.code, e.message); } - if (path == null) { - throw CameraException( - 'INVALID_PATH', - 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', - ); - } - - return XFile(path); + return XFile(path!); } @override diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index aea651b10623..6f4a30ea7901 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -38,13 +38,10 @@ class Camera : public CaptureControllerListener { virtual bool HasDeviceId(std::string &device_id) = 0; virtual bool HasCameraId(int64_t camera_id) = 0; - virtual bool AddPendingResult(PendingResultType type, std::unique_ptr> result) = 0; virtual bool HasPendingResultByType(PendingResultType type) = 0; - virtual camera_windows::CaptureController *GetCaptureController() = 0; - virtual void InitCamera(flutter::TextureRegistrar *texture_registrar, flutter::BinaryMessenger *messenger, bool enable_audio, diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 41ce28fc0775..77f9d7f55598 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -49,7 +49,6 @@ class CameraPlugin : public flutter::Plugin, Camera *GetCameraByDeviceId(std::string &device_id); Camera *GetCameraByCameraId(int64_t camera_id); void DisposeCameraByCameraId(int64_t camera_id); - bool EnumerateVideoCaptureDeviceSources(IMFActivate ***devices, UINT32 *count) override; @@ -61,28 +60,20 @@ class CameraPlugin : public flutter::Plugin, // Method handlers void AvailableCamerasMethodHandler( std::unique_ptr> result); - void CreateMethodHandler(const EncodableMap &args, std::unique_ptr> result); - void InitializeMethodHandler(const EncodableMap &args, std::unique_ptr> result); - void TakePictureMethodHandler(const EncodableMap &args, std::unique_ptr> result); - void StartVideoRecordingMethodHandler(const EncodableMap &args, std::unique_ptr> result); - void StopVideoRecordingMethodHandler(const EncodableMap &args, std::unique_ptr> result); - void ResumePreviewMethodHandler(const EncodableMap &args, std::unique_ptr> result); - void PausePreviewMethodHandler(const EncodableMap &args, std::unique_ptr> result); - void DisposeMethodHandler(const EncodableMap &args, std::unique_ptr> result); }; From caa38a2ceeac985cf8b38da7f89369d07fb3b9e2 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 18:49:30 +0200 Subject: [PATCH 36/93] [camera_windows] code cleanup --- packages/camera/camera_windows/windows/camera.cpp | 2 +- packages/camera/camera_windows/windows/camera.h | 6 ------ .../camera_windows/windows/capture_controller_listener.h | 8 -------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index cb60f0031409..569f0f718aa7 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -63,7 +63,7 @@ void CameraImpl::InitCamera( } // Adds pending result to the pending_results map. -// If result already exists, call result error handler +// Calls method result error handler, if result already exists. bool CameraImpl::AddPendingResult( PendingResultType type, std::unique_ptr> result) { assert(result); diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 6f4a30ea7901..84ebff0455a5 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -81,24 +81,18 @@ class CameraImpl : public Camera { bool HasDeviceId(std::string &device_id) override { return device_id_ == device_id; }; - bool HasCameraId(int64_t camera_id) override { return camera_id_ == camera_id; }; - bool AddPendingResult(PendingResultType type, std::unique_ptr> result) override; - bool HasPendingResultByType(PendingResultType type) override; - camera_windows::CaptureController *GetCaptureController() override { return capture_controller_.get(); }; - void InitCamera(flutter::TextureRegistrar *texture_registrar, flutter::BinaryMessenger *messenger, bool enable_audio, ResolutionPreset resolution_preset) override; - void InitCamera( std::unique_ptr capture_controller_factory, flutter::TextureRegistrar *texture_registrar, diff --git a/packages/camera/camera_windows/windows/capture_controller_listener.h b/packages/camera/camera_windows/windows/capture_controller_listener.h index 50ca5f5bc926..355ae3693a47 100644 --- a/packages/camera/camera_windows/windows/capture_controller_listener.h +++ b/packages/camera/camera_windows/windows/capture_controller_listener.h @@ -15,29 +15,21 @@ class CaptureControllerListener { virtual void OnCreateCaptureEngineSucceeded(int64_t texture_id) = 0; virtual void OnCreateCaptureEngineFailed(const std::string& error) = 0; - virtual void OnStartPreviewSucceeded(int32_t width, int32_t height) = 0; virtual void OnStartPreviewFailed(const std::string& error) = 0; - virtual void OnResumePreviewSucceeded() = 0; virtual void OnResumePreviewFailed(const std::string& error) = 0; - virtual void OnPausePreviewSucceeded() = 0; virtual void OnPausePreviewFailed(const std::string& error) = 0; - virtual void OnStartRecordSucceeded() = 0; virtual void OnStartRecordFailed(const std::string& error) = 0; - virtual void OnStopRecordSucceeded(const std::string& filepath) = 0; virtual void OnStopRecordFailed(const std::string& error) = 0; - virtual void OnTakePictureSucceeded(const std::string& filepath) = 0; virtual void OnTakePictureFailed(const std::string& error) = 0; - virtual void OnVideoRecordSucceeded(const std::string& filepath, int64_t video_duration) = 0; virtual void OnVideoRecordFailed(const std::string& error) = 0; - virtual void OnCaptureError(const std::string& error) = 0; }; From d41b64aca4752f20cb2bd87e9cdf0f5f99057d5c Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 19:05:55 +0200 Subject: [PATCH 37/93] [camera_windows] prefer initializing at declaration rather than in the constructor --- .../camera/camera_windows/windows/camera.cpp | 7 ++--- .../camera/camera_windows/windows/camera.h | 6 ++-- .../windows/capture_engine_listener.h | 4 +-- .../camera_windows/windows/test/mocks.h | 30 +++++++++---------- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index 569f0f718aa7..abf85d26656e 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -27,11 +27,8 @@ std::unique_ptr> BuildChannelForCamera( } // namespace CameraImpl::CameraImpl(const std::string &device_id) - : device_id_(device_id), - capture_controller_(nullptr), - messenger_(nullptr), - camera_id_(-1), - Camera(device_id) {} + : device_id_(device_id), Camera(device_id) {} + CameraImpl::~CameraImpl() { capture_controller_ = nullptr; diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 84ebff0455a5..7bb3bc0b52fd 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -100,9 +100,9 @@ class CameraImpl : public Camera { ResolutionPreset resolution_preset); private: - std::unique_ptr capture_controller_; - flutter::BinaryMessenger *messenger_; - int64_t camera_id_; + std::unique_ptr capture_controller_ = nullptr; + flutter::BinaryMessenger *messenger_ = nullptr; + int64_t camera_id_ = -1; std::string device_id_; // Pending results. diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 9aa8d66cdbc2..0f8aef012157 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -30,7 +30,7 @@ class CaptureEngineListener : public IMFCaptureEngineOnSampleCallback, public IMFCaptureEngineOnEventCallback { public: CaptureEngineListener(CaptureEngineObserver* observer) - : ref_(0), observer_(observer) {} + : observer_(observer) {} ~CaptureEngineListener(){}; @@ -51,7 +51,7 @@ class CaptureEngineListener : public IMFCaptureEngineOnSampleCallback, private: CaptureEngineObserver* observer_; - volatile ULONG ref_; + volatile ULONG ref_ = 0; }; } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 542bb32ec43e..a135019ac655 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -58,7 +58,7 @@ class MockBinaryMessenger : public flutter::BinaryMessenger { class MockTextureRegistrar : public flutter::TextureRegistrar { public: - MockTextureRegistrar() : texture_id_(-1), texture_(nullptr) { + MockTextureRegistrar() { ON_CALL(*this, RegisterTexture) .WillByDefault([this](flutter::TextureVariant* texture) -> int64_t { EXPECT_TRUE(texture); @@ -93,8 +93,9 @@ class MockTextureRegistrar : public flutter::TextureRegistrar { MOCK_METHOD(bool, UnregisterTexture, (int64_t), (override)); MOCK_METHOD(bool, MarkTextureFrameAvailable, (int64_t), (override)); - int64_t texture_id_; - flutter::TextureVariant* texture_; + + int64_t texture_id_ = -1; + flutter::TextureVariant* texture_ = nullptr; }; class MockCameraFactory : public CameraFactory { @@ -266,7 +267,7 @@ class MockCameraPlugin : public CameraPlugin { class MockCaptureSource : public IMFCaptureSource { public: - MockCaptureSource() : ref_(0){}; + MockCaptureSource(){}; ~MockCaptureSource() = default; // IUnknown @@ -327,13 +328,13 @@ class MockCaptureSource : public IMFCaptureSource { (UINT32 uifriendlyName, DWORD* pdwActualStreamIndex)); private: - volatile ULONG ref_; + volatile ULONG ref_ = 0; }; // Uses IMFMediaSourceEx which has SetD3DManager method. class MockMediaSource : public IMFMediaSourceEx { public: - MockMediaSource() : ref_(0){}; + MockMediaSource(){}; ~MockMediaSource() = default; // IUnknown @@ -412,7 +413,7 @@ class MockMediaSource : public IMFMediaSourceEx { HRESULT SetD3DManager(IUnknown* manager) { return S_OK; } private: - volatile ULONG ref_; + volatile ULONG ref_ = 0; }; class MockCapturePreviewSink : public IMFCapturePreviewSink { @@ -530,7 +531,7 @@ class MockCapturePreviewSink : public IMFCapturePreviewSink { private: ~MockCapturePreviewSink() = default; - volatile ULONG ref_; + volatile ULONG ref_ = 0; }; class MockCaptureRecordSink : public IMFCaptureRecordSink { @@ -605,7 +606,7 @@ class MockCaptureRecordSink : public IMFCaptureRecordSink { private: ~MockCaptureRecordSink() = default; - volatile ULONG ref_; + volatile ULONG ref_ = 0; }; class MockCapturePhotoSink : public IMFCapturePhotoSink { @@ -667,7 +668,7 @@ class MockCapturePhotoSink : public IMFCapturePhotoSink { private: ~MockCapturePhotoSink() = default; - volatile ULONG ref_; + volatile ULONG ref_ = 0; }; template @@ -826,8 +827,7 @@ class FakeIMFAttributesBase : public T { class FakeMediaType : public FakeIMFAttributesBase { public: FakeMediaType(GUID major_type, GUID sub_type, int width, int height) - : ref_(0), - major_type_(major_type), + : major_type_(major_type), sub_type_(sub_type), width_(width), height_(height){}; @@ -917,7 +917,7 @@ class FakeMediaType : public FakeIMFAttributesBase { private: ~FakeMediaType() = default; - volatile ULONG ref_; + volatile ULONG ref_ = 0; const GUID major_type_; const GUID sub_type_; const int width_; @@ -926,7 +926,7 @@ class FakeMediaType : public FakeIMFAttributesBase { class MockCaptureEngine : public IMFCaptureEngine { public: - MockCaptureEngine() : ref_(0) { + MockCaptureEngine() { ON_CALL(*this, Initialize) .WillByDefault([this](IMFCaptureEngineOnEventCallback* callback, IMFAttributes* attributes, IUnknown* audioSource, @@ -996,7 +996,7 @@ class MockCaptureEngine : public IMFCaptureEngine { ComPtr callback_; ComPtr videoSource_; ComPtr audioSource_; - volatile ULONG ref_; + volatile ULONG ref_ = 0; bool initialized_ = false; }; From c2bd09c66d359f03ad53e72564d0637603449d42 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 24 Jan 2022 19:21:27 +0200 Subject: [PATCH 38/93] [camera_windows] fix wrong return type --- packages/camera/camera_windows/windows/camera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index abf85d26656e..e1f1d7680623 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -91,7 +91,7 @@ std::unique_ptr> CameraImpl::GetPendingResultByType( bool CameraImpl::HasPendingResultByType(PendingResultType type) { if (pending_results_.empty()) { - return nullptr; + return false; } auto it = pending_results_.find(type); if (it == pending_results_.end()) { From 9cd612dfc3152819ed89236499741d750d2898ad Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 09:27:29 +0200 Subject: [PATCH 39/93] [camera_windows] separate record handler --- .../camera_windows/windows/CMakeLists.txt | 2 + .../windows/capture_controller.cpp | 537 ++++++------------ .../windows/capture_controller.h | 38 +- .../camera_windows/windows/record_handler.cpp | 254 +++++++++ .../camera_windows/windows/record_handler.h | 94 +++ 5 files changed, 554 insertions(+), 371 deletions(-) create mode 100644 packages/camera/camera_windows/windows/record_handler.cpp create mode 100644 packages/camera/camera_windows/windows/record_handler.h diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index da6845196c61..b49a67044f67 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -20,6 +20,8 @@ list(APPEND PLUGIN_SOURCES "string_utils.cpp" "device_info.h" "device_info.cpp" + "record_handler.h" + "record_handler.cpp" ) add_library(${PLUGIN_NAME} SHARED diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 96b9114db7da..ac1bce7bec24 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -8,8 +8,10 @@ #include #include +#include #include +#include "record_handler.h" #include "string_utils.h" namespace camera_windows { @@ -17,7 +19,7 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; CaptureControllerImpl::CaptureControllerImpl( - CaptureControllerListener *listener) + CaptureControllerListener* listener) : capture_controller_listener_(listener), CaptureController(){}; CaptureControllerImpl::~CaptureControllerImpl() { @@ -27,7 +29,7 @@ CaptureControllerImpl::~CaptureControllerImpl() { // static bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( - IMFActivate ***devices, UINT32 *count) { + IMFActivate*** devices, UINT32* count) { ComPtr attributes; HRESULT hr = MFCreateAttributes(&attributes, 1); @@ -44,8 +46,8 @@ bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( return SUCCEEDED(hr); } -HRESULT BuildMediaTypeForVideoPreview(IMFMediaType *src_media_type, - IMFMediaType **preview_media_type) { +HRESULT BuildMediaTypeForVideoPreview(IMFMediaType* src_media_type, + IMFMediaType** preview_media_type) { assert(src_media_type); ComPtr new_media_type; @@ -73,8 +75,8 @@ HRESULT BuildMediaTypeForVideoPreview(IMFMediaType *src_media_type, } // Initializes media type for photo capture for jpeg images. -HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, - IMFMediaType **photo_media_type, +HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType* src_media_type, + IMFMediaType** photo_media_type, GUID image_format) { assert(src_media_type); ComPtr new_media_type; @@ -101,102 +103,11 @@ HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType *src_media_type, return hr; } -// Initializes media type for video capture. -HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, - IMFMediaType **video_record_media_type, - GUID capture_format) { - assert(src_media_type); - ComPtr new_media_type; - - HRESULT hr = MFCreateMediaType(&new_media_type); - - if (SUCCEEDED(hr)) { - // Clones everything from original media type. - hr = src_media_type->CopyAllItems(new_media_type.Get()); - } - - if (SUCCEEDED(hr)) { - hr = new_media_type->SetGUID(MF_MT_SUBTYPE, capture_format); - } - - if (SUCCEEDED(hr)) { - new_media_type.CopyTo(video_record_media_type); - } - - return hr; -} - -// Queries interface object from collection. -template -HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, - Q **ppObj) { - ComPtr pUnk; - HRESULT hr = pCollection->GetElement(index, pUnk.GetAddressOf()); - if (SUCCEEDED(hr)) { - hr = pUnk->QueryInterface(IID_PPV_ARGS(ppObj)); - } - return hr; -} - -// Initializes media type for audo capture. -HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { - ComPtr audio_output_attributes; - ComPtr src_media_type; - ComPtr new_media_type; - ComPtr available_output_types; - DWORD mt_count = 0; - - HRESULT hr = MFCreateAttributes(&audio_output_attributes, 1); - - if (SUCCEEDED(hr)) { - // Enumerates only low latency audio outputs. - hr = audio_output_attributes->SetUINT32(MF_LOW_LATENCY, TRUE); - } - - if (SUCCEEDED(hr)) { - DWORD mft_flags = (MFT_ENUM_FLAG_ALL & (~MFT_ENUM_FLAG_FIELDOFUSE)) | - MFT_ENUM_FLAG_SORTANDFILTER; - - hr = MFTranscodeGetAudioOutputAvailableTypes( - MFAudioFormat_AAC, mft_flags, audio_output_attributes.Get(), - available_output_types.GetAddressOf()); - } - - if (SUCCEEDED(hr)) { - hr = GetCollectionObject(available_output_types.Get(), 0, - src_media_type.GetAddressOf()); - } - - if (SUCCEEDED(hr)) { - hr = available_output_types->GetElementCount(&mt_count); - } - - if (mt_count == 0) { - // No sources found, mark process as failure. - hr = E_FAIL; - } - - if (SUCCEEDED(hr)) { - // Create new media type to copy original media type to. - hr = MFCreateMediaType(&new_media_type); - } - - if (SUCCEEDED(hr)) { - hr = src_media_type->CopyAllItems(new_media_type.Get()); - } - - if (SUCCEEDED(hr)) { - new_media_type.CopyTo(audio_record_media_type); - } - - return hr; -} - // Uses first audio source to capture audio. // Note: Enumerating audio sources via platform interface is not supported. HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { audio_source_ = nullptr; - IMFActivate **devices = nullptr; + IMFActivate** devices = nullptr; UINT32 count = 0; ComPtr attributes; @@ -212,7 +123,7 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { } if (SUCCEEDED(hr) && count > 0) { - wchar_t *audio_device_id; + wchar_t* audio_device_id; UINT32 audio_device_id_size; // Use first audio device. @@ -251,7 +162,7 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { } HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( - const std::string &video_device_id) { + const std::string& video_device_id) { video_source_ = nullptr; ComPtr video_capture_source_attributes; @@ -318,30 +229,38 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine() { if (!capture_engine_) { ComPtr capture_engine_factory; - if (SUCCEEDED(hr)) { - hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&capture_engine_factory)); + hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&capture_engine_factory)); + if (FAILED(hr)) { + return hr; } - if (SUCCEEDED(hr)) { - // Creates CaptureEngine. - hr = capture_engine_factory->CreateInstance( - CLSID_MFCaptureEngine, IID_PPV_ARGS(&capture_engine_)); + // Creates CaptureEngine. + hr = capture_engine_factory->CreateInstance(CLSID_MFCaptureEngine, + IID_PPV_ARGS(&capture_engine_)); + if (FAILED(hr)) { + return hr; } } - if (SUCCEEDED(hr)) { - hr = CreateD3DManagerWithDX11Device(); + hr = CreateD3DManagerWithDX11Device(); + + if (FAILED(hr)) { + return hr; } - if (SUCCEEDED(hr) && !video_source_) { + if (!video_source_) { hr = CreateVideoCaptureSourceForDevice(video_device_id_); + if (FAILED(hr)) { + return hr; + } } - if (enable_audio_record_ && !audio_source_) { - if (SUCCEEDED(hr)) { - hr = CreateDefaultAudioCaptureSource(); + if (record_audio_ && !audio_source_) { + hr = CreateDefaultAudioCaptureSource(); + if (FAILED(hr)) { + return hr; } } @@ -350,26 +269,26 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine() { ComPtr(new CaptureEngineListener(this)); } - if (SUCCEEDED(hr)) { - hr = MFCreateAttributes(&attributes, 2); - } - - if (SUCCEEDED(hr)) { - hr = attributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, - dxgi_device_manager_.Get()); + hr = MFCreateAttributes(&attributes, 2); + if (FAILED(hr)) { + return hr; } - if (SUCCEEDED(hr)) { - hr = attributes->SetUINT32(MF_CAPTURE_ENGINE_USE_VIDEO_DEVICE_ONLY, - !enable_audio_record_); + hr = attributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, + dxgi_device_manager_.Get()); + if (FAILED(hr)) { + return hr; } - if (SUCCEEDED(hr)) { - hr = capture_engine_->Initialize(capture_engine_callback_handler_.Get(), - attributes.Get(), audio_source_.Get(), - video_source_.Get()); + hr = attributes->SetUINT32(MF_CAPTURE_ENGINE_USE_VIDEO_DEVICE_ONLY, + !record_audio_); + if (FAILED(hr)) { + return hr; } + hr = capture_engine_->Initialize(capture_engine_callback_handler_.Get(), + attributes.Get(), audio_source_.Get(), + video_source_.Get()); return hr; } @@ -378,26 +297,29 @@ void CaptureControllerImpl::ResetCaptureController() { StopPreview(); } - if (recording_type_ == RecordingType::RECORDING_TYPE_CONTINUOUS) { - StopRecord(); - } else if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { - StopTimedRecord(); + if (record_handler_) { + if (record_handler_->IsContinuousRecording()) { + StopRecord(); + } else if (record_handler_->IsTimedRecording()) { + StopTimedRecord(); + } + } + + // Shuts down the media foundation platform object. + // Releases all resources including threads. + // Application should call MFShutdown the same number of times as MFStartup + if (media_foundation_started_) { + MFShutdown(); } // States - initialized_ = false; - capture_engine_initialization_pending_ = false; + media_foundation_started_ = false; + capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; + record_handler_ = nullptr; + preview_pending_ = false; previewing_ = false; - record_start_pending_ = false; - record_stop_pending_ = false; - recording_ = false; pending_image_capture_ = false; - max_video_duration_ms_ = -1; - recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; - record_start_timestamp_us_ = -1; - recording_duration_us_ = 0; - max_video_duration_ms_ = -1; // Preview preview_sink_ = nullptr; @@ -435,26 +357,41 @@ void CaptureControllerImpl::ResetCaptureController() { } void CaptureControllerImpl::InitCaptureDevice( - flutter::TextureRegistrar *texture_registrar, const std::string &device_id, + flutter::TextureRegistrar* texture_registrar, const std::string& device_id, bool enable_audio, ResolutionPreset resolution_preset) { assert(capture_controller_listener_); - if (initialized_ && texture_id_ >= 0) { + if (capture_engine_state_ == CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && + texture_id_ >= 0) { return capture_controller_listener_->OnCreateCaptureEngineFailed( "Capture device already initialized"); - } else if (capture_engine_initialization_pending_) { + } else if (capture_engine_state_ == + CaptureEngineState::CAPTURE_ENGINE_INITIALIZING) { return capture_controller_listener_->OnCreateCaptureEngineFailed( "Capture device already initializing"); } - capture_engine_initialization_pending_ = true; + capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_INITIALIZING; resolution_preset_ = resolution_preset; - enable_audio_record_ = enable_audio; + record_audio_ = enable_audio; texture_registrar_ = texture_registrar; video_device_id_ = device_id; - HRESULT hr = CreateCaptureEngine(); + // MFStartup must be called before using Media Foundation. + if (!media_foundation_started_) { + HRESULT hr = MFStartup(MF_VERSION); + + if (FAILED(hr)) { + capture_controller_listener_->OnCreateCaptureEngineFailed( + "Failed to create camera"); + ResetCaptureController(); + return; + } + + media_foundation_started_ = true; + } + HRESULT hr = CreateCaptureEngine(); if (FAILED(hr)) { capture_controller_listener_->OnCreateCaptureEngineFailed( "Failed to create camera"); @@ -463,7 +400,7 @@ void CaptureControllerImpl::InitCaptureDevice( } } -const FlutterDesktopPixelBuffer * +const FlutterDesktopPixelBuffer* CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, size_t target_height) { if (this->source_buffer_data_ && this->source_buffer_size_ > 0 && @@ -472,9 +409,9 @@ CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, this->preview_frame_width_ * this->preview_frame_height_; dest_buffer_ = std::make_unique(pixels_total * 4); - MFVideoFormatRGB32Pixel *src = - (MFVideoFormatRGB32Pixel *)this->source_buffer_data_.get(); - FlutterDesktopPixel *dst = (FlutterDesktopPixel *)dest_buffer_.get(); + MFVideoFormatRGB32Pixel* src = + (MFVideoFormatRGB32Pixel*)this->source_buffer_data_.get(); + FlutterDesktopPixel* dst = (FlutterDesktopPixel*)dest_buffer_.get(); for (uint32_t i = 0; i < pixels_total; i++) { dst[i].r = src[i].r; @@ -495,29 +432,29 @@ CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, void CaptureControllerImpl::TakePicture(const std::string filepath) { assert(capture_controller_listener_); - if (!initialized_) { - return capture_controller_listener_->OnTakePictureFailed("Not initialized"); + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { + return OnPicture(false, "Not initialized"); } if (pending_image_capture_) { - return capture_controller_listener_->OnTakePictureFailed( - "Already capturing image"); + return OnPicture(false, "Already capturing image"); } HRESULT hr = InitPhotoSink(filepath); - if (SUCCEEDED(hr)) { - // Request new photo. - pending_picture_path_ = filepath; - pending_image_capture_ = true; - hr = capture_engine_->TakePhoto(); + if (FAILED(hr)) { + return OnPicture(false, "Failed to init photo sink"); } + // Request new photo + pending_picture_path_ = filepath; + pending_image_capture_ = true; + hr = capture_engine_->TakePhoto(); + if (FAILED(hr)) { pending_image_capture_ = false; pending_picture_path_ = std::string(); - return capture_controller_listener_->OnTakePictureFailed( - "Failed to take picture"); + return OnPicture(false, "Failed to take picture"); } } @@ -547,7 +484,7 @@ uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { } HRESULT CaptureControllerImpl::FindBaseMediaTypes() { - if (!initialized_) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return E_FAIL; } @@ -606,7 +543,7 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { } HRESULT CaptureControllerImpl::InitPreviewSink() { - if (!initialized_) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return E_FAIL; } @@ -658,7 +595,7 @@ HRESULT CaptureControllerImpl::InitPreviewSink() { return hr; } -HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { +HRESULT CaptureControllerImpl::InitPhotoSink(const std::string& filepath) { HRESULT hr = S_OK; if (photo_sink_) { @@ -716,118 +653,37 @@ HRESULT CaptureControllerImpl::InitPhotoSink(const std::string &filepath) { return hr; } -HRESULT CaptureControllerImpl::InitRecordSink(const std::string &filepath) { - HRESULT hr = S_OK; - - if (record_sink_) { - // If record sink already exists, only update output filename. - hr = record_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); - - if (FAILED(hr)) { - record_sink_ = nullptr; - } - - return hr; - } - - ComPtr video_record_media_type; - ComPtr capture_sink; - - // Gets sink with record type. - hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, - &capture_sink); - - if (SUCCEEDED(hr)) { - hr = capture_sink.As(&record_sink_); - } - - if (SUCCEEDED(hr) && !base_capture_media_type_) { - hr = FindBaseMediaTypes(); - } - - if (SUCCEEDED(hr)) { - // Removes existing streams if available. - hr = record_sink_->RemoveAllStreams(); - } - - if (SUCCEEDED(hr)) { - hr = BuildMediaTypeForVideoCapture(base_capture_media_type_.Get(), - video_record_media_type.GetAddressOf(), - MFVideoFormat_H264); - } - - if (SUCCEEDED(hr)) { - DWORD video_record_sink_stream_index; - hr = record_sink_->AddStream( - (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, - video_record_media_type.Get(), nullptr, - &video_record_sink_stream_index); - } - - ComPtr audio_record_media_type; - if (SUCCEEDED(hr) && enable_audio_record_) { - HRESULT audio_capture_hr = S_OK; - audio_capture_hr = - BuildMediaTypeForAudioCapture(audio_record_media_type.GetAddressOf()); - - if (SUCCEEDED(audio_capture_hr)) { - DWORD audio_record_sink_stream_index; - hr = record_sink_->AddStream( - (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_AUDIO, - audio_record_media_type.Get(), nullptr, - &audio_record_sink_stream_index); - } - } - - if (SUCCEEDED(hr)) { - hr = record_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); - } - - if (FAILED(hr)) { - record_sink_ = nullptr; - } - - return hr; -} - // Starts recording. // Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response process. -void CaptureControllerImpl::StartRecord(const std::string &filepath, +void CaptureControllerImpl::StartRecord(const std::string& filepath, int64_t max_video_duration_ms) { assert(capture_controller_listener_); - if (!initialized_) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return capture_controller_listener_->OnStartRecordFailed( "Camera not initialized. Camera should be disposed and reinitialized."); - } else if (recording_) { - return capture_controller_listener_->OnStartRecordFailed( - "Already recording"); - } else if (record_start_pending_) { - return capture_controller_listener_->OnStartRecordFailed( - "Start record already requested"); } - HRESULT hr = InitRecordSink(filepath); - - if (SUCCEEDED(hr)) { - recording_type_ = max_video_duration_ms < 0 - ? RecordingType::RECORDING_TYPE_CONTINUOUS - : RecordingType::RECORDING_TYPE_TIMED; - max_video_duration_ms_ = max_video_duration_ms; - record_start_timestamp_us_ = -1; - recording_duration_us_ = 0; - pending_record_path_ = filepath; - record_start_pending_ = true; + if (!record_handler_) { + record_handler_ = std::make_unique(record_audio_); + } else if (record_handler_->CanStart()) { + return capture_controller_listener_->OnStartRecordFailed( + "Recording cannot be started. Previous recording must be stopped " + "first."); + } - // Request to start recording. - // Check MF_CAPTURE_ENGINE_RECORD_STARTED event with CaptureEngineListener - hr = capture_engine_->StartRecord(); + if (!base_capture_media_type_) { + // Enumerates mediatypes and finds media type for video capture. + if (FAILED(FindBaseMediaTypes())) { + return OnRecordStarted(false, "Failed to initialize video recording"); + } } - if (FAILED(hr)) { - record_start_pending_ = false; - recording_ = false; + if (!record_handler_->StartRecord(filepath, max_video_duration_ms, + capture_engine_.Get(), + base_capture_media_type_.Get())) { + record_handler_ = nullptr; return capture_controller_listener_->OnStartRecordFailed( - "Failed to initialize video recording"); + "Failed to start video recording"); } } @@ -836,25 +692,20 @@ void CaptureControllerImpl::StartRecord(const std::string &filepath, void CaptureControllerImpl::StopRecord() { assert(capture_controller_listener_); - if (!initialized_) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return capture_controller_listener_->OnStopRecordFailed( "Camera not initialized. Camera should be disposed and reinitialized."); - } else if (!recording_ && !record_start_pending_) { - return capture_controller_listener_->OnStopRecordFailed("Not recording"); - } else if (record_stop_pending_) { - return capture_controller_listener_->OnStopRecordFailed( - "Stop already requested"); } - // Request to stop recording. - record_stop_pending_ = true; - HRESULT hr = capture_engine_->StopRecord(true, false); - - if (FAILED(hr)) { - record_stop_pending_ = false; - recording_ = false; + if (!record_handler_ && !record_handler_->CanStop()) { return capture_controller_listener_->OnStopRecordFailed( - "Failed to stop recording"); + "Recording cannot be stopped."); + } + + if (!record_handler_->StopRecord(capture_engine_.Get())) { + record_handler_ = nullptr; + return capture_controller_listener_->OnStartRecordFailed( + "Failed to stop video recording"); } } @@ -862,19 +713,12 @@ void CaptureControllerImpl::StopRecord() { // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process. void CaptureControllerImpl::StopTimedRecord() { assert(capture_controller_listener_); - if (!recording_ && record_stop_pending_ && - recording_type_ != RecordingType::RECORDING_TYPE_TIMED) { + if (!record_handler_ || !record_handler_->IsTimedRecording()) { return; } - // Request to stop recording. - // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event with CaptureEngineListener - record_stop_pending_ = true; - HRESULT hr = capture_engine_->StopRecord(true, false); - - if (FAILED(hr)) { - record_stop_pending_ = false; - recording_ = false; + if (!record_handler_->StopRecord(capture_engine_.Get())) { + record_handler_ = nullptr; return capture_controller_listener_->OnVideoRecordFailed( "Failed to record video"); } @@ -885,7 +729,7 @@ void CaptureControllerImpl::StopTimedRecord() { void CaptureControllerImpl::StartPreview() { assert(capture_controller_listener_); - if (!initialized_) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return OnPreviewStarted( false, "Camera not initialized. Camera should be disposed and reinitialized."); @@ -917,7 +761,8 @@ void CaptureControllerImpl::StartPreview() { void CaptureControllerImpl::StopPreview() { assert(capture_controller_listener_); - if (!initialized_ && (!previewing_ && !preview_pending_)) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && + (!previewing_ && !preview_pending_)) { return; } @@ -957,22 +802,24 @@ void CaptureControllerImpl::ResumePreview() { // Handles capture engine events. // Called via IMFCaptureEngineOnEventCallback implementation. // Implements CaptureEngineObserver::OnEvent. -void CaptureControllerImpl::OnEvent(IMFMediaEvent *event) { - if (!initialized_ && !capture_engine_initialization_pending_) { +void CaptureControllerImpl::OnEvent(IMFMediaEvent* event) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && + capture_engine_state_ != + CaptureEngineState::CAPTURE_ENGINE_INITIALIZING) { return; } - HRESULT event_hr; - HRESULT hr = event->GetStatus(&event_hr); - GUID extended_type_guid; - if (SUCCEEDED(hr)) { - hr = event->GetExtendedType(&extended_type_guid); - } - - if (SUCCEEDED(hr)) { + if (SUCCEEDED(event->GetExtendedType(&extended_type_guid))) { std::string error; + + HRESULT event_hr; + if (FAILED(event->GetStatus(&event_hr))) { + return; + } + if (FAILED(event_hr)) { + // Reads system error error = std::system_category().message(event_hr); } @@ -1002,7 +849,7 @@ void CaptureControllerImpl::OnEvent(IMFMediaEvent *event) { } // Handles Picture event and informs CaptureControllerListener. -void CaptureControllerImpl::OnPicture(bool success, const std::string &error) { +void CaptureControllerImpl::OnPicture(bool success, const std::string& error) { if (capture_controller_listener_) { if (success && !pending_picture_path_.empty()) { capture_controller_listener_->OnTakePictureSucceeded( @@ -1017,13 +864,13 @@ void CaptureControllerImpl::OnPicture(bool success, const std::string &error) { // Handles CaptureEngineInitialized event and informs CaptureControllerListener. void CaptureControllerImpl::OnCaptureEngineInitialized( - bool success, const std::string &error) { + bool success, const std::string& error) { if (capture_controller_listener_) { // Create flutter desktop pixelbuffer texture; texture_ = std::make_unique(flutter::PixelBufferTexture( [this](size_t width, - size_t height) -> const FlutterDesktopPixelBuffer * { + size_t height) -> const FlutterDesktopPixelBuffer* { return this->ConvertPixelBufferForFlutter(width, height); })); @@ -1032,22 +879,19 @@ void CaptureControllerImpl::OnCaptureEngineInitialized( if (new_texture_id >= 0) { texture_id_ = new_texture_id; capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id_); - initialized_ = true; + capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_INITIALIZED; } else { - texture_ = nullptr; - texture_id_ = -1; capture_controller_listener_->OnCreateCaptureEngineFailed( "Failed to create texture_id"); - initialized_ = false; + // Reset state + ResetCaptureController(); } } - - capture_engine_initialization_pending_ = false; } // Handles CaptureEngineError event and informs CaptureControllerListener. void CaptureControllerImpl::OnCaptureEngineError(HRESULT hr, - const std::string &error) { + const std::string& error) { if (capture_controller_listener_) { capture_controller_listener_->OnCaptureError(error); } @@ -1058,7 +902,7 @@ void CaptureControllerImpl::OnCaptureEngineError(HRESULT hr, // Handles PreviewStarted event and informs CaptureControllerListener. void CaptureControllerImpl::OnPreviewStarted(bool success, - const std::string &error) { + const std::string& error) { if (capture_controller_listener_) { if (success && preview_frame_width_ > 0 && preview_frame_height_ > 0) { capture_controller_listener_->OnStartPreviewSucceeded( @@ -1075,61 +919,58 @@ void CaptureControllerImpl::OnPreviewStarted(bool success, // Handles PreviewStopped event. void CaptureControllerImpl::OnPreviewStopped(bool success, - const std::string &error) { + const std::string& error) { // update state previewing_ = false; }; // Handles RecordStarted event and informs CaptureControllerListener. void CaptureControllerImpl::OnRecordStarted(bool success, - const std::string &error) { - if (capture_controller_listener_) { - if (success) { + const std::string& error) { + if (success) { + if (capture_controller_listener_) { capture_controller_listener_->OnStartRecordSucceeded(); - } else { + } + record_handler_->OnRecordStarted(); + } else { + if (capture_controller_listener_) { capture_controller_listener_->OnStartRecordFailed(error); } + record_handler_ = nullptr; } - - // update state - record_start_pending_ = false; - recording_ = success; }; // Handles RecordStopped event and informs CaptureControllerListener. void CaptureControllerImpl::OnRecordStopped(bool success, - const std::string &error) { - if (capture_controller_listener_) { - // Always call stop record handlers, + const std::string& error) { + if (capture_controller_listener_ && record_handler_) { + // Always calls stop record handler, // to handle separate stop record request for timed records. - if (success && !pending_record_path_.empty()) { - capture_controller_listener_->OnStopRecordSucceeded(pending_record_path_); - } else { - capture_controller_listener_->OnStopRecordFailed(error); - } + std::string path = record_handler_->GetRecordPath(); - if (recording_type_ == RecordingType::RECORDING_TYPE_TIMED) { - if (success && !pending_record_path_.empty()) { + if (success) { + capture_controller_listener_->OnStopRecordSucceeded(path); + if (record_handler_->IsTimedRecording()) { capture_controller_listener_->OnVideoRecordSucceeded( - pending_record_path_, (recording_duration_us_ / 1000)); - - } else { + path, (record_handler_->GetRecordedDuration() / 1000)); + } + } else { + capture_controller_listener_->OnStopRecordFailed(error); + if (record_handler_->IsTimedRecording()) { capture_controller_listener_->OnVideoRecordFailed(error); } } } // update state - recording_ = false; - record_stop_pending_ = false; - recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; - pending_record_path_ = std::string(); + + record_handler_ = nullptr; } // Returns pointer to databuffer. // Called via IMFCaptureEngineOnSampleCallback implementation. // Implements CaptureEngineObserver::GetSourceBuffer. -uint8_t *CaptureControllerImpl::GetSourceBuffer(uint32_t current_length) { +uint8_t* CaptureControllerImpl::GetSourceBuffer(uint32_t current_length) { if (this->source_buffer_data_ == nullptr || this->source_buffer_size_ != current_length) { // Update source buffer size. @@ -1154,7 +995,7 @@ void CaptureControllerImpl::OnBufferUpdated() { // Called via IMFCaptureEngineOnSampleCallback implementation. // Implements CaptureEngineObserver::UpdateCaptureTime. void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { - if (!initialized_) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return; } @@ -1164,17 +1005,9 @@ void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { } // Checks if max_video_duration_ms is passed. - if (recording_ && recording_type_ == RecordingType::RECORDING_TYPE_TIMED && - max_video_duration_ms_ > 0) { - if (record_start_timestamp_us_ < 0) { - record_start_timestamp_us_ = capture_time_us; - } - - recording_duration_us_ = (capture_time_us - record_start_timestamp_us_); - - if (!record_stop_pending_ && - recording_duration_us_ >= - (static_cast(max_video_duration_ms_) * 1000)) { + if (record_handler_) { + record_handler_->UpdateRecordingTime(capture_time_us); + if (record_handler_->ShouldStopTimedRecording()) { StopTimedRecord(); } } diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 4e0575397bbb..ae69c450f055 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -14,12 +14,12 @@ #include #include -#include #include #include #include "capture_controller_listener.h" #include "capture_engine_listener.h" +#include "record_handler.h" namespace camera_windows { using Microsoft::WRL::ComPtr; @@ -61,10 +61,10 @@ enum ResolutionPreset { RESOLUTION_PRESET_MAX, }; -enum RecordingType { - RECORDING_TYPE_NOT_SET, - RECORDING_TYPE_CONTINUOUS, - RECORDING_TYPE_TIMED +enum CaptureEngineState { + CAPTURE_ENGINE_NOT_INITIALIZED, + CAPTURE_ENGINE_INITIALIZING, + CAPTURE_ENGINE_INITIALIZED }; class VideoCaptureDeviceEnumerator { @@ -114,7 +114,10 @@ class CaptureControllerImpl : public CaptureController, CaptureControllerImpl(const CaptureControllerImpl&) = delete; CaptureControllerImpl& operator=(const CaptureControllerImpl&) = delete; - bool IsInitialized() { return initialized_; } + bool IsInitialized() { + return capture_engine_state_ == + CaptureEngineState::CAPTURE_ENGINE_INITIALIZED; + } bool IsPreviewing() { return previewing_; } void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, @@ -135,7 +138,9 @@ class CaptureControllerImpl : public CaptureController, // Handlers for CaptureEngineListener events. // From CaptureEngineObserver. bool IsReadyForSample() override { - return initialized_ && previewing_ && !preview_paused_; + return capture_engine_state_ == + CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && + previewing_ && !preview_paused_; } void OnEvent(IMFMediaEvent* event) override; @@ -160,16 +165,20 @@ class CaptureControllerImpl : public CaptureController, }; private: + // states + CaptureEngineState capture_engine_state_ = + CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; + std::unique_ptr record_handler_ = nullptr; + CaptureControllerListener* capture_controller_listener_ = nullptr; - bool initialized_ = false; - bool enable_audio_record_ = false; + bool media_foundation_started_ = false; + bool record_audio_ = false; std::string video_device_id_; ResolutionPreset resolution_preset_ = ResolutionPreset::RESOLUTION_PRESET_MEDIUM; // CaptureEngine objects - bool capture_engine_initialization_pending_ = false; ComPtr capture_engine_; ComPtr capture_engine_callback_handler_; @@ -203,12 +212,6 @@ class CaptureControllerImpl : public CaptureController, // Photo / Record bool pending_image_capture_ = false; - bool record_start_pending_ = false; - bool record_stop_pending_ = false; - bool recording_ = false; - int64_t record_start_timestamp_us_ = -1; - uint64_t recording_duration_us_ = 0; - int64_t max_video_duration_ms_ = -1; uint32_t capture_frame_width_ = 0; uint32_t capture_frame_height_ = 0; @@ -216,9 +219,6 @@ class CaptureControllerImpl : public CaptureController, ComPtr photo_sink_; ComPtr record_sink_; std::string pending_picture_path_ = ""; - std::string pending_record_path_ = ""; - - RecordingType recording_type_ = RecordingType::RECORDING_TYPE_NOT_SET; void ResetCaptureController(); uint32_t GetMaxPreviewHeight(); diff --git a/packages/camera/camera_windows/windows/record_handler.cpp b/packages/camera/camera_windows/windows/record_handler.cpp new file mode 100644 index 000000000000..1041f7ed3bad --- /dev/null +++ b/packages/camera/camera_windows/windows/record_handler.cpp @@ -0,0 +1,254 @@ +// 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 "record_handler.h" + +#include +#include + +#include + +#include "string_utils.h" + +namespace camera_windows { + +using Microsoft::WRL::ComPtr; + +// Initializes media type for video capture. +HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, + IMFMediaType **video_record_media_type, + GUID capture_format) { + assert(src_media_type); + ComPtr new_media_type; + + HRESULT hr = MFCreateMediaType(&new_media_type); + if (FAILED(hr)) { + return hr; + } + + // Clones everything from original media type. + hr = src_media_type->CopyAllItems(new_media_type.Get()); + if (FAILED(hr)) { + return hr; + } + + hr = new_media_type->SetGUID(MF_MT_SUBTYPE, capture_format); + if (FAILED(hr)) { + return hr; + } + + new_media_type.CopyTo(video_record_media_type); + return S_OK; +} + +// Queries interface object from collection. +template +HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, + Q **ppObj) { + ComPtr pUnk; + HRESULT hr = pCollection->GetElement(index, pUnk.GetAddressOf()); + if (FAILED(hr)) { + return hr; + } + return pUnk->QueryInterface(IID_PPV_ARGS(ppObj)); +} + +// Initializes media type for audo capture. +HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { + ComPtr audio_output_attributes; + ComPtr src_media_type; + ComPtr new_media_type; + ComPtr available_output_types; + DWORD mt_count = 0; + + HRESULT hr = MFCreateAttributes(&audio_output_attributes, 1); + if (FAILED(hr)) { + return hr; + } + + // Enumerates only low latency audio outputs. + hr = audio_output_attributes->SetUINT32(MF_LOW_LATENCY, TRUE); + if (FAILED(hr)) { + return hr; + } + + DWORD mft_flags = (MFT_ENUM_FLAG_ALL & (~MFT_ENUM_FLAG_FIELDOFUSE)) | + MFT_ENUM_FLAG_SORTANDFILTER; + + hr = MFTranscodeGetAudioOutputAvailableTypes( + MFAudioFormat_AAC, mft_flags, audio_output_attributes.Get(), + available_output_types.GetAddressOf()); + if (FAILED(hr)) { + return hr; + } + + hr = GetCollectionObject(available_output_types.Get(), 0, + src_media_type.GetAddressOf()); + if (FAILED(hr)) { + return hr; + } + + hr = available_output_types->GetElementCount(&mt_count); + if (FAILED(hr)) { + return hr; + } + + if (mt_count == 0) { + // No sources found, mark process as failure. + return E_FAIL; + } + + // Create new media type to copy original media type to. + hr = MFCreateMediaType(&new_media_type); + if (FAILED(hr)) { + return hr; + } + + hr = src_media_type->CopyAllItems(new_media_type.Get()); + if (FAILED(hr)) { + return hr; + } + + new_media_type.CopyTo(audio_record_media_type); + return hr; +} + +HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine *capture_engine, + IMFMediaType *base_media_type) { + HRESULT hr = S_OK; + if (record_sink_) { + // If record sink already exists, only update output filename. + hr = record_sink_->SetOutputFileName(Utf16FromUtf8(file_path_).c_str()); + + if (FAILED(hr)) { + record_sink_ = nullptr; + } + return hr; + } + + ComPtr video_record_media_type; + ComPtr capture_sink; + + // Gets sink from capture engine with record type. + + hr = capture_engine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, + &capture_sink); + if (FAILED(hr)) { + return hr; + } + + hr = capture_sink.As(&record_sink_); + if (FAILED(hr)) { + return hr; + } + + // Removes existing streams if available. + hr = record_sink_->RemoveAllStreams(); + if (FAILED(hr)) { + return hr; + } + + hr = BuildMediaTypeForVideoCapture(base_media_type, + video_record_media_type.GetAddressOf(), + MFVideoFormat_H264); + if (FAILED(hr)) { + return hr; + } + + DWORD video_record_sink_stream_index; + hr = record_sink_->AddStream( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, + video_record_media_type.Get(), nullptr, &video_record_sink_stream_index); + if (FAILED(hr)) { + return hr; + } + + if (record_audio_) { + ComPtr audio_record_media_type; + HRESULT audio_capture_hr = S_OK; + audio_capture_hr = + BuildMediaTypeForAudioCapture(audio_record_media_type.GetAddressOf()); + + if (SUCCEEDED(audio_capture_hr)) { + DWORD audio_record_sink_stream_index; + hr = record_sink_->AddStream( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_AUDIO, + audio_record_media_type.Get(), nullptr, + &audio_record_sink_stream_index); + } + + if (FAILED(hr)) { + return hr; + } + } + + hr = record_sink_->SetOutputFileName(Utf16FromUtf8(file_path_).c_str()); + + return hr; +} + +bool RecordHandler::StartRecord(const std::string &file_path, + int64_t max_duration, + IMFCaptureEngine *capture_engine, + IMFMediaType *base_media_type) { + assert(!file_path.empty()); + assert(capture_engine); + assert(base_media_type); + + type_ = max_duration < 0 ? RecordingType::CONTINUOUS : RecordingType::TIMED; + max_video_duration_ms_ = max_duration; + file_path_ = file_path; + + if (FAILED(InitRecordSink(capture_engine, base_media_type))) { + return false; + } + + recording_state_ = RecordingState::STARTING; + capture_engine->StartRecord(); + + return true; +} + +bool RecordHandler::StopRecord(IMFCaptureEngine *capture_engine) { + if (recording_state_ == RecordingState::RECORDING) { + recording_state_ = RecordingState::STOPPING; + HRESULT hr = capture_engine->StopRecord(true, false); + return SUCCEEDED(hr); + } + return false; +} + +void RecordHandler::OnRecordStarted() { + if (recording_state_ == RecordingState::STARTING) { + recording_state_ = RecordingState::RECORDING; + } +} + +void RecordHandler::OnRecordStopped() { + if (recording_state_ == RecordingState::STOPPING) { + file_path_ = ""; + recording_start_timestamp_us_ = -1; + recording_duration_us_ = 0; + max_video_duration_ms_ = -1; + recording_state_ = RecordingState::NOT_RECORDING; + } +} + +void RecordHandler::UpdateRecordingTime(uint64_t timestamp) { + if (recording_start_timestamp_us_ < 0) { + recording_start_timestamp_us_ = timestamp; + } + + recording_duration_us_ = (timestamp - recording_start_timestamp_us_); +} + +bool RecordHandler::ShouldStopTimedRecording() { + return type_ == RecordingType::TIMED && + recording_state_ == RecordingState::RECORDING && + max_video_duration_ms_ > 0 && + recording_duration_us_ >= + (static_cast(max_video_duration_ms_) * 1000); +} + +} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h new file mode 100644 index 000000000000..fddb5fb8a093 --- /dev/null +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -0,0 +1,94 @@ +// 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_RECORDING_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORDING_H_ + +#include +#include +#include + +#include +#include + +namespace camera_windows { +using Microsoft::WRL::ComPtr; + +enum RecordingType { CONTINUOUS, TIMED }; + +enum RecordingState { + NOT_RECORDING, + STARTING, + RECORDING, + STOPPING, +}; + +class RecordHandler { + public: + RecordHandler(bool record_audio) : record_audio_(record_audio){}; + virtual ~RecordHandler() = default; + + // Prevent copying. + RecordHandler(RecordHandler const &) = delete; + RecordHandler &operator=(RecordHandler const &) = delete; + + // Initializes record sink and asks capture engine to start recording. + // Sets record state to STARTING. + // Returns false if recording cannot be started. + bool StartRecord(const std::string &filepath, int64_t max_duration, + IMFCaptureEngine *capture_engine, + IMFMediaType *base_media_type); + + // Stops existing recording. + // Returns false if recording cannot be stopped. + bool StopRecord(IMFCaptureEngine *capture_engine); + + // Set the record handler recording state to RECORDING. + void OnRecordStarted(); + + // Resets the record handler state and sets recording state to NOT_RECORDING. + void OnRecordStopped(); + + // Returns true if recording type is continuous recording. + bool IsContinuousRecording() { return type_ == RecordingType::CONTINUOUS; }; + + // Returns true if recording type is timed recording. + bool IsTimedRecording() { return type_ == RecordingType::TIMED; }; + + // Returns true if new recording can be started. + bool CanStart() { return recording_state_ == NOT_RECORDING; }; + + // Returns true if recording can be stopped. + bool CanStop() { return recording_state_ == RECORDING; }; + + // Returns path to video recording. + std::string GetRecordPath() { return file_path_; }; + + // Returns path to video recording in microseconds. + uint64_t GetRecordedDuration() { return recording_duration_us_; }; + + // Calculates new recording time from capture timestamp. + void UpdateRecordingTime(uint64_t timestamp); + + // Tests if recording time has overlapped the max duration + // given for timed recordings. + bool ShouldStopTimedRecording(); + + private: + // Initializes record sink for video file capture. + HRESULT InitRecordSink(IMFCaptureEngine *capture_engine, + IMFMediaType *base_media_type); + + bool record_audio_ = false; + int64_t max_video_duration_ms_ = -1; + int64_t recording_start_timestamp_us_ = -1; + uint64_t recording_duration_us_ = 0; + std::string file_path_ = ""; + RecordingState recording_state_ = RecordingState::NOT_RECORDING; + RecordingType type_; + ComPtr record_sink_; +}; +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORDING_H_ \ No newline at end of file From 528b94abd93fa33e30ecbfb69a2818ce673a9158 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 09:43:34 +0200 Subject: [PATCH 40/93] [camera_windows] type-align pointers --- .../example/windows/runner/main.cpp | 2 +- .../camera/camera_windows/windows/camera.cpp | 42 ++++----- .../camera/camera_windows/windows/camera.h | 74 ++++++++-------- .../camera_windows/windows/camera_plugin.cpp | 86 +++++++++---------- .../camera_windows/windows/camera_plugin.h | 44 +++++----- .../windows/capture_engine_listener.cpp | 18 ++-- .../camera_windows/windows/device_info.cpp | 2 +- .../camera_windows/windows/device_info.h | 2 +- .../camera_windows/windows/record_handler.cpp | 22 ++--- .../camera_windows/windows/record_handler.h | 16 ++-- .../camera_windows/windows/string_utils.h | 4 +- 11 files changed, 156 insertions(+), 156 deletions(-) diff --git a/packages/camera/camera_windows/example/windows/runner/main.cpp b/packages/camera/camera_windows/example/windows/runner/main.cpp index 2ec184ba841b..755a90b42f19 100644 --- a/packages/camera/camera_windows/example/windows/runner/main.cpp +++ b/packages/camera/camera_windows/example/windows/runner/main.cpp @@ -10,7 +10,7 @@ #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { + _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index e1f1d7680623..7f19c85fcfb6 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -17,7 +17,7 @@ const char kErrorEvent[] = "error"; // Helper function for creating messaging channel for camera. std::unique_ptr> BuildChannelForCamera( - flutter::BinaryMessenger *messenger, int64_t camera_id) { + flutter::BinaryMessenger* messenger, int64_t camera_id) { auto channel_name = std::string(kCameraMethodChannelBaseName) + std::to_string(camera_id); return std::make_unique>( @@ -26,7 +26,7 @@ std::unique_ptr> BuildChannelForCamera( } // namespace -CameraImpl::CameraImpl(const std::string &device_id) +CameraImpl::CameraImpl(const std::string& device_id) : device_id_(device_id), Camera(device_id) {} CameraImpl::~CameraImpl() { @@ -36,8 +36,8 @@ CameraImpl::~CameraImpl() { "Plugin disposed before request was handled"); } -void CameraImpl::InitCamera(flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger, +void CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, bool enable_audio, ResolutionPreset resolution_preset) { auto capture_controller_factory = @@ -48,8 +48,8 @@ void CameraImpl::InitCamera(flutter::TextureRegistrar *texture_registrar, void CameraImpl::InitCamera( std::unique_ptr capture_controller_factory, - flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger, bool enable_audio, + flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, bool enable_audio, ResolutionPreset resolution_preset) { assert(!device_id_.empty()); messenger_ = messenger; @@ -100,9 +100,9 @@ bool CameraImpl::HasPendingResultByType(PendingResultType type) { return it->second != nullptr; } -void CameraImpl::SendErrorForPendingResults(const std::string &error_id, - const std::string &descripion) { - for (const auto &pending_result : pending_results_) { +void CameraImpl::SendErrorForPendingResults(const std::string& error_id, + const std::string& descripion) { + for (const auto& pending_result : pending_results_) { std::move(pending_result.second)->Error(error_id, descripion); } pending_results_.clear(); @@ -121,7 +121,7 @@ void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { } // From CaptureControllerListener. -void CameraImpl::OnCreateCaptureEngineFailed(const std::string &error) { +void CameraImpl::OnCreateCaptureEngineFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::CREATE_CAMERA); if (pending_result) { @@ -141,7 +141,7 @@ void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { }; // From CaptureControllerListener. -void CameraImpl::OnStartPreviewFailed(const std::string &error) { +void CameraImpl::OnStartPreviewFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); if (pending_result) { pending_result->Error("camera_error", error); @@ -158,7 +158,7 @@ void CameraImpl::OnResumePreviewSucceeded() { } // From CaptureControllerListener. -void CameraImpl::OnResumePreviewFailed(const std::string &error) { +void CameraImpl::OnResumePreviewFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::RESUME_PREVIEW); if (pending_result) { @@ -176,7 +176,7 @@ void CameraImpl::OnPausePreviewSucceeded() { } // From CaptureControllerListener. -void CameraImpl::OnPausePreviewFailed(const std::string &error) { +void CameraImpl::OnPausePreviewFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); if (pending_result) { @@ -193,7 +193,7 @@ void CameraImpl::OnStartRecordSucceeded() { }; // From CaptureControllerListener. -void CameraImpl::OnStartRecordFailed(const std::string &error) { +void CameraImpl::OnStartRecordFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); if (pending_result) { pending_result->Error("camera_error", error); @@ -201,7 +201,7 @@ void CameraImpl::OnStartRecordFailed(const std::string &error) { }; // From CaptureControllerListener. -void CameraImpl::OnStopRecordSucceeded(const std::string &filepath) { +void CameraImpl::OnStopRecordSucceeded(const std::string& filepath) { auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); if (pending_result) { pending_result->Success(EncodableValue(filepath)); @@ -209,7 +209,7 @@ void CameraImpl::OnStopRecordSucceeded(const std::string &filepath) { }; // From CaptureControllerListener. -void CameraImpl::OnStopRecordFailed(const std::string &error) { +void CameraImpl::OnStopRecordFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); if (pending_result) { pending_result->Error("camera_error", error); @@ -217,7 +217,7 @@ void CameraImpl::OnStopRecordFailed(const std::string &error) { }; // From CaptureControllerListener. -void CameraImpl::OnTakePictureSucceeded(const std::string &filepath) { +void CameraImpl::OnTakePictureSucceeded(const std::string& filepath) { auto pending_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); if (pending_result) { pending_result->Success(EncodableValue(filepath)); @@ -225,7 +225,7 @@ void CameraImpl::OnTakePictureSucceeded(const std::string &filepath) { }; // From CaptureControllerListener. -void CameraImpl::OnTakePictureFailed(const std::string &error) { +void CameraImpl::OnTakePictureFailed(const std::string& error) { auto pending_take_picture_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); if (pending_take_picture_result) { @@ -234,7 +234,7 @@ void CameraImpl::OnTakePictureFailed(const std::string &error) { }; // From CaptureControllerListener. -void CameraImpl::OnVideoRecordSucceeded(const std::string &filepath, +void CameraImpl::OnVideoRecordSucceeded(const std::string& filepath, int64_t video_duration) { if (messenger_ && camera_id_ >= 0) { auto channel = BuildChannelForCamera(messenger_, camera_id_); @@ -250,9 +250,9 @@ void CameraImpl::OnVideoRecordSucceeded(const std::string &filepath, } // From CaptureControllerListener. -void CameraImpl::OnVideoRecordFailed(const std::string &error){}; +void CameraImpl::OnVideoRecordFailed(const std::string& error){}; -void CameraImpl::OnCaptureError(const std::string &error) { +void CameraImpl::OnCaptureError(const std::string& error) { if (messenger_ && camera_id_ >= 0) { auto channel = BuildChannelForCamera(messenger_, camera_id_); std::unique_ptr message_data = diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 7bb3bc0b52fd..f963f0bb1fe1 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -29,56 +29,56 @@ enum PendingResultType { class Camera : public CaptureControllerListener { public: - Camera(const std::string &device_id){}; + Camera(const std::string& device_id){}; virtual ~Camera() = default; // Disallow copy and move. - Camera(const Camera &) = delete; - Camera &operator=(const Camera &) = delete; + Camera(const Camera&) = delete; + Camera& operator=(const Camera&) = delete; - virtual bool HasDeviceId(std::string &device_id) = 0; + virtual bool HasDeviceId(std::string& device_id) = 0; virtual bool HasCameraId(int64_t camera_id) = 0; virtual bool AddPendingResult(PendingResultType type, std::unique_ptr> result) = 0; virtual bool HasPendingResultByType(PendingResultType type) = 0; - virtual camera_windows::CaptureController *GetCaptureController() = 0; - virtual void InitCamera(flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger, + virtual camera_windows::CaptureController* GetCaptureController() = 0; + virtual void InitCamera(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, bool enable_audio, ResolutionPreset resolution_preset) = 0; }; class CameraImpl : public Camera { public: - CameraImpl(const std::string &device_id); + CameraImpl(const std::string& device_id); virtual ~CameraImpl(); // Disallow copy and move. - CameraImpl(const CameraImpl &) = delete; - CameraImpl &operator=(const CameraImpl &) = delete; + CameraImpl(const CameraImpl&) = delete; + CameraImpl& operator=(const CameraImpl&) = delete; // From CaptureControllerListener. void OnCreateCaptureEngineSucceeded(int64_t texture_id) override; - void OnCreateCaptureEngineFailed(const std::string &error) override; + void OnCreateCaptureEngineFailed(const std::string& error) override; void OnStartPreviewSucceeded(int32_t width, int32_t height) override; - void OnStartPreviewFailed(const std::string &error) override; + void OnStartPreviewFailed(const std::string& error) override; void OnPausePreviewSucceeded() override; - void OnPausePreviewFailed(const std::string &error) override; + void OnPausePreviewFailed(const std::string& error) override; void OnResumePreviewSucceeded() override; - void OnResumePreviewFailed(const std::string &error) override; + void OnResumePreviewFailed(const std::string& error) override; void OnStartRecordSucceeded() override; - void OnStartRecordFailed(const std::string &error) override; - void OnStopRecordSucceeded(const std::string &filepath) override; - void OnStopRecordFailed(const std::string &error) override; - void OnTakePictureSucceeded(const std::string &filepath) override; - void OnTakePictureFailed(const std::string &error) override; - void OnVideoRecordSucceeded(const std::string &filepath, + void OnStartRecordFailed(const std::string& error) override; + void OnStopRecordSucceeded(const std::string& filepath) override; + void OnStopRecordFailed(const std::string& error) override; + void OnTakePictureSucceeded(const std::string& filepath) override; + void OnTakePictureFailed(const std::string& error) override; + void OnVideoRecordSucceeded(const std::string& filepath, int64_t video_duration) override; - void OnVideoRecordFailed(const std::string &error) override; - void OnCaptureError(const std::string &error) override; + void OnVideoRecordFailed(const std::string& error) override; + void OnCaptureError(const std::string& error) override; // From Camera. - bool HasDeviceId(std::string &device_id) override { + bool HasDeviceId(std::string& device_id) override { return device_id_ == device_id; }; bool HasCameraId(int64_t camera_id) override { @@ -87,21 +87,21 @@ class CameraImpl : public Camera { bool AddPendingResult(PendingResultType type, std::unique_ptr> result) override; bool HasPendingResultByType(PendingResultType type) override; - camera_windows::CaptureController *GetCaptureController() override { + camera_windows::CaptureController* GetCaptureController() override { return capture_controller_.get(); }; - void InitCamera(flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger, bool enable_audio, + void InitCamera(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, bool enable_audio, ResolutionPreset resolution_preset) override; void InitCamera( std::unique_ptr capture_controller_factory, - flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger, bool enable_audio, + flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, bool enable_audio, ResolutionPreset resolution_preset); private: std::unique_ptr capture_controller_ = nullptr; - flutter::BinaryMessenger *messenger_ = nullptr; + flutter::BinaryMessenger* messenger_ = nullptr; int64_t camera_id_ = -1; std::string device_id_; @@ -109,8 +109,8 @@ class CameraImpl : public Camera { std::map>> pending_results_; std::unique_ptr> GetPendingResultByType( PendingResultType type); - void SendErrorForPendingResults(const std::string &error_id, - const std::string &descripion); + void SendErrorForPendingResults(const std::string& error_id, + const std::string& descripion); }; class CameraFactory { @@ -119,11 +119,11 @@ class CameraFactory { virtual ~CameraFactory() = default; // Disallow copy and move. - CameraFactory(const CameraFactory &) = delete; - CameraFactory &operator=(const CameraFactory &) = delete; + CameraFactory(const CameraFactory&) = delete; + CameraFactory& operator=(const CameraFactory&) = delete; virtual std::unique_ptr CreateCamera( - const std::string &device_id) = 0; + const std::string& device_id) = 0; }; class CameraFactoryImpl : public CameraFactory { @@ -132,10 +132,10 @@ class CameraFactoryImpl : public CameraFactory { virtual ~CameraFactoryImpl() = default; // Disallow copy and move. - CameraFactoryImpl(const CameraFactoryImpl &) = delete; - CameraFactoryImpl &operator=(const CameraFactoryImpl &) = delete; + CameraFactoryImpl(const CameraFactoryImpl&) = delete; + CameraFactoryImpl& operator=(const CameraFactoryImpl&) = delete; - std::unique_ptr CreateCamera(const std::string &device_id) override { + std::unique_ptr CreateCamera(const std::string& device_id) override { return std::make_unique(device_id); }; }; diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 96cfb1d025b6..3e29ee13f0fa 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -60,7 +60,7 @@ const std::string kVideoCaptureExtension = "mp4"; // Looks for |key| in |map|, returning the associated value if it is present, or // a nullptr if not. -const EncodableValue *ValueOrNull(const EncodableMap &map, const char *key) { +const EncodableValue* ValueOrNull(const EncodableMap& map, const char* key) { auto it = map.find(EncodableValue(key)); if (it == map.end()) { return nullptr; @@ -69,7 +69,7 @@ const EncodableValue *ValueOrNull(const EncodableMap &map, const char *key) { } // Parses resolution preset argument to enum value -ResolutionPreset ParseResolutionPreset(const std::string &resolution_preset) { +ResolutionPreset ParseResolutionPreset(const std::string& resolution_preset) { if (resolution_preset.compare(kResolutionPresetValueLow) == 0) { return ResolutionPreset::RESOLUTION_PRESET_LOW; } else if (resolution_preset.compare(kResolutionPresetValueMedium) == 0) { @@ -86,8 +86,8 @@ ResolutionPreset ParseResolutionPreset(const std::string &resolution_preset) { return ResolutionPreset::RESOLUTION_PRESET_AUTO; } -bool HasCurrentTextureId(int64_t current_camera_id, const EncodableMap &args) { - const auto *camera_id = +bool HasCurrentTextureId(int64_t current_camera_id, const EncodableMap& args) { + const auto* camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { @@ -96,16 +96,16 @@ bool HasCurrentTextureId(int64_t current_camera_id, const EncodableMap &args) { return current_camera_id == *camera_id; } -std::unique_ptr GetDeviceInfo(IMFActivate *device) { +std::unique_ptr GetDeviceInfo(IMFActivate* device) { assert(device); auto device_info = std::make_unique(); - wchar_t *name; + wchar_t* name; UINT32 name_size; HRESULT hr = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size); if (SUCCEEDED(hr)) { - wchar_t *id; + wchar_t* id; UINT32 id_size; hr = device->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, &id_size); @@ -144,8 +144,8 @@ std::string GetCurrentTimeString() { return time_start + std::to_string(ms - s * 1000); } -bool GetFilePathForPicture(std::string &filename) { - wchar_t *known_folder_path = nullptr; +bool GetFilePathForPicture(std::string& filename) { + wchar_t* known_folder_path = nullptr; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Pictures, KF_FLAG_CREATE, nullptr, &known_folder_path); @@ -159,8 +159,8 @@ bool GetFilePathForPicture(std::string &filename) { return SUCCEEDED(hr); } -bool GetFilePathForVideo(std::string &filename) { - wchar_t *known_folder_path = nullptr; +bool GetFilePathForVideo(std::string& filename) { + wchar_t* known_folder_path = nullptr; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Videos, KF_FLAG_CREATE, nullptr, &known_folder_path); @@ -177,7 +177,7 @@ bool GetFilePathForVideo(std::string &filename) { // static void CameraPlugin::RegisterWithRegistrar( - flutter::PluginRegistrarWindows *registrar) { + flutter::PluginRegistrarWindows* registrar) { auto channel = std::make_unique>( registrar->messenger(), kChannelName, &flutter::StandardMethodCodec::GetInstance()); @@ -186,21 +186,21 @@ void CameraPlugin::RegisterWithRegistrar( registrar->texture_registrar(), registrar->messenger()); channel->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto &call, auto result) { + [plugin_pointer = plugin.get()](const auto& call, auto result) { plugin_pointer->HandleMethodCall(call, std::move(result)); }); registrar->AddPlugin(std::move(plugin)); } -CameraPlugin::CameraPlugin(flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger) +CameraPlugin::CameraPlugin(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger) : texture_registrar_(texture_registrar), messenger_(messenger), camera_factory_(std::make_unique()) {} -CameraPlugin::CameraPlugin(flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger, +CameraPlugin::CameraPlugin(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, std::unique_ptr camera_factory) : texture_registrar_(texture_registrar), messenger_(messenger), @@ -209,56 +209,56 @@ CameraPlugin::CameraPlugin(flutter::TextureRegistrar *texture_registrar, CameraPlugin::~CameraPlugin() {} void CameraPlugin::HandleMethodCall( - const flutter::MethodCall<> &method_call, + const flutter::MethodCall<>& method_call, std::unique_ptr> result) { - const std::string &method_name = method_call.method_name(); + const std::string& method_name = method_call.method_name(); if (method_name.compare(kAvailableCamerasMethod) == 0) { return AvailableCamerasMethodHandler(std::move(result)); } else if (method_name.compare(kCreateMethod) == 0) { - const auto *arguments = + const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return CreateMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kInitializeMethod) == 0) { - const auto *arguments = + const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return this->InitializeMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kTakePictureMethod) == 0) { - const auto *arguments = + const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return TakePictureMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kStartVideoRecordingMethod) == 0) { - const auto *arguments = + const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return StartVideoRecordingMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kStopVideoRecordingMethod) == 0) { - const auto *arguments = + const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return StopVideoRecordingMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kPausePreview) == 0) { - const auto *arguments = + const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return PausePreviewMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kResumePreview) == 0) { - const auto *arguments = + const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return ResumePreviewMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kDisposeMethod) == 0) { - const auto *arguments = + const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); @@ -271,7 +271,7 @@ void CameraPlugin::HandleMethodCall( void CameraPlugin::AvailableCamerasMethodHandler( std::unique_ptr> result) { // Enumerate devices. - IMFActivate **devices; + IMFActivate** devices; UINT32 count = 0; if (!this->EnumerateVideoCaptureDeviceSources(&devices, &count)) { result->Error("System error", "Failed to get available cameras"); @@ -302,14 +302,14 @@ void CameraPlugin::AvailableCamerasMethodHandler( result->Success(std::move(EncodableValue(devices_list))); } -bool CameraPlugin::EnumerateVideoCaptureDeviceSources(IMFActivate ***devices, - UINT32 *count) { +bool CameraPlugin::EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, + UINT32* count) { return CaptureControllerImpl::EnumerateVideoCaptureDeviceSources(devices, count); } // Loops through cameras and returns camera with matching device_id or nullptr. -Camera *CameraPlugin::GetCameraByDeviceId(std::string &device_id) { +Camera* CameraPlugin::GetCameraByDeviceId(std::string& device_id) { for (auto it = begin(cameras_); it != end(cameras_); ++it) { if ((*it)->HasDeviceId(device_id)) { return it->get(); @@ -319,7 +319,7 @@ Camera *CameraPlugin::GetCameraByDeviceId(std::string &device_id) { } // Loops through cameras and returns camera with matching camera_id or nullptr. -Camera *CameraPlugin::GetCameraByCameraId(int64_t camera_id) { +Camera* CameraPlugin::GetCameraByCameraId(int64_t camera_id) { for (auto it = begin(cameras_); it != end(cameras_); ++it) { if ((*it)->HasCameraId(camera_id)) { return it->get(); @@ -340,9 +340,9 @@ void CameraPlugin::DisposeCameraByCameraId(int64_t camera_id) { // Creates and initializes capture controller // and MFCaptureEngine for requested device. void CameraPlugin::CreateMethodHandler( - const EncodableMap &args, std::unique_ptr> result) { + const EncodableMap& args, std::unique_ptr> result) { // Parse enableAudio argument. - const auto *enable_audio = + const auto* enable_audio = std::get_if(ValueOrNull(args, kEnableAudioKey)); if (!enable_audio) { return result->Error("argument_error", @@ -350,7 +350,7 @@ void CameraPlugin::CreateMethodHandler( } // Parse cameraName argument. - const auto *camera_name = + const auto* camera_name = std::get_if(ValueOrNull(args, kCameraNameKey)); if (!camera_name) { return result->Error("argument_error", @@ -380,7 +380,7 @@ void CameraPlugin::CreateMethodHandler( if (camera->AddPendingResult(PendingResultType::CREATE_CAMERA, std::move(result))) { // Parse resolution preset argument. - const auto *resolution_preset_argument = + const auto* resolution_preset_argument = std::get_if(ValueOrNull(args, kResolutionPresetKey)); ResolutionPreset resolution_preset; if (resolution_preset_argument) { @@ -396,7 +396,7 @@ void CameraPlugin::CreateMethodHandler( } void CameraPlugin::InitializeMethodHandler( - const EncodableMap &args, std::unique_ptr> result) { + const EncodableMap& args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { return result->Error("argument_error", @@ -422,7 +422,7 @@ void CameraPlugin::InitializeMethodHandler( } void CameraPlugin::PausePreviewMethodHandler( - const EncodableMap &args, std::unique_ptr> result) { + const EncodableMap& args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { return result->Error("argument_error", @@ -448,7 +448,7 @@ void CameraPlugin::PausePreviewMethodHandler( } void CameraPlugin::ResumePreviewMethodHandler( - const EncodableMap &args, std::unique_ptr> result) { + const EncodableMap& args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { return result->Error("argument_error", @@ -474,7 +474,7 @@ void CameraPlugin::ResumePreviewMethodHandler( } void CameraPlugin::StartVideoRecordingMethodHandler( - const EncodableMap &args, std::unique_ptr> result) { + const EncodableMap& args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { return result->Error("argument_error", @@ -515,7 +515,7 @@ void CameraPlugin::StartVideoRecordingMethodHandler( } void CameraPlugin::StopVideoRecordingMethodHandler( - const EncodableMap &args, std::unique_ptr> result) { + const EncodableMap& args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { return result->Error("argument_error", @@ -541,7 +541,7 @@ void CameraPlugin::StopVideoRecordingMethodHandler( } void CameraPlugin::TakePictureMethodHandler( - const EncodableMap &args, std::unique_ptr> result) { + const EncodableMap& args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { return result->Error("argument_error", @@ -572,7 +572,7 @@ void CameraPlugin::TakePictureMethodHandler( } void CameraPlugin::DisposeMethodHandler( - const EncodableMap &args, std::unique_ptr> result) { + const EncodableMap& args, std::unique_ptr> result) { auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); if (!camera_id) { return result->Error("argument_error", diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 77f9d7f55598..8957e8a925b4 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -22,59 +22,59 @@ using flutter::MethodResult; class CameraPlugin : public flutter::Plugin, public VideoCaptureDeviceEnumerator { public: - static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); - CameraPlugin(flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger); + CameraPlugin(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger); // Creates a plugin instance with the given CameraFactory instance. // Exists for unit testing with mock implementations. - CameraPlugin(flutter::TextureRegistrar *texture_registrar, - flutter::BinaryMessenger *messenger, + CameraPlugin(flutter::TextureRegistrar* texture_registrar, + flutter::BinaryMessenger* messenger, std::unique_ptr camera_factory); virtual ~CameraPlugin(); // Disallow copy and move. - CameraPlugin(const CameraPlugin &) = delete; - CameraPlugin &operator=(const CameraPlugin &) = delete; + CameraPlugin(const CameraPlugin&) = delete; + CameraPlugin& operator=(const CameraPlugin&) = delete; // Called when a method is called on plugin channel. - void HandleMethodCall(const flutter::MethodCall<> &method_call, + void HandleMethodCall(const flutter::MethodCall<>& method_call, std::unique_ptr> result); protected: std::vector> cameras_; - Camera *GetCameraByDeviceId(std::string &device_id); - Camera *GetCameraByCameraId(int64_t camera_id); + Camera* GetCameraByDeviceId(std::string& device_id); + Camera* GetCameraByCameraId(int64_t camera_id); void DisposeCameraByCameraId(int64_t camera_id); - bool EnumerateVideoCaptureDeviceSources(IMFActivate ***devices, - UINT32 *count) override; + bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, + UINT32* count) override; private: std::unique_ptr camera_factory_; - flutter::TextureRegistrar *texture_registrar_; - flutter::BinaryMessenger *messenger_; + flutter::TextureRegistrar* texture_registrar_; + flutter::BinaryMessenger* messenger_; // Method handlers void AvailableCamerasMethodHandler( std::unique_ptr> result); - void CreateMethodHandler(const EncodableMap &args, + void CreateMethodHandler(const EncodableMap& args, std::unique_ptr> result); - void InitializeMethodHandler(const EncodableMap &args, + void InitializeMethodHandler(const EncodableMap& args, std::unique_ptr> result); - void TakePictureMethodHandler(const EncodableMap &args, + void TakePictureMethodHandler(const EncodableMap& args, std::unique_ptr> result); - void StartVideoRecordingMethodHandler(const EncodableMap &args, + void StartVideoRecordingMethodHandler(const EncodableMap& args, std::unique_ptr> result); - void StopVideoRecordingMethodHandler(const EncodableMap &args, + void StopVideoRecordingMethodHandler(const EncodableMap& args, std::unique_ptr> result); - void ResumePreviewMethodHandler(const EncodableMap &args, + void ResumePreviewMethodHandler(const EncodableMap& args, std::unique_ptr> result); - void PausePreviewMethodHandler(const EncodableMap &args, + void PausePreviewMethodHandler(const EncodableMap& args, std::unique_ptr> result); - void DisposeMethodHandler(const EncodableMap &args, + void DisposeMethodHandler(const EncodableMap& args, std::unique_ptr> result); }; diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp index 219206ce0416..6b58b5413871 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.cpp +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -29,23 +29,23 @@ CaptureEngineListener::Release() { // IUnknown STDMETHODIMP_(HRESULT) -CaptureEngineListener::QueryInterface(const IID &riid, void **ppv) { +CaptureEngineListener::QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFCaptureEngineOnEventCallback) { - *ppv = static_cast(this); - ((IUnknown *)*ppv)->AddRef(); + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); return S_OK; } else if (riid == IID_IMFCaptureEngineOnSampleCallback) { - *ppv = static_cast(this); - ((IUnknown *)*ppv)->AddRef(); + *ppv = static_cast(this); + ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } -STDMETHODIMP CaptureEngineListener::OnEvent(IMFMediaEvent *event) { +STDMETHODIMP CaptureEngineListener::OnEvent(IMFMediaEvent* event) { if (observer_) { observer_->OnEvent(event); } @@ -53,7 +53,7 @@ STDMETHODIMP CaptureEngineListener::OnEvent(IMFMediaEvent *event) { } // IMFCaptureEngineOnSampleCallback -HRESULT CaptureEngineListener::OnSample(IMFSample *sample) { +HRESULT CaptureEngineListener::OnSample(IMFSample* sample) { HRESULT hr = S_OK; if (this->observer_ && sample) { @@ -77,9 +77,9 @@ HRESULT CaptureEngineListener::OnSample(IMFSample *sample) { if (SUCCEEDED(hr) && buffer) { DWORD max_length = 0; DWORD current_length = 0; - uint8_t *data; + uint8_t* data; if (SUCCEEDED(buffer->Lock(&data, &max_length, ¤t_length))) { - uint8_t *src_buffer = this->observer_->GetSourceBuffer(current_length); + uint8_t* src_buffer = this->observer_->GetSourceBuffer(current_length); if (src_buffer) { std::copy(data, data + current_length, src_buffer); } diff --git a/packages/camera/camera_windows/windows/device_info.cpp b/packages/camera/camera_windows/windows/device_info.cpp index 8c7546bbdbc4..47dae658279b 100644 --- a/packages/camera/camera_windows/windows/device_info.cpp +++ b/packages/camera/camera_windows/windows/device_info.cpp @@ -14,7 +14,7 @@ std::string GetUniqueDeviceName( } std::unique_ptr ParseDeviceInfoFromCameraName( - const std::string &camera_name) { + const std::string& camera_name) { size_t delimeter_index = camera_name.rfind(' ', camera_name.length()); if (delimeter_index != std::string::npos) { auto deviceInfo = std::make_unique(); diff --git a/packages/camera/camera_windows/windows/device_info.h b/packages/camera/camera_windows/windows/device_info.h index 904c3cb35b2a..0c74fe0d3fcd 100644 --- a/packages/camera/camera_windows/windows/device_info.h +++ b/packages/camera/camera_windows/windows/device_info.h @@ -18,7 +18,7 @@ struct CaptureDeviceInfo { std::string GetUniqueDeviceName(std::unique_ptr device_info); std::unique_ptr ParseDeviceInfoFromCameraName( - const std::string &device_name); + const std::string& device_name); } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/record_handler.cpp b/packages/camera/camera_windows/windows/record_handler.cpp index 1041f7ed3bad..0bc51377a7da 100644 --- a/packages/camera/camera_windows/windows/record_handler.cpp +++ b/packages/camera/camera_windows/windows/record_handler.cpp @@ -16,8 +16,8 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; // Initializes media type for video capture. -HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, - IMFMediaType **video_record_media_type, +HRESULT BuildMediaTypeForVideoCapture(IMFMediaType* src_media_type, + IMFMediaType** video_record_media_type, GUID capture_format) { assert(src_media_type); ComPtr new_media_type; @@ -44,8 +44,8 @@ HRESULT BuildMediaTypeForVideoCapture(IMFMediaType *src_media_type, // Queries interface object from collection. template -HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, - Q **ppObj) { +HRESULT GetCollectionObject(IMFCollection* pCollection, DWORD index, + Q** ppObj) { ComPtr pUnk; HRESULT hr = pCollection->GetElement(index, pUnk.GetAddressOf()); if (FAILED(hr)) { @@ -55,7 +55,7 @@ HRESULT GetCollectionObject(IMFCollection *pCollection, DWORD index, } // Initializes media type for audo capture. -HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { +HRESULT BuildMediaTypeForAudioCapture(IMFMediaType** audio_record_media_type) { ComPtr audio_output_attributes; ComPtr src_media_type; ComPtr new_media_type; @@ -114,8 +114,8 @@ HRESULT BuildMediaTypeForAudioCapture(IMFMediaType **audio_record_media_type) { return hr; } -HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine *capture_engine, - IMFMediaType *base_media_type) { +HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type) { HRESULT hr = S_OK; if (record_sink_) { // If record sink already exists, only update output filename. @@ -188,10 +188,10 @@ HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine *capture_engine, return hr; } -bool RecordHandler::StartRecord(const std::string &file_path, +bool RecordHandler::StartRecord(const std::string& file_path, int64_t max_duration, - IMFCaptureEngine *capture_engine, - IMFMediaType *base_media_type) { + IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type) { assert(!file_path.empty()); assert(capture_engine); assert(base_media_type); @@ -210,7 +210,7 @@ bool RecordHandler::StartRecord(const std::string &file_path, return true; } -bool RecordHandler::StopRecord(IMFCaptureEngine *capture_engine) { +bool RecordHandler::StopRecord(IMFCaptureEngine* capture_engine) { if (recording_state_ == RecordingState::RECORDING) { recording_state_ = RecordingState::STOPPING; HRESULT hr = capture_engine->StopRecord(true, false); diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index fddb5fb8a093..436f9ee72f10 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -30,19 +30,19 @@ class RecordHandler { virtual ~RecordHandler() = default; // Prevent copying. - RecordHandler(RecordHandler const &) = delete; - RecordHandler &operator=(RecordHandler const &) = delete; + RecordHandler(RecordHandler const&) = delete; + RecordHandler& operator=(RecordHandler const&) = delete; // Initializes record sink and asks capture engine to start recording. // Sets record state to STARTING. // Returns false if recording cannot be started. - bool StartRecord(const std::string &filepath, int64_t max_duration, - IMFCaptureEngine *capture_engine, - IMFMediaType *base_media_type); + bool StartRecord(const std::string& filepath, int64_t max_duration, + IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type); // Stops existing recording. // Returns false if recording cannot be stopped. - bool StopRecord(IMFCaptureEngine *capture_engine); + bool StopRecord(IMFCaptureEngine* capture_engine); // Set the record handler recording state to RECORDING. void OnRecordStarted(); @@ -77,8 +77,8 @@ class RecordHandler { private: // Initializes record sink for video file capture. - HRESULT InitRecordSink(IMFCaptureEngine *capture_engine, - IMFMediaType *base_media_type); + HRESULT InitRecordSink(IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type); bool record_audio_ = false; int64_t max_video_duration_ms_ = -1; diff --git a/packages/camera/camera_windows/windows/string_utils.h b/packages/camera/camera_windows/windows/string_utils.h index 348b0376db62..562c46a0feea 100644 --- a/packages/camera/camera_windows/windows/string_utils.h +++ b/packages/camera/camera_windows/windows/string_utils.h @@ -12,10 +12,10 @@ namespace camera_windows { // Converts the given UTF-16 string to UTF-8. -std::string Utf8FromUtf16(const std::wstring &utf16_string); +std::string Utf8FromUtf16(const std::wstring& utf16_string); // Converts the given UTF-8 string to UTF-16. -std::wstring Utf16FromUtf8(const std::string &utf8_string); +std::wstring Utf16FromUtf8(const std::string& utf8_string); } // namespace camera_windows From c123cd2b30fa39882e99a0bec7d9ce6b00691361 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 17:54:00 +0200 Subject: [PATCH 41/93] [camera_windows] refactor capture_controller --- .../camera_windows/example/lib/main.dart | 1 + .../camera_windows/windows/CMakeLists.txt | 4 + .../camera/camera_windows/windows/camera.cpp | 40 +- .../camera/camera_windows/windows/camera.h | 30 +- .../camera_windows/windows/camera_plugin.cpp | 87 ++- .../camera_windows/windows/camera_plugin.h | 49 +- .../windows/capture_controller.cpp | 517 +++++++----------- .../windows/capture_controller.h | 141 +++-- .../windows/capture_controller_listener.h | 76 ++- .../windows/capture_engine_listener.cpp | 2 +- .../windows/capture_engine_listener.h | 13 +- .../camera_windows/windows/photo_handler.cpp | 146 +++++ .../camera_windows/windows/photo_handler.h | 74 +++ .../windows/preview_handler.cpp | 163 ++++++ .../camera_windows/windows/preview_handler.h | 100 ++++ .../camera_windows/windows/record_handler.cpp | 20 +- .../camera_windows/windows/record_handler.h | 49 +- .../windows/test/camera_plugin_test.cpp | 6 +- .../windows/test/camera_test.cpp | 16 +- .../camera_windows/windows/test/mocks.h | 10 +- 20 files changed, 1029 insertions(+), 515 deletions(-) create mode 100644 packages/camera/camera_windows/windows/photo_handler.cpp create mode 100644 packages/camera/camera_windows/windows/photo_handler.h create mode 100644 packages/camera/camera_windows/windows/preview_handler.cpp create mode 100644 packages/camera/camera_windows/windows/preview_handler.h diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index bee5ed6c4150..0d5438a90b0c 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -149,6 +149,7 @@ class _MyAppState extends State { _previewSize = null; _recording = false; _recordingTimed = false; + _previewPaused = false; }); getAvailableCameras(); } on CameraException catch (e) { diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index b49a67044f67..4a5bdb7ec892 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -20,8 +20,12 @@ list(APPEND PLUGIN_SOURCES "string_utils.cpp" "device_info.h" "device_info.cpp" + "preview_handler.h" + "preview_handler.cpp" "record_handler.h" "record_handler.cpp" + "photo_handler.h" + "photo_handler.cpp" ) add_library(${PLUGIN_NAME} SHARED diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index 7f19c85fcfb6..c967fa8823ce 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -32,7 +32,7 @@ CameraImpl::CameraImpl(const std::string& device_id) CameraImpl::~CameraImpl() { capture_controller_ = nullptr; - SendErrorForPendingResults("Plugin disposed", + SendErrorForPendingResults("plugin_disposed", "Plugin disposed before request was handled"); } @@ -59,8 +59,6 @@ void CameraImpl::InitCamera( enable_audio, resolution_preset); } -// Adds pending result to the pending_results map. -// Calls method result error handler, if result already exists. bool CameraImpl::AddPendingResult( PendingResultType type, std::unique_ptr> result) { assert(result); @@ -100,15 +98,14 @@ bool CameraImpl::HasPendingResultByType(PendingResultType type) { return it->second != nullptr; } -void CameraImpl::SendErrorForPendingResults(const std::string& error_id, +void CameraImpl::SendErrorForPendingResults(const std::string& error_code, const std::string& descripion) { for (const auto& pending_result : pending_results_) { - std::move(pending_result.second)->Error(error_id, descripion); + std::move(pending_result.second)->Error(error_code, descripion); } pending_results_.clear(); } -// From CaptureControllerListener. void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { // Use texture id as camera id camera_id_ = texture_id; @@ -120,7 +117,6 @@ void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { } } -// From CaptureControllerListener. void CameraImpl::OnCreateCaptureEngineFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::CREATE_CAMERA); @@ -129,7 +125,6 @@ void CameraImpl::OnCreateCaptureEngineFailed(const std::string& error) { } } -// From CaptureControllerListener. void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); if (pending_result) { @@ -140,7 +135,6 @@ void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { } }; -// From CaptureControllerListener. void CameraImpl::OnStartPreviewFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); if (pending_result) { @@ -148,7 +142,6 @@ void CameraImpl::OnStartPreviewFailed(const std::string& error) { } }; -// From CaptureControllerListener. void CameraImpl::OnResumePreviewSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::RESUME_PREVIEW); @@ -157,7 +150,6 @@ void CameraImpl::OnResumePreviewSucceeded() { } } -// From CaptureControllerListener. void CameraImpl::OnResumePreviewFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::RESUME_PREVIEW); @@ -166,7 +158,6 @@ void CameraImpl::OnResumePreviewFailed(const std::string& error) { } } -// From CaptureControllerListener. void CameraImpl::OnPausePreviewSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); @@ -175,7 +166,6 @@ void CameraImpl::OnPausePreviewSucceeded() { } } -// From CaptureControllerListener. void CameraImpl::OnPausePreviewFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); @@ -184,7 +174,6 @@ void CameraImpl::OnPausePreviewFailed(const std::string& error) { } } -// From CaptureControllerListener. void CameraImpl::OnStartRecordSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); if (pending_result) { @@ -192,7 +181,6 @@ void CameraImpl::OnStartRecordSucceeded() { } }; -// From CaptureControllerListener. void CameraImpl::OnStartRecordFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); if (pending_result) { @@ -200,15 +188,13 @@ void CameraImpl::OnStartRecordFailed(const std::string& error) { } }; -// From CaptureControllerListener. -void CameraImpl::OnStopRecordSucceeded(const std::string& filepath) { +void CameraImpl::OnStopRecordSucceeded(const std::string& file_path) { auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); if (pending_result) { - pending_result->Success(EncodableValue(filepath)); + pending_result->Success(EncodableValue(file_path)); } }; -// From CaptureControllerListener. void CameraImpl::OnStopRecordFailed(const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); if (pending_result) { @@ -216,15 +202,13 @@ void CameraImpl::OnStopRecordFailed(const std::string& error) { } }; -// From CaptureControllerListener. -void CameraImpl::OnTakePictureSucceeded(const std::string& filepath) { +void CameraImpl::OnTakePictureSucceeded(const std::string& file_path) { auto pending_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); if (pending_result) { - pending_result->Success(EncodableValue(filepath)); + pending_result->Success(EncodableValue(file_path)); } }; -// From CaptureControllerListener. void CameraImpl::OnTakePictureFailed(const std::string& error) { auto pending_take_picture_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); @@ -233,23 +217,21 @@ void CameraImpl::OnTakePictureFailed(const std::string& error) { } }; -// From CaptureControllerListener. -void CameraImpl::OnVideoRecordSucceeded(const std::string& filepath, - int64_t video_duration) { +void CameraImpl::OnVideoRecordSucceeded(const std::string& file_path, + int64_t video_duration_ms) { if (messenger_ && camera_id_ >= 0) { auto channel = BuildChannelForCamera(messenger_, camera_id_); std::unique_ptr message_data = std::make_unique( - EncodableMap({{EncodableValue("path"), EncodableValue(filepath)}, + EncodableMap({{EncodableValue("path"), EncodableValue(file_path)}, {EncodableValue("maxVideoDuration"), - EncodableValue(video_duration)}})); + EncodableValue(video_duration_ms)}})); channel->InvokeMethod(kVideoRecordedEvent, std::move(message_data)); } } -// From CaptureControllerListener. void CameraImpl::OnVideoRecordFailed(const std::string& error){}; void CameraImpl::OnCaptureError(const std::string& error) { diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index f963f0bb1fe1..f70f6d69a271 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -36,12 +36,24 @@ class Camera : public CaptureControllerListener { Camera(const Camera&) = delete; Camera& operator=(const Camera&) = delete; + // Tests if current camera has given device id. virtual bool HasDeviceId(std::string& device_id) = 0; + + // Tests if current camera has given camera id. virtual bool HasCameraId(int64_t camera_id) = 0; + + // Adds pending result to the pending_results map. + // Calls method result error handler, if result already exists. virtual bool AddPendingResult(PendingResultType type, std::unique_ptr> result) = 0; + + // Checks if pending result with given type already exists. virtual bool HasPendingResultByType(PendingResultType type) = 0; + + // Returns pointer to capture controller. virtual camera_windows::CaptureController* GetCaptureController() = 0; + + // Initializes camera and capture controller. virtual void InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool enable_audio, @@ -68,11 +80,11 @@ class CameraImpl : public Camera { void OnResumePreviewFailed(const std::string& error) override; void OnStartRecordSucceeded() override; void OnStartRecordFailed(const std::string& error) override; - void OnStopRecordSucceeded(const std::string& filepath) override; + void OnStopRecordSucceeded(const std::string& file_path) override; void OnStopRecordFailed(const std::string& error) override; - void OnTakePictureSucceeded(const std::string& filepath) override; + void OnTakePictureSucceeded(const std::string& file_path) override; void OnTakePictureFailed(const std::string& error) override; - void OnVideoRecordSucceeded(const std::string& filepath, + void OnVideoRecordSucceeded(const std::string& file_path, int64_t video_duration) override; void OnVideoRecordFailed(const std::string& error) override; void OnCaptureError(const std::string& error) override; @@ -93,6 +105,9 @@ class CameraImpl : public Camera { void InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool enable_audio, ResolutionPreset resolution_preset) override; + + // Inits camera with capture controller factory. + // Called by InitCamera implementation but also used in tests. void InitCamera( std::unique_ptr capture_controller_factory, flutter::TextureRegistrar* texture_registrar, @@ -109,7 +124,14 @@ class CameraImpl : public Camera { std::map>> pending_results_; std::unique_ptr> GetPendingResultByType( PendingResultType type); - void SendErrorForPendingResults(const std::string& error_id, + + // Loops through all pending results calls their + // error handler with given error id and description. + // Pending results are cleared in the process. + // + // error_code: A string error code describing the error. + // error_message: A user-readable error message (optional). + void SendErrorForPendingResults(const std::string& error_code, const std::string& descripion); }; diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 3e29ee13f0fa..fff1b4f11ced 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -68,7 +68,7 @@ const EncodableValue* ValueOrNull(const EncodableMap& map, const char* key) { return &(it->second); } -// Parses resolution preset argument to enum value +// Parses resolution preset argument to enum value. ResolutionPreset ParseResolutionPreset(const std::string& resolution_preset) { if (resolution_preset.compare(kResolutionPresetValueLow) == 0) { return ResolutionPreset::RESOLUTION_PRESET_LOW; @@ -86,16 +86,7 @@ ResolutionPreset ParseResolutionPreset(const std::string& resolution_preset) { return ResolutionPreset::RESOLUTION_PRESET_AUTO; } -bool HasCurrentTextureId(int64_t current_camera_id, const EncodableMap& args) { - const auto* camera_id = - std::get_if(ValueOrNull(args, kCameraIdKey)); - - if (!camera_id) { - return false; - } - return current_camera_id == *camera_id; -} - +// Builds CaptureDeviceInfo object from given device holding device name and id. std::unique_ptr GetDeviceInfo(IMFActivate* device) { assert(device); auto device_info = std::make_unique(); @@ -122,6 +113,8 @@ std::unique_ptr GetDeviceInfo(IMFActivate* device) { return device_info; } +// Builds datetime string from current time. +// Used as part of the filenames for captured pictures and videos. std::string GetCurrentTimeString() { std::chrono::system_clock::duration now = std::chrono::system_clock::now().time_since_epoch(); @@ -144,7 +137,8 @@ std::string GetCurrentTimeString() { return time_start + std::to_string(ms - s * 1000); } -bool GetFilePathForPicture(std::string& filename) { +// Builds file path for picture capture. +bool GetFilePathForPicture(std::string& file_path) { wchar_t* known_folder_path = nullptr; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Pictures, KF_FLAG_CREATE, nullptr, &known_folder_path); @@ -152,14 +146,15 @@ bool GetFilePathForPicture(std::string& filename) { if (SUCCEEDED(hr)) { std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); - filename = path + "\\" + "PhotoCapture_" + GetCurrentTimeString() + "." + - kPictureCaptureExtension; + file_path = path + "\\" + "PhotoCapture_" + GetCurrentTimeString() + "." + + kPictureCaptureExtension; } return SUCCEEDED(hr); } -bool GetFilePathForVideo(std::string& filename) { +// Builds file path for video capture. +bool GetFilePathForVideo(std::string& file_path) { wchar_t* known_folder_path = nullptr; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Videos, KF_FLAG_CREATE, nullptr, &known_folder_path); @@ -167,8 +162,8 @@ bool GetFilePathForVideo(std::string& filename) { if (SUCCEEDED(hr)) { std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); - filename = path + "\\" + "VideoCapture_" + GetCurrentTimeString() + "." + - kVideoCaptureExtension; + file_path = path + "\\" + "VideoCapture_" + GetCurrentTimeString() + "." + + kVideoCaptureExtension; } return SUCCEEDED(hr); @@ -268,6 +263,33 @@ void CameraPlugin::HandleMethodCall( } } +Camera* CameraPlugin::GetCameraByDeviceId(std::string& device_id) { + for (auto it = begin(cameras_); it != end(cameras_); ++it) { + if ((*it)->HasDeviceId(device_id)) { + return it->get(); + } + } + return nullptr; +} + +Camera* CameraPlugin::GetCameraByCameraId(int64_t camera_id) { + for (auto it = begin(cameras_); it != end(cameras_); ++it) { + if ((*it)->HasCameraId(camera_id)) { + return it->get(); + } + } + return nullptr; +} + +void CameraPlugin::DisposeCameraByCameraId(int64_t camera_id) { + for (auto it = begin(cameras_); it != end(cameras_); ++it) { + if ((*it)->HasCameraId(camera_id)) { + cameras_.erase(it); + return; + } + } +} + void CameraPlugin::AvailableCamerasMethodHandler( std::unique_ptr> result) { // Enumerate devices. @@ -308,37 +330,6 @@ bool CameraPlugin::EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, count); } -// Loops through cameras and returns camera with matching device_id or nullptr. -Camera* CameraPlugin::GetCameraByDeviceId(std::string& device_id) { - for (auto it = begin(cameras_); it != end(cameras_); ++it) { - if ((*it)->HasDeviceId(device_id)) { - return it->get(); - } - } - return nullptr; -} - -// Loops through cameras and returns camera with matching camera_id or nullptr. -Camera* CameraPlugin::GetCameraByCameraId(int64_t camera_id) { - for (auto it = begin(cameras_); it != end(cameras_); ++it) { - if ((*it)->HasCameraId(camera_id)) { - return it->get(); - } - } - return nullptr; -} - -void CameraPlugin::DisposeCameraByCameraId(int64_t camera_id) { - for (auto it = begin(cameras_); it != end(cameras_); ++it) { - if ((*it)->HasCameraId(camera_id)) { - cameras_.erase(it); - return; - } - } -} - -// Creates and initializes capture controller -// and MFCaptureEngine for requested device. void CameraPlugin::CreateMethodHandler( const EncodableMap& args, std::unique_ptr> result) { // Parse enableAudio argument. diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 8957e8a925b4..3c4313d80ca5 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -46,9 +46,18 @@ class CameraPlugin : public flutter::Plugin, protected: std::vector> cameras_; + // Loops through cameras and returns camera + // with matching device_id or nullptr. Camera* GetCameraByDeviceId(std::string& device_id); + + // Loops through cameras and returns camera + // with matching camera_id or nullptr. Camera* GetCameraByCameraId(int64_t camera_id); + + // Disposes camera by camera id. void DisposeCameraByCameraId(int64_t camera_id); + + // Enumerates video capture devices via static CameraController method. bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) override; @@ -57,23 +66,57 @@ class CameraPlugin : public flutter::Plugin, flutter::TextureRegistrar* texture_registrar_; flutter::BinaryMessenger* messenger_; - // Method handlers + // Handles availableCameras method calls. + // Enumerates video capture devices and + // returns list of available camera devices. void AvailableCamerasMethodHandler( std::unique_ptr> result); + + // Handles create method calls. + // Creates and initializes capture controller + // and MFCaptureEngine for requested device. + // Stores MethodResult object to be handled after request is processed. void CreateMethodHandler(const EncodableMap& args, std::unique_ptr> result); + + // Handles initialize method calls. + // Requests existing camera controller to start preview. + // Stores MethodResult object to be handled after request is processed. void InitializeMethodHandler(const EncodableMap& args, std::unique_ptr> result); + + // Handles takePicture method calls. + // Requests existing camera controller to take photo. + // Stores MethodResult object to be handled after request is processed. void TakePictureMethodHandler(const EncodableMap& args, std::unique_ptr> result); + + // Handles startVideoRecording method calls. + // Requests existing camera controller to start recording. + // Stores MethodResult object to be handled after request is processed. void StartVideoRecordingMethodHandler(const EncodableMap& args, std::unique_ptr> result); + + // Handles stopVideoRecording method calls. + // Requests existing camera controller to stop recording. + // Stores MethodResult object to be handled after request is processed. void StopVideoRecordingMethodHandler(const EncodableMap& args, std::unique_ptr> result); - void ResumePreviewMethodHandler(const EncodableMap& args, - std::unique_ptr> result); + + // Handles pausePreview method calls. + // Requests existing camera controller to pause recording. + // Stores MethodResult object to be handled after request is processed. void PausePreviewMethodHandler(const EncodableMap& args, std::unique_ptr> result); + + // Handles resumePreview method calls. + // Requests existing camera controller to resume preview. + // Stores MethodResult object to be handled after request is processed. + void ResumePreviewMethodHandler(const EncodableMap& args, + std::unique_ptr> result); + + // Handles dsipose method calls. + // Disposes camera if exists. void DisposeMethodHandler(const EncodableMap& args, std::unique_ptr> result); }; diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index ac1bce7bec24..3fb250a59cea 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -11,6 +11,8 @@ #include #include +#include "photo_handler.h" +#include "preview_handler.h" #include "record_handler.h" #include "string_utils.h" @@ -46,65 +48,6 @@ bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( return SUCCEEDED(hr); } -HRESULT BuildMediaTypeForVideoPreview(IMFMediaType* src_media_type, - IMFMediaType** preview_media_type) { - assert(src_media_type); - ComPtr new_media_type; - - HRESULT hr = MFCreateMediaType(&new_media_type); - - if (SUCCEEDED(hr)) { - // Clones everything from original media type. - hr = src_media_type->CopyAllItems(new_media_type.Get()); - } - - if (SUCCEEDED(hr)) { - // Changes subtype to MFVideoFormat_RGB32. - hr = new_media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); - } - - if (SUCCEEDED(hr)) { - hr = new_media_type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); - } - - if (SUCCEEDED(hr)) { - new_media_type.CopyTo(preview_media_type); - } - - return hr; -} - -// Initializes media type for photo capture for jpeg images. -HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType* src_media_type, - IMFMediaType** photo_media_type, - GUID image_format) { - assert(src_media_type); - ComPtr new_media_type; - - HRESULT hr = MFCreateMediaType(&new_media_type); - - if (SUCCEEDED(hr)) { - // Clones everything from original media type. - hr = src_media_type->CopyAllItems(new_media_type.Get()); - } - - if (SUCCEEDED(hr)) { - hr = new_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Image); - } - - if (SUCCEEDED(hr)) { - hr = new_media_type->SetGUID(MF_MT_SUBTYPE, image_format); - } - - if (SUCCEEDED(hr)) { - new_media_type.CopyTo(photo_media_type); - } - - return hr; -} - -// Uses first audio source to capture audio. -// Note: Enumerating audio sources via platform interface is not supported. HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { audio_source_ = nullptr; IMFActivate** devices = nullptr; @@ -189,9 +132,9 @@ HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( return hr; } -// Create DX11 Device and D3D Manager -// TODO: Use existing ANGLE device HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { + // TODO: Use existing ANGLE device + HRESULT hr = S_OK; hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, nullptr, 0, @@ -293,10 +236,6 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine() { } void CaptureControllerImpl::ResetCaptureController() { - if (previewing_) { - StopPreview(); - } - if (record_handler_) { if (record_handler_->IsContinuousRecording()) { StopRecord(); @@ -305,6 +244,10 @@ void CaptureControllerImpl::ResetCaptureController() { } } + if (preview_handler_ && preview_handler_->IsInitialized()) { + StopPreview(); + } + // Shuts down the media foundation platform object. // Releases all resources including threads. // Application should call MFShutdown the same number of times as MFStartup @@ -316,29 +259,14 @@ void CaptureControllerImpl::ResetCaptureController() { media_foundation_started_ = false; capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; record_handler_ = nullptr; - - preview_pending_ = false; - previewing_ = false; - pending_image_capture_ = false; - - // Preview - preview_sink_ = nullptr; + preview_handler_ = nullptr; + photo_handler_ = nullptr; preview_frame_width_ = 0; preview_frame_height_ = 0; - - // Photo / Record - photo_sink_ = nullptr; - record_sink_ = nullptr; - capture_frame_width_ = 0; - capture_frame_height_ = 0; - - // CaptureEngine capture_engine_callback_handler_ = nullptr; capture_engine_ = nullptr; - audio_source_ = nullptr; video_source_ = nullptr; - base_preview_media_type_ = nullptr; base_capture_media_type_ = nullptr; @@ -353,6 +281,7 @@ void CaptureControllerImpl::ResetCaptureController() { if (texture_registrar_ && texture_id_ > -1) { texture_registrar_->UnregisterTexture(texture_id_); } + texture_id_ = -1; texture_ = nullptr; } @@ -429,32 +358,34 @@ CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, return nullptr; } -void CaptureControllerImpl::TakePicture(const std::string filepath) { - assert(capture_controller_listener_); +void CaptureControllerImpl::TakePicture(const std::string file_path) { + assert(capture_engine_callback_handler_); + assert(capture_engine_); if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return OnPicture(false, "Not initialized"); } - if (pending_image_capture_) { - return OnPicture(false, "Already capturing image"); + if (!base_capture_media_type_) { + // Enumerates mediatypes and finds media type for video capture. + if (FAILED(FindBaseMediaTypes())) { + return OnPicture(false, "Failed to initialize photo capture"); + } } - HRESULT hr = InitPhotoSink(filepath); - - if (FAILED(hr)) { - return OnPicture(false, "Failed to init photo sink"); + if (!photo_handler_) { + photo_handler_ = std::make_unique(); + } else if (photo_handler_->IsTakingPhoto()) { + return OnPicture(false, "Photo already requested"); } - // Request new photo - pending_picture_path_ = filepath; - pending_image_capture_ = true; - hr = capture_engine_->TakePhoto(); - - if (FAILED(hr)) { - pending_image_capture_ = false; - pending_picture_path_ = std::string(); - return OnPicture(false, "Failed to take picture"); + // Check MF_CAPTURE_ENGINE_PHOTO_TAKEN event handling + // for response process. + if (!photo_handler_->TakePhoto(file_path, capture_engine_.Get(), + base_capture_media_type_.Get())) { + // Destroy photo handler on error cases to make sure state is resetted. + photo_handler_ = nullptr; + return OnPicture(false, "Failed to take photo"); } } @@ -483,194 +414,87 @@ uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { } } -HRESULT CaptureControllerImpl::FindBaseMediaTypes() { - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { - return E_FAIL; - } - - ComPtr source; - HRESULT hr = capture_engine_->GetSource(&source); - - if (SUCCEEDED(hr)) { - ComPtr media_type; - uint32_t max_height = GetMaxPreviewHeight(); - - // Loop native media types. - for (int i = 0;; i++) { - // Release media type if exists from previous loop. - media_type = nullptr; - - if (FAILED(source->GetAvailableDeviceMediaType( - (DWORD) - MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, - i, media_type.GetAddressOf()))) { - break; - } +// Finds best mediat type for given source stream index and max height; +bool FindBestMediaType(DWORD source_stream_index, IMFCaptureSource* source, + IMFMediaType** target_media_type, uint32_t max_height, + uint32_t* target_frame_width, + uint32_t* target_frame_height) { + assert(source); + ComPtr media_type; + + uint32_t best_width = 0; + uint32_t best_height = 0; + + // Loop native media types. + for (int i = 0;; i++) { + if (FAILED(source->GetAvailableDeviceMediaType( + source_stream_index, i, media_type.GetAddressOf()))) { + break; + } - uint32_t frame_width; - uint32_t frame_height; - if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, - &frame_width, &frame_height))) { - // Update media type for photo and record capture. - if (capture_frame_width_ < frame_width || - capture_frame_height_ < frame_height) { - base_capture_media_type_ = media_type; - - capture_frame_width_ = frame_width; - capture_frame_height_ = frame_height; - } - - // Update media type for preview. - if (frame_height <= max_height && - (preview_frame_width_ < frame_width || - preview_frame_height_ < frame_height)) { - base_preview_media_type_ = media_type; - - preview_frame_width_ = frame_width; - preview_frame_height_ = frame_height; - } + uint32_t frame_width; + uint32_t frame_height; + if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, + &frame_width, &frame_height))) { + // Update target mediatype + if (frame_height <= max_height && + (best_width < frame_width || best_height < frame_height)) { + media_type.CopyTo(target_media_type); + best_width = frame_width; + best_height = frame_height; } } + } - if (base_preview_media_type_ && base_capture_media_type_) { - hr = S_OK; - } else { - hr = E_FAIL; - } + if (target_frame_width && target_frame_height) { + *target_frame_width = best_width; + *target_frame_height = best_height; } - return hr; + return *target_media_type != nullptr; } -HRESULT CaptureControllerImpl::InitPreviewSink() { +HRESULT CaptureControllerImpl::FindBaseMediaTypes() { if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return E_FAIL; } - HRESULT hr = S_OK; - if (preview_sink_) { - return hr; - } - - ComPtr preview_media_type; - ComPtr capture_sink; - - // Get sink with preview type. - hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, - &capture_sink); - - if (capture_sink && SUCCEEDED(hr)) { - hr = capture_sink.As(&preview_sink_); - } - - if (preview_sink_ && SUCCEEDED(hr)) { - hr = preview_sink_->RemoveAllStreams(); - } - - if (SUCCEEDED(hr) && !base_preview_media_type_) { - hr = FindBaseMediaTypes(); - } - - if (SUCCEEDED(hr)) { - hr = BuildMediaTypeForVideoPreview(base_preview_media_type_.Get(), - preview_media_type.GetAddressOf()); - } + ComPtr source; + HRESULT hr = capture_engine_->GetSource(&source); if (SUCCEEDED(hr)) { - DWORD preview_sink_stream_index; - hr = preview_sink_->AddStream( - (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, - preview_media_type.Get(), nullptr, &preview_sink_stream_index); - - if (SUCCEEDED(hr)) { - hr = preview_sink_->SetSampleCallback( - preview_sink_stream_index, capture_engine_callback_handler_.Get()); + // Find base media type for previewing. + uint32_t max_preview_height = GetMaxPreviewHeight(); + if (!FindBestMediaType( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, + source.Get(), base_preview_media_type_.GetAddressOf(), + max_preview_height, &preview_frame_width_, + &preview_frame_height_)) { + return E_FAIL; } - } - if (FAILED(hr)) { - preview_sink_ = nullptr; - } - - return hr; -} - -HRESULT CaptureControllerImpl::InitPhotoSink(const std::string& filepath) { - HRESULT hr = S_OK; - - if (photo_sink_) { - // If photo sink already exists, only update output filename. - hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); - - if (FAILED(hr)) { - photo_sink_ = nullptr; + // Find base media type for record and photo capture. + if (!FindBestMediaType( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, + source.Get(), base_capture_media_type_.GetAddressOf(), + (uint32_t)0xffffffff, nullptr, nullptr)) { + return E_FAIL; } - - return hr; - } - - ComPtr photo_media_type; - ComPtr capture_sink; - - // Gets sink with photo type. - hr = capture_engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, - &capture_sink); - - if (SUCCEEDED(hr)) { - hr = capture_sink.As(&photo_sink_); - } - - if (SUCCEEDED(hr) && !base_capture_media_type_) { - hr = FindBaseMediaTypes(); - } - - if (SUCCEEDED(hr)) { - hr = BuildMediaTypeForPhotoCapture(base_capture_media_type_.Get(), - photo_media_type.GetAddressOf(), - GUID_ContainerFormatJpeg); - } - - if (SUCCEEDED(hr)) { - // Removes existing streams if available. - hr = photo_sink_->RemoveAllStreams(); - } - - if (SUCCEEDED(hr)) { - DWORD dwSinkStreamIndex; - hr = photo_sink_->AddStream( - (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, - photo_media_type.Get(), nullptr, &dwSinkStreamIndex); - } - - if (SUCCEEDED(hr)) { - hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(filepath).c_str()); - } - - if (FAILED(hr)) { - photo_sink_ = nullptr; } return hr; } -// Starts recording. -// Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response process. -void CaptureControllerImpl::StartRecord(const std::string& filepath, +void CaptureControllerImpl::StartRecord(const std::string& file_path, int64_t max_video_duration_ms) { - assert(capture_controller_listener_); + assert(capture_engine_); + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { - return capture_controller_listener_->OnStartRecordFailed( + return OnRecordStarted( + false, "Camera not initialized. Camera should be disposed and reinitialized."); } - if (!record_handler_) { - record_handler_ = std::make_unique(record_audio_); - } else if (record_handler_->CanStart()) { - return capture_controller_listener_->OnStartRecordFailed( - "Recording cannot be started. Previous recording must be stopped " - "first."); - } - if (!base_capture_media_type_) { // Enumerates mediatypes and finds media type for video capture. if (FAILED(FindBaseMediaTypes())) { @@ -678,34 +502,43 @@ void CaptureControllerImpl::StartRecord(const std::string& filepath, } } - if (!record_handler_->StartRecord(filepath, max_video_duration_ms, + if (!record_handler_) { + record_handler_ = std::make_unique(record_audio_); + } else if (!record_handler_->CanStart()) { + return OnRecordStarted( + false, + "Recording cannot be started. Previous recording must be stopped " + "first."); + } + + // Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response process. + if (!record_handler_->StartRecord(file_path, max_video_duration_ms, capture_engine_.Get(), base_capture_media_type_.Get())) { + // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; - return capture_controller_listener_->OnStartRecordFailed( - "Failed to start video recording"); + return OnRecordStarted(false, "Failed to start video recording"); } } -// Stops recording. -// Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process. void CaptureControllerImpl::StopRecord() { assert(capture_controller_listener_); if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { - return capture_controller_listener_->OnStopRecordFailed( + return OnRecordStopped( + false, "Camera not initialized. Camera should be disposed and reinitialized."); } if (!record_handler_ && !record_handler_->CanStop()) { - return capture_controller_listener_->OnStopRecordFailed( - "Recording cannot be stopped."); + return OnRecordStopped(false, "Recording cannot be stopped."); } + // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process. if (!record_handler_->StopRecord(capture_engine_.Get())) { + // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; - return capture_controller_listener_->OnStartRecordFailed( - "Failed to stop video recording"); + return OnRecordStopped(false, "Failed to stop video recording"); } } @@ -718,16 +551,18 @@ void CaptureControllerImpl::StopTimedRecord() { } if (!record_handler_->StopRecord(capture_engine_.Get())) { + // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; return capture_controller_listener_->OnVideoRecordFailed( "Failed to record video"); } } -// Starts capturing preview frames using preview sink +// Starts capturing preview frames using preview handler // After first frame is captured, OnPreviewStarted is called void CaptureControllerImpl::StartPreview() { - assert(capture_controller_listener_); + assert(capture_engine_callback_handler_); + assert(capture_engine_); if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return OnPreviewStarted( @@ -735,22 +570,29 @@ void CaptureControllerImpl::StartPreview() { "Camera not initialized. Camera should be disposed and reinitialized."); } - if (previewing_) { - // Return success if preview already started - return OnPreviewStarted(true, ""); + if (!base_preview_media_type_) { + // Enumerates mediatypes and finds media type for video capture. + if (FAILED(FindBaseMediaTypes())) { + return OnPreviewStarted(false, "Failed to initialize video preview"); + } } - HRESULT hr = InitPreviewSink(); - - if (SUCCEEDED(hr)) { - preview_pending_ = true; - - // Requests to start preview. - hr = capture_engine_->StartPreview(); + if (!preview_handler_) { + preview_handler_ = std::make_unique(); + } else if (preview_handler_->IsInitialized()) { + return OnPreviewStarted(true, ""); + } else { + return OnPreviewStarted(false, "Preview already exists"); } - if (FAILED(hr)) { - return OnPreviewStarted(false, "Failed to start preview"); + // Check MF_CAPTURE_ENGINE_PREVIEW_STARTED event handling for response + // process. + if (!preview_handler_->StartPreview(capture_engine_.Get(), + base_capture_media_type_.Get(), + capture_engine_callback_handler_.Get())) { + // Destroy preview handler on error cases to make sure state is resetted. + preview_handler_ = nullptr; + return OnPreviewStarted(false, "Failed to start video preview"); } } @@ -759,33 +601,33 @@ void CaptureControllerImpl::StartPreview() { // pausing and resuming the preview. // Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event handling for response process. void CaptureControllerImpl::StopPreview() { - assert(capture_controller_listener_); + assert(capture_engine_); if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && - (!previewing_ && !preview_pending_)) { + !preview_handler_) { return; } // Requests to stop preview. - capture_engine_->StopPreview(); + preview_handler_->StopPreview(capture_engine_.Get()); } // Marks preview as paused. // When preview is paused, captured frames are not processed for preview // and flutter texture is not updated void CaptureControllerImpl::PausePreview() { - if (!previewing_) { - preview_paused_ = false; + assert(capture_controller_listener_); - if (capture_controller_listener_) { - return capture_controller_listener_->OnPausePreviewFailed( - "Preview not started"); - } + if (!preview_handler_ && !preview_handler_->IsInitialized()) { + return capture_controller_listener_->OnPausePreviewFailed( + "Preview not started"); } - preview_paused_ = true; - if (capture_controller_listener_) { + if (preview_handler_->PausePreview()) { capture_controller_listener_->OnPausePreviewSucceeded(); + } else { + capture_controller_listener_->OnPausePreviewFailed( + "Failed to pause preview"); } } @@ -793,9 +635,18 @@ void CaptureControllerImpl::PausePreview() { // When preview is not paused, captured frames are processed for preview // and flutter texture is updated. void CaptureControllerImpl::ResumePreview() { - preview_paused_ = false; - if (capture_controller_listener_) { + assert(capture_controller_listener_); + + if (!preview_handler_ && !preview_handler_->IsInitialized()) { + return capture_controller_listener_->OnResumePreviewFailed( + "Preview not started"); + } + + if (preview_handler_->ResumePreview()) { capture_controller_listener_->OnResumePreviewSucceeded(); + } else { + capture_controller_listener_->OnResumePreviewFailed( + "Failed to pause preview"); } } @@ -850,19 +701,23 @@ void CaptureControllerImpl::OnEvent(IMFMediaEvent* event) { // Handles Picture event and informs CaptureControllerListener. void CaptureControllerImpl::OnPicture(bool success, const std::string& error) { - if (capture_controller_listener_) { - if (success && !pending_picture_path_.empty()) { - capture_controller_listener_->OnTakePictureSucceeded( - pending_picture_path_); - } else { + if (success && photo_handler_) { + if (capture_controller_listener_) { + std::string path = photo_handler_->GetPhotoPath(); + capture_controller_listener_->OnTakePictureSucceeded(path); + } + photo_handler_->OnPhotoTaken(); + } else { + if (capture_controller_listener_) { capture_controller_listener_->OnTakePictureFailed(error); } + // Destroy photo handler on error cases to make sure state is resetted. + photo_handler_ = nullptr; } - pending_image_capture_ = false; - pending_picture_path_ = std::string(); } -// Handles CaptureEngineInitialized event and informs CaptureControllerListener. +// Handles CaptureEngineInitialized event and informs +// CaptureControllerListener. void CaptureControllerImpl::OnCaptureEngineInitialized( bool success, const std::string& error) { if (capture_controller_listener_) { @@ -901,8 +756,17 @@ void CaptureControllerImpl::OnCaptureEngineError(HRESULT hr, } // Handles PreviewStarted event and informs CaptureControllerListener. +// This should be called only after first frame has been received or +// in error cases. void CaptureControllerImpl::OnPreviewStarted(bool success, const std::string& error) { + if (preview_handler_ && success) { + preview_handler_->OnPreviewStarted(); + } else { + // Destroy preview handler on error cases to make sure state is resetted. + preview_handler_ = nullptr; + } + if (capture_controller_listener_) { if (success && preview_frame_width_ > 0 && preview_frame_height_ > 0) { capture_controller_listener_->OnStartPreviewSucceeded( @@ -911,31 +775,30 @@ void CaptureControllerImpl::OnPreviewStarted(bool success, capture_controller_listener_->OnStartPreviewFailed(error); } } - - // update state - preview_pending_ = false; - previewing_ = success; }; // Handles PreviewStopped event. void CaptureControllerImpl::OnPreviewStopped(bool success, const std::string& error) { - // update state - previewing_ = false; + // Preview handler is destroyed if preview is stopped as it + // does not have any use anymore. + preview_handler_ = nullptr; }; // Handles RecordStarted event and informs CaptureControllerListener. void CaptureControllerImpl::OnRecordStarted(bool success, const std::string& error) { - if (success) { + if (success && record_handler_) { + record_handler_->OnRecordStarted(); if (capture_controller_listener_) { capture_controller_listener_->OnStartRecordSucceeded(); } - record_handler_->OnRecordStarted(); } else { if (capture_controller_listener_) { capture_controller_listener_->OnStartRecordFailed(error); } + + // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; } }; @@ -944,11 +807,11 @@ void CaptureControllerImpl::OnRecordStarted(bool success, void CaptureControllerImpl::OnRecordStopped(bool success, const std::string& error) { if (capture_controller_listener_ && record_handler_) { - // Always calls stop record handler, + // Always calls OnStopRecord listener methods // to handle separate stop record request for timed records. - std::string path = record_handler_->GetRecordPath(); if (success) { + std::string path = record_handler_->GetRecordPath(); capture_controller_listener_->OnStopRecordSucceeded(path); if (record_handler_->IsTimedRecording()) { capture_controller_listener_->OnVideoRecordSucceeded( @@ -962,21 +825,24 @@ void CaptureControllerImpl::OnRecordStopped(bool success, } } - // update state - - record_handler_ = nullptr; + if (success && record_handler_) { + record_handler_->OnRecordStopped(); + } else { + // Destroy record handler on error cases to make sure state is resetted. + record_handler_ = nullptr; + } } // Returns pointer to databuffer. // Called via IMFCaptureEngineOnSampleCallback implementation. -// Implements CaptureEngineObserver::GetSourceBuffer. -uint8_t* CaptureControllerImpl::GetSourceBuffer(uint32_t current_length) { +// Implements CaptureEngineObserver::GetFrameBuffer. +uint8_t* CaptureControllerImpl::GetFrameBuffer(uint32_t new_length) { if (this->source_buffer_data_ == nullptr || - this->source_buffer_size_ != current_length) { + this->source_buffer_size_ != new_length) { // Update source buffer size. this->source_buffer_data_ = nullptr; - this->source_buffer_data_ = std::make_unique(current_length); - this->source_buffer_size_ = current_length; + this->source_buffer_data_ = std::make_unique(new_length); + this->source_buffer_size_ = new_length; } return this->source_buffer_data_.get(); } @@ -999,8 +865,9 @@ void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { return; } - if (preview_pending_) { - // Informs that first frame is captured succeffully and preview has started. + if (preview_handler_ && preview_handler_->IsStarting()) { + // Informs that first frame is captured succeffully and preview has + // started. OnPreviewStarted(true, ""); } diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index ae69c450f055..c89b900402d6 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -19,6 +19,8 @@ #include "capture_controller_listener.h" #include "capture_engine_listener.h" +#include "photo_handler.h" +#include "preview_handler.h" #include "record_handler.h" namespace camera_windows { @@ -82,23 +84,52 @@ class CaptureController { CaptureController(const CaptureController&) = delete; CaptureController& operator=(const CaptureController&) = delete; + // Initializes capture controller with given device id. + // Requests to initialize capture engine. + // + // texture_registrar: Pointer to Flutter TextureRegistrar instance. Used to + // register texture for capture preview. + // device_id: A string that holds information of camera device id to + // be captured. + // enable_audio: A boolean value telling if audio should be captured on + // video recording. + // resolution_preset: Maximum capture resolution height. virtual void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, const std::string& device_id, bool enable_audio, ResolutionPreset resolution_preset) = 0; + // Returns texture id for preview virtual int64_t GetTextureId() = 0; + + // Returns preview frame width virtual uint32_t GetPreviewWidth() = 0; + + // Returns preview frame height virtual uint32_t GetPreviewHeight() = 0; - // Actions + // Starts the preview. + // Initializes preview handler and requests to start preview. virtual void StartPreview() = 0; + + // Pauses the preview. virtual void PausePreview() = 0; + + // Resumes the preview. virtual void ResumePreview() = 0; - virtual void StartRecord(const std::string& filepath, + + // Starts the record. + // Initializes record handler and requests to start recording. + virtual void StartRecord(const std::string& file_path, int64_t max_video_duration_ms) = 0; + + // Stops the on going recording. + // Uses existing record handler and requests to stop recording. virtual void StopRecord() = 0; - virtual void TakePicture(const std::string filepath) = 0; + + // Captures photo. + // Initializes photo handler and requests to take photo. + virtual void TakePicture(const std::string file_path) = 0; }; class CaptureControllerImpl : public CaptureController, @@ -114,136 +145,134 @@ class CaptureControllerImpl : public CaptureController, CaptureControllerImpl(const CaptureControllerImpl&) = delete; CaptureControllerImpl& operator=(const CaptureControllerImpl&) = delete; - bool IsInitialized() { - return capture_engine_state_ == - CaptureEngineState::CAPTURE_ENGINE_INITIALIZED; - } - bool IsPreviewing() { return previewing_; } - + // From CaptureController void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, const std::string& device_id, bool enable_audio, ResolutionPreset resolution_preset) override; int64_t GetTextureId() override { return texture_id_; } uint32_t GetPreviewWidth() override { return preview_frame_width_; } uint32_t GetPreviewHeight() override { return preview_frame_height_; } - void StartPreview() override; void PausePreview() override; void ResumePreview() override; - void StartRecord(const std::string& filepath, + void StartRecord(const std::string& file_path, int64_t max_video_duration_ms) override; void StopRecord() override; - void TakePicture(const std::string filepath) override; + void TakePicture(const std::string file_path) override; - // Handlers for CaptureEngineListener events. // From CaptureEngineObserver. bool IsReadyForSample() override { return capture_engine_state_ == CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && - previewing_ && !preview_paused_; + preview_handler_ && preview_handler_->IsRunning(); } - void OnEvent(IMFMediaEvent* event) override; - - uint8_t* GetSourceBuffer(uint32_t current_length) override; + uint8_t* GetFrameBuffer(uint32_t current_length) override; void OnBufferUpdated() override; void UpdateCaptureTime(uint64_t capture_time) override; - // Sets capture engine, for mocking purposes. + // Sets capture engine, for testing purposes. void SetCaptureEngine(IMFCaptureEngine* capture_engine) { capture_engine_ = capture_engine; }; - // Sets video source, for mocking purposes. + // Sets video source, for testing purposes. void SetVideoSource(IMFMediaSource* video_source) { video_source_ = video_source; }; - // Sets audio source, for mocking purposes. + // Sets audio source, for testing purposes. void SetAudioSource(IMFMediaSource* audio_source) { audio_source_ = audio_source; }; private: - // states - CaptureEngineState capture_engine_state_ = - CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; - std::unique_ptr record_handler_ = nullptr; - - CaptureControllerListener* capture_controller_listener_ = nullptr; bool media_foundation_started_ = false; bool record_audio_ = false; + uint32_t preview_frame_width_ = 0; + uint32_t preview_frame_height_ = 0; + std::unique_ptr record_handler_ = nullptr; + std::unique_ptr preview_handler_ = nullptr; + std::unique_ptr photo_handler_ = nullptr; + CaptureControllerListener* capture_controller_listener_ = nullptr; std::string video_device_id_; - + CaptureEngineState capture_engine_state_ = + CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; ResolutionPreset resolution_preset_ = ResolutionPreset::RESOLUTION_PRESET_MEDIUM; - - // CaptureEngine objects ComPtr capture_engine_; ComPtr capture_engine_callback_handler_; - ComPtr dxgi_device_manager_; ComPtr dx11_device_; - UINT dx_device_reset_token_ = 0; - - // Sources + ComPtr base_capture_media_type_; + ComPtr base_preview_media_type_; ComPtr video_source_; ComPtr audio_source_; + UINT dx_device_reset_token_ = 0; // Texture int64_t texture_id_ = -1; flutter::TextureRegistrar* texture_registrar_ = nullptr; std::unique_ptr texture_; - FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; uint32_t source_buffer_size_ = 0; std::unique_ptr source_buffer_data_ = nullptr; std::unique_ptr dest_buffer_ = nullptr; uint32_t bytes_per_pixel_ = 4; - // Preview - bool preview_paused_ = false; - bool preview_pending_ = false; - bool previewing_ = false; - uint32_t preview_frame_width_ = 0; - uint32_t preview_frame_height_ = 0; - ComPtr base_preview_media_type_; - ComPtr preview_sink_; - - // Photo / Record - bool pending_image_capture_ = false; - - uint32_t capture_frame_width_ = 0; - uint32_t capture_frame_height_ = 0; - ComPtr base_capture_media_type_; - ComPtr photo_sink_; - ComPtr record_sink_; - std::string pending_picture_path_ = ""; - + // Resets capture controller state. + // This is called if capture engine creation fails or is disposed. void ResetCaptureController(); + + // Returns max preview height calculated from resolution present. uint32_t GetMaxPreviewHeight(); + + // Uses first audio source to capture audio. + // Note: Enumerating audio sources via platform interface is not supported. HRESULT CreateDefaultAudioCaptureSource(); + + // Initializes video capture source from camera device. HRESULT CreateVideoCaptureSourceForDevice(const std::string& video_device_id); + + // Creates DX11 Device and D3D Manager. HRESULT CreateD3DManagerWithDX11Device(); + // Initializes capture engine object. HRESULT CreateCaptureEngine(); + // Enumerates video_sources media types and finds out best resolution + // for preview and video capture. HRESULT FindBaseMediaTypes(); - HRESULT InitPreviewSink(); - HRESULT InitPhotoSink(const std::string& filepath); - HRESULT InitRecordSink(const std::string& filepath); + // Stops timed video record. Called internally when record handler when max + // recording time is exceeded. void StopTimedRecord(); + + // Stops preview. Called internally on camera reset and dispose. void StopPreview(); + // Handles capture engine initalization event. void OnCaptureEngineInitialized(bool success, const std::string& error); + + // Handles capture engine errors. void OnCaptureEngineError(HRESULT hr, const std::string& error); + + // Handles picture events. void OnPicture(bool success, const std::string& error); + + // Handles preview started events. void OnPreviewStarted(bool success, const std::string& error); + + // Handles preview stopped events. void OnPreviewStopped(bool success, const std::string& error); + + // Handles record started events. void OnRecordStarted(bool success, const std::string& error); + + // Handles record stopped events. void OnRecordStopped(bool success, const std::string& error); + // Converts local pixel buffer to flutter pixel buffer const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, size_t height); }; diff --git a/packages/camera/camera_windows/windows/capture_controller_listener.h b/packages/camera/camera_windows/windows/capture_controller_listener.h index 355ae3693a47..04595f5dfb20 100644 --- a/packages/camera/camera_windows/windows/capture_controller_listener.h +++ b/packages/camera/camera_windows/windows/capture_controller_listener.h @@ -13,23 +13,87 @@ class CaptureControllerListener { public: virtual ~CaptureControllerListener() = default; + // Called by CaptureController on successful capture engine initialization. + // + // texture_id: A 64bit integer id registered by TextureRegistrar virtual void OnCreateCaptureEngineSucceeded(int64_t texture_id) = 0; + + // Called by CaptureController if initializing the capture engine fails. + // + // error: A string describing the error. virtual void OnCreateCaptureEngineFailed(const std::string& error) = 0; + + // Called by CaptureController on successfully started preview. + // + // width: Preview frame width. + // height: Preview frame height. virtual void OnStartPreviewSucceeded(int32_t width, int32_t height) = 0; + + // Called by CaptureController if starting the preview fails. + // + // error: A string describing the error. virtual void OnStartPreviewFailed(const std::string& error) = 0; - virtual void OnResumePreviewSucceeded() = 0; - virtual void OnResumePreviewFailed(const std::string& error) = 0; + + // Called by CaptureController on successfully paused preview. virtual void OnPausePreviewSucceeded() = 0; + + // Called by CaptureController if pausing the preview fails. + // + // error: A string describing the error. virtual void OnPausePreviewFailed(const std::string& error) = 0; + + // Called by CaptureController on successfully resumed preview. + virtual void OnResumePreviewSucceeded() = 0; + + // Called by CaptureController if resuming the preview fails. + // + // error: A string describing the error. + virtual void OnResumePreviewFailed(const std::string& error) = 0; + + // Called by CaptureController on successfully started recording. virtual void OnStartRecordSucceeded() = 0; + + // Called by CaptureController if starting the recording fails. + // + // error: A string describing the error. virtual void OnStartRecordFailed(const std::string& error) = 0; - virtual void OnStopRecordSucceeded(const std::string& filepath) = 0; + + // Called by CaptureController on successfully stopped recording. + // + // file_path: File path to the recorded video file. + virtual void OnStopRecordSucceeded(const std::string& file_path) = 0; + + // Called by CaptureController if stopping the recording fails. + // + // error: A string describing the error. virtual void OnStopRecordFailed(const std::string& error) = 0; - virtual void OnTakePictureSucceeded(const std::string& filepath) = 0; + + // Called by CaptureController on successfully captured picture. + // + // file_path: File path to the captured image. + virtual void OnTakePictureSucceeded(const std::string& file_path) = 0; + + // Called by CaptureController if taking picture fails. + // + // error: A string describing the error. virtual void OnTakePictureFailed(const std::string& error) = 0; - virtual void OnVideoRecordSucceeded(const std::string& filepath, - int64_t video_duration) = 0; + + // Called by CaptureController when timed recording is successfully recorded. + // + // file_path: File path to the captured image. + // video_duration: Duration of recorded video in milliseconds. + virtual void OnVideoRecordSucceeded(const std::string& file_path, + int64_t video_duration_ms) = 0; + + // Called by CaptureController if timed recording fails. + // + // error: A string describing the error. virtual void OnVideoRecordFailed(const std::string& error) = 0; + + // Called by CaptureController if capture engine returns error. + // For example when camera is disconnected while on use. + // + // error: A string describing the error. virtual void OnCaptureError(const std::string& error) = 0; }; diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp index 6b58b5413871..f3c63a3d2aab 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.cpp +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -79,7 +79,7 @@ HRESULT CaptureEngineListener::OnSample(IMFSample* sample) { DWORD current_length = 0; uint8_t* data; if (SUCCEEDED(buffer->Lock(&data, &max_length, ¤t_length))) { - uint8_t* src_buffer = this->observer_->GetSourceBuffer(current_length); + uint8_t* src_buffer = this->observer_->GetFrameBuffer(current_length); if (src_buffer) { std::copy(data, data + current_length, src_buffer); } diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 0f8aef012157..aec6b5bdcf1f 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -15,14 +15,21 @@ class CaptureEngineObserver { public: virtual ~CaptureEngineObserver() = default; + // Returns true if sample can be processed. virtual bool IsReadyForSample() = 0; - // Event functions + // Handles Capture Engine media events. virtual void OnEvent(IMFMediaEvent* event) = 0; - // Sample functions - virtual uint8_t* GetSourceBuffer(uint32_t current_length) = 0; + // Returns frame buffer target for new capture sample. + virtual uint8_t* GetFrameBuffer(uint32_t new_length) = 0; + + // Handles buffer update events. + // Informs texture registrar of the new frame. virtual void OnBufferUpdated() = 0; + + // Handles capture timestamps updates. + // Used to stop timed recordings when recorded time is exceeded. virtual void UpdateCaptureTime(uint64_t capture_time) = 0; }; diff --git a/packages/camera/camera_windows/windows/photo_handler.cpp b/packages/camera/camera_windows/windows/photo_handler.cpp new file mode 100644 index 000000000000..79cf02c9f9fe --- /dev/null +++ b/packages/camera/camera_windows/windows/photo_handler.cpp @@ -0,0 +1,146 @@ +// 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 "photo_handler.h" + +#include +#include +#include + +#include + +#include "capture_engine_listener.h" +#include "string_utils.h" + +namespace camera_windows { + +using Microsoft::WRL::ComPtr; + +// Initializes media type for photo capture for jpeg images. +HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType* src_media_type, + IMFMediaType** photo_media_type, + GUID image_format) { + assert(src_media_type); + ComPtr new_media_type; + + HRESULT hr = MFCreateMediaType(&new_media_type); + if (FAILED(hr)) { + return hr; + } + + // Clones everything from original media type. + hr = src_media_type->CopyAllItems(new_media_type.Get()); + if (FAILED(hr)) { + return hr; + } + + hr = new_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Image); + if (FAILED(hr)) { + return hr; + } + + hr = new_media_type->SetGUID(MF_MT_SUBTYPE, image_format); + if (FAILED(hr)) { + return hr; + } + + new_media_type.CopyTo(photo_media_type); + return hr; +} + +HRESULT PhotoHandler::InitPhotoSink(IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type) { + assert(capture_engine); + assert(base_media_type); + + HRESULT hr = S_OK; + + if (photo_sink_) { + // If photo sink already exists, only update output filename. + hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(file_path_).c_str()); + + if (FAILED(hr)) { + photo_sink_ = nullptr; + } + + return hr; + } + + ComPtr photo_media_type; + ComPtr capture_sink; + + // Get sink with photo type. + hr = + capture_engine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, &capture_sink); + if (FAILED(hr)) { + return hr; + } + + hr = capture_sink.As(&photo_sink_); + if (FAILED(hr)) { + photo_sink_ = nullptr; + return hr; + } + + hr = photo_sink_->RemoveAllStreams(); + if (FAILED(hr)) { + photo_sink_ = nullptr; + return hr; + } + + hr = BuildMediaTypeForPhotoCapture(base_media_type, + photo_media_type.GetAddressOf(), + GUID_ContainerFormatJpeg); + + if (FAILED(hr)) { + photo_sink_ = nullptr; + return hr; + } + + hr = photo_sink_->RemoveAllStreams(); + if (FAILED(hr)) { + photo_sink_ = nullptr; + return hr; + } + + DWORD photo_sink_stream_index; + hr = photo_sink_->AddStream( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, + photo_media_type.Get(), nullptr, &photo_sink_stream_index); + if (FAILED(hr)) { + return hr; + } + + hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(file_path_).c_str()); + if (FAILED(hr)) { + photo_sink_ = nullptr; + return hr; + } + + return hr; +} + +bool PhotoHandler::TakePhoto(const std::string& file_path, + IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type) { + assert(!file_path.empty()); + assert(capture_engine); + assert(base_media_type); + + file_path_ = file_path; + + if (FAILED(InitPhotoSink(capture_engine, base_media_type))) { + return false; + } + + photo_state_ = PhotoState::PHOTO_STATE__TAKING; + return SUCCEEDED(capture_engine->TakePhoto()); +} + +void PhotoHandler::OnPhotoTaken() { + assert(photo_state_ == PhotoState::PHOTO_STATE__TAKING); + photo_state_ = PhotoState::PHOTO_STATE__IDLE; +} + +} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h new file mode 100644 index 000000000000..a47b1e8cb6ce --- /dev/null +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -0,0 +1,74 @@ +// 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_PHOTO_HANDLER_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PHOTO_HANDLER_H_ + +#include +#include +#include + +#include +#include + +#include "capture_engine_listener.h" + +namespace camera_windows { +using Microsoft::WRL::ComPtr; + +enum PhotoState { + PHOTO_STATE__NOT_STARTED, + PHOTO_STATE__IDLE, + PHOTO_STATE__TAKING, +}; + +class PhotoHandler { + public: + PhotoHandler(){}; + virtual ~PhotoHandler() = default; + + // Prevent copying. + PhotoHandler(PhotoHandler const&) = delete; + PhotoHandler& operator=(PhotoHandler const&) = delete; + + // Initializes photo sink if not initialized and requests capture engine to + // start photoing. Sets photo state to PHOTO_STATE__TAKING. + // Returns false if photo cannot be taken. + // + // capture_engine: A pointer to capture engine instance. + // Called to take the photo. + // base_media_type: A pointer to base media type used as a base + // for the actual photo capture media type. + // file_path: A string that hold file path for photo capture. + bool TakePhoto(const std::string& file_path, IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type); + + // Set the photo handler recording state to PHOTO_STATE__IDLE. + void OnPhotoTaken(); + + // Returns true if photo state is PHOTO_STATE__IDLE + bool IsInitialized() { + return photo_state_ == PhotoState::PHOTO_STATE__IDLE; + }; + + // Returns true if photo state is PHOTO_STATE__TAKING. + bool IsTakingPhoto() { + return photo_state_ == PhotoState::PHOTO_STATE__TAKING; + }; + + // Returns path to photo capture. + std::string GetPhotoPath() { return file_path_; }; + + private: + // Initializes record sink for video file capture. + HRESULT InitPhotoSink(IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type); + + std::string file_path_ = ""; + PhotoState photo_state_ = PhotoState::PHOTO_STATE__NOT_STARTED; + ComPtr photo_sink_; +}; +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PHOTO_HANDLER_H_ \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/preview_handler.cpp b/packages/camera/camera_windows/windows/preview_handler.cpp new file mode 100644 index 000000000000..2f1ee73e8376 --- /dev/null +++ b/packages/camera/camera_windows/windows/preview_handler.cpp @@ -0,0 +1,163 @@ +// 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 "preview_handler.h" + +#include +#include + +#include + +#include "capture_engine_listener.h" +#include "string_utils.h" + +namespace camera_windows { + +using Microsoft::WRL::ComPtr; + +// Initializes media type for video preview. +HRESULT BuildMediaTypeForVideoPreview(IMFMediaType* src_media_type, + IMFMediaType** preview_media_type) { + assert(src_media_type); + ComPtr new_media_type; + + HRESULT hr = MFCreateMediaType(&new_media_type); + if (FAILED(hr)) { + return hr; + } + + // Clones everything from original media type. + hr = src_media_type->CopyAllItems(new_media_type.Get()); + if (FAILED(hr)) { + return hr; + } + + // Changes subtype to MFVideoFormat_RGB32. + hr = new_media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); + if (FAILED(hr)) { + return hr; + } + + hr = new_media_type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + if (FAILED(hr)) { + return hr; + } + + new_media_type.CopyTo(preview_media_type); + + return hr; +} + +HRESULT PreviewHandler::InitPreviewSink( + IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type, + CaptureEngineListener* sample_callback) { + assert(capture_engine); + assert(base_media_type); + assert(sample_callback); + + HRESULT hr = S_OK; + + if (preview_sink_) { + // Preview sink already initialized. + return hr; + } + + ComPtr preview_media_type; + ComPtr capture_sink; + + // Get sink with preview type. + hr = capture_engine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, + &capture_sink); + if (FAILED(hr)) { + return hr; + } + + hr = capture_sink.As(&preview_sink_); + if (FAILED(hr)) { + preview_sink_ = nullptr; + return hr; + } + + hr = preview_sink_->RemoveAllStreams(); + if (FAILED(hr)) { + preview_sink_ = nullptr; + return hr; + } + + hr = BuildMediaTypeForVideoPreview(base_media_type, + preview_media_type.GetAddressOf()); + + if (FAILED(hr)) { + preview_sink_ = nullptr; + return hr; + } + + DWORD preview_sink_stream_index; + hr = preview_sink_->AddStream( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, + preview_media_type.Get(), nullptr, &preview_sink_stream_index); + + if (FAILED(hr)) { + return hr; + } + + hr = preview_sink_->SetSampleCallback(preview_sink_stream_index, + sample_callback); + + if (FAILED(hr)) { + preview_sink_ = nullptr; + return hr; + } + + return hr; +} + +bool PreviewHandler::StartPreview(IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type, + CaptureEngineListener* sample_callback) { + assert(capture_engine); + assert(base_media_type); + + if (FAILED( + InitPreviewSink(capture_engine, base_media_type, sample_callback))) { + return false; + } + + preview_state_ = PreviewState::PREVIEW_STATE__STARTING; + return SUCCEEDED(capture_engine->StartPreview()); +} + +bool PreviewHandler::StopPreview(IMFCaptureEngine* capture_engine) { + if (preview_state_ == PreviewState::PREVIEW_STATE__RUNNING || + preview_state_ == PreviewState::PREVIEW_STATE__PAUSED) { + preview_state_ = PreviewState::PREVIEW_STATE__STOPPING; + return SUCCEEDED(capture_engine->StopPreview()); + } + return false; +} + +bool PreviewHandler::PausePreview() { + if (preview_state_ != PreviewState::PREVIEW_STATE__RUNNING) { + return false; + } + preview_state_ = PreviewState::PREVIEW_STATE__PAUSED; + return true; +} + +bool PreviewHandler::ResumePreview() { + if (preview_state_ != PreviewState::PREVIEW_STATE__PAUSED) { + return false; + } + preview_state_ = PreviewState::PREVIEW_STATE__RUNNING; + return true; +} + +void PreviewHandler::OnPreviewStarted() { + assert(preview_state_ == PreviewState::PREVIEW_STATE__STARTING); + if (preview_state_ == PreviewState::PREVIEW_STATE__STARTING) { + preview_state_ = PreviewState::PREVIEW_STATE__RUNNING; + } +} + +} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h new file mode 100644 index 000000000000..66e692ae27b2 --- /dev/null +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -0,0 +1,100 @@ +// 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_PREVIEW_HANDLER_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PREVIEW_HANDLER_H_ + +#include +#include +#include + +#include +#include + +#include "capture_engine_listener.h" + +namespace camera_windows { +using Microsoft::WRL::ComPtr; + +enum PreviewState { + PREVIEW_STATE__NOT_STARTED, + PREVIEW_STATE__STARTING, + PREVIEW_STATE__RUNNING, + PREVIEW_STATE__PAUSED, + PREVIEW_STATE__STOPPING +}; + +class PreviewHandler { + public: + PreviewHandler(){}; + virtual ~PreviewHandler() = default; + + // Prevent copying. + PreviewHandler(PreviewHandler const&) = delete; + PreviewHandler& operator=(PreviewHandler const&) = delete; + + // Initializes preview sink and requests capture engine to start previewing. + // Sets preview state to PREVIEW_STATE__STARTING. + // Returns false if recording cannot be started. + // + // capture_engine: A pointer to capture engine instance. Used to start + // the actual recording. + // base_media_type: A pointer to base media type used as a base + // for the actual video capture media type. + // sample_callback: A pointer to capture engine listener. + // This is set as sample callback for preview sink. + bool StartPreview(IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type, + CaptureEngineListener* sample_callback); + + // Stops existing recording. + // Returns false if recording cannot be stopped. + // + // capture_engine: A pointer to capture engine instance. Used to stop + // the ongoing recording. + bool StopPreview(IMFCaptureEngine* capture_engine); + + // Set the preview handler recording state to PREVIEW_STATE__PAUSED. + bool PausePreview(); + + // Set the preview handler recording state to PREVIEW_STATE__RUNNING. + bool ResumePreview(); + + // Set the preview handler recording state to PREVIEW_STATE__RUNNING. + void OnPreviewStarted(); + + // Returns true if preview state is PREVIEW_STATE__RUNNING or + // PREVIEW_STATE__PAUSED. + bool IsInitialized() { + return preview_state_ == PreviewState::PREVIEW_STATE__RUNNING && + preview_state_ == PreviewState::PREVIEW_STATE__PAUSED; + }; + + // Returns true if preview state is PREVIEW_STATE__RUNNING. + bool IsRunning() { + return preview_state_ == PreviewState::PREVIEW_STATE__RUNNING; + }; + + // Return true if preview state is PREVIEW_STATE__PAUSED + bool IsPaused() { + return preview_state_ == PreviewState::PREVIEW_STATE__PAUSED; + }; + + // Returns true if preview state is PREVIEW_STATE__STARTING. + bool IsStarting() { + return preview_state_ == PreviewState::PREVIEW_STATE__STARTING; + }; + + private: + // Initializes record sink for video file capture. + HRESULT InitPreviewSink(IMFCaptureEngine* capture_engine, + IMFMediaType* base_media_type, + CaptureEngineListener* sample_callback); + + PreviewState preview_state_ = PreviewState::PREVIEW_STATE__NOT_STARTED; + ComPtr preview_sink_; +}; +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PREVIEW_HANDLER_H_ \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/record_handler.cpp b/packages/camera/camera_windows/windows/record_handler.cpp index 0bc51377a7da..88b9bd2169d7 100644 --- a/packages/camera/camera_windows/windows/record_handler.cpp +++ b/packages/camera/camera_windows/windows/record_handler.cpp @@ -116,6 +116,10 @@ HRESULT BuildMediaTypeForAudioCapture(IMFMediaType** audio_record_media_type) { HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type) { + assert(!file_path_.empty()); + assert(capture_engine); + assert(base_media_type); + HRESULT hr = S_OK; if (record_sink_) { // If record sink already exists, only update output filename. @@ -204,15 +208,15 @@ bool RecordHandler::StartRecord(const std::string& file_path, return false; } - recording_state_ = RecordingState::STARTING; + recording_state_ = RecordState::RECORD_STATE__STARTING; capture_engine->StartRecord(); return true; } bool RecordHandler::StopRecord(IMFCaptureEngine* capture_engine) { - if (recording_state_ == RecordingState::RECORDING) { - recording_state_ = RecordingState::STOPPING; + if (recording_state_ == RecordState::RECORD_STATE__RUNNING) { + recording_state_ = RecordState::RECORD_STATE__STOPPING; HRESULT hr = capture_engine->StopRecord(true, false); return SUCCEEDED(hr); } @@ -220,18 +224,18 @@ bool RecordHandler::StopRecord(IMFCaptureEngine* capture_engine) { } void RecordHandler::OnRecordStarted() { - if (recording_state_ == RecordingState::STARTING) { - recording_state_ = RecordingState::RECORDING; + if (recording_state_ == RecordState::RECORD_STATE__STARTING) { + recording_state_ = RecordState::RECORD_STATE__RUNNING; } } void RecordHandler::OnRecordStopped() { - if (recording_state_ == RecordingState::STOPPING) { + if (recording_state_ == RecordState::RECORD_STATE__STOPPING) { file_path_ = ""; recording_start_timestamp_us_ = -1; recording_duration_us_ = 0; max_video_duration_ms_ = -1; - recording_state_ = RecordingState::NOT_RECORDING; + recording_state_ = RecordState::RECORD_STATE__NOT_STARTED; } } @@ -245,7 +249,7 @@ void RecordHandler::UpdateRecordingTime(uint64_t timestamp) { bool RecordHandler::ShouldStopTimedRecording() { return type_ == RecordingType::TIMED && - recording_state_ == RecordingState::RECORDING && + recording_state_ == RecordState::RECORD_STATE__RUNNING && max_video_duration_ms_ > 0 && recording_duration_us_ >= (static_cast(max_video_duration_ms_) * 1000); diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index 436f9ee72f10..37b24fe8ae9f 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -2,8 +2,8 @@ // 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_RECORDING_H_ -#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORDING_H_ +#ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORD_HANDLER_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORD_HANDLER_H_ #include #include @@ -17,11 +17,11 @@ using Microsoft::WRL::ComPtr; enum RecordingType { CONTINUOUS, TIMED }; -enum RecordingState { - NOT_RECORDING, - STARTING, - RECORDING, - STOPPING, +enum RecordState { + RECORD_STATE__NOT_STARTED, + RECORD_STATE__STARTING, + RECORD_STATE__RUNNING, + RECORD_STATE__STOPPING }; class RecordHandler { @@ -33,21 +33,34 @@ class RecordHandler { RecordHandler(RecordHandler const&) = delete; RecordHandler& operator=(RecordHandler const&) = delete; - // Initializes record sink and asks capture engine to start recording. - // Sets record state to STARTING. + // Initializes record sink and requests capture engine to start recording. + // Sets record state to RECORD_STATE__STARTING. // Returns false if recording cannot be started. - bool StartRecord(const std::string& filepath, int64_t max_duration, + // + // file_path: A string that hold file path for video capture. + // max_duration: A int64 value of maximun recording duration. + // If value is -1 video recording is considered as + // a continuous recording. + // capture_engine: A pointer to capture engine instance. Used to start + // the actual recording. + // base_media_type: A pointer to base media type used as a base + // for the actual video capture media type. + bool StartRecord(const std::string& file_path, int64_t max_duration, IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type); // Stops existing recording. // Returns false if recording cannot be stopped. + // + // capture_engine: A pointer to capture engine instance. Used to stop + // the ongoing recording. bool StopRecord(IMFCaptureEngine* capture_engine); - // Set the record handler recording state to RECORDING. + // Set the record handler recording state to RECORD_STATE__RUNNING. void OnRecordStarted(); - // Resets the record handler state and sets recording state to NOT_RECORDING. + // Resets the record handler state and sets recording state to + // RECORD_STATE__NOT_STARTED. void OnRecordStopped(); // Returns true if recording type is continuous recording. @@ -57,10 +70,14 @@ class RecordHandler { bool IsTimedRecording() { return type_ == RecordingType::TIMED; }; // Returns true if new recording can be started. - bool CanStart() { return recording_state_ == NOT_RECORDING; }; + bool CanStart() { + return recording_state_ == RecordState::RECORD_STATE__NOT_STARTED; + }; // Returns true if recording can be stopped. - bool CanStop() { return recording_state_ == RECORDING; }; + bool CanStop() { + return recording_state_ == RecordState::RECORD_STATE__RUNNING; + }; // Returns path to video recording. std::string GetRecordPath() { return file_path_; }; @@ -85,10 +102,10 @@ class RecordHandler { int64_t recording_start_timestamp_us_ = -1; uint64_t recording_duration_us_ = 0; std::string file_path_ = ""; - RecordingState recording_state_ = RecordingState::NOT_RECORDING; + RecordState recording_state_ = RecordState::RECORD_STATE__NOT_STARTED; RecordingType type_; ComPtr record_sink_; }; } // namespace camera_windows -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORDING_H_ \ No newline at end of file +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORD_HANDLER_H_ \ No newline at end of file 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 96c720174e55..65b39ee217a7 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -400,7 +400,7 @@ TEST(CameraPlugin, TakePictureHandlerCallsTakePictureWithPath) { EXPECT_CALL(*capture_controller, TakePicture(EndsWith(".jpeg"))) .Times(1) - .WillOnce([cam = camera.get()](const std::string filepath) { + .WillOnce([cam = camera.get()](const std::string file_path) { assert(cam->pending_result_); return cam->pending_result_->Success(); }); @@ -514,7 +514,7 @@ TEST(CameraPlugin, StartVideoRecordingHandlerCallsStartRecordWithPath) { EXPECT_CALL(*capture_controller, StartRecord(EndsWith(".mp4"), -1)) .Times(1) - .WillOnce([cam = camera.get()](const std::string filepath, + .WillOnce([cam = camera.get()](const std::string file_path, int64_t max_video_duration_ms) { assert(cam->pending_result_); return cam->pending_result_->Success(); @@ -586,7 +586,7 @@ TEST(CameraPlugin, EXPECT_CALL(*capture_controller, StartRecord(EndsWith(".mp4"), Eq(mock_video_duration))) .Times(1) - .WillOnce([cam = camera.get()](const std::string filepath, + .WillOnce([cam = camera.get()](const std::string file_path, int64_t max_video_duration_ms) { assert(cam->pending_result_); return cam->pending_result_->Success(); diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp index 2276169f4a5f..d45fed1b3064 100644 --- a/packages/camera/camera_windows/windows/test/camera_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -242,14 +242,14 @@ TEST(Camera, OnStopRecordSucceededReturnsSuccess) { std::unique_ptr result = std::make_unique(); - std::string filepath = "C:\temp\filename.mp4"; + std::string file_path = "C:\temp\filename.mp4"; EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(filepath)))); + EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(file_path)))); camera->AddPendingResult(PendingResultType::STOP_RECORD, std::move(result)); - camera->OnStopRecordSucceeded(filepath); + camera->OnStopRecordSucceeded(file_path); } TEST(Camera, OnStopRecordFailedReturnsError) { @@ -274,14 +274,14 @@ TEST(Camera, OnTakePictureSucceededReturnsSuccess) { std::unique_ptr result = std::make_unique(); - std::string filepath = "C:\temp\filename.jpeg"; + std::string file_path = "C:\temp\filename.jpeg"; EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(filepath)))); + EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(file_path)))); camera->AddPendingResult(PendingResultType::TAKE_PICTURE, std::move(result)); - camera->OnTakePictureSucceeded(filepath); + camera->OnTakePictureSucceeded(file_path); } TEST(Camera, OnTakePictureFailedReturnsError) { @@ -309,7 +309,7 @@ TEST(Camera, OnVideoRecordSucceededInvokesCameraChannelEvent) { std::unique_ptr binary_messenger = std::make_unique(); - std::string filepath = "C:\temp\filename.mp4"; + std::string file_path = "C:\temp\filename.mp4"; int64_t camera_id = 12345; std::string camera_channel = std::string("flutter.io/cameraPlugin/camera") + std::to_string(camera_id); @@ -332,7 +332,7 @@ TEST(Camera, OnVideoRecordSucceededInvokesCameraChannelEvent) { // Pass camera id for camera camera->OnCreateCaptureEngineSucceeded(camera_id); - camera->OnVideoRecordSucceeded(filepath, video_duration); + camera->OnVideoRecordSucceeded(file_path, video_duration); } } // namespace test diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index a135019ac655..e50d3d188764 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -154,17 +154,17 @@ class MockCamera : public Camera { MOCK_METHOD(void, OnStartRecordFailed, (const std::string& error), (override)); - MOCK_METHOD(void, OnStopRecordSucceeded, (const std::string& filepath), + MOCK_METHOD(void, OnStopRecordSucceeded, (const std::string& file_path), (override)); MOCK_METHOD(void, OnStopRecordFailed, (const std::string& error), (override)); - MOCK_METHOD(void, OnTakePictureSucceeded, (const std::string& filepath), + MOCK_METHOD(void, OnTakePictureSucceeded, (const std::string& file_path), (override)); MOCK_METHOD(void, OnTakePictureFailed, (const std::string& error), (override)); MOCK_METHOD(void, OnVideoRecordSucceeded, - (const std::string& filepath, int64_t video_duration), + (const std::string& file_path, int64_t video_duration), (override)); MOCK_METHOD(void, OnVideoRecordFailed, (const std::string& error), (override)); @@ -227,10 +227,10 @@ class MockCaptureController : public CaptureController { MOCK_METHOD(void, ResumePreview, (), (override)); MOCK_METHOD(void, PausePreview, (), (override)); MOCK_METHOD(void, StartRecord, - (const std::string& filepath, int64_t max_video_duration_ms), + (const std::string& file_path, int64_t max_video_duration_ms), (override)); MOCK_METHOD(void, StopRecord, (), (override)); - MOCK_METHOD(void, TakePicture, (const std::string filepath), (override)); + MOCK_METHOD(void, TakePicture, (const std::string file_path), (override)); }; // MockCameraPlugin extends CameraPlugin behaviour a bit to allow adding cameras From 2e23161e2052b3b30ea9c37d6d40bbe2c9020af2 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 18:12:27 +0200 Subject: [PATCH 42/93] [camera_windows] fix unit tests --- .../camera/camera_windows/windows/camera.cpp | 8 ++++---- .../camera/camera_windows/windows/camera.h | 6 +++--- .../camera_windows/windows/camera_plugin.cpp | 6 +++--- .../windows/capture_controller.cpp | 6 +++--- .../windows/capture_controller.h | 8 ++++---- .../camera_windows/windows/photo_handler.cpp | 6 ------ .../windows/preview_handler.cpp | 3 ++- .../windows/test/camera_plugin_test.cpp | 4 ++-- .../windows/test/capture_controller_test.cpp | 19 ++++++++++++++++++- .../camera_windows/windows/test/mocks.h | 4 ++-- 10 files changed, 41 insertions(+), 29 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index c967fa8823ce..a8d20cd9d38b 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -38,25 +38,25 @@ CameraImpl::~CameraImpl() { void CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, - bool enable_audio, + bool record_audio, ResolutionPreset resolution_preset) { auto capture_controller_factory = std::make_unique(); InitCamera(std::move(capture_controller_factory), texture_registrar, - messenger, enable_audio, resolution_preset); + messenger, record_audio, resolution_preset); } void CameraImpl::InitCamera( std::unique_ptr capture_controller_factory, flutter::TextureRegistrar* texture_registrar, - flutter::BinaryMessenger* messenger, bool enable_audio, + flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) { assert(!device_id_.empty()); messenger_ = messenger; capture_controller_ = capture_controller_factory->CreateCaptureController(this); capture_controller_->InitCaptureDevice(texture_registrar, device_id_, - enable_audio, resolution_preset); + record_audio, resolution_preset); } bool CameraImpl::AddPendingResult( diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index f70f6d69a271..ba6cd201961c 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -56,7 +56,7 @@ class Camera : public CaptureControllerListener { // Initializes camera and capture controller. virtual void InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, - bool enable_audio, + bool record_audio, ResolutionPreset resolution_preset) = 0; }; @@ -103,7 +103,7 @@ class CameraImpl : public Camera { return capture_controller_.get(); }; void InitCamera(flutter::TextureRegistrar* texture_registrar, - flutter::BinaryMessenger* messenger, bool enable_audio, + flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) override; // Inits camera with capture controller factory. @@ -111,7 +111,7 @@ class CameraImpl : public Camera { void InitCamera( std::unique_ptr capture_controller_factory, flutter::TextureRegistrar* texture_registrar, - flutter::BinaryMessenger* messenger, bool enable_audio, + flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset); private: diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index fff1b4f11ced..7c89cca3edaa 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -333,9 +333,9 @@ bool CameraPlugin::EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, void CameraPlugin::CreateMethodHandler( const EncodableMap& args, std::unique_ptr> result) { // Parse enableAudio argument. - const auto* enable_audio = + const auto* record_audio = std::get_if(ValueOrNull(args, kEnableAudioKey)); - if (!enable_audio) { + if (!record_audio) { return result->Error("argument_error", std::string(kEnableAudioKey) + " argument missing"); } @@ -380,7 +380,7 @@ void CameraPlugin::CreateMethodHandler( resolution_preset = ResolutionPreset::RESOLUTION_PRESET_AUTO; } - camera->InitCamera(texture_registrar_, messenger_, *enable_audio, + camera->InitCamera(texture_registrar_, messenger_, *record_audio, resolution_preset); cameras_.push_back(std::move(camera)); } diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 3fb250a59cea..d97ca2da668c 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -244,7 +244,7 @@ void CaptureControllerImpl::ResetCaptureController() { } } - if (preview_handler_ && preview_handler_->IsInitialized()) { + if (preview_handler_) { StopPreview(); } @@ -287,7 +287,7 @@ void CaptureControllerImpl::ResetCaptureController() { void CaptureControllerImpl::InitCaptureDevice( flutter::TextureRegistrar* texture_registrar, const std::string& device_id, - bool enable_audio, ResolutionPreset resolution_preset) { + bool record_audio, ResolutionPreset resolution_preset) { assert(capture_controller_listener_); if (capture_engine_state_ == CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && @@ -302,7 +302,7 @@ void CaptureControllerImpl::InitCaptureDevice( capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_INITIALIZING; resolution_preset_ = resolution_preset; - record_audio_ = enable_audio; + record_audio_ = record_audio; texture_registrar_ = texture_registrar; video_device_id_ = device_id; diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index c89b900402d6..218ca41f37eb 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -91,12 +91,12 @@ class CaptureController { // register texture for capture preview. // device_id: A string that holds information of camera device id to // be captured. - // enable_audio: A boolean value telling if audio should be captured on + // record_audio: A boolean value telling if audio should be captured on // video recording. // resolution_preset: Maximum capture resolution height. virtual void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, const std::string& device_id, - bool enable_audio, + bool record_audio, ResolutionPreset resolution_preset) = 0; // Returns texture id for preview @@ -147,7 +147,7 @@ class CaptureControllerImpl : public CaptureController, // From CaptureController void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, - const std::string& device_id, bool enable_audio, + const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset) override; int64_t GetTextureId() override { return texture_id_; } uint32_t GetPreviewWidth() override { return preview_frame_width_; } @@ -191,6 +191,7 @@ class CaptureControllerImpl : public CaptureController, bool record_audio_ = false; uint32_t preview_frame_width_ = 0; uint32_t preview_frame_height_ = 0; + UINT dx_device_reset_token_ = 0; std::unique_ptr record_handler_ = nullptr; std::unique_ptr preview_handler_ = nullptr; std::unique_ptr photo_handler_ = nullptr; @@ -208,7 +209,6 @@ class CaptureControllerImpl : public CaptureController, ComPtr base_preview_media_type_; ComPtr video_source_; ComPtr audio_source_; - UINT dx_device_reset_token_ = 0; // Texture int64_t texture_id_ = -1; diff --git a/packages/camera/camera_windows/windows/photo_handler.cpp b/packages/camera/camera_windows/windows/photo_handler.cpp index 79cf02c9f9fe..1db9f4e5daef 100644 --- a/packages/camera/camera_windows/windows/photo_handler.cpp +++ b/packages/camera/camera_windows/windows/photo_handler.cpp @@ -98,12 +98,6 @@ HRESULT PhotoHandler::InitPhotoSink(IMFCaptureEngine* capture_engine, return hr; } - hr = photo_sink_->RemoveAllStreams(); - if (FAILED(hr)) { - photo_sink_ = nullptr; - return hr; - } - DWORD photo_sink_stream_index; hr = photo_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, diff --git a/packages/camera/camera_windows/windows/preview_handler.cpp b/packages/camera/camera_windows/windows/preview_handler.cpp index 2f1ee73e8376..3add2029e168 100644 --- a/packages/camera/camera_windows/windows/preview_handler.cpp +++ b/packages/camera/camera_windows/windows/preview_handler.cpp @@ -129,7 +129,8 @@ bool PreviewHandler::StartPreview(IMFCaptureEngine* capture_engine, } bool PreviewHandler::StopPreview(IMFCaptureEngine* capture_engine) { - if (preview_state_ == PreviewState::PREVIEW_STATE__RUNNING || + if (preview_state_ == PreviewState::PREVIEW_STATE__STARTING || + preview_state_ == PreviewState::PREVIEW_STATE__RUNNING || preview_state_ == PreviewState::PREVIEW_STATE__PAUSED) { preview_state_ = PreviewState::PREVIEW_STATE__STOPPING; return SUCCEEDED(capture_engine->StopPreview()); 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 65b39ee217a7..071a3fad9f6f 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -116,7 +116,7 @@ TEST(CameraPlugin, CreateHandlerCallsInitCamera) { .Times(1) .WillOnce([cam = camera.get()]( flutter::TextureRegistrar* texture_registrar, - flutter::BinaryMessenger* messenger, bool enable_audio, + flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) { assert(cam->pending_result_); return cam->pending_result_->Success(EncodableValue(1)); @@ -202,7 +202,7 @@ TEST(CameraPlugin, CreateHandlerErrorOnExistingDeviceId) { .Times(1) .WillOnce([cam = camera.get()]( flutter::TextureRegistrar* texture_registrar, - flutter::BinaryMessenger* messenger, bool enable_audio, + flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) { assert(cam->pending_result_); return cam->pending_result_->Success(EncodableValue(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 887b50e96e96..1e4f1aa323ba 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -60,7 +60,7 @@ void MockInitCaptureController(CaptureControllerImpl* capture_controller, EXPECT_CALL(*engine, Initialize).Times(1); capture_controller->InitCaptureDevice( - texture_registrar, MOCK_DEVICE_ID, true, + texture_registrar, MOCK_DEVICE_ID, false, ResolutionPreset::RESOLUTION_PRESET_AUTO); // MockCaptureEngine::Initialize is called @@ -125,6 +125,23 @@ void MockStartPreview(CaptureControllerImpl* capture_controller, return S_OK; }); + EXPECT_CALL( + *capture_source, + GetAvailableDeviceMediaType( + Eq((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD), + _, _)) + .WillRepeatedly([mock_preview_width, mock_preview_height]( + DWORD stream_index, DWORD media_type_index, + IMFMediaType** media_type) { + // We give only one media type to loop through + if (media_type_index != 0) return MF_E_NO_MORE_TYPES; + *media_type = + new FakeMediaType(MFMediaType_Video, MFVideoFormat_RGB32, + mock_preview_width, mock_preview_height); + (*media_type)->AddRef(); + return S_OK; + }); + EXPECT_CALL(*engine, StartPreview()).Times(1).WillOnce(Return(S_OK)); // Called by destructor diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index e50d3d188764..85b9d478b121 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -184,7 +184,7 @@ class MockCamera : public Camera { MOCK_METHOD(void, InitCamera, (flutter::TextureRegistrar * texture_registrar, - flutter::BinaryMessenger* messenger, bool enable_audio, + flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset), (override)); @@ -214,7 +214,7 @@ class MockCaptureController : public CaptureController { MOCK_METHOD(void, InitCaptureDevice, (flutter::TextureRegistrar * texture_registrar, - const std::string& device_id, bool enable_audio, + const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset), (override)); From b7679f1a4ea7cd59ea1375fb1c47fe56c9b41de2 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 18:46:38 +0200 Subject: [PATCH 43/93] [camera_windows] prefer smart pointers --- .../camera_windows/example/lib/main.dart | 29 ++-- .../camera_windows/windows/camera_plugin.cpp | 31 ++-- .../windows/capture_controller.cpp | 144 +++++++++--------- .../camera_windows/windows/record_handler.cpp | 2 + 4 files changed, 110 insertions(+), 96 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index 0d5438a90b0c..f943c645f91c 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -196,17 +196,22 @@ class _MyAppState extends State { Future toggleRecord() async { if (_initialized && _cameraId > 0) { - if (!_recording) { - await CameraPlatform.instance.startVideoRecording(_cameraId); + if (_recordingTimed) { + // Request to stop timed recording short. + await CameraPlatform.instance.stopVideoRecording(_cameraId); } else { - final XFile _file = - await CameraPlatform.instance.stopVideoRecording(_cameraId); + if (!_recording) { + await CameraPlatform.instance.startVideoRecording(_cameraId); + } else { + final XFile _file = + await CameraPlatform.instance.stopVideoRecording(_cameraId); - showInSnackBar('Video captured to: ${_file.path}'); + showInSnackBar('Video captured to: ${_file.path}'); + } + setState(() { + _recording = !_recording; + }); } - setState(() { - _recording = !_recording; - }); } } @@ -295,11 +300,11 @@ class _MyAppState extends State { ), const SizedBox(width: 5), ElevatedButton( - onPressed: (_initialized && !_recordingTimed) - ? toggleRecord - : null, + onPressed: _initialized ? toggleRecord : null, child: Text( - _recording ? 'Stop recording' : 'Record Video', + (_recording || _recordingTimed) + ? 'Stop recording' + : 'Record Video', ), ), const SizedBox(width: 5), diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 7c89cca3edaa..d935fd7756ee 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -4,6 +4,7 @@ #include "camera_plugin.h" +#include #include #include #include @@ -90,26 +91,26 @@ ResolutionPreset ParseResolutionPreset(const std::string& resolution_preset) { std::unique_ptr GetDeviceInfo(IMFActivate* device) { assert(device); auto device_info = std::make_unique(); - wchar_t* name; + CComHeapPtr name; UINT32 name_size; HRESULT hr = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size); - if (SUCCEEDED(hr)) { - wchar_t* id; - UINT32 id_size; - hr = device->GetAllocatedString( - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, &id_size); - - if (SUCCEEDED(hr)) { - device_info->display_name = Utf8FromUtf16(std::wstring(name, name_size)); - device_info->device_id = Utf8FromUtf16(std::wstring(id, id_size)); - } + if (FAILED(hr)) { + return device_info; + } + + CComHeapPtr id; + UINT32 id_size; + hr = device->GetAllocatedString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, &id_size); - ::CoTaskMemFree(id); + if (FAILED(hr)) { + return device_info; } - ::CoTaskMemFree(name); + device_info->display_name = Utf8FromUtf16(std::wstring(name, name_size)); + device_info->device_id = Utf8FromUtf16(std::wstring(id, id_size)); return device_info; } @@ -293,7 +294,7 @@ void CameraPlugin::DisposeCameraByCameraId(int64_t camera_id) { void CameraPlugin::AvailableCamerasMethodHandler( std::unique_ptr> result) { // Enumerate devices. - IMFActivate** devices; + CComHeapPtr devices; UINT32 count = 0; if (!this->EnumerateVideoCaptureDeviceSources(&devices, &count)) { result->Error("System error", "Failed to get available cameras"); @@ -303,7 +304,6 @@ void CameraPlugin::AvailableCamerasMethodHandler( if (count == 0) { result->Success(EncodableValue(EncodableList())); - CoTaskMemFree(devices); return; } @@ -320,7 +320,6 @@ void CameraPlugin::AvailableCamerasMethodHandler( })); } - CoTaskMemFree(devices); result->Success(std::move(EncodableValue(devices_list))); } diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index d97ca2da668c..2a7caf531efd 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -4,6 +4,7 @@ #include "capture_controller.h" +#include #include #include @@ -35,22 +36,27 @@ bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( ComPtr attributes; HRESULT hr = MFCreateAttributes(&attributes, 1); + if (FAILED(hr)) { + return false; + } - if (SUCCEEDED(hr)) { - hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (FAILED(hr)) { + return false; } - if (SUCCEEDED(hr)) { - hr = MFEnumDeviceSources(attributes.Get(), devices, count); + hr = MFEnumDeviceSources(attributes.Get(), devices, count); + if (FAILED(hr)) { + return false; } - return SUCCEEDED(hr); + return true; } HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { audio_source_ = nullptr; - IMFActivate** devices = nullptr; + CComHeapPtr devices; UINT32 count = 0; ComPtr attributes; @@ -66,7 +72,7 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { } if (SUCCEEDED(hr) && count > 0) { - wchar_t* audio_device_id; + CComHeapPtr audio_device_id; UINT32 audio_device_id_size; // Use first audio device. @@ -95,12 +101,8 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { audio_source_.GetAddressOf()); } } - - CoTaskMemFree(audio_device_id); } - CoTaskMemFree(devices); - return hr; } @@ -111,24 +113,26 @@ HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( ComPtr video_capture_source_attributes; HRESULT hr = MFCreateAttributes(&video_capture_source_attributes, 2); - - if (SUCCEEDED(hr)) { - hr = video_capture_source_attributes->SetGUID( - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (FAILED(hr)) { + return hr; } - if (SUCCEEDED(hr)) { - hr = video_capture_source_attributes->SetString( - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, - Utf16FromUtf8(video_device_id).c_str()); + hr = video_capture_source_attributes->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (FAILED(hr)) { + return hr; } - if (SUCCEEDED(hr)) { - hr = MFCreateDeviceSource(video_capture_source_attributes.Get(), - video_source_.GetAddressOf()); + hr = video_capture_source_attributes->SetString( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, + Utf16FromUtf8(video_device_id).c_str()); + if (FAILED(hr)) { + return hr; } + hr = MFCreateDeviceSource(video_capture_source_attributes.Get(), + video_source_.GetAddressOf()); return hr; } @@ -139,26 +143,27 @@ HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, &dx11_device_, nullptr, nullptr); - - if (SUCCEEDED(hr)) { - // Enable multithread protection - ComPtr multi_thread; - hr = dx11_device_.As(&multi_thread); - if (SUCCEEDED(hr)) { - multi_thread->SetMultithreadProtected(TRUE); - } + if (FAILED(hr)) { + return hr; } - if (SUCCEEDED(hr)) { - hr = MFCreateDXGIDeviceManager(&dx_device_reset_token_, - dxgi_device_manager_.GetAddressOf()); + // Enable multithread protection + ComPtr multi_thread; + hr = dx11_device_.As(&multi_thread); + if (FAILED(hr)) { + return hr; } - if (SUCCEEDED(hr)) { - hr = dxgi_device_manager_->ResetDevice(dx11_device_.Get(), - dx_device_reset_token_); + multi_thread->SetMultithreadProtected(TRUE); + + hr = MFCreateDXGIDeviceManager(&dx_device_reset_token_, + dxgi_device_manager_.GetAddressOf()); + if (FAILED(hr)) { + return hr; } + hr = dxgi_device_manager_->ResetDevice(dx11_device_.Get(), + dx_device_reset_token_); return hr; } @@ -461,28 +466,28 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { ComPtr source; HRESULT hr = capture_engine_->GetSource(&source); + if (FAILED(hr)) { + return hr; + } - if (SUCCEEDED(hr)) { - // Find base media type for previewing. - uint32_t max_preview_height = GetMaxPreviewHeight(); - if (!FindBestMediaType( - (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, - source.Get(), base_preview_media_type_.GetAddressOf(), - max_preview_height, &preview_frame_width_, - &preview_frame_height_)) { - return E_FAIL; - } + // Find base media type for previewing. + uint32_t max_preview_height = GetMaxPreviewHeight(); + if (!FindBestMediaType( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, + source.Get(), base_preview_media_type_.GetAddressOf(), + max_preview_height, &preview_frame_width_, &preview_frame_height_)) { + return E_FAIL; + } - // Find base media type for record and photo capture. - if (!FindBestMediaType( - (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, - source.Get(), base_capture_media_type_.GetAddressOf(), - (uint32_t)0xffffffff, nullptr, nullptr)) { - return E_FAIL; - } + // Find base media type for record and photo capture. + if (!FindBestMediaType( + (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, + source.Get(), base_capture_media_type_.GetAddressOf(), + (uint32_t)0xffffffff, nullptr, nullptr)) { + return E_FAIL; } - return hr; + return S_OK; } void CaptureControllerImpl::StartRecord(const std::string& file_path, @@ -490,9 +495,9 @@ void CaptureControllerImpl::StartRecord(const std::string& file_path, assert(capture_engine_); if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { - return OnRecordStarted( - false, - "Camera not initialized. Camera should be disposed and reinitialized."); + return OnRecordStarted(false, + "Camera not initialized. Camera should be " + "disposed and reinitialized."); } if (!base_capture_media_type_) { @@ -511,7 +516,8 @@ void CaptureControllerImpl::StartRecord(const std::string& file_path, "first."); } - // Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response process. + // Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response + // process. if (!record_handler_->StartRecord(file_path, max_video_duration_ms, capture_engine_.Get(), base_capture_media_type_.Get())) { @@ -525,16 +531,17 @@ void CaptureControllerImpl::StopRecord() { assert(capture_controller_listener_); if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { - return OnRecordStopped( - false, - "Camera not initialized. Camera should be disposed and reinitialized."); + return OnRecordStopped(false, + "Camera not initialized. Camera should be " + "disposed and reinitialized."); } if (!record_handler_ && !record_handler_->CanStop()) { return OnRecordStopped(false, "Recording cannot be stopped."); } - // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process. + // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response + // process. if (!record_handler_->StopRecord(capture_engine_.Get())) { // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; @@ -565,9 +572,9 @@ void CaptureControllerImpl::StartPreview() { assert(capture_engine_); if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { - return OnPreviewStarted( - false, - "Camera not initialized. Camera should be disposed and reinitialized."); + return OnPreviewStarted(false, + "Camera not initialized. Camera should be " + "disposed and reinitialized."); } if (!base_preview_media_type_) { @@ -599,7 +606,8 @@ void CaptureControllerImpl::StartPreview() { // Stops preview. Called by destructor // Use PausePreview and ResumePreview methods to for // pausing and resuming the preview. -// Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event handling for response process. +// Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event handling for response +// process. void CaptureControllerImpl::StopPreview() { assert(capture_engine_); diff --git a/packages/camera/camera_windows/windows/record_handler.cpp b/packages/camera/camera_windows/windows/record_handler.cpp index 88b9bd2169d7..ad9b52d1806b 100644 --- a/packages/camera/camera_windows/windows/record_handler.cpp +++ b/packages/camera/camera_windows/windows/record_handler.cpp @@ -203,6 +203,8 @@ bool RecordHandler::StartRecord(const std::string& file_path, type_ = max_duration < 0 ? RecordingType::CONTINUOUS : RecordingType::TIMED; max_video_duration_ms_ = max_duration; file_path_ = file_path; + recording_start_timestamp_us_ = -1; + recording_duration_us_ = 0; if (FAILED(InitRecordSink(capture_engine, base_media_type))) { return false; From 2f6763ac9cda6233f0c99b341fec267b9f82e1bf Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 19:02:17 +0200 Subject: [PATCH 44/93] [camera_windows] re-use cameras method channel --- .../camera/camera_windows/windows/camera.cpp | 35 +++++++++++-------- .../camera/camera_windows/windows/camera.h | 5 +++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index a8d20cd9d38b..c37bf8caa42b 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -9,23 +9,11 @@ using flutter::EncodableList; using flutter::EncodableMap; using flutter::EncodableValue; -namespace { // Camera channel events. const char kCameraMethodChannelBaseName[] = "flutter.io/cameraPlugin/camera"; const char kVideoRecordedEvent[] = "video_recorded"; const char kErrorEvent[] = "error"; -// Helper function for creating messaging channel for camera. -std::unique_ptr> BuildChannelForCamera( - flutter::BinaryMessenger* messenger, int64_t camera_id) { - auto channel_name = - std::string(kCameraMethodChannelBaseName) + std::to_string(camera_id); - return std::make_unique>( - messenger, channel_name, &flutter::StandardMethodCodec::GetInstance()); -} - -} // namespace - CameraImpl::CameraImpl(const std::string& device_id) : device_id_(device_id), Camera(device_id) {} @@ -106,6 +94,24 @@ void CameraImpl::SendErrorForPendingResults(const std::string& error_code, pending_results_.clear(); } +MethodChannel<>* CameraImpl::GetMethodChannel() { + assert(messenger_); + assert(camera_id_); + + // Use existing channel if initialized + if (camera_channel_) { + return camera_channel_.get(); + } + + auto channel_name = + std::string(kCameraMethodChannelBaseName) + std::to_string(camera_id_); + + camera_channel_ = std::make_unique>( + messenger_, channel_name, &flutter::StandardMethodCodec::GetInstance()); + + return camera_channel_.get(); +} + void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { // Use texture id as camera id camera_id_ = texture_id; @@ -220,7 +226,7 @@ void CameraImpl::OnTakePictureFailed(const std::string& error) { void CameraImpl::OnVideoRecordSucceeded(const std::string& file_path, int64_t video_duration_ms) { if (messenger_ && camera_id_ >= 0) { - auto channel = BuildChannelForCamera(messenger_, camera_id_); + auto channel = GetMethodChannel(); std::unique_ptr message_data = std::make_unique( @@ -236,7 +242,8 @@ void CameraImpl::OnVideoRecordFailed(const std::string& error){}; void CameraImpl::OnCaptureError(const std::string& error) { if (messenger_ && camera_id_ >= 0) { - auto channel = BuildChannelForCamera(messenger_, camera_id_); + auto channel = GetMethodChannel(); + std::unique_ptr message_data = std::make_unique(EncodableMap( {{EncodableValue("description"), EncodableValue(error)}})); diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index ba6cd201961c..e855636f7cb0 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -15,6 +15,7 @@ namespace camera_windows { using flutter::EncodableMap; +using flutter::MethodChannel; using flutter::MethodResult; enum PendingResultType { @@ -116,6 +117,7 @@ class CameraImpl : public Camera { private: std::unique_ptr capture_controller_ = nullptr; + std::unique_ptr> camera_channel_ = nullptr; flutter::BinaryMessenger* messenger_ = nullptr; int64_t camera_id_ = -1; std::string device_id_; @@ -133,6 +135,9 @@ class CameraImpl : public Camera { // error_message: A user-readable error message (optional). void SendErrorForPendingResults(const std::string& error_code, const std::string& descripion); + + // Initializes method channel instance and returns pointer it + MethodChannel<>* GetMethodChannel(); }; class CameraFactory { From 3fe71193d7a69c0e7c234570239c16d61aaecefb Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 19:26:26 +0200 Subject: [PATCH 45/93] [camera_windows] Fix dart documentation errors --- .../camera_windows/example/lib/main.dart | 70 ++++++++++--------- .../camera_windows/lib/camera_windows.dart | 67 +++++++++--------- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index f943c645f91c..e0054c9e47d9 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -12,7 +12,7 @@ void main() { runApp(MyApp()); } -// App for testing. +/// Example app for Camera Windows plugin. class MyApp extends StatefulWidget { @override State createState() => _MyAppState(); @@ -34,19 +34,19 @@ class _MyAppState extends State { void initState() { super.initState(); WidgetsFlutterBinding.ensureInitialized(); - getAvailableCameras(); + _getAvailableCameras(); } @override void dispose() { - disposeCurrentCamera(); + _disposeCurrentCamera(); _errorStreamSubscription?.cancel(); _errorStreamSubscription = null; super.dispose(); } - // Fetches list of available cameras from camera_windows plugin. - Future getAvailableCameras() async { + /// Fetches list of available cameras from camera_windows plugin. + Future _getAvailableCameras() async { String cameraInfo; List cameras = []; @@ -72,7 +72,7 @@ class _MyAppState extends State { } /// Initializes the camera on the device. - Future initializeFirstCamera() async { + Future _initializeFirstCamera() async { assert(_cameras.isNotEmpty); assert(!_initialized); final Completer _initializeCompleter = @@ -88,8 +88,9 @@ class _MyAppState extends State { ); _errorStreamSubscription?.cancel(); - _errorStreamSubscription = - CameraPlatform.instance.onCameraError(cameraId).listen(onCameraError); + _errorStreamSubscription = CameraPlatform.instance + .onCameraError(cameraId) + .listen(_onCameraError); unawaited(CameraPlatform.instance .onCameraInitialized(cameraId) @@ -123,7 +124,8 @@ class _MyAppState extends State { } on CameraException catch (e) { debugPrint('Failed to dispose camera: ${e.code}: ${e.description}'); } - // Reset state. + + /// Reset state. setState(() { _initialized = false; _cameraId = -1; @@ -137,7 +139,7 @@ class _MyAppState extends State { } } - Future disposeCurrentCamera() async { + Future _disposeCurrentCamera() async { assert(_cameraId > 0); assert(_initialized); try { @@ -151,7 +153,7 @@ class _MyAppState extends State { _recordingTimed = false; _previewPaused = false; }); - getAvailableCameras(); + _getAvailableCameras(); } on CameraException catch (e) { setState(() { _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.description}'; @@ -159,16 +161,16 @@ class _MyAppState extends State { } } - Widget buildPreview() { + Widget _buildPreview() { return CameraPlatform.instance.buildPreview(_cameraId); } - Future takePicture() async { + Future _takePicture() async { final XFile _file = await CameraPlatform.instance.takePicture(_cameraId); - showInSnackBar('Picture captured to: ${_file.path}'); + _showInSnackBar('Picture captured to: ${_file.path}'); } - Future recordTimed(int seconds) async { + Future _recordTimed(int seconds) async { if (_initialized && _cameraId > 0 && !_recordingTimed) { CameraPlatform.instance .onVideoRecordedEvent(_cameraId) @@ -179,7 +181,7 @@ class _MyAppState extends State { _recordingTimed = false; }); - showInSnackBar('Video captured to: ${event.file.path}'); + _showInSnackBar('Video captured to: ${event.file.path}'); } }); @@ -194,10 +196,10 @@ class _MyAppState extends State { } } - Future toggleRecord() async { + Future _toggleRecord() async { if (_initialized && _cameraId > 0) { if (_recordingTimed) { - // Request to stop timed recording short. + /// Request to stop timed recording short. await CameraPlatform.instance.stopVideoRecording(_cameraId); } else { if (!_recording) { @@ -206,7 +208,7 @@ class _MyAppState extends State { final XFile _file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - showInSnackBar('Video captured to: ${_file.path}'); + _showInSnackBar('Video captured to: ${_file.path}'); } setState(() { _recording = !_recording; @@ -215,7 +217,7 @@ class _MyAppState extends State { } } - Future togglePreview() async { + Future _togglePreview() async { if (_initialized && _cameraId > 0) { if (!_previewPaused) { await CameraPlatform.instance.pausePreview(_cameraId); @@ -228,23 +230,23 @@ class _MyAppState extends State { } } - void onCameraError(CameraErrorEvent event) { - scaffoldMessengerKey.currentState + void _onCameraError(CameraErrorEvent event) { + _scaffoldMessengerKey.currentState ?.showSnackBar(SnackBar(content: Text('Error: ${event.description}'))); } - void showInSnackBar(String message) { - scaffoldMessengerKey.currentState + void _showInSnackBar(String message) { + _scaffoldMessengerKey.currentState ?.showSnackBar(SnackBar(content: Text(message))); } - final GlobalKey scaffoldMessengerKey = + final GlobalKey _scaffoldMessengerKey = GlobalKey(); @override Widget build(BuildContext context) { return MaterialApp( - scaffoldMessengerKey: scaffoldMessengerKey, + scaffoldMessengerKey: _scaffoldMessengerKey, home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), @@ -260,7 +262,7 @@ class _MyAppState extends State { ), if (_cameras.isEmpty) ElevatedButton( - onPressed: getAvailableCameras, + onPressed: _getAvailableCameras, child: const Text('Re-check available cameras'), ), if (_cameras.isNotEmpty) @@ -281,26 +283,26 @@ class _MyAppState extends State { const SizedBox(width: 20), ElevatedButton( onPressed: _initialized - ? disposeCurrentCamera - : initializeFirstCamera, + ? _disposeCurrentCamera + : _initializeFirstCamera, child: Text(_initialized ? 'Dispose camera' : 'Create camera'), ), const SizedBox(width: 5), ElevatedButton( - onPressed: _initialized ? takePicture : null, + onPressed: _initialized ? _takePicture : null, child: const Text('Take picture'), ), const SizedBox(width: 5), ElevatedButton( - onPressed: _initialized ? togglePreview : null, + onPressed: _initialized ? _togglePreview : null, child: Text( _previewPaused ? 'Resume preview' : 'Pause preview', ), ), const SizedBox(width: 5), ElevatedButton( - onPressed: _initialized ? toggleRecord : null, + onPressed: _initialized ? _toggleRecord : null, child: Text( (_recording || _recordingTimed) ? 'Stop recording' @@ -310,7 +312,7 @@ class _MyAppState extends State { const SizedBox(width: 5), ElevatedButton( onPressed: (_initialized && !_recording && !_recordingTimed) - ? () => recordTimed(5) + ? () => _recordTimed(5) : null, child: const Text( 'Record 5 seconds', @@ -332,7 +334,7 @@ class _MyAppState extends State { ), child: AspectRatio( aspectRatio: _previewSize!.width / _previewSize!.height, - child: buildPreview(), + child: _buildPreview(), ), ), ), diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index e37379172eaa..8c4c057488f2 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -14,31 +14,31 @@ import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:stream_transform/stream_transform.dart'; -// An implementation of [CameraPlatform] for Windows. +/// An implementation of [CameraPlatform] for Windows. class CameraWindows extends CameraPlatform { - // Registers the Windows implementation of CameraPlatform. + /// Registers the Windows implementation of CameraPlatform. static void registerWith() { CameraPlatform.instance = CameraWindows(); } - // The method channel used to interact with the native platform. + /// The method channel used to interact with the native platform. final MethodChannel _pluginChannel = const MethodChannel('plugins.flutter.io/camera'); - // Camera specific method channels to allow comminicating with specific cameras. + /// Camera specific method channels to allow comminicating with specific cameras. final Map _cameraChannels = {}; - // The controller that broadcasts events coming from handleCameraMethodCall - // - // It is a `broadcast` because multiple controllers will connect to - // different stream views of this Controller. - // This is only exposed for test purposes. It shouldn't be used by clients of - // the plugin as it may break or change at any time. + /// The controller that broadcasts events coming from handleCameraMethodCall + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); - // Returns a stream of camera events for the given [cameraId]. + /// Returns a stream of camera events for the given [cameraId]. Stream _cameraEvents(int cameraId) => cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); @@ -99,7 +99,7 @@ class CameraWindows extends CameraPlatform { }) async { final int requestedCameraId = cameraId; - // Creates channel for camera events. + /// Creates channel for camera events. _cameraChannels.putIfAbsent(requestedCameraId, () { final MethodChannel channel = MethodChannel('flutter.io/cameraPlugin/camera$requestedCameraId'); @@ -159,7 +159,7 @@ class CameraWindows extends CameraPlatform { @override Stream onCameraResolutionChanged(int cameraId) { - // Windows camera plugin does not support resolution changed events. + /// Windows camera plugin does not support resolution changed events. return const Stream.empty(); } @@ -180,8 +180,8 @@ class CameraWindows extends CameraPlatform { @override Stream onDeviceOrientationChanged() { - // Windows camera plugin does not support capture orientations. - // Force device orientation to landscape as by default camera plugin uses portraitUp orientation. + /// Windows camera plugin does not support capture orientations. + /// Force device orientation to landscape as by default camera plugin uses portraitUp orientation. return Stream.value( const DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight), ); @@ -288,29 +288,29 @@ class CameraWindows extends CameraPlatform { @override Future getMinExposureOffset(int cameraId) async { - // Explosure offset is not supported by camera windows plugin yet. - // Default min offset value is returned. + /// Explosure offset is not supported by camera windows plugin yet. + /// Default min offset value is returned. return 0.0; } @override Future getMaxExposureOffset(int cameraId) async { - // Explosure offset is not supported by camera windows plugin yet. - // Default max offset value is returned. + /// Explosure offset is not supported by camera windows plugin yet. + /// Default max offset value is returned. return 0.0; } @override Future getExposureOffsetStepSize(int cameraId) async { - // Explosure offset is not supported by camera windows plugin yet. - // Default step value is returned. + /// Explosure offset is not supported by camera windows plugin yet. + /// Default step value is returned. return 1.0; } @override Future setExposureOffset(int cameraId, double offset) async { - // Explosure offset is not supported by camera windows plugin yet. - // Default exposure offset value is returned as a response. + /// Explosure offset is not supported by camera windows plugin yet. + /// Default exposure offset value is returned as a response. return 0.0; } @@ -329,15 +329,15 @@ class CameraWindows extends CameraPlatform { @override Future getMinZoomLevel(int cameraId) async { - // Zoom level is not supported by camera windows plugin yet. - // Default min zoom level value is returned as a response. + /// Zoom level is not supported by camera windows plugin yet. + /// Default min zoom level value is returned as a response. return 1.0; } @override Future getMaxZoomLevel(int cameraId) async { - // Zoom level is not supported by camera windows plugin yet. - // Default max zoom level value is returned as a response. + /// Zoom level is not supported by camera windows plugin yet. + /// Default max zoom level value is returned as a response. return 1.0; } @@ -375,7 +375,7 @@ class CameraWindows extends CameraPlatform { return Texture(textureId: cameraId); } - // Returns the resolution preset as a String. + /// Returns the resolution preset as a String. String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { case ResolutionPreset.max: @@ -395,10 +395,10 @@ class CameraWindows extends CameraPlatform { } } - // Converts messages received from the native platform into camera events. - // - // This is only exposed for test purposes. It shouldn't be used by clients of - // the plugin as it may break or change at any time. + /// Converts messages received from the native platform into camera events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. @visibleForTesting Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { @@ -410,7 +410,8 @@ class CameraWindows extends CameraPlatform { ); break; case 'video_recorded': - // This is called if maxVideoDuration was given on record start. + + /// This is called if maxVideoDuration was given on record start. cameraEventStreamController.add( VideoRecordedEvent( cameraId, From 81691bc61a9860dcf18bd05a5f85c7b17aa7bf1d Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 19:44:28 +0200 Subject: [PATCH 46/93] [camera_windows] fix depedencies --- packages/camera/camera_windows/lib/camera_windows.dart | 4 +--- packages/camera/camera_windows/pubspec.yaml | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 8c4c057488f2..d1e6f02be414 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -11,7 +11,6 @@ import 'package:cross_file/cross_file.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:meta/meta.dart'; import 'package:stream_transform/stream_transform.dart'; /// An implementation of [CameraPlatform] for Windows. @@ -410,8 +409,7 @@ class CameraWindows extends CameraPlatform { ); break; case 'video_recorded': - - /// This is called if maxVideoDuration was given on record start. + // This is called if maxVideoDuration was given on record start. cameraEventStreamController.add( VideoRecordedEvent( cameraId, diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 8c900e6b4852..113cae19d33a 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -18,8 +18,10 @@ flutter: dependencies: camera_platform_interface: ^2.1.2 + cross_file: ^0.3.1 flutter: sdk: flutter + stream_transform: ^2.0.0 dev_dependencies: flutter_lints: ^1.0.0 From 85c7d854e6de1355645af73338dcc3f4e324d4da Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 26 Jan 2022 20:03:17 +0200 Subject: [PATCH 47/93] [camera_windows] fix class member declaration order --- .../camera/camera_windows/windows/camera.h | 23 +++---- .../camera_windows/windows/camera_plugin.h | 12 ++-- .../windows/capture_controller.cpp | 11 ++-- .../windows/capture_controller.h | 66 +++++++++---------- 4 files changed, 56 insertions(+), 56 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index e855636f7cb0..6efe289c9dca 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -116,17 +116,6 @@ class CameraImpl : public Camera { ResolutionPreset resolution_preset); private: - std::unique_ptr capture_controller_ = nullptr; - std::unique_ptr> camera_channel_ = nullptr; - flutter::BinaryMessenger* messenger_ = nullptr; - int64_t camera_id_ = -1; - std::string device_id_; - - // Pending results. - std::map>> pending_results_; - std::unique_ptr> GetPendingResultByType( - PendingResultType type); - // Loops through all pending results calls their // error handler with given error id and description. // Pending results are cleared in the process. @@ -138,6 +127,18 @@ class CameraImpl : public Camera { // Initializes method channel instance and returns pointer it MethodChannel<>* GetMethodChannel(); + + // Finds pending result from pending_results map by type. + // Returns nullptr if type is not present. + std::unique_ptr> GetPendingResultByType( + PendingResultType type); + + std::map>> pending_results_; + std::unique_ptr capture_controller_ = nullptr; + std::unique_ptr> camera_channel_ = nullptr; + flutter::BinaryMessenger* messenger_ = nullptr; + int64_t camera_id_ = -1; + std::string device_id_; }; class CameraFactory { diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 3c4313d80ca5..91b23149a67b 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -44,8 +44,6 @@ class CameraPlugin : public flutter::Plugin, std::unique_ptr> result); protected: - std::vector> cameras_; - // Loops through cameras and returns camera // with matching device_id or nullptr. Camera* GetCameraByDeviceId(std::string& device_id); @@ -61,11 +59,9 @@ class CameraPlugin : public flutter::Plugin, bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) override; - private: - std::unique_ptr camera_factory_; - flutter::TextureRegistrar* texture_registrar_; - flutter::BinaryMessenger* messenger_; + std::vector> cameras_; + private: // Handles availableCameras method calls. // Enumerates video capture devices and // returns list of available camera devices. @@ -119,6 +115,10 @@ class CameraPlugin : public flutter::Plugin, // Disposes camera if exists. void DisposeMethodHandler(const EncodableMap& args, std::unique_ptr> result); + + std::unique_ptr camera_factory_; + flutter::TextureRegistrar* texture_registrar_; + flutter::BinaryMessenger* messenger_; }; } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 2a7caf531efd..b2b333512f33 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -337,14 +337,14 @@ void CaptureControllerImpl::InitCaptureDevice( const FlutterDesktopPixelBuffer* CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, size_t target_height) { - if (this->source_buffer_data_ && this->source_buffer_size_ > 0 && + if (this->source_buffer_ && this->source_buffer_size_ > 0 && this->preview_frame_width_ > 0 && this->preview_frame_height_ > 0) { uint32_t pixels_total = this->preview_frame_width_ * this->preview_frame_height_; dest_buffer_ = std::make_unique(pixels_total * 4); MFVideoFormatRGB32Pixel* src = - (MFVideoFormatRGB32Pixel*)this->source_buffer_data_.get(); + (MFVideoFormatRGB32Pixel*)this->source_buffer_.get(); FlutterDesktopPixel* dst = (FlutterDesktopPixel*)dest_buffer_.get(); for (uint32_t i = 0; i < pixels_total; i++) { @@ -845,14 +845,13 @@ void CaptureControllerImpl::OnRecordStopped(bool success, // Called via IMFCaptureEngineOnSampleCallback implementation. // Implements CaptureEngineObserver::GetFrameBuffer. uint8_t* CaptureControllerImpl::GetFrameBuffer(uint32_t new_length) { - if (this->source_buffer_data_ == nullptr || + if (this->source_buffer_ == nullptr || this->source_buffer_size_ != new_length) { // Update source buffer size. - this->source_buffer_data_ = nullptr; - this->source_buffer_data_ = std::make_unique(new_length); + this->source_buffer_ = std::make_unique(new_length); this->source_buffer_size_ = new_length; } - return this->source_buffer_data_.get(); + return this->source_buffer_.get(); } // Marks texture frame available after buffer is updated. diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 218ca41f37eb..9708aaad54d4 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -187,39 +187,6 @@ class CaptureControllerImpl : public CaptureController, }; private: - bool media_foundation_started_ = false; - bool record_audio_ = false; - uint32_t preview_frame_width_ = 0; - uint32_t preview_frame_height_ = 0; - UINT dx_device_reset_token_ = 0; - std::unique_ptr record_handler_ = nullptr; - std::unique_ptr preview_handler_ = nullptr; - std::unique_ptr photo_handler_ = nullptr; - CaptureControllerListener* capture_controller_listener_ = nullptr; - std::string video_device_id_; - CaptureEngineState capture_engine_state_ = - CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; - ResolutionPreset resolution_preset_ = - ResolutionPreset::RESOLUTION_PRESET_MEDIUM; - ComPtr capture_engine_; - ComPtr capture_engine_callback_handler_; - ComPtr dxgi_device_manager_; - ComPtr dx11_device_; - ComPtr base_capture_media_type_; - ComPtr base_preview_media_type_; - ComPtr video_source_; - ComPtr audio_source_; - - // Texture - int64_t texture_id_ = -1; - flutter::TextureRegistrar* texture_registrar_ = nullptr; - std::unique_ptr texture_; - FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; - uint32_t source_buffer_size_ = 0; - std::unique_ptr source_buffer_data_ = nullptr; - std::unique_ptr dest_buffer_ = nullptr; - uint32_t bytes_per_pixel_ = 4; - // Resets capture controller state. // This is called if capture engine creation fails or is disposed. void ResetCaptureController(); @@ -275,6 +242,39 @@ class CaptureControllerImpl : public CaptureController, // Converts local pixel buffer to flutter pixel buffer const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, size_t height); + + bool media_foundation_started_ = false; + bool record_audio_ = false; + uint32_t preview_frame_width_ = 0; + uint32_t preview_frame_height_ = 0; + UINT dx_device_reset_token_ = 0; + std::unique_ptr record_handler_ = nullptr; + std::unique_ptr preview_handler_ = nullptr; + std::unique_ptr photo_handler_ = nullptr; + CaptureControllerListener* capture_controller_listener_ = nullptr; + std::string video_device_id_; + CaptureEngineState capture_engine_state_ = + CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; + ResolutionPreset resolution_preset_ = + ResolutionPreset::RESOLUTION_PRESET_MEDIUM; + ComPtr capture_engine_; + ComPtr capture_engine_callback_handler_; + ComPtr dxgi_device_manager_; + ComPtr dx11_device_; + ComPtr base_capture_media_type_; + ComPtr base_preview_media_type_; + ComPtr video_source_; + ComPtr audio_source_; + + // Texture + int64_t texture_id_ = -1; + uint32_t bytes_per_pixel_ = 4; + uint32_t source_buffer_size_ = 0; + std::unique_ptr source_buffer_ = nullptr; + std::unique_ptr dest_buffer_ = nullptr; + std::unique_ptr texture_; + flutter::TextureRegistrar* texture_registrar_ = nullptr; + FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; }; class CaptureControllerFactory { From 40cb3536892f1e85b45d53210e24cc157e5a4b5e Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 27 Jan 2022 08:57:32 +0200 Subject: [PATCH 48/93] [camera_windows] use friend class for tests --- .../camera/camera_windows/windows/camera_plugin.h | 15 +++++++++++---- .../camera_windows/windows/capture_controller.h | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 91b23149a67b..9350645eea09 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -19,6 +19,13 @@ namespace camera_windows { using flutter::MethodResult; +namespace test { +namespace { +// Forward declaration of test class. +class MockCameraPlugin; +} // namespace +} // namespace test + class CameraPlugin : public flutter::Plugin, public VideoCaptureDeviceEnumerator { public: @@ -43,7 +50,7 @@ class CameraPlugin : public flutter::Plugin, void HandleMethodCall(const flutter::MethodCall<>& method_call, std::unique_ptr> result); - protected: + private: // Loops through cameras and returns camera // with matching device_id or nullptr. Camera* GetCameraByDeviceId(std::string& device_id); @@ -59,9 +66,6 @@ class CameraPlugin : public flutter::Plugin, bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) override; - std::vector> cameras_; - - private: // Handles availableCameras method calls. // Enumerates video capture devices and // returns list of available camera devices. @@ -119,6 +123,9 @@ class CameraPlugin : public flutter::Plugin, std::unique_ptr camera_factory_; flutter::TextureRegistrar* texture_registrar_; flutter::BinaryMessenger* messenger_; + std::vector> cameras_; + + friend class camera_windows::test::MockCameraPlugin; }; } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 9708aaad54d4..849f70d8ed1e 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -70,7 +70,7 @@ enum CaptureEngineState { }; class VideoCaptureDeviceEnumerator { - protected: + private: virtual bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) = 0; }; From 8bb54eaa7bca258d6bea9ee3287024c38b95fe10 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 27 Jan 2022 14:25:09 +0200 Subject: [PATCH 49/93] [camera_windows] remove need for atl support Replaced with simplified version of CComHeapPtr --- .../camera_windows/windows/CMakeLists.txt | 1 + .../camera_windows/windows/camera_plugin.cpp | 8 +-- .../windows/capture_controller.cpp | 6 +- .../camera_windows/windows/com_heap_ptr.h | 64 +++++++++++++++++++ 4 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 packages/camera/camera_windows/windows/com_heap_ptr.h diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index 4a5bdb7ec892..91c6cefd1db4 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -26,6 +26,7 @@ list(APPEND PLUGIN_SOURCES "record_handler.cpp" "photo_handler.h" "photo_handler.cpp" + "com_heap_ptr.h" ) add_library(${PLUGIN_NAME} SHARED diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index d935fd7756ee..4857f0f0b8e4 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -4,7 +4,6 @@ #include "camera_plugin.h" -#include #include #include #include @@ -19,6 +18,7 @@ #include #include +#include "com_heap_ptr.h" #include "device_info.h" #include "string_utils.h" @@ -91,7 +91,7 @@ ResolutionPreset ParseResolutionPreset(const std::string& resolution_preset) { std::unique_ptr GetDeviceInfo(IMFActivate* device) { assert(device); auto device_info = std::make_unique(); - CComHeapPtr name; + ComHeapPtr name; UINT32 name_size; HRESULT hr = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, @@ -100,7 +100,7 @@ std::unique_ptr GetDeviceInfo(IMFActivate* device) { return device_info; } - CComHeapPtr id; + ComHeapPtr id; UINT32 id_size; hr = device->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, &id_size); @@ -294,7 +294,7 @@ void CameraPlugin::DisposeCameraByCameraId(int64_t camera_id) { void CameraPlugin::AvailableCamerasMethodHandler( std::unique_ptr> result) { // Enumerate devices. - CComHeapPtr devices; + ComHeapPtr devices; UINT32 count = 0; if (!this->EnumerateVideoCaptureDeviceSources(&devices, &count)) { result->Error("System error", "Failed to get available cameras"); diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index b2b333512f33..ecbc9e9a74f4 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -4,7 +4,6 @@ #include "capture_controller.h" -#include #include #include @@ -12,6 +11,7 @@ #include #include +#include "com_heap_ptr.h" #include "photo_handler.h" #include "preview_handler.h" #include "record_handler.h" @@ -56,7 +56,7 @@ bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { audio_source_ = nullptr; - CComHeapPtr devices; + ComHeapPtr devices; UINT32 count = 0; ComPtr attributes; @@ -72,7 +72,7 @@ HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { } if (SUCCEEDED(hr) && count > 0) { - CComHeapPtr audio_device_id; + ComHeapPtr audio_device_id; UINT32 audio_device_id_size; // Use first audio device. diff --git a/packages/camera/camera_windows/windows/com_heap_ptr.h b/packages/camera/camera_windows/windows/com_heap_ptr.h new file mode 100644 index 000000000000..630f4c2d9be9 --- /dev/null +++ b/packages/camera/camera_windows/windows/com_heap_ptr.h @@ -0,0 +1,64 @@ +// 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_COMHEAPPTR_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_COMHEAPPTR_H_ + +#include + +#include + +namespace camera_windows { +// Wrapper for COM object for automatic memory release support +// Destructor uses CoTaskMemFree to release memory allocations. +template +class ComHeapPtr { + public: + ComHeapPtr() : p_obj_(nullptr){}; + ComHeapPtr(T* p_obj) : p_obj_(p_obj){}; + + // Frees memory on destruction. + ~ComHeapPtr() { Free(); }; + + // Prevent copying / ownership transfer as not currently needed. + ComHeapPtr(ComHeapPtr const&) = delete; + ComHeapPtr& operator=(ComHeapPtr const&) = delete; + + // Returns the pointer to the memory. + operator T*() { return p_obj_; }; + + // Returns the pointer to the memory. + T* operator->() { + assert(p_obj_ != nullptr); + return p_obj_; + }; + + // Returns the pointer to the memory. + const T* operator->() const { + assert(p_obj_ != nullptr); + return p_obj_; + }; + + // Returns the pointer to the memory. + T** operator&() { + // Wrapped object must be nullptr to avoid memory leaks. + // Object can be released with Reset(nullptr). + assert(p_obj_ == nullptr); + return &p_obj_; + }; + + // Free the memory pointed to, and set the pointer to nullptr. + void Free() { + if (p_obj_) { + CoTaskMemFree(p_obj_); + } + p_obj_ = nullptr; + } + + private: + // Pointer to memory. + T* p_obj_; +}; +} // namespace camera_windows +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_COMHEAPPTR_H_ From eecaa0d38816d9f8985731ae7a5daa3d3aae92f3 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 27 Jan 2022 15:25:38 +0200 Subject: [PATCH 50/93] [camera_windows] multicamera support for example --- .../camera_windows/example/lib/main.dart | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index e0054c9e47d9..b580b390a7af 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -21,6 +21,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { String _cameraInfo = 'Unknown'; List _cameras = []; + int _cameraIndex = 0; int _cameraId = -1; bool _initialized = false; bool _recording = false; @@ -50,12 +51,14 @@ class _MyAppState extends State { String cameraInfo; List cameras = []; + int cameraIndex = _cameraIndex; try { cameras = await CameraPlatform.instance.availableCameras(); + cameraIndex = cameraIndex % cameras.length; if (cameras.isEmpty) { cameraInfo = 'No available cameras'; } else { - cameraInfo = 'Found camera: ${cameras.first.name}'; + cameraInfo = 'Found camera: ${cameras[cameraIndex].name}'; } } on PlatformException catch (e) { cameraInfo = 'Failed to get cameras: ${e.code}: ${e.message}'; @@ -66,20 +69,22 @@ class _MyAppState extends State { } setState(() { + _cameraIndex = cameraIndex; _cameras = cameras; _cameraInfo = cameraInfo; }); } /// Initializes the camera on the device. - Future _initializeFirstCamera() async { + Future _initializeCamera() async { assert(_cameras.isNotEmpty); assert(!_initialized); final Completer _initializeCompleter = Completer(); int cameraId = -1; try { - final CameraDescription camera = _cameras.first; + final int cameraIndex = _cameraIndex % _cameras.length; + final CameraDescription camera = _cameras[cameraIndex]; cameraId = await CameraPlatform.instance.createCamera( camera, @@ -114,6 +119,7 @@ class _MyAppState extends State { setState(() { _initialized = true; _cameraId = cameraId; + _cameraIndex = cameraIndex; _cameraInfo = 'Capturing camera: ${camera.name}'; }); } on CameraException catch (e) { @@ -153,7 +159,7 @@ class _MyAppState extends State { _recordingTimed = false; _previewPaused = false; }); - _getAvailableCameras(); + await _getAvailableCameras(); } on CameraException catch (e) { setState(() { _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.description}'; @@ -230,6 +236,18 @@ class _MyAppState extends State { } } + Future _switchCamera() async { + if (_cameraId > -1) { + await _disposeCurrentCamera(); + } + if (_cameras.isNotEmpty) { + setState(() { + _cameraIndex = (_cameraIndex + 1) % _cameras.length; + }); + await _initializeCamera(); + } + } + void _onCameraError(CameraErrorEvent event) { _scaffoldMessengerKey.currentState ?.showSnackBar(SnackBar(content: Text('Error: ${event.description}'))); @@ -284,7 +302,7 @@ class _MyAppState extends State { ElevatedButton( onPressed: _initialized ? _disposeCurrentCamera - : _initializeFirstCamera, + : _initializeCamera, child: Text(_initialized ? 'Dispose camera' : 'Create camera'), ), @@ -318,6 +336,15 @@ class _MyAppState extends State { 'Record 5 seconds', ), ), + if (_cameras.length > 1) ...[ + const SizedBox(width: 5), + ElevatedButton( + onPressed: _switchCamera, + child: const Text( + 'Switch camera', + ), + ), + ] ], ), const SizedBox(height: 5), From c10e10110373414ffa56d326729e3ff7efec333d Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 27 Jan 2022 16:48:55 +0200 Subject: [PATCH 51/93] [camera_windows] fix media type for preview --- .../camera/camera_windows/windows/capture_controller.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index ecbc9e9a74f4..850ca83bf89f 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -198,6 +198,7 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine() { return hr; } + // Creates video source only if not already initialized by test framework if (!video_source_) { hr = CreateVideoCaptureSourceForDevice(video_device_id_); if (FAILED(hr)) { @@ -205,6 +206,7 @@ HRESULT CaptureControllerImpl::CreateCaptureEngine() { } } + // Creates audio source only if not already initialized by test framework if (record_audio_ && !audio_source_) { hr = CreateDefaultAudioCaptureSource(); if (FAILED(hr)) { @@ -471,11 +473,11 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { } // Find base media type for previewing. - uint32_t max_preview_height = GetMaxPreviewHeight(); if (!FindBestMediaType( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, source.Get(), base_preview_media_type_.GetAddressOf(), - max_preview_height, &preview_frame_width_, &preview_frame_height_)) { + GetMaxPreviewHeight(), &preview_frame_width_, + &preview_frame_height_)) { return E_FAIL; } @@ -595,7 +597,7 @@ void CaptureControllerImpl::StartPreview() { // Check MF_CAPTURE_ENGINE_PREVIEW_STARTED event handling for response // process. if (!preview_handler_->StartPreview(capture_engine_.Get(), - base_capture_media_type_.Get(), + base_preview_media_type_.Get(), capture_engine_callback_handler_.Get())) { // Destroy preview handler on error cases to make sure state is resetted. preview_handler_ = nullptr; From ac273f63a6c19bfda9f84793b731e77a17ad7779 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 27 Jan 2022 17:31:50 +0200 Subject: [PATCH 52/93] [camera_windows] use constexpr --- .../camera/camera_windows/windows/camera.cpp | 7 +-- .../camera_windows/windows/camera_plugin.cpp | 50 +++++++++---------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index c37bf8caa42b..5dcf324327ea 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -10,9 +10,10 @@ using flutter::EncodableMap; using flutter::EncodableValue; // Camera channel events. -const char kCameraMethodChannelBaseName[] = "flutter.io/cameraPlugin/camera"; -const char kVideoRecordedEvent[] = "video_recorded"; -const char kErrorEvent[] = "error"; +constexpr char kCameraMethodChannelBaseName[] = + "flutter.io/cameraPlugin/camera"; +constexpr char kVideoRecordedEvent[] = "video_recorded"; +constexpr char kErrorEvent[] = "error"; CameraImpl::CameraImpl(const std::string& device_id) : device_id_(device_id), Camera(device_id) {} diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 4857f0f0b8e4..657db51df9db 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -30,31 +30,31 @@ using flutter::EncodableValue; namespace { // Channel events -const char kChannelName[] = "plugins.flutter.io/camera"; - -const char kAvailableCamerasMethod[] = "availableCameras"; -const char kCreateMethod[] = "create"; -const char kInitializeMethod[] = "initialize"; -const char kTakePictureMethod[] = "takePicture"; -const char kStartVideoRecordingMethod[] = "startVideoRecording"; -const char kStopVideoRecordingMethod[] = "stopVideoRecording"; -const char kPausePreview[] = "pausePreview"; -const char kResumePreview[] = "resumePreview"; -const char kDisposeMethod[] = "dispose"; - -const char kCameraNameKey[] = "cameraName"; -const char kResolutionPresetKey[] = "resolutionPreset"; -const char kEnableAudioKey[] = "enableAudio"; - -const char kCameraIdKey[] = "cameraId"; -const char kMaxVideoDurationKey[] = "maxVideoDuration"; - -const char kResolutionPresetValueLow[] = "low"; -const char kResolutionPresetValueMedium[] = "medium"; -const char kResolutionPresetValueHigh[] = "high"; -const char kResolutionPresetValueVeryHigh[] = "veryHigh"; -const char kResolutionPresetValueUltraHigh[] = "ultraHigh"; -const char kResolutionPresetValueMax[] = "max"; +constexpr char kChannelName[] = "plugins.flutter.io/camera"; + +constexpr char kAvailableCamerasMethod[] = "availableCameras"; +constexpr char kCreateMethod[] = "create"; +constexpr char kInitializeMethod[] = "initialize"; +constexpr char kTakePictureMethod[] = "takePicture"; +constexpr char kStartVideoRecordingMethod[] = "startVideoRecording"; +constexpr char kStopVideoRecordingMethod[] = "stopVideoRecording"; +constexpr char kPausePreview[] = "pausePreview"; +constexpr char kResumePreview[] = "resumePreview"; +constexpr char kDisposeMethod[] = "dispose"; + +constexpr char kCameraNameKey[] = "cameraName"; +constexpr char kResolutionPresetKey[] = "resolutionPreset"; +constexpr char kEnableAudioKey[] = "enableAudio"; + +constexpr char kCameraIdKey[] = "cameraId"; +constexpr char kMaxVideoDurationKey[] = "maxVideoDuration"; + +constexpr char kResolutionPresetValueLow[] = "low"; +constexpr char kResolutionPresetValueMedium[] = "medium"; +constexpr char kResolutionPresetValueHigh[] = "high"; +constexpr char kResolutionPresetValueVeryHigh[] = "veryHigh"; +constexpr char kResolutionPresetValueUltraHigh[] = "ultraHigh"; +constexpr char kResolutionPresetValueMax[] = "max"; const std::string kPictureCaptureExtension = "jpeg"; const std::string kVideoCaptureExtension = "mp4"; From c86970c6de1f6c168957c46da4d40de4229e4ea7 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 27 Jan 2022 17:32:11 +0200 Subject: [PATCH 53/93] [camera_windows] add resolution presets to example app --- .../camera_windows/example/lib/main.dart | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index b580b390a7af..f4815cb8bd04 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -29,6 +29,7 @@ class _MyAppState extends State { bool _recordAudio = true; bool _previewPaused = false; Size? _previewSize; + ResolutionPreset _resolutionPreset = ResolutionPreset.max; StreamSubscription? _errorStreamSubscription; @override @@ -88,7 +89,7 @@ class _MyAppState extends State { cameraId = await CameraPlatform.instance.createCamera( camera, - ResolutionPreset.veryHigh, + _resolutionPreset, enableAudio: _recordAudio, ); @@ -224,7 +225,7 @@ class _MyAppState extends State { } Future _togglePreview() async { - if (_initialized && _cameraId > 0) { + if (_initialized && _cameraId >= 0) { if (!_previewPaused) { await CameraPlatform.instance.pausePreview(_cameraId); } else { @@ -237,13 +238,36 @@ class _MyAppState extends State { } Future _switchCamera() async { - if (_cameraId > -1) { + _cameraIndex = (_cameraIndex + 1) % _cameras.length; + if (_initialized && _cameraId >= 0) { await _disposeCurrentCamera(); + if (_cameras.isNotEmpty) { + await _initializeCamera(); + } + } else { + await _getAvailableCameras(); } - if (_cameras.isNotEmpty) { - setState(() { - _cameraIndex = (_cameraIndex + 1) % _cameras.length; - }); + } + + Future _onResolutionChange(ResolutionPreset? newValue) async { + if (newValue == null) { + return; + } + setState(() { + _resolutionPreset = newValue; + }); + if (_initialized && _cameraId >= 0) { + await _disposeCurrentCamera(); + await _initializeCamera(); + } + } + + Future _onAudioChange(bool recordAudio) async { + setState(() { + _recordAudio = recordAudio; + }); + if (_initialized && _cameraId >= 0) { + await _disposeCurrentCamera(); await _initializeCamera(); } } @@ -254,8 +278,10 @@ class _MyAppState extends State { } void _showInSnackBar(String message) { - _scaffoldMessengerKey.currentState - ?.showSnackBar(SnackBar(content: Text(message))); + _scaffoldMessengerKey.currentState?.showSnackBar(SnackBar( + content: Text(message), + duration: const Duration(seconds: 1), + )); } final GlobalKey _scaffoldMessengerKey = @@ -263,6 +289,15 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { + final List> resolutionItems = + ResolutionPreset.values + .map>((ResolutionPreset value) { + return DropdownMenuItem( + value: value, + child: Text(value.toString()), + ); + }).toList(); + return MaterialApp( scaffoldMessengerKey: _scaffoldMessengerKey, home: Scaffold( @@ -287,17 +322,19 @@ class _MyAppState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ + DropdownButton( + value: _resolutionPreset, + onChanged: (ResolutionPreset? value) => + _onResolutionChange(value), + items: resolutionItems, + ), + const SizedBox(width: 20), const Text( 'Audio:', ), Switch( - value: _recordAudio, - onChanged: !_initialized - ? (bool state) => setState(() { - _recordAudio = state; - }) - : null, - ), + value: _recordAudio, + onChanged: (bool state) => _onAudioChange(state)), const SizedBox(width: 20), ElevatedButton( onPressed: _initialized From 5aa5dcde2a687b612076da51a0f62ffda329abfb Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 27 Jan 2022 17:33:24 +0200 Subject: [PATCH 54/93] [camera_windows] fixes to comments - avoid describing private fields on comments --- .../camera/camera_windows/windows/camera.h | 8 ++++---- .../camera_windows/windows/camera_plugin.h | 19 +++++++++---------- .../windows/capture_controller.h | 2 +- .../windows/capture_engine_listener.h | 2 +- .../camera_windows/windows/photo_handler.h | 11 ++++++----- .../camera_windows/windows/preview_handler.h | 17 ++++++++--------- .../camera_windows/windows/record_handler.h | 8 ++++---- 7 files changed, 33 insertions(+), 34 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 6efe289c9dca..2a1aa4d1db09 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -43,8 +43,8 @@ class Camera : public CaptureControllerListener { // Tests if current camera has given camera id. virtual bool HasCameraId(int64_t camera_id) = 0; - // Adds pending result to the pending_results map. - // Calls method result error handler, if result already exists. + // Adds a pending result. + // Returns an error result if the result has already been added. virtual bool AddPendingResult(PendingResultType type, std::unique_ptr> result) = 0; @@ -116,7 +116,7 @@ class CameraImpl : public Camera { ResolutionPreset resolution_preset); private: - // Loops through all pending results calls their + // Loops through all pending results and calls their // error handler with given error id and description. // Pending results are cleared in the process. // @@ -128,7 +128,7 @@ class CameraImpl : public Camera { // Initializes method channel instance and returns pointer it MethodChannel<>* GetMethodChannel(); - // Finds pending result from pending_results map by type. + // Finds pending result by type. // Returns nullptr if type is not present. std::unique_ptr> GetPendingResultByType( PendingResultType type); diff --git a/packages/camera/camera_windows/windows/camera_plugin.h b/packages/camera/camera_windows/windows/camera_plugin.h index 9350645eea09..1baa2477beb5 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.h +++ b/packages/camera/camera_windows/windows/camera_plugin.h @@ -62,7 +62,7 @@ class CameraPlugin : public flutter::Plugin, // Disposes camera by camera id. void DisposeCameraByCameraId(int64_t camera_id); - // Enumerates video capture devices via static CameraController method. + // Enumerates video capture devices. bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) override; @@ -73,45 +73,44 @@ class CameraPlugin : public flutter::Plugin, std::unique_ptr> result); // Handles create method calls. - // Creates and initializes capture controller - // and MFCaptureEngine for requested device. - // Stores MethodResult object to be handled after request is processed. + // Creates camera and initializes capture controller for requested device. + // Stores result object to be handled after request is processed. void CreateMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles initialize method calls. // Requests existing camera controller to start preview. - // Stores MethodResult object to be handled after request is processed. + // Stores result object to be handled after request is processed. void InitializeMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles takePicture method calls. // Requests existing camera controller to take photo. - // Stores MethodResult object to be handled after request is processed. + // Stores result object to be handled after request is processed. void TakePictureMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles startVideoRecording method calls. // Requests existing camera controller to start recording. - // Stores MethodResult object to be handled after request is processed. + // Stores result object to be handled after request is processed. void StartVideoRecordingMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles stopVideoRecording method calls. // Requests existing camera controller to stop recording. - // Stores MethodResult object to be handled after request is processed. + // Stores result object to be handled after request is processed. void StopVideoRecordingMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles pausePreview method calls. // Requests existing camera controller to pause recording. - // Stores MethodResult object to be handled after request is processed. + // Stores result object to be handled after request is processed. void PausePreviewMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles resumePreview method calls. // Requests existing camera controller to resume preview. - // Stores MethodResult object to be handled after request is processed. + // Stores result object to be handled after request is processed. void ResumePreviewMethodHandler(const EncodableMap& args, std::unique_ptr> result); diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 849f70d8ed1e..2dd215554290 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -239,7 +239,7 @@ class CaptureControllerImpl : public CaptureController, // Handles record stopped events. void OnRecordStopped(bool success, const std::string& error); - // Converts local pixel buffer to flutter pixel buffer + // Converts local pixel buffer to flutter pixel buffer. const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, size_t height); diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index aec6b5bdcf1f..340598a4a70b 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -18,7 +18,7 @@ class CaptureEngineObserver { // Returns true if sample can be processed. virtual bool IsReadyForSample() = 0; - // Handles Capture Engine media events. + // Handles CaptureEngine media events. virtual void OnEvent(IMFMediaEvent* event) = 0; // Returns frame buffer target for new capture sample. diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index a47b1e8cb6ce..80660477f57e 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -32,8 +32,9 @@ class PhotoHandler { PhotoHandler(PhotoHandler const&) = delete; PhotoHandler& operator=(PhotoHandler const&) = delete; - // Initializes photo sink if not initialized and requests capture engine to - // start photoing. Sets photo state to PHOTO_STATE__TAKING. + // Initializes photo sink if not initialized and requests + // capture engine to take photo. + // Sets photo state to: taking. // Returns false if photo cannot be taken. // // capture_engine: A pointer to capture engine instance. @@ -44,15 +45,15 @@ class PhotoHandler { bool TakePhoto(const std::string& file_path, IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type); - // Set the photo handler recording state to PHOTO_STATE__IDLE. + // Set the photo handler recording state to: idle. void OnPhotoTaken(); - // Returns true if photo state is PHOTO_STATE__IDLE + // Returns true if photo state is idle bool IsInitialized() { return photo_state_ == PhotoState::PHOTO_STATE__IDLE; }; - // Returns true if photo state is PHOTO_STATE__TAKING. + // Returns true if photo state is taking. bool IsTakingPhoto() { return photo_state_ == PhotoState::PHOTO_STATE__TAKING; }; diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h index 66e692ae27b2..b009f73ff1bd 100644 --- a/packages/camera/camera_windows/windows/preview_handler.h +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -35,7 +35,7 @@ class PreviewHandler { PreviewHandler& operator=(PreviewHandler const&) = delete; // Initializes preview sink and requests capture engine to start previewing. - // Sets preview state to PREVIEW_STATE__STARTING. + // Sets preview state to: starting. // Returns false if recording cannot be started. // // capture_engine: A pointer to capture engine instance. Used to start @@ -55,33 +55,32 @@ class PreviewHandler { // the ongoing recording. bool StopPreview(IMFCaptureEngine* capture_engine); - // Set the preview handler recording state to PREVIEW_STATE__PAUSED. + // Set the preview handler recording state to: paused. bool PausePreview(); - // Set the preview handler recording state to PREVIEW_STATE__RUNNING. + // Set the preview handler recording state to: running. bool ResumePreview(); - // Set the preview handler recording state to PREVIEW_STATE__RUNNING. + // Set the preview handler recording state to: running. void OnPreviewStarted(); - // Returns true if preview state is PREVIEW_STATE__RUNNING or - // PREVIEW_STATE__PAUSED. + // Returns true if preview state is running or paused. bool IsInitialized() { return preview_state_ == PreviewState::PREVIEW_STATE__RUNNING && preview_state_ == PreviewState::PREVIEW_STATE__PAUSED; }; - // Returns true if preview state is PREVIEW_STATE__RUNNING. + // Returns true if preview state is running. bool IsRunning() { return preview_state_ == PreviewState::PREVIEW_STATE__RUNNING; }; - // Return true if preview state is PREVIEW_STATE__PAUSED + // Return true if preview state is paused. bool IsPaused() { return preview_state_ == PreviewState::PREVIEW_STATE__PAUSED; }; - // Returns true if preview state is PREVIEW_STATE__STARTING. + // Returns true if preview state is starting. bool IsStarting() { return preview_state_ == PreviewState::PREVIEW_STATE__STARTING; }; diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index 37b24fe8ae9f..268e66547959 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -34,7 +34,7 @@ class RecordHandler { RecordHandler& operator=(RecordHandler const&) = delete; // Initializes record sink and requests capture engine to start recording. - // Sets record state to RECORD_STATE__STARTING. + // Sets record state to: starting. // Returns false if recording cannot be started. // // file_path: A string that hold file path for video capture. @@ -56,11 +56,11 @@ class RecordHandler { // the ongoing recording. bool StopRecord(IMFCaptureEngine* capture_engine); - // Set the record handler recording state to RECORD_STATE__RUNNING. + // Set the record handler recording state to: running. void OnRecordStarted(); - // Resets the record handler state and sets recording state to - // RECORD_STATE__NOT_STARTED. + // Resets the record handler state and + // sets recording state to: not started. void OnRecordStopped(); // Returns true if recording type is continuous recording. From 44855d95fcbfaacb007e13bca72440d4855ea3db Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 28 Jan 2022 13:48:48 +0200 Subject: [PATCH 55/93] [camera_windows] prefer best framerate --- .../camera_windows/example/lib/main.dart | 2 +- .../windows/capture_controller.cpp | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index f4815cb8bd04..a74f5eeec4bb 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -29,7 +29,7 @@ class _MyAppState extends State { bool _recordAudio = true; bool _previewPaused = false; Size? _previewSize; - ResolutionPreset _resolutionPreset = ResolutionPreset.max; + ResolutionPreset _resolutionPreset = ResolutionPreset.veryHigh; StreamSubscription? _errorStreamSubscription; @override diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 850ca83bf89f..b6631e5d6836 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -425,12 +425,14 @@ uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { bool FindBestMediaType(DWORD source_stream_index, IMFCaptureSource* source, IMFMediaType** target_media_type, uint32_t max_height, uint32_t* target_frame_width, - uint32_t* target_frame_height) { + uint32_t* target_frame_height, + float minimum_accepted_framerate = 15.f) { assert(source); ComPtr media_type; uint32_t best_width = 0; uint32_t best_height = 0; + float best_framerate = 0.f; // Loop native media types. for (int i = 0;; i++) { @@ -439,16 +441,32 @@ bool FindBestMediaType(DWORD source_stream_index, IMFCaptureSource* source, break; } + uint32_t frame_rate_numerator, frame_rate_denominator; + if (FAILED(MFGetAttributeRatio(media_type.Get(), MF_MT_FRAME_RATE, + &frame_rate_numerator, + &frame_rate_denominator)) || + !frame_rate_denominator) { + continue; + } + + float frame_rate = + static_cast(frame_rate_numerator) / frame_rate_denominator; + if (frame_rate < minimum_accepted_framerate) { + continue; + } + uint32_t frame_width; uint32_t frame_height; if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, &frame_width, &frame_height))) { // Update target mediatype if (frame_height <= max_height && - (best_width < frame_width || best_height < frame_height)) { + (best_width < frame_width || best_height < frame_height || + best_framerate < frame_rate)) { media_type.CopyTo(target_media_type); best_width = frame_width; best_height = frame_height; + best_framerate = frame_rate; } } } From de9367e8367af76db3068fc80ec5fcc312380255 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 28 Jan 2022 15:18:49 +0200 Subject: [PATCH 56/93] [camera_windows] mock frame rate --- packages/camera/camera_windows/windows/test/mocks.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 85b9d478b121..f0e578590184 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -837,6 +837,9 @@ class FakeMediaType : public FakeIMFAttributesBase { if (key == MF_MT_FRAME_SIZE) { *value = (int64_t)width_ << 32 | (int64_t)height_; return S_OK; + } else if (key == MF_MT_FRAME_RATE) { + *value = (int64_t)frame_rate_ << 32 | 1; + return S_OK; } return E_FAIL; }; @@ -857,6 +860,7 @@ class FakeMediaType : public FakeIMFAttributesBase { HRESULT CopyAllItems(IMFAttributes* pDest) override { pDest->SetUINT64(MF_MT_FRAME_SIZE, (int64_t)width_ << 32 | (int64_t)height_); + pDest->SetUINT64(MF_MT_FRAME_RATE, (int64_t)frame_rate_ << 32 | 1); pDest->SetGUID(MF_MT_MAJOR_TYPE, major_type_); pDest->SetGUID(MF_MT_SUBTYPE, sub_type_); return S_OK; @@ -922,6 +926,7 @@ class FakeMediaType : public FakeIMFAttributesBase { const GUID sub_type_; const int width_; const int height_; + const int frame_rate_ = 30; }; class MockCaptureEngine : public IMFCaptureEngine { From 9b4399fb21d60db957c0593f022baeb97cd603d2 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 28 Jan 2022 15:42:46 +0200 Subject: [PATCH 57/93] [camera_windows] Add AUTHORS file --- packages/camera/camera_windows/AUTHORS | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 packages/camera/camera_windows/AUTHORS diff --git a/packages/camera/camera_windows/AUTHORS b/packages/camera/camera_windows/AUTHORS new file mode 100644 index 000000000000..b2178a5e8444 --- /dev/null +++ b/packages/camera/camera_windows/AUTHORS @@ -0,0 +1,8 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. +Joonas Kerttula +Codemate Ltd. From d151466562918836441a40d242bdc28aa9fd7cb3 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 28 Jan 2022 17:29:22 +0200 Subject: [PATCH 58/93] [camera_windows] separate texture handler --- .../camera_windows/windows/CMakeLists.txt | 2 + .../camera/camera_windows/windows/camera.h | 5 +- .../windows/capture_controller.cpp | 97 +++++------------ .../windows/capture_controller.h | 49 ++------- .../windows/capture_engine_listener.cpp | 8 +- .../windows/capture_engine_listener.h | 10 +- .../windows/test/capture_controller_test.cpp | 4 +- .../camera_windows/windows/test/mocks.h | 1 - .../windows/texture_handler.cpp | 102 ++++++++++++++++++ .../camera_windows/windows/texture_handler.h | 77 +++++++++++++ 10 files changed, 226 insertions(+), 129 deletions(-) create mode 100644 packages/camera/camera_windows/windows/texture_handler.cpp create mode 100644 packages/camera/camera_windows/windows/texture_handler.h diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index 91c6cefd1db4..a71a4cbabbab 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -26,6 +26,8 @@ list(APPEND PLUGIN_SOURCES "record_handler.cpp" "photo_handler.h" "photo_handler.cpp" + "texture_handler.h" + "texture_handler.cpp" "com_heap_ptr.h" ) diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 2a1aa4d1db09..9c9715dc1210 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -70,7 +70,7 @@ class CameraImpl : public Camera { CameraImpl(const CameraImpl&) = delete; CameraImpl& operator=(const CameraImpl&) = delete; - // From CaptureControllerListener. + // CaptureControllerListener void OnCreateCaptureEngineSucceeded(int64_t texture_id) override; void OnCreateCaptureEngineFailed(const std::string& error) override; void OnStartPreviewSucceeded(int32_t width, int32_t height) override; @@ -90,7 +90,7 @@ class CameraImpl : public Camera { void OnVideoRecordFailed(const std::string& error) override; void OnCaptureError(const std::string& error) override; - // From Camera. + // Camera bool HasDeviceId(std::string& device_id) override { return device_id_ == device_id; }; @@ -150,6 +150,7 @@ class CameraFactory { CameraFactory(const CameraFactory&) = delete; CameraFactory& operator=(const CameraFactory&) = delete; + // Creates camera for given device id. virtual std::unique_ptr CreateCamera( const std::string& device_id) = 0; }; diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index b6631e5d6836..02be53fc5733 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -16,6 +16,7 @@ #include "preview_handler.h" #include "record_handler.h" #include "string_utils.h" +#include "texture_handler.h" namespace camera_windows { @@ -265,9 +266,6 @@ void CaptureControllerImpl::ResetCaptureController() { // States media_foundation_started_ = false; capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; - record_handler_ = nullptr; - preview_handler_ = nullptr; - photo_handler_ = nullptr; preview_frame_width_ = 0; preview_frame_height_ = 0; capture_engine_callback_handler_ = nullptr; @@ -284,12 +282,10 @@ void CaptureControllerImpl::ResetCaptureController() { dxgi_device_manager_ = nullptr; dx11_device_ = nullptr; - // Texture - if (texture_registrar_ && texture_id_ > -1) { - texture_registrar_->UnregisterTexture(texture_id_); - } - texture_id_ = -1; - texture_ = nullptr; + record_handler_ = nullptr; + preview_handler_ = nullptr; + photo_handler_ = nullptr; + texture_handler_ = nullptr; } void CaptureControllerImpl::InitCaptureDevice( @@ -297,8 +293,7 @@ void CaptureControllerImpl::InitCaptureDevice( bool record_audio, ResolutionPreset resolution_preset) { assert(capture_controller_listener_); - if (capture_engine_state_ == CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && - texture_id_ >= 0) { + if (capture_engine_state_ == CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { return capture_controller_listener_->OnCreateCaptureEngineFailed( "Capture device already initialized"); } else if (capture_engine_state_ == @@ -336,35 +331,6 @@ void CaptureControllerImpl::InitCaptureDevice( } } -const FlutterDesktopPixelBuffer* -CaptureControllerImpl::ConvertPixelBufferForFlutter(size_t target_width, - size_t target_height) { - if (this->source_buffer_ && this->source_buffer_size_ > 0 && - this->preview_frame_width_ > 0 && this->preview_frame_height_ > 0) { - uint32_t pixels_total = - this->preview_frame_width_ * this->preview_frame_height_; - dest_buffer_ = std::make_unique(pixels_total * 4); - - MFVideoFormatRGB32Pixel* src = - (MFVideoFormatRGB32Pixel*)this->source_buffer_.get(); - FlutterDesktopPixel* dst = (FlutterDesktopPixel*)dest_buffer_.get(); - - for (uint32_t i = 0; i < pixels_total; i++) { - dst[i].r = src[i].r; - dst[i].g = src[i].g; - dst[i].b = src[i].b; - dst[i].a = 255; - } - - // TODO: add release_callback and clear dest_buffer after each frame. - this->flutter_desktop_pixel_buffer_.buffer = dest_buffer_.get(); - this->flutter_desktop_pixel_buffer_.width = this->preview_frame_width_; - this->flutter_desktop_pixel_buffer_.height = this->preview_frame_height_; - return &this->flutter_desktop_pixel_buffer_; - } - return nullptr; -} - void CaptureControllerImpl::TakePicture(const std::string file_path) { assert(capture_engine_callback_handler_); assert(capture_engine_); @@ -590,8 +556,10 @@ void CaptureControllerImpl::StopTimedRecord() { void CaptureControllerImpl::StartPreview() { assert(capture_engine_callback_handler_); assert(capture_engine_); + assert(texture_handler_); - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { + if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED || + !texture_handler_) { return OnPreviewStarted(false, "Camera not initialized. Camera should be " "disposed and reinitialized."); @@ -604,6 +572,9 @@ void CaptureControllerImpl::StartPreview() { } } + texture_handler_->UpdateTextureSize(preview_frame_width_, + preview_frame_height_); + if (!preview_handler_) { preview_handler_ = std::make_unique(); } else if (preview_handler_->IsInitialized()) { @@ -749,19 +720,12 @@ void CaptureControllerImpl::OnPicture(bool success, const std::string& error) { void CaptureControllerImpl::OnCaptureEngineInitialized( bool success, const std::string& error) { if (capture_controller_listener_) { - // Create flutter desktop pixelbuffer texture; - texture_ = - std::make_unique(flutter::PixelBufferTexture( - [this](size_t width, - size_t height) -> const FlutterDesktopPixelBuffer* { - return this->ConvertPixelBufferForFlutter(width, height); - })); - - auto new_texture_id = texture_registrar_->RegisterTexture(texture_.get()); - - if (new_texture_id >= 0) { - texture_id_ = new_texture_id; - capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id_); + // Create texture handler and register new texture. + texture_handler_ = std::make_unique(texture_registrar_); + + int64_t texture_id = texture_handler_->RegisterTexture(); + if (texture_id >= 0) { + capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id); capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_INITIALIZED; } else { capture_controller_listener_->OnCreateCaptureEngineFailed( @@ -861,26 +825,15 @@ void CaptureControllerImpl::OnRecordStopped(bool success, } } -// Returns pointer to databuffer. +// Updates texture handlers buffer with given data. // Called via IMFCaptureEngineOnSampleCallback implementation. -// Implements CaptureEngineObserver::GetFrameBuffer. -uint8_t* CaptureControllerImpl::GetFrameBuffer(uint32_t new_length) { - if (this->source_buffer_ == nullptr || - this->source_buffer_size_ != new_length) { - // Update source buffer size. - this->source_buffer_ = std::make_unique(new_length); - this->source_buffer_size_ = new_length; - } - return this->source_buffer_.get(); -} - -// Marks texture frame available after buffer is updated. -// Called via IMFCaptureEngineOnSampleCallback implementation. -// Implements CaptureEngineObserver::OnBufferUpdated. -void CaptureControllerImpl::OnBufferUpdated() { - if (this->texture_registrar_ && this->texture_id_ >= 0) { - this->texture_registrar_->MarkTextureFrameAvailable(this->texture_id_); +// Implements CaptureEngineObserver::UpdateBuffer. +bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer, + uint32_t data_length) { + if (!texture_handler_) { + return false; } + return texture_handler_->UpdateBuffer(buffer, data_length); } // Handles capture time update from each processed frame. diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 2dd215554290..c9d72a20703b 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -22,24 +22,12 @@ #include "photo_handler.h" #include "preview_handler.h" #include "record_handler.h" +#include "texture_handler.h" namespace camera_windows { +using flutter::TextureRegistrar; using Microsoft::WRL::ComPtr; -struct FlutterDesktopPixel { - BYTE r = 0; - BYTE g = 0; - BYTE b = 0; - BYTE a = 0; -}; - -struct MFVideoFormatRGB32Pixel { - BYTE b = 0; - BYTE g = 0; - BYTE r = 0; - BYTE x = 0; -}; - enum ResolutionPreset { // AUTO RESOLUTION_PRESET_AUTO, @@ -94,14 +82,11 @@ class CaptureController { // record_audio: A boolean value telling if audio should be captured on // video recording. // resolution_preset: Maximum capture resolution height. - virtual void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, + virtual void InitCaptureDevice(TextureRegistrar* texture_registrar, const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset) = 0; - // Returns texture id for preview - virtual int64_t GetTextureId() = 0; - // Returns preview frame width virtual uint32_t GetPreviewWidth() = 0; @@ -145,11 +130,10 @@ class CaptureControllerImpl : public CaptureController, CaptureControllerImpl(const CaptureControllerImpl&) = delete; CaptureControllerImpl& operator=(const CaptureControllerImpl&) = delete; - // From CaptureController - void InitCaptureDevice(flutter::TextureRegistrar* texture_registrar, + // CaptureController + void InitCaptureDevice(TextureRegistrar* texture_registrar, const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset) override; - int64_t GetTextureId() override { return texture_id_; } uint32_t GetPreviewWidth() override { return preview_frame_width_; } uint32_t GetPreviewHeight() override { return preview_frame_height_; } void StartPreview() override; @@ -160,15 +144,14 @@ class CaptureControllerImpl : public CaptureController, void StopRecord() override; void TakePicture(const std::string file_path) override; - // From CaptureEngineObserver. + // CaptureEngineObserver + void OnEvent(IMFMediaEvent* event) override; bool IsReadyForSample() override { return capture_engine_state_ == CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && preview_handler_ && preview_handler_->IsRunning(); } - void OnEvent(IMFMediaEvent* event) override; - uint8_t* GetFrameBuffer(uint32_t current_length) override; - void OnBufferUpdated() override; + bool UpdateBuffer(uint8_t* data, uint32_t data_length) override; void UpdateCaptureTime(uint64_t capture_time) override; // Sets capture engine, for testing purposes. @@ -239,10 +222,6 @@ class CaptureControllerImpl : public CaptureController, // Handles record stopped events. void OnRecordStopped(bool success, const std::string& error); - // Converts local pixel buffer to flutter pixel buffer. - const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, - size_t height); - bool media_foundation_started_ = false; bool record_audio_ = false; uint32_t preview_frame_width_ = 0; @@ -251,7 +230,9 @@ class CaptureControllerImpl : public CaptureController, std::unique_ptr record_handler_ = nullptr; std::unique_ptr preview_handler_ = nullptr; std::unique_ptr photo_handler_ = nullptr; + std::unique_ptr texture_handler_ = nullptr; CaptureControllerListener* capture_controller_listener_ = nullptr; + std::string video_device_id_; CaptureEngineState capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; @@ -266,15 +247,7 @@ class CaptureControllerImpl : public CaptureController, ComPtr video_source_; ComPtr audio_source_; - // Texture - int64_t texture_id_ = -1; - uint32_t bytes_per_pixel_ = 4; - uint32_t source_buffer_size_ = 0; - std::unique_ptr source_buffer_ = nullptr; - std::unique_ptr dest_buffer_ = nullptr; - std::unique_ptr texture_; - flutter::TextureRegistrar* texture_registrar_ = nullptr; - FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; + TextureRegistrar* texture_registrar_ = nullptr; }; class CaptureControllerFactory { diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp index f3c63a3d2aab..a79da9cde4ea 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.cpp +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -79,15 +79,9 @@ HRESULT CaptureEngineListener::OnSample(IMFSample* sample) { DWORD current_length = 0; uint8_t* data; if (SUCCEEDED(buffer->Lock(&data, &max_length, ¤t_length))) { - uint8_t* src_buffer = this->observer_->GetFrameBuffer(current_length); - if (src_buffer) { - std::copy(data, data + current_length, src_buffer); - } + this->observer_->UpdateBuffer(data, current_length); } hr = buffer->Unlock(); - if (SUCCEEDED(hr)) { - this->observer_->OnBufferUpdated(); - } } } return hr; diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 340598a4a70b..8ece3c7a1d42 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -18,15 +18,11 @@ class CaptureEngineObserver { // Returns true if sample can be processed. virtual bool IsReadyForSample() = 0; - // Handles CaptureEngine media events. + // Handles Capture Engine media events. virtual void OnEvent(IMFMediaEvent* event) = 0; - // Returns frame buffer target for new capture sample. - virtual uint8_t* GetFrameBuffer(uint32_t new_length) = 0; - - // Handles buffer update events. - // Informs texture registrar of the new frame. - virtual void OnBufferUpdated() = 0; + // Updates texture buffer + virtual bool UpdateBuffer(uint8_t* data, uint32_t new_length) = 0; // Handles capture timestamps updates. // Used to stop timed recordings when recorded time is exceeded. 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 1e4f1aa323ba..afaf25030fc3 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -60,7 +60,7 @@ void MockInitCaptureController(CaptureControllerImpl* capture_controller, EXPECT_CALL(*engine, Initialize).Times(1); capture_controller->InitCaptureDevice( - texture_registrar, MOCK_DEVICE_ID, false, + texture_registrar, MOCK_DEVICE_ID, true, ResolutionPreset::RESOLUTION_PRESET_AUTO); // MockCaptureEngine::Initialize is called @@ -184,7 +184,7 @@ void MockRecordStart(CaptureControllerImpl* capture_controller, }); EXPECT_CALL(*record_sink, RemoveAllStreams).Times(1).WillOnce(Return(S_OK)); - EXPECT_CALL(*record_sink, AddStream).Times(1).WillOnce(Return(S_OK)); + EXPECT_CALL(*record_sink, AddStream).Times(2).WillRepeatedly(Return(S_OK)); EXPECT_CALL(*record_sink, SetOutputFileName).Times(1).WillOnce(Return(S_OK)); capture_controller->StartRecord(mock_path_to_video, -1); diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index f0e578590184..52ddad89941d 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -218,7 +218,6 @@ class MockCaptureController : public CaptureController { ResolutionPreset resolution_preset), (override)); - MOCK_METHOD(int64_t, GetTextureId, (), (override)); MOCK_METHOD(uint32_t, GetPreviewWidth, (), (override)); MOCK_METHOD(uint32_t, GetPreviewHeight, (), (override)); diff --git a/packages/camera/camera_windows/windows/texture_handler.cpp b/packages/camera/camera_windows/windows/texture_handler.cpp new file mode 100644 index 000000000000..d983804c3d19 --- /dev/null +++ b/packages/camera/camera_windows/windows/texture_handler.cpp @@ -0,0 +1,102 @@ +// 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 "texture_handler.h" + +#include + +namespace camera_windows { + +TextureHandler::~TextureHandler() { + if (texture_registrar_ && texture_id_ > -1) { + texture_registrar_->UnregisterTexture(texture_id_); + } + texture_id_ = -1; + texture_registrar_ = nullptr; + texture_ = nullptr; + dest_buffer_ = nullptr; + source_buffer_ = nullptr; +} + +int64_t TextureHandler::RegisterTexture() { + if (!texture_registrar_) { + return -1; + } + + // Create flutter desktop pixelbuffer texture; + texture_ = + std::make_unique(flutter::PixelBufferTexture( + [this](size_t width, + size_t height) -> const FlutterDesktopPixelBuffer* { + return this->ConvertPixelBufferForFlutter(width, height); + })); + + texture_id_ = texture_registrar_->RegisterTexture(texture_.get()); + return texture_id_; +} + +bool TextureHandler::UpdateBuffer(uint8_t* data, uint32_t data_length) { + // Scoped lock guard. + { + const std::lock_guard lock(source_buffer_mutex); + if (!texture_registrar_ || texture_id_ == -1) { + return false; + } + + if (source_buffer_ == nullptr || source_buffer_size_ != data_length) { + // Update source buffer size. + source_buffer_ = std::make_unique(data_length); + source_buffer_size_ = data_length; + } + std::copy(data, data + data_length, source_buffer_.get()); + } + OnBufferUpdated(); + return true; +}; + +// Marks texture frame available after buffer is updated. +void TextureHandler::OnBufferUpdated() { + if (texture_registrar_ && texture_id_ > -1) { + texture_registrar_->MarkTextureFrameAvailable(texture_id_); + } +} + +const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( + size_t target_width, size_t target_height) { + // Lock guard source buffer + const std::lock_guard lock(source_buffer_mutex); + if (!texture_registrar_) { + return nullptr; + } + + const uint32_t bytes_per_pixel = 4; + const uint32_t pixels_total = preview_frame_width_ * preview_frame_height_; + const uint32_t data_size = pixels_total * bytes_per_pixel; + if (source_buffer_ && data_size > 0 && source_buffer_size_ == data_size) { + dest_buffer_ = std::make_unique(data_size); + + // Map buffers to structs for easier conversion. + MFVideoFormatRGB32Pixel* src = + (MFVideoFormatRGB32Pixel*)source_buffer_.get(); + FlutterDesktopPixel* dst = (FlutterDesktopPixel*)dest_buffer_.get(); + + for (uint32_t i = 0; i < pixels_total; i++) { + dst[i].r = src[i].r; + dst[i].g = src[i].g; + dst[i].b = src[i].b; + dst[i].a = 255; + } + + // TODO: add release_callback for FlutterDesktopPixelBuffer + // and clear dest_buffer after each frame. + flutter_desktop_pixel_buffer_.buffer = dest_buffer_.get(); + flutter_desktop_pixel_buffer_.width = preview_frame_width_; + flutter_desktop_pixel_buffer_.height = preview_frame_height_; + return &flutter_desktop_pixel_buffer_; + } + + return nullptr; +} + +} // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h new file mode 100644 index 000000000000..b6ad50fc856c --- /dev/null +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -0,0 +1,77 @@ +// 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_TEXTURE_HANDLER_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEXTURE_HANDLER_H_ + +#include + +#include +#include +#include + +namespace camera_windows { + +// Describes flutter desktop pixelbuffers pixel data order. +struct FlutterDesktopPixel { + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + uint8_t a = 0; +}; + +// Describes MFVideoFormat_RGB32 data order. +struct MFVideoFormatRGB32Pixel { + uint8_t b = 0; + uint8_t g = 0; + uint8_t r = 0; + uint8_t x = 0; +}; + +class TextureHandler { + public: + TextureHandler(flutter::TextureRegistrar* texture_registrar) + : texture_registrar_(texture_registrar){}; + virtual ~TextureHandler(); + + // Prevent copying. + TextureHandler(TextureHandler const&) = delete; + TextureHandler& operator=(TextureHandler const&) = delete; + + // Updates source data buffer with given data. + bool UpdateBuffer(uint8_t* data, uint32_t data_length); + + // Registers texture and updates given texture_id pointer value. + int64_t RegisterTexture(); + + // Updates current preview texture size. + void UpdateTextureSize(uint32_t width, uint32_t height) { + preview_frame_width_ = width; + preview_frame_height_ = height; + }; + + private: + // Informs flutter texture registrar of updated texture. + void OnBufferUpdated(); + + // Converts local pixel buffer to flutter pixel buffer. + const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, + size_t height); + + int64_t texture_id_ = -1; + uint32_t bytes_per_pixel_ = 4; + uint32_t source_buffer_size_ = 0; + uint32_t preview_frame_width_ = 0; + uint32_t preview_frame_height_ = 0; + std::unique_ptr source_buffer_ = nullptr; + std::unique_ptr dest_buffer_ = nullptr; + std::unique_ptr texture_; + FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; + flutter::TextureRegistrar* texture_registrar_ = nullptr; + + std::mutex source_buffer_mutex; +}; +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEXTURE_HANDLER_H_ \ No newline at end of file From 951fd078afcebdb25b325a40ba2864d5c618e052 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 28 Jan 2022 17:32:06 +0200 Subject: [PATCH 59/93] [camera_windows] fix formatting --- packages/camera/camera_windows/windows/string_utils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/windows/string_utils.cpp b/packages/camera/camera_windows/windows/string_utils.cpp index eda147d81448..2e60e1bb01a7 100644 --- a/packages/camera/camera_windows/windows/string_utils.cpp +++ b/packages/camera/camera_windows/windows/string_utils.cpp @@ -12,7 +12,7 @@ namespace camera_windows { // Converts the given UTF-16 string to UTF-8. -std::string Utf8FromUtf16(const std::wstring &utf16_string) { +std::string Utf8FromUtf16(const std::wstring& utf16_string) { if (utf16_string.empty()) { return std::string(); } @@ -35,7 +35,7 @@ std::string Utf8FromUtf16(const std::wstring &utf16_string) { } // Converts the given UTF-8 string to UTF-16. -std::wstring Utf16FromUtf8(const std::string &utf8_string) { +std::wstring Utf16FromUtf8(const std::string& utf8_string) { if (utf8_string.empty()) { return std::wstring(); } From d24228ee7fca0a7ac5f16a1354a05d7af902520b Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 28 Jan 2022 20:40:57 +0200 Subject: [PATCH 60/93] [camera_windows] Remove generated_plugin_registrant.h --- .../windows/flutter/generated_plugin_registrant.h | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 packages/camera/camera_windows/example/windows/flutter/generated_plugin_registrant.h diff --git a/packages/camera/camera_windows/example/windows/flutter/generated_plugin_registrant.h b/packages/camera/camera_windows/example/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85a931..000000000000 --- a/packages/camera/camera_windows/example/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ From 343338733d39d2cf3c9bc15868b8df548fa92371 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 13:41:59 +0200 Subject: [PATCH 61/93] [camera_windows] fix buffer locking - Minumum flutter version updated to 2.8.0 - Fix occasional crash issues with texture processing threads. - Thread safe texture handler destruction. --- .../camera_windows/example/pubspec.yaml | 2 +- .../windows/flutter/generated_plugins.cmake | 8 ++++ packages/camera/camera_windows/pubspec.yaml | 2 +- .../windows/test/capture_controller_test.cpp | 3 ++ .../windows/texture_handler.cpp | 37 +++++++++++-------- .../camera_windows/windows/texture_handler.h | 5 ++- 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index 6cce239c317e..fde5e04d0ec1 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.5.0" + flutter: ">=2.8.0" dependencies: camera_platform_interface: ^2.1.2 diff --git a/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake b/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake index 62644e4539c0..458d22dac410 100644 --- a/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake +++ b/packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake @@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST camera_windows ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 113cae19d33a..099af4dfeb77 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -6,7 +6,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.5.0" + flutter: ">=2.8.0" flutter: plugin: 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 afaf25030fc3..cb8059dc1769 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -289,6 +289,9 @@ TEST(CaptureController, StartPreviewStartsProcessingSamples) { EXPECT_EQ(converted_buffer_data[i].g, mock_green_pixel); EXPECT_EQ(converted_buffer_data[i].b, mock_blue_pixel); } + + // Call release callback to get mutex lock unlocked. + converted_buffer->release_callback(converted_buffer->release_context); } converted_buffer = nullptr; } diff --git a/packages/camera/camera_windows/windows/texture_handler.cpp b/packages/camera/camera_windows/windows/texture_handler.cpp index d983804c3d19..c82feeba8844 100644 --- a/packages/camera/camera_windows/windows/texture_handler.cpp +++ b/packages/camera/camera_windows/windows/texture_handler.cpp @@ -9,14 +9,14 @@ namespace camera_windows { TextureHandler::~TextureHandler() { - if (texture_registrar_ && texture_id_ > -1) { + // Texture might still be processed while destructor is called. + // Lock mutex for safe destruction + + const std::lock_guard lock(buffer_mutex_); + if (TextureRegistered()) { texture_registrar_->UnregisterTexture(texture_id_); - } texture_id_ = -1; - texture_registrar_ = nullptr; - texture_ = nullptr; - dest_buffer_ = nullptr; - source_buffer_ = nullptr; + } } int64_t TextureHandler::RegisterTexture() { @@ -39,8 +39,8 @@ int64_t TextureHandler::RegisterTexture() { bool TextureHandler::UpdateBuffer(uint8_t* data, uint32_t data_length) { // Scoped lock guard. { - const std::lock_guard lock(source_buffer_mutex); - if (!texture_registrar_ || texture_id_ == -1) { + const std::lock_guard lock(buffer_mutex_); + if (!TextureRegistered()) { return false; } @@ -57,16 +57,16 @@ bool TextureHandler::UpdateBuffer(uint8_t* data, uint32_t data_length) { // Marks texture frame available after buffer is updated. void TextureHandler::OnBufferUpdated() { - if (texture_registrar_ && texture_id_ > -1) { + if (TextureRegistered()) { texture_registrar_->MarkTextureFrameAvailable(texture_id_); } } const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( size_t target_width, size_t target_height) { - // Lock guard source buffer - const std::lock_guard lock(source_buffer_mutex); - if (!texture_registrar_) { + // Lock buffer mutex to protect texture processing + std::unique_lock buffer_lock(buffer_mutex_); + if (!TextureRegistered()) { return nullptr; } @@ -88,14 +88,21 @@ const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( dst[i].a = 255; } - // TODO: add release_callback for FlutterDesktopPixelBuffer - // and clear dest_buffer after each frame. flutter_desktop_pixel_buffer_.buffer = dest_buffer_.get(); flutter_desktop_pixel_buffer_.width = preview_frame_width_; flutter_desktop_pixel_buffer_.height = preview_frame_height_; + + // Releases unique_lock and set mutex pointer for release context. + flutter_desktop_pixel_buffer_.release_context = buffer_lock.release(); + + // Unlocks mutex after texture is processed. + flutter_desktop_pixel_buffer_.release_callback = [](void* release_context) { + auto mutex = reinterpret_cast(release_context); + mutex->unlock(); + }; + return &flutter_desktop_pixel_buffer_; } - return nullptr; } diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index b6ad50fc856c..7ed9628414cc 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -59,6 +59,9 @@ class TextureHandler { const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, size_t height); + // Check if texture id is available and texture registrar is available + bool TextureRegistered() { return texture_registrar_ && texture_id_ > -1; } + int64_t texture_id_ = -1; uint32_t bytes_per_pixel_ = 4; uint32_t source_buffer_size_ = 0; @@ -70,7 +73,7 @@ class TextureHandler { FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; flutter::TextureRegistrar* texture_registrar_ = nullptr; - std::mutex source_buffer_mutex; + std::mutex buffer_mutex_; }; } // namespace camera_windows From bd27944fb3149deeae0795d91062573719053ef3 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 16:47:41 +0200 Subject: [PATCH 62/93] [camera_windows] Minor fixes --- .../camera_windows/example/lib/main.dart | 174 ++++++++++-------- .../camera/camera_windows/windows/camera.cpp | 8 +- .../windows/capture_controller.cpp | 23 +-- .../windows/capture_controller.h | 6 + 4 files changed, 118 insertions(+), 93 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index a74f5eeec4bb..641fc1c16c95 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -36,7 +36,7 @@ class _MyAppState extends State { void initState() { super.initState(); WidgetsFlutterBinding.ensureInitialized(); - _getAvailableCameras(); + _fetchCameras(); } @override @@ -48,42 +48,45 @@ class _MyAppState extends State { } /// Fetches list of available cameras from camera_windows plugin. - Future _getAvailableCameras() async { + Future _fetchCameras() async { String cameraInfo; List cameras = []; - int cameraIndex = _cameraIndex; + int cameraIndex = 0; try { cameras = await CameraPlatform.instance.availableCameras(); - cameraIndex = cameraIndex % cameras.length; if (cameras.isEmpty) { cameraInfo = 'No available cameras'; } else { + cameraIndex = _cameraIndex % cameras.length; cameraInfo = 'Found camera: ${cameras[cameraIndex].name}'; } } on PlatformException catch (e) { cameraInfo = 'Failed to get cameras: ${e.code}: ${e.message}'; } - if (!mounted) { - return; + if (mounted) { + setState(() { + _cameraIndex = cameraIndex; + _cameras = cameras; + _cameraInfo = cameraInfo; + }); } - - setState(() { - _cameraIndex = cameraIndex; - _cameras = cameras; - _cameraInfo = cameraInfo; - }); } /// Initializes the camera on the device. Future _initializeCamera() async { - assert(_cameras.isNotEmpty); assert(!_initialized); - final Completer _initializeCompleter = - Completer(); + + if (_cameras.isEmpty) { + return; + } + int cameraId = -1; try { + final Completer _initializeCompleter = + Completer(); + final int cameraIndex = _cameraIndex % _cameras.length; final CameraDescription camera = _cameras[cameraIndex]; @@ -117,12 +120,14 @@ class _MyAppState extends State { ), ); - setState(() { - _initialized = true; - _cameraId = cameraId; - _cameraIndex = cameraIndex; - _cameraInfo = 'Capturing camera: ${camera.name}'; - }); + if (mounted) { + setState(() { + _initialized = true; + _cameraId = cameraId; + _cameraIndex = cameraIndex; + _cameraInfo = 'Capturing camera: ${camera.name}'; + }); + } } on CameraException catch (e) { try { if (cameraId >= 0) { @@ -132,17 +137,19 @@ class _MyAppState extends State { debugPrint('Failed to dispose camera: ${e.code}: ${e.description}'); } - /// Reset state. - setState(() { - _initialized = false; - _cameraId = -1; - _cameraInfo = 'Camera disposed'; - _previewSize = null; - _recording = false; - _recordingTimed = false; - _cameraInfo = - 'Failed to initialize camera: ${e.code}: ${e.description}'; - }); + // Reset state. + if (mounted) { + setState(() { + _initialized = false; + _cameraId = -1; + _cameraIndex = 0; + _previewSize = null; + _recording = false; + _recordingTimed = false; + _cameraInfo = + 'Failed to initialize camera: ${e.code}: ${e.description}'; + }); + } } } @@ -151,20 +158,24 @@ class _MyAppState extends State { assert(_initialized); try { await CameraPlatform.instance.dispose(_cameraId); - setState(() { - _initialized = false; - _cameraId = -1; - _cameraInfo = 'Camera disposed'; - _previewSize = null; - _recording = false; - _recordingTimed = false; - _previewPaused = false; - }); - await _getAvailableCameras(); + + if (mounted) { + setState(() { + _initialized = false; + _cameraId = -1; + _previewSize = null; + _recording = false; + _recordingTimed = false; + _previewPaused = false; + _cameraInfo = 'Camera disposed'; + }); + } } on CameraException catch (e) { - setState(() { - _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.description}'; - }); + if (mounted) { + setState(() { + _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.description}'; + }); + } } } @@ -197,9 +208,11 @@ class _MyAppState extends State { maxVideoDuration: Duration(seconds: seconds), ); - setState(() { - _recordingTimed = true; - }); + if (mounted) { + setState(() { + _recordingTimed = true; + }); + } } } @@ -217,9 +230,12 @@ class _MyAppState extends State { _showInSnackBar('Video captured to: ${_file.path}'); } - setState(() { - _recording = !_recording; - }); + + if (mounted) { + setState(() { + _recording = !_recording; + }); + } } } } @@ -231,32 +247,36 @@ class _MyAppState extends State { } else { await CameraPlatform.instance.resumePreview(_cameraId); } - setState(() { - _previewPaused = !_previewPaused; - }); + if (mounted) { + setState(() { + _previewPaused = !_previewPaused; + }); + } } } Future _switchCamera() async { - _cameraIndex = (_cameraIndex + 1) % _cameras.length; - if (_initialized && _cameraId >= 0) { - await _disposeCurrentCamera(); - if (_cameras.isNotEmpty) { - await _initializeCamera(); + if (_cameras.isNotEmpty) { + // select next index; + _cameraIndex = (_cameraIndex + 1) % _cameras.length; + if (_initialized && _cameraId >= 0) { + await _disposeCurrentCamera(); + await _fetchCameras(); + if (_cameras.isNotEmpty) { + await _initializeCamera(); + } + } else { + await _fetchCameras(); } - } else { - await _getAvailableCameras(); } } - Future _onResolutionChange(ResolutionPreset? newValue) async { - if (newValue == null) { - return; - } + Future _onResolutionChange(ResolutionPreset newValue) async { setState(() { _resolutionPreset = newValue; }); if (_initialized && _cameraId >= 0) { + // Re-inits camera with new resolution preset. await _disposeCurrentCamera(); await _initializeCamera(); } @@ -267,14 +287,21 @@ class _MyAppState extends State { _recordAudio = recordAudio; }); if (_initialized && _cameraId >= 0) { + // Re-inits camera with new record audio setting. await _disposeCurrentCamera(); await _initializeCamera(); } } void _onCameraError(CameraErrorEvent event) { - _scaffoldMessengerKey.currentState - ?.showSnackBar(SnackBar(content: Text('Error: ${event.description}'))); + if (mounted) { + _scaffoldMessengerKey.currentState?.showSnackBar( + SnackBar(content: Text('Error: ${event.description}'))); + + // Dispose camera on camera error as it can not be used anymore. + _disposeCurrentCamera(); + _fetchCameras(); + } } void _showInSnackBar(String message) { @@ -315,7 +342,7 @@ class _MyAppState extends State { ), if (_cameras.isEmpty) ElevatedButton( - onPressed: _getAvailableCameras, + onPressed: _fetchCameras, child: const Text('Re-check available cameras'), ), if (_cameras.isNotEmpty) @@ -324,14 +351,15 @@ class _MyAppState extends State { children: [ DropdownButton( value: _resolutionPreset, - onChanged: (ResolutionPreset? value) => - _onResolutionChange(value), + onChanged: (ResolutionPreset? value) { + if (value != null) { + _onResolutionChange(value); + } + }, items: resolutionItems, ), const SizedBox(width: 20), - const Text( - 'Audio:', - ), + const Text('Audio:'), Switch( value: _recordAudio, onChanged: (bool state) => _onAudioChange(state)), diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index 5dcf324327ea..7e5b9ce1ad2b 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -64,9 +64,6 @@ bool CameraImpl::AddPendingResult( std::unique_ptr> CameraImpl::GetPendingResultByType( PendingResultType type) { - if (pending_results_.empty()) { - return nullptr; - } auto it = pending_results_.find(type); if (it == pending_results_.end()) { return nullptr; @@ -77,9 +74,6 @@ std::unique_ptr> CameraImpl::GetPendingResultByType( } bool CameraImpl::HasPendingResultByType(PendingResultType type) { - if (pending_results_.empty()) { - return false; - } auto it = pending_results_.find(type); if (it == pending_results_.end()) { return false; @@ -90,7 +84,7 @@ bool CameraImpl::HasPendingResultByType(PendingResultType type) { void CameraImpl::SendErrorForPendingResults(const std::string& error_code, const std::string& descripion) { for (const auto& pending_result : pending_results_) { - std::move(pending_result.second)->Error(error_code, descripion); + pending_result.second->Error(error_code, descripion); } pending_results_.clear(); } diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 02be53fc5733..29b92c798941 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -293,7 +293,7 @@ void CaptureControllerImpl::InitCaptureDevice( bool record_audio, ResolutionPreset resolution_preset) { assert(capture_controller_listener_); - if (capture_engine_state_ == CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { + if (IsInitialized()) { return capture_controller_listener_->OnCreateCaptureEngineFailed( "Capture device already initialized"); } else if (capture_engine_state_ == @@ -335,7 +335,7 @@ void CaptureControllerImpl::TakePicture(const std::string file_path) { assert(capture_engine_callback_handler_); assert(capture_engine_); - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { + if (!IsInitialized()) { return OnPicture(false, "Not initialized"); } @@ -446,7 +446,7 @@ bool FindBestMediaType(DWORD source_stream_index, IMFCaptureSource* source, } HRESULT CaptureControllerImpl::FindBaseMediaTypes() { - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { + if (!IsInitialized()) { return E_FAIL; } @@ -480,7 +480,7 @@ void CaptureControllerImpl::StartRecord(const std::string& file_path, int64_t max_video_duration_ms) { assert(capture_engine_); - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { + if (!IsInitialized()) { return OnRecordStarted(false, "Camera not initialized. Camera should be " "disposed and reinitialized."); @@ -516,7 +516,7 @@ void CaptureControllerImpl::StartRecord(const std::string& file_path, void CaptureControllerImpl::StopRecord() { assert(capture_controller_listener_); - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { + if (!IsInitialized()) { return OnRecordStopped(false, "Camera not initialized. Camera should be " "disposed and reinitialized."); @@ -558,8 +558,7 @@ void CaptureControllerImpl::StartPreview() { assert(capture_engine_); assert(texture_handler_); - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED || - !texture_handler_) { + if (!IsInitialized() || !texture_handler_) { return OnPreviewStarted(false, "Camera not initialized. Camera should be " "disposed and reinitialized."); @@ -602,8 +601,7 @@ void CaptureControllerImpl::StartPreview() { void CaptureControllerImpl::StopPreview() { assert(capture_engine_); - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && - !preview_handler_) { + if (!IsInitialized() && !preview_handler_) { return; } @@ -653,9 +651,8 @@ void CaptureControllerImpl::ResumePreview() { // Called via IMFCaptureEngineOnEventCallback implementation. // Implements CaptureEngineObserver::OnEvent. void CaptureControllerImpl::OnEvent(IMFMediaEvent* event) { - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && - capture_engine_state_ != - CaptureEngineState::CAPTURE_ENGINE_INITIALIZING) { + if (!IsInitialized() && capture_engine_state_ != + CaptureEngineState::CAPTURE_ENGINE_INITIALIZING) { return; } @@ -841,7 +838,7 @@ bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer, // Called via IMFCaptureEngineOnSampleCallback implementation. // Implements CaptureEngineObserver::UpdateCaptureTime. void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { - if (capture_engine_state_ != CaptureEngineState::CAPTURE_ENGINE_INITIALIZED) { + if (!IsInitialized()) { return; } diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index c9d72a20703b..686feef82bc1 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -170,6 +170,12 @@ class CaptureControllerImpl : public CaptureController, }; private: + // Helper function to return initialized state as boolean; + bool IsInitialized() { + return capture_engine_state_ == + CaptureEngineState::CAPTURE_ENGINE_INITIALIZED; + }; + // Resets capture controller state. // This is called if capture engine creation fails or is disposed. void ResetCaptureController(); From a0e10f7fa6f2dc1cbd44858584aad8908532f445 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 17:00:50 +0200 Subject: [PATCH 63/93] [camera_windows] Software mirror preview support - Not used yet, but will be used for front facing cameras after facing detection is done. --- .../windows/texture_handler.cpp | 29 +++++++++++++++---- .../camera_windows/windows/texture_handler.h | 4 +++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/camera/camera_windows/windows/texture_handler.cpp b/packages/camera/camera_windows/windows/texture_handler.cpp index c82feeba8844..a4ce566b1674 100644 --- a/packages/camera/camera_windows/windows/texture_handler.cpp +++ b/packages/camera/camera_windows/windows/texture_handler.cpp @@ -15,7 +15,7 @@ TextureHandler::~TextureHandler() { const std::lock_guard lock(buffer_mutex_); if (TextureRegistered()) { texture_registrar_->UnregisterTexture(texture_id_); - texture_id_ = -1; + texture_id_ = -1; } } @@ -81,11 +81,28 @@ const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( (MFVideoFormatRGB32Pixel*)source_buffer_.get(); FlutterDesktopPixel* dst = (FlutterDesktopPixel*)dest_buffer_.get(); - for (uint32_t i = 0; i < pixels_total; i++) { - dst[i].r = src[i].r; - dst[i].g = src[i].g; - dst[i].b = src[i].b; - dst[i].a = 255; + for (uint32_t y = 0; y < preview_frame_height_; y++) { + for (uint32_t x = 0; x < preview_frame_width_; x++) { + uint32_t sp = (y * preview_frame_width_) + x; + if (mirror_preview_) { + // Software mirror more implementation. + // IMFCapturePreviewSink also has the SetMirrorState setting, + // but if enabled, samples will not be processed. + + // Calculates mirrored pixel position. + uint32_t tp = + (y * preview_frame_width_) + ((preview_frame_width_ - 1) - x); + dst[tp].r = src[sp].r; + dst[tp].g = src[sp].g; + dst[tp].b = src[sp].b; + dst[tp].a = 255; + } else { + dst[sp].r = src[sp].r; + dst[sp].g = src[sp].g; + dst[sp].b = src[sp].b; + dst[sp].a = 255; + } + } } flutter_desktop_pixel_buffer_.buffer = dest_buffer_.get(); diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index 7ed9628414cc..00dcd3950f93 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -51,6 +51,9 @@ class TextureHandler { preview_frame_height_ = height; }; + // Sets software mirror state. + void SetMirrorPreviewState(bool mirror) { mirror_preview_ = mirror; }; + private: // Informs flutter texture registrar of updated texture. void OnBufferUpdated(); @@ -62,6 +65,7 @@ class TextureHandler { // Check if texture id is available and texture registrar is available bool TextureRegistered() { return texture_registrar_ && texture_id_ > -1; } + bool mirror_preview_ = false; int64_t texture_id_ = -1; uint32_t bytes_per_pixel_ = 4; uint32_t source_buffer_size_ = 0; From b8be9cf5a3e8280b3c1be4f866c40682b2945c1b Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 17:55:23 +0200 Subject: [PATCH 64/93] [camera_windows] add missing camera_closing event support --- .../camera_windows/example/lib/main.dart | 14 ++ .../camera_windows/example/pubspec.yaml | 2 +- .../camera_windows/lib/camera_windows.dart | 199 ++++++++---------- .../camera_windows/lib/src/utils/utils.dart | 18 -- .../camera/camera_windows/windows/camera.cpp | 13 +- .../camera/camera_windows/windows/camera.h | 6 +- 6 files changed, 125 insertions(+), 127 deletions(-) delete mode 100644 packages/camera/camera_windows/lib/src/utils/utils.dart diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index 641fc1c16c95..30642b4e46da 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -31,6 +31,7 @@ class _MyAppState extends State { Size? _previewSize; ResolutionPreset _resolutionPreset = ResolutionPreset.veryHigh; StreamSubscription? _errorStreamSubscription; + StreamSubscription? _cameraClosingStreamSubscription; @override void initState() { @@ -44,6 +45,8 @@ class _MyAppState extends State { _disposeCurrentCamera(); _errorStreamSubscription?.cancel(); _errorStreamSubscription = null; + _cameraClosingStreamSubscription?.cancel(); + _cameraClosingStreamSubscription = null; super.dispose(); } @@ -101,6 +104,11 @@ class _MyAppState extends State { .onCameraError(cameraId) .listen(_onCameraError); + _cameraClosingStreamSubscription?.cancel(); + _cameraClosingStreamSubscription = CameraPlatform.instance + .onCameraClosing(cameraId) + .listen(_onCameraClosing); + unawaited(CameraPlatform.instance .onCameraInitialized(cameraId) .first @@ -304,6 +312,12 @@ class _MyAppState extends State { } } + void _onCameraClosing(CameraClosingEvent event) { + if (mounted) { + _showInSnackBar('Camera is closing'); + } + } + void _showInSnackBar(String message) { _scaffoldMessengerKey.currentState?.showSnackBar(SnackBar( content: Text(message), diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index fde5e04d0ec1..4c65a0a9838b 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -26,4 +26,4 @@ dev_dependencies: pedantic: ^1.10.0 flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index d1e6f02be414..ea12f8eb513a 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -6,9 +6,7 @@ import 'dart:async'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_windows/src/utils/utils.dart'; import 'package:cross_file/cross_file.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; @@ -31,15 +29,12 @@ class CameraWindows extends CameraPlatform { /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. - /// This is only exposed for test purposes. It shouldn't be used by clients of - /// the plugin as it may break or change at any time. - @visibleForTesting - final StreamController cameraEventStreamController = + final StreamController _cameraEventStreamController = StreamController.broadcast(); /// Returns a stream of camera events for the given [cameraId]. Stream _cameraEvents(int cameraId) => - cameraEventStreamController.stream + _cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); @override @@ -56,7 +51,7 @@ class CameraWindows extends CameraPlatform { return CameraDescription( name: camera['name'] as String, lensDirection: - parseCameraLensDirection(camera['lensFacing'] as String), + _parseCameraLensDirection(camera['lensFacing'] as String), sensorOrientation: camera['sensorOrientation'] as int, ); }).toList(); @@ -72,12 +67,11 @@ class CameraWindows extends CameraPlatform { bool enableAudio = false, }) async { try { + // If resolutionPreset is not specified, plugin selects the highest resolution possible. final Map? reply = await _pluginChannel .invokeMapMethod('create', { 'cameraName': cameraDescription.name, - 'resolutionPreset': resolutionPreset != null - ? _serializeResolutionPreset(resolutionPreset) - : null, + 'resolutionPreset': _serializeResolutionPreset(resolutionPreset), 'enableAudio': enableAudio, }); @@ -103,7 +97,7 @@ class CameraWindows extends CameraPlatform { final MethodChannel channel = MethodChannel('flutter.io/cameraPlugin/camera$requestedCameraId'); channel.setMethodCallHandler( - (MethodCall call) => handleCameraMethodCall(call, requestedCameraId), + (MethodCall call) => _handleCameraMethodCall(call, requestedCameraId), ); return channel; }); @@ -120,7 +114,7 @@ class CameraWindows extends CameraPlatform { throw CameraException(e.code, e.message); } - cameraEventStreamController.add( + _cameraEventStreamController.add( CameraInitializedEvent( requestedCameraId, reply!['previewWidth']!, @@ -135,20 +129,17 @@ class CameraWindows extends CameraPlatform { @override Future dispose(int cameraId) async { + await _pluginChannel.invokeMethod( + 'dispose', + {'cameraId': cameraId}, + ); + + // Destroy method channel after camera is disposed to be able to handle last messages. if (_cameraChannels.containsKey(cameraId)) { final MethodChannel? cameraChannel = _cameraChannels[cameraId]; cameraChannel?.setMethodCallHandler(null); _cameraChannels.remove(cameraId); } - - try { - await _pluginChannel.invokeMethod( - 'dispose', - {'cameraId': cameraId}, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } } @override @@ -158,7 +149,12 @@ class CameraWindows extends CameraPlatform { @override Stream onCameraResolutionChanged(int cameraId) { - /// Windows camera plugin does not support resolution changed events. + /// Windows API does not automatically change the camera's resolution + /// during capture so these events are never send from the platform. + /// Support for changing resolution should be implemented, if support for + /// requesting resolution change is added to camera platform interface. + /// + /// Returns empty stream. return const Stream.empty(); } @@ -179,11 +175,10 @@ class CameraWindows extends CameraPlatform { @override Stream onDeviceOrientationChanged() { - /// Windows camera plugin does not support capture orientations. - /// Force device orientation to landscape as by default camera plugin uses portraitUp orientation. - return Stream.value( - const DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight), - ); + // TODO(jokerttu): Implement device orientation detection. + // + // Returns empty stream. + return const Stream.empty(); } @override @@ -191,89 +186,78 @@ class CameraWindows extends CameraPlatform { int cameraId, DeviceOrientation orientation, ) async { + // TODO(jokerttu): Implement lock capture orientation feature. throw UnimplementedError('lockCaptureOrientation() is not implemented.'); } @override Future unlockCaptureOrientation(int cameraId) async { + // TODO(jokerttu): Implement unlock capture orientation feature. throw UnimplementedError('unlockCaptureOrientation() is not implemented.'); } @override Future takePicture(int cameraId) async { final String? path; - try { - path = await _pluginChannel.invokeMethod( - 'takePicture', - {'cameraId': cameraId}, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } + path = await _pluginChannel.invokeMethod( + 'takePicture', + {'cameraId': cameraId}, + ); return XFile(path!); } @override - Future prepareForVideoRecording() async { - try { + Future prepareForVideoRecording() => _pluginChannel.invokeMethod('prepareForVideoRecording'); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } @override Future startVideoRecording( int cameraId, { Duration? maxVideoDuration, }) async { - try { - await _pluginChannel.invokeMethod( - 'startVideoRecording', - { - 'cameraId': cameraId, - 'maxVideoDuration': maxVideoDuration?.inMilliseconds, - }, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } + await _pluginChannel.invokeMethod( + 'startVideoRecording', + { + 'cameraId': cameraId, + 'maxVideoDuration': maxVideoDuration?.inMilliseconds, + }, + ); } @override Future stopVideoRecording(int cameraId) async { final String? path; - try { - path = await _pluginChannel.invokeMethod( - 'stopVideoRecording', - {'cameraId': cameraId}, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } + path = await _pluginChannel.invokeMethod( + 'stopVideoRecording', + {'cameraId': cameraId}, + ); return XFile(path!); } @override Future pauseVideoRecording(int cameraId) async { - throw UnimplementedError('pauseVideoRecording() is not implemented.'); + throw UnsupportedError( + 'pauseVideoRecording() is not supported due to Win32 API limitations.'); } @override Future resumeVideoRecording(int cameraId) async { - throw UnimplementedError('resumeVideoRecording() is not implemented.'); + throw UnsupportedError( + 'resumeVideoRecording() is not supported due to Win32 API limitations.'); } @override Future setFlashMode(int cameraId, FlashMode mode) async { + // TODO(jokerttu): Implement flash mode support throw UnimplementedError('setFlashMode() is not implemented.'); } @override Future setExposureMode(int cameraId, ExposureMode mode) async { + // TODO(jokerttu): Implement explosure mode support throw UnimplementedError('setExposureMode() is not implemented.'); } @@ -282,39 +266,40 @@ class CameraWindows extends CameraPlatform { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); - throw UnimplementedError('setExposurePoint() is not implemented.'); + throw UnsupportedError( + 'setExposurePoint() is not supported due to Win32 API limitations.'); } @override Future getMinExposureOffset(int cameraId) async { - /// Explosure offset is not supported by camera windows plugin yet. - /// Default min offset value is returned. + // TODO(jokerttu): Implement exposure offset support. + // Value is returned to support existing implementations. return 0.0; } @override Future getMaxExposureOffset(int cameraId) async { - /// Explosure offset is not supported by camera windows plugin yet. - /// Default max offset value is returned. + // TODO(jokerttu): Implement exposure offset support. + // Value is returned to support existing implementations. return 0.0; } @override Future getExposureOffsetStepSize(int cameraId) async { - /// Explosure offset is not supported by camera windows plugin yet. - /// Default step value is returned. + // TODO(jokerttu): Implement exposure offset support. + // Value is returned to support existing implementations. return 1.0; } @override Future setExposureOffset(int cameraId, double offset) async { - /// Explosure offset is not supported by camera windows plugin yet. - /// Default exposure offset value is returned as a response. - return 0.0; + // TODO(jokerttu): Implement exposure offset support. + throw UnimplementedError('setExposureOffset() is not implemented.'); } @override Future setFocusMode(int cameraId, FocusMode mode) async { + // TODO(jokerttu): Implement focus mode support. throw UnimplementedError('setFocusMode() is not implemented.'); } @@ -323,50 +308,44 @@ class CameraWindows extends CameraPlatform { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); - throw UnimplementedError('setFocusPoint() is not implemented.'); + throw UnsupportedError( + 'setFocusPoint() is not supported due to Win32 API limitations.'); } @override Future getMinZoomLevel(int cameraId) async { - /// Zoom level is not supported by camera windows plugin yet. - /// Default min zoom level value is returned as a response. + // TODO(jokerttu): Implement zoom level support. + // Value is returned to support existing implementations. return 1.0; } @override Future getMaxZoomLevel(int cameraId) async { - /// Zoom level is not supported by camera windows plugin yet. - /// Default max zoom level value is returned as a response. + // TODO(jokerttu): Implement zoom level support. + // Value is returned to support existing implementations. return 1.0; } @override Future setZoomLevel(int cameraId, double zoom) async { + // TODO(jokerttu): Implement zoom level support. throw UnimplementedError('setZoomLevel() is not implemented.'); } @override Future pausePreview(int cameraId) async { - try { - await _pluginChannel.invokeMethod( - 'pausePreview', - {'cameraId': cameraId}, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } + await _pluginChannel.invokeMethod( + 'pausePreview', + {'cameraId': cameraId}, + ); } @override Future resumePreview(int cameraId) async { - try { - await _pluginChannel.invokeMethod( - 'resumePreview', - {'cameraId': cameraId}, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } + await _pluginChannel.invokeMethod( + 'resumePreview', + {'cameraId': cameraId}, + ); } @override @@ -374,9 +353,11 @@ class CameraWindows extends CameraPlatform { return Texture(textureId: cameraId); } - /// Returns the resolution preset as a String. - String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { + /// Returns the resolution preset as a nullable String. + String? _serializeResolutionPreset(ResolutionPreset? resolutionPreset) { switch (resolutionPreset) { + case null: + return null; case ResolutionPreset.max: return 'max'; case ResolutionPreset.ultraHigh: @@ -389,20 +370,14 @@ class CameraWindows extends CameraPlatform { return 'medium'; case ResolutionPreset.low: return 'low'; - default: - throw ArgumentError('Unknown ResolutionPreset value'); } } /// Converts messages received from the native platform into camera events. - /// - /// This is only exposed for test purposes. It shouldn't be used by clients of - /// the plugin as it may break or change at any time. - @visibleForTesting - Future handleCameraMethodCall(MethodCall call, int cameraId) async { + Future _handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'camera_closing': - cameraEventStreamController.add( + _cameraEventStreamController.add( CameraClosingEvent( cameraId, ), @@ -410,7 +385,7 @@ class CameraWindows extends CameraPlatform { break; case 'video_recorded': // This is called if maxVideoDuration was given on record start. - cameraEventStreamController.add( + _cameraEventStreamController.add( VideoRecordedEvent( cameraId, XFile(call.arguments['path'] as String), @@ -423,7 +398,7 @@ class CameraWindows extends CameraPlatform { ); break; case 'error': - cameraEventStreamController.add( + _cameraEventStreamController.add( CameraErrorEvent( cameraId, call.arguments['description'] as String, @@ -434,4 +409,16 @@ class CameraWindows extends CameraPlatform { throw MissingPluginException(); } } + + CameraLensDirection _parseCameraLensDirection(String string) { + switch (string) { + case 'front': + return CameraLensDirection.front; + case 'back': + return CameraLensDirection.back; + case 'external': + return CameraLensDirection.external; + } + throw ArgumentError('Unknown CameraLensDirection value'); + } } diff --git a/packages/camera/camera_windows/lib/src/utils/utils.dart b/packages/camera/camera_windows/lib/src/utils/utils.dart deleted file mode 100644 index b98e20e85a20..000000000000 --- a/packages/camera/camera_windows/lib/src/utils/utils.dart +++ /dev/null @@ -1,18 +0,0 @@ -// 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 'package:camera_platform_interface/camera_platform_interface.dart'; - -/// Parses a string into a corresponding CameraLensDirection. -CameraLensDirection parseCameraLensDirection(String string) { - switch (string) { - case 'front': - return CameraLensDirection.front; - case 'back': - return CameraLensDirection.back; - case 'external': - return CameraLensDirection.external; - } - throw ArgumentError('Unknown CameraLensDirection value'); -} diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index 7e5b9ce1ad2b..dcbde1d2ab24 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -13,14 +13,17 @@ using flutter::EncodableValue; constexpr char kCameraMethodChannelBaseName[] = "flutter.io/cameraPlugin/camera"; constexpr char kVideoRecordedEvent[] = "video_recorded"; +constexpr char kCameraClosingEvent[] = "camera_closing"; constexpr char kErrorEvent[] = "error"; CameraImpl::CameraImpl(const std::string& device_id) : device_id_(device_id), Camera(device_id) {} CameraImpl::~CameraImpl() { - capture_controller_ = nullptr; + // Sends camera closing event. + OnCameraClosing(); + capture_controller_ = nullptr; SendErrorForPendingResults("plugin_disposed", "Plugin disposed before request was handled"); } @@ -248,4 +251,12 @@ void CameraImpl::OnCaptureError(const std::string& error) { SendErrorForPendingResults("capture_error", error); } +void CameraImpl::OnCameraClosing() { + if (messenger_ && camera_id_ >= 0) { + auto channel = GetMethodChannel(); + channel->InvokeMethod(kCameraClosingEvent, + std::move(std::make_unique())); + } +} + } // namespace camera_windows \ No newline at end of file diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 9c9715dc1210..6d4a350b2808 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -125,7 +125,11 @@ class CameraImpl : public Camera { void SendErrorForPendingResults(const std::string& error_code, const std::string& descripion); - // Initializes method channel instance and returns pointer it + // Called when camera is disposed. + // Sends camera closing message to the cameras method channel. + void OnCameraClosing(); + + // Initializes method channel instance and returns pointer it. MethodChannel<>* GetMethodChannel(); // Finds pending result by type. From dbf7e66551d5845bc58ff1353d7b330749d17ad4 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 18:37:57 +0200 Subject: [PATCH 65/93] [camera_windows] add trailing white spaces --- packages/camera/camera_windows/windows/camera.cpp | 2 +- .../camera/camera_windows/windows/capture_engine_listener.cpp | 3 ++- packages/camera/camera_windows/windows/com_heap_ptr.h | 2 ++ packages/camera/camera_windows/windows/device_info.cpp | 3 ++- packages/camera/camera_windows/windows/device_info.h | 2 +- packages/camera/camera_windows/windows/photo_handler.cpp | 2 +- packages/camera/camera_windows/windows/photo_handler.h | 3 ++- packages/camera/camera_windows/windows/preview_handler.cpp | 2 +- packages/camera/camera_windows/windows/preview_handler.h | 3 ++- packages/camera/camera_windows/windows/record_handler.cpp | 2 +- packages/camera/camera_windows/windows/record_handler.h | 3 ++- packages/camera/camera_windows/windows/test/mocks.h | 1 + packages/camera/camera_windows/windows/texture_handler.cpp | 2 +- packages/camera/camera_windows/windows/texture_handler.h | 3 ++- 14 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index dcbde1d2ab24..b389e43f90b6 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -259,4 +259,4 @@ void CameraImpl::OnCameraClosing() { } } -} // namespace camera_windows \ No newline at end of file +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.cpp b/packages/camera/camera_windows/windows/capture_engine_listener.cpp index a79da9cde4ea..5425b388287a 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.cpp +++ b/packages/camera/camera_windows/windows/capture_engine_listener.cpp @@ -86,4 +86,5 @@ HRESULT CaptureEngineListener::OnSample(IMFSample* sample) { } return hr; } -} // namespace camera_windows \ No newline at end of file + +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/com_heap_ptr.h b/packages/camera/camera_windows/windows/com_heap_ptr.h index 630f4c2d9be9..1075d4efe700 100644 --- a/packages/camera/camera_windows/windows/com_heap_ptr.h +++ b/packages/camera/camera_windows/windows/com_heap_ptr.h @@ -60,5 +60,7 @@ class ComHeapPtr { // Pointer to memory. T* p_obj_; }; + } // namespace camera_windows + #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_COMHEAPPTR_H_ diff --git a/packages/camera/camera_windows/windows/device_info.cpp b/packages/camera/camera_windows/windows/device_info.cpp index 47dae658279b..6f07f5e14d8c 100644 --- a/packages/camera/camera_windows/windows/device_info.cpp +++ b/packages/camera/camera_windows/windows/device_info.cpp @@ -26,4 +26,5 @@ std::unique_ptr ParseDeviceInfoFromCameraName( return nullptr; } -} // namespace camera_windows \ No newline at end of file + +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/device_info.h b/packages/camera/camera_windows/windows/device_info.h index 0c74fe0d3fcd..7206bebc5544 100644 --- a/packages/camera/camera_windows/windows/device_info.h +++ b/packages/camera/camera_windows/windows/device_info.h @@ -22,4 +22,4 @@ std::unique_ptr ParseDeviceInfoFromCameraName( } // namespace camera_windows -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ \ No newline at end of file +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ diff --git a/packages/camera/camera_windows/windows/photo_handler.cpp b/packages/camera/camera_windows/windows/photo_handler.cpp index 1db9f4e5daef..f34bf08cb4bf 100644 --- a/packages/camera/camera_windows/windows/photo_handler.cpp +++ b/packages/camera/camera_windows/windows/photo_handler.cpp @@ -137,4 +137,4 @@ void PhotoHandler::OnPhotoTaken() { photo_state_ = PhotoState::PHOTO_STATE__IDLE; } -} // namespace camera_windows \ No newline at end of file +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index 80660477f57e..c6ab07a7a26b 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -70,6 +70,7 @@ class PhotoHandler { PhotoState photo_state_ = PhotoState::PHOTO_STATE__NOT_STARTED; ComPtr photo_sink_; }; + } // namespace camera_windows -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PHOTO_HANDLER_H_ \ No newline at end of file +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PHOTO_HANDLER_H_ diff --git a/packages/camera/camera_windows/windows/preview_handler.cpp b/packages/camera/camera_windows/windows/preview_handler.cpp index 3add2029e168..f909ef445215 100644 --- a/packages/camera/camera_windows/windows/preview_handler.cpp +++ b/packages/camera/camera_windows/windows/preview_handler.cpp @@ -161,4 +161,4 @@ void PreviewHandler::OnPreviewStarted() { } } -} // namespace camera_windows \ No newline at end of file +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h index b009f73ff1bd..acaeb929d3cd 100644 --- a/packages/camera/camera_windows/windows/preview_handler.h +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -94,6 +94,7 @@ class PreviewHandler { PreviewState preview_state_ = PreviewState::PREVIEW_STATE__NOT_STARTED; ComPtr preview_sink_; }; + } // namespace camera_windows -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PREVIEW_HANDLER_H_ \ No newline at end of file +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PREVIEW_HANDLER_H_ diff --git a/packages/camera/camera_windows/windows/record_handler.cpp b/packages/camera/camera_windows/windows/record_handler.cpp index ad9b52d1806b..9d255aea1815 100644 --- a/packages/camera/camera_windows/windows/record_handler.cpp +++ b/packages/camera/camera_windows/windows/record_handler.cpp @@ -257,4 +257,4 @@ bool RecordHandler::ShouldStopTimedRecording() { (static_cast(max_video_duration_ms_) * 1000); } -} // namespace camera_windows \ No newline at end of file +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index 268e66547959..d2772f97a760 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -106,6 +106,7 @@ class RecordHandler { RecordingType type_; ComPtr record_sink_; }; + } // namespace camera_windows -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORD_HANDLER_H_ \ No newline at end of file +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORD_HANDLER_H_ diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index 52ddad89941d..dab30ce764d9 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -1007,6 +1007,7 @@ class MockCaptureEngine : public IMFCaptureEngine { #define MOCK_DEVICE_ID "mock_device_id" #define MOCK_CAMERA_NAME "mock_camera_name <" MOCK_DEVICE_ID ">" #define MOCK_INVALID_CAMERA_NAME "invalid_camera_name" + } // namespace } // namespace test } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/texture_handler.cpp b/packages/camera/camera_windows/windows/texture_handler.cpp index a4ce566b1674..b6154e45fd9c 100644 --- a/packages/camera/camera_windows/windows/texture_handler.cpp +++ b/packages/camera/camera_windows/windows/texture_handler.cpp @@ -123,4 +123,4 @@ const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( return nullptr; } -} // namespace camera_windows \ No newline at end of file +} // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index 00dcd3950f93..db5b45e69ecb 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -79,6 +79,7 @@ class TextureHandler { std::mutex buffer_mutex_; }; + } // namespace camera_windows -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEXTURE_HANDLER_H_ \ No newline at end of file +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEXTURE_HANDLER_H_ From ab9dfb426e9f553f6bf693d9526c235080cd665e Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 19:11:11 +0200 Subject: [PATCH 66/93] [camera_windows] fix camera tests --- .../camera/camera_windows/windows/test/camera_test.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp index d45fed1b3064..c8db84f2f2a3 100644 --- a/packages/camera/camera_windows/windows/test/camera_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -320,8 +320,10 @@ TEST(Camera, OnVideoRecordSucceededInvokesCameraChannelEvent) { .WillOnce( []() { return std::make_unique>(); }); - // TODO: test binary content - EXPECT_CALL(*binary_messenger, Send(Eq(camera_channel), _, _, _)).Times(1); + // TODO: test binary content. + // First time is video record success message, + // and second is camera closing message. + EXPECT_CALL(*binary_messenger, Send(Eq(camera_channel), _, _, _)).Times(2); // Init camera with mock capture controller factory camera->InitCamera(std::move(capture_controller_factory), @@ -333,6 +335,9 @@ TEST(Camera, OnVideoRecordSucceededInvokesCameraChannelEvent) { camera->OnCreateCaptureEngineSucceeded(camera_id); camera->OnVideoRecordSucceeded(file_path, video_duration); + + // Dispose camera before message channel. + camera = nullptr; } } // namespace test From 7f0f9b62fbb3e59b3407458e30b4e200a6b6f679 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 19:30:40 +0200 Subject: [PATCH 67/93] [camera_windows] fix integration tests --- .../example/integration_test/camera_test.dart | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/camera/camera_windows/example/integration_test/camera_test.dart b/packages/camera/camera_windows/example/integration_test/camera_test.dart index 3b5a346545dc..ee1121b3b8b8 100644 --- a/packages/camera/camera_windows/example/integration_test/camera_test.dart +++ b/packages/camera/camera_windows/example/integration_test/camera_test.dart @@ -32,7 +32,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.takePicture(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -42,7 +42,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.startVideoRecording(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -52,7 +52,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.stopVideoRecording(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -62,7 +62,7 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.pausePreview(1234), - throwsA(isA())); + throwsA(isA())); }); }); @@ -72,26 +72,18 @@ void main() { final CameraPlatform camera = CameraPlatform.instance; expect(() async => await camera.resumePreview(1234), - throwsA(isA())); + throwsA(isA())); }); }); group('onDeviceOrientationChanged', () { - testWidgets('emits the initial DeviceOrientationChangedEvent', - (WidgetTester _) async { + testWidgets('returns empty stream', (WidgetTester _) async { final Stream eventStream = CameraPlatform.instance.onDeviceOrientationChanged(); - final StreamQueue streamQueue = - StreamQueue(eventStream); - expect( - await streamQueue.next, - equals( - const DeviceOrientationChangedEvent( - DeviceOrientation.landscapeRight, - ), - ), + await eventStream.isEmpty, + equals(true), ); }); }); From 54e445d24270b57112a681d4a0991f66ed4aef04 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 20:00:11 +0200 Subject: [PATCH 68/93] [camera_windows] handle both 32bit and 64bit camera ids --- .../camera_windows/windows/camera_plugin.cpp | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 657db51df9db..769ccaf2cc4b 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -69,6 +69,20 @@ const EncodableValue* ValueOrNull(const EncodableMap& map, const char* key) { return &(it->second); } +// Looks for |key| in |map|, returning the associated int64 value if it is +// present, or a nullptr if not. +const int64_t* Int64OrNull(const EncodableMap& map, const char* key) { + auto value = ValueOrNull(map, key); + if (!value) { + return nullptr; + } + + if (std::holds_alternative(*value)) { + return (int64_t*)std::get_if(value); + } + return std::get_if(value); +} + // Parses resolution preset argument to enum value. ResolutionPreset ParseResolutionPreset(const std::string& resolution_preset) { if (resolution_preset.compare(kResolutionPresetValueLow) == 0) { @@ -387,7 +401,7 @@ void CameraPlugin::CreateMethodHandler( void CameraPlugin::InitializeMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + auto camera_id = Int64OrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -413,7 +427,7 @@ void CameraPlugin::InitializeMethodHandler( void CameraPlugin::PausePreviewMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + auto camera_id = Int64OrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -439,7 +453,7 @@ void CameraPlugin::PausePreviewMethodHandler( void CameraPlugin::ResumePreviewMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + auto camera_id = Int64OrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -465,7 +479,7 @@ void CameraPlugin::ResumePreviewMethodHandler( void CameraPlugin::StartVideoRecordingMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + auto camera_id = Int64OrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -506,7 +520,7 @@ void CameraPlugin::StartVideoRecordingMethodHandler( void CameraPlugin::StopVideoRecordingMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + auto camera_id = Int64OrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -532,7 +546,7 @@ void CameraPlugin::StopVideoRecordingMethodHandler( void CameraPlugin::TakePictureMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + auto camera_id = Int64OrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -563,7 +577,7 @@ void CameraPlugin::TakePictureMethodHandler( void CameraPlugin::DisposeMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = std::get_if(ValueOrNull(args, kCameraIdKey)); + auto camera_id = Int64OrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); From 1004d9fa7ddeac56ef491b17ac330ef6232ad251 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 22:52:11 +0200 Subject: [PATCH 69/93] [camera_windows] update readme and todos --- packages/camera/camera_windows/README.md | 62 +++++++++++-------- .../camera_windows/lib/camera_windows.dart | 26 ++++---- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/packages/camera/camera_windows/README.md b/packages/camera/camera_windows/README.md index 9f549dfc3ad5..1f38464e3f9f 100644 --- a/packages/camera/camera_windows/README.md +++ b/packages/camera/camera_windows/README.md @@ -1,55 +1,67 @@ # Camera Windows Plugin -The windows implementation of [`camera`][camera]. +The Windows implementation of [`camera`][camera]. *Note*: This plugin is under development. -See [missing implementations and limitations](#limitations-on-the-windows-platform) +See [missing implementations and limitations](#limitations-on-the-windows-platform). ## Usage ### Depend on the package -This package is not an [endorsed][endorsed-federated-plugin] +This package is not an [endorsed][endorsed-federated-plugin] implementation of the [`camera`][camera] plugin, so you'll need to -[add it explicitly][install] +[add it explicitly][install]. -## Limitations on the Windows platform +## Missing features on the Windows platform ### Device orientation -The device orientation is not detected for cameras. +The device orientation detection +is not yet implemented: [issue #97540][device-orientation-issue]. -- `CameraPlatform.onDeviceOrientationChanged` stream always -returns the following value: `DeviceOrientation.landscapeRight` +### Pause and Resume video recording -### Taking a picture +Pausing and resuming the video recording +is not supported due to Win32 API limitations. -Captured pictures are saved to default `Pictures` folder. -This folder cannot be changed at the moment. +### Exposure mode, point and offset -### Video recording +Support for explosure mode and offset +is not yet implemented: [issue #97537][camera-control-issue]. -Captures videos are saved to default `Videos` folder. -This folder cannot be changed at the moment. +Explosure points are not supported due to +the current limitations of the Win32 API. -Video recording does not work if preview is not started. -If preview is not drawn on the screen it is recommended to pause preview -to avoid unnecessary processing of the textures while recording. +### Focus mode and point -Pausing and resuming the video recording is not supported. +Support for explosure mode and offset +is not yet implemented: [issue #97537][camera-control-issue]. -### Other limitations +### Flash mode -The windows implementation of [`camera`][camera] -is missing the following features: +Support for flash mode is not yet implemented: [issue #97537][camera-control-issue]. -- Exposure mode, point and offset -- Focus mode and point -- Image format group -- Streaming of frames +Focus points are not supported due to +the current limitations of the Win32 API. + +### Streaming of frames + +Support for image streaming is not yet implemented: [issue #97542][image-streams-issue]. + + +## Error handling + +Camera errors can be listened using platforms `onCameraError` method. + +Listening to errors is important, and in certain situations, +disposing of the camera is the only way to reset the situation. [camera]: https://pub.dev/packages/camera [endorsed-federated-plugin]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [install]: https://pub.dev/packages/camera_windows/install +[camera-control-issue]: https://github.com/flutter/flutter/issues/97537 +[device-orientation-issue]: https://github.com/flutter/flutter/issues/97540 +[image-streams-issue]: https://github.com/flutter/flutter/issues/97542 \ No newline at end of file diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index ea12f8eb513a..7243e3210330 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -175,7 +175,7 @@ class CameraWindows extends CameraPlatform { @override Stream onDeviceOrientationChanged() { - // TODO(jokerttu): Implement device orientation detection. + // TODO(jokerttu): Implement device orientation detection, https://github.com/flutter/flutter/issues/97540. // // Returns empty stream. return const Stream.empty(); @@ -186,13 +186,13 @@ class CameraWindows extends CameraPlatform { int cameraId, DeviceOrientation orientation, ) async { - // TODO(jokerttu): Implement lock capture orientation feature. + // TODO(jokerttu): Implement lock capture orientation feature, https://github.com/flutter/flutter/issues/97540. throw UnimplementedError('lockCaptureOrientation() is not implemented.'); } @override Future unlockCaptureOrientation(int cameraId) async { - // TODO(jokerttu): Implement unlock capture orientation feature. + // TODO(jokerttu): Implement unlock capture orientation feature, https://github.com/flutter/flutter/issues/97540. throw UnimplementedError('unlockCaptureOrientation() is not implemented.'); } @@ -251,13 +251,13 @@ class CameraWindows extends CameraPlatform { @override Future setFlashMode(int cameraId, FlashMode mode) async { - // TODO(jokerttu): Implement flash mode support + // TODO(jokerttu): Implement flash mode support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setFlashMode() is not implemented.'); } @override Future setExposureMode(int cameraId, ExposureMode mode) async { - // TODO(jokerttu): Implement explosure mode support + // TODO(jokerttu): Implement explosure mode support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setExposureMode() is not implemented.'); } @@ -272,34 +272,34 @@ class CameraWindows extends CameraPlatform { @override Future getMinExposureOffset(int cameraId) async { - // TODO(jokerttu): Implement exposure offset support. + // TODO(jokerttu): Implement exposure control support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 0.0; } @override Future getMaxExposureOffset(int cameraId) async { - // TODO(jokerttu): Implement exposure offset support. + // TODO(jokerttu): Implement exposure control support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 0.0; } @override Future getExposureOffsetStepSize(int cameraId) async { - // TODO(jokerttu): Implement exposure offset support. + // TODO(jokerttu): Implement exposure control support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 1.0; } @override Future setExposureOffset(int cameraId, double offset) async { - // TODO(jokerttu): Implement exposure offset support. + // TODO(jokerttu): Implement exposure control support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setExposureOffset() is not implemented.'); } @override Future setFocusMode(int cameraId, FocusMode mode) async { - // TODO(jokerttu): Implement focus mode support. + // TODO(jokerttu): Implement focus mode support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setFocusMode() is not implemented.'); } @@ -314,21 +314,21 @@ class CameraWindows extends CameraPlatform { @override Future getMinZoomLevel(int cameraId) async { - // TODO(jokerttu): Implement zoom level support. + // TODO(jokerttu): Implement zoom level support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 1.0; } @override Future getMaxZoomLevel(int cameraId) async { - // TODO(jokerttu): Implement zoom level support. + // TODO(jokerttu): Implement zoom level support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 1.0; } @override Future setZoomLevel(int cameraId, double zoom) async { - // TODO(jokerttu): Implement zoom level support. + // TODO(jokerttu): Implement zoom level support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setZoomLevel() is not implemented.'); } From 059216f61a831e16939e5e41a22f03a0a1f2961d Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 23:09:23 +0200 Subject: [PATCH 70/93] [camera_windows] update version number --- 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 29f648fd17f1..1318780830f8 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,3 +1,3 @@ -## 0.0.1 +## 0.1.0 * Initial release diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 099af4dfeb77..b77b9c17bb79 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -1,6 +1,6 @@ name: camera_windows description: A Flutter plugin for getting information about and controlling the camera on Windows. -version: 0.0.1 +version: 0.1.0 repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 From 9ae75fbfb47d3f761bbdb452dc95b36e21bc6e60 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Mon, 31 Jan 2022 23:09:47 +0200 Subject: [PATCH 71/93] [camera_windows] minor fixes to comments --- .../camera_windows/example/integration_test/camera_test.dart | 5 +++-- packages/camera/camera_windows/lib/camera_windows.dart | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/camera/camera_windows/example/integration_test/camera_test.dart b/packages/camera/camera_windows/example/integration_test/camera_test.dart index ee1121b3b8b8..faa189e2bd50 100644 --- a/packages/camera/camera_windows/example/integration_test/camera_test.dart +++ b/packages/camera/camera_windows/example/integration_test/camera_test.dart @@ -3,15 +3,16 @@ // found in the LICENSE file. import 'dart:async'; -import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; // Note that these integration tests do not currently cover -// all features and code paths, as they can only be tested if +// most features and code paths, as they can only be tested if // one or more cameras are available in the test environment. +// Native unit tests with better coverage are available at +// the native part of the plugin implementation. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 7243e3210330..fbc85c72f782 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -153,8 +153,6 @@ class CameraWindows extends CameraPlatform { /// during capture so these events are never send from the platform. /// Support for changing resolution should be implemented, if support for /// requesting resolution change is added to camera platform interface. - /// - /// Returns empty stream. return const Stream.empty(); } @@ -176,8 +174,6 @@ class CameraWindows extends CameraPlatform { @override Stream onDeviceOrientationChanged() { // TODO(jokerttu): Implement device orientation detection, https://github.com/flutter/flutter/issues/97540. - // - // Returns empty stream. return const Stream.empty(); } From 0c6ebc3e2face7aa31616821450b9f03933324db Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 1 Feb 2022 00:35:29 +0200 Subject: [PATCH 72/93] [camera_windows] revert device orientation change - Fixed camera aspect ratio on camera plugin example --- .../example/integration_test/camera_test.dart | 15 ++++++++++++--- .../camera/camera_windows/lib/camera_windows.dart | 5 ++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera_windows/example/integration_test/camera_test.dart b/packages/camera/camera_windows/example/integration_test/camera_test.dart index faa189e2bd50..cda0f402de6c 100644 --- a/packages/camera/camera_windows/example/integration_test/camera_test.dart +++ b/packages/camera/camera_windows/example/integration_test/camera_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -78,13 +79,21 @@ void main() { }); group('onDeviceOrientationChanged', () { - testWidgets('returns empty stream', (WidgetTester _) async { + testWidgets('emits the initial DeviceOrientationChangedEvent', + (WidgetTester _) async { final Stream eventStream = CameraPlatform.instance.onDeviceOrientationChanged(); + final StreamQueue streamQueue = + StreamQueue(eventStream); + expect( - await eventStream.isEmpty, - equals(true), + await streamQueue.next, + equals( + const DeviceOrientationChangedEvent( + DeviceOrientation.landscapeRight, + ), + ), ); }); }); diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index fbc85c72f782..ddcd5e5f1680 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -174,7 +174,10 @@ class CameraWindows extends CameraPlatform { @override Stream onDeviceOrientationChanged() { // TODO(jokerttu): Implement device orientation detection, https://github.com/flutter/flutter/issues/97540. - return const Stream.empty(); + // Force device orientation to landscape as by default camera plugin uses portraitUp orientation. + return Stream.value( + const DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight), + ); } @override From 3414b91f390383795c5f7a480eeb8549b63b68fc Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 1 Feb 2022 00:39:02 +0200 Subject: [PATCH 73/93] [camera_windows] fix error type - Use UnimplementedError instead of MissingPluginException --- packages/camera/camera_windows/lib/camera_windows.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index ddcd5e5f1680..48cf7e10d027 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -405,7 +405,7 @@ class CameraWindows extends CameraPlatform { ); break; default: - throw MissingPluginException(); + throw UnimplementedError(); } } From 3852a6142812f7c09f8045ed1bbe877ab6bfed2d Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 1 Feb 2022 08:48:57 +0200 Subject: [PATCH 74/93] [camera_windows] fix example error handling --- .../camera_windows/example/lib/main.dart | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index 30642b4e46da..6758d186655d 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -162,27 +162,28 @@ class _MyAppState extends State { } Future _disposeCurrentCamera() async { - assert(_cameraId > 0); - assert(_initialized); - try { - await CameraPlatform.instance.dispose(_cameraId); + if (_cameraId >= 0 && _initialized) { + try { + await CameraPlatform.instance.dispose(_cameraId); - if (mounted) { - setState(() { - _initialized = false; - _cameraId = -1; - _previewSize = null; - _recording = false; - _recordingTimed = false; - _previewPaused = false; - _cameraInfo = 'Camera disposed'; - }); - } - } on CameraException catch (e) { - if (mounted) { - setState(() { - _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.description}'; - }); + if (mounted) { + setState(() { + _initialized = false; + _cameraId = -1; + _previewSize = null; + _recording = false; + _recordingTimed = false; + _previewPaused = false; + _cameraInfo = 'Camera disposed'; + }); + } + } on CameraException catch (e) { + if (mounted) { + setState(() { + _cameraInfo = + 'Failed to dispose camera: ${e.code}: ${e.description}'; + }); + } } } } From 0e76d0361ce8ee1728d6537cca3f15d1f746518b Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 1 Feb 2022 08:53:00 +0200 Subject: [PATCH 75/93] [camera_windows] language check for README --- packages/camera/camera_windows/README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/camera/camera_windows/README.md b/packages/camera/camera_windows/README.md index 1f38464e3f9f..dc27bcc85e9d 100644 --- a/packages/camera/camera_windows/README.md +++ b/packages/camera/camera_windows/README.md @@ -3,7 +3,7 @@ The Windows implementation of [`camera`][camera]. *Note*: This plugin is under development. -See [missing implementations and limitations](#limitations-on-the-windows-platform). +See [missing implementations and limitations](#missing-features-on-the-windows-platform). ## Usage @@ -17,21 +17,21 @@ implementation of the [`camera`][camera] plugin, so you'll need to ### Device orientation -The device orientation detection +Device orientation detection is not yet implemented: [issue #97540][device-orientation-issue]. ### Pause and Resume video recording Pausing and resuming the video recording -is not supported due to Win32 API limitations. +is not supported due to Windows API limitations. ### Exposure mode, point and offset Support for explosure mode and offset is not yet implemented: [issue #97537][camera-control-issue]. -Explosure points are not supported due to -the current limitations of the Win32 API. +Exposure points are not supported due to +limitations of the Windows API. ### Focus mode and point @@ -43,16 +43,15 @@ is not yet implemented: [issue #97537][camera-control-issue]. Support for flash mode is not yet implemented: [issue #97537][camera-control-issue]. Focus points are not supported due to -the current limitations of the Win32 API. +current limitations of the Windows API. ### Streaming of frames Support for image streaming is not yet implemented: [issue #97542][image-streams-issue]. - ## Error handling -Camera errors can be listened using platforms `onCameraError` method. +Camera errors can be listened using the platform's `onCameraError` method. Listening to errors is important, and in certain situations, disposing of the camera is the only way to reset the situation. From c895ff4428973cc200c4cb9fac17844f38291490 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 1 Feb 2022 14:51:19 +0200 Subject: [PATCH 76/93] [camera_windows] code style fixes --- .../camera_windows/windows/CMakeLists.txt | 4 +- .../camera/camera_windows/windows/camera.cpp | 34 +++-- .../camera/camera_windows/windows/camera.h | 32 ++-- .../camera_windows/windows/camera_plugin.cpp | 140 +++++++++--------- .../windows/capture_controller.cpp | 30 ++-- .../windows/capture_controller.h | 59 +++----- ...evice_info.cpp => capture_device_info.cpp} | 19 ++- .../windows/capture_device_info.h | 49 ++++++ .../windows/capture_engine_listener.h | 12 +- .../camera_windows/windows/com_heap_ptr.h | 14 +- .../camera_windows/windows/device_info.h | 25 ---- .../camera_windows/windows/photo_handler.cpp | 7 +- .../camera_windows/windows/photo_handler.h | 22 ++- .../windows/preview_handler.cpp | 24 +-- .../camera_windows/windows/preview_handler.h | 34 ++--- .../camera_windows/windows/record_handler.cpp | 20 +-- .../camera_windows/windows/record_handler.h | 29 ++-- .../windows/test/camera_plugin_test.cpp | 36 ++--- .../windows/test/camera_test.cpp | 37 +++-- .../windows/test/capture_controller_test.cpp | 5 +- .../windows/texture_handler.cpp | 1 - .../camera_windows/windows/texture_handler.h | 6 +- 22 files changed, 320 insertions(+), 319 deletions(-) rename packages/camera/camera_windows/windows/{device_info.cpp => capture_device_info.cpp} (50%) create mode 100644 packages/camera/camera_windows/windows/capture_device_info.h delete mode 100644 packages/camera/camera_windows/windows/device_info.h diff --git a/packages/camera/camera_windows/windows/CMakeLists.txt b/packages/camera/camera_windows/windows/CMakeLists.txt index a71a4cbabbab..caeb1095f5a5 100644 --- a/packages/camera/camera_windows/windows/CMakeLists.txt +++ b/packages/camera/camera_windows/windows/CMakeLists.txt @@ -18,8 +18,8 @@ list(APPEND PLUGIN_SOURCES "capture_engine_listener.cpp" "string_utils.h" "string_utils.cpp" - "device_info.h" - "device_info.cpp" + "capture_device_info.h" + "capture_device_info.cpp" "preview_handler.h" "preview_handler.cpp" "record_handler.h" diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index b389e43f90b6..6336116a43ae 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -114,7 +114,7 @@ void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { // Use texture id as camera id camera_id_ = texture_id; auto pending_result = - GetPendingResultByType(PendingResultType::CREATE_CAMERA); + GetPendingResultByType(PendingResultType::kCreateCamera); if (pending_result) { pending_result->Success(EncodableMap( {{EncodableValue("cameraId"), EncodableValue(texture_id)}})); @@ -123,24 +123,26 @@ void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { void CameraImpl::OnCreateCaptureEngineFailed(const std::string& error) { auto pending_result = - GetPendingResultByType(PendingResultType::CREATE_CAMERA); + GetPendingResultByType(PendingResultType::kCreateCamera); if (pending_result) { pending_result->Error("camera_error", error); } } void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { - auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); + auto pending_result = GetPendingResultByType(PendingResultType::kInitialize); if (pending_result) { pending_result->Success(EncodableValue(EncodableMap({ - {EncodableValue("previewWidth"), EncodableValue((float)width)}, - {EncodableValue("previewHeight"), EncodableValue((float)height)}, + {EncodableValue("previewWidth"), + EncodableValue(static_cast(width))}, + {EncodableValue("previewHeight"), + EncodableValue(static_cast(height))}, }))); } }; void CameraImpl::OnStartPreviewFailed(const std::string& error) { - auto pending_result = GetPendingResultByType(PendingResultType::INITIALIZE); + auto pending_result = GetPendingResultByType(PendingResultType::kInitialize); if (pending_result) { pending_result->Error("camera_error", error); } @@ -148,7 +150,7 @@ void CameraImpl::OnStartPreviewFailed(const std::string& error) { void CameraImpl::OnResumePreviewSucceeded() { auto pending_result = - GetPendingResultByType(PendingResultType::RESUME_PREVIEW); + GetPendingResultByType(PendingResultType::kResumePreview); if (pending_result) { pending_result->Success(); } @@ -156,7 +158,7 @@ void CameraImpl::OnResumePreviewSucceeded() { void CameraImpl::OnResumePreviewFailed(const std::string& error) { auto pending_result = - GetPendingResultByType(PendingResultType::RESUME_PREVIEW); + GetPendingResultByType(PendingResultType::kResumePreview); if (pending_result) { pending_result->Error("camera_error", error); } @@ -164,7 +166,7 @@ void CameraImpl::OnResumePreviewFailed(const std::string& error) { void CameraImpl::OnPausePreviewSucceeded() { auto pending_result = - GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); + GetPendingResultByType(PendingResultType::kPausePreview); if (pending_result) { pending_result->Success(); } @@ -172,42 +174,42 @@ void CameraImpl::OnPausePreviewSucceeded() { void CameraImpl::OnPausePreviewFailed(const std::string& error) { auto pending_result = - GetPendingResultByType(PendingResultType::PAUSE_PREVIEW); + GetPendingResultByType(PendingResultType::kPausePreview); if (pending_result) { pending_result->Error("camera_error", error); } } void CameraImpl::OnStartRecordSucceeded() { - auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); + auto pending_result = GetPendingResultByType(PendingResultType::kStartRecord); if (pending_result) { pending_result->Success(); } }; void CameraImpl::OnStartRecordFailed(const std::string& error) { - auto pending_result = GetPendingResultByType(PendingResultType::START_RECORD); + auto pending_result = GetPendingResultByType(PendingResultType::kStartRecord); if (pending_result) { pending_result->Error("camera_error", error); } }; void CameraImpl::OnStopRecordSucceeded(const std::string& file_path) { - auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); + auto pending_result = GetPendingResultByType(PendingResultType::kStopRecord); if (pending_result) { pending_result->Success(EncodableValue(file_path)); } }; void CameraImpl::OnStopRecordFailed(const std::string& error) { - auto pending_result = GetPendingResultByType(PendingResultType::STOP_RECORD); + auto pending_result = GetPendingResultByType(PendingResultType::kStopRecord); if (pending_result) { pending_result->Error("camera_error", error); } }; void CameraImpl::OnTakePictureSucceeded(const std::string& file_path) { - auto pending_result = GetPendingResultByType(PendingResultType::TAKE_PICTURE); + auto pending_result = GetPendingResultByType(PendingResultType::kTakePicture); if (pending_result) { pending_result->Success(EncodableValue(file_path)); } @@ -215,7 +217,7 @@ void CameraImpl::OnTakePictureSucceeded(const std::string& file_path) { void CameraImpl::OnTakePictureFailed(const std::string& error) { auto pending_take_picture_result = - GetPendingResultByType(PendingResultType::TAKE_PICTURE); + GetPendingResultByType(PendingResultType::kTakePicture); if (pending_take_picture_result) { pending_take_picture_result->Error("camera_error", error); } diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 6d4a350b2808..e7cda22c10e6 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -18,19 +18,19 @@ using flutter::EncodableMap; using flutter::MethodChannel; using flutter::MethodResult; -enum PendingResultType { - CREATE_CAMERA, - INITIALIZE, - TAKE_PICTURE, - START_RECORD, - STOP_RECORD, - PAUSE_PREVIEW, - RESUME_PREVIEW, +enum class PendingResultType { + kCreateCamera, + kInitialize, + kTakePicture, + kStartRecord, + kStopRecord, + kPausePreview, + kResumePreview, }; class Camera : public CaptureControllerListener { public: - Camera(const std::string& device_id){}; + explicit Camera(const std::string& device_id) {} virtual ~Camera() = default; // Disallow copy and move. @@ -63,7 +63,7 @@ class Camera : public CaptureControllerListener { class CameraImpl : public Camera { public: - CameraImpl(const std::string& device_id); + explicit CameraImpl(const std::string& device_id); virtual ~CameraImpl(); // Disallow copy and move. @@ -93,16 +93,16 @@ class CameraImpl : public Camera { // Camera bool HasDeviceId(std::string& device_id) override { return device_id_ == device_id; - }; + } bool HasCameraId(int64_t camera_id) override { return camera_id_ == camera_id; - }; + } bool AddPendingResult(PendingResultType type, std::unique_ptr> result) override; bool HasPendingResultByType(PendingResultType type) override; camera_windows::CaptureController* GetCaptureController() override { return capture_controller_.get(); - }; + } void InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) override; @@ -147,7 +147,7 @@ class CameraImpl : public Camera { class CameraFactory { public: - CameraFactory(){}; + CameraFactory() {} virtual ~CameraFactory() = default; // Disallow copy and move. @@ -161,7 +161,7 @@ class CameraFactory { class CameraFactoryImpl : public CameraFactory { public: - CameraFactoryImpl(){}; + CameraFactoryImpl() {} virtual ~CameraFactoryImpl() = default; // Disallow copy and move. @@ -170,7 +170,7 @@ class CameraFactoryImpl : public CameraFactory { std::unique_ptr CreateCamera(const std::string& device_id) override { return std::make_unique(device_id); - }; + } }; } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 769ccaf2cc4b..5074209291a3 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -18,8 +18,8 @@ #include #include +#include "capture_device_info.h" #include "com_heap_ptr.h" -#include "device_info.h" #include "string_utils.h" namespace camera_windows { @@ -70,35 +70,40 @@ const EncodableValue* ValueOrNull(const EncodableMap& map, const char* key) { } // Looks for |key| in |map|, returning the associated int64 value if it is -// present, or a nullptr if not. -const int64_t* Int64OrNull(const EncodableMap& map, const char* key) { +// present, or std::nullopt if not. +std::optional GetInt64ValueOrNull(const EncodableMap& map, + const char* key) { auto value = ValueOrNull(map, key); if (!value) { - return nullptr; + return std::nullopt; } if (std::holds_alternative(*value)) { - return (int64_t*)std::get_if(value); + return static_cast(std::get(*value)); + } + auto val64 = std::get_if(value); + if (!val64) { + return std::nullopt; } - return std::get_if(value); + return *val64; } // Parses resolution preset argument to enum value. ResolutionPreset ParseResolutionPreset(const std::string& resolution_preset) { if (resolution_preset.compare(kResolutionPresetValueLow) == 0) { - return ResolutionPreset::RESOLUTION_PRESET_LOW; + return ResolutionPreset::kLow; } else if (resolution_preset.compare(kResolutionPresetValueMedium) == 0) { - return ResolutionPreset::RESOLUTION_PRESET_MEDIUM; + return ResolutionPreset::kMedium; } else if (resolution_preset.compare(kResolutionPresetValueHigh) == 0) { - return ResolutionPreset::RESOLUTION_PRESET_HIGH; + return ResolutionPreset::kHigh; } else if (resolution_preset.compare(kResolutionPresetValueVeryHigh) == 0) { - return ResolutionPreset::RESOLUTION_PRESET_VERY_HIGH; + return ResolutionPreset::kVeryHigh; } else if (resolution_preset.compare(kResolutionPresetValueUltraHigh) == 0) { - return ResolutionPreset::RESOLUTION_PRESET_ULTRA_HIGH; + return ResolutionPreset::kUltraHigh; } else if (resolution_preset.compare(kResolutionPresetValueMax) == 0) { - return ResolutionPreset::RESOLUTION_PRESET_MAX; + return ResolutionPreset::kMax; } - return ResolutionPreset::RESOLUTION_PRESET_AUTO; + return ResolutionPreset::kAuto; } // Builds CaptureDeviceInfo object from given device holding device name and id. @@ -123,8 +128,8 @@ std::unique_ptr GetDeviceInfo(IMFActivate* device) { return device_info; } - device_info->display_name = Utf8FromUtf16(std::wstring(name, name_size)); - device_info->device_id = Utf8FromUtf16(std::wstring(id, id_size)); + device_info->SetDisplayName(Utf8FromUtf16(std::wstring(name, name_size))); + device_info->SetDeviceID(Utf8FromUtf16(std::wstring(id, id_size))); return device_info; } @@ -135,7 +140,8 @@ std::string GetCurrentTimeString() { std::chrono::system_clock::now().time_since_epoch(); auto s = std::chrono::duration_cast(now).count(); - auto ms = std::chrono::duration_cast(now).count(); + auto ms = + std::chrono::duration_cast(now).count() % 1000; struct tm newtime; localtime_s(&newtime, &s); @@ -148,40 +154,38 @@ std::string GetCurrentTimeString() { time_start.resize(len); } - // Add milliseconds - return time_start + std::to_string(ms - s * 1000); + // Add milliseconds to make sure the filename is unique + return time_start + std::to_string(ms); } // Builds file path for picture capture. -bool GetFilePathForPicture(std::string& file_path) { +std::optional GetFilePathForPicture() { wchar_t* known_folder_path = nullptr; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Pictures, KF_FLAG_CREATE, nullptr, &known_folder_path); - - if (SUCCEEDED(hr)) { - std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); - - file_path = path + "\\" + "PhotoCapture_" + GetCurrentTimeString() + "." + - kPictureCaptureExtension; + if (FAILED(hr)) { + return std::nullopt; } - return SUCCEEDED(hr); + std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); + + return path + "\\" + "PhotoCapture_" + GetCurrentTimeString() + "." + + kPictureCaptureExtension; } // Builds file path for video capture. -bool GetFilePathForVideo(std::string& file_path) { +std::optional GetFilePathForVideo() { wchar_t* known_folder_path = nullptr; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Videos, KF_FLAG_CREATE, nullptr, &known_folder_path); - - if (SUCCEEDED(hr)) { - std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); - - file_path = path + "\\" + "VideoCapture_" + GetCurrentTimeString() + "." + - kVideoCaptureExtension; + if (FAILED(hr)) { + return std::nullopt; } - return SUCCEEDED(hr); + std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); + + return path + "\\" + "VideoCapture_" + GetCurrentTimeString() + "." + + kVideoCaptureExtension; } } // namespace @@ -325,7 +329,7 @@ void CameraPlugin::AvailableCamerasMethodHandler( EncodableList devices_list; for (UINT32 i = 0; i < count; ++i) { auto device_info = GetDeviceInfo(devices[i]); - auto deviceName = GetUniqueDeviceName(std::move(device_info)); + auto deviceName = device_info->GetUniqueDeviceName(); devices_list.push_back(EncodableMap({ {EncodableValue("name"), EncodableValue(deviceName)}, @@ -360,28 +364,29 @@ void CameraPlugin::CreateMethodHandler( return result->Error("argument_error", std::string(kCameraNameKey) + " argument missing"); } - auto device_info = ParseDeviceInfoFromCameraName(*camera_name); - if (!device_info) { + auto device_info = std::make_unique(); + if (!device_info->ParseDeviceInfoFromCameraName(*camera_name)) { return result->Error( "camera_error", "Cannot parse argument " + std::string(kCameraNameKey)); } - if (GetCameraByDeviceId(device_info->device_id)) { + auto device_id = device_info->GetDeviceId(); + if (GetCameraByDeviceId(device_id)) { return result->Error("camera_error", "Camera with given device id already exists. Existing " "camera must be disposed before creating it again."); } std::unique_ptr camera = - camera_factory_->CreateCamera(device_info->device_id); + camera_factory_->CreateCamera(device_id); - if (camera->HasPendingResultByType(PendingResultType::CREATE_CAMERA)) { + if (camera->HasPendingResultByType(PendingResultType::kCreateCamera)) { return result->Error("camera_error", "Pending camera creation request exists"); } - if (camera->AddPendingResult(PendingResultType::CREATE_CAMERA, + if (camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(result))) { // Parse resolution preset argument. const auto* resolution_preset_argument = @@ -390,7 +395,7 @@ void CameraPlugin::CreateMethodHandler( if (resolution_preset_argument) { resolution_preset = ParseResolutionPreset(*resolution_preset_argument); } else { - resolution_preset = ResolutionPreset::RESOLUTION_PRESET_AUTO; + resolution_preset = ResolutionPreset::kAuto; } camera->InitCamera(texture_registrar_, messenger_, *record_audio, @@ -401,7 +406,7 @@ void CameraPlugin::CreateMethodHandler( void CameraPlugin::InitializeMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = Int64OrNull(args, kCameraIdKey); + auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -412,12 +417,12 @@ void CameraPlugin::InitializeMethodHandler( return result->Error("camera_error", "Camera not created"); } - if (camera->HasPendingResultByType(PendingResultType::INITIALIZE)) { + if (camera->HasPendingResultByType(PendingResultType::kInitialize)) { return result->Error("camera_error", "Pending initialization request exists"); } - if (camera->AddPendingResult(PendingResultType::INITIALIZE, + if (camera->AddPendingResult(PendingResultType::kInitialize, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); @@ -427,7 +432,7 @@ void CameraPlugin::InitializeMethodHandler( void CameraPlugin::PausePreviewMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = Int64OrNull(args, kCameraIdKey); + auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -438,12 +443,12 @@ void CameraPlugin::PausePreviewMethodHandler( return result->Error("camera_error", "Camera not created"); } - if (camera->HasPendingResultByType(PendingResultType::PAUSE_PREVIEW)) { + if (camera->HasPendingResultByType(PendingResultType::kPausePreview)) { return result->Error("camera_error", "Pending pause preview request exists"); } - if (camera->AddPendingResult(PendingResultType::PAUSE_PREVIEW, + if (camera->AddPendingResult(PendingResultType::kPausePreview, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); @@ -453,7 +458,7 @@ void CameraPlugin::PausePreviewMethodHandler( void CameraPlugin::ResumePreviewMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = Int64OrNull(args, kCameraIdKey); + auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -464,12 +469,12 @@ void CameraPlugin::ResumePreviewMethodHandler( return result->Error("camera_error", "Camera not created"); } - if (camera->HasPendingResultByType(PendingResultType::RESUME_PREVIEW)) { + if (camera->HasPendingResultByType(PendingResultType::kResumePreview)) { return result->Error("camera_error", "Pending resume preview request exists"); } - if (camera->AddPendingResult(PendingResultType::RESUME_PREVIEW, + if (camera->AddPendingResult(PendingResultType::kResumePreview, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); @@ -479,7 +484,7 @@ void CameraPlugin::ResumePreviewMethodHandler( void CameraPlugin::StartVideoRecordingMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = Int64OrNull(args, kCameraIdKey); + auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -490,7 +495,7 @@ void CameraPlugin::StartVideoRecordingMethodHandler( return result->Error("camera_error", "Camera not created"); } - if (camera->HasPendingResultByType(PendingResultType::START_RECORD)) { + if (camera->HasPendingResultByType(PendingResultType::kStartRecord)) { return result->Error("camera_error", "Pending start recording request exists"); } @@ -503,14 +508,13 @@ void CameraPlugin::StartVideoRecordingMethodHandler( max_video_duration_ms = *requested_max_video_duration_ms; } - std::string path; - if (GetFilePathForVideo(path)) { - if (camera->AddPendingResult(PendingResultType::START_RECORD, + std::optional path = GetFilePathForVideo(); + if (path) { + if (camera->AddPendingResult(PendingResultType::kStartRecord, std::move(result))) { - auto str_path = std::string(path); auto cc = camera->GetCaptureController(); assert(cc); - cc->StartRecord(str_path, max_video_duration_ms); + cc->StartRecord(*path, max_video_duration_ms); } } else { return result->Error("system_error", @@ -520,7 +524,7 @@ void CameraPlugin::StartVideoRecordingMethodHandler( void CameraPlugin::StopVideoRecordingMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = Int64OrNull(args, kCameraIdKey); + auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -531,12 +535,12 @@ void CameraPlugin::StopVideoRecordingMethodHandler( return result->Error("camera_error", "Camera not created"); } - if (camera->HasPendingResultByType(PendingResultType::STOP_RECORD)) { + if (camera->HasPendingResultByType(PendingResultType::kStopRecord)) { return result->Error("camera_error", "Pending stop recording request exists"); } - if (camera->AddPendingResult(PendingResultType::STOP_RECORD, + if (camera->AddPendingResult(PendingResultType::kStopRecord, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); @@ -546,7 +550,7 @@ void CameraPlugin::StopVideoRecordingMethodHandler( void CameraPlugin::TakePictureMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = Int64OrNull(args, kCameraIdKey); + auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); @@ -557,17 +561,17 @@ void CameraPlugin::TakePictureMethodHandler( return result->Error("camera_error", "Camera not created"); } - if (camera->HasPendingResultByType(PendingResultType::TAKE_PICTURE)) { + if (camera->HasPendingResultByType(PendingResultType::kTakePicture)) { return result->Error("camera_error", "Pending take picture request exists"); } - std::string path; - if (GetFilePathForPicture(path)) { - if (camera->AddPendingResult(PendingResultType::TAKE_PICTURE, + std::optional path = GetFilePathForPicture(); + if (path) { + if (camera->AddPendingResult(PendingResultType::kTakePicture, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); - cc->TakePicture(path); + cc->TakePicture(*path); } } else { return result->Error("system_error", @@ -577,7 +581,7 @@ void CameraPlugin::TakePictureMethodHandler( void CameraPlugin::DisposeMethodHandler( const EncodableMap& args, std::unique_ptr> result) { - auto camera_id = Int64OrNull(args, kCameraIdKey); + auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 29b92c798941..33f7614ec8db 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -265,7 +265,7 @@ void CaptureControllerImpl::ResetCaptureController() { // States media_foundation_started_ = false; - capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; + capture_engine_state_ = CaptureEngineState::kNotInitialized; preview_frame_width_ = 0; preview_frame_height_ = 0; capture_engine_callback_handler_ = nullptr; @@ -296,13 +296,12 @@ void CaptureControllerImpl::InitCaptureDevice( if (IsInitialized()) { return capture_controller_listener_->OnCreateCaptureEngineFailed( "Capture device already initialized"); - } else if (capture_engine_state_ == - CaptureEngineState::CAPTURE_ENGINE_INITIALIZING) { + } else if (capture_engine_state_ == CaptureEngineState::kInitializing) { return capture_controller_listener_->OnCreateCaptureEngineFailed( "Capture device already initializing"); } - capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_INITIALIZING; + capture_engine_state_ = CaptureEngineState::kInitializing; resolution_preset_ = resolution_preset; record_audio_ = record_audio; texture_registrar_ = texture_registrar; @@ -364,22 +363,23 @@ void CaptureControllerImpl::TakePicture(const std::string file_path) { uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { switch (resolution_preset_) { - case RESOLUTION_PRESET_LOW: + case ResolutionPreset::kLow: return 240; break; - case RESOLUTION_PRESET_MEDIUM: + case ResolutionPreset::kMedium: return 480; break; - case RESOLUTION_PRESET_HIGH: + case ResolutionPreset::kHigh: return 720; break; - case RESOLUTION_PRESET_VERY_HIGH: + case ResolutionPreset::kVeryHigh: return 1080; break; - case RESOLUTION_PRESET_ULTRA_HIGH: + case ResolutionPreset::kUltraHigh: return 2160; break; - case RESOLUTION_PRESET_AUTO: + case ResolutionPreset::kMax: + case ResolutionPreset::kAuto: default: // no limit. return 0xffffffff; @@ -468,8 +468,8 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { // Find base media type for record and photo capture. if (!FindBestMediaType( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, - source.Get(), base_capture_media_type_.GetAddressOf(), - (uint32_t)0xffffffff, nullptr, nullptr)) { + source.Get(), base_capture_media_type_.GetAddressOf(), 0xffffffff, + nullptr, nullptr)) { return E_FAIL; } @@ -651,8 +651,8 @@ void CaptureControllerImpl::ResumePreview() { // Called via IMFCaptureEngineOnEventCallback implementation. // Implements CaptureEngineObserver::OnEvent. void CaptureControllerImpl::OnEvent(IMFMediaEvent* event) { - if (!IsInitialized() && capture_engine_state_ != - CaptureEngineState::CAPTURE_ENGINE_INITIALIZING) { + if (!IsInitialized() && + capture_engine_state_ != CaptureEngineState::kInitializing) { return; } @@ -723,7 +723,7 @@ void CaptureControllerImpl::OnCaptureEngineInitialized( int64_t texture_id = texture_handler_->RegisterTexture(); if (texture_id >= 0) { capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id); - capture_engine_state_ = CaptureEngineState::CAPTURE_ENGINE_INITIALIZED; + capture_engine_state_ = CaptureEngineState::kInitialized; } else { capture_controller_listener_->OnCreateCaptureEngineFailed( "Failed to create texture_id"); diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 686feef82bc1..d014a71f0b64 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -28,34 +28,24 @@ namespace camera_windows { using flutter::TextureRegistrar; using Microsoft::WRL::ComPtr; -enum ResolutionPreset { - // AUTO - RESOLUTION_PRESET_AUTO, - +enum class ResolutionPreset { + // Automatic resolution + kAuto, // 240p (320x240) - RESOLUTION_PRESET_LOW, - + kLow, // 480p (720x480) - RESOLUTION_PRESET_MEDIUM, - + kMedium, // 720p (1280x720) - RESOLUTION_PRESET_HIGH, - + kHigh, // 1080p (1920x1080) - RESOLUTION_PRESET_VERY_HIGH, - + kVeryHigh, // 2160p (4096x2160) - RESOLUTION_PRESET_ULTRA_HIGH, - + kUltraHigh, // The highest resolution available. - RESOLUTION_PRESET_MAX, + kMax, }; -enum CaptureEngineState { - CAPTURE_ENGINE_NOT_INITIALIZED, - CAPTURE_ENGINE_INITIALIZING, - CAPTURE_ENGINE_INITIALIZED -}; +enum class CaptureEngineState { kNotInitialized, kInitializing, kInitialized }; class VideoCaptureDeviceEnumerator { private: @@ -65,7 +55,7 @@ class VideoCaptureDeviceEnumerator { class CaptureController { public: - CaptureController(){}; + CaptureController() {} virtual ~CaptureController() = default; // Disallow copy and move. @@ -123,7 +113,7 @@ class CaptureControllerImpl : public CaptureController, static bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count); - CaptureControllerImpl(CaptureControllerListener* listener); + explicit CaptureControllerImpl(CaptureControllerListener* listener); virtual ~CaptureControllerImpl(); // Disallow copy and move. @@ -147,8 +137,7 @@ class CaptureControllerImpl : public CaptureController, // CaptureEngineObserver void OnEvent(IMFMediaEvent* event) override; bool IsReadyForSample() override { - return capture_engine_state_ == - CaptureEngineState::CAPTURE_ENGINE_INITIALIZED && + return capture_engine_state_ == CaptureEngineState::kInitialized && preview_handler_ && preview_handler_->IsRunning(); } bool UpdateBuffer(uint8_t* data, uint32_t data_length) override; @@ -157,24 +146,23 @@ class CaptureControllerImpl : public CaptureController, // Sets capture engine, for testing purposes. void SetCaptureEngine(IMFCaptureEngine* capture_engine) { capture_engine_ = capture_engine; - }; + } // Sets video source, for testing purposes. void SetVideoSource(IMFMediaSource* video_source) { video_source_ = video_source; - }; + } // Sets audio source, for testing purposes. void SetAudioSource(IMFMediaSource* audio_source) { audio_source_ = audio_source; - }; + } private: // Helper function to return initialized state as boolean; bool IsInitialized() { - return capture_engine_state_ == - CaptureEngineState::CAPTURE_ENGINE_INITIALIZED; - }; + return capture_engine_state_ == CaptureEngineState::kInitialized; + } // Resets capture controller state. // This is called if capture engine creation fails or is disposed. @@ -241,9 +229,8 @@ class CaptureControllerImpl : public CaptureController, std::string video_device_id_; CaptureEngineState capture_engine_state_ = - CaptureEngineState::CAPTURE_ENGINE_NOT_INITIALIZED; - ResolutionPreset resolution_preset_ = - ResolutionPreset::RESOLUTION_PRESET_MEDIUM; + CaptureEngineState::kNotInitialized; + ResolutionPreset resolution_preset_ = ResolutionPreset::kMedium; ComPtr capture_engine_; ComPtr capture_engine_callback_handler_; ComPtr dxgi_device_manager_; @@ -258,7 +245,7 @@ class CaptureControllerImpl : public CaptureController, class CaptureControllerFactory { public: - CaptureControllerFactory(){}; + CaptureControllerFactory() {} virtual ~CaptureControllerFactory() = default; // Disallow copy and move. @@ -271,7 +258,7 @@ class CaptureControllerFactory { class CaptureControllerFactoryImpl : public CaptureControllerFactory { public: - CaptureControllerFactoryImpl(){}; + CaptureControllerFactoryImpl() {} virtual ~CaptureControllerFactoryImpl() = default; // Disallow copy and move. @@ -282,7 +269,7 @@ class CaptureControllerFactoryImpl : public CaptureControllerFactory { std::unique_ptr CreateCaptureController( CaptureControllerListener* listener) override { return std::make_unique(listener); - }; + } }; } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/device_info.cpp b/packages/camera/camera_windows/windows/capture_device_info.cpp similarity index 50% rename from packages/camera/camera_windows/windows/device_info.cpp rename to packages/camera/camera_windows/windows/capture_device_info.cpp index 6f07f5e14d8c..dab7d14e7298 100644 --- a/packages/camera/camera_windows/windows/device_info.cpp +++ b/packages/camera/camera_windows/windows/capture_device_info.cpp @@ -2,29 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device_info.h" +#include "capture_device_info.h" #include #include namespace camera_windows { -std::string GetUniqueDeviceName( - std::unique_ptr device_info) { - return device_info->display_name + " <" + device_info->device_id + ">"; +std::string CaptureDeviceInfo::GetUniqueDeviceName() { + return display_name_ + " <" + device_id_ + ">"; } -std::unique_ptr ParseDeviceInfoFromCameraName( +bool CaptureDeviceInfo::ParseDeviceInfoFromCameraName( const std::string& camera_name) { size_t delimeter_index = camera_name.rfind(' ', camera_name.length()); if (delimeter_index != std::string::npos) { auto deviceInfo = std::make_unique(); - deviceInfo->display_name = camera_name.substr(0, delimeter_index); - deviceInfo->device_id = camera_name.substr( - delimeter_index + 2, camera_name.length() - delimeter_index - 3); - return deviceInfo; + display_name_ = camera_name.substr(0, delimeter_index); + device_id_ = camera_name.substr(delimeter_index + 2, + camera_name.length() - delimeter_index - 3); + return true; } - return nullptr; + return false; } } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/capture_device_info.h b/packages/camera/camera_windows/windows/capture_device_info.h new file mode 100644 index 000000000000..7fef62302118 --- /dev/null +++ b/packages/camera/camera_windows/windows/capture_device_info.h @@ -0,0 +1,49 @@ +// 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_DEVICE_INFO_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ + +#include +#include + +namespace camera_windows { + +class CaptureDeviceInfo { + public: + CaptureDeviceInfo() {} + virtual ~CaptureDeviceInfo() = default; + + // Disallow copy and move. + CaptureDeviceInfo(const CaptureDeviceInfo&) = delete; + CaptureDeviceInfo& operator=(const CaptureDeviceInfo&) = delete; + + // Build unique device name from display name and device id. + // Format: "display_name ". + std::string GetUniqueDeviceName(); + + // Parses display name and device id from unique device name format. + // Format: "display_name ". + bool CaptureDeviceInfo::ParseDeviceInfoFromCameraName( + const std::string& camera_name); + + // Updates display name. + void SetDisplayName(const std::string& display_name) { + display_name_ = display_name; + } + + // Updates device id. + void SetDeviceID(const std::string& device_id) { device_id_ = device_id; } + + // Returns device id. + std::string GetDeviceId() { return device_id_; } + + private: + std::string display_name_; + std::string device_id_; +}; + +} // namespace camera_windows + +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 8ece3c7a1d42..05c975e0363d 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -7,6 +7,7 @@ #include +#include #include namespace camera_windows { @@ -29,13 +30,18 @@ class CaptureEngineObserver { virtual void UpdateCaptureTime(uint64_t capture_time) = 0; }; +// Listener for capture engine events and samples. +// Events are redirected for observers for processing. +// Samples are preprosessed and send to observer if it +// is ready to process samples. class CaptureEngineListener : public IMFCaptureEngineOnSampleCallback, public IMFCaptureEngineOnEventCallback { public: - CaptureEngineListener(CaptureEngineObserver* observer) - : observer_(observer) {} + CaptureEngineListener(CaptureEngineObserver* observer) : observer_(observer) { + assert(observer); + } - ~CaptureEngineListener(){}; + ~CaptureEngineListener() {} // Disallow copy and move. CaptureEngineListener(const CaptureEngineListener&) = delete; diff --git a/packages/camera/camera_windows/windows/com_heap_ptr.h b/packages/camera/camera_windows/windows/com_heap_ptr.h index 1075d4efe700..ca891d8348a6 100644 --- a/packages/camera/camera_windows/windows/com_heap_ptr.h +++ b/packages/camera/camera_windows/windows/com_heap_ptr.h @@ -15,30 +15,30 @@ namespace camera_windows { template class ComHeapPtr { public: - ComHeapPtr() : p_obj_(nullptr){}; - ComHeapPtr(T* p_obj) : p_obj_(p_obj){}; + ComHeapPtr() : p_obj_(nullptr) {} + ComHeapPtr(T* p_obj) : p_obj_(p_obj) {} // Frees memory on destruction. - ~ComHeapPtr() { Free(); }; + ~ComHeapPtr() { Free(); } // Prevent copying / ownership transfer as not currently needed. ComHeapPtr(ComHeapPtr const&) = delete; ComHeapPtr& operator=(ComHeapPtr const&) = delete; // Returns the pointer to the memory. - operator T*() { return p_obj_; }; + operator T*() { return p_obj_; } // Returns the pointer to the memory. T* operator->() { assert(p_obj_ != nullptr); return p_obj_; - }; + } // Returns the pointer to the memory. const T* operator->() const { assert(p_obj_ != nullptr); return p_obj_; - }; + } // Returns the pointer to the memory. T** operator&() { @@ -46,7 +46,7 @@ class ComHeapPtr { // Object can be released with Reset(nullptr). assert(p_obj_ == nullptr); return &p_obj_; - }; + } // Free the memory pointed to, and set the pointer to nullptr. void Free() { diff --git a/packages/camera/camera_windows/windows/device_info.h b/packages/camera/camera_windows/windows/device_info.h deleted file mode 100644 index 7206bebc5544..000000000000 --- a/packages/camera/camera_windows/windows/device_info.h +++ /dev/null @@ -1,25 +0,0 @@ -// 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_DEVICE_INFO_H_ -#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ - -#include -#include - -namespace camera_windows { - -struct CaptureDeviceInfo { - std::string display_name; - std::string device_id; -}; - -std::string GetUniqueDeviceName(std::unique_ptr device_info); - -std::unique_ptr ParseDeviceInfoFromCameraName( - const std::string& device_name); - -} // namespace camera_windows - -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ diff --git a/packages/camera/camera_windows/windows/photo_handler.cpp b/packages/camera/camera_windows/windows/photo_handler.cpp index f34bf08cb4bf..10df230c2cf2 100644 --- a/packages/camera/camera_windows/windows/photo_handler.cpp +++ b/packages/camera/camera_windows/windows/photo_handler.cpp @@ -103,6 +103,7 @@ HRESULT PhotoHandler::InitPhotoSink(IMFCaptureEngine* capture_engine, (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, photo_media_type.Get(), nullptr, &photo_sink_stream_index); if (FAILED(hr)) { + photo_sink_ = nullptr; return hr; } @@ -128,13 +129,13 @@ bool PhotoHandler::TakePhoto(const std::string& file_path, return false; } - photo_state_ = PhotoState::PHOTO_STATE__TAKING; + photo_state_ = PhotoState::kTakingPhoto; return SUCCEEDED(capture_engine->TakePhoto()); } void PhotoHandler::OnPhotoTaken() { - assert(photo_state_ == PhotoState::PHOTO_STATE__TAKING); - photo_state_ = PhotoState::PHOTO_STATE__IDLE; + assert(photo_state_ == PhotoState::kTakingPhoto); + photo_state_ = PhotoState::kIdle; } } // namespace camera_windows diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index c6ab07a7a26b..42bbaf14334a 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -17,15 +17,15 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -enum PhotoState { - PHOTO_STATE__NOT_STARTED, - PHOTO_STATE__IDLE, - PHOTO_STATE__TAKING, +enum class PhotoState { + kNotStarted, + kIdle, + kTakingPhoto, }; class PhotoHandler { public: - PhotoHandler(){}; + PhotoHandler() {} virtual ~PhotoHandler() = default; // Prevent copying. @@ -49,17 +49,13 @@ class PhotoHandler { void OnPhotoTaken(); // Returns true if photo state is idle - bool IsInitialized() { - return photo_state_ == PhotoState::PHOTO_STATE__IDLE; - }; + bool IsInitialized() { return photo_state_ == PhotoState::kIdle; } // Returns true if photo state is taking. - bool IsTakingPhoto() { - return photo_state_ == PhotoState::PHOTO_STATE__TAKING; - }; + bool IsTakingPhoto() { return photo_state_ == PhotoState::kTakingPhoto; } // Returns path to photo capture. - std::string GetPhotoPath() { return file_path_; }; + std::string GetPhotoPath() { return file_path_; } private: // Initializes record sink for video file capture. @@ -67,7 +63,7 @@ class PhotoHandler { IMFMediaType* base_media_type); std::string file_path_ = ""; - PhotoState photo_state_ = PhotoState::PHOTO_STATE__NOT_STARTED; + PhotoState photo_state_ = PhotoState::kNotStarted; ComPtr photo_sink_; }; diff --git a/packages/camera/camera_windows/windows/preview_handler.cpp b/packages/camera/camera_windows/windows/preview_handler.cpp index f909ef445215..d7fb2721259c 100644 --- a/packages/camera/camera_windows/windows/preview_handler.cpp +++ b/packages/camera/camera_windows/windows/preview_handler.cpp @@ -124,40 +124,40 @@ bool PreviewHandler::StartPreview(IMFCaptureEngine* capture_engine, return false; } - preview_state_ = PreviewState::PREVIEW_STATE__STARTING; + preview_state_ = PreviewState::kStarting; return SUCCEEDED(capture_engine->StartPreview()); } bool PreviewHandler::StopPreview(IMFCaptureEngine* capture_engine) { - if (preview_state_ == PreviewState::PREVIEW_STATE__STARTING || - preview_state_ == PreviewState::PREVIEW_STATE__RUNNING || - preview_state_ == PreviewState::PREVIEW_STATE__PAUSED) { - preview_state_ = PreviewState::PREVIEW_STATE__STOPPING; + if (preview_state_ == PreviewState::kStarting || + preview_state_ == PreviewState::kRunning || + preview_state_ == PreviewState::kPaused) { + preview_state_ = PreviewState::kStopping; return SUCCEEDED(capture_engine->StopPreview()); } return false; } bool PreviewHandler::PausePreview() { - if (preview_state_ != PreviewState::PREVIEW_STATE__RUNNING) { + if (preview_state_ != PreviewState::kRunning) { return false; } - preview_state_ = PreviewState::PREVIEW_STATE__PAUSED; + preview_state_ = PreviewState::kPaused; return true; } bool PreviewHandler::ResumePreview() { - if (preview_state_ != PreviewState::PREVIEW_STATE__PAUSED) { + if (preview_state_ != PreviewState::kPaused) { return false; } - preview_state_ = PreviewState::PREVIEW_STATE__RUNNING; + preview_state_ = PreviewState::kRunning; return true; } void PreviewHandler::OnPreviewStarted() { - assert(preview_state_ == PreviewState::PREVIEW_STATE__STARTING); - if (preview_state_ == PreviewState::PREVIEW_STATE__STARTING) { - preview_state_ = PreviewState::PREVIEW_STATE__RUNNING; + assert(preview_state_ == PreviewState::kStarting); + if (preview_state_ == PreviewState::kStarting) { + preview_state_ = PreviewState::kRunning; } } diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h index acaeb929d3cd..50cf063c7261 100644 --- a/packages/camera/camera_windows/windows/preview_handler.h +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -17,17 +17,17 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -enum PreviewState { - PREVIEW_STATE__NOT_STARTED, - PREVIEW_STATE__STARTING, - PREVIEW_STATE__RUNNING, - PREVIEW_STATE__PAUSED, - PREVIEW_STATE__STOPPING +enum class PreviewState { + kNotStarted, + kStarting, + kRunning, + kPaused, + kStopping }; class PreviewHandler { public: - PreviewHandler(){}; + PreviewHandler() {} virtual ~PreviewHandler() = default; // Prevent copying. @@ -66,24 +66,18 @@ class PreviewHandler { // Returns true if preview state is running or paused. bool IsInitialized() { - return preview_state_ == PreviewState::PREVIEW_STATE__RUNNING && - preview_state_ == PreviewState::PREVIEW_STATE__PAUSED; - }; + return preview_state_ == PreviewState::kRunning && + preview_state_ == PreviewState::kPaused; + } // Returns true if preview state is running. - bool IsRunning() { - return preview_state_ == PreviewState::PREVIEW_STATE__RUNNING; - }; + bool IsRunning() { return preview_state_ == PreviewState::kRunning; } // Return true if preview state is paused. - bool IsPaused() { - return preview_state_ == PreviewState::PREVIEW_STATE__PAUSED; - }; + bool IsPaused() { return preview_state_ == PreviewState::kPaused; } // Returns true if preview state is starting. - bool IsStarting() { - return preview_state_ == PreviewState::PREVIEW_STATE__STARTING; - }; + bool IsStarting() { return preview_state_ == PreviewState::kStarting; } private: // Initializes record sink for video file capture. @@ -91,7 +85,7 @@ class PreviewHandler { IMFMediaType* base_media_type, CaptureEngineListener* sample_callback); - PreviewState preview_state_ = PreviewState::PREVIEW_STATE__NOT_STARTED; + PreviewState preview_state_ = PreviewState::kNotStarted; ComPtr preview_sink_; }; diff --git a/packages/camera/camera_windows/windows/record_handler.cpp b/packages/camera/camera_windows/windows/record_handler.cpp index 9d255aea1815..7538f1a3ab8c 100644 --- a/packages/camera/camera_windows/windows/record_handler.cpp +++ b/packages/camera/camera_windows/windows/record_handler.cpp @@ -200,7 +200,7 @@ bool RecordHandler::StartRecord(const std::string& file_path, assert(capture_engine); assert(base_media_type); - type_ = max_duration < 0 ? RecordingType::CONTINUOUS : RecordingType::TIMED; + type_ = max_duration < 0 ? RecordingType::kContinuous : RecordingType::kTimed; max_video_duration_ms_ = max_duration; file_path_ = file_path; recording_start_timestamp_us_ = -1; @@ -210,15 +210,15 @@ bool RecordHandler::StartRecord(const std::string& file_path, return false; } - recording_state_ = RecordState::RECORD_STATE__STARTING; + recording_state_ = RecordState::kStarting; capture_engine->StartRecord(); return true; } bool RecordHandler::StopRecord(IMFCaptureEngine* capture_engine) { - if (recording_state_ == RecordState::RECORD_STATE__RUNNING) { - recording_state_ = RecordState::RECORD_STATE__STOPPING; + if (recording_state_ == RecordState::kRunning) { + recording_state_ = RecordState::kStopping; HRESULT hr = capture_engine->StopRecord(true, false); return SUCCEEDED(hr); } @@ -226,18 +226,18 @@ bool RecordHandler::StopRecord(IMFCaptureEngine* capture_engine) { } void RecordHandler::OnRecordStarted() { - if (recording_state_ == RecordState::RECORD_STATE__STARTING) { - recording_state_ = RecordState::RECORD_STATE__RUNNING; + if (recording_state_ == RecordState::kStarting) { + recording_state_ = RecordState::kRunning; } } void RecordHandler::OnRecordStopped() { - if (recording_state_ == RecordState::RECORD_STATE__STOPPING) { + if (recording_state_ == RecordState::kStopping) { file_path_ = ""; recording_start_timestamp_us_ = -1; recording_duration_us_ = 0; max_video_duration_ms_ = -1; - recording_state_ = RecordState::RECORD_STATE__NOT_STARTED; + recording_state_ = RecordState::kNotStarted; } } @@ -250,8 +250,8 @@ void RecordHandler::UpdateRecordingTime(uint64_t timestamp) { } bool RecordHandler::ShouldStopTimedRecording() { - return type_ == RecordingType::TIMED && - recording_state_ == RecordState::RECORD_STATE__RUNNING && + return type_ == RecordingType::kTimed && + recording_state_ == RecordState::kRunning && max_video_duration_ms_ > 0 && recording_duration_us_ >= (static_cast(max_video_duration_ms_) * 1000); diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index d2772f97a760..96891c661b1f 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -15,18 +15,13 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -enum RecordingType { CONTINUOUS, TIMED }; +enum class RecordingType { kContinuous, kTimed }; -enum RecordState { - RECORD_STATE__NOT_STARTED, - RECORD_STATE__STARTING, - RECORD_STATE__RUNNING, - RECORD_STATE__STOPPING -}; +enum class RecordState { kNotStarted, kStarting, kRunning, kStopping }; class RecordHandler { public: - RecordHandler(bool record_audio) : record_audio_(record_audio){}; + RecordHandler(bool record_audio) : record_audio_(record_audio) {} virtual ~RecordHandler() = default; // Prevent copying. @@ -64,26 +59,22 @@ class RecordHandler { void OnRecordStopped(); // Returns true if recording type is continuous recording. - bool IsContinuousRecording() { return type_ == RecordingType::CONTINUOUS; }; + bool IsContinuousRecording() { return type_ == RecordingType::kContinuous; } // Returns true if recording type is timed recording. - bool IsTimedRecording() { return type_ == RecordingType::TIMED; }; + bool IsTimedRecording() { return type_ == RecordingType::kTimed; } // Returns true if new recording can be started. - bool CanStart() { - return recording_state_ == RecordState::RECORD_STATE__NOT_STARTED; - }; + bool CanStart() { return recording_state_ == RecordState::kNotStarted; } // Returns true if recording can be stopped. - bool CanStop() { - return recording_state_ == RecordState::RECORD_STATE__RUNNING; - }; + bool CanStop() { return recording_state_ == RecordState::kRunning; } // Returns path to video recording. - std::string GetRecordPath() { return file_path_; }; + std::string GetRecordPath() { return file_path_; } // Returns path to video recording in microseconds. - uint64_t GetRecordedDuration() { return recording_duration_us_; }; + uint64_t GetRecordedDuration() { return recording_duration_us_; } // Calculates new recording time from capture timestamp. void UpdateRecordingTime(uint64_t timestamp); @@ -102,7 +93,7 @@ class RecordHandler { int64_t recording_start_timestamp_us_ = -1; uint64_t recording_duration_us_ = 0; std::string file_path_ = ""; - RecordState recording_state_ = RecordState::RECORD_STATE__NOT_STARTED; + RecordState recording_state_ = RecordState::kNotStarted; RecordingType type_; ComPtr record_sink_; }; 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 071a3fad9f6f..45c1bfb90326 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -100,12 +100,12 @@ TEST(CameraPlugin, CreateHandlerCallsInitCamera) { std::make_unique(MOCK_DEVICE_ID); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::CREATE_CAMERA))) + HasPendingResultByType(Eq(PendingResultType::kCreateCamera))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, - AddPendingResult(Eq(PendingResultType::CREATE_CAMERA), _)) + AddPendingResult(Eq(PendingResultType::kCreateCamera), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { @@ -186,12 +186,12 @@ TEST(CameraPlugin, CreateHandlerErrorOnExistingDeviceId) { std::make_unique(MOCK_DEVICE_ID); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::CREATE_CAMERA))) + HasPendingResultByType(Eq(PendingResultType::kCreateCamera))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, - AddPendingResult(Eq(PendingResultType::CREATE_CAMERA), _)) + AddPendingResult(Eq(PendingResultType::kCreateCamera), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { @@ -265,11 +265,11 @@ TEST(CameraPlugin, InitializeHandlerCallStartPreview) { }); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::INITIALIZE))) + HasPendingResultByType(Eq(PendingResultType::kInitialize))) .Times(1) .WillOnce(Return(false)); - EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::INITIALIZE), _)) + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kInitialize), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { @@ -379,11 +379,11 @@ TEST(CameraPlugin, TakePictureHandlerCallsTakePictureWithPath) { }); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::TAKE_PICTURE))) + HasPendingResultByType(Eq(PendingResultType::kTakePicture))) .Times(1) .WillOnce(Return(false)); - EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::TAKE_PICTURE), _)) + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kTakePicture), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { @@ -493,11 +493,11 @@ TEST(CameraPlugin, StartVideoRecordingHandlerCallsStartRecordWithPath) { }); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::START_RECORD))) + HasPendingResultByType(Eq(PendingResultType::kStartRecord))) .Times(1) .WillOnce(Return(false)); - EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::START_RECORD), _)) + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kStartRecord), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { @@ -564,11 +564,11 @@ TEST(CameraPlugin, }); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::START_RECORD))) + HasPendingResultByType(Eq(PendingResultType::kStartRecord))) .Times(1) .WillOnce(Return(false)); - EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::START_RECORD), _)) + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kStartRecord), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { @@ -681,11 +681,11 @@ TEST(CameraPlugin, StopVideoRecordingHandlerCallsStopRecord) { }); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::STOP_RECORD))) + HasPendingResultByType(Eq(PendingResultType::kStopRecord))) .Times(1) .WillOnce(Return(false)); - EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::STOP_RECORD), _)) + EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kStopRecord), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { @@ -795,12 +795,12 @@ TEST(CameraPlugin, ResumePreviewHandlerCallsResumePreview) { }); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::RESUME_PREVIEW))) + HasPendingResultByType(Eq(PendingResultType::kResumePreview))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, - AddPendingResult(Eq(PendingResultType::RESUME_PREVIEW), _)) + AddPendingResult(Eq(PendingResultType::kResumePreview), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { @@ -910,12 +910,12 @@ TEST(CameraPlugin, PausePreviewHandlerCallsPausePreview) { }); EXPECT_CALL(*camera, - HasPendingResultByType(Eq(PendingResultType::PAUSE_PREVIEW))) + HasPendingResultByType(Eq(PendingResultType::kPausePreview))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, - AddPendingResult(Eq(PendingResultType::PAUSE_PREVIEW), _)) + AddPendingResult(Eq(PendingResultType::kPausePreview), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp index c8db84f2f2a3..004847499191 100644 --- a/packages/camera/camera_windows/windows/test/camera_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -43,7 +43,7 @@ TEST(Camera, InitCameraCreatesCaptureController) { camera->InitCamera(std::move(capture_controller_factory), std::make_unique().get(), std::make_unique().get(), false, - ResolutionPreset::RESOLUTION_PRESET_AUTO); + ResolutionPreset::kAuto); EXPECT_TRUE(camera->GetCaptureController() != nullptr); } @@ -60,11 +60,11 @@ TEST(Camera, AddPendingResultReturnsErrorForDuplicates) { EXPECT_CALL(*first_pending_result, SuccessInternal); EXPECT_CALL(*second_pending_result, ErrorInternal).Times(1); - camera->AddPendingResult(PendingResultType::CREATE_CAMERA, + camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(first_pending_result)); // This should fail - camera->AddPendingResult(PendingResultType::CREATE_CAMERA, + camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(second_pending_result)); // Mark pending result as succeeded @@ -85,7 +85,7 @@ TEST(Camera, OnCreateCaptureEngineSucceededReturnsCameraId) { SuccessInternal(Pointee(EncodableValue(EncodableMap( {{EncodableValue("cameraId"), EncodableValue(texture_id)}}))))); - camera->AddPendingResult(PendingResultType::CREATE_CAMERA, std::move(result)); + camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(result)); camera->OnCreateCaptureEngineSucceeded(texture_id); } @@ -101,7 +101,7 @@ TEST(Camera, OnCreateCaptureEngineFailedReturnsError) { EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); - camera->AddPendingResult(PendingResultType::CREATE_CAMERA, std::move(result)); + camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(result)); camera->OnCreateCaptureEngineFailed(error_text); } @@ -123,7 +123,7 @@ TEST(Camera, OnStartPreviewSucceededReturnsFrameSize) { {EncodableValue("previewHeight"), EncodableValue((float)height)}, }))))); - camera->AddPendingResult(PendingResultType::INITIALIZE, std::move(result)); + camera->AddPendingResult(PendingResultType::kInitialize, std::move(result)); camera->OnStartPreviewSucceeded(width, height); } @@ -139,7 +139,7 @@ TEST(Camera, OnStartPreviewFailedReturnsError) { EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); - camera->AddPendingResult(PendingResultType::INITIALIZE, std::move(result)); + camera->AddPendingResult(PendingResultType::kInitialize, std::move(result)); camera->OnStartPreviewFailed(error_text); } @@ -153,7 +153,7 @@ TEST(Camera, OnPausePreviewSucceededReturnsSuccess) { EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(nullptr)); - camera->AddPendingResult(PendingResultType::PAUSE_PREVIEW, std::move(result)); + camera->AddPendingResult(PendingResultType::kPausePreview, std::move(result)); camera->OnPausePreviewSucceeded(); } @@ -169,7 +169,7 @@ TEST(Camera, OnPausePreviewFailedReturnsError) { EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); - camera->AddPendingResult(PendingResultType::PAUSE_PREVIEW, std::move(result)); + camera->AddPendingResult(PendingResultType::kPausePreview, std::move(result)); camera->OnPausePreviewFailed(error_text); } @@ -183,7 +183,7 @@ TEST(Camera, OnResumePreviewSucceededReturnsSuccess) { EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(nullptr)); - camera->AddPendingResult(PendingResultType::RESUME_PREVIEW, + camera->AddPendingResult(PendingResultType::kResumePreview, std::move(result)); camera->OnResumePreviewSucceeded(); @@ -200,7 +200,7 @@ TEST(Camera, OnResumePreviewFailedReturnsError) { EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); - camera->AddPendingResult(PendingResultType::RESUME_PREVIEW, + camera->AddPendingResult(PendingResultType::kResumePreview, std::move(result)); camera->OnResumePreviewFailed(error_text); @@ -215,7 +215,7 @@ TEST(Camera, OnStartRecordSucceededReturnsSuccess) { EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(nullptr)); - camera->AddPendingResult(PendingResultType::START_RECORD, std::move(result)); + camera->AddPendingResult(PendingResultType::kStartRecord, std::move(result)); camera->OnStartRecordSucceeded(); } @@ -231,7 +231,7 @@ TEST(Camera, OnStartRecordFailedReturnsError) { EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); - camera->AddPendingResult(PendingResultType::START_RECORD, std::move(result)); + camera->AddPendingResult(PendingResultType::kStartRecord, std::move(result)); camera->OnStartRecordFailed(error_text); } @@ -247,7 +247,7 @@ TEST(Camera, OnStopRecordSucceededReturnsSuccess) { EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(file_path)))); - camera->AddPendingResult(PendingResultType::STOP_RECORD, std::move(result)); + camera->AddPendingResult(PendingResultType::kStopRecord, std::move(result)); camera->OnStopRecordSucceeded(file_path); } @@ -263,7 +263,7 @@ TEST(Camera, OnStopRecordFailedReturnsError) { EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); - camera->AddPendingResult(PendingResultType::STOP_RECORD, std::move(result)); + camera->AddPendingResult(PendingResultType::kStopRecord, std::move(result)); camera->OnStopRecordFailed(error_text); } @@ -279,7 +279,7 @@ TEST(Camera, OnTakePictureSucceededReturnsSuccess) { EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(file_path)))); - camera->AddPendingResult(PendingResultType::TAKE_PICTURE, std::move(result)); + camera->AddPendingResult(PendingResultType::kTakePicture, std::move(result)); camera->OnTakePictureSucceeded(file_path); } @@ -295,7 +295,7 @@ TEST(Camera, OnTakePictureFailedReturnsError) { EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(_, Eq(error_text), _)); - camera->AddPendingResult(PendingResultType::TAKE_PICTURE, std::move(result)); + camera->AddPendingResult(PendingResultType::kTakePicture, std::move(result)); camera->OnTakePictureFailed(error_text); } @@ -328,8 +328,7 @@ TEST(Camera, OnVideoRecordSucceededInvokesCameraChannelEvent) { // Init camera with mock capture controller factory camera->InitCamera(std::move(capture_controller_factory), std::make_unique().get(), - binary_messenger.get(), false, - ResolutionPreset::RESOLUTION_PRESET_AUTO); + binary_messenger.get(), false, ResolutionPreset::kAuto); // Pass camera id for camera camera->OnCreateCaptureEngineSucceeded(camera_id); 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 cb8059dc1769..7520af7a4af8 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -59,9 +59,8 @@ void MockInitCaptureController(CaptureControllerImpl* capture_controller, .Times(1); EXPECT_CALL(*engine, Initialize).Times(1); - capture_controller->InitCaptureDevice( - texture_registrar, MOCK_DEVICE_ID, true, - ResolutionPreset::RESOLUTION_PRESET_AUTO); + capture_controller->InitCaptureDevice(texture_registrar, MOCK_DEVICE_ID, true, + ResolutionPreset::kAuto); // MockCaptureEngine::Initialize is called EXPECT_TRUE(engine->initialized_); diff --git a/packages/camera/camera_windows/windows/texture_handler.cpp b/packages/camera/camera_windows/windows/texture_handler.cpp index b6154e45fd9c..0cf10d6f8e44 100644 --- a/packages/camera/camera_windows/windows/texture_handler.cpp +++ b/packages/camera/camera_windows/windows/texture_handler.cpp @@ -11,7 +11,6 @@ namespace camera_windows { TextureHandler::~TextureHandler() { // Texture might still be processed while destructor is called. // Lock mutex for safe destruction - const std::lock_guard lock(buffer_mutex_); if (TextureRegistered()) { texture_registrar_->UnregisterTexture(texture_id_); diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index db5b45e69ecb..03e1eefe4b79 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -32,7 +32,7 @@ struct MFVideoFormatRGB32Pixel { class TextureHandler { public: TextureHandler(flutter::TextureRegistrar* texture_registrar) - : texture_registrar_(texture_registrar){}; + : texture_registrar_(texture_registrar) {} virtual ~TextureHandler(); // Prevent copying. @@ -49,10 +49,10 @@ class TextureHandler { void UpdateTextureSize(uint32_t width, uint32_t height) { preview_frame_width_ = width; preview_frame_height_ = height; - }; + } // Sets software mirror state. - void SetMirrorPreviewState(bool mirror) { mirror_preview_ = mirror; }; + void SetMirrorPreviewState(bool mirror) { mirror_preview_ = mirror; } private: // Informs flutter texture registrar of updated texture. From c7ddc0c7112feeaa991b84b1fd0ba3fd349ff244 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 1 Feb 2022 16:13:59 +0200 Subject: [PATCH 77/93] [camera_windows] refactor pixelbuffer logic --- .../windows/texture_handler.cpp | 53 +++++++++++-------- .../camera_windows/windows/texture_handler.h | 14 +++-- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/packages/camera/camera_windows/windows/texture_handler.cpp b/packages/camera/camera_windows/windows/texture_handler.cpp index 0cf10d6f8e44..f891f63d7650 100644 --- a/packages/camera/camera_windows/windows/texture_handler.cpp +++ b/packages/camera/camera_windows/windows/texture_handler.cpp @@ -12,10 +12,12 @@ TextureHandler::~TextureHandler() { // Texture might still be processed while destructor is called. // Lock mutex for safe destruction const std::lock_guard lock(buffer_mutex_); - if (TextureRegistered()) { + if (texture_registrar_ && texture_id_ > 0) { texture_registrar_->UnregisterTexture(texture_id_); - texture_id_ = -1; } + texture_id_ = -1; + texture_ = nullptr; + texture_registrar_ = nullptr; } int64_t TextureHandler::RegisterTexture() { @@ -43,12 +45,11 @@ bool TextureHandler::UpdateBuffer(uint8_t* data, uint32_t data_length) { return false; } - if (source_buffer_ == nullptr || source_buffer_size_ != data_length) { + if (source_buffer_.size() != data_length) { // Update source buffer size. - source_buffer_ = std::make_unique(data_length); - source_buffer_size_ = data_length; + source_buffer_.resize(data_length); } - std::copy(data, data + data_length, source_buffer_.get()); + std::copy(data, data + data_length, source_buffer_.data()); } OnBufferUpdated(); return true; @@ -72,19 +73,21 @@ const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( const uint32_t bytes_per_pixel = 4; const uint32_t pixels_total = preview_frame_width_ * preview_frame_height_; const uint32_t data_size = pixels_total * bytes_per_pixel; - if (source_buffer_ && data_size > 0 && source_buffer_size_ == data_size) { - dest_buffer_ = std::make_unique(data_size); + if (data_size > 0 && source_buffer_.size() == data_size) { + if (dest_buffer_.size() != data_size) { + dest_buffer_.resize(data_size); + } // Map buffers to structs for easier conversion. MFVideoFormatRGB32Pixel* src = - (MFVideoFormatRGB32Pixel*)source_buffer_.get(); - FlutterDesktopPixel* dst = (FlutterDesktopPixel*)dest_buffer_.get(); + (MFVideoFormatRGB32Pixel*)source_buffer_.data(); + FlutterDesktopPixel* dst = (FlutterDesktopPixel*)dest_buffer_.data(); for (uint32_t y = 0; y < preview_frame_height_; y++) { for (uint32_t x = 0; x < preview_frame_width_; x++) { uint32_t sp = (y * preview_frame_width_) + x; if (mirror_preview_) { - // Software mirror more implementation. + // Software mirror mode. // IMFCapturePreviewSink also has the SetMirrorState setting, // but if enabled, samples will not be processed. @@ -104,20 +107,26 @@ const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( } } - flutter_desktop_pixel_buffer_.buffer = dest_buffer_.get(); - flutter_desktop_pixel_buffer_.width = preview_frame_width_; - flutter_desktop_pixel_buffer_.height = preview_frame_height_; + if (!flutter_desktop_pixel_buffer_) { + flutter_desktop_pixel_buffer_ = + std::make_unique(); - // Releases unique_lock and set mutex pointer for release context. - flutter_desktop_pixel_buffer_.release_context = buffer_lock.release(); + // Unlocks mutex after texture is processed. + flutter_desktop_pixel_buffer_->release_callback = + [](void* release_context) { + auto mutex = reinterpret_cast(release_context); + mutex->unlock(); + }; + } + + flutter_desktop_pixel_buffer_->buffer = dest_buffer_.data(); + flutter_desktop_pixel_buffer_->width = preview_frame_width_; + flutter_desktop_pixel_buffer_->height = preview_frame_height_; - // Unlocks mutex after texture is processed. - flutter_desktop_pixel_buffer_.release_callback = [](void* release_context) { - auto mutex = reinterpret_cast(release_context); - mutex->unlock(); - }; + // Releases unique_lock and set mutex pointer for release context. + flutter_desktop_pixel_buffer_->release_context = buffer_lock.release(); - return &flutter_desktop_pixel_buffer_; + return flutter_desktop_pixel_buffer_.get(); } return nullptr; } diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index 03e1eefe4b79..76c3d15334a2 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -62,8 +62,10 @@ class TextureHandler { const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, size_t height); - // Check if texture id is available and texture registrar is available - bool TextureRegistered() { return texture_registrar_ && texture_id_ > -1; } + // Checks if texture registrar, texture id and texture are available. + bool TextureRegistered() { + return texture_registrar_ && texture_ && texture_id_ > -1; + } bool mirror_preview_ = false; int64_t texture_id_ = -1; @@ -71,10 +73,12 @@ class TextureHandler { uint32_t source_buffer_size_ = 0; uint32_t preview_frame_width_ = 0; uint32_t preview_frame_height_ = 0; - std::unique_ptr source_buffer_ = nullptr; - std::unique_ptr dest_buffer_ = nullptr; + + std::vector source_buffer_; + std::vector dest_buffer_; std::unique_ptr texture_; - FlutterDesktopPixelBuffer flutter_desktop_pixel_buffer_ = {}; + std::unique_ptr flutter_desktop_pixel_buffer_ = + nullptr; flutter::TextureRegistrar* texture_registrar_ = nullptr; std::mutex buffer_mutex_; From 66a598f09ad9a8107187258c9a281d592df0a4eb Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 1 Feb 2022 16:55:19 +0200 Subject: [PATCH 78/93] [camera_windows] prefer smart pointers --- packages/camera/camera_windows/windows/camera_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp index 5074209291a3..82ff5a78720b 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -160,7 +160,7 @@ std::string GetCurrentTimeString() { // Builds file path for picture capture. std::optional GetFilePathForPicture() { - wchar_t* known_folder_path = nullptr; + ComHeapPtr known_folder_path; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Pictures, KF_FLAG_CREATE, nullptr, &known_folder_path); if (FAILED(hr)) { @@ -175,7 +175,7 @@ std::optional GetFilePathForPicture() { // Builds file path for video capture. std::optional GetFilePathForVideo() { - wchar_t* known_folder_path = nullptr; + ComHeapPtr known_folder_path; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Videos, KF_FLAG_CREATE, nullptr, &known_folder_path); if (FAILED(hr)) { From 44e5b90d76526648140263ec97ab109b2ae3dec2 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 09:08:56 +0200 Subject: [PATCH 79/93] [camera_windows] fix error format to utf8 --- .../camera/camera_windows/windows/capture_controller.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 33f7614ec8db..14abaeaf0eef 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -4,12 +4,12 @@ #include "capture_controller.h" +#include #include #include #include #include -#include #include "com_heap_ptr.h" #include "photo_handler.h" @@ -667,7 +667,8 @@ void CaptureControllerImpl::OnEvent(IMFMediaEvent* event) { if (FAILED(event_hr)) { // Reads system error - error = std::system_category().message(event_hr); + _com_error err(event_hr); + error = Utf8FromUtf16(err.ErrorMessage()); } if (extended_type_guid == MF_CAPTURE_ENGINE_ERROR) { From 73fdb7fa7b4ebd978308ab472fa34496cad318cf Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 10:16:57 +0200 Subject: [PATCH 80/93] [camera_windows] improve example app quality --- .../camera_windows/example/lib/main.dart | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/camera/camera_windows/example/lib/main.dart b/packages/camera/camera_windows/example/lib/main.dart index 6758d186655d..b73e00cac52b 100644 --- a/packages/camera/camera_windows/example/lib/main.dart +++ b/packages/camera/camera_windows/example/lib/main.dart @@ -87,9 +87,6 @@ class _MyAppState extends State { int cameraId = -1; try { - final Completer _initializeCompleter = - Completer(); - final int cameraIndex = _cameraIndex % _cameras.length; final CameraDescription camera = _cameras[cameraIndex]; @@ -109,23 +106,18 @@ class _MyAppState extends State { .onCameraClosing(cameraId) .listen(_onCameraClosing); - unawaited(CameraPlatform.instance - .onCameraInitialized(cameraId) - .first - .then((CameraInitializedEvent event) { - _initializeCompleter.complete(event); - })); + final Future initialized = + CameraPlatform.instance.onCameraInitialized(cameraId).first; await CameraPlatform.instance.initializeCamera( cameraId, imageFormatGroup: ImageFormatGroup.unknown, ); - _previewSize = await _initializeCompleter.future.then( - (CameraInitializedEvent event) => Size( - event.previewWidth, - event.previewHeight, - ), + final CameraInitializedEvent event = await initialized; + _previewSize = Size( + event.previewWidth, + event.previewHeight, ); if (mounted) { From 518f1f2d611c1c6ef780576c0c45acec7982c5fb Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 10:26:31 +0200 Subject: [PATCH 81/93] [camera_windows] fix type casts --- .../camera_windows/windows/texture_handler.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/windows/texture_handler.cpp b/packages/camera/camera_windows/windows/texture_handler.cpp index f891f63d7650..a7c94738698a 100644 --- a/packages/camera/camera_windows/windows/texture_handler.cpp +++ b/packages/camera/camera_windows/windows/texture_handler.cpp @@ -64,6 +64,15 @@ void TextureHandler::OnBufferUpdated() { const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( size_t target_width, size_t target_height) { + // TODO: optimize image processing size by adjusting capture size + // dynamically to match target_width and target_height. + // If target size changes, create new media type for preview and set new + // target framesize to MF_MT_FRAME_SIZE attribute. + // Size should be kept inside requested resolution preset. + // Update output media type with IMFCaptureSink2::SetOutputMediaType method + // call and implement IMFCaptureEngineOnSampleCallback2::OnSynchronizedEvent + // to detect size changes. + // Lock buffer mutex to protect texture processing std::unique_lock buffer_lock(buffer_mutex_); if (!TextureRegistered()) { @@ -80,8 +89,9 @@ const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( // Map buffers to structs for easier conversion. MFVideoFormatRGB32Pixel* src = - (MFVideoFormatRGB32Pixel*)source_buffer_.data(); - FlutterDesktopPixel* dst = (FlutterDesktopPixel*)dest_buffer_.data(); + reinterpret_cast(source_buffer_.data()); + FlutterDesktopPixel* dst = + reinterpret_cast(dest_buffer_.data()); for (uint32_t y = 0; y < preview_frame_height_; y++) { for (uint32_t x = 0; x < preview_frame_width_; x++) { From 0a68ffbd1eff3d143eb2227d2947e4c0f97eb38a Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 18:05:50 +0200 Subject: [PATCH 82/93] [camera_windows] minor code style fixes --- packages/camera/camera_windows/example/pubspec.yaml | 1 - packages/camera/camera_windows/lib/camera_windows.dart | 6 +++--- packages/camera/camera_windows/pubspec.yaml | 1 - packages/camera/camera_windows/windows/camera.cpp | 2 +- packages/camera/camera_windows/windows/camera.h | 4 ++-- .../camera/camera_windows/windows/camera_plugin.cpp | 2 +- .../camera/camera_windows/windows/capture_controller.h | 10 +++++----- .../camera_windows/windows/capture_device_info.h | 7 +++---- packages/camera/camera_windows/windows/com_heap_ptr.h | 2 +- packages/camera/camera_windows/windows/photo_handler.h | 2 +- .../camera/camera_windows/windows/record_handler.h | 2 +- .../camera/camera_windows/windows/test/camera_test.cpp | 3 ++- 12 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index 4c65a0a9838b..aa806a292333 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -23,7 +23,6 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter - pedantic: ^1.10.0 flutter: uses-material-design: true diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 48cf7e10d027..97a135d64bde 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -20,7 +20,7 @@ class CameraWindows extends CameraPlatform { /// The method channel used to interact with the native platform. final MethodChannel _pluginChannel = - const MethodChannel('plugins.flutter.io/camera'); + const MethodChannel('plugins.flutter.io/camera_windows'); /// Camera specific method channels to allow comminicating with specific cameras. final Map _cameraChannels = {}; @@ -94,8 +94,8 @@ class CameraWindows extends CameraPlatform { /// Creates channel for camera events. _cameraChannels.putIfAbsent(requestedCameraId, () { - final MethodChannel channel = - MethodChannel('flutter.io/cameraPlugin/camera$requestedCameraId'); + final MethodChannel channel = MethodChannel( + 'plugins.flutter.io/camera_windows/camera$requestedCameraId'); channel.setMethodCallHandler( (MethodCall call) => _handleCameraMethodCall(call, requestedCameraId), ); diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index b77b9c17bb79..1ab8af9d8e6d 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -24,6 +24,5 @@ dependencies: stream_transform: ^2.0.0 dev_dependencies: - flutter_lints: ^1.0.0 flutter_test: sdk: flutter diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index 6336116a43ae..1f3bb5e548f5 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -11,7 +11,7 @@ using flutter::EncodableValue; // Camera channel events. constexpr char kCameraMethodChannelBaseName[] = - "flutter.io/cameraPlugin/camera"; + "plugins.flutter.io/camera_windows/camera"; constexpr char kVideoRecordedEvent[] = "video_recorded"; constexpr char kCameraClosingEvent[] = "camera_closing"; constexpr char kErrorEvent[] = "error"; diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index e7cda22c10e6..210cbab0d180 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -138,8 +138,8 @@ class CameraImpl : public Camera { PendingResultType type); std::map>> pending_results_; - std::unique_ptr capture_controller_ = nullptr; - std::unique_ptr> camera_channel_ = nullptr; + std::unique_ptr capture_controller_; + std::unique_ptr> camera_channel_; flutter::BinaryMessenger* messenger_ = nullptr; 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 82ff5a78720b..3b795e02047a 100644 --- a/packages/camera/camera_windows/windows/camera_plugin.cpp +++ b/packages/camera/camera_windows/windows/camera_plugin.cpp @@ -30,7 +30,7 @@ using flutter::EncodableValue; namespace { // Channel events -constexpr char kChannelName[] = "plugins.flutter.io/camera"; +constexpr char kChannelName[] = "plugins.flutter.io/camera_windows"; constexpr char kAvailableCamerasMethod[] = "availableCameras"; constexpr char kCreateMethod[] = "create"; diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index d014a71f0b64..129283567add 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -221,11 +221,11 @@ class CaptureControllerImpl : public CaptureController, uint32_t preview_frame_width_ = 0; uint32_t preview_frame_height_ = 0; UINT dx_device_reset_token_ = 0; - std::unique_ptr record_handler_ = nullptr; - std::unique_ptr preview_handler_ = nullptr; - std::unique_ptr photo_handler_ = nullptr; - std::unique_ptr texture_handler_ = nullptr; - CaptureControllerListener* capture_controller_listener_ = nullptr; + std::unique_ptr record_handler_; + std::unique_ptr preview_handler_; + std::unique_ptr photo_handler_; + std::unique_ptr texture_handler_; + CaptureControllerListener* capture_controller_listener_; std::string video_device_id_; CaptureEngineState capture_engine_state_ = diff --git a/packages/camera/camera_windows/windows/capture_device_info.h b/packages/camera/camera_windows/windows/capture_device_info.h index 7fef62302118..b416b6131f3e 100644 --- a/packages/camera/camera_windows/windows/capture_device_info.h +++ b/packages/camera/camera_windows/windows/capture_device_info.h @@ -2,10 +2,9 @@ // 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_DEVICE_INFO_H_ -#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ +#ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_DEVICE_INFO_H_ +#define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_DEVICE_INFO_H_ -#include #include namespace camera_windows { @@ -46,4 +45,4 @@ class CaptureDeviceInfo { } // namespace camera_windows -#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_DEVICE_INFO_H_ +#endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_DEVICE_INFO_H_ diff --git a/packages/camera/camera_windows/windows/com_heap_ptr.h b/packages/camera/camera_windows/windows/com_heap_ptr.h index ca891d8348a6..a314ed3c8878 100644 --- a/packages/camera/camera_windows/windows/com_heap_ptr.h +++ b/packages/camera/camera_windows/windows/com_heap_ptr.h @@ -48,7 +48,7 @@ class ComHeapPtr { return &p_obj_; } - // Free the memory pointed to, and set the pointer to nullptr. + // Frees the memory pointed to, and sets the pointer to nullptr. void Free() { if (p_obj_) { CoTaskMemFree(p_obj_); diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index 42bbaf14334a..5546af189f0d 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -62,7 +62,7 @@ class PhotoHandler { HRESULT InitPhotoSink(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type); - std::string file_path_ = ""; + std::string file_path_; PhotoState photo_state_ = PhotoState::kNotStarted; ComPtr photo_sink_; }; diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index 96891c661b1f..5e9e5c7bfb6c 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -92,7 +92,7 @@ class RecordHandler { int64_t max_video_duration_ms_ = -1; int64_t recording_start_timestamp_us_ = -1; uint64_t recording_duration_us_ = 0; - std::string file_path_ = ""; + std::string file_path_; RecordState recording_state_ = RecordState::kNotStarted; RecordingType type_; ComPtr record_sink_; diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp index 004847499191..899c1fdaea62 100644 --- a/packages/camera/camera_windows/windows/test/camera_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_test.cpp @@ -312,7 +312,8 @@ TEST(Camera, OnVideoRecordSucceededInvokesCameraChannelEvent) { std::string file_path = "C:\temp\filename.mp4"; int64_t camera_id = 12345; std::string camera_channel = - std::string("flutter.io/cameraPlugin/camera") + std::to_string(camera_id); + std::string("plugins.flutter.io/camera_windows/camera") + + std::to_string(camera_id); int64_t video_duration = 1000000; EXPECT_CALL(*capture_controller_factory, CreateCaptureController) From 417b7051086b6c4bc1ad54b82263bafaa17c5f87 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 18:45:07 +0200 Subject: [PATCH 83/93] [camera_windows] mirror preview by default --- packages/camera/camera_windows/windows/texture_handler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index 76c3d15334a2..24d9e7dcaadc 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -67,7 +67,7 @@ class TextureHandler { return texture_registrar_ && texture_ && texture_id_ > -1; } - bool mirror_preview_ = false; + bool mirror_preview_ = true; int64_t texture_id_ = -1; uint32_t bytes_per_pixel_ = 4; uint32_t source_buffer_size_ = 0; From 3bbbf49851b1d4572dcfd357929cbf44ed41bedb Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 18:57:35 +0200 Subject: [PATCH 84/93] [camera_windows] add stub dart tests --- .../camera_windows/lib/camera_windows.dart | 23 ++++++------- .../test/camera_windows_test.dart | 33 +++++++++++++++++++ 2 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 packages/camera/camera_windows/test/camera_windows_test.dart diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 97a135d64bde..e5d23d20c0f2 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -19,7 +19,8 @@ class CameraWindows extends CameraPlatform { } /// The method channel used to interact with the native platform. - final MethodChannel _pluginChannel = + @visibleForTesting + final MethodChannel pluginChannel = const MethodChannel('plugins.flutter.io/camera_windows'); /// Camera specific method channels to allow comminicating with specific cameras. @@ -40,7 +41,7 @@ class CameraWindows extends CameraPlatform { @override Future> availableCameras() async { try { - final List>? cameras = await _pluginChannel + final List>? cameras = await pluginChannel .invokeListMethod>('availableCameras'); if (cameras == null) { @@ -68,7 +69,7 @@ class CameraWindows extends CameraPlatform { }) async { try { // If resolutionPreset is not specified, plugin selects the highest resolution possible. - final Map? reply = await _pluginChannel + final Map? reply = await pluginChannel .invokeMapMethod('create', { 'cameraName': cameraDescription.name, 'resolutionPreset': _serializeResolutionPreset(resolutionPreset), @@ -104,7 +105,7 @@ class CameraWindows extends CameraPlatform { final Map? reply; try { - reply = await _pluginChannel.invokeMapMethod( + reply = await pluginChannel.invokeMapMethod( 'initialize', { 'cameraId': requestedCameraId, @@ -129,7 +130,7 @@ class CameraWindows extends CameraPlatform { @override Future dispose(int cameraId) async { - await _pluginChannel.invokeMethod( + await pluginChannel.invokeMethod( 'dispose', {'cameraId': cameraId}, ); @@ -198,7 +199,7 @@ class CameraWindows extends CameraPlatform { @override Future takePicture(int cameraId) async { final String? path; - path = await _pluginChannel.invokeMethod( + path = await pluginChannel.invokeMethod( 'takePicture', {'cameraId': cameraId}, ); @@ -208,14 +209,14 @@ class CameraWindows extends CameraPlatform { @override Future prepareForVideoRecording() => - _pluginChannel.invokeMethod('prepareForVideoRecording'); + pluginChannel.invokeMethod('prepareForVideoRecording'); @override Future startVideoRecording( int cameraId, { Duration? maxVideoDuration, }) async { - await _pluginChannel.invokeMethod( + await pluginChannel.invokeMethod( 'startVideoRecording', { 'cameraId': cameraId, @@ -228,7 +229,7 @@ class CameraWindows extends CameraPlatform { Future stopVideoRecording(int cameraId) async { final String? path; - path = await _pluginChannel.invokeMethod( + path = await pluginChannel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, ); @@ -333,7 +334,7 @@ class CameraWindows extends CameraPlatform { @override Future pausePreview(int cameraId) async { - await _pluginChannel.invokeMethod( + await pluginChannel.invokeMethod( 'pausePreview', {'cameraId': cameraId}, ); @@ -341,7 +342,7 @@ class CameraWindows extends CameraPlatform { @override Future resumePreview(int cameraId) async { - await _pluginChannel.invokeMethod( + await pluginChannel.invokeMethod( 'resumePreview', {'cameraId': cameraId}, ); diff --git a/packages/camera/camera_windows/test/camera_windows_test.dart b/packages/camera/camera_windows/test/camera_windows_test.dart new file mode 100644 index 000000000000..b682fbec446d --- /dev/null +++ b/packages/camera/camera_windows/test/camera_windows_test.dart @@ -0,0 +1,33 @@ +// 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 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_windows/camera_windows.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$CameraWindows()', () { + final CameraWindows plugin = CameraWindows(); + + final List log = []; + + setUp(() { + plugin.pluginChannel + .setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return null; + }); + + log.clear(); + }); + + test('registered instance', () { + CameraWindows.registerWith(); + expect(CameraPlatform.instance, isA()); + }); + }); +} From 6966b9d34846b4f40bf5f078fed67c976dc1c613 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 21:11:39 +0200 Subject: [PATCH 85/93] [camera_windows] add class and enum descriptions --- packages/camera/camera_windows/windows/camera.h | 9 +++++++++ .../camera_windows/windows/capture_controller.h | 13 ++++++++++++- .../windows/capture_controller_listener.h | 1 + .../camera_windows/windows/capture_device_info.h | 2 ++ .../windows/capture_engine_listener.h | 2 ++ .../camera/camera_windows/windows/photo_handler.h | 3 +++ .../camera/camera_windows/windows/preview_handler.h | 5 +++++ .../camera/camera_windows/windows/record_handler.h | 11 ++++++++++- .../camera/camera_windows/windows/texture_handler.h | 2 ++ 9 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 210cbab0d180..3229200d9b41 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -18,6 +18,8 @@ using flutter::EncodableMap; using flutter::MethodChannel; using flutter::MethodResult; +// A set of result types that are stored +// for processing asynchronous commands. enum class PendingResultType { kCreateCamera, kInitialize, @@ -28,6 +30,8 @@ enum class PendingResultType { kResumePreview, }; +// Interface for a class that handles camera messaging and its capture +// controller instance. class Camera : public CaptureControllerListener { public: explicit Camera(const std::string& device_id) {} @@ -61,6 +65,9 @@ class Camera : public CaptureControllerListener { ResolutionPreset resolution_preset) = 0; }; +// The camera wrapper, which initializes the capture controller, listens for its +// events, processes pending results and sends processed events through the +// method channel. class CameraImpl : public Camera { public: explicit CameraImpl(const std::string& device_id); @@ -145,6 +152,7 @@ class CameraImpl : public Camera { std::string device_id_; }; +// Interface for a class that creates camera objects. class CameraFactory { public: CameraFactory() {} @@ -159,6 +167,7 @@ class CameraFactory { const std::string& device_id) = 0; }; +// Creates camera instances. class CameraFactoryImpl : public CameraFactory { public: CameraFactoryImpl() {} diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 129283567add..0f520978cc41 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -28,8 +28,9 @@ namespace camera_windows { using flutter::TextureRegistrar; using Microsoft::WRL::ComPtr; +// A set of presets that can be used to request a capture resolution. enum class ResolutionPreset { - // Automatic resolution + // Automatic resolution, uses the highest resolution available. kAuto, // 240p (320x240) kLow, @@ -45,14 +46,19 @@ enum class ResolutionPreset { kMax, }; +// Various states that the capture controller can have for capture engine +// it controls. When created the state is in an not initialized state +// and transtions in sequential order of the states. enum class CaptureEngineState { kNotInitialized, kInitializing, kInitialized }; +// Interface for a class that enumerates video capture device sources. class VideoCaptureDeviceEnumerator { private: virtual bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) = 0; }; +// Interface for a capture controller. class CaptureController { public: CaptureController() {} @@ -107,6 +113,9 @@ class CaptureController { virtual void TakePicture(const std::string file_path) = 0; }; +// Handles capture engine creating on capture process for previewing, +// capturing videos, and taking photos. +// Processes events and samples send by capture engine instance. class CaptureControllerImpl : public CaptureController, public CaptureEngineObserver { public: @@ -243,6 +252,7 @@ class CaptureControllerImpl : public CaptureController, TextureRegistrar* texture_registrar_ = nullptr; }; +// Interface for a class that creates capture controllers. class CaptureControllerFactory { public: CaptureControllerFactory() {} @@ -256,6 +266,7 @@ class CaptureControllerFactory { CaptureControllerListener* listener) = 0; }; +// Creates capture controller instance. class CaptureControllerFactoryImpl : public CaptureControllerFactory { public: CaptureControllerFactoryImpl() {} diff --git a/packages/camera/camera_windows/windows/capture_controller_listener.h b/packages/camera/camera_windows/windows/capture_controller_listener.h index 04595f5dfb20..ba75af6ff73b 100644 --- a/packages/camera/camera_windows/windows/capture_controller_listener.h +++ b/packages/camera/camera_windows/windows/capture_controller_listener.h @@ -9,6 +9,7 @@ namespace camera_windows { +// Interface for classes that listen events from the capture controller. class CaptureControllerListener { public: virtual ~CaptureControllerListener() = default; diff --git a/packages/camera/camera_windows/windows/capture_device_info.h b/packages/camera/camera_windows/windows/capture_device_info.h index b416b6131f3e..78d6e584ac40 100644 --- a/packages/camera/camera_windows/windows/capture_device_info.h +++ b/packages/camera/camera_windows/windows/capture_device_info.h @@ -9,6 +9,8 @@ namespace camera_windows { +// Holds capture device information and converts it to a unique device name. +// Parses device name back to a display id and device id. class CaptureDeviceInfo { public: CaptureDeviceInfo() {} diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 05c975e0363d..6ff7f1ef6f7b 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -12,6 +12,8 @@ namespace camera_windows { +// An interface for a class that listens and handles the +// events from capture engine listener. class CaptureEngineObserver { public: virtual ~CaptureEngineObserver() = default; diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index 5546af189f0d..c867393589b1 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -17,12 +17,15 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; +// Various states that the photo handler can be in. When created the handler is +// in an not started state and transtions in sequential order of the states. enum class PhotoState { kNotStarted, kIdle, kTakingPhoto, }; +// Handles the photo sink initialization and tracks photo capture states. class PhotoHandler { public: PhotoHandler() {} diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h index 50cf063c7261..8b3898f38281 100644 --- a/packages/camera/camera_windows/windows/preview_handler.h +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -17,6 +17,10 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; +// Various states that the preview handler can be in. When created the handler +// is in an not started state and mostly transtions in sequential order of the +// states. When preview is running it can be set to the pause state and resumed +// back to the running state. enum class PreviewState { kNotStarted, kStarting, @@ -25,6 +29,7 @@ enum class PreviewState { kStopping }; +// Handles the preview sink initialization and tracks preview states. class PreviewHandler { public: PreviewHandler() {} diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index 5e9e5c7bfb6c..c50b4c6e6c15 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -15,10 +15,19 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -enum class RecordingType { kContinuous, kTimed }; +enum class RecordingType { + // Recording continues until it is stopped with a separate stop command. + kContinuous, + // Recording stops automatically after requested record time is passed. + kTimed +}; +// Various states that the record handler can be in. When created the handler +// is in an not started state and transtions in sequential order of the +// states. enum class RecordState { kNotStarted, kStarting, kRunning, kStopping }; +// Handles the record sink initialization and tracks record capture states. class RecordHandler { public: RecordHandler(bool record_audio) : record_audio_(record_audio) {} diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index 24d9e7dcaadc..5822688d08ba 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -29,6 +29,8 @@ struct MFVideoFormatRGB32Pixel { uint8_t x = 0; }; +// Handles the registration of flutter textures, pixel buffers, +// and the conversion of texture formats. class TextureHandler { public: TextureHandler(flutter::TextureRegistrar* texture_registrar) From a44e5a0c3792ed26cd1884b8d36f13333c4fa6e2 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 22:06:14 +0200 Subject: [PATCH 86/93] [camera_windows] language checks for class comments --- .../camera/camera_windows/windows/capture_controller.h | 5 ++--- packages/camera/camera_windows/windows/preview_handler.h | 8 ++++---- packages/camera/camera_windows/windows/texture_handler.h | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 0f520978cc41..6efe37187277 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -113,9 +113,8 @@ class CaptureController { virtual void TakePicture(const std::string file_path) = 0; }; -// Handles capture engine creating on capture process for previewing, -// capturing videos, and taking photos. -// Processes events and samples send by capture engine instance. +// Handles creating the capture engine for preview, video capture and photo +// capture. Processes events and samples sent by the capture engine instance. class CaptureControllerImpl : public CaptureController, public CaptureEngineObserver { public: diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h index 8b3898f38281..cf18efa7ebe7 100644 --- a/packages/camera/camera_windows/windows/preview_handler.h +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -17,10 +17,10 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -// Various states that the preview handler can be in. When created the handler -// is in an not started state and mostly transtions in sequential order of the -// states. When preview is running it can be set to the pause state and resumed -// back to the running state. +// States the preview handler can be in. When created, the handler starts in +// not started state and mostly transitions in sequential order of the states. +// When the preview is running, it can be set to the pause state and resumed to +// running state. enum class PreviewState { kNotStarted, kStarting, diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index 5822688d08ba..860e66aab5b7 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -29,7 +29,7 @@ struct MFVideoFormatRGB32Pixel { uint8_t x = 0; }; -// Handles the registration of flutter textures, pixel buffers, +// Handles the registration of Flutter textures, pixel buffers, // and the conversion of texture formats. class TextureHandler { public: From 4cfd61063834f0a15bcac7890a13d4c5b423fef1 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 23:36:32 +0200 Subject: [PATCH 87/93] [camera_windows] add dart unit tests --- .../camera_windows/lib/camera_windows.dart | 29 +- .../test/camera_windows_test.dart | 653 +++++++++++++++++- .../test/utils/method_channel_mock.dart | 39 ++ 3 files changed, 699 insertions(+), 22 deletions(-) create mode 100644 packages/camera/camera_windows/test/utils/method_channel_mock.dart diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index e5d23d20c0f2..80b9498dfaf5 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -30,12 +30,15 @@ class CameraWindows extends CameraPlatform { /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. - final StreamController _cameraEventStreamController = + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + final StreamController cameraEventStreamController = StreamController.broadcast(); /// Returns a stream of camera events for the given [cameraId]. Stream _cameraEvents(int cameraId) => - _cameraEventStreamController.stream + cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); @override @@ -52,7 +55,7 @@ class CameraWindows extends CameraPlatform { return CameraDescription( name: camera['name'] as String, lensDirection: - _parseCameraLensDirection(camera['lensFacing'] as String), + parseCameraLensDirection(camera['lensFacing'] as String), sensorOrientation: camera['sensorOrientation'] as int, ); }).toList(); @@ -98,7 +101,7 @@ class CameraWindows extends CameraPlatform { final MethodChannel channel = MethodChannel( 'plugins.flutter.io/camera_windows/camera$requestedCameraId'); channel.setMethodCallHandler( - (MethodCall call) => _handleCameraMethodCall(call, requestedCameraId), + (MethodCall call) => handleCameraMethodCall(call, requestedCameraId), ); return channel; }); @@ -115,7 +118,7 @@ class CameraWindows extends CameraPlatform { throw CameraException(e.code, e.message); } - _cameraEventStreamController.add( + cameraEventStreamController.add( CameraInitializedEvent( requestedCameraId, reply!['previewWidth']!, @@ -373,11 +376,13 @@ class CameraWindows extends CameraPlatform { } } - /// Converts messages received from the native platform into camera events. - Future _handleCameraMethodCall(MethodCall call, int cameraId) async { + /// Converts messages received from the native platform into camera events. /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'camera_closing': - _cameraEventStreamController.add( + cameraEventStreamController.add( CameraClosingEvent( cameraId, ), @@ -385,7 +390,7 @@ class CameraWindows extends CameraPlatform { break; case 'video_recorded': // This is called if maxVideoDuration was given on record start. - _cameraEventStreamController.add( + cameraEventStreamController.add( VideoRecordedEvent( cameraId, XFile(call.arguments['path'] as String), @@ -398,7 +403,7 @@ class CameraWindows extends CameraPlatform { ); break; case 'error': - _cameraEventStreamController.add( + cameraEventStreamController.add( CameraErrorEvent( cameraId, call.arguments['description'] as String, @@ -410,7 +415,9 @@ class CameraWindows extends CameraPlatform { } } - CameraLensDirection _parseCameraLensDirection(String string) { + /// Parses string presentation of the camera lens direction and returns enum value. + @visibleForTesting + CameraLensDirection parseCameraLensDirection(String string) { switch (string) { case 'front': return CameraLensDirection.front; diff --git a/packages/camera/camera_windows/test/camera_windows_test.dart b/packages/camera/camera_windows/test/camera_windows_test.dart index b682fbec446d..c1a0fe40325f 100644 --- a/packages/camera/camera_windows/test/camera_windows_test.dart +++ b/packages/camera/camera_windows/test/camera_windows_test.dart @@ -2,32 +2,663 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_windows/camera_windows.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import './utils/method_channel_mock.dart'; void main() { + const String pluginChannelName = 'plugins.flutter.io/camera_windows'; TestWidgetsFlutterBinding.ensureInitialized(); group('$CameraWindows()', () { - final CameraWindows plugin = CameraWindows(); + test('registered instance', () { + CameraWindows.registerWith(); + expect(CameraPlatform.instance, isA()); + }); + + group('Creation, Initialization & Disposal Tests', () { + test('Should send creation data and receive back a camera id', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: pluginChannelName, + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + } + }); + final CameraWindows plugin = CameraWindows(); + + // Act + final int cameraId = await plugin.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 0), + ResolutionPreset.high, + ); + + // Assert + expect(cameraMockChannel.log, [ + isMethodCall( + 'create', + arguments: { + 'cameraName': 'Test', + 'resolutionPreset': 'high', + 'enableAudio': false + }, + ), + ]); + expect(cameraId, 1); + }); - final List log = []; + test( + 'Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: pluginChannelName, + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final CameraWindows plugin = CameraWindows(); - setUp(() { - plugin.pluginChannel - .setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - return null; + // Act + expect( + () => plugin.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); }); - log.clear(); + test( + 'Should throw CameraException when initialize throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: pluginChannelName, + methods: { + 'initialize': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }, + ); + final CameraWindows plugin = CameraWindows(); + + // Act + expect( + () => plugin.initializeCamera(0), + throwsA( + isA() + .having((CameraException e) => e.code, 'code', + 'TESTING_ERROR_CODE') + .having( + (CameraException e) => e.description, + 'description', + 'Mock error message used during testing.', + ), + ), + ); + }, + ); + + test('Should send initialization data', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: pluginChannelName, + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + }, + 'initialize': { + 'previewWidth': 1920.toDouble(), + 'previewHeight': 1080.toDouble() + }, + }); + final CameraWindows plugin = CameraWindows(); + final int cameraId = await plugin.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + + // Act + await plugin.initializeCamera(cameraId); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + isMethodCall( + 'initialize', + arguments: {'cameraId': 1}, + ), + ]); + }); + + test('Should send a disposal call on dispose', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: pluginChannelName, + methods: { + 'create': {'cameraId': 1}, + 'initialize': { + 'previewWidth': 1920.toDouble(), + 'previewHeight': 1080.toDouble() + }, + 'dispose': {'cameraId': 1} + }); + + final CameraWindows plugin = CameraWindows(); + final int cameraId = await plugin.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + await plugin.initializeCamera(cameraId); + + // Act + await plugin.dispose(cameraId); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + anything, + isMethodCall( + 'dispose', + arguments: {'cameraId': 1}, + ), + ]); + }); }); - test('registered instance', () { - CameraWindows.registerWith(); - expect(CameraPlatform.instance, isA()); + group('Event Tests', () { + late CameraWindows plugin; + late int cameraId; + setUp(() async { + MethodChannelMock( + channelName: pluginChannelName, + methods: { + 'create': {'cameraId': 1}, + 'initialize': { + 'previewWidth': 1920.toDouble(), + 'previewHeight': 1080.toDouble() + }, + }, + ); + + plugin = CameraWindows(); + cameraId = await plugin.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + await plugin.initializeCamera(cameraId); + }); + + test('Should receive camera closing events', () async { + // Act + final Stream eventStream = + plugin.onCameraClosing(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraClosingEvent event = CameraClosingEvent(cameraId); + await plugin.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await plugin.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await plugin.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera error events', () async { + // Act + final Stream errorStream = + plugin.onCameraError(cameraId); + final StreamQueue streamQueue = + StreamQueue(errorStream); + + // Emit test events + final CameraErrorEvent event = + CameraErrorEvent(cameraId, 'Error Description'); + await plugin.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await plugin.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await plugin.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + }); + + group('Function Tests', () { + late CameraWindows plugin; + late int cameraId; + + setUp(() async { + MethodChannelMock( + channelName: pluginChannelName, + methods: { + 'create': {'cameraId': 1}, + 'initialize': { + 'previewWidth': 1920.toDouble(), + 'previewHeight': 1080.toDouble() + }, + }, + ); + plugin = CameraWindows(); + cameraId = await plugin.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + await plugin.initializeCamera(cameraId); + }); + + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + final List returnData = [ + { + 'name': 'Test 1', + 'lensFacing': 'front', + 'sensorOrientation': 1 + }, + { + 'name': 'Test 2', + 'lensFacing': 'back', + 'sensorOrientation': 2 + } + ]; + final MethodChannelMock channel = MethodChannelMock( + channelName: pluginChannelName, + methods: {'availableCameras': returnData}, + ); + + // Act + final List cameras = await plugin.availableCameras(); + + // Assert + expect(channel.log, [ + isMethodCall('availableCameras', arguments: null), + ]); + expect(cameras.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + final CameraDescription cameraDescription = CameraDescription( + name: returnData[i]['name']! as String, + lensDirection: plugin.parseCameraLensDirection( + returnData[i]['lensFacing']! as String), + sensorOrientation: returnData[i]['sensorOrientation']! as int, + ); + expect(cameras[i], cameraDescription); + } + }); + + test( + 'Should throw CameraException when availableCameras throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: pluginChannelName, + methods: { + 'availableCameras': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + + // Act + expect( + plugin.availableCameras, + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test('Should take a picture and return an XFile instance', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: pluginChannelName, + methods: {'takePicture': '/test/path.jpg'}); + + // Act + final XFile file = await plugin.takePicture(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('takePicture', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.jpg'); + }); + + test('Should prepare for video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: pluginChannelName, + methods: {'prepareForVideoRecording': null}, + ); + + // Act + await plugin.prepareForVideoRecording(); + + // Assert + expect(channel.log, [ + isMethodCall('prepareForVideoRecording', arguments: null), + ]); + }); + + test('Should start recording a video', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: pluginChannelName, + methods: {'startVideoRecording': null}, + ); + + // Act + await plugin.startVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': null, + }), + ]); + }); + + test('Should pass maxVideoDuration when starting recording a video', + () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: pluginChannelName, + methods: {'startVideoRecording': null}, + ); + + // Act + await plugin.startVideoRecording( + cameraId, + maxVideoDuration: const Duration(seconds: 10), + ); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': 10000 + }), + ]); + }); + + test('Should stop a video recording and return the file', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: pluginChannelName, + methods: {'stopVideoRecording': '/test/path.mp4'}, + ); + + // Act + final XFile file = await plugin.stopVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('stopVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.mp4'); + }); + + test('Should throw UnsupportedError when pause video recording is called', + () async { + // Act + expect( + () => plugin.pauseVideoRecording(cameraId), + throwsA(isA()), + ); + }); + + test( + 'Should throw UnsupportedError when resume video recording is called', + () async { + // Act + expect( + () => plugin.resumeVideoRecording(cameraId), + throwsA(isA()), + ); + }); + + test('Should throw UnimplementedError when flash mode is set', () async { + // Act + expect( + () => plugin.setFlashMode(cameraId, FlashMode.torch), + throwsA(isA()), + ); + }); + + test('Should throw UnimplementedError when exposure mode is set', + () async { + // Act + expect( + () => plugin.setExposureMode(cameraId, ExposureMode.auto), + throwsA(isA()), + ); + }); + + test('Should throw UnsupportedError when exposure point is set', + () async { + // Act + expect( + () => plugin.setExposurePoint(cameraId, null), + throwsA(isA()), + ); + }); + + test('Should get the min exposure offset', () async { + // Act + final double minExposureOffset = + await plugin.getMinExposureOffset(cameraId); + + // Assert + expect(minExposureOffset, 0.0); + }); + + test('Should get the max exposure offset', () async { + // Act + final double maxExposureOffset = + await plugin.getMaxExposureOffset(cameraId); + + // Assert + expect(maxExposureOffset, 0.0); + }); + + test('Should get the exposure offset step size', () async { + // Act + final double stepSize = + await plugin.getExposureOffsetStepSize(cameraId); + + // Assert + expect(stepSize, 1.0); + }); + + test('Should throw UnimplementedError when exposure offset is set', + () async { + // Act + expect( + () => plugin.setExposureOffset(cameraId, 0.5), + throwsA(isA()), + ); + }); + + test('Should throw UnimplementedError when focus mode is set', () async { + // Act + expect( + () => plugin.setFocusMode(cameraId, FocusMode.auto), + throwsA(isA()), + ); + }); + + test('Should throw UnsupportedError when exposure point is set', + () async { + // Act + expect( + () => plugin.setFocusMode(cameraId, FocusMode.auto), + throwsA(isA()), + ); + }); + + test('Should build a texture widget as preview widget', () async { + // Act + final Widget widget = plugin.buildPreview(cameraId); + + // Act + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); + }); + + test('Should throw UnimplementedError when handling unknown method', () { + final CameraWindows plugin = CameraWindows(); + + expect( + () => plugin.handleCameraMethodCall( + const MethodCall('unknown_method'), 1), + throwsA(isA())); + }); + + test('Should get the max zoom level', () async { + // Act + final double maxZoomLevel = await plugin.getMaxZoomLevel(cameraId); + + // Assert + expect(maxZoomLevel, 1.0); + }); + + test('Should get the min zoom level', () async { + // Act + final double maxZoomLevel = await plugin.getMinZoomLevel(cameraId); + + // Assert + expect(maxZoomLevel, 1.0); + }); + + test('Should throw UnimplementedError when zoom level is set', () async { + // Act + expect( + () => plugin.setZoomLevel(cameraId, 2.0), + throwsA(isA()), + ); + }); + + test( + 'Should throw UnimplementedError when lock capture orientation is called', + () async { + // Act + expect( + () => plugin.setZoomLevel(cameraId, 2.0), + throwsA(isA()), + ); + }); + + test( + 'Should throw UnimplementedError when unlock capture orientation is called', + () async { + // Act + expect( + () => plugin.unlockCaptureOrientation(cameraId), + throwsA(isA()), + ); + }); + + test('Should pause the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: pluginChannelName, + methods: {'pausePreview': null}, + ); + + // Act + await plugin.pausePreview(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('pausePreview', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should resume the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: pluginChannelName, + methods: {'resumePreview': null}, + ); + + // Act + await plugin.resumePreview(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumePreview', + arguments: {'cameraId': cameraId}), + ]); + }); }); }); } diff --git a/packages/camera/camera_windows/test/utils/method_channel_mock.dart b/packages/camera/camera_windows/test/utils/method_channel_mock.dart new file mode 100644 index 000000000000..2c152422b210 --- /dev/null +++ b/packages/camera/camera_windows/test/utils/method_channel_mock.dart @@ -0,0 +1,39 @@ +// 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 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class MethodChannelMock { + MethodChannelMock({ + required String channelName, + this.delay, + required this.methods, + }) : methodChannel = MethodChannel(channelName) { + methodChannel.setMockMethodCallHandler(_handler); + } + + final Duration? delay; + final MethodChannel methodChannel; + final Map methods; + final List log = []; + + Future _handler(MethodCall methodCall) async { + log.add(methodCall); + + if (!methods.containsKey(methodCall.method)) { + throw MissingPluginException('No TEST implementation found for method ' + '${methodCall.method} on channel ${methodChannel.name}'); + } + + return Future.delayed(delay ?? Duration.zero, () { + final dynamic result = methods[methodCall.method]; + if (result is Exception) { + throw result; + } + + return Future.value(result); + }); + } +} From 6daee1b4a0b4d86acb116eea33c841aec1ee0516 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Wed, 2 Feb 2022 23:49:47 +0200 Subject: [PATCH 88/93] [camera_windows] add missing test dependency --- packages/camera/camera_windows/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 1ab8af9d8e6d..1081c3dfc01f 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -24,5 +24,6 @@ dependencies: stream_transform: ^2.0.0 dev_dependencies: + async: ^2.5.0 flutter_test: sdk: flutter From 31c2e95233422c85d4c2182dd74b79efd54ef079 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 2 Feb 2022 15:24:29 -0800 Subject: [PATCH 89/93] Tweaks and cleanup for documentation comments --- .../camera_windows/lib/camera_windows.dart | 6 ++- .../test/utils/method_channel_mock.dart | 6 +++ .../camera/camera_windows/windows/camera.h | 41 +++++++++++-------- .../windows/capture_controller.h | 41 +++++++++++-------- .../windows/capture_controller_listener.h | 9 ++-- .../windows/capture_device_info.h | 3 +- .../windows/capture_engine_listener.h | 11 +++-- .../camera_windows/windows/photo_handler.h | 23 ++++++----- .../camera_windows/windows/preview_handler.h | 14 ++++--- .../camera_windows/windows/record_handler.h | 20 +++++---- .../camera_windows/windows/texture_handler.h | 4 +- 11 files changed, 104 insertions(+), 74 deletions(-) diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 80b9498dfaf5..33f8bfb68fac 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -376,8 +376,10 @@ class CameraWindows extends CameraPlatform { } } - /// Converts messages received from the native platform into camera events. /// This is only exposed for test purposes. It shouldn't be used by clients of - /// the plugin as it may break or change at any time. + /// Converts messages received from the native platform into camera events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients + /// of the plugin as it may break or change at any time. @visibleForTesting Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { diff --git a/packages/camera/camera_windows/test/utils/method_channel_mock.dart b/packages/camera/camera_windows/test/utils/method_channel_mock.dart index 2c152422b210..22f7ecead589 100644 --- a/packages/camera/camera_windows/test/utils/method_channel_mock.dart +++ b/packages/camera/camera_windows/test/utils/method_channel_mock.dart @@ -5,7 +5,13 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +/// A mock [MethodChannel] implementation for use in tests. class MethodChannelMock { + /// Creates a new instance with the specified channel name. + /// + /// This method channel will handle all method invocations specified by + /// returning the value mapped to the method name key. If a delay is + /// specified, results are returned after the delay has elapsed. MethodChannelMock({ required String channelName, this.delay, diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 3229200d9b41..2053c2f49472 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -30,8 +30,10 @@ enum class PendingResultType { kResumePreview, }; -// Interface for a class that handles camera messaging and its capture -// controller instance. +// Interface implemented by cameras. +// +// Access is provided to an associated |CaptureController|, which can be used +// to capture video or photo from the camera. class Camera : public CaptureControllerListener { public: explicit Camera(const std::string& device_id) {} @@ -41,33 +43,37 @@ class Camera : public CaptureControllerListener { Camera(const Camera&) = delete; Camera& operator=(const Camera&) = delete; - // Tests if current camera has given device id. + // Tests if this camera has the specified device ID. virtual bool HasDeviceId(std::string& device_id) = 0; - // Tests if current camera has given camera id. + // Tests if this camera has the specified camera ID. virtual bool HasCameraId(int64_t camera_id) = 0; // Adds a pending result. + // // Returns an error result if the result has already been added. virtual bool AddPendingResult(PendingResultType type, std::unique_ptr> result) = 0; - // Checks if pending result with given type already exists. + // Checks if a pending result of the specified type already exists. virtual bool HasPendingResultByType(PendingResultType type) = 0; - // Returns pointer to capture controller. + // Returns a |CaptureController| that allows capturing video or still photos + // from this camera. virtual camera_windows::CaptureController* GetCaptureController() = 0; - // Initializes camera and capture controller. + // Initializes this camera and its associated capture controller. virtual void InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) = 0; }; -// The camera wrapper, which initializes the capture controller, listens for its -// events, processes pending results and sends processed events through the -// method channel. +// Concrete implementation of the |Camera| interface. +// +// This implementation is responsible for initializing the capture controller, +// listening for camera events, processing pending results, and notifying +// application code of processed events via the method channel. class CameraImpl : public Camera { public: explicit CameraImpl(const std::string& device_id); @@ -114,8 +120,10 @@ class CameraImpl : public Camera { flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) override; - // Inits camera with capture controller factory. - // Called by InitCamera implementation but also used in tests. + // Initializes the camera and its associated capture controller. + // + // This is a convenience method called by |InitCamera| but also used in + // tests. void InitCamera( std::unique_ptr capture_controller_factory, flutter::TextureRegistrar* texture_registrar, @@ -123,9 +131,8 @@ class CameraImpl : public Camera { ResolutionPreset resolution_preset); private: - // Loops through all pending results and calls their - // error handler with given error id and description. - // Pending results are cleared in the process. + // Loops through all pending results and calls their error handler with given + // error ID and description. Pending results are cleared in the process. // // error_code: A string error code describing the error. // error_message: A user-readable error message (optional). @@ -152,7 +159,7 @@ class CameraImpl : public Camera { std::string device_id_; }; -// Interface for a class that creates camera objects. +// Factory class for creating |Camera| instances from a specified device ID. class CameraFactory { public: CameraFactory() {} @@ -167,7 +174,7 @@ class CameraFactory { const std::string& device_id) = 0; }; -// Creates camera instances. +// Concrete implementation of |CameraFactory|. class CameraFactoryImpl : public CameraFactory { public: CameraFactoryImpl() {} diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 6efe37187277..246b3ed49aef 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -28,7 +28,7 @@ namespace camera_windows { using flutter::TextureRegistrar; using Microsoft::WRL::ComPtr; -// A set of presets that can be used to request a capture resolution. +// Camera resolution presets. Used to request a capture resolution. enum class ResolutionPreset { // Automatic resolution, uses the highest resolution available. kAuto, @@ -46,9 +46,11 @@ enum class ResolutionPreset { kMax, }; -// Various states that the capture controller can have for capture engine -// it controls. When created the state is in an not initialized state -// and transtions in sequential order of the states. +// Camera capture engine state. +// +// On creation, |CaptureControllers| start in state |kNotInitialized| then. On +// initialization, the capture controller transitions to the |kInitializing| +// and then |kInitialized| state. enum class CaptureEngineState { kNotInitialized, kInitializing, kInitialized }; // Interface for a class that enumerates video capture device sources. @@ -58,7 +60,10 @@ class VideoCaptureDeviceEnumerator { UINT32* count) = 0; }; -// Interface for a capture controller. +// Interface implemented by capture controllers. +// +// Capture controllers are used to capture video streams or still photos from +// their associated |Camera|. class CaptureController { public: CaptureController() {} @@ -68,8 +73,7 @@ class CaptureController { CaptureController(const CaptureController&) = delete; CaptureController& operator=(const CaptureController&) = delete; - // Initializes capture controller with given device id. - // Requests to initialize capture engine. + // Initializes the capture controller with the specified device id. // // texture_registrar: Pointer to Flutter TextureRegistrar instance. Used to // register texture for capture preview. @@ -90,7 +94,6 @@ class CaptureController { virtual uint32_t GetPreviewHeight() = 0; // Starts the preview. - // Initializes preview handler and requests to start preview. virtual void StartPreview() = 0; // Pauses the preview. @@ -99,22 +102,22 @@ class CaptureController { // Resumes the preview. virtual void ResumePreview() = 0; - // Starts the record. - // Initializes record handler and requests to start recording. + // Starts recording video. virtual void StartRecord(const std::string& file_path, int64_t max_video_duration_ms) = 0; - // Stops the on going recording. - // Uses existing record handler and requests to stop recording. + // Stops the current video recording. virtual void StopRecord() = 0; - // Captures photo. - // Initializes photo handler and requests to take photo. + // Captures a still photo. virtual void TakePicture(const std::string file_path) = 0; }; -// Handles creating the capture engine for preview, video capture and photo -// capture. Processes events and samples sent by the capture engine instance. +// Concrete implementation of the |CaptureController| interface. +// +// Handles the video preview stream via a |PreviewHandler| instance, video +// capture via a |RecordHandler| instance, and still photo capture via a +// |PhotoHandler| instance. class CaptureControllerImpl : public CaptureController, public CaptureEngineObserver { public: @@ -251,7 +254,7 @@ class CaptureControllerImpl : public CaptureController, TextureRegistrar* texture_registrar_ = nullptr; }; -// Interface for a class that creates capture controllers. +// Inferface for factory classes that create |CaptureController| instances. class CaptureControllerFactory { public: CaptureControllerFactory() {} @@ -261,11 +264,13 @@ class CaptureControllerFactory { CaptureControllerFactory(const CaptureControllerFactory&) = delete; CaptureControllerFactory& operator=(const CaptureControllerFactory&) = delete; + // Create and return a |CaptureController| that makes callbacks on the + // specified |CaptureControllerListener|, which must not be null. virtual std::unique_ptr CreateCaptureController( CaptureControllerListener* listener) = 0; }; -// Creates capture controller instance. +// Concreate implementation of |CaptureControllerFactory|. class CaptureControllerFactoryImpl : public CaptureControllerFactory { public: CaptureControllerFactoryImpl() {} diff --git a/packages/camera/camera_windows/windows/capture_controller_listener.h b/packages/camera/camera_windows/windows/capture_controller_listener.h index ba75af6ff73b..0e713ea7af18 100644 --- a/packages/camera/camera_windows/windows/capture_controller_listener.h +++ b/packages/camera/camera_windows/windows/capture_controller_listener.h @@ -9,7 +9,8 @@ namespace camera_windows { -// Interface for classes that listen events from the capture controller. +// Interface for classes that receives callbacks on events from the associated +// |CaptureController|. class CaptureControllerListener { public: virtual ~CaptureControllerListener() = default; @@ -61,7 +62,7 @@ class CaptureControllerListener { // Called by CaptureController on successfully stopped recording. // - // file_path: File path to the recorded video file. + // file_path: Filesystem path of the recorded video file. virtual void OnStopRecordSucceeded(const std::string& file_path) = 0; // Called by CaptureController if stopping the recording fails. @@ -71,7 +72,7 @@ class CaptureControllerListener { // Called by CaptureController on successfully captured picture. // - // file_path: File path to the captured image. + // file_path: Filesystem path of the captured image. virtual void OnTakePictureSucceeded(const std::string& file_path) = 0; // Called by CaptureController if taking picture fails. @@ -81,7 +82,7 @@ class CaptureControllerListener { // Called by CaptureController when timed recording is successfully recorded. // - // file_path: File path to the captured image. + // file_path: Filesystem path of the captured image. // video_duration: Duration of recorded video in milliseconds. virtual void OnVideoRecordSucceeded(const std::string& file_path, int64_t video_duration_ms) = 0; diff --git a/packages/camera/camera_windows/windows/capture_device_info.h b/packages/camera/camera_windows/windows/capture_device_info.h index 78d6e584ac40..71c4bb399827 100644 --- a/packages/camera/camera_windows/windows/capture_device_info.h +++ b/packages/camera/camera_windows/windows/capture_device_info.h @@ -9,8 +9,7 @@ namespace camera_windows { -// Holds capture device information and converts it to a unique device name. -// Parses device name back to a display id and device id. +// Name and device ID information for a capture device. class CaptureDeviceInfo { public: CaptureDeviceInfo() {} diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 6ff7f1ef6f7b..3ea2b22772a2 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -12,8 +12,7 @@ namespace camera_windows { -// An interface for a class that listens and handles the -// events from capture engine listener. +// A class that implements callbacks for events from a |CaptureEngineListneer|. class CaptureEngineObserver { public: virtual ~CaptureEngineObserver() = default; @@ -32,10 +31,10 @@ class CaptureEngineObserver { virtual void UpdateCaptureTime(uint64_t capture_time) = 0; }; -// Listener for capture engine events and samples. -// Events are redirected for observers for processing. -// Samples are preprosessed and send to observer if it -// is ready to process samples. +// Listener for Windows Media Foundation capture engine events and samples. +// +// Events are redirected to observers for processing. Samples are preprosessed +// and sent to the associated observer if it is ready to process samples. class CaptureEngineListener : public IMFCaptureEngineOnSampleCallback, public IMFCaptureEngineOnEventCallback { public: diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index c867393589b1..3388ee0198fb 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -17,15 +17,17 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -// Various states that the photo handler can be in. When created the handler is -// in an not started state and transtions in sequential order of the states. +// Various states that the photo handler can be in. +// +// When created, the handler is in |kNotStarted| state and transtions in +// sequential order through the states. enum class PhotoState { kNotStarted, kIdle, kTakingPhoto, }; -// Handles the photo sink initialization and tracks photo capture states. +// Handles photo sink initialization and tracks photo capture states. class PhotoHandler { public: PhotoHandler() {} @@ -35,9 +37,10 @@ class PhotoHandler { PhotoHandler(PhotoHandler const&) = delete; PhotoHandler& operator=(PhotoHandler const&) = delete; - // Initializes photo sink if not initialized and requests - // capture engine to take photo. - // Sets photo state to: taking. + // Initializes photo sink if not initialized and requests the capture engine + // to take photo. + // + // Sets photo state to: kTakingPhoto. // Returns false if photo cannot be taken. // // capture_engine: A pointer to capture engine instance. @@ -48,16 +51,16 @@ class PhotoHandler { bool TakePhoto(const std::string& file_path, IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type); - // Set the photo handler recording state to: idle. + // Set the photo handler recording state to: kIdel. void OnPhotoTaken(); - // Returns true if photo state is idle + // Returns true if photo state is kIdle. bool IsInitialized() { return photo_state_ == PhotoState::kIdle; } - // Returns true if photo state is taking. + // Returns true if photo state is kTakingPhoto. bool IsTakingPhoto() { return photo_state_ == PhotoState::kTakingPhoto; } - // Returns path to photo capture. + // Returns the filesystem path of the captured photo. std::string GetPhotoPath() { return file_path_; } private: diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h index cf18efa7ebe7..dc2cc2c088cf 100644 --- a/packages/camera/camera_windows/windows/preview_handler.h +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -17,10 +17,11 @@ namespace camera_windows { using Microsoft::WRL::ComPtr; -// States the preview handler can be in. When created, the handler starts in -// not started state and mostly transitions in sequential order of the states. -// When the preview is running, it can be set to the pause state and resumed to -// running state. +// States the preview handler can be in. +// +// When created, the handler starts in |kNotStarted| state and mostly +// transitions in sequential order of the states. When the preview is running, +// it can be set to the |kPaused| state and later resumed to |kRunning| state. enum class PreviewState { kNotStarted, kStarting, @@ -29,7 +30,10 @@ enum class PreviewState { kStopping }; -// Handles the preview sink initialization and tracks preview states. +// Handler for a camera's video preview. +// +// Handles preview sink initialization and manages the state of the video +// preview. class PreviewHandler { public: PreviewHandler() {} diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index c50b4c6e6c15..2445b0527baf 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -22,12 +22,15 @@ enum class RecordingType { kTimed }; -// Various states that the record handler can be in. When created the handler -// is in an not started state and transtions in sequential order of the -// states. +// States that the record handler can be in. +// +// When created, the handler is in an not started state and transtions in +// sequential order through the states. enum class RecordState { kNotStarted, kStarting, kRunning, kStopping }; -// Handles the record sink initialization and tracks record capture states. +// Handler for video recording via the camera. +// +// Handles record sink initialization and manages the state of video recording. class RecordHandler { public: RecordHandler(bool record_audio) : record_audio_(record_audio) {} @@ -38,6 +41,7 @@ class RecordHandler { RecordHandler& operator=(RecordHandler const&) = delete; // Initializes record sink and requests capture engine to start recording. + // // Sets record state to: starting. // Returns false if recording cannot be started. // @@ -79,17 +83,17 @@ class RecordHandler { // Returns true if recording can be stopped. bool CanStop() { return recording_state_ == RecordState::kRunning; } - // Returns path to video recording. + // Returns the filesystem path of the video recording. std::string GetRecordPath() { return file_path_; } - // Returns path to video recording in microseconds. + // Returns the duration of the video recording in microseconds. uint64_t GetRecordedDuration() { return recording_duration_us_; } // Calculates new recording time from capture timestamp. void UpdateRecordingTime(uint64_t timestamp); - // Tests if recording time has overlapped the max duration - // given for timed recordings. + // Returns true if recording time has exceeded the maximum duration for timed + // recordings. bool ShouldStopTimedRecording(); private: diff --git a/packages/camera/camera_windows/windows/texture_handler.h b/packages/camera/camera_windows/windows/texture_handler.h index 860e66aab5b7..b85611c25608 100644 --- a/packages/camera/camera_windows/windows/texture_handler.h +++ b/packages/camera/camera_windows/windows/texture_handler.h @@ -29,8 +29,8 @@ struct MFVideoFormatRGB32Pixel { uint8_t x = 0; }; -// Handles the registration of Flutter textures, pixel buffers, -// and the conversion of texture formats. +// Handles the registration of Flutter textures, pixel buffers, and the +// conversion of texture formats. class TextureHandler { public: TextureHandler(flutter::TextureRegistrar* texture_registrar) From 6bcb48543edc61faf4197398ae1a742dded4552b Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 2 Feb 2022 15:25:02 -0800 Subject: [PATCH 90/93] Make functions const where possible --- packages/camera/camera_windows/windows/camera.cpp | 2 +- packages/camera/camera_windows/windows/camera.h | 12 ++++++------ .../camera_windows/windows/capture_controller.cpp | 2 +- .../camera_windows/windows/capture_controller.h | 14 +++++++------- .../camera_windows/windows/capture_device_info.cpp | 2 +- .../camera_windows/windows/capture_device_info.h | 4 ++-- .../windows/capture_engine_listener.h | 2 +- .../camera/camera_windows/windows/photo_handler.h | 6 +++--- .../camera_windows/windows/preview_handler.h | 8 ++++---- .../camera_windows/windows/record_handler.cpp | 2 +- .../camera/camera_windows/windows/record_handler.h | 14 +++++++------- .../camera/camera_windows/windows/test/mocks.h | 10 +++++----- 12 files changed, 39 insertions(+), 39 deletions(-) diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp index 1f3bb5e548f5..c21f8ab0af78 100644 --- a/packages/camera/camera_windows/windows/camera.cpp +++ b/packages/camera/camera_windows/windows/camera.cpp @@ -76,7 +76,7 @@ std::unique_ptr> CameraImpl::GetPendingResultByType( return result; } -bool CameraImpl::HasPendingResultByType(PendingResultType type) { +bool CameraImpl::HasPendingResultByType(PendingResultType type) const { auto it = pending_results_.find(type); if (it == pending_results_.end()) { return false; diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h index 2053c2f49472..6996231c7ab4 100644 --- a/packages/camera/camera_windows/windows/camera.h +++ b/packages/camera/camera_windows/windows/camera.h @@ -44,10 +44,10 @@ class Camera : public CaptureControllerListener { Camera& operator=(const Camera&) = delete; // Tests if this camera has the specified device ID. - virtual bool HasDeviceId(std::string& device_id) = 0; + virtual bool HasDeviceId(std::string& device_id) const = 0; // Tests if this camera has the specified camera ID. - virtual bool HasCameraId(int64_t camera_id) = 0; + virtual bool HasCameraId(int64_t camera_id) const = 0; // Adds a pending result. // @@ -56,7 +56,7 @@ class Camera : public CaptureControllerListener { std::unique_ptr> result) = 0; // Checks if a pending result of the specified type already exists. - virtual bool HasPendingResultByType(PendingResultType type) = 0; + virtual bool HasPendingResultByType(PendingResultType type) const = 0; // Returns a |CaptureController| that allows capturing video or still photos // from this camera. @@ -104,15 +104,15 @@ class CameraImpl : public Camera { void OnCaptureError(const std::string& error) override; // Camera - bool HasDeviceId(std::string& device_id) override { + bool HasDeviceId(std::string& device_id) const override { return device_id_ == device_id; } - bool HasCameraId(int64_t camera_id) override { + bool HasCameraId(int64_t camera_id) const override { return camera_id_ == camera_id; } bool AddPendingResult(PendingResultType type, std::unique_ptr> result) override; - bool HasPendingResultByType(PendingResultType type) override; + bool HasPendingResultByType(PendingResultType type) const override; camera_windows::CaptureController* GetCaptureController() override { return capture_controller_.get(); } diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 14abaeaf0eef..7c80ec4a1a2d 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -361,7 +361,7 @@ void CaptureControllerImpl::TakePicture(const std::string file_path) { } } -uint32_t CaptureControllerImpl::GetMaxPreviewHeight() { +uint32_t CaptureControllerImpl::GetMaxPreviewHeight() const { switch (resolution_preset_) { case ResolutionPreset::kLow: return 240; diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 246b3ed49aef..3ae1ded08077 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -88,10 +88,10 @@ class CaptureController { ResolutionPreset resolution_preset) = 0; // Returns preview frame width - virtual uint32_t GetPreviewWidth() = 0; + virtual uint32_t GetPreviewWidth() const = 0; // Returns preview frame height - virtual uint32_t GetPreviewHeight() = 0; + virtual uint32_t GetPreviewHeight() const = 0; // Starts the preview. virtual void StartPreview() = 0; @@ -135,8 +135,8 @@ class CaptureControllerImpl : public CaptureController, void InitCaptureDevice(TextureRegistrar* texture_registrar, const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset) override; - uint32_t GetPreviewWidth() override { return preview_frame_width_; } - uint32_t GetPreviewHeight() override { return preview_frame_height_; } + uint32_t GetPreviewWidth() const override { return preview_frame_width_; } + uint32_t GetPreviewHeight() const override { return preview_frame_height_; } void StartPreview() override; void PausePreview() override; void ResumePreview() override; @@ -147,7 +147,7 @@ class CaptureControllerImpl : public CaptureController, // CaptureEngineObserver void OnEvent(IMFMediaEvent* event) override; - bool IsReadyForSample() override { + bool IsReadyForSample() const override { return capture_engine_state_ == CaptureEngineState::kInitialized && preview_handler_ && preview_handler_->IsRunning(); } @@ -171,7 +171,7 @@ class CaptureControllerImpl : public CaptureController, private: // Helper function to return initialized state as boolean; - bool IsInitialized() { + bool IsInitialized() const { return capture_engine_state_ == CaptureEngineState::kInitialized; } @@ -180,7 +180,7 @@ class CaptureControllerImpl : public CaptureController, void ResetCaptureController(); // Returns max preview height calculated from resolution present. - uint32_t GetMaxPreviewHeight(); + uint32_t GetMaxPreviewHeight() const; // Uses first audio source to capture audio. // Note: Enumerating audio sources via platform interface is not supported. diff --git a/packages/camera/camera_windows/windows/capture_device_info.cpp b/packages/camera/camera_windows/windows/capture_device_info.cpp index dab7d14e7298..446056a71c44 100644 --- a/packages/camera/camera_windows/windows/capture_device_info.cpp +++ b/packages/camera/camera_windows/windows/capture_device_info.cpp @@ -8,7 +8,7 @@ #include namespace camera_windows { -std::string CaptureDeviceInfo::GetUniqueDeviceName() { +std::string CaptureDeviceInfo::GetUniqueDeviceName() const { return display_name_ + " <" + device_id_ + ">"; } diff --git a/packages/camera/camera_windows/windows/capture_device_info.h b/packages/camera/camera_windows/windows/capture_device_info.h index 71c4bb399827..63ffa8571092 100644 --- a/packages/camera/camera_windows/windows/capture_device_info.h +++ b/packages/camera/camera_windows/windows/capture_device_info.h @@ -21,7 +21,7 @@ class CaptureDeviceInfo { // Build unique device name from display name and device id. // Format: "display_name ". - std::string GetUniqueDeviceName(); + std::string GetUniqueDeviceName() const; // Parses display name and device id from unique device name format. // Format: "display_name ". @@ -37,7 +37,7 @@ class CaptureDeviceInfo { void SetDeviceID(const std::string& device_id) { device_id_ = device_id; } // Returns device id. - std::string GetDeviceId() { return device_id_; } + std::string GetDeviceId() const { return device_id_; } private: std::string display_name_; diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 3ea2b22772a2..9d49a78de02b 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -18,7 +18,7 @@ class CaptureEngineObserver { virtual ~CaptureEngineObserver() = default; // Returns true if sample can be processed. - virtual bool IsReadyForSample() = 0; + virtual bool IsReadyForSample() const = 0; // Handles Capture Engine media events. virtual void OnEvent(IMFMediaEvent* event) = 0; diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index 3388ee0198fb..9342910daed9 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -55,13 +55,13 @@ class PhotoHandler { void OnPhotoTaken(); // Returns true if photo state is kIdle. - bool IsInitialized() { return photo_state_ == PhotoState::kIdle; } + bool IsInitialized() const { return photo_state_ == PhotoState::kIdle; } // Returns true if photo state is kTakingPhoto. - bool IsTakingPhoto() { return photo_state_ == PhotoState::kTakingPhoto; } + bool IsTakingPhoto() const { return photo_state_ == PhotoState::kTakingPhoto; } // Returns the filesystem path of the captured photo. - std::string GetPhotoPath() { return file_path_; } + std::string GetPhotoPath() const { return file_path_; } private: // Initializes record sink for video file capture. diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h index dc2cc2c088cf..97b85fc28568 100644 --- a/packages/camera/camera_windows/windows/preview_handler.h +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -74,19 +74,19 @@ class PreviewHandler { void OnPreviewStarted(); // Returns true if preview state is running or paused. - bool IsInitialized() { + bool IsInitialized() const { return preview_state_ == PreviewState::kRunning && preview_state_ == PreviewState::kPaused; } // Returns true if preview state is running. - bool IsRunning() { return preview_state_ == PreviewState::kRunning; } + bool IsRunning() const { return preview_state_ == PreviewState::kRunning; } // Return true if preview state is paused. - bool IsPaused() { return preview_state_ == PreviewState::kPaused; } + bool IsPaused() const { return preview_state_ == PreviewState::kPaused; } // Returns true if preview state is starting. - bool IsStarting() { return preview_state_ == PreviewState::kStarting; } + bool IsStarting() const { return preview_state_ == PreviewState::kStarting; } private: // Initializes record sink for video file capture. diff --git a/packages/camera/camera_windows/windows/record_handler.cpp b/packages/camera/camera_windows/windows/record_handler.cpp index 7538f1a3ab8c..1cb258e162a5 100644 --- a/packages/camera/camera_windows/windows/record_handler.cpp +++ b/packages/camera/camera_windows/windows/record_handler.cpp @@ -249,7 +249,7 @@ void RecordHandler::UpdateRecordingTime(uint64_t timestamp) { recording_duration_us_ = (timestamp - recording_start_timestamp_us_); } -bool RecordHandler::ShouldStopTimedRecording() { +bool RecordHandler::ShouldStopTimedRecording() const { return type_ == RecordingType::kTimed && recording_state_ == RecordState::kRunning && max_video_duration_ms_ > 0 && diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index 2445b0527baf..8ec1458d73b3 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -72,29 +72,29 @@ class RecordHandler { void OnRecordStopped(); // Returns true if recording type is continuous recording. - bool IsContinuousRecording() { return type_ == RecordingType::kContinuous; } + bool IsContinuousRecording() const { return type_ == RecordingType::kContinuous; } // Returns true if recording type is timed recording. - bool IsTimedRecording() { return type_ == RecordingType::kTimed; } + bool IsTimedRecording() const { return type_ == RecordingType::kTimed; } // Returns true if new recording can be started. - bool CanStart() { return recording_state_ == RecordState::kNotStarted; } + bool CanStart() const { return recording_state_ == RecordState::kNotStarted; } // Returns true if recording can be stopped. - bool CanStop() { return recording_state_ == RecordState::kRunning; } + bool CanStop() const { return recording_state_ == RecordState::kRunning; } // Returns the filesystem path of the video recording. - std::string GetRecordPath() { return file_path_; } + std::string GetRecordPath() const { return file_path_; } // Returns the duration of the video recording in microseconds. - uint64_t GetRecordedDuration() { return recording_duration_us_; } + uint64_t GetRecordedDuration() const { return recording_duration_us_; } // Calculates new recording time from capture timestamp. void UpdateRecordingTime(uint64_t timestamp); // Returns true if recording time has exceeded the maximum duration for timed // recordings. - bool ShouldStopTimedRecording(); + bool ShouldStopTimedRecording() const; private: // Initializes record sink for video file capture. diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index dab30ce764d9..c86d1ca4a090 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -170,14 +170,14 @@ class MockCamera : public Camera { (override)); MOCK_METHOD(void, OnCaptureError, (const std::string& error), (override)); - MOCK_METHOD(bool, HasDeviceId, (std::string & device_id), (override)); - MOCK_METHOD(bool, HasCameraId, (int64_t camera_id), (override)); + MOCK_METHOD(bool, HasDeviceId, (std::string & device_id), (const override)); + MOCK_METHOD(bool, HasCameraId, (int64_t camera_id), (const override)); MOCK_METHOD(bool, AddPendingResult, (PendingResultType type, std::unique_ptr> result), (override)); MOCK_METHOD(bool, HasPendingResultByType, (PendingResultType type), - (override)); + (const override)); MOCK_METHOD(camera_windows::CaptureController*, GetCaptureController, (), (override)); @@ -218,8 +218,8 @@ class MockCaptureController : public CaptureController { ResolutionPreset resolution_preset), (override)); - MOCK_METHOD(uint32_t, GetPreviewWidth, (), (override)); - MOCK_METHOD(uint32_t, GetPreviewHeight, (), (override)); + MOCK_METHOD(uint32_t, GetPreviewWidth, (), (const override)); + MOCK_METHOD(uint32_t, GetPreviewHeight, (), (const override)); // Actions MOCK_METHOD(void, StartPreview, (), (override)); From 003f5f4bc336f16aa5e24ccb6e27a13f11b6969c Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 2 Feb 2022 17:07:53 -0800 Subject: [PATCH 91/93] Pass strings by const reference There were a few places where strings were being passed by value. Instead, pass by const reference. --- .../camera/camera_windows/windows/capture_controller.cpp | 2 +- packages/camera/camera_windows/windows/capture_controller.h | 4 ++-- .../camera_windows/windows/test/camera_plugin_test.cpp | 6 +++--- packages/camera/camera_windows/windows/test/mocks.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 7c80ec4a1a2d..084b03640bef 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -330,7 +330,7 @@ void CaptureControllerImpl::InitCaptureDevice( } } -void CaptureControllerImpl::TakePicture(const std::string file_path) { +void CaptureControllerImpl::TakePicture(const std::string& file_path) { assert(capture_engine_callback_handler_); assert(capture_engine_); diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 3ae1ded08077..105ce5f3260b 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -110,7 +110,7 @@ class CaptureController { virtual void StopRecord() = 0; // Captures a still photo. - virtual void TakePicture(const std::string file_path) = 0; + virtual void TakePicture(const std::string& file_path) = 0; }; // Concrete implementation of the |CaptureController| interface. @@ -143,7 +143,7 @@ class CaptureControllerImpl : public CaptureController, void StartRecord(const std::string& file_path, int64_t max_video_duration_ms) override; void StopRecord() override; - void TakePicture(const std::string file_path) override; + void TakePicture(const std::string& file_path) override; // CaptureEngineObserver void OnEvent(IMFMediaEvent* event) override; 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 45c1bfb90326..309268a1fb90 100644 --- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp +++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp @@ -400,7 +400,7 @@ TEST(CameraPlugin, TakePictureHandlerCallsTakePictureWithPath) { EXPECT_CALL(*capture_controller, TakePicture(EndsWith(".jpeg"))) .Times(1) - .WillOnce([cam = camera.get()](const std::string file_path) { + .WillOnce([cam = camera.get()](const std::string& file_path) { assert(cam->pending_result_); return cam->pending_result_->Success(); }); @@ -514,7 +514,7 @@ TEST(CameraPlugin, StartVideoRecordingHandlerCallsStartRecordWithPath) { EXPECT_CALL(*capture_controller, StartRecord(EndsWith(".mp4"), -1)) .Times(1) - .WillOnce([cam = camera.get()](const std::string file_path, + .WillOnce([cam = camera.get()](const std::string& file_path, int64_t max_video_duration_ms) { assert(cam->pending_result_); return cam->pending_result_->Success(); @@ -586,7 +586,7 @@ TEST(CameraPlugin, EXPECT_CALL(*capture_controller, StartRecord(EndsWith(".mp4"), Eq(mock_video_duration))) .Times(1) - .WillOnce([cam = camera.get()](const std::string file_path, + .WillOnce([cam = camera.get()](const std::string& file_path, int64_t max_video_duration_ms) { assert(cam->pending_result_); return cam->pending_result_->Success(); diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h index c86d1ca4a090..0781989e94c2 100644 --- a/packages/camera/camera_windows/windows/test/mocks.h +++ b/packages/camera/camera_windows/windows/test/mocks.h @@ -229,7 +229,7 @@ class MockCaptureController : public CaptureController { (const std::string& file_path, int64_t max_video_duration_ms), (override)); MOCK_METHOD(void, StopRecord, (), (override)); - MOCK_METHOD(void, TakePicture, (const std::string file_path), (override)); + MOCK_METHOD(void, TakePicture, (const std::string& file_path), (override)); }; // MockCameraPlugin extends CameraPlugin behaviour a bit to allow adding cameras From b689b2caa7361db1eb861577982beb82a2fa3150 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 3 Feb 2022 08:18:19 +0200 Subject: [PATCH 92/93] [camera_windows] tweaks to the documentation --- packages/camera/camera_windows/windows/capture_controller.h | 4 ++-- .../camera/camera_windows/windows/capture_engine_listener.h | 2 +- packages/camera/camera_windows/windows/record_handler.h | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 105ce5f3260b..34e378109d8f 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -48,8 +48,8 @@ enum class ResolutionPreset { // Camera capture engine state. // -// On creation, |CaptureControllers| start in state |kNotInitialized| then. On -// initialization, the capture controller transitions to the |kInitializing| +// On creation, |CaptureControllers| start in state |kNotInitialized|. +// On initialization, the capture controller transitions to the |kInitializing| // and then |kInitialized| state. enum class CaptureEngineState { kNotInitialized, kInitializing, kInitialized }; diff --git a/packages/camera/camera_windows/windows/capture_engine_listener.h b/packages/camera/camera_windows/windows/capture_engine_listener.h index 9d49a78de02b..081e3ea0f764 100644 --- a/packages/camera/camera_windows/windows/capture_engine_listener.h +++ b/packages/camera/camera_windows/windows/capture_engine_listener.h @@ -12,7 +12,7 @@ namespace camera_windows { -// A class that implements callbacks for events from a |CaptureEngineListneer|. +// A class that implements callbacks for events from a |CaptureEngineListener|. class CaptureEngineObserver { public: virtual ~CaptureEngineObserver() = default; diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index 8ec1458d73b3..0daa7f6546a1 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -24,7 +24,7 @@ enum class RecordingType { // States that the record handler can be in. // -// When created, the handler is in an not started state and transtions in +// When created, the handler starts in |kNotStarted| state and transtions in // sequential order through the states. enum class RecordState { kNotStarted, kStarting, kRunning, kStopping }; @@ -72,7 +72,9 @@ class RecordHandler { void OnRecordStopped(); // Returns true if recording type is continuous recording. - bool IsContinuousRecording() const { return type_ == RecordingType::kContinuous; } + bool IsContinuousRecording() const { + return type_ == RecordingType::kContinuous; + } // Returns true if recording type is timed recording. bool IsTimedRecording() const { return type_ == RecordingType::kTimed; } From 7b1845f2c245d15810004acfffc2c4c41492504c Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 3 Feb 2022 13:06:46 +0200 Subject: [PATCH 93/93] [camera_windows] fix formatting --- packages/camera/camera_windows/windows/photo_handler.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index 9342910daed9..ef0d98bfc45f 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -58,7 +58,9 @@ class PhotoHandler { bool IsInitialized() const { return photo_state_ == PhotoState::kIdle; } // Returns true if photo state is kTakingPhoto. - bool IsTakingPhoto() const { return photo_state_ == PhotoState::kTakingPhoto; } + bool IsTakingPhoto() const { + return photo_state_ == PhotoState::kTakingPhoto; + } // Returns the filesystem path of the captured photo. std::string GetPhotoPath() const { return file_path_; }