Skip to content

Commit

Permalink
macOS でカメラの映像をキャプチャする
Browse files Browse the repository at this point in the history
  • Loading branch information
melpon committed Nov 13, 2019
1 parent 8785ef1 commit f1293e0
Show file tree
Hide file tree
Showing 9 changed files with 388 additions and 84 deletions.
6 changes: 6 additions & 0 deletions .clang-format
Expand Up @@ -4,3 +4,9 @@ BasedOnStyle: Chromium
DerivePointerAlignment: false
PointerAlignment: Left
ReflowComments: false
---
Language: ObjC
BasedOnStyle: Chromium
DerivePointerAlignment: false
PointerAlignment: Left
ReflowComments: false
6 changes: 6 additions & 0 deletions CMakeLists.txt
Expand Up @@ -97,13 +97,19 @@ set(_SOURCES
)

if (WIN32)
list(APPEND _SOURCES
src/unity_camera_capturer_d3d11.cpp)

add_library(SoraUnitySdk SHARED ${_SOURCES})
elseif (APPLE)
list(APPEND _SOURCES
src/unity_camera_capturer_metal.mm
src/mac_helper/mac_capturer.mm
src/mac_helper/objc_codec_factory_helper.mm)

# Objective-C++ の設定
set_source_files_properties(src/unity_camera_capturer.mm PROPERTIES
COMPILE_OPTIONS "-x;objective-c++;-fmodules;-fcxx-modules")
set_source_files_properties(src/mac_helper/mac_capturer.mm PROPERTIES
COMPILE_OPTIONS "-x;objective-c++;-fmodules;-fcxx-modules")
set_source_files_properties(src/mac_helper/objc_codec_factory_helper.mm PROPERTIES
Expand Down
68 changes: 68 additions & 0 deletions src/unity/IUnityGraphicsMetal.h
@@ -0,0 +1,68 @@
#pragma once
#include "IUnityInterface.h"

#ifndef __OBJC__
#error metal plugin is objc code.
#endif
#ifndef __clang__
#error only clang compiler is supported.
#endif

@class NSBundle;
@protocol MTLDevice;
@protocol MTLCommandBuffer;
@protocol MTLCommandEncoder;
@protocol MTLTexture;
@class MTLRenderPassDescriptor;


UNITY_DECLARE_INTERFACE(IUnityGraphicsMetalV1)
{
NSBundle* (UNITY_INTERFACE_API * MetalBundle)();
id<MTLDevice>(UNITY_INTERFACE_API * MetalDevice)();

id<MTLCommandBuffer>(UNITY_INTERFACE_API * CurrentCommandBuffer)();

// for custom rendering support there are two scenarios:
// you want to use current in-flight MTLCommandEncoder (NB: it might be nil)
id<MTLCommandEncoder>(UNITY_INTERFACE_API * CurrentCommandEncoder)();
// or you might want to create your own encoder.
// In that case you should end unity's encoder before creating your own and end yours before returning control to unity
void(UNITY_INTERFACE_API * EndCurrentCommandEncoder)();

// returns MTLRenderPassDescriptor used to create current MTLCommandEncoder
MTLRenderPassDescriptor* (UNITY_INTERFACE_API * CurrentRenderPassDescriptor)();

// converting trampoline UnityRenderBufferHandle into native RenderBuffer
UnityRenderBuffer(UNITY_INTERFACE_API * RenderBufferFromHandle)(void* bufferHandle);

// access to RenderBuffer's texure
// NB: you pass here *native* RenderBuffer, acquired by calling (C#) RenderBuffer.GetNativeRenderBufferPtr
// AAResolvedTextureFromRenderBuffer will return nil in case of non-AA RenderBuffer or if called for depth RenderBuffer
// StencilTextureFromRenderBuffer will return nil in case of no-stencil RenderBuffer or if called for color RenderBuffer
id<MTLTexture>(UNITY_INTERFACE_API * TextureFromRenderBuffer)(UnityRenderBuffer buffer);
id<MTLTexture>(UNITY_INTERFACE_API * AAResolvedTextureFromRenderBuffer)(UnityRenderBuffer buffer);
id<MTLTexture>(UNITY_INTERFACE_API * StencilTextureFromRenderBuffer)(UnityRenderBuffer buffer);
};
UNITY_REGISTER_INTERFACE_GUID(0x29F8F3D03833465EULL, 0x92138551C15D823DULL, IUnityGraphicsMetalV1)


// deprecated: please use versioned interface above

UNITY_DECLARE_INTERFACE(IUnityGraphicsMetal)
{
NSBundle* (UNITY_INTERFACE_API * MetalBundle)();
id<MTLDevice>(UNITY_INTERFACE_API * MetalDevice)();

id<MTLCommandBuffer>(UNITY_INTERFACE_API * CurrentCommandBuffer)();
id<MTLCommandEncoder>(UNITY_INTERFACE_API * CurrentCommandEncoder)();
void(UNITY_INTERFACE_API * EndCurrentCommandEncoder)();
MTLRenderPassDescriptor* (UNITY_INTERFACE_API * CurrentRenderPassDescriptor)();

UnityRenderBuffer(UNITY_INTERFACE_API * RenderBufferFromHandle)(void* bufferHandle);

id<MTLTexture>(UNITY_INTERFACE_API * TextureFromRenderBuffer)(UnityRenderBuffer buffer);
id<MTLTexture>(UNITY_INTERFACE_API * AAResolvedTextureFromRenderBuffer)(UnityRenderBuffer buffer);
id<MTLTexture>(UNITY_INTERFACE_API * StencilTextureFromRenderBuffer)(UnityRenderBuffer buffer);
};
UNITY_REGISTER_INTERFACE_GUID(0x992C8EAEA95811E5ULL, 0x9A62C4B5B9876117ULL, IUnityGraphicsMetal)
81 changes: 10 additions & 71 deletions src/unity_camera_capturer.cpp
Expand Up @@ -16,61 +16,17 @@ rtc::scoped_refptr<UnityCameraCapturer> UnityCameraCapturer::Create(
}

void UnityCameraCapturer::OnRender() {
#ifdef _WIN32
D3D11_MAPPED_SUBRESOURCE resource;

auto dc = context_->GetDeviceContext();
if (dc == nullptr) {
RTC_LOG(LS_ERROR) << "ID3D11DeviceContext is null";
auto i420_buffer = capturer_->Capture();
if (!i420_buffer) {
return;
}

// ピクセルデータが取れない(と思う)ので、カメラテクスチャから自前のテクスチャにコピーする
dc->CopyResource((ID3D11Texture2D*)frame_texture_,
(ID3D11Resource*)camera_texture_);

HRESULT hr =
dc->Map((ID3D11Resource*)frame_texture_, 0, D3D11_MAP_READ, 0, &resource);
if (!SUCCEEDED(hr)) {
RTC_LOG(LS_ERROR) << "ID3D11DeviceContext::Map is failed: hr=" << hr;
return;
}

// Windows の場合は座標系の関係で上下反転してるので、頑張って元の向きに戻す
// TODO(melpon): Graphics.Blit を使って効率よく反転させる
// (ref: https://github.com/Unity-Technologies/com.unity.webrtc/blob/a526753e0c18d681d20f3eb9878b9c28442e2bfb/Runtime/Scripts/WebRTC.cs#L264-L268)
std::unique_ptr<uint8_t> buf(new uint8_t[width_ * height_ * 4]);
for (int i = 0; i < height_; i++) {
std::memcpy(buf.get() + width_ * 4 * i,
((const uint8_t*)resource.pData) +
resource.RowPitch * (height_ - i - 1),
width_ * 4);
}

// I420 に変換して VideoFrame 作って OnFrame 呼び出し
//RTC_LOG(LS_INFO) << "GOT FRAME: pData=0x" << resource.pData
// << " RowPitch=" << resource.RowPitch
// << " DepthPitch=" << resource.DepthPitch;
rtc::scoped_refptr<webrtc::I420Buffer> i420_buffer =
webrtc::I420Buffer::Create(width_, height_);
//libyuv::ARGBToI420((const uint8_t*)resource.pData, resource.RowPitch,
// i420_buffer->MutableDataY(), i420_buffer->StrideY(),
// i420_buffer->MutableDataU(), i420_buffer->StrideU(),
// i420_buffer->MutableDataV(), i420_buffer->StrideV(),
// width_, height_);
libyuv::ARGBToI420(buf.get(), width_ * 4, i420_buffer->MutableDataY(),
i420_buffer->StrideY(), i420_buffer->MutableDataU(),
i420_buffer->StrideU(), i420_buffer->MutableDataV(),
i420_buffer->StrideV(), width_, height_);
auto video_frame = webrtc::VideoFrame::Builder()
.set_video_frame_buffer(i420_buffer)
.set_rotation(webrtc::kVideoRotation_0)
.set_timestamp_us(clock_->TimeInMicroseconds())
.build();
this->OnFrame(video_frame);

dc->Unmap((ID3D11Resource*)frame_texture_, 0);
#endif
}

void UnityCameraCapturer::OnFrame(const webrtc::VideoFrame& frame) {
Expand All @@ -81,37 +37,20 @@ bool UnityCameraCapturer::Init(UnityContext* context,
void* unity_camera_texture,
int width,
int height) {
context_ = context;
camera_texture_ = unity_camera_texture;
width_ = width;
height_ = height;

#ifdef _WIN32
auto device = context->GetDevice();
if (device == nullptr) {
#ifdef WIN32
capturer_.reset(new D3D11Impl());
if (!capturer_->Init(context, unity_camera_texture, width, height)) {
return false;
}
#endif

// ピクセルデータにアクセスする用のテクスチャを用意する
ID3D11Texture2D* texture = nullptr;
D3D11_TEXTURE2D_DESC desc = {0};
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
HRESULT hr = device->CreateTexture2D(&desc, NULL, &texture);
if (!SUCCEEDED(hr)) {
RTC_LOG(LS_ERROR) << "ID3D11Device::CreateTexture2D is failed: hr=" << hr;
#ifdef __APPLE__
capturer_.reset(new MetalImpl());
if (!capturer_->Init(context, unity_camera_texture, width, height)) {
return false;
}

frame_texture_ = texture;
#endif

return true;
}

Expand Down
41 changes: 36 additions & 5 deletions src/unity_camera_capturer.h
Expand Up @@ -18,13 +18,44 @@ namespace sora {

class UnityCameraCapturer : public sora::ScalableVideoTrackSource,
public rtc::VideoSinkInterface<webrtc::VideoFrame> {
UnityContext* context_;
void* camera_texture_;
void* frame_texture_;
int width_;
int height_;
webrtc::Clock* clock_ = webrtc::Clock::GetRealTimeClock();

#ifdef WIN32
class D3D11Impl {
UnityContext* context_;
void* camera_texture_;
void* frame_texture_;
int width_;
int height_;

public:
bool Init(UnityContext* context,
void* camera_texture,
int width,
int height);
rtc::scoped_refptr<webrtc::I420Buffer> Capture();
};
std::unique_ptr<D3D11Impl> capturer_;
#endif

#ifdef __APPLE__
class MetalImpl {
UnityContext* context_;
void* camera_texture_;
void* frame_texture_;
int width_;
int height_;

public:
bool Init(UnityContext* context,
void* camera_texture,
int width,
int height);
rtc::scoped_refptr<webrtc::I420Buffer> Capture();
};
std::unique_ptr<MetalImpl> capturer_;
#endif

public:
static rtc::scoped_refptr<UnityCameraCapturer> Create(
UnityContext* context,
Expand Down
94 changes: 94 additions & 0 deletions src/unity_camera_capturer_d3d11.cpp
@@ -0,0 +1,94 @@
#include "unity_camera_capturer.h"

namespace sora {

bool UnityCameraCapturer::D3D11Impl::Init(UnityContext* context,
void* camera_texture,
int width,
int height) {
context_ = context;
camera_texture_ = camera_texture;
width_ = width;
height_ = height;

auto device = context->GetDevice();
if (device == nullptr) {
return false;
}

// ピクセルデータにアクセスする用のテクスチャを用意する
ID3D11Texture2D* texture = nullptr;
D3D11_TEXTURE2D_DESC desc = {0};
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
HRESULT hr = device->CreateTexture2D(&desc, NULL, &texture);
if (!SUCCEEDED(hr)) {
RTC_LOG(LS_ERROR) << "ID3D11Device::CreateTexture2D is failed: hr=" << hr;
return false;
}

frame_texture_ = texture;
return true;
}

rtc::scoped_refptr<webrtc::I420Buffer>
UnityCameraCapturer::D3D11Impl::Capture() {
D3D11_MAPPED_SUBRESOURCE resource;

auto dc = context_->GetDeviceContext();
if (dc == nullptr) {
RTC_LOG(LS_ERROR) << "ID3D11DeviceContext is null";
return;
}

// ピクセルデータが取れない(と思う)ので、カメラテクスチャから自前のテクスチャにコピーする
dc->CopyResource((ID3D11Texture2D*)frame_texture_,
(ID3D11Resource*)camera_texture_);

HRESULT hr =
dc->Map((ID3D11Resource*)frame_texture_, 0, D3D11_MAP_READ, 0, &resource);
if (!SUCCEEDED(hr)) {
RTC_LOG(LS_ERROR) << "ID3D11DeviceContext::Map is failed: hr=" << hr;
return;
}

// Windows の場合は座標系の関係で上下反転してるので、頑張って元の向きに戻す
// TODO(melpon): Graphics.Blit を使って効率よく反転させる
// (ref: https://github.com/Unity-Technologies/com.unity.webrtc/blob/a526753e0c18d681d20f3eb9878b9c28442e2bfb/Runtime/Scripts/WebRTC.cs#L264-L268)
std::unique_ptr<uint8_t[]> buf(new uint8_t[width_ * height_ * 4]);
for (int i = 0; i < height_; i++) {
std::memcpy(buf.get() + width_ * 4 * i,
((const uint8_t*)resource.pData) +
resource.RowPitch * (height_ - i - 1),
width_ * 4);
}

// I420 に変換して VideoFrame 作って OnFrame 呼び出し
//RTC_LOG(LS_INFO) << "GOT FRAME: pData=0x" << resource.pData
// << " RowPitch=" << resource.RowPitch
// << " DepthPitch=" << resource.DepthPitch;
rtc::scoped_refptr<webrtc::I420Buffer> i420_buffer =
webrtc::I420Buffer::Create(width_, height_);
//libyuv::ARGBToI420((const uint8_t*)resource.pData, resource.RowPitch,
// i420_buffer->MutableDataY(), i420_buffer->StrideY(),
// i420_buffer->MutableDataU(), i420_buffer->StrideU(),
// i420_buffer->MutableDataV(), i420_buffer->StrideV(),
// width_, height_);
libyuv::ARGBToI420(buf.get(), width_ * 4, i420_buffer->MutableDataY(),
i420_buffer->StrideY(), i420_buffer->MutableDataU(),
i420_buffer->StrideU(), i420_buffer->MutableDataV(),
i420_buffer->StrideV(), width_, height_);

dc->Unmap((ID3D11Resource*)frame_texture_, 0);

return i420_buffer;
}

} // namespace sora

0 comments on commit f1293e0

Please sign in to comment.