blob: 384c86ac109bf27fc9216e53d008322d10df94a2 [file] [log] [blame]
// 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.
#include "capture_controller.h"
#include <comdef.h>
#include <wincodec.h>
#include <wrl/client.h>
#include <cassert>
#include <chrono>
#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"
namespace camera_windows {
using Microsoft::WRL::ComPtr;
CameraResult GetCameraResult(HRESULT hr) {
if (SUCCEEDED(hr)) {
return CameraResult::kSuccess;
}
return hr == E_ACCESSDENIED ? CameraResult::kAccessDenied
: CameraResult::kError;
}
CaptureControllerImpl::CaptureControllerImpl(
CaptureControllerListener* listener)
: capture_controller_listener_(listener), CaptureController(){};
CaptureControllerImpl::~CaptureControllerImpl() {
ResetCaptureController();
capture_controller_listener_ = nullptr;
};
// static
bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources(
IMFActivate*** devices, UINT32* count) {
ComPtr<IMFAttributes> attributes;
HRESULT hr = MFCreateAttributes(&attributes, 1);
if (FAILED(hr)) {
return false;
}
hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr)) {
return false;
}
hr = MFEnumDeviceSources(attributes.Get(), devices, count);
if (FAILED(hr)) {
return false;
}
return true;
}
HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() {
audio_source_ = nullptr;
ComHeapPtr<IMFActivate*> devices;
UINT32 count = 0;
ComPtr<IMFAttributes> attributes;
HRESULT hr = MFCreateAttributes(&attributes, 1);
if (SUCCEEDED(hr)) {
hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID);
}
if (SUCCEEDED(hr)) {
hr = MFEnumDeviceSources(attributes.Get(), &devices, &count);
}
if (SUCCEEDED(hr) && count > 0) {
ComHeapPtr<wchar_t> audio_device_id;
UINT32 audio_device_id_size;
// Use first audio device.
hr = devices[0]->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID, &audio_device_id,
&audio_device_id_size);
if (SUCCEEDED(hr)) {
ComPtr<IMFAttributes> audio_capture_source_attributes;
hr = MFCreateAttributes(&audio_capture_source_attributes, 2);
if (SUCCEEDED(hr)) {
hr = audio_capture_source_attributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID);
}
if (SUCCEEDED(hr)) {
hr = audio_capture_source_attributes->SetString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID,
audio_device_id);
}
if (SUCCEEDED(hr)) {
hr = MFCreateDeviceSource(audio_capture_source_attributes.Get(),
audio_source_.GetAddressOf());
}
}
}
return hr;
}
HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice(
const std::string& video_device_id) {
video_source_ = nullptr;
ComPtr<IMFAttributes> video_capture_source_attributes;
HRESULT hr = MFCreateAttributes(&video_capture_source_attributes, 2);
if (FAILED(hr)) {
return hr;
}
hr = video_capture_source_attributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr)) {
return hr;
}
hr = video_capture_source_attributes->SetString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
Utf16FromUtf8(video_device_id).c_str());
if (FAILED(hr)) {
return hr;
}
hr = MFCreateDeviceSource(video_capture_source_attributes.Get(),
video_source_.GetAddressOf());
return hr;
}
HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() {
// TODO: Use existing ANGLE device
HRESULT hr = S_OK;
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT, nullptr, 0,
D3D11_SDK_VERSION, &dx11_device_, nullptr, nullptr);
if (FAILED(hr)) {
return hr;
}
// Enable multithread protection
ComPtr<ID3D10Multithread> multi_thread;
hr = dx11_device_.As(&multi_thread);
if (FAILED(hr)) {
return hr;
}
multi_thread->SetMultithreadProtected(TRUE);
hr = MFCreateDXGIDeviceManager(&dx_device_reset_token_,
dxgi_device_manager_.GetAddressOf());
if (FAILED(hr)) {
return hr;
}
hr = dxgi_device_manager_->ResetDevice(dx11_device_.Get(),
dx_device_reset_token_);
return hr;
}
HRESULT CaptureControllerImpl::CreateCaptureEngine() {
assert(!video_device_id_.empty());
HRESULT hr = S_OK;
ComPtr<IMFAttributes> attributes;
// Creates capture engine only if not already initialized by test framework
if (!capture_engine_) {
ComPtr<IMFCaptureEngineClassFactory> capture_engine_factory;
hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&capture_engine_factory));
if (FAILED(hr)) {
return hr;
}
// Creates CaptureEngine.
hr = capture_engine_factory->CreateInstance(CLSID_MFCaptureEngine,
IID_PPV_ARGS(&capture_engine_));
if (FAILED(hr)) {
return hr;
}
}
hr = CreateD3DManagerWithDX11Device();
if (FAILED(hr)) {
return hr;
}
// Creates video source only if not already initialized by test framework
if (!video_source_) {
hr = CreateVideoCaptureSourceForDevice(video_device_id_);
if (FAILED(hr)) {
return hr;
}
}
// Creates audio source only if not already initialized by test framework
if (record_audio_ && !audio_source_) {
hr = CreateDefaultAudioCaptureSource();
if (FAILED(hr)) {
return hr;
}
}
if (!capture_engine_callback_handler_) {
capture_engine_callback_handler_ =
ComPtr<CaptureEngineListener>(new CaptureEngineListener(this));
}
hr = MFCreateAttributes(&attributes, 2);
if (FAILED(hr)) {
return hr;
}
hr = attributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER,
dxgi_device_manager_.Get());
if (FAILED(hr)) {
return hr;
}
hr = attributes->SetUINT32(MF_CAPTURE_ENGINE_USE_VIDEO_DEVICE_ONLY,
!record_audio_);
if (FAILED(hr)) {
return hr;
}
// Check MF_CAPTURE_ENGINE_INITIALIZED event handling
// for response process.
hr = capture_engine_->Initialize(capture_engine_callback_handler_.Get(),
attributes.Get(), audio_source_.Get(),
video_source_.Get());
return hr;
}
void CaptureControllerImpl::ResetCaptureController() {
if (record_handler_ && record_handler_->CanStop()) {
if (record_handler_->IsContinuousRecording()) {
StopRecord();
} else if (record_handler_->IsTimedRecording()) {
StopTimedRecord();
}
}
if (preview_handler_) {
StopPreview();
}
// Shuts down the media foundation platform object.
// Releases all resources including threads.
// Application should call MFShutdown the same number of times as MFStartup
if (media_foundation_started_) {
MFShutdown();
}
// States
media_foundation_started_ = false;
capture_engine_state_ = CaptureEngineState::kNotInitialized;
preview_frame_width_ = 0;
preview_frame_height_ = 0;
capture_engine_callback_handler_ = nullptr;
capture_engine_ = nullptr;
audio_source_ = nullptr;
video_source_ = nullptr;
base_preview_media_type_ = nullptr;
base_capture_media_type_ = nullptr;
if (dxgi_device_manager_) {
dxgi_device_manager_->ResetDevice(dx11_device_.Get(),
dx_device_reset_token_);
}
dxgi_device_manager_ = nullptr;
dx11_device_ = nullptr;
record_handler_ = nullptr;
preview_handler_ = nullptr;
photo_handler_ = nullptr;
texture_handler_ = nullptr;
}
bool CaptureControllerImpl::InitCaptureDevice(
flutter::TextureRegistrar* texture_registrar, const std::string& device_id,
bool record_audio, ResolutionPreset resolution_preset) {
assert(capture_controller_listener_);
if (IsInitialized()) {
capture_controller_listener_->OnCreateCaptureEngineFailed(
CameraResult::kError, "Capture device already initialized");
return false;
} else if (capture_engine_state_ == CaptureEngineState::kInitializing) {
capture_controller_listener_->OnCreateCaptureEngineFailed(
CameraResult::kError, "Capture device already initializing");
return false;
}
capture_engine_state_ = CaptureEngineState::kInitializing;
resolution_preset_ = resolution_preset;
record_audio_ = record_audio;
texture_registrar_ = texture_registrar;
video_device_id_ = device_id;
// MFStartup must be called before using Media Foundation.
if (!media_foundation_started_) {
HRESULT hr = MFStartup(MF_VERSION);
if (FAILED(hr)) {
capture_controller_listener_->OnCreateCaptureEngineFailed(
GetCameraResult(hr), "Failed to create camera");
ResetCaptureController();
return false;
}
media_foundation_started_ = true;
}
HRESULT hr = CreateCaptureEngine();
if (FAILED(hr)) {
capture_controller_listener_->OnCreateCaptureEngineFailed(
GetCameraResult(hr), "Failed to create camera");
ResetCaptureController();
return false;
}
return true;
}
void CaptureControllerImpl::TakePicture(const std::string& file_path) {
assert(capture_engine_callback_handler_);
assert(capture_engine_);
if (!IsInitialized()) {
return OnPicture(CameraResult::kError, "Not initialized");
}
HRESULT hr = S_OK;
if (!base_capture_media_type_) {
// Enumerates mediatypes and finds media type for video capture.
hr = FindBaseMediaTypes();
if (FAILED(hr)) {
return OnPicture(GetCameraResult(hr),
"Failed to initialize photo capture");
}
}
if (!photo_handler_) {
photo_handler_ = std::make_unique<PhotoHandler>();
} else if (photo_handler_->IsTakingPhoto()) {
return OnPicture(CameraResult::kError, "Photo already requested");
}
// 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());
if (FAILED(hr)) {
// Destroy photo handler on error cases to make sure state is resetted.
photo_handler_ = nullptr;
return OnPicture(GetCameraResult(hr), "Failed to take photo");
}
}
uint32_t CaptureControllerImpl::GetMaxPreviewHeight() const {
switch (resolution_preset_) {
case ResolutionPreset::kLow:
return 240;
break;
case ResolutionPreset::kMedium:
return 480;
break;
case ResolutionPreset::kHigh:
return 720;
break;
case ResolutionPreset::kVeryHigh:
return 1080;
break;
case ResolutionPreset::kUltraHigh:
return 2160;
break;
case ResolutionPreset::kMax:
case ResolutionPreset::kAuto:
default:
// no limit.
return 0xffffffff;
break;
}
}
// 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,
uint32_t* target_frame_width,
uint32_t* target_frame_height,
float minimum_accepted_framerate = 15.f) {
assert(source);
ComPtr<IMFMediaType> media_type;
uint32_t best_width = 0;
uint32_t best_height = 0;
float best_framerate = 0.f;
// Loop native media types.
for (int i = 0;; i++) {
if (FAILED(source->GetAvailableDeviceMediaType(
source_stream_index, i, media_type.GetAddressOf()))) {
break;
}
uint32_t frame_rate_numerator, frame_rate_denominator;
if (FAILED(MFGetAttributeRatio(media_type.Get(), MF_MT_FRAME_RATE,
&frame_rate_numerator,
&frame_rate_denominator)) ||
!frame_rate_denominator) {
continue;
}
float frame_rate =
static_cast<float>(frame_rate_numerator) / frame_rate_denominator;
if (frame_rate < minimum_accepted_framerate) {
continue;
}
uint32_t frame_width;
uint32_t frame_height;
if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE,
&frame_width, &frame_height))) {
// Update target mediatype
if (frame_height <= max_height &&
(best_width < frame_width || best_height < frame_height ||
best_framerate < frame_rate)) {
media_type.CopyTo(target_media_type);
best_width = frame_width;
best_height = frame_height;
best_framerate = frame_rate;
}
}
}
if (target_frame_width && target_frame_height) {
*target_frame_width = best_width;
*target_frame_height = best_height;
}
return *target_media_type != nullptr;
}
HRESULT CaptureControllerImpl::FindBaseMediaTypes() {
if (!IsInitialized()) {
return E_FAIL;
}
ComPtr<IMFCaptureSource> source;
HRESULT hr = capture_engine_->GetSource(&source);
if (FAILED(hr)) {
return hr;
}
// Find base media type for previewing.
if (!FindBestMediaType(
(DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW,
source.Get(), 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.Get(), base_capture_media_type_.GetAddressOf(), 0xffffffff,
nullptr, nullptr)) {
return E_FAIL;
}
return S_OK;
}
void CaptureControllerImpl::StartRecord(const std::string& file_path,
int64_t max_video_duration_ms) {
assert(capture_engine_);
if (!IsInitialized()) {
return OnRecordStarted(CameraResult::kError,
"Camera not initialized. Camera should be "
"disposed and reinitialized.");
}
HRESULT hr = S_OK;
if (!base_capture_media_type_) {
// Enumerates mediatypes and finds media type for video capture.
hr = FindBaseMediaTypes();
if (FAILED(hr)) {
return OnRecordStarted(GetCameraResult(hr),
"Failed to initialize video recording");
}
}
if (!record_handler_) {
record_handler_ = std::make_unique<RecordHandler>(record_audio_);
} else if (!record_handler_->CanStart()) {
return OnRecordStarted(
CameraResult::kError,
"Recording cannot be started. Previous recording must be stopped "
"first.");
}
// Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response
// process.
hr = record_handler_->StartRecord(file_path, max_video_duration_ms,
capture_engine_.Get(),
base_capture_media_type_.Get());
if (FAILED(hr)) {
// Destroy record handler on error cases to make sure state is resetted.
record_handler_ = nullptr;
return OnRecordStarted(GetCameraResult(hr),
"Failed to start video recording");
}
}
void CaptureControllerImpl::StopRecord() {
assert(capture_controller_listener_);
if (!IsInitialized()) {
return OnRecordStopped(CameraResult::kError,
"Camera not initialized. Camera should be "
"disposed and reinitialized.");
}
if (!record_handler_ && !record_handler_->CanStop()) {
return OnRecordStopped(CameraResult::kError,
"Recording cannot be stopped.");
}
// Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response
// process.
HRESULT hr = record_handler_->StopRecord(capture_engine_.Get());
if (FAILED(hr)) {
return OnRecordStopped(GetCameraResult(hr),
"Failed to stop video recording");
}
}
// Stops timed recording. Called internally when requested time is passed.
// Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process.
void CaptureControllerImpl::StopTimedRecord() {
assert(capture_controller_listener_);
if (!record_handler_ || !record_handler_->IsTimedRecording()) {
return;
}
HRESULT hr = record_handler_->StopRecord(capture_engine_.Get());
if (FAILED(hr)) {
// Destroy record handler on error cases to make sure state is resetted.
record_handler_ = nullptr;
return capture_controller_listener_->OnVideoRecordFailed(
GetCameraResult(hr), "Failed to record video");
}
}
// Starts capturing preview frames using preview handler
// After first frame is captured, OnPreviewStarted is called
void CaptureControllerImpl::StartPreview() {
assert(capture_engine_callback_handler_);
assert(capture_engine_);
assert(texture_handler_);
if (!IsInitialized() || !texture_handler_) {
return OnPreviewStarted(CameraResult::kError,
"Camera not initialized. Camera should be "
"disposed and reinitialized.");
}
HRESULT hr = S_OK;
if (!base_preview_media_type_) {
// Enumerates mediatypes and finds media type for video capture.
hr = FindBaseMediaTypes();
if (FAILED(hr)) {
return OnPreviewStarted(GetCameraResult(hr),
"Failed to initialize video preview");
}
}
texture_handler_->UpdateTextureSize(preview_frame_width_,
preview_frame_height_);
// TODO(loic-sharma): This does not handle duplicate calls properly.
// See: https://github.com/flutter/flutter/issues/108404
if (!preview_handler_) {
preview_handler_ = std::make_unique<PreviewHandler>();
} else if (preview_handler_->IsInitialized()) {
return OnPreviewStarted(CameraResult::kSuccess, "");
} else {
return OnPreviewStarted(CameraResult::kError, "Preview already exists");
}
// 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());
if (FAILED(hr)) {
// Destroy preview handler on error cases to make sure state is resetted.
preview_handler_ = nullptr;
return OnPreviewStarted(GetCameraResult(hr),
"Failed to start video preview");
}
}
// Stops preview. Called by destructor
// Use PausePreview and ResumePreview methods to for
// pausing and resuming the preview.
// Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event handling for response
// process.
HRESULT CaptureControllerImpl::StopPreview() {
assert(capture_engine_);
if (!IsInitialized() || !preview_handler_) {
return S_OK;
}
// Requests to stop preview.
return preview_handler_->StopPreview(capture_engine_.Get());
}
// Marks preview as paused.
// When preview is paused, captured frames are not processed for preview
// and flutter texture is not updated
void CaptureControllerImpl::PausePreview() {
assert(capture_controller_listener_);
if (!preview_handler_ || !preview_handler_->IsInitialized()) {
return capture_controller_listener_->OnPausePreviewFailed(
CameraResult::kError, "Preview not started");
}
if (preview_handler_->PausePreview()) {
capture_controller_listener_->OnPausePreviewSucceeded();
} else {
capture_controller_listener_->OnPausePreviewFailed(
CameraResult::kError, "Failed to pause preview");
}
}
// Marks preview as not paused.
// When preview is not paused, captured frames are processed for preview
// and flutter texture is updated.
void CaptureControllerImpl::ResumePreview() {
assert(capture_controller_listener_);
if (!preview_handler_ || !preview_handler_->IsInitialized()) {
return capture_controller_listener_->OnResumePreviewFailed(
CameraResult::kError, "Preview not started");
}
if (preview_handler_->ResumePreview()) {
capture_controller_listener_->OnResumePreviewSucceeded();
} else {
capture_controller_listener_->OnResumePreviewFailed(
CameraResult::kError, "Failed to pause preview");
}
}
// Handles capture engine events.
// Called via IMFCaptureEngineOnEventCallback implementation.
// Implements CaptureEngineObserver::OnEvent.
void CaptureControllerImpl::OnEvent(IMFMediaEvent* event) {
if (!IsInitialized() &&
capture_engine_state_ != CaptureEngineState::kInitializing) {
return;
}
GUID extended_type_guid;
if (SUCCEEDED(event->GetExtendedType(&extended_type_guid))) {
std::string error;
HRESULT event_hr;
if (FAILED(event->GetStatus(&event_hr))) {
return;
}
if (FAILED(event_hr)) {
// Reads system error
_com_error err(event_hr);
error = Utf8FromUtf16(err.ErrorMessage());
}
CameraResult event_result = GetCameraResult(event_hr);
if (extended_type_guid == MF_CAPTURE_ENGINE_ERROR) {
OnCaptureEngineError(event_result, error);
} else if (extended_type_guid == MF_CAPTURE_ENGINE_INITIALIZED) {
OnCaptureEngineInitialized(event_result, error);
} else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) {
// Preview is marked as started after first frame is captured.
// This is because, CaptureEngine might inform that preview is started
// even if error is thrown right after.
} else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) {
OnPreviewStopped(event_result, error);
} else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STARTED) {
OnRecordStarted(event_result, error);
} else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STOPPED) {
OnRecordStopped(event_result, error);
} else if (extended_type_guid == MF_CAPTURE_ENGINE_PHOTO_TAKEN) {
OnPicture(event_result, error);
} else if (extended_type_guid == MF_CAPTURE_ENGINE_CAMERA_STREAM_BLOCKED) {
// TODO: Inform capture state to flutter.
} else if (extended_type_guid ==
MF_CAPTURE_ENGINE_CAMERA_STREAM_UNBLOCKED) {
// TODO: Inform capture state to flutter.
}
}
}
// Handles Picture event and informs CaptureControllerListener.
void CaptureControllerImpl::OnPicture(CameraResult result,
const std::string& error) {
if (result == CameraResult::kSuccess && photo_handler_) {
if (capture_controller_listener_) {
std::string path = photo_handler_->GetPhotoPath();
capture_controller_listener_->OnTakePictureSucceeded(path);
}
photo_handler_->OnPhotoTaken();
} else {
if (capture_controller_listener_) {
capture_controller_listener_->OnTakePictureFailed(result, error);
}
// Destroy photo handler on error cases to make sure state is resetted.
photo_handler_ = nullptr;
}
}
// Handles CaptureEngineInitialized event and informs
// CaptureControllerListener.
void CaptureControllerImpl::OnCaptureEngineInitialized(
CameraResult result, const std::string& error) {
if (capture_controller_listener_) {
if (result != CameraResult::kSuccess) {
capture_controller_listener_->OnCreateCaptureEngineFailed(
result, "Failed to initialize capture engine");
ResetCaptureController();
return;
}
// Create texture handler and register new texture.
texture_handler_ = std::make_unique<TextureHandler>(texture_registrar_);
int64_t texture_id = texture_handler_->RegisterTexture();
if (texture_id >= 0) {
capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id);
capture_engine_state_ = CaptureEngineState::kInitialized;
} else {
capture_controller_listener_->OnCreateCaptureEngineFailed(
CameraResult::kError, "Failed to create texture_id");
// Reset state
ResetCaptureController();
}
}
}
// Handles CaptureEngineError event and informs CaptureControllerListener.
void CaptureControllerImpl::OnCaptureEngineError(CameraResult result,
const std::string& error) {
if (capture_controller_listener_) {
capture_controller_listener_->OnCaptureError(result, error);
}
// TODO: If MF_CAPTURE_ENGINE_ERROR is returned,
// should capture controller be reinitialized automatically?
}
// Handles PreviewStarted event and informs CaptureControllerListener.
// This should be called only after first frame has been received or
// in error cases.
void CaptureControllerImpl::OnPreviewStarted(CameraResult result,
const std::string& error) {
if (preview_handler_ && result == CameraResult::kSuccess) {
preview_handler_->OnPreviewStarted();
} else {
// Destroy preview handler on error cases to make sure state is resetted.
preview_handler_ = nullptr;
}
if (capture_controller_listener_) {
if (result == CameraResult::kSuccess && preview_frame_width_ > 0 &&
preview_frame_height_ > 0) {
capture_controller_listener_->OnStartPreviewSucceeded(
preview_frame_width_, preview_frame_height_);
} else {
capture_controller_listener_->OnStartPreviewFailed(result, error);
}
}
};
// Handles PreviewStopped event.
void CaptureControllerImpl::OnPreviewStopped(CameraResult result,
const std::string& error) {
// Preview handler is destroyed if preview is stopped as it
// does not have any use anymore.
preview_handler_ = nullptr;
};
// Handles RecordStarted event and informs CaptureControllerListener.
void CaptureControllerImpl::OnRecordStarted(CameraResult result,
const std::string& error) {
if (result == CameraResult::kSuccess && record_handler_) {
record_handler_->OnRecordStarted();
if (capture_controller_listener_) {
capture_controller_listener_->OnStartRecordSucceeded();
}
} else {
if (capture_controller_listener_) {
capture_controller_listener_->OnStartRecordFailed(result, error);
}
// Destroy record handler on error cases to make sure state is resetted.
record_handler_ = nullptr;
}
};
// Handles RecordStopped event and informs CaptureControllerListener.
void CaptureControllerImpl::OnRecordStopped(CameraResult result,
const std::string& error) {
if (capture_controller_listener_ && record_handler_) {
// Always calls OnStopRecord listener methods
// to handle separate stop record request for timed records.
if (result == CameraResult::kSuccess) {
std::string path = record_handler_->GetRecordPath();
capture_controller_listener_->OnStopRecordSucceeded(path);
if (record_handler_->IsTimedRecording()) {
capture_controller_listener_->OnVideoRecordSucceeded(
path, (record_handler_->GetRecordedDuration() / 1000));
}
} else {
capture_controller_listener_->OnStopRecordFailed(result, error);
if (record_handler_->IsTimedRecording()) {
capture_controller_listener_->OnVideoRecordFailed(result, error);
}
}
}
if (result == CameraResult::kSuccess && record_handler_) {
record_handler_->OnRecordStopped();
} else {
// Destroy record handler on error cases to make sure state is resetted.
record_handler_ = nullptr;
}
}
// Updates texture handlers buffer with given data.
// Called via IMFCaptureEngineOnSampleCallback implementation.
// Implements CaptureEngineObserver::UpdateBuffer.
bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer,
uint32_t data_length) {
if (!texture_handler_) {
return false;
}
return texture_handler_->UpdateBuffer(buffer, data_length);
}
// Handles capture time update from each processed frame.
// Stops timed recordings if requested recording duration has passed.
// Called via IMFCaptureEngineOnSampleCallback implementation.
// Implements CaptureEngineObserver::UpdateCaptureTime.
void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) {
if (!IsInitialized()) {
return;
}
if (preview_handler_ && preview_handler_->IsStarting()) {
// Informs that first frame is captured successfully and preview has
// started.
OnPreviewStarted(CameraResult::kSuccess, "");
}
// Checks if max_video_duration_ms is passed.
if (record_handler_) {
record_handler_->UpdateRecordingTime(capture_time_us);
if (record_handler_->ShouldStopTimedRecording()) {
StopTimedRecord();
}
}
}
} // namespace camera_windows