diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md index 9a001ea738d..4c41ec180dc 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.2.7 +* Fixes initializing video preview with latest webcam driver [#140014](https://github.com/flutter/flutter/issues/140014) * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 0.2.6+2 diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index d064fa093a0..655f1798436 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -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+2 +version: 0.2.7 environment: sdk: ^3.7.0 diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp index 1afa5c0a8c4..1a6d373c692 100644 --- a/packages/camera/camera_windows/windows/capture_controller.cpp +++ b/packages/camera/camera_windows/windows/capture_controller.cpp @@ -371,7 +371,8 @@ void CaptureControllerImpl::TakePicture(const std::string& file_path) { // Check MF_CAPTURE_ENGINE_PHOTO_TAKEN event handling // for response process. hr = photo_handler_->TakePhoto(file_path, capture_engine_.Get(), - base_capture_media_type_.Get()); + base_capture_media_type_.Get(), + photo_source_stream_index_); if (FAILED(hr)) { // Destroy photo handler on error cases to make sure state is resetted. photo_handler_ = nullptr; @@ -398,6 +399,42 @@ uint32_t CaptureControllerImpl::GetMaxPreviewHeight() const { } } +enum class PlatformStreamCategory { video, photo, audio }; + +HRESULT GetMediaSourceStreamIndex( + IMFCaptureSource* source, DWORD* source_stream_index, + PlatformStreamCategory target_stream_category) { + DWORD stream_count = 0; + HRESULT hr = source->GetDeviceStreamCount(&stream_count); + if (FAILED(hr)) { + return hr; + } + + for (DWORD stream_index = 0; stream_index < stream_count; stream_index++) { + MF_CAPTURE_ENGINE_STREAM_CATEGORY stream_category; + hr = source->GetDeviceStreamCategory(stream_index, &stream_category); + if (FAILED(hr)) { + return hr; + } + + if ((target_stream_category == PlatformStreamCategory::video && + (stream_category == MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_PREVIEW || + stream_category == + MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_CAPTURE)) || + (target_stream_category == PlatformStreamCategory::photo && + (stream_category == + MF_CAPTURE_ENGINE_STREAM_CATEGORY_PHOTO_DEPENDENT || + stream_category == + MF_CAPTURE_ENGINE_STREAM_CATEGORY_PHOTO_INDEPENDENT)) || + (target_stream_category == PlatformStreamCategory::audio && + stream_category == MF_CAPTURE_ENGINE_STREAM_CATEGORY_AUDIO)) { + *source_stream_index = stream_index; + return S_OK; + } + } + return E_FAIL; +} + // Finds best media 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, @@ -472,23 +509,48 @@ HRESULT CaptureControllerImpl::FindBaseMediaTypes() { HRESULT CaptureControllerImpl::FindBaseMediaTypesForSource( IMFCaptureSource* source) { + HRESULT hr; + hr = GetMediaSourceStreamIndex(source, &video_source_stream_index_, + PlatformStreamCategory::video); + if (FAILED(hr)) { + return E_FAIL; + } + + hr = GetMediaSourceStreamIndex(source, &photo_source_stream_index_, + PlatformStreamCategory::photo); + if (FAILED(hr)) { + // Use the same source stream for photo as video on fail + photo_source_stream_index_ = video_source_stream_index_; + } + + if (media_settings_.enable_audio()) { + hr = GetMediaSourceStreamIndex(source, &audio_source_stream_index_, + PlatformStreamCategory::audio); + if (FAILED(hr)) { + return E_FAIL; + } + } + // Find base media type for previewing. - if (!FindBestMediaType( - (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, - source, base_preview_media_type_.GetAddressOf(), - GetMaxPreviewHeight(), &preview_frame_width_, - &preview_frame_height_)) { + if (!FindBestMediaType(video_source_stream_index_, source, + base_preview_media_type_.GetAddressOf(), + GetMaxPreviewHeight(), &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, base_capture_media_type_.GetAddressOf(), 0xffffffff, nullptr, - nullptr)) { + hr = source->SetCurrentDeviceMediaType(video_source_stream_index_, + base_preview_media_type_.Get()); + if (FAILED(hr)) { return E_FAIL; } + // Find base media type for record and photo capture. + if (!FindBestMediaType(video_source_stream_index_, source, + base_capture_media_type_.GetAddressOf(), 0xffffffff, + nullptr, nullptr)) { + return E_FAIL; + } return S_OK; } @@ -523,8 +585,9 @@ void CaptureControllerImpl::StartRecord(const std::string& file_path) { // Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response // process. - hr = record_handler_->StartRecord(file_path, capture_engine_.Get(), - base_capture_media_type_.Get()); + hr = record_handler_->StartRecord( + file_path, capture_engine_.Get(), base_capture_media_type_.Get(), + video_source_stream_index_, audio_source_stream_index_); if (FAILED(hr)) { // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; @@ -610,9 +673,9 @@ void CaptureControllerImpl::StartPreview() { // Check MF_CAPTURE_ENGINE_PREVIEW_STARTED event handling for response // process. - hr = preview_handler_->StartPreview(capture_engine_.Get(), - base_preview_media_type_.Get(), - capture_engine_callback_handler_.Get()); + hr = preview_handler_->StartPreview( + capture_engine_.Get(), base_preview_media_type_.Get(), + video_source_stream_index_, capture_engine_callback_handler_.Get()); if (FAILED(hr)) { // Destroy preview handler on error cases to make sure state is resetted. diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h index 7b804bb1ebc..9d53ac9cabb 100644 --- a/packages/camera/camera_windows/windows/capture_controller.h +++ b/packages/camera/camera_windows/windows/capture_controller.h @@ -214,6 +214,9 @@ class CaptureControllerImpl : public CaptureController, uint32_t preview_frame_width_ = 0; uint32_t preview_frame_height_ = 0; + DWORD video_source_stream_index_ = (DWORD)MF_CAPTURE_ENGINE_MEDIASOURCE; + DWORD photo_source_stream_index_ = (DWORD)MF_CAPTURE_ENGINE_MEDIASOURCE; + DWORD audio_source_stream_index_ = (DWORD)MF_CAPTURE_ENGINE_MEDIASOURCE; UINT dx_device_reset_token_ = 0; std::unique_ptr record_handler_; std::unique_ptr preview_handler_; diff --git a/packages/camera/camera_windows/windows/photo_handler.cpp b/packages/camera/camera_windows/windows/photo_handler.cpp index 7544b5d25ab..57419469138 100644 --- a/packages/camera/camera_windows/windows/photo_handler.cpp +++ b/packages/camera/camera_windows/windows/photo_handler.cpp @@ -50,7 +50,8 @@ HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType* src_media_type, } HRESULT PhotoHandler::InitPhotoSink(IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type) { + IMFMediaType* base_media_type, + DWORD source_stream_index) { assert(capture_engine); assert(base_media_type); @@ -99,9 +100,8 @@ HRESULT PhotoHandler::InitPhotoSink(IMFCaptureEngine* capture_engine, } 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); + hr = photo_sink_->AddStream(source_stream_index, photo_media_type.Get(), + nullptr, &photo_sink_stream_index); if (FAILED(hr)) { photo_sink_ = nullptr; return hr; @@ -118,14 +118,16 @@ HRESULT PhotoHandler::InitPhotoSink(IMFCaptureEngine* capture_engine, HRESULT PhotoHandler::TakePhoto(const std::string& file_path, IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type) { + IMFMediaType* base_media_type, + DWORD source_stream_index) { assert(!file_path.empty()); assert(capture_engine); assert(base_media_type); file_path_ = file_path; - HRESULT hr = InitPhotoSink(capture_engine, base_media_type); + HRESULT hr = + InitPhotoSink(capture_engine, base_media_type, source_stream_index); if (FAILED(hr)) { return hr; } diff --git a/packages/camera/camera_windows/windows/photo_handler.h b/packages/camera/camera_windows/windows/photo_handler.h index 60dd5d8ddf8..4d87d270311 100644 --- a/packages/camera/camera_windows/windows/photo_handler.h +++ b/packages/camera/camera_windows/windows/photo_handler.h @@ -47,9 +47,10 @@ class PhotoHandler { // 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. + // source_stream_index: Integer index of the source stream in MediaFoundation. HRESULT TakePhoto(const std::string& file_path, IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type); + IMFMediaType* base_media_type, DWORD source_stream_index); // Set the photo handler recording state to: kIdle. void OnPhotoTaken(); @@ -68,7 +69,8 @@ class PhotoHandler { private: // Initializes record sink for video file capture. HRESULT InitPhotoSink(IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type); + IMFMediaType* base_media_type, + DWORD source_stream_index); std::string file_path_; PhotoState photo_state_ = PhotoState::kNotStarted; diff --git a/packages/camera/camera_windows/windows/preview_handler.cpp b/packages/camera/camera_windows/windows/preview_handler.cpp index 771f1a44e1c..6561c377cab 100644 --- a/packages/camera/camera_windows/windows/preview_handler.cpp +++ b/packages/camera/camera_windows/windows/preview_handler.cpp @@ -51,7 +51,7 @@ HRESULT BuildMediaTypeForVideoPreview(IMFMediaType* src_media_type, HRESULT PreviewHandler::InitPreviewSink( IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type, - CaptureEngineListener* sample_callback) { + DWORD source_stream_index, CaptureEngineListener* sample_callback) { assert(capture_engine); assert(base_media_type); assert(sample_callback); @@ -94,9 +94,8 @@ HRESULT PreviewHandler::InitPreviewSink( } 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); + hr = preview_sink_->AddStream(source_stream_index, preview_media_type.Get(), + nullptr, &preview_sink_stream_index); if (FAILED(hr)) { return hr; @@ -115,12 +114,13 @@ HRESULT PreviewHandler::InitPreviewSink( HRESULT PreviewHandler::StartPreview(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type, + DWORD source_stream_index, CaptureEngineListener* sample_callback) { assert(capture_engine); assert(base_media_type); - HRESULT hr = - InitPreviewSink(capture_engine, base_media_type, sample_callback); + HRESULT hr = InitPreviewSink(capture_engine, base_media_type, + source_stream_index, sample_callback); if (FAILED(hr)) { return hr; diff --git a/packages/camera/camera_windows/windows/preview_handler.h b/packages/camera/camera_windows/windows/preview_handler.h index b3e5557f3a9..ec55fc05975 100644 --- a/packages/camera/camera_windows/windows/preview_handler.h +++ b/packages/camera/camera_windows/windows/preview_handler.h @@ -52,8 +52,10 @@ class PreviewHandler { // for the actual video capture media type. // sample_callback: A pointer to capture engine listener. // This is set as sample callback for preview sink. + // source_stream_index: Integer index of the preview source stream in + // MediaFoundation. HRESULT StartPreview(IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type, + IMFMediaType* base_media_type, DWORD source_stream_index, CaptureEngineListener* sample_callback); // Stops existing recording. @@ -90,6 +92,7 @@ class PreviewHandler { // Initializes record sink for video file capture. HRESULT InitPreviewSink(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type, + DWORD source_stream_index, CaptureEngineListener* sample_callback); PreviewState preview_state_ = PreviewState::kNotStarted; diff --git a/packages/camera/camera_windows/windows/record_handler.cpp b/packages/camera/camera_windows/windows/record_handler.cpp index 3de5d698ca2..4e3c965975e 100644 --- a/packages/camera/camera_windows/windows/record_handler.cpp +++ b/packages/camera/camera_windows/windows/record_handler.cpp @@ -131,7 +131,9 @@ inline HRESULT SetAudioBitrate(IMFMediaType* pType, UINT32 bitrate) { } HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type) { + IMFMediaType* base_media_type, + DWORD video_source_stream_index, + DWORD audio_source_stream_index) { assert(!file_path_.empty()); assert(capture_engine); assert(base_media_type); @@ -189,9 +191,9 @@ HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine* capture_engine, } 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); + hr = record_sink_->AddStream(video_source_stream_index, + video_record_media_type.Get(), nullptr, + &video_record_sink_stream_index); if (FAILED(hr)) { return hr; } @@ -210,10 +212,9 @@ HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine* capture_engine, } 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); + hr = record_sink_->AddStream(audio_source_stream_index, + audio_record_media_type.Get(), nullptr, + &audio_record_sink_stream_index); } if (FAILED(hr)) { @@ -228,7 +229,9 @@ HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine* capture_engine, HRESULT RecordHandler::StartRecord(const std::string& file_path, IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type) { + IMFMediaType* base_media_type, + DWORD video_source_stream_index, + DWORD audio_source_stream_index) { assert(!file_path.empty()); assert(capture_engine); assert(base_media_type); @@ -237,7 +240,9 @@ HRESULT RecordHandler::StartRecord(const std::string& file_path, recording_start_timestamp_us_ = -1; recording_duration_us_ = 0; - HRESULT hr = InitRecordSink(capture_engine, base_media_type); + HRESULT hr = + InitRecordSink(capture_engine, base_media_type, video_source_stream_index, + audio_source_stream_index); if (FAILED(hr)) { return hr; } diff --git a/packages/camera/camera_windows/windows/record_handler.h b/packages/camera/camera_windows/windows/record_handler.h index 79b30855833..7823bb653fd 100644 --- a/packages/camera/camera_windows/windows/record_handler.h +++ b/packages/camera/camera_windows/windows/record_handler.h @@ -48,9 +48,14 @@ class RecordHandler { // the actual recording. // base_media_type: A pointer to base media type used as a base // for the actual video capture media type. + // video_source_stream_index: Integer index of the video source stream in + // MediaFoundation. audio_source_stream_index: Integer index of the audio + // source stream in MediaFoundation. HRESULT StartRecord(const std::string& file_path, IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type); + IMFMediaType* base_media_type, + DWORD video_source_stream_index, + DWORD audio_source_stream_index); // Stops existing recording. // @@ -83,7 +88,9 @@ class RecordHandler { private: // Initializes record sink for video file capture. HRESULT InitRecordSink(IMFCaptureEngine* capture_engine, - IMFMediaType* base_media_type); + IMFMediaType* base_media_type, + DWORD video_source_stream_index, + DWORD audio_source_stream_index); const PlatformMediaSettings media_settings_; int64_t recording_start_timestamp_us_ = -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 442f405910b..5aba1482415 100644 --- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp +++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp @@ -85,12 +85,34 @@ void MockAvailableMediaTypes(MockCaptureEngine* engine, return S_OK; }); - EXPECT_CALL( - *capture_source, - GetAvailableDeviceMediaType( - Eq((DWORD) - MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW), - _, _)) + EXPECT_CALL(*capture_source, GetDeviceStreamCount) + .WillRepeatedly([](DWORD* stream_count) { + *stream_count = 3; + return S_OK; + }); + + EXPECT_CALL(*capture_source, GetDeviceStreamCategory(Eq(0), _)) + .WillRepeatedly([](DWORD source_stream_index, + MF_CAPTURE_ENGINE_STREAM_CATEGORY* stream_category) { + *stream_category = MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_PREVIEW; + return S_OK; + }); + + EXPECT_CALL(*capture_source, GetDeviceStreamCategory(Eq(1), _)) + .WillRepeatedly([](DWORD source_stream_index, + MF_CAPTURE_ENGINE_STREAM_CATEGORY* stream_category) { + *stream_category = MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_CAPTURE; + return S_OK; + }); + + EXPECT_CALL(*capture_source, GetDeviceStreamCategory(Eq(2), _)) + .WillRepeatedly([](DWORD source_stream_index, + MF_CAPTURE_ENGINE_STREAM_CATEGORY* stream_category) { + *stream_category = MF_CAPTURE_ENGINE_STREAM_CATEGORY_AUDIO; + return S_OK; + }); + + EXPECT_CALL(*capture_source, GetAvailableDeviceMediaType(Eq(0), _, _)) .WillRepeatedly([mock_preview_width, mock_preview_height]( DWORD stream_index, DWORD media_type_index, IMFMediaType** media_type) { @@ -103,11 +125,7 @@ void MockAvailableMediaTypes(MockCaptureEngine* engine, return S_OK; }); - EXPECT_CALL( - *capture_source, - GetAvailableDeviceMediaType( - Eq((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD), - _, _)) + EXPECT_CALL(*capture_source, GetAvailableDeviceMediaType(Eq(1), _, _)) .WillRepeatedly([mock_preview_width, mock_preview_height]( DWORD stream_index, DWORD media_type_index, IMFMediaType** media_type) { @@ -162,6 +180,10 @@ void MockStartPreview(CaptureControllerImpl* capture_controller, .Times(1) .WillOnce(Return(S_OK)); + EXPECT_CALL(*capture_source.Get(), SetCurrentDeviceMediaType(Eq((DWORD)0), _)) + .Times(1) + .WillOnce(Return(S_OK)); + EXPECT_CALL(*engine, StartPreview()).Times(1).WillOnce(Return(S_OK)); // Called by destructor @@ -781,16 +803,12 @@ TEST(CaptureController, StartRecordWithSettingsSuccess) { EXPECT_CALL( *record_sink.Get(), - AddStream( - Eq((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD), - WithFpsAndBitrate(kFps, kVideoBitrate), _, _)) + AddStream(Eq((DWORD)0), WithFpsAndBitrate(kFps, kVideoBitrate), _, _)) .Times(1) .WillRepeatedly(Return(S_OK)); - EXPECT_CALL( - *record_sink.Get(), - AddStream(Eq((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_AUDIO), - WithAudioBitrate(kAudioBitrate), _, _)) + EXPECT_CALL(*record_sink.Get(), + AddStream(Eq((DWORD)2), WithAudioBitrate(kAudioBitrate), _, _)) .Times(1) .WillRepeatedly(Return(S_OK));