diff --git a/MainPage.xaml.cpp b/MainPage.xaml.cpp index 191ae3b633fcbce0616252082e0e786bf871c3d4..bdcbb29d3437a4f6a8285ee664b56d61553da05b 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 d97fd59e7bcd4eb15669c02ee5a0cd55e311ecf2..554ae31ba52effa5f105435c5978e8a29c02b888 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 2508fb8d2d5f1ce461a82e78c4398f2a7b681d88..38033d87208fb76b6197d078c8058dc8dedfaf64 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 83c13880717556375c6df624d4bdd3af98f41556..2398085ca8244bb672572f48934ecd60c150cd0c 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 2f6e4f4514aa1304df4dcdc0234cb2abcb36723d..879542c1f2239a3a3dc4cc00af87d09816bb6f3a 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 0000000000000000000000000000000000000000..84ad568d15827ba868382daf3428df16dd1e9108 --- /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 0000000000000000000000000000000000000000..ad544d5ae1aadf0d9559791f96cd169dbc548cb0 --- /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 0000000000000000000000000000000000000000..b40be163bf38de8172d565f2a0a78ee986304670 --- /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 0000000000000000000000000000000000000000..74ad03d78277e8ff1b552a02a50b0d549cf60146 --- /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 0000000000000000000000000000000000000000..86d973bb77ed7df2df48fb8d2ca0f6f5be746f1a --- /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 0000000000000000000000000000000000000000..de174b078cd8c1779279b4180a57037c4089dcf2 --- /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 d81d3f5ba9ea784ae16567d31f2c12f7fc2fdc07..57240faf6d16c726a4911b7519e096c7300c410b 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 112741cbd21289f7dbb36626b26bbe9fbfc1bd9c..ee44f937e1374efa74267b66b9afbe31fddbbbd6 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 0f17ac0a844eebfc03149a2f9e42ce507f388ea8..d548f959d56bf488fe9690ac453930d7cf7cc07f 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 0000000000000000000000000000000000000000..84220a5cec4d0f8061ac20f490f14462028eddfd --- /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 0000000000000000000000000000000000000000..24ba76b038da5e259c00c9cda8e239ccab72326c --- /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 1a22547fbc35fedbdcb3b99aada487896ce93b89..f61275d15b3555ce08c99dd80839da8bfd17c410 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 7e2d8256b2a2dbd06ebdc5f63447e9e49d72b7aa..7a002b4f5f9419dc47c34fbc2ce5df6c7cadf579 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 802976c2de7206c605d3c7c48657ae37a0ac7e9e..6f1281fa3bda30288882f3471956f0e15a58b50f 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 8e1641bd400508364ed420268fad1e6b7b157e4c..2d88f468ddc4c376179693b9bce9bb1c1f950eac 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">