Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,8 @@ class CameraController extends ValueNotifier<CameraValue> {
// 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
60 changes: 60 additions & 0 deletions packages/camera/camera_windows/lib/camera_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:math';

import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_windows/type_conversion.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:stream_transform/stream_transform.dart';
Expand All @@ -25,6 +26,12 @@ class CameraWindows extends CameraPlatform {
/// Camera specific method channels to allow communicating with specific cameras.
final Map<int, MethodChannel> _cameraChannels = <int, MethodChannel>{};

// The stream to receive frames from the native code.
StreamSubscription<dynamic>? _platformImageStreamSubscription;

// The stream for vending frames to platform interface clients.
StreamController<CameraImageData>? _frameStreamController;

/// The controller that broadcasts events coming from handleCameraMethodCall
///
/// It is a `broadcast` because multiple controllers will connect to
Expand Down Expand Up @@ -277,6 +284,59 @@ class CameraWindows extends CameraPlatform {
'resumeVideoRecording() is not supported due to Win32 API limitations.');
}

@override
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
{CameraImageStreamOptions? options}) {
_installStreamController(
onListen: () => _onFrameStreamListen(cameraId),
onCancel: () => _onFrameStreamCancel(cameraId));
return _frameStreamController!.stream;
}

StreamController<CameraImageData> _installStreamController(
{void Function()? onListen, void Function()? onCancel}) {
_frameStreamController = StreamController<CameraImageData>(
onListen: onListen ?? () {},
onPause: _onFrameStreamPauseResume,
onResume: _onFrameStreamPauseResume,
onCancel: onCancel ?? () {},
);
return _frameStreamController!;
}

void _onFrameStreamListen(int cameraId) {
_startPlatformStream(cameraId);
}

Future<void> _startPlatformStream(int cameraId) async {
_startStreamListener();
await pluginChannel.invokeMethod<void>(
'startImageStream', <String, dynamic>{'cameraId': cameraId});
}

void _startStreamListener() {
const EventChannel cameraEventChannel =
EventChannel('plugins.flutter.io/camera_android/imageStream');
_platformImageStreamSubscription =
cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) {
_frameStreamController!
.add(cameraImageFromPlatformData(imageData as Map<dynamic, dynamic>));
});
}

FutureOr<void> _onFrameStreamCancel(int cameraId) async {
await pluginChannel.invokeMethod<void>(
'stopImageStream', <String, dynamic>{'cameraId': cameraId});
await _platformImageStreamSubscription?.cancel();
_platformImageStreamSubscription = null;
_frameStreamController = null;
}

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
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(
bytes: data['data'] as Uint8List,
bytesPerRow: ((data['width'] as int) * 4),
)
]);
}
2 changes: 2 additions & 0 deletions packages/camera/camera_windows/windows/camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ enum class PendingResultType {
kTakePicture,
kStartRecord,
kStopRecord,
kStartStream,
kStopStream,
kPausePreview,
kResumePreview,
};
Expand Down
97 changes: 96 additions & 1 deletion packages/camera/camera_windows/windows/camera_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

#include <flutter/flutter_view.h>
#include <flutter/method_channel.h>
#include <flutter/event_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <flutter/event_stream_handler_functions.h>
#include <mfapi.h>
#include <mfidl.h>
#include <shlobj.h>
Expand All @@ -18,6 +20,8 @@
#include <chrono>
#include <memory>

#include <iostream>

#include "capture_device_info.h"
#include "com_heap_ptr.h"
#include "string_utils.h"
Expand All @@ -31,13 +35,17 @@ namespace {

// Channel events
constexpr char kChannelName[] = "plugins.flutter.io/camera_windows";
constexpr char kFrameEventChannelName[] = "plugins.flutter.io/camera_android/imageStream";

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 kStartImageStreamingMethod[] = "startImageStream";
constexpr char kStopImageStreamingMethod[] = "stopImageStream";

constexpr char kPausePreview[] = "pausePreview";
constexpr char kResumePreview[] = "resumePreview";
constexpr char kDisposeMethod[] = "dispose";
Expand All @@ -62,6 +70,8 @@ constexpr char kResolutionPresetValueMax[] = "max";
const std::string kPictureCaptureExtension = "jpeg";
const std::string kVideoCaptureExtension = "mp4";

std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> event_sink;

// 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) {
Expand Down Expand Up @@ -198,6 +208,9 @@ void CameraPlugin::RegisterWithRegistrar(
auto channel = std::make_unique<flutter::MethodChannel<>>(
registrar->messenger(), kChannelName,
&flutter::StandardMethodCodec::GetInstance());
auto frameEventchannel = std::make_unique<flutter::EventChannel<>>(
registrar->messenger(), kFrameEventChannelName,
&flutter::StandardMethodCodec::GetInstance());

std::unique_ptr<CameraPlugin> plugin = std::make_unique<CameraPlugin>(
registrar->texture_registrar(), registrar->messenger());
Expand All @@ -206,6 +219,16 @@ void CameraPlugin::RegisterWithRegistrar(
[plugin_pointer = plugin.get()](const auto& call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});
auto event_channel_handler = std::make_unique<flutter::StreamHandlerFunctions<>>(
[](auto arguments, auto events) {
event_sink = std::move(events);
return nullptr;
},
[](auto arguments) {
event_sink.reset();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was going to do a quick fix to the issues I raised in my previous comments, but then I noticed the code related to event_sink. This is a global, which is already very dangerous, but it's also std::moved between classes while still being referenced persistently in both. This code would likely fail the second time an image stream was started, and would absolutely fail with multiple engines.

Given the scope of issues here, I'm going to revert this. @cbracken FYI.

return nullptr;
});
frameEventchannel->SetStreamHandler(std::move(event_channel_handler));

registrar->AddPlugin(std::move(plugin));
}
Expand Down Expand Up @@ -262,7 +285,19 @@ void CameraPlugin::HandleMethodCall(
assert(arguments);

return StopVideoRecordingMethodHandler(*arguments, std::move(result));
} else if (method_name.compare(kPausePreview) == 0) {
} else if (method_name.compare(kStartImageStreamingMethod) == 0) {
const auto* arguments =
std::get_if<flutter::EncodableMap>(method_call.arguments());
assert(arguments);

return StartImageStreamingMethodHandler(*arguments, std::move(result));
}else if (method_name.compare(kStopImageStreamingMethod) == 0) {
const auto* arguments =
std::get_if<flutter::EncodableMap>(method_call.arguments());
assert(arguments);

return StopImageStreamingMethodHandler(*arguments, std::move(result));
}else if (method_name.compare(kPausePreview) == 0) {
const auto* arguments =
std::get_if<flutter::EncodableMap>(method_call.arguments());
assert(arguments);
Expand Down Expand Up @@ -569,6 +604,66 @@ void CameraPlugin::StopVideoRecordingMethodHandler(
cc->StopRecord();
}
}
void CameraPlugin::StartImageStreamingMethodHandler(
const EncodableMap& args, std::unique_ptr<flutter::MethodResult<>> result) {
auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); //get camera id
if (!camera_id) {
return result->Error("argument_error",
std::string(kCameraIdKey) + " missing");
}
//check if request already exists
auto camera = GetCameraByCameraId(*camera_id);
Copy link
Member

Choose a reason for hiding this comment

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

Similar comment here (and at the equivalent spot in the stop method); please make the type explicit.

if (!camera) {
return result->Error("camera_error", "Camera not created");
}
if (camera->HasPendingResultByType(PendingResultType::kStartStream)) {
return result->Error("camera_error",
"Pending start stream request exists");
}

if (!event_sink) {
return result->Error("camera_error",
"Unable to make event channel from windows");
}

if (camera->AddPendingResult(PendingResultType::kStartStream,
std::move(result))) {
auto cc = camera->GetCaptureController();
assert(cc);
cc->StartImageStream(std::move(event_sink));
}


}
void CameraPlugin::StopImageStreamingMethodHandler(
const EncodableMap& args, std::unique_ptr<flutter::MethodResult<>> result) {
auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); //get camera id
if (!camera_id) {
return result->Error("argument_error",
std::string(kCameraIdKey) + " missing");
}
//check if request already exists
auto camera = GetCameraByCameraId(*camera_id);
if (!camera) {
return result->Error("camera_error", "Camera not created");
}
if (camera->HasPendingResultByType(PendingResultType::kStartStream)) {
return result->Error("camera_error",
"Pending start stream request exists");
}

if (!event_sink) {
return result->Error("camera_error",
"Unable to make event channel from windows");
}

if (camera->AddPendingResult(PendingResultType::kStartStream,
std::move(result))) {
auto cc = camera->GetCaptureController();
assert(cc);
cc->StopImageStream();
}
}

void CameraPlugin::TakePictureMethodHandler(
const EncodableMap& args, std::unique_ptr<flutter::MethodResult<>> result) {
Expand Down
9 changes: 8 additions & 1 deletion packages/camera/camera_windows/windows/camera_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

#include <flutter/flutter_view.h>
#include <flutter/method_channel.h>
#include <flutter/event_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>


#include <functional>

#include "camera.h"
Expand Down Expand Up @@ -101,7 +103,12 @@ class CameraPlugin : public flutter::Plugin,
// Stores result object to be handled after request is processed.
void StopVideoRecordingMethodHandler(const EncodableMap& args,
std::unique_ptr<MethodResult<>> result);

// Handles StartImageStreaming method calls.
void StartImageStreamingMethodHandler(const EncodableMap& args,
std::unique_ptr<MethodResult<>> result);
// Handles StopImageStreaming method calls.
void StopImageStreamingMethodHandler(const EncodableMap& args,
std::unique_ptr<MethodResult<>> result);
// Handles pausePreview method calls.
// Requests existing camera controller to pause recording.
// Stores result object to be handled after request is processed.
Expand Down
41 changes: 39 additions & 2 deletions packages/camera/camera_windows/windows/capture_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@
#include <cassert>
#include <chrono>

#include <iostream>

#include "com_heap_ptr.h"
#include "photo_handler.h"
#include "preview_handler.h"
#include "record_handler.h"
#include "string_utils.h"
#include "texture_handler.h"

#include <flutter/event_stream_handler_functions.h>
#include <flutter/standard_method_codec.h>


namespace camera_windows {

using Microsoft::WRL::ComPtr;
Expand Down Expand Up @@ -561,6 +567,15 @@ void CaptureControllerImpl::StopRecord() {
"Failed to stop video recording");
}
}
void CaptureControllerImpl::StartImageStream(std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> sink) {
assert(capture_controller_listener_);
image_stream_sink_ = std::move(sink);
}

void CaptureControllerImpl::StopImageStream() {
assert(capture_controller_listener_);
image_stream_sink_.reset();
}

// Stops timed recording. Called internally when requested time is passed.
// Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process.
Expand Down Expand Up @@ -878,8 +893,30 @@ bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer,
if (!texture_handler_) {
return false;
}
return texture_handler_->UpdateBuffer(buffer, data_length);
}
if (image_stream_sink_) {
// Convert the buffer data to a std::vector<uint8_t>
std::vector<uint8_t> buffer_data(buffer, buffer + data_length);

// Ensure preview_frame_height_ and preview_frame_width_ are of supported types
int preview_frame_height = static_cast<int>(preview_frame_height_);
int preview_frame_width = static_cast<int>(preview_frame_width_);

// Create a map to hold the buffer data and data length
flutter::EncodableMap data_map;
data_map[flutter::EncodableValue("data")] = flutter::EncodableValue(buffer_data);
data_map[flutter::EncodableValue("height")] = flutter::EncodableValue(preview_frame_height);
data_map[flutter::EncodableValue("width")] = flutter::EncodableValue(preview_frame_width);
data_map[flutter::EncodableValue("length")] = flutter::EncodableValue(static_cast<int>(data_length));

// Wrap the map in a flutter::EncodableValue
flutter::EncodableValue encoded_value(data_map);

// Send the encoded value through the image_stream_sink_
image_stream_sink_->Success(encoded_value);
}
return texture_handler_->UpdateBuffer(buffer, data_length);

}

// Handles capture time update from each processed frame.
// Stops timed recordings if requested recording duration has passed.
Expand Down
Loading