From 14ba30c4cf605dcaaed57c8dde3315db1e36d7e8 Mon Sep 17 00:00:00 2001 From: atraczyk <andreastraczyk@gmail.com> Date: Thu, 22 Sep 2016 18:31:59 -0400 Subject: [PATCH] video: add video - adds incoming video - adds webcam preview - adds device enumeration - adds daemon video signal handlers - adds outgoing video Tuleap: #1200 Change-Id: Ife5f6acc2ee400665e096e44e2111e03cab0299a --- MainPage.xaml.cpp | 35 ++- MainPage.xaml.h | 6 +- RingD.cpp | 99 +++++++- RingDebug.h | 14 ++ Utils.h | 53 +++- Video.cpp | 282 ++++++++++++++++++++++ Video.h | 156 ++++++++++++ VideoCaptureManager.cpp | 411 ++++++++++++++++++++++++++++++++ VideoCaptureManager.h | 103 ++++++++ VideoManager.cpp | 41 ++++ VideoManager.h | 56 +++++ VideoPage.xaml | 36 ++- VideoPage.xaml.cpp | 142 ++++++++++- VideoPage.xaml.h | 7 + VideoRendererManager.cpp | 122 ++++++++++ VideoRendererManager.h | 91 +++++++ WelcomePage.xaml.cpp | 4 +- pch.h | 8 +- ring-client-uwp.vcxproj | 8 + ring-client-uwp.vcxproj.filters | 33 +++ 20 files changed, 1669 insertions(+), 38 deletions(-) create mode 100644 Video.cpp create mode 100644 Video.h create mode 100644 VideoCaptureManager.cpp create mode 100644 VideoCaptureManager.h create mode 100644 VideoManager.cpp create mode 100644 VideoManager.h create mode 100644 VideoRendererManager.cpp create mode 100644 VideoRendererManager.h diff --git a/MainPage.xaml.cpp b/MainPage.xaml.cpp index 191ae3b..bdcbb29 100644 --- a/MainPage.xaml.cpp +++ b/MainPage.xaml.cpp @@ -106,7 +106,6 @@ RingClientUWP::MainPage::showFrame(Windows::UI::Xaml::Controls::Frame^ frame) _navGrid_->SetRow(_welcomeFrame_, 1); } else if (frame == _videoFrame_) { _navGrid_->SetRow(_videoFrame_, 1); - dynamic_cast<VideoPage^>(_videoFrame_->Content)->updatePageContent(); } else if (frame == _messageTextFrame_) { _navGrid_->SetRow(_messageTextFrame_, 1); } @@ -260,3 +259,37 @@ void RingClientUWP::MainPage::OnstateChange(Platform::String ^callId, RingClient } } + +void +MainPage::Application_Suspending(Object^, Windows::ApplicationModel::SuspendingEventArgs^ e) +{ + WriteLine("Application_Suspending"); + if (Frame->CurrentSourcePageType.Name == + Interop::TypeName(MainPage::typeid).Name) + { + if (Video::VideoManager::instance->captureManager()->captureTaskTokenSource) + Video::VideoManager::instance->captureManager()->captureTaskTokenSource->cancel(); + //displayInformation->OrientationChanged -= displayInformationEventToken; + auto deferral = e->SuspendingOperation->GetDeferral(); + Video::VideoManager::instance->captureManager()->CleanupCameraAsync() + .then([this, deferral]() { + deferral->Complete(); + }); + } +} + +void +MainPage::Application_VisibilityChanged(Object^ sender, VisibilityChangedEventArgs^ e) +{ + if (e->Visible) + { + WriteLine("->Visible"); + if (Video::VideoManager::instance->captureManager()->isInitialized) { + Video::VideoManager::instance->captureManager()->InitializeCameraAsync(); + } + } + else + { + WriteLine("->Invisible"); + } +} diff --git a/MainPage.xaml.h b/MainPage.xaml.h index d97fd59..554ae31 100644 --- a/MainPage.xaml.h +++ b/MainPage.xaml.h @@ -45,6 +45,10 @@ protected: void OnResize(Platform::Object^ sender, Windows::UI::Core::WindowSizeChangedEventArgs^ e); private: + // event handlers + void Application_Suspending(Object^, Windows::ApplicationModel::SuspendingEventArgs^ e); + void Application_VisibilityChanged(Object^ sender, Windows::UI::Core::VisibilityChangedEventArgs^ e); + // Multi-monitor, DPI, scale factor change, and window resize detection void DisplayProperties_DpiChanged(Windows::Graphics::Display::DisplayInformation^ sender, Platform::Object^ args); Windows::Foundation::EventRegistrationToken dpiChangedtoken; @@ -58,4 +62,4 @@ private: void OnpressHangUpCall(); void OnstateChange(Platform::String ^callId, RingClientUWP::CallStatus state, int code); }; -} \ No newline at end of file +} diff --git a/RingD.cpp b/RingD.cpp index 2508fb8..38033d8 100644 --- a/RingD.cpp +++ b/RingD.cpp @@ -23,6 +23,7 @@ #include "callmanager_interface.h" #include "configurationmanager_interface.h" #include "presencemanager_interface.h" +#include "videomanager_interface.h" #include "fileutils.h" #include "account_schema.h" #include "account_const.h" @@ -32,6 +33,9 @@ using namespace Windows::ApplicationModel::Core; using namespace Windows::Storage; using namespace Windows::UI::Core; +using namespace Windows::Media; +using namespace Windows::Media::MediaProperties; +using namespace Windows::Media::Capture; using namespace RingClientUWP; using namespace RingClientUWP::Utils; @@ -311,14 +315,7 @@ RingClientUWP::RingD::startDaemon() RingDebug::instance->print(toto); })); }) - /* to remove from daemon API, this callback is never used */ - //DRing::exportable_callback<DRing::CallSignal::NewCallCreated>([&]( - // const std::string& accountId, - // const std::string& callId, - // const std::string& to) - //{ /*...*/ }) }; - registerCallHandlers(callHandlers); std::map<std::string, SharedCallback> getAppPathHandler = @@ -330,6 +327,91 @@ RingClientUWP::RingD::startDaemon() }; registerCallHandlers(getAppPathHandler); + std::map<std::string, SharedCallback> incomingVideoHandlers = + { + DRing::exportable_callback<DRing::VideoSignal::DeviceEvent> + ([this]() { + MSG_("<DeviceEvent>"); + }), + DRing::exportable_callback<DRing::VideoSignal::DecodingStarted> + ([this](const std::string &id, const std::string &shmPath, int width, int height, bool isMixer) { + MSG_("<DecodingStarted>"); + Video::VideoManager::instance->rendererManager()->startedDecoding( + Utils::toPlatformString(id), + width, + height); + }), + DRing::exportable_callback<DRing::VideoSignal::DecodingStopped> + ([this](const std::string &id, const std::string &shmPath, bool isMixer) { + MSG_("<DecodingStopped>"); + MSG_("Removing renderer id:" + id); + /*auto Id = Utils::toPlatformString(id); + auto renderer = Video::VideoManager::instance->rendererManager()->renderer(Id); + if (renderer) + renderer->isRendering = false;*/ + Video::VideoManager::instance->rendererManager()->removeRenderer(Utils::toPlatformString(id)); + }) + }; + registerVideoHandlers(incomingVideoHandlers); + + using namespace Video; + std::map<std::string, SharedCallback> outgoingVideoHandlers = + { + DRing::exportable_callback<DRing::VideoSignal::GetCameraInfo> + ([this](const std::string& device, + std::vector<std::string> *formats, + std::vector<unsigned> *sizes, + std::vector<unsigned> *rates) { + MSG_("\n<GetCameraInfo>\n"); + auto device_list = VideoManager::instance->captureManager()->deviceList; + + for (unsigned int i = 0; i < device_list->Size; i++) { + auto dev = device_list->GetAt(i); + if (device == Utils::toString(dev->name())) { + auto channel = dev->channel(); + Vector<Video::Resolution^>^ resolutions = channel->resolutionList(); + for (auto res : resolutions) { + formats->emplace_back(Utils::toString(res->format())); + sizes->emplace_back(res->size()->width()); + sizes->emplace_back(res->size()->height()); + rates->emplace_back(res->activeRate()->value()); + } + } + } + }), + DRing::exportable_callback<DRing::VideoSignal::SetParameters> + ([this](const std::string& device, + std::string format, + const int width, + const int height, + const int rate) { + MSG_("\n<SetParameters>\n"); + VideoManager::instance->captureManager()->activeDevice->SetDeviceProperties( + Utils::toPlatformString(format),width,height,rate); + }), + DRing::exportable_callback<DRing::VideoSignal::StartCapture> + ([&](const std::string& device) { + MSG_("\n<StartCapture>\n"); + dispatcher->RunAsync(CoreDispatcherPriority::Normal, + ref new DispatchedHandler([=]() { + VideoManager::instance->captureManager()->InitializeCameraAsync(); + VideoManager::instance->captureManager()->videoFrameCopyInvoker->Start(); + })); + }), + DRing::exportable_callback<DRing::VideoSignal::StopCapture> + ([&]() { + MSG_("\n<StopCapture>\n"); + dispatcher->RunAsync(CoreDispatcherPriority::Normal, + ref new DispatchedHandler([=]() { + VideoManager::instance->captureManager()->StopPreviewAsync(); + if (VideoManager::instance->captureManager()->captureTaskTokenSource) + VideoManager::instance->captureManager()->captureTaskTokenSource->cancel(); + VideoManager::instance->captureManager()->videoFrameCopyInvoker->Stop(); + })); + }) + }; + registerVideoHandlers(outgoingVideoHandlers); + DRing::init(static_cast<DRing::InitFlag>(DRing::DRING_FLAG_CONSOLE_LOG | DRing::DRING_FLAG_DEBUG)); @@ -338,8 +420,7 @@ RingClientUWP::RingD::startDaemon() return; } else { - if (!hasConfig) - { + if (!hasConfig) { tasksList_.push(ref new RingD::Task(Request::AddRingAccount)); tasksList_.push(ref new RingD::Task(Request::AddSIPAccount)); } diff --git a/RingDebug.h b/RingDebug.h index 83c1388..2398085 100644 --- a/RingDebug.h +++ b/RingDebug.h @@ -54,6 +54,20 @@ private: RingDebug() {}; // singleton }; +void WriteLine(String^ str) +{ + std::wstringstream wStringstream; + wStringstream << str->Data() << "\n"; + OutputDebugString(wStringstream.str().c_str()); +} + +void WriteException(Exception^ ex) +{ + std::wstringstream wStringstream; + wStringstream << "0x" << ex->HResult << ": " << ex->Message->Data(); + OutputDebugString(wStringstream.str().c_str()); +} + #define MSG_(cstr) CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(CoreDispatcherPriority::Low, \ ref new DispatchedHandler([=]() { RingDebug::instance->print(cstr); })) diff --git a/Utils.h b/Utils.h index 2f6e4f4..879542c 100644 --- a/Utils.h +++ b/Utils.h @@ -20,8 +20,11 @@ #include <pch.h> using namespace Platform; +using namespace Platform::Collections; using namespace Windows::Storage; +typedef Windows::UI::Xaml::Visibility VIS; + namespace RingClientUWP { namespace Utils @@ -50,7 +53,8 @@ fileExists(StorageFolder^ folder, String^ fileName) }); } -std::string makeString(const std::wstring& wstr) +inline std::string +makeString(const std::wstring& wstr) { auto wideData = wstr.c_str(); int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wideData, -1, nullptr, 0, NULL, NULL); @@ -65,7 +69,8 @@ std::string makeString(const std::wstring& wstr) return std::string(utf8.get()); } -std::wstring makeWString(const std::string& str) +inline std::wstring +makeWString(const std::string& str) { auto utf8Data = str.c_str(); int bufferSize = MultiByteToWideChar(CP_UTF8, 0, utf8Data, -1, nullptr, 0); @@ -80,19 +85,22 @@ std::wstring makeWString(const std::string& str) return std::wstring(wide.get());; } -std::string toString(Platform::String ^str) +inline std::string +toString(Platform::String ^str) { std::wstring wsstr(str->Data()); return makeString(wsstr); } -Platform::String^ toPlatformString(const std::string& str) +inline Platform::String^ +toPlatformString(const std::string& str) { std::wstring wsstr = makeWString(str); return ref new Platform::String(wsstr.c_str(), wsstr.length()); } -Platform::String^ Trim(Platform::String^ s) +Platform::String^ +Trim(Platform::String^ s) { const WCHAR* first = s->Begin(); const WCHAR* last = s->End(); @@ -107,7 +115,8 @@ Platform::String^ Trim(Platform::String^ s) } /* fix some issue in the daemon --> <...@...> */ -Platform::String^ TrimRingId(Platform::String^ s) +Platform::String^ +TrimRingId(Platform::String^ s) { const WCHAR* first = s->Begin(); const WCHAR* last = s->End(); @@ -125,7 +134,8 @@ Platform::String^ TrimRingId(Platform::String^ s) } /* fix some issue in the daemon --> remove "@..." */ -Platform::String^ TrimRingId2(Platform::String^ s) +Platform::String^ +TrimRingId2(Platform::String^ s) { const WCHAR* first = s->Begin(); const WCHAR* last = s->End(); @@ -138,7 +148,8 @@ Platform::String^ TrimRingId2(Platform::String^ s) return ref new Platform::String(first, static_cast<unsigned int>(last - first)); } -Platform::String^ GetNewGUID() +Platform::String^ +GetNewGUID() { GUID result; HRESULT hr = CoCreateGuid(&result); @@ -159,5 +170,31 @@ getStringFromFile(const std::string& filename) (std::istreambuf_iterator<char>())); } +inline Map<String^,String^>^ +convertMap(const std::map<std::string, std::string>& m) +{ + auto temp = ref new Map<String^,String^>; + for (const auto& pair : m) { + temp->Insert( + Utils::toPlatformString(pair.first), + Utils::toPlatformString(pair.second) + ); + } + return temp; +} + +inline std::map<std::string, std::string> +convertMap(Map<String^,String^>^ m) +{ + std::map<std::string, std::string> temp; + for (const auto& pair : m) { + temp.insert( + std::make_pair( + Utils::toString(pair->Key), + Utils::toString(pair->Value))); + } + return temp; +} + } } diff --git a/Video.cpp b/Video.cpp new file mode 100644 index 0000000..84ad568 --- /dev/null +++ b/Video.cpp @@ -0,0 +1,282 @@ +/************************************************************************** +* 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 "Video.h" + +using namespace RingClientUWP; +using namespace Video; + +using namespace Platform; + +/************************************************************ + * * + * Size * + * * + ***********************************************************/ + +unsigned int +Video::Size::width() +{ + return m_Width; +} + +unsigned int +Video::Size::height() +{ + return m_Height; +} + +void +Video::Size::setWidth(unsigned int width) +{ + m_Width = width; +} + +void +Video::Size::setHeight(unsigned int height) +{ + m_Height = height; +} + +/************************************************************ + * * + * Rate * + * * + ***********************************************************/ + +String^ +Rate::name() +{ + return m_name; +} + +unsigned int +Rate::value() +{ + return m_value; +} + +void +Rate::setName(String^ name) +{ + m_name = name; +} + +void +Rate::setValue(unsigned int value) +{ + m_value = value; +} + +/************************************************************ + * * + * Channel * + * * + ***********************************************************/ +Channel::Channel() +{ + m_validResolutions = ref new Vector<Resolution^>(); +} + +String^ +Channel::name() +{ + return m_name; +} + +Resolution^ +Channel::currentResolution() +{ + return m_currentResolution; +} + +void +Channel::setName(String^ name) +{ + m_name = name; +} + +void +Channel::setCurrentResolution(Resolution^ currentResolution) +{ + m_currentResolution = currentResolution; +} + +Vector<Resolution^>^ +Channel::resolutionList() +{ + return m_validResolutions; +} + +/************************************************************ + * * + * Resolution * + * * + ***********************************************************/ + +Resolution::Resolution() +{ + m_size = ref new Size(); + m_validRates = ref new Vector<Rate^>(); +} + +Resolution::Resolution(Video::Size^ size): + m_size(size) +{ } + +String^ +Resolution::name() +{ + return size()->width().ToString() + "x" + size()->height().ToString(); +} + +Rate^ +Resolution::activeRate() +{ + return m_currentRate; +} + +Vector<Rate^>^ +Resolution::rateList() +{ + return m_validRates; +} + +Video::Size^ +Resolution::size() +{ + return m_size; +} + +String^ +Resolution::format() +{ + return m_format; +} + +void +Resolution::setWidth(int width) +{ + m_size->setWidth(width); +} + +void +Resolution::setHeight(int height) +{ + m_size->setHeight(height); +} + +void +Resolution::setFormat(String^ format) +{ + m_format = format; +} + +bool +Resolution::setActiveRate(Rate^ rate) +{ + if (m_currentRate == rate) + return false; + + m_currentRate = rate; + // set camera device rate here + return true; +} + +/************************************************************ + * * + * Device * + * * + ***********************************************************/ + +Device::Device(String^ id) +{ + m_deviceId = id; + m_channels = ref new Vector<Channel^>(); +} + +String^ +Device::id() +{ + return m_deviceId; +} + +Vector<Channel^>^ +Device::channelList() +{ + return m_channels; +} + +String^ +Device::name() +{ + return m_name; +} + +Channel^ +Device::channel() +{ + return m_currentChannel; +} + +bool +Device::setCurrentChannel(Channel^ channel) +{ + if (m_currentChannel == channel) + return false; + m_currentChannel = channel; + return true; +} + +void +Device::setName(String^ name) +{ + m_name = name; +} + +void +Device::save() +{ +} + +bool +Device::isActive() +{ + return false; + //return Video::DeviceModel::instance().activeDevice() == this; +} + +void +Device::SetDeviceProperties(String^ format, int width, int height, int rate) +{ + auto rl = m_currentChannel->resolutionList(); + for (auto res : rl) { + if (res->format() == format && + res->size()->width() == width && + res->size()->height() == height && + res->activeRate()->value() == rate) + { + m_currentChannel->setCurrentResolution(res); + WriteLine("SetDeviceProperties"); + return; + } + } +} \ No newline at end of file diff --git a/Video.h b/Video.h new file mode 100644 index 0000000..ad544d5 --- /dev/null +++ b/Video.h @@ -0,0 +1,156 @@ +/************************************************************************** +* 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/>. * +**************************************************************************/ + +#pragma once + +using namespace Platform; + +namespace RingClientUWP +{ + +namespace Video +{ + +ref class Rate; +ref class Resolution; +ref class Device; + +public ref class Size sealed +{ +internal: + unsigned int width (); + unsigned int height (); + + void setWidth ( unsigned int width ); + void setHeight ( unsigned int height ); + +public: + Size() { }; + Size(unsigned int w, unsigned int h): + m_Width(w), + m_Height(h) { }; + Size(Size^ rhs): + m_Width(rhs->m_Width), + m_Height(rhs->m_Height) { }; + +private: + unsigned int m_Width; + unsigned int m_Height; + +}; + +public ref class Rate sealed +{ +internal: + String^ name (); + unsigned int value (); + + void setName ( String^ name ); + void setValue ( unsigned int value ); + +private: + String^ m_name; + unsigned int m_value; + +}; + +public ref class Channel sealed +{ +internal: + String^ name (); + Resolution^ currentResolution (); + Vector<Resolution^>^ resolutionList (); + + void setName ( String^ name ); + void setCurrentResolution ( Resolution^ currentResolution); + +public: + Channel(); + +private: + String^ m_name; + Resolution^ m_currentResolution; + Vector<Resolution^>^ m_validResolutions; + +}; + +public ref class Resolution sealed +{ +internal: + String^ name (); + Rate^ activeRate (); + Vector<Rate^>^ rateList (); + Size^ size (); + String^ format (); + + bool setActiveRate ( Rate^ rate ); + void setWidth ( int width ); + void setHeight ( int height ); + void setFormat ( String^ format ); + +public: + Resolution(); + Resolution(Size^ size); + +private: + Rate^ m_currentRate; + Vector<Rate^>^ m_validRates; + Size^ m_size; + String^ m_format; + +}; + +public ref class Device sealed +{ +internal: + class PreferenceNames { + public: + constexpr static const char* RATE = "rate" ; + constexpr static const char* NAME = "name" ; + constexpr static const char* CHANNEL = "channel"; + constexpr static const char* SIZE = "size" ; + }; + + Vector<Channel^>^ channelList (); + String^ id (); + String^ name (); + Channel^ channel (); + + bool setCurrentChannel ( Channel^ channel ); + void setName ( String^ name ); + + +public: + Device(String^ id); + void SetDeviceProperties(String^ format, int width, int height, int rate); + + void save (); + bool isActive (); + +private: + String^ m_deviceId ; + String^ m_name ; + Channel^ m_currentChannel ; + Vector<Channel^>^ m_channels ; + bool m_requireSave ; + +}; + +} /* namespace Video */ +} /* namespace RingClientUWP */ \ No newline at end of file diff --git a/VideoCaptureManager.cpp b/VideoCaptureManager.cpp new file mode 100644 index 0000000..b40be16 --- /dev/null +++ b/VideoCaptureManager.cpp @@ -0,0 +1,411 @@ +/************************************************************************** +* 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)); +} \ No newline at end of file diff --git a/VideoCaptureManager.h b/VideoCaptureManager.h new file mode 100644 index 0000000..74ad03d --- /dev/null +++ b/VideoCaptureManager.h @@ -0,0 +1,103 @@ +/************************************************************************** +* 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/>. * +**************************************************************************/ +#pragma once + +using namespace Windows::UI::Xaml; +using namespace Windows::ApplicationModel::Core; +using namespace Windows::Devices::Enumeration; +using namespace Windows::Media::Capture; +using namespace Windows::Foundation; +using namespace Concurrency; + +namespace RingClientUWP +{ + +delegate void StartPreviewing(); +delegate void StopPreviewing(); +delegate Windows::UI::Xaml::Controls::CaptureElement^ GetSink(); + +namespace Video +{ + +public ref class VideoCaptureManager sealed +{ +internal: + property bool isPreviewing + { + bool get() { return isPreviewing_; } + void set(bool value) { isPreviewing_ = value; } + } + + Map<String^,String^>^ getSettings(String^ device); + + VideoCaptureManager(); + + Windows::Graphics::Display::DisplayInformation^ displayInformation; + Windows::Graphics::Display::DisplayOrientations displayOrientation; + + Windows::System::Display::DisplayRequest^ displayRequest; + + const GUID RotationKey; + + bool externalCamera; + bool mirroringPreview; + bool isInitialized; + bool isChangingCamera; + bool isRendering; + + Vector<Device^>^ deviceList; + Device^ activeDevice; + + DeviceInformationCollection^ devInfoCollection; + + Platform::Agile<Windows::Media::Capture::MediaCapture^> mediaCapture; + + task<void> InitializeCameraAsync(); + task<void> StartPreviewAsync(); + task<void> StopPreviewAsync(); + task<void> EnumerateWebcamsAsync(); + task<void> CleanupCameraAsync(); + + // event tokens + EventRegistrationToken mediaCaptureFailedEventToken; + EventRegistrationToken displayInformationEventToken; + EventRegistrationToken visibilityChangedEventToken; + + cancellation_token_source* captureTaskTokenSource; + + void MediaCapture_Failed(MediaCapture ^currentCaptureObject, MediaCaptureFailedEventArgs^ errorEventArgs); + void AddVideoDevice(uint8_t index); + void SetCaptureSettings(); + + DispatcherTimer^ videoFrameCopyInvoker; + task<void> CopyFrameAsync(); + void CopyFrame(Object^ sender, Object^ e); + void InitializeCopyFrameDispatcher(); + + event StartPreviewing^ startPreviewing; + event StopPreviewing^ stopPreviewing; + event GetSink^ getSink; + +private: + bool isPreviewing_; + +}; + +} /* namespace Video */ +} /* namespace RingClientUWP */ \ No newline at end of file diff --git a/VideoManager.cpp b/VideoManager.cpp new file mode 100644 index 0000000..86d973b --- /dev/null +++ b/VideoManager.cpp @@ -0,0 +1,41 @@ +/************************************************************************** +* 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 "VideoManager.h" + +using namespace RingClientUWP; +using namespace Video; + +VideoManager::VideoManager() +{ + videoCaptureManager = ref new VideoCaptureManager(); + videoRendererManager = ref new VideoRendererManager(); +} + +VideoCaptureManager^ +VideoManager::captureManager() +{ + return videoCaptureManager; +} + +VideoRendererManager^ +VideoManager::rendererManager() +{ + return videoRendererManager; +} \ No newline at end of file diff --git a/VideoManager.h b/VideoManager.h new file mode 100644 index 0000000..de174b0 --- /dev/null +++ b/VideoManager.h @@ -0,0 +1,56 @@ +/************************************************************************** +* 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/>. * +**************************************************************************/ +#pragma once + +namespace RingClientUWP +{ + +namespace Video +{ + +ref class VideoCaptureManager; +ref class VideoRendererManager; + +public ref class VideoManager sealed +{ +internal: + /* ref class singleton */ + static property VideoManager^ instance + { + VideoManager^ get() + { + static VideoManager^ instance_ = ref new VideoManager(); + return instance_; + } + } + +public: + VideoCaptureManager^ captureManager (); + VideoRendererManager^ rendererManager (); + +private: + VideoManager(); + + VideoCaptureManager^ videoCaptureManager; + VideoRendererManager^ videoRendererManager; + +}; + +} /* namespace Video */ +} /* namespace RingClientUWP */ \ No newline at end of file diff --git a/VideoPage.xaml b/VideoPage.xaml index d81d3f5..57240fa 100644 --- a/VideoPage.xaml +++ b/VideoPage.xaml @@ -137,19 +137,33 @@ <RowDefinition Height="*"/> <RowDefinition x:Name="_rowChatBx_" Height="0"/> </Grid.RowDefinitions> - <Grid Background="#000000" - Grid.Row="0" - PointerMoved="_videoControl__PointerMoved"> + <Grid Background="#000000" + Grid.Row="0" + PointerMoved="_videoControl__PointerMoved"> <StackPanel x:Name="_headerBar_" - Background="{StaticResource SemiTransparentBlack}" - HorizontalAlignment="Stretch" - VerticalAlignment="Top" - Height="50"> - <TextBlock x:Name="_callee_" - Text="callee" - Foreground="White" - Margin="20,10"/> + Background="{StaticResource SemiTransparentBlack}" + HorizontalAlignment="Stretch" + VerticalAlignment="Top" + Height="50"> + <TextBlock x:Name="_callee_" + Text="callee" + Foreground="White" + Margin="20,10"/> </StackPanel> + + <!-- video --> + <Image Name="IncomingVideoImage" + Grid.Column="0" + Canvas.ZIndex="-1"/> + + <!--camera preview--> + <CaptureElement Name="PreviewImage" + Width="200" + VerticalAlignment="Center" + HorizontalAlignment="Right" + Stretch="Uniform" + Grid.Column="0"/> + <StackPanel x:Name="_controlsBar_" HorizontalAlignment="Center" VerticalAlignment="Bottom" diff --git a/VideoPage.xaml.cpp b/VideoPage.xaml.cpp index 112741c..ee44f93 100644 --- a/VideoPage.xaml.cpp +++ b/VideoPage.xaml.cpp @@ -20,8 +20,11 @@ #include "VideoPage.xaml.h" +#include <MemoryBuffer.h> // IMemoryBufferByteAccess + using namespace RingClientUWP::Views; using namespace ViewModel; +using namespace Video; using namespace Concurrency; using namespace Platform; @@ -39,19 +42,98 @@ using namespace Windows::Media::Capture; using namespace Windows::ApplicationModel::Core; using namespace Windows::UI::Core; +using namespace Windows::Graphics::Display; +using namespace Windows::Graphics::Imaging; +using namespace Windows::Media; +using namespace Windows::UI::Xaml::Media::Imaging; +using namespace Windows::Media::Capture; +using namespace Windows::Devices::Sensors; + VideoPage::VideoPage() { InitializeComponent(); -} -void -RingClientUWP::Views::VideoPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) -{ + VideoManager::instance->captureManager()->displayInformation = DisplayInformation::GetForCurrentView(); + VideoManager::instance->captureManager()->EnumerateWebcamsAsync(); + + Page::NavigationCacheMode = Navigation::NavigationCacheMode::Required; + + VideoManager::instance->rendererManager()->writeVideoFrame += + ref new WriteVideoFrame([this](String^ id, uint8_t* buf, int width, int height) + { + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, + ref new DispatchedHandler([=]() { + try { + if (!VideoManager::instance->rendererManager()->renderers->Size) + return; + VideoManager::instance->rendererManager()->renderer(id)->isRendering = true; + create_task(WriteFrameAsSoftwareBitmapAsync(id, buf, width, height)) + .then([=](task<void> previousTask) { + try { + previousTask.get(); + } + catch (Platform::Exception^ e) { + WriteLine( "Caught exception from previous task.\n" ); + } + }); + } + catch(Platform::COMException^ e) { + WriteLine(e->ToString()); + } + })); + }); + + VideoManager::instance->captureManager()->startPreviewing += + ref new StartPreviewing([this]() + { + PreviewImage->Visibility = Windows::UI::Xaml::Visibility::Visible; + PreviewImage->FlowDirection = VideoManager::instance->captureManager()->mirroringPreview ? + Windows::UI::Xaml::FlowDirection::RightToLeft : + Windows::UI::Xaml::FlowDirection::LeftToRight; + }); + + VideoManager::instance->captureManager()->stopPreviewing += + ref new StopPreviewing([this]() + { + PreviewImage->Source = nullptr; + PreviewImage->Visibility = Windows::UI::Xaml::Visibility::Collapsed; + }); + + VideoManager::instance->captureManager()->getSink += + ref new GetSink([this]() + { + return PreviewImage; + }); + + VideoManager::instance->rendererManager()->clearRenderTarget += + ref new ClearRenderTarget([this]() + { + IncomingVideoImage->Source = nullptr; + }); + + RingD::instance->incomingAccountMessage += + ref new IncomingAccountMessage([&](String^ accountId, String^ from, String^ payload) + { + scrollDown(); + }); + + RingD::instance->stateChange += + ref new StateChange([&](String^ callId, CallStatus state, int code) + { + if (state == CallStatus::ENDED) { + Video::VideoManager::instance->rendererManager()->raiseClearRenderTarget(); + } + }); + RingD::instance->incomingAccountMessage += ref new IncomingAccountMessage([&](String^ accountId, String^ from, String^ payload) { scrollDown(); }); +} +void +RingClientUWP::Views::VideoPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) +{ updatePageContent(); } @@ -202,4 +284,54 @@ void RingClientUWP::Views::VideoPage::btnAny_entered(Platform::Object^ sender, W void RingClientUWP::Views::VideoPage::btnAny_exited(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { barFading_ = true; -} \ No newline at end of file +} + +task<void> +VideoPage::WriteFrameAsSoftwareBitmapAsync(String^ id, uint8_t* buf, int width, int height) +{ + auto vframe = ref new VideoFrame(BitmapPixelFormat::Bgra8, width, height); + auto frame = vframe->SoftwareBitmap; + + const int BYTES_PER_PIXEL = 4; + + BitmapBuffer^ buffer = frame->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); + + 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; + + data[currPixel + 0] = buf[currPixel + 0]; + data[currPixel + 1] = buf[currPixel + 1]; + data[currPixel + 2] = buf[currPixel + 2]; + } + } + } + delete reference; + delete buffer; + + VideoManager::instance->rendererManager()->renderer(id)->isRendering = false; + + auto sbSource = ref new Media::Imaging::SoftwareBitmapSource(); + return create_task(sbSource->SetBitmapAsync(frame)) + .then([this, sbSource]() + { + try { + IncomingVideoImage->Source = sbSource; + } + catch (Exception^ e) { + WriteException(e); + } + }); +} diff --git a/VideoPage.xaml.h b/VideoPage.xaml.h index 0f17ac0..d548f95 100644 --- a/VideoPage.xaml.h +++ b/VideoPage.xaml.h @@ -23,6 +23,11 @@ using namespace Windows::Media::Capture; using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::UI::Xaml; +using namespace Windows::ApplicationModel::Core; +using namespace Windows::Devices::Enumeration; + + namespace RingClientUWP { /* delegate */ @@ -94,6 +99,8 @@ private: void _messageTextBox__KeyDown(Platform::Object^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs^ e); void sendMessage(); + Concurrency::task<void> WriteFrameAsSoftwareBitmapAsync(String^ id, uint8_t* buf, int width, int height); + void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); void _btnCancel__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); void _btnHangUp__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e); diff --git a/VideoRendererManager.cpp b/VideoRendererManager.cpp new file mode 100644 index 0000000..84220a5 --- /dev/null +++ b/VideoRendererManager.cpp @@ -0,0 +1,122 @@ +/************************************************************************** +* 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 "VideoRendererManager.h" + +using namespace RingClientUWP; +using namespace Video; + +using namespace Platform; + +using namespace Windows::UI::Core; +using namespace Windows::ApplicationModel::Core; + +void +VideoRenderer::bindSinkFunctions() +{ + using namespace std::placeholders; + target.pull = std::bind(&VideoRenderer::requestFrameBuffer,this, _1); + target.push = std::bind(&VideoRenderer::onNewFrame,this, _1); +} + +DRing::SinkTarget::FrameBufferPtr +VideoRenderer::requestFrameBuffer(std::size_t bytes) +{ + std::lock_guard<std::mutex> lk(video_mutex); + if (!daemonFramePtr_) + daemonFramePtr_.reset(new DRing::FrameBuffer); + daemonFramePtr_->storage.resize(bytes); + daemonFramePtr_->ptr = daemonFramePtr_->storage.data(); + daemonFramePtr_->ptrSize = bytes; + return std::move(daemonFramePtr_); +} + +void +VideoRenderer::onNewFrame(DRing::SinkTarget::FrameBufferPtr buf) +{ + std::lock_guard<std::mutex> lk(video_mutex); + daemonFramePtr_ = std::move(buf); + auto id = Utils::toPlatformString(this->id); + VideoManager::instance->rendererManager()->raiseWriteVideoFrame(id); +} + +VideoRendererManager::VideoRendererManager() +{ + renderers = ref new Map<String^, VideoRendererWrapper^>(); +} + +void +VideoRendererManager::startedDecoding(String^ id, int width, int height) +{ + renderers->Insert(id, ref new VideoRendererWrapper()); + auto renderer = renderers->Lookup(id)->renderer; + renderer->id = Utils::toString(id); + renderer->bindSinkFunctions(); + renderer->width = width; + renderer->height = height; + MSG_(Utils::toString( "startedDecoding for sink id: " + id).c_str()); + registerSinkTarget(id, renderer->target); +} + +void +VideoRendererManager::registerSinkTarget(String^ sinkID, const DRing::SinkTarget& target) +{ + MSG_(Utils::toString( "registerSinkTarget for sink id: " + sinkID).c_str()); + DRing::registerSinkTarget(Utils::toString(sinkID), target); +} + +void +VideoRendererManager::raiseWriteVideoFrame(String^ id) +{ + auto renderer = renderers->Lookup(id)->renderer; + if (!renderer) + return; + writeVideoFrame(id, + renderer->daemonFramePtr_->ptr, + renderer->width, + renderer->height); +} + +void +VideoRendererManager::raiseClearRenderTarget() +{ + clearRenderTarget(); +} + +void +VideoRendererManager::removeRenderer(String^ id) +{ + if(!renderers) + return; + std::unique_lock<std::mutex> lk(renderers->Lookup(id)->render_mutex); + renderers->Lookup(id)->frame_cv.wait(lk, [=] { + return !renderers->Lookup(id)->isRendering; + }); + renderers->Remove(id); +} + +VideoRendererWrapper^ +VideoRendererManager::renderer(String^ id) +{ + if(!renderers) + return nullptr; + return renderers->Lookup(id); +} \ No newline at end of file diff --git a/VideoRendererManager.h b/VideoRendererManager.h new file mode 100644 index 0000000..24ba76b --- /dev/null +++ b/VideoRendererManager.h @@ -0,0 +1,91 @@ +/************************************************************************** +* 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/>. * +**************************************************************************/ + +#pragma once + +#include "videomanager_interface.h" + +namespace RingClientUWP +{ + +delegate void WriteVideoFrame(String^, uint8_t*, int, int); +delegate void ClearRenderTarget(); + +namespace Video +{ + +class VideoRenderer +{ +public: + std::string id; + std::mutex video_mutex; + DRing::SinkTarget target; + DRing::SinkTarget::FrameBufferPtr daemonFramePtr_; + int width; + int height; + + void bindSinkFunctions(); + + DRing::SinkTarget::FrameBufferPtr requestFrameBuffer(std::size_t bytes); + void onNewFrame(DRing::SinkTarget::FrameBufferPtr buf); + +}; + +ref class VideoRendererWrapper sealed +{ +internal: + VideoRendererWrapper() { + renderer = std::make_shared<VideoRenderer>(); + isRendering = false; + }; + + std::mutex render_mutex; + std::condition_variable frame_cv; + + std::shared_ptr<VideoRenderer> renderer; + bool isRendering; +}; + +public ref class VideoRendererManager sealed +{ +internal: + void startedDecoding(String^ id, int width, int height); + void registerSinkTarget(String^ sinkID, const DRing::SinkTarget& target); + + /* events */ + event WriteVideoFrame^ writeVideoFrame; + event ClearRenderTarget^ clearRenderTarget; + + VideoRendererWrapper^ renderer(String^ id); + Map<String^,VideoRendererWrapper^>^ renderers; + +public: + VideoRendererManager(); + + void raiseWriteVideoFrame(String^ id); + void raiseClearRenderTarget(); + + void removeRenderer(String^ id); + +private: + +}; + +} /* namespace Video */ +} /* namespace RingClientUWP */ \ No newline at end of file diff --git a/WelcomePage.xaml.cpp b/WelcomePage.xaml.cpp index 1a22547..f61275d 100644 --- a/WelcomePage.xaml.cpp +++ b/WelcomePage.xaml.cpp @@ -33,8 +33,8 @@ void WelcomePage::PositionImage() { Rect imageBounds; - imageBounds.Width = _welcomePage_->ActualWidth; - imageBounds.Height = _welcomePage_->ActualWidth; + imageBounds.Width = static_cast<float>(_welcomePage_->ActualWidth); + imageBounds.Height = static_cast<float>(_welcomePage_->ActualHeight); _welcomeImage_->SetValue(Canvas::LeftProperty, imageBounds.Width * 0.5 - _welcomeImage_->Width * 0.5); _welcomeImage_->SetValue(Canvas::TopProperty, imageBounds.Height * 0.5 - _welcomeImage_->Height * 0.5); diff --git a/pch.h b/pch.h index 7e2d825..7a002b4 100644 --- a/pch.h +++ b/pch.h @@ -43,4 +43,10 @@ #include "RingD.h" #include "RingDebug.h" #include "Utils.h" -#include "UserPreferences.h" \ No newline at end of file +#include "UserPreferences.h" + +/* video headers */ +#include "Video.h" +#include "VideoCaptureManager.h" +#include "VideoManager.h" +#include "VideoRendererManager.h" \ No newline at end of file diff --git a/ring-client-uwp.vcxproj b/ring-client-uwp.vcxproj index 802976c..6f1281f 100644 --- a/ring-client-uwp.vcxproj +++ b/ring-client-uwp.vcxproj @@ -193,6 +193,10 @@ <ClInclude Include="SmartPanelItemsViewModel.h" /> <ClInclude Include="UserPreferences.h" /> <ClInclude Include="Utils.h" /> + <ClInclude Include="Video.h" /> + <ClInclude Include="VideoCaptureManager.h" /> + <ClInclude Include="VideoManager.h" /> + <ClInclude Include="VideoRendererManager.h" /> <ClInclude Include="VideoPage.xaml.h"> <DependentUpon>VideoPage.xaml</DependentUpon> </ClInclude> @@ -306,6 +310,10 @@ <ClCompile Include="SmartPanelItem.cpp" /> <ClCompile Include="SmartPanelItemsViewModel.cpp" /> <ClCompile Include="UserPreferences.cpp" /> + <ClCompile Include="Video.cpp" /> + <ClCompile Include="VideoCaptureManager.cpp" /> + <ClCompile Include="VideoManager.cpp" /> + <ClCompile Include="VideoRendererManager.cpp" /> <ClCompile Include="VideoPage.xaml.cpp"> <DependentUpon>VideoPage.xaml</DependentUpon> </ClCompile> diff --git a/ring-client-uwp.vcxproj.filters b/ring-client-uwp.vcxproj.filters index 8e1641b..2d88f46 100644 --- a/ring-client-uwp.vcxproj.filters +++ b/ring-client-uwp.vcxproj.filters @@ -38,6 +38,15 @@ <Filter Include="Controls"> <UniqueIdentifier>{2cffcd5e-0546-4629-a152-37efd9c1128f}</UniqueIdentifier> </Filter> + <Filter Include="Media"> + <UniqueIdentifier>{bec54fb8-3a88-4687-8cbf-87325df1bcc7}</UniqueIdentifier> + </Filter> + <Filter Include="Media\Video"> + <UniqueIdentifier>{f711ca0c-c71f-47a7-9352-441ab4b44d5d}</UniqueIdentifier> + </Filter> + <Filter Include="Media\Audio"> + <UniqueIdentifier>{448e3594-0555-4c62-be25-71e1cebc80e1}</UniqueIdentifier> + </Filter> </ItemGroup> <ItemGroup> <ApplicationDefinition Include="App.xaml" /> @@ -89,6 +98,18 @@ <ClCompile Include="SmartPanelItemsViewModel.cpp"> <Filter>ModelViews</Filter> </ClCompile> + <ClCompile Include="VideoManager.cpp"> + <Filter>Media\Video</Filter> + </ClCompile> + <ClCompile Include="VideoRendererManager.cpp"> + <Filter>Media\Video</Filter> + </ClCompile> + <ClCompile Include="Video.cpp"> + <Filter>Media\Video</Filter> + </ClCompile> + <ClCompile Include="VideoCaptureManager.cpp"> + <Filter>Media\Video</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h" /> @@ -140,6 +161,18 @@ <ClInclude Include="SmartPanelItemsViewModel.h"> <Filter>ModelViews</Filter> </ClInclude> + <ClInclude Include="VideoCaptureManager.h"> + <Filter>Media\Video</Filter> + </ClInclude> + <ClInclude Include="VideoManager.h"> + <Filter>Media\Video</Filter> + </ClInclude> + <ClInclude Include="VideoRendererManager.h"> + <Filter>Media\Video</Filter> + </ClInclude> + <ClInclude Include="Video.h"> + <Filter>Media\Video</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Image Include="Assets\LockScreenLogo.scale-200.png"> -- GitLab