Skip to content

Commit b31a279

Browse files
authored
[camera_windows] Support image streams on Windows platform (#7067)
Adds support to the windows camera implementation to allow streaming of frames. Fixes flutter/flutter#97542
1 parent 3d358d9 commit b31a279

15 files changed

+609
-2
lines changed

packages/camera/camera_windows/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.2.5
2+
3+
* Adds support for streaming frames.
4+
15
## 0.2.4+1
26

37
* Updates to pigeon 21.

packages/camera/camera_windows/lib/camera_windows.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
1111
import 'package:stream_transform/stream_transform.dart';
1212

1313
import 'src/messages.g.dart';
14+
import 'type_conversion.dart';
1415

1516
/// An implementation of [CameraPlatform] for Windows.
1617
class CameraWindows extends CameraPlatform {
@@ -29,6 +30,12 @@ class CameraWindows extends CameraPlatform {
2930
/// Camera specific method channels to allow communicating with specific cameras.
3031
final Map<int, MethodChannel> _cameraChannels = <int, MethodChannel>{};
3132

33+
// The stream to receive frames from the native code.
34+
StreamSubscription<dynamic>? _platformImageStreamSubscription;
35+
36+
// The stream for vending frames to platform interface clients.
37+
StreamController<CameraImageData>? _frameStreamController;
38+
3239
/// The controller that broadcasts events coming from handleCameraMethodCall
3340
///
3441
/// It is a `broadcast` because multiple controllers will connect to
@@ -242,6 +249,57 @@ class CameraWindows extends CameraPlatform {
242249
'resumeVideoRecording() is not supported due to Win32 API limitations.');
243250
}
244251

252+
@override
253+
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
254+
{CameraImageStreamOptions? options}) {
255+
_installStreamController(
256+
onListen: () => _onFrameStreamListen(cameraId),
257+
onCancel: () => _onFrameStreamCancel(cameraId));
258+
return _frameStreamController!.stream;
259+
}
260+
261+
StreamController<CameraImageData> _installStreamController(
262+
{void Function()? onListen, void Function()? onCancel}) {
263+
_frameStreamController = StreamController<CameraImageData>(
264+
onListen: onListen ?? () {},
265+
onPause: _onFrameStreamPauseResume,
266+
onResume: _onFrameStreamPauseResume,
267+
onCancel: onCancel ?? () {},
268+
);
269+
return _frameStreamController!;
270+
}
271+
272+
void _onFrameStreamListen(int cameraId) {
273+
_startPlatformStream(cameraId);
274+
}
275+
276+
Future<void> _startPlatformStream(int cameraId) async {
277+
_startStreamListener();
278+
await _hostApi.startImageStream(cameraId);
279+
}
280+
281+
void _startStreamListener() {
282+
const EventChannel cameraEventChannel =
283+
EventChannel('plugins.flutter.io/camera_android/imageStream');
284+
_platformImageStreamSubscription =
285+
cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) {
286+
_frameStreamController!
287+
.add(cameraImageFromPlatformData(imageData as Map<dynamic, dynamic>));
288+
});
289+
}
290+
291+
FutureOr<void> _onFrameStreamCancel(int cameraId) async {
292+
await _hostApi.stopImageStream(cameraId);
293+
await _platformImageStreamSubscription?.cancel();
294+
_platformImageStreamSubscription = null;
295+
_frameStreamController = null;
296+
}
297+
298+
void _onFrameStreamPauseResume() {
299+
throw CameraException('InvalidCall',
300+
'Pause and resume are not supported for onStreamedFrameAvailable');
301+
}
302+
245303
@override
246304
Future<void> setFlashMode(int cameraId, FlashMode mode) async {
247305
// TODO(jokerttu): Implement flash mode support, https://github.com/flutter/flutter/issues/97537.

packages/camera/camera_windows/lib/src/messages.g.dart

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,56 @@ class CameraApi {
348348
}
349349
}
350350

351+
/// Starts the image stream for the given camera.
352+
Future<void> startImageStream(int cameraId) async {
353+
final String __pigeon_channelName =
354+
'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$__pigeon_messageChannelSuffix';
355+
final BasicMessageChannel<Object?> __pigeon_channel =
356+
BasicMessageChannel<Object?>(
357+
__pigeon_channelName,
358+
pigeonChannelCodec,
359+
binaryMessenger: __pigeon_binaryMessenger,
360+
);
361+
final List<Object?>? __pigeon_replyList =
362+
await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
363+
if (__pigeon_replyList == null) {
364+
throw _createConnectionError(__pigeon_channelName);
365+
} else if (__pigeon_replyList.length > 1) {
366+
throw PlatformException(
367+
code: __pigeon_replyList[0]! as String,
368+
message: __pigeon_replyList[1] as String?,
369+
details: __pigeon_replyList[2],
370+
);
371+
} else {
372+
return;
373+
}
374+
}
375+
376+
/// Stops the image stream for the given camera.
377+
Future<void> stopImageStream(int cameraId) async {
378+
final String __pigeon_channelName =
379+
'dev.flutter.pigeon.camera_windows.CameraApi.stopImageStream$__pigeon_messageChannelSuffix';
380+
final BasicMessageChannel<Object?> __pigeon_channel =
381+
BasicMessageChannel<Object?>(
382+
__pigeon_channelName,
383+
pigeonChannelCodec,
384+
binaryMessenger: __pigeon_binaryMessenger,
385+
);
386+
final List<Object?>? __pigeon_replyList =
387+
await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
388+
if (__pigeon_replyList == null) {
389+
throw _createConnectionError(__pigeon_channelName);
390+
} else if (__pigeon_replyList.length > 1) {
391+
throw PlatformException(
392+
code: __pigeon_replyList[0]! as String,
393+
message: __pigeon_replyList[1] as String?,
394+
details: __pigeon_replyList[2],
395+
);
396+
} else {
397+
return;
398+
}
399+
}
400+
351401
/// Starts the preview stream for the given camera.
352402
Future<void> pausePreview(int cameraId) async {
353403
final String __pigeon_channelName =
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:typed_data';
6+
7+
import 'package:camera_platform_interface/camera_platform_interface.dart';
8+
9+
/// Converts method channel call [data] for `receivedImageStreamData` to a
10+
/// [CameraImageData].
11+
CameraImageData cameraImageFromPlatformData(Map<dynamic, dynamic> data) {
12+
return CameraImageData(
13+
format: const CameraImageFormat(ImageFormatGroup.bgra8888, raw: 0),
14+
height: data['height'] as int,
15+
width: data['width'] as int,
16+
lensAperture: data['lensAperture'] as double?,
17+
sensorExposureTime: data['sensorExposureTime'] as int?,
18+
sensorSensitivity: data['sensorSensitivity'] as double?,
19+
planes: <CameraImagePlane>[
20+
CameraImagePlane(
21+
bytes: data['data'] as Uint8List,
22+
bytesPerRow: (data['width'] as int) * 4,
23+
)
24+
]);
25+
}

packages/camera/camera_windows/pigeons/messages.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ abstract class CameraApi {
7070
@async
7171
String stopVideoRecording(int cameraId);
7272

73+
/// Starts the image stream for the given camera.
74+
@async
75+
void startImageStream(int cameraId);
76+
77+
/// Stops the image stream for the given camera.
78+
@async
79+
void stopImageStream(int cameraId);
80+
7381
/// Starts the preview stream for the given camera.
7482
@async
7583
void pausePreview(int cameraId);

packages/camera/camera_windows/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_windows
22
description: A Flutter plugin for getting information about and controlling the camera on Windows.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_windows
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.2.4+1
5+
version: 0.2.5
66

77
environment:
88
sdk: ^3.2.0

packages/camera/camera_windows/windows/camera.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ enum class PendingResultType {
2828
kTakePicture,
2929
kStartRecord,
3030
kStopRecord,
31+
kStartStream,
32+
kStopStream,
3133
kPausePreview,
3234
kResumePreview,
3335
};

packages/camera/camera_windows/windows/camera_plugin.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "camera_plugin.h"
66

7+
#include <flutter/event_channel.h>
8+
#include <flutter/event_stream_handler_functions.h>
79
#include <flutter/flutter_view.h>
810
#include <flutter/method_channel.h>
911
#include <flutter/plugin_registrar_windows.h>
@@ -32,6 +34,10 @@ namespace {
3234

3335
const std::string kPictureCaptureExtension = "jpeg";
3436
const std::string kVideoCaptureExtension = "mp4";
37+
constexpr char kFrameEventChannelName[] =
38+
"plugins.flutter.io/camera_android/imageStream";
39+
40+
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> event_sink;
3541

3642
// Builds CaptureDeviceInfo object from given device holding device name and id.
3743
std::unique_ptr<CaptureDeviceInfo> GetDeviceInfo(IMFActivate* device) {
@@ -116,12 +122,34 @@ std::optional<std::string> GetFilePathForVideo() {
116122
}
117123
} // namespace
118124

125+
// a setter for the event sink helpful for testing.
126+
void CameraPlugin::SetEventSink(
127+
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> events) {
128+
event_sink = std::move(events);
129+
}
130+
119131
// static
120132
void CameraPlugin::RegisterWithRegistrar(
121133
flutter::PluginRegistrarWindows* registrar) {
122134
std::unique_ptr<CameraPlugin> plugin = std::make_unique<CameraPlugin>(
123135
registrar->texture_registrar(), registrar->messenger());
124136

137+
auto frameEventchannel = std::make_unique<flutter::EventChannel<>>(
138+
registrar->messenger(), kFrameEventChannelName,
139+
&flutter::StandardMethodCodec::GetInstance());
140+
141+
auto event_channel_handler =
142+
std::make_unique<flutter::StreamHandlerFunctions<>>(
143+
[plugin = plugin.get()](auto arguments, auto events) {
144+
plugin->SetEventSink(std::move(events));
145+
return nullptr;
146+
},
147+
[](auto arguments) {
148+
event_sink.reset();
149+
return nullptr;
150+
});
151+
frameEventchannel->SetStreamHandler(std::move(event_channel_handler));
152+
125153
CameraApi::SetUp(registrar->messenger(), plugin.get());
126154

127155
registrar->AddPlugin(std::move(plugin));
@@ -341,6 +369,53 @@ void CameraPlugin::StopVideoRecording(
341369
}
342370
}
343371

372+
void CameraPlugin::StartImageStream(
373+
int64_t camera_id,
374+
std::function<void(std::optional<FlutterError> reply)> result) {
375+
// check if request already exists
376+
Camera* camera = GetCameraByCameraId(camera_id);
377+
if (!camera) {
378+
return result(FlutterError("camera_error", "Camera not created"));
379+
}
380+
if (camera->HasPendingResultByType(PendingResultType::kStartStream)) {
381+
return result(
382+
FlutterError("camera_error", "Pending start stream request exists"));
383+
}
384+
385+
if (!event_sink) {
386+
return result(FlutterError("camera_error",
387+
"Unable to make event channel from windows"));
388+
}
389+
390+
if (camera->AddPendingVoidResult(PendingResultType::kStartStream,
391+
std::move(result))) {
392+
CaptureController* cc = camera->GetCaptureController();
393+
assert(cc);
394+
cc->StartImageStream(std::move(event_sink));
395+
}
396+
}
397+
398+
void CameraPlugin::StopImageStream(
399+
int64_t camera_id,
400+
std::function<void(std::optional<FlutterError> reply)> result) {
401+
// check if request already exists
402+
Camera* camera = GetCameraByCameraId(camera_id);
403+
if (!camera) {
404+
return result(FlutterError("camera_error", "Camera not created"));
405+
}
406+
if (camera->HasPendingResultByType(PendingResultType::kStopStream)) {
407+
return result(
408+
FlutterError("camera_error", "Pending stop stream request exists"));
409+
}
410+
411+
if (camera->AddPendingVoidResult(PendingResultType::kStopStream,
412+
std::move(result))) {
413+
CaptureController* cc = camera->GetCaptureController();
414+
assert(cc);
415+
cc->StopImageStream();
416+
}
417+
}
418+
344419
void CameraPlugin::TakePicture(
345420
int64_t camera_id, std::function<void(ErrorOr<std::string> reply)> result) {
346421
auto camera = GetCameraByCameraId(camera_id);

packages/camera/camera_windows/windows/camera_plugin.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class CameraPlugin : public flutter::Plugin,
3131
public CameraApi,
3232
public VideoCaptureDeviceEnumerator {
3333
public:
34+
void SetEventSink(
35+
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> events);
3436
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar);
3537

3638
CameraPlugin(flutter::TextureRegistrar* texture_registrar,
@@ -68,6 +70,12 @@ class CameraPlugin : public flutter::Plugin,
6870
void StopVideoRecording(
6971
int64_t camera_id,
7072
std::function<void(ErrorOr<std::string> reply)> result) override;
73+
void StartImageStream(
74+
int64_t camera_id,
75+
std::function<void(std::optional<FlutterError> reply)> result) override;
76+
void StopImageStream(
77+
int64_t camera_id,
78+
std::function<void(std::optional<FlutterError> reply)> result) override;
7179
void TakePicture(
7280
int64_t camera_id,
7381
std::function<void(ErrorOr<std::string> reply)> result) override;

packages/camera/camera_windows/windows/capture_controller.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
#include "capture_controller.h"
66

77
#include <comdef.h>
8+
#include <flutter/event_stream_handler_functions.h>
9+
#include <flutter/standard_method_codec.h>
810
#include <wincodec.h>
911
#include <wrl/client.h>
1012

1113
#include <cassert>
1214
#include <chrono>
15+
#include <iostream>
1316

1417
#include "com_heap_ptr.h"
1518
#include "photo_handler.h"
@@ -550,6 +553,16 @@ void CaptureControllerImpl::StopRecord() {
550553
"Failed to stop video recording");
551554
}
552555
}
556+
void CaptureControllerImpl::StartImageStream(
557+
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> sink) {
558+
assert(capture_controller_listener_);
559+
image_stream_sink_ = std::move(sink);
560+
}
561+
562+
void CaptureControllerImpl::StopImageStream() {
563+
assert(capture_controller_listener_);
564+
image_stream_sink_.reset();
565+
}
553566

554567
// Starts capturing preview frames using preview handler
555568
// After first frame is captured, OnPreviewStarted is called
@@ -843,6 +856,32 @@ bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer,
843856
if (!texture_handler_) {
844857
return false;
845858
}
859+
if (image_stream_sink_) {
860+
// Convert the buffer data to a std::vector<uint8_t>.
861+
std::vector<uint8_t> buffer_data(buffer, buffer + data_length);
862+
863+
// Ensure preview_frame_height_ and preview_frame_width_ are of supported
864+
// types.
865+
int preview_frame_height = static_cast<int>(preview_frame_height_);
866+
int preview_frame_width = static_cast<int>(preview_frame_width_);
867+
868+
// Create a map to hold the buffer data and data length.
869+
flutter::EncodableMap data_map;
870+
data_map[flutter::EncodableValue("data")] =
871+
flutter::EncodableValue(buffer_data);
872+
data_map[flutter::EncodableValue("height")] =
873+
flutter::EncodableValue(preview_frame_height);
874+
data_map[flutter::EncodableValue("width")] =
875+
flutter::EncodableValue(preview_frame_width);
876+
data_map[flutter::EncodableValue("length")] =
877+
flutter::EncodableValue(static_cast<int>(data_length));
878+
879+
// Wrap the map in a flutter::EncodableValue.
880+
flutter::EncodableValue encoded_value(data_map);
881+
882+
// Send the encoded value through the image_stream_sink_.
883+
image_stream_sink_->Success(encoded_value);
884+
}
846885
return texture_handler_->UpdateBuffer(buffer, data_length);
847886
}
848887

0 commit comments

Comments
 (0)