Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions WebRtcInterop/CameraVideoSource.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#include "pch.h"

#include "CameraVideoSource.h"

#include <api/make_ref_counted.h>
#include <modules/video_capture/video_capture_defines.h>
#include <modules/video_capture/video_capture_factory.h>
#include <vector>

namespace webrtc
{
VideoCaptureCapability CreateCapability(const int32_t width, const int32_t height, const int32_t fps)
{
VideoCaptureCapability capability;
capability.width = width;
capability.height = height;
capability.maxFPS = fps;
return capability;
}

bool AreCapabilitiesEqual(const VideoCaptureCapability& left, const VideoCaptureCapability& right)
{
return left.width == right.width &&
left.height == right.height &&
left.maxFPS == right.maxFPS &&
left.videoType == right.videoType &&
left.interlaced == right.interlaced;
}

void AddCapabilityIfMissing(std::vector<VideoCaptureCapability>& capabilities, const VideoCaptureCapability& candidate)
{
for (const auto& existing : capabilities)
{
if (AreCapabilitiesEqual(existing, candidate))
return;
}

capabilities.push_back(candidate);
}

std::vector<VideoCaptureCapability> BuildCaptureCapabilityCandidates(const std::string& deviceId)
{
std::vector<VideoCaptureCapability> candidates;
std::vector<VideoCaptureCapability> requested = {
CreateCapability(1280, 720, 30),
CreateCapability(1280, 720, 15),
CreateCapability(960, 540, 30),
CreateCapability(640, 480, 30),
CreateCapability(640, 480, 15),
CreateCapability(320, 240, 30)
};

const std::unique_ptr<VideoCaptureModule::DeviceInfo> deviceInfo(
VideoCaptureFactory::CreateDeviceInfo());
if (!deviceInfo)
return requested;

for (const auto& capability : requested)
{
VideoCaptureCapability matched;
if (deviceInfo->GetBestMatchedCapability(deviceId.c_str(), capability, matched) >= 0)
AddCapabilityIfMissing(candidates, matched);
}

for (const auto& capability : requested)
AddCapabilityIfMissing(candidates, capability);

return candidates;
}
}

namespace WebRtcInterop
{
CameraVideoSource::CameraVideoSource(const webrtc::scoped_refptr<webrtc::VideoCaptureModule>& captureModule)
: state_(static_cast<int>(kInitializing)),
stopping_(false),
capture_started_(false),
capture_module_(captureModule)
{
}

CameraVideoSource::~CameraVideoSource()
{
Stop();
}

webrtc::scoped_refptr<CameraVideoSource> CameraVideoSource::Create(const std::string& deviceId)
{
if (deviceId.empty())
return nullptr;

auto captureModule = webrtc::VideoCaptureFactory::Create(deviceId.c_str());
if (!captureModule)
return nullptr;

auto source = webrtc::make_ref_counted<CameraVideoSource>(captureModule);
if (!source->Start(deviceId))
return nullptr;

return source;
}

webrtc::MediaSourceInterface::SourceState CameraVideoSource::state() const
{
return static_cast<SourceState>(state_.load());
}

void CameraVideoSource::AddOrUpdateSink(VideoSinkInterface<webrtc::VideoFrame>* sink,
const webrtc::VideoSinkWants& wants)
{
broadcaster_.AddOrUpdateSink(sink, wants);
}

void CameraVideoSource::RemoveSink(VideoSinkInterface<webrtc::VideoFrame>* sink)
{
broadcaster_.RemoveSink(sink);
}

void CameraVideoSource::OnFrame(const webrtc::VideoFrame& frame)
{
if (stopping_.load())
return;

broadcaster_.OnFrame(frame);
}

bool CameraVideoSource::Start(const std::string& deviceId)
{
if (!capture_module_)
return false;

capture_module_->RegisterCaptureDataCallback(this);
const auto capabilities = webrtc::BuildCaptureCapabilityCandidates(deviceId);
for (const auto& capability : capabilities)
{
if (capture_module_->StartCapture(capability) == 0)
{
capture_started_.store(true);
state_.store(kLive);
return true;
}
}

capture_module_->DeRegisterCaptureDataCallback();
state_.store(kEnded);
return false;
}

void CameraVideoSource::Stop()
{
if (stopping_.exchange(true))
return;

if (capture_module_)
{
if (capture_started_.exchange(false))
capture_module_->StopCapture();

capture_module_->DeRegisterCaptureDataCallback();
capture_module_ = nullptr;
}

state_.store(kEnded);
}
}
53 changes: 53 additions & 0 deletions WebRtcInterop/CameraVideoSource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once

#include <atomic>
#include <optional>
#include <string>

#include <api/media_stream_interface.h>
#include <api/notifier.h>
#include <api/video/video_frame.h>
#include <api/video/video_sink_interface.h>
#include <media/base/video_broadcaster.h>
#include <modules/video_capture/video_capture.h>

namespace WebRtcInterop
{
class CameraVideoSource : public webrtc::Notifier<webrtc::VideoTrackSourceInterface>,
public webrtc::VideoSinkInterface<webrtc::VideoFrame>
{
public:
static webrtc::scoped_refptr<CameraVideoSource> Create(const std::string& deviceId);
explicit CameraVideoSource(const webrtc::scoped_refptr<webrtc::VideoCaptureModule>& captureModule);
~CameraVideoSource() override;

SourceState state() const override;
bool remote() const override { return false; }
bool is_screencast() const override { return false; }
std::optional<bool> needs_denoising() const override { return std::nullopt; }
bool GetStats(Stats* /* stats */) override { return false; }

void AddOrUpdateSink(VideoSinkInterface* sink,
const webrtc::VideoSinkWants& wants) override;
void RemoveSink(VideoSinkInterface* sink) override;

bool SupportsEncodedOutput() const override { return false; }
void GenerateKeyFrame() override {}
void AddEncodedSink(
VideoSinkInterface<webrtc::RecordableEncodedFrame>* /* sink */) override {}
void RemoveEncodedSink(
VideoSinkInterface<webrtc::RecordableEncodedFrame>* /* sink */) override {}

void OnFrame(const webrtc::VideoFrame& frame) override;

private:
bool Start(const std::string& deviceId);
void Stop();

std::atomic<int> state_;
std::atomic<bool> stopping_;
std::atomic<bool> capture_started_;
webrtc::scoped_refptr<webrtc::VideoCaptureModule> capture_module_;
webrtc::VideoBroadcaster broadcaster_;
};
}
20 changes: 20 additions & 0 deletions WebRtcInterop/InteropHResult.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "pch.h"

#include "InteropHResult.h"

using namespace System::ComponentModel;
using namespace System::Runtime::InteropServices;

namespace WebRtcInterop
{
void InteropHResult::ThrowIfFailed(HRESULT hr, System::String^ message)
{
if (SUCCEEDED(hr))
return;

if (HRESULT_FACILITY(hr) == FACILITY_WIN32)
throw gcnew Win32Exception(HRESULT_CODE(hr), message);

throw gcnew COMException(message, hr);
}
}
12 changes: 12 additions & 0 deletions WebRtcInterop/InteropHResult.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <winerror.h>

namespace WebRtcInterop
{
public ref class InteropHResult abstract sealed
{
public:
static void ThrowIfFailed(HRESULT hr, System::String^ message);
};
}
117 changes: 117 additions & 0 deletions WebRtcInterop/Marshaling/MarshalMedia.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#pragma once

#include <msclr/marshal.h>
#include <msclr/marshal_cppstd.h>

namespace msclr { namespace interop
{
// Marshal ValueRange<T> from native to managed
// ValueRange<uint>: handles uint?, double?
template<>
inline WebRtcNet::ValueRange<uint>^ marshal_as(const std::pair<uint32_t, uint32_t>& from)
{
auto range = gcnew WebRtcNet::ValueRange<uint>();
range->Min = from.first;
range->Max = from.second;
return range;
}

template<>
inline WebRtcNet::ValueRange<double>^ marshal_as(const std::pair<double, double>& from)
{
auto range = gcnew WebRtcNet::ValueRange<double>();
range->Min = from.first;
range->Max = from.second;
return range;
}

// Marshal MediaDeviceKind enum
template<>
inline WebRtcNet::Media::MediaDeviceKind marshal_as(webrtc::MediaDeviceInfo::Kind from)
{
switch (from)
{
case webrtc::MediaDeviceInfo::Kind::kAudioInput:
return WebRtcNet::Media::MediaDeviceKind::AudioInput;
case webrtc::MediaDeviceInfo::Kind::kAudioOutput:
return WebRtcNet::Media::MediaDeviceKind::AudioOutput;
case webrtc::MediaDeviceInfo::Kind::kVideoInput:
return WebRtcNet::Media::MediaDeviceKind::VideoInput;
default:
throw gcnew System::ArgumentException("Unknown device kind");
}
}

template<>
inline webrtc::MediaDeviceInfo::Kind marshal_as(WebRtcNet::Media::MediaDeviceKind from)
{
switch (from)
{
case WebRtcNet::Media::MediaDeviceKind::AudioInput:
return webrtc::MediaDeviceInfo::Kind::kAudioInput;
case WebRtcNet::Media::MediaDeviceKind::AudioOutput:
return webrtc::MediaDeviceInfo::Kind::kAudioOutput;
case WebRtcNet::Media::MediaDeviceKind::VideoInput:
return webrtc::MediaDeviceInfo::Kind::kVideoInput;
default:
throw gcnew System::ArgumentException("Unknown device kind");
}
}

// Marshal MediaDeviceInfo: native to managed (one-way)
template<>
inline WebRtcNet::Media::MediaDeviceInfo^ marshal_as(const webrtc::MediaDeviceInfo& from)
{
auto deviceId = marshal_as<System::String^>(from.device_id());
auto kind = marshal_as<WebRtcNet::Media::MediaDeviceKind>(from.kind());
auto label = marshal_as<System::String^>(from.label());
auto groupId = marshal_as<System::String^>(from.group_id());

// Use reflection to call internal constructor
auto ctor = WebRtcNet::Media::MediaDeviceInfo::typeid->GetConstructor(
System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Instance,
nullptr,
gcnew array<System::Type^> {
System::String::typeid,
WebRtcNet::Media::MediaDeviceKind::typeid,
System::String::typeid,
System::String::typeid
},
nullptr);

if (ctor == nullptr)
throw gcnew System::InvalidOperationException("Cannot find internal MediaDeviceInfo constructor");

return safe_cast<WebRtcNet::Media::MediaDeviceInfo^>(
ctor->Invoke(gcnew array<System::Object^> { deviceId, kind, label, groupId }));
}

// Marshal MediaStreamTrackState enum
template<>
inline WebRtcNet::Media::MediaStreamTrackState marshal_as(webrtc::MediaStreamTrackInterface::TrackState from)
{
switch (from)
{
case webrtc::MediaStreamTrackInterface::TrackState::kLive:
return WebRtcNet::Media::MediaStreamTrackState::Live;
case webrtc::MediaStreamTrackInterface::TrackState::kEnded:
return WebRtcNet::Media::MediaStreamTrackState::Ended;
default:
throw gcnew System::ArgumentException("Unknown track state");
}
}

template<>
inline webrtc::MediaStreamTrackInterface::TrackState marshal_as(WebRtcNet::Media::MediaStreamTrackState from)
{
switch (from)
{
case WebRtcNet::Media::MediaStreamTrackState::Live:
return webrtc::MediaStreamTrackInterface::TrackState::kLive;
case WebRtcNet::Media::MediaStreamTrackState::Ended:
return webrtc::MediaStreamTrackInterface::TrackState::kEnded;
default:
throw gcnew System::ArgumentException("Unknown track state");
}
}
}}
Loading
Loading