blob: b40be163bf38de8172d565f2a0a78ee986304670 [file] [log] [blame]
/**************************************************************************
* Copyright (C) 2016 by Savoir-faire Linux *
* Author: Jäger Nicolas <nicolas.jager@savoirfairelinux.com> *
* Author: Traczyk Andreas <andreas.traczyk@savoirfairelinux.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
**************************************************************************/
#include "pch.h"
#include "VideoCaptureManager.h"
#include <MemoryBuffer.h> // IMemoryBufferByteAccess
using namespace RingClientUWP;
using namespace Video;
using namespace Windows::Graphics::Display;
using namespace Windows::Graphics::Imaging;
using namespace Windows::UI::Xaml::Media::Imaging;
using namespace Windows::Media;
using namespace Windows::Media::MediaProperties;
using namespace Windows::Media::Capture;
VideoCaptureManager::VideoCaptureManager():
mediaCapture(nullptr)
, isInitialized(false)
, isPreviewing_(false)
, isChangingCamera(false)
, isRendering(false)
, externalCamera(false)
, mirroringPreview(false)
, displayOrientation(DisplayOrientations::Portrait)
, displayRequest(ref new Windows::System::Display::DisplayRequest())
, RotationKey({ 0xC380465D, 0x2271, 0x428C,{ 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1 } })
{
deviceList = ref new Vector<Device^>();
InitializeCopyFrameDispatcher();
captureTaskTokenSource = new cancellation_token_source();
}
Map<String^,String^>^
VideoCaptureManager::getSettings(String^ device)
{
return Utils::convertMap(DRing::getSettings(Utils::toString(device)));
}
void
VideoCaptureManager::MediaCapture_Failed(Capture::MediaCapture^, Capture::MediaCaptureFailedEventArgs^ errorEventArgs)
{
WriteLine("MediaCapture_Failed");
std::wstringstream ss;
ss << "MediaCapture_Failed: 0x" << errorEventArgs->Code << ": " << errorEventArgs->Message->Data();
WriteLine(ref new String(ss.str().c_str()));
if (captureTaskTokenSource)
captureTaskTokenSource->cancel();
CleanupCameraAsync();
}
task<void>
VideoCaptureManager::CleanupCameraAsync()
{
WriteLine("CleanupCameraAsync");
std::vector<task<void>> taskList;
if (isInitialized)
{
if (isPreviewing)
{
auto stopPreviewTask = create_task(StopPreviewAsync());
taskList.push_back(stopPreviewTask);
}
isInitialized = false;
}
return when_all(taskList.begin(), taskList.end())
.then([this]()
{
if (mediaCapture.Get() != nullptr)
{
mediaCapture->Failed -= mediaCaptureFailedEventToken;
mediaCapture = nullptr;
}
});
}
task<void>
VideoCaptureManager::EnumerateWebcamsAsync()
{
devInfoCollection = nullptr;
deviceList->Clear();
return create_task(DeviceInformation::FindAllAsync(DeviceClass::VideoCapture))
.then([this](task<DeviceInformationCollection^> findTask)
{
try {
devInfoCollection = findTask.get();
if (devInfoCollection == nullptr || devInfoCollection->Size == 0) {
WriteLine("No WebCams found.");
}
else {
for (unsigned int i = 0; i < devInfoCollection->Size; i++) {
AddVideoDevice(i);
}
WriteLine("Enumerating Webcams completed successfully.");
}
}
catch (Platform::Exception^ e) {
WriteException(e);
}
});
}
task<void>
VideoCaptureManager::StartPreviewAsync()
{
WriteLine("StartPreviewAsync");
displayRequest->RequestActive();
auto sink = getSink();
sink->Source = mediaCapture.Get();
return create_task(mediaCapture->StartPreviewAsync())
.then([this](task<void> previewTask)
{
try {
previewTask.get();
isPreviewing = true;
startPreviewing();
WriteLine("StartPreviewAsync DONE");
}
catch (Exception ^e) {
WriteException(e);
}
});
}
task<void>
VideoCaptureManager::StopPreviewAsync()
{
WriteLine("StopPreviewAsync");
if (captureTaskTokenSource)
captureTaskTokenSource->cancel();
if (mediaCapture.Get()) {
return create_task(mediaCapture->StopPreviewAsync())
.then([this](task<void> stopTask)
{
try {
stopTask.get();
isPreviewing = false;
stopPreviewing();
displayRequest->RequestRelease();
WriteLine("StopPreviewAsync DONE");
}
catch (Exception ^e) {
WriteException(e);
}
});
}
else {
return create_task([](){});
}
}
task<void>
VideoCaptureManager::InitializeCameraAsync()
{
WriteLine("InitializeCameraAsync");
if (captureTaskTokenSource)
captureTaskTokenSource->cancel();
mediaCapture = ref new MediaCapture();
auto devInfo = devInfoCollection->GetAt(0); //preferences - video capture device
mediaCaptureFailedEventToken = mediaCapture->Failed +=
ref new Capture::MediaCaptureFailedEventHandler(this, &VideoCaptureManager::MediaCapture_Failed);
if (devInfo == nullptr)
return create_task([](){});
auto settings = ref new MediaCaptureInitializationSettings();
settings->VideoDeviceId = devInfo->Id;
return create_task(mediaCapture->InitializeAsync(settings))
.then([this](task<void> initTask)
{
try {
initTask.get();
SetCaptureSettings();
isInitialized = true;
WriteLine("InitializeCameraAsync DONE");
return StartPreviewAsync();
}
catch (Exception ^e) {
WriteException(e);
return create_task([](){});
}
});
}
void
VideoCaptureManager::AddVideoDevice(uint8_t index)
{
WriteLine("GetDeviceCaps " + index.ToString());
Platform::Agile<Windows::Media::Capture::MediaCapture^> mc;
mc = ref new MediaCapture();
auto devInfo = devInfoCollection->GetAt(index);
if (devInfo == nullptr)
return;
auto settings = ref new MediaCaptureInitializationSettings();
settings->VideoDeviceId = devInfo->Id;
create_task(mc->InitializeAsync(settings))
.then([=](task<void> initTask)
{
try {
initTask.get();
auto allprops = mc->VideoDeviceController->GetAvailableMediaStreamProperties(MediaStreamType::VideoPreview);
Video::Device^ device = ref new Device(devInfo->Id);
Video::Channel^ channel = ref new Channel();
for (auto props : allprops) {
MediaProperties::VideoEncodingProperties^ vidprops = static_cast<VideoEncodingProperties^>(props);
int width = vidprops->Width;
int height = vidprops->Height;
Video::Resolution^ resolution = ref new Resolution(ref new Size(width,height));
Video::Rate^ rate = ref new Rate();
unsigned int frame_rate = 0;
if (vidprops->FrameRate->Denominator != 0)
frame_rate = vidprops->FrameRate->Numerator / vidprops->FrameRate->Denominator;
rate->setValue(frame_rate);
rate->setName(rate->value().ToString() + "fps");
resolution->setActiveRate(rate);
String^ format = vidprops->Subtype;
resolution->setFormat(format);
channel->resolutionList()->Append(resolution);
WriteLine(devInfo->Name + " "
+ width.ToString() + "x" + height.ToString()
+ " " + frame_rate.ToString() + "FPS" + " " + format);
}
device->channelList()->Append(channel);
device->setCurrentChannel(device->channelList()->GetAt(0));
auto location = devInfo->EnclosureLocation;
if (location != nullptr) {
if (location->Panel == Windows::Devices::Enumeration::Panel::Front) {
device->setName(devInfo->Name + "-Front");
}
else if (location->Panel == Windows::Devices::Enumeration::Panel::Back) {
device->setName(devInfo->Name + "-Back"); //ignore
}
else {
device->setName(devInfo->Name);
}
}
else {
device->setName(devInfo->Name);
}
this->deviceList->Append(device);
this->activeDevice = deviceList->GetAt(0);
WriteLine("GetDeviceCaps DONE");
DRing::addVideoDevice(Utils::toString(device->name()));
}
catch (Platform::Exception^ e) {
WriteException(e);
}
});
}
void
VideoCaptureManager::InitializeCopyFrameDispatcher()
{
try {
TimeSpan timeSpan;
timeSpan.Duration = static_cast<long long>(1e7) / 30; // framerate
if (videoFrameCopyInvoker != nullptr)
delete(videoFrameCopyInvoker);
videoFrameCopyInvoker = ref new DispatcherTimer;
videoFrameCopyInvoker->Interval = timeSpan;
videoFrameCopyInvoker->Tick += ref new Windows::Foundation::EventHandler<Object^>(this, &VideoCaptureManager::CopyFrame);
isRendering = false;
}
catch (Exception^ e) {
WriteLine(e->ToString());
}
}
void
VideoCaptureManager::CopyFrame(Object^ sender, Object^ e)
{
if (!isRendering && isPreviewing) {
try {
create_task(VideoCaptureManager::CopyFrameAsync());
}
catch(Platform::COMException^ e) {
WriteLine(e->ToString());
}
}
}
task<void>
VideoCaptureManager::CopyFrameAsync()
{
auto previewProperties = static_cast<MediaProperties::VideoEncodingProperties^>(
mediaCapture->VideoDeviceController->GetMediaStreamProperties(Capture::MediaStreamType::VideoPreview));
unsigned int videoFrameWidth = previewProperties->Width;
unsigned int videoFrameHeight = previewProperties->Height;
auto videoFrame = ref new VideoFrame(BitmapPixelFormat::Bgra8, videoFrameWidth, videoFrameHeight);
try {
if (captureTaskTokenSource) {
delete captureTaskTokenSource;
captureTaskTokenSource = new cancellation_token_source();
}
return create_task(mediaCapture->GetPreviewFrameAsync(videoFrame), captureTaskTokenSource->get_token())
.then([this](VideoFrame^ currentFrame)
{
try {
isRendering = true;
auto bitmap = currentFrame->SoftwareBitmap;
if (bitmap->BitmapPixelFormat == BitmapPixelFormat::Bgra8) {
const int BYTES_PER_PIXEL = 4;
BitmapBuffer^ buffer = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite);
IMemoryBufferReference^ reference = buffer->CreateReference();
Microsoft::WRL::ComPtr<IMemoryBufferByteAccess> byteAccess;
if (SUCCEEDED(reinterpret_cast<IUnknown*>(reference)->QueryInterface(
IID_PPV_ARGS(&byteAccess)))) {
byte* data;
unsigned capacity;
byteAccess->GetBuffer(&data, &capacity);
auto desc = buffer->GetPlaneDescription(0);
byte* buf = (byte*)DRing::obtainFrame(capacity);
if (buf) {
for (int row = 0; row < desc.Height; row++) {
for (int col = 0; col < desc.Width; col++) {
auto currPixel = desc.StartIndex + desc.Stride * row
+ BYTES_PER_PIXEL * col;
buf[currPixel + 0] = data[currPixel + 0];
buf[currPixel + 1] = data[currPixel + 1];
buf[currPixel + 2] = data[currPixel + 2];
}
}
}
DRing::releaseFrame((void*)buf);
}
delete reference;
delete buffer;
}
delete currentFrame;
}
catch (Exception^ e) {
WriteLine("failed to copy frame to bitmap");
}
}).then([=](task<void> previousTask) {
try {
previousTask.get();
isRendering = false;
}
catch (Platform::Exception^ e) {
WriteLine( "Caught exception from previous task.\n" );
}
});
}
catch(Exception^ e) {
WriteException(e);
throw std::exception();
}
}
void
VideoCaptureManager::SetCaptureSettings()
{
auto vp = ref new VideoEncodingProperties;
auto res = activeDevice->channel()->currentResolution();
vp->Width = res->size()->width();
vp->Height = res->size()->height();
vp->FrameRate->Numerator = res->activeRate()->value();
vp->FrameRate->Denominator = 1;
vp->Subtype = res->format();
auto encodingProperties = static_cast<IMediaEncodingProperties^>(vp);
create_task(mediaCapture->VideoDeviceController->SetMediaStreamPropertiesAsync(
MediaStreamType::VideoPreview, encodingProperties));
}