Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[camera_windows] Restore image streaming support #8234

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.11.1

* Allows image streaming on Windows platform.

## 0.11.0+2

* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
Expand Down
14 changes: 8 additions & 6 deletions packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -480,13 +480,14 @@ class CameraController extends ValueNotifier<CameraValue> {
/// Throws a [CameraException] if image streaming or video recording has
/// already started.
///
/// The `startImageStream` method is only available on Android and iOS (other
/// platforms won't be supported in current setup).
/// The `startImageStream` method is only available on Android, iOS and
/// Windows (other platforms won't be supported in current setup).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #7220 (comment). This PR should include adding an API to query support, rather than modifying the legacy approach of hard-coding platforms.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, Windows is the sole outlier without image streaming support. Perhaps we could just eliminate the check altogether?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

camera_web does not support streaming. There may also be third-party, unendorsed implementation that don't support streaming.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve been looking into this, but it’s not quite clear how to deal with the versioning. If I add a supportsImageStreaming method to camera_platform_interface and bump the version to 2.9.0, then the platform packages won’t be able to depend on that, since they resolve the dependency from pub.dev.

So, as I understand the situation, we should:

  • first create a PR with just that method in the platform interface,
  • release platform interface 2.9.0 and then
  • implement that method in the platform packages by updating their camera_platform_interface dependency to ^2.9.0

Is this right at all, or am I missing something?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #8250 to get started.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

///
// TODO(bmparr): Add settings for resolution and fps.
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {
assert(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);
defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.windows);
_throwIfNotInitialized('startImageStream');
if (value.isRecordingVideo) {
throw CameraException(
Expand Down Expand Up @@ -518,11 +519,12 @@ class CameraController extends ValueNotifier<CameraValue> {
/// Throws a [CameraException] if image streaming was not started or video
/// recording was started.
///
/// The `stopImageStream` method is only available on Android and iOS (other
/// platforms won't be supported in current setup).
/// The `stopImageStream` method is only available on Android, iOS and
/// Windows (other platforms won't be supported in current setup).
Future<void> stopImageStream() async {
assert(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);
defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.windows);
_throwIfNotInitialized('stopImageStream');
if (!value.isStreamingImages) {
throw CameraException(
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.11.0+2
version: 0.11.1

environment:
sdk: ^3.3.0
Expand Down
4 changes: 4 additions & 0 deletions packages/camera/camera_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.7

* Restores support for streaming frames.

## 0.2.6+1

* Fixes black bars on camera preview [#122966](https://github.com/flutter/flutter/issues/122966).
Expand Down
32 changes: 32 additions & 0 deletions packages/camera/camera_windows/lib/camera_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
import 'package:stream_transform/stream_transform.dart';

import 'src/messages.g.dart';
import 'type_conversion.dart';

/// An implementation of [CameraPlatform] for Windows.
class CameraWindows extends CameraPlatform {
Expand Down Expand Up @@ -234,6 +235,37 @@ class CameraWindows extends CameraPlatform {
'resumeVideoRecording() is not supported due to Win32 API limitations.');
}

@override
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
{CameraImageStreamOptions? options}) {
late StreamController<CameraImageData> controller;
StreamSubscription<dynamic>? subscription;

controller = StreamController<CameraImageData>(
onListen: () async {
final String eventChannelName =
await _hostApi.startImageStream(cameraId);
final EventChannel imageStreamChannel =
EventChannel(eventChannelName);
subscription = imageStreamChannel.receiveBroadcastStream().listen(
(dynamic image) => controller.add(
cameraImageFromPlatformData(image as Map<dynamic, dynamic>)));
},
onPause: _onFrameStreamPauseResume,
onResume: _onFrameStreamPauseResume,
onCancel: () async {
// Cancelling the subscription stops the image capture on the native side.
await subscription?.cancel();
});

return controller.stream;
}

void _onFrameStreamPauseResume() {
throw CameraException('InvalidCall',
'Pause and resume are not supported for onStreamedFrameAvailable');
}

@override
Future<void> setFlashMode(int cameraId, FlashMode mode) async {
// TODO(jokerttu): Implement flash mode support, https://github.com/flutter/flutter/issues/97537.
Expand Down
34 changes: 33 additions & 1 deletion packages/camera/camera_windows/lib/src/messages.g.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Autogenerated from Pigeon (v22.6.0), do not edit directly.
// Autogenerated from Pigeon (v22.6.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers

Expand Down Expand Up @@ -362,6 +362,38 @@ class CameraApi {
}
}

/// Starts the image stream for the given camera.
/// Returns the name of the [EventChannel] used to deliver the images.
/// Cancelling the subscription to the channel stops the capture.
Future<String> startImageStream(int cameraId) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as String?)!;
}
}

/// Starts the preview stream for the given camera.
Future<void> pausePreview(int cameraId) async {
final String pigeonVar_channelName =
Expand Down
25 changes: 25 additions & 0 deletions packages/camera/camera_windows/lib/type_conversion.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data';

import 'package:camera_platform_interface/camera_platform_interface.dart';

/// Converts method channel call [data] for `receivedImageStreamData` to a
/// [CameraImageData].
CameraImageData cameraImageFromPlatformData(Map<dynamic, dynamic> data) {
return CameraImageData(
format: const CameraImageFormat(ImageFormatGroup.bgra8888, raw: 0),
height: data['height'] as int,
width: data['width'] as int,
lensAperture: data['lensAperture'] as double?,
sensorExposureTime: data['sensorExposureTime'] as int?,
sensorSensitivity: data['sensorSensitivity'] as double?,
planes: <CameraImagePlane>[
CameraImagePlane(
bytes: data['data'] as Uint8List,
bytesPerRow: (data['width'] as int) * 4,
)
]);
}
5 changes: 5 additions & 0 deletions packages/camera/camera_windows/pigeons/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ abstract class CameraApi {
@async
String stopVideoRecording(int cameraId);

/// Starts the image stream for the given camera.
/// Returns the name of the [EventChannel] used to deliver the images.
/// Cancelling the subscription to the channel stops the capture.
String startImageStream(int cameraId);

/// Starts the preview stream for the given camera.
@async
void pausePreview(int cameraId);
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_windows
description: A Flutter plugin for getting information about and controlling the camera on Windows.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_windows
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.2.6+1
version: 0.2.7

environment:
sdk: ^3.3.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,24 +166,15 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi {
) as _i4.Future<String>);

@override
_i4.Future<void> startImageStream(int? cameraId) => (super.noSuchMethod(
_i4.Future<String> startImageStream(int? cameraId) => (super.noSuchMethod(
Invocation.method(
#startImageStream,
[cameraId],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<void> stopImageStream(int? cameraId) => (super.noSuchMethod(
Invocation.method(
#stopImageStream,
[cameraId],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i4.Future<String>.value('imageStream/$cameraId'),
returnValueForMissingStub:
_i4.Future<String>.value('imageStream/$cameraId'),
) as _i4.Future<String>);

@override
_i4.Future<void> pausePreview(int? cameraId) => (super.noSuchMethod(
Expand Down
4 changes: 4 additions & 0 deletions packages/camera/camera_windows/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ list(APPEND PLUGIN_SOURCES
"texture_handler.h"
"texture_handler.cpp"
"com_heap_ptr.h"
"task_runner.h"
"task_runner_window.h"
"task_runner_window.cpp"
)

add_library(${PLUGIN_NAME} SHARED
Expand Down Expand Up @@ -84,6 +87,7 @@ add_executable(${TEST_RUNNER}
test/camera_plugin_test.cpp
test/camera_test.cpp
test/capture_controller_test.cpp
test/task_runner_window_test.cpp
${PLUGIN_SOURCES}
)
apply_standard_settings(${TEST_RUNNER})
Expand Down
10 changes: 6 additions & 4 deletions packages/camera/camera_windows/windows/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,26 @@ CameraImpl::~CameraImpl() {

bool CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
const PlatformMediaSettings& media_settings) {
const PlatformMediaSettings& media_settings,
std::shared_ptr<TaskRunner> task_runner) {
auto capture_controller_factory =
std::make_unique<CaptureControllerFactoryImpl>();
return InitCamera(std::move(capture_controller_factory), texture_registrar,
messenger, media_settings);
messenger, media_settings, task_runner);
}

bool CameraImpl::InitCamera(
std::unique_ptr<CaptureControllerFactory> capture_controller_factory,
flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
const PlatformMediaSettings& media_settings) {
const PlatformMediaSettings& media_settings,
std::shared_ptr<TaskRunner> task_runner) {
assert(!device_id_.empty());
messenger_ = messenger;
capture_controller_ =
capture_controller_factory->CreateCaptureController(this);
return capture_controller_->InitCaptureDevice(texture_registrar, device_id_,
media_settings);
media_settings, task_runner);
}

bool CameraImpl::AddPendingVoidResult(
Expand Down
10 changes: 7 additions & 3 deletions packages/camera/camera_windows/windows/camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class Camera : public CaptureControllerListener {
// Returns false if initialization fails.
virtual bool InitCamera(flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
const PlatformMediaSettings& media_settings) = 0;
const PlatformMediaSettings& media_settings,
std::shared_ptr<TaskRunner> task_runner) = 0;
};

// Concrete implementation of the |Camera| interface.
Expand Down Expand Up @@ -151,7 +152,8 @@ class CameraImpl : public Camera {
}
bool InitCamera(flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
const PlatformMediaSettings& media_settings) override;
const PlatformMediaSettings& media_settings,
std::shared_ptr<TaskRunner> task_runner) override;

// Initializes the camera and its associated capture controller.
//
Expand All @@ -163,7 +165,8 @@ class CameraImpl : public Camera {
std::unique_ptr<CaptureControllerFactory> capture_controller_factory,
flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
const PlatformMediaSettings& media_settings);
const PlatformMediaSettings& media_settings,
std::shared_ptr<TaskRunner> task_runner);

private:
// A generic type for any pending asyncronous result.
Expand Down Expand Up @@ -230,6 +233,7 @@ class CameraImpl : public Camera {
std::unique_ptr<CaptureController> capture_controller_;
std::unique_ptr<CameraEventApi> event_api_;
flutter::BinaryMessenger* messenger_ = nullptr;
std::shared_ptr<TaskRunner> task_runner_;
int64_t camera_id_ = -1;
std::string device_id_;
};
Expand Down
Loading
Loading