Skip to content

Commit 6a38937

Browse files
committed
Revert "[camera_windows] Revert: Support image streams on Windows platform (flutter#7951)"
This reverts commit b3a5e33.
1 parent 46aeb2b commit 6a38937

File tree

13 files changed

+604
-1
lines changed

13 files changed

+604
-1
lines changed

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 {
@@ -32,6 +33,12 @@ class CameraWindows extends CameraPlatform {
3233
final Map<int, HostCameraMessageHandler> hostCameraHandlers =
3334
<int, HostCameraMessageHandler>{};
3435

36+
// The stream to receive frames from the native code.
37+
StreamSubscription<dynamic>? _platformImageStreamSubscription;
38+
39+
// The stream for vending frames to platform interface clients.
40+
StreamController<CameraImageData>? _frameStreamController;
41+
3542
/// The controller that broadcasts events coming from handleCameraMethodCall
3643
///
3744
/// It is a `broadcast` because multiple controllers will connect to
@@ -234,6 +241,57 @@ class CameraWindows extends CameraPlatform {
234241
'resumeVideoRecording() is not supported due to Win32 API limitations.');
235242
}
236243

244+
@override
245+
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
246+
{CameraImageStreamOptions? options}) {
247+
_installStreamController(
248+
onListen: () => _onFrameStreamListen(cameraId),
249+
onCancel: () => _onFrameStreamCancel(cameraId));
250+
return _frameStreamController!.stream;
251+
}
252+
253+
StreamController<CameraImageData> _installStreamController(
254+
{void Function()? onListen, void Function()? onCancel}) {
255+
_frameStreamController = StreamController<CameraImageData>(
256+
onListen: onListen ?? () {},
257+
onPause: _onFrameStreamPauseResume,
258+
onResume: _onFrameStreamPauseResume,
259+
onCancel: onCancel ?? () {},
260+
);
261+
return _frameStreamController!;
262+
}
263+
264+
void _onFrameStreamListen(int cameraId) {
265+
_startPlatformStream(cameraId);
266+
}
267+
268+
Future<void> _startPlatformStream(int cameraId) async {
269+
_startStreamListener();
270+
await _hostApi.startImageStream(cameraId);
271+
}
272+
273+
void _startStreamListener() {
274+
const EventChannel cameraEventChannel =
275+
EventChannel('plugins.flutter.io/camera_android/imageStream');
276+
_platformImageStreamSubscription =
277+
cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) {
278+
_frameStreamController!
279+
.add(cameraImageFromPlatformData(imageData as Map<dynamic, dynamic>));
280+
});
281+
}
282+
283+
FutureOr<void> _onFrameStreamCancel(int cameraId) async {
284+
await _hostApi.stopImageStream(cameraId);
285+
await _platformImageStreamSubscription?.cancel();
286+
_platformImageStreamSubscription = null;
287+
_frameStreamController = null;
288+
}
289+
290+
void _onFrameStreamPauseResume() {
291+
throw CameraException('InvalidCall',
292+
'Pause and resume are not supported for onStreamedFrameAvailable');
293+
}
294+
237295
@override
238296
Future<void> setFlashMode(int cameraId, FlashMode mode) async {
239297
// 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
@@ -362,6 +362,56 @@ class CameraApi {
362362
}
363363
}
364364

365+
/// Starts the image stream for the given camera.
366+
Future<void> startImageStream(int cameraId) async {
367+
final String pigeonVar_channelName =
368+
'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$pigeonVar_messageChannelSuffix';
369+
final BasicMessageChannel<Object?> pigeonVar_channel =
370+
BasicMessageChannel<Object?>(
371+
pigeonVar_channelName,
372+
pigeonChannelCodec,
373+
binaryMessenger: pigeonVar_binaryMessenger,
374+
);
375+
final List<Object?>? pigeonVar_replyList =
376+
await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
377+
if (pigeonVar_replyList == null) {
378+
throw _createConnectionError(pigeonVar_channelName);
379+
} else if (pigeonVar_replyList.length > 1) {
380+
throw PlatformException(
381+
code: pigeonVar_replyList[0]! as String,
382+
message: pigeonVar_replyList[1] as String?,
383+
details: pigeonVar_replyList[2],
384+
);
385+
} else {
386+
return;
387+
}
388+
}
389+
390+
/// Stops the image stream for the given camera.
391+
Future<void> stopImageStream(int cameraId) async {
392+
final String pigeonVar_channelName =
393+
'dev.flutter.pigeon.camera_windows.CameraApi.stopImageStream$pigeonVar_messageChannelSuffix';
394+
final BasicMessageChannel<Object?> pigeonVar_channel =
395+
BasicMessageChannel<Object?>(
396+
pigeonVar_channelName,
397+
pigeonChannelCodec,
398+
binaryMessenger: pigeonVar_binaryMessenger,
399+
);
400+
final List<Object?>? pigeonVar_replyList =
401+
await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
402+
if (pigeonVar_replyList == null) {
403+
throw _createConnectionError(pigeonVar_channelName);
404+
} else if (pigeonVar_replyList.length > 1) {
405+
throw PlatformException(
406+
code: pigeonVar_replyList[0]! as String,
407+
message: pigeonVar_replyList[1] as String?,
408+
details: pigeonVar_replyList[2],
409+
);
410+
} else {
411+
return;
412+
}
413+
}
414+
365415
/// Starts the preview stream for the given camera.
366416
Future<void> pausePreview(int cameraId) async {
367417
final String pigeonVar_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/windows/camera.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ enum class PendingResultType {
2222
kTakePicture,
2323
kStartRecord,
2424
kStopRecord,
25+
kStartStream,
26+
kStopStream,
2527
kPausePreview,
2628
kResumePreview,
2729
};

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"
@@ -555,6 +558,16 @@ void CaptureControllerImpl::StopRecord() {
555558
"Failed to stop video recording");
556559
}
557560
}
561+
void CaptureControllerImpl::StartImageStream(
562+
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> sink) {
563+
assert(capture_controller_listener_);
564+
image_stream_sink_ = std::move(sink);
565+
}
566+
567+
void CaptureControllerImpl::StopImageStream() {
568+
assert(capture_controller_listener_);
569+
image_stream_sink_.reset();
570+
}
558571

559572
// Starts capturing preview frames using preview handler
560573
// After first frame is captured, OnPreviewStarted is called
@@ -863,6 +876,32 @@ bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer,
863876
if (!texture_handler_) {
864877
return false;
865878
}
879+
if (image_stream_sink_) {
880+
// Convert the buffer data to a std::vector<uint8_t>.
881+
std::vector<uint8_t> buffer_data(buffer, buffer + data_length);
882+
883+
// Ensure preview_frame_height_ and preview_frame_width_ are of supported
884+
// types.
885+
int preview_frame_height = static_cast<int>(preview_frame_height_);
886+
int preview_frame_width = static_cast<int>(preview_frame_width_);
887+
888+
// Create a map to hold the buffer data and data length.
889+
flutter::EncodableMap data_map;
890+
data_map[flutter::EncodableValue("data")] =
891+
flutter::EncodableValue(buffer_data);
892+
data_map[flutter::EncodableValue("height")] =
893+
flutter::EncodableValue(preview_frame_height);
894+
data_map[flutter::EncodableValue("width")] =
895+
flutter::EncodableValue(preview_frame_width);
896+
data_map[flutter::EncodableValue("length")] =
897+
flutter::EncodableValue(static_cast<int>(data_length));
898+
899+
// Wrap the map in a flutter::EncodableValue.
900+
flutter::EncodableValue encoded_value(data_map);
901+
902+
// Send the encoded value through the image_stream_sink_.
903+
image_stream_sink_->Success(encoded_value);
904+
}
866905
return texture_handler_->UpdateBuffer(buffer, data_length);
867906
}
868907

0 commit comments

Comments
 (0)