mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-09-20 04:42:18 +02:00
decklink: Add HDR playback support
This commit is contained in:
parent
40edda536d
commit
bb12fe9db5
@ -116,9 +116,6 @@ void output_start()
|
||||
|
||||
context.stage_index = 0;
|
||||
|
||||
const video_output_info *mainVOI =
|
||||
video_output_get_info(obs_get_video());
|
||||
|
||||
video_output_info vi = {0};
|
||||
vi.format = VIDEO_FORMAT_BGRA;
|
||||
vi.width = width;
|
||||
@ -126,7 +123,7 @@ void output_start()
|
||||
vi.fps_den = context.ovi.fps_den;
|
||||
vi.fps_num = context.ovi.fps_num;
|
||||
vi.cache_size = 16;
|
||||
vi.colorspace = mainVOI->colorspace;
|
||||
vi.colorspace = VIDEO_CS_DEFAULT;
|
||||
vi.range = VIDEO_RANGE_FULL;
|
||||
vi.name = "decklink_output";
|
||||
|
||||
@ -253,9 +250,6 @@ void preview_output_start()
|
||||
|
||||
context.stage_index = 0;
|
||||
|
||||
const video_output_info *mainVOI =
|
||||
video_output_get_info(obs_get_video());
|
||||
|
||||
video_output_info vi = {0};
|
||||
vi.format = VIDEO_FORMAT_BGRA;
|
||||
vi.width = width;
|
||||
@ -263,7 +257,7 @@ void preview_output_start()
|
||||
vi.fps_den = context.ovi.fps_den;
|
||||
vi.fps_num = context.ovi.fps_num;
|
||||
vi.cache_size = 16;
|
||||
vi.colorspace = mainVOI->colorspace;
|
||||
vi.colorspace = VIDEO_CS_DEFAULT;
|
||||
vi.range = VIDEO_RANGE_FULL;
|
||||
vi.name = "decklink_preview_output";
|
||||
|
||||
@ -386,13 +380,24 @@ static void decklink_ui_render(void *param)
|
||||
return;
|
||||
|
||||
const bool previous = gs_framebuffer_srgb_enabled();
|
||||
gs_enable_framebuffer_srgb(true);
|
||||
const bool source_hdr = (context.ovi.colorspace == VIDEO_CS_2100_PQ) ||
|
||||
(context.ovi.colorspace == VIDEO_CS_2100_HLG);
|
||||
const bool target_hdr = source_hdr &&
|
||||
(conversion->colorspace == VIDEO_CS_2100_PQ);
|
||||
gs_enable_framebuffer_srgb(!target_hdr);
|
||||
gs_enable_blending(false);
|
||||
|
||||
gs_effect_t *const effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
|
||||
gs_effect_set_texture_srgb(gs_effect_get_param_by_name(effect, "image"),
|
||||
tex);
|
||||
while (gs_effect_loop(effect, "DrawAlphaDivide")) {
|
||||
const char *const tech_name =
|
||||
target_hdr ? "DrawAlphaDivideR10L"
|
||||
: (source_hdr ? "DrawAlphaDivideTonemap"
|
||||
: "DrawAlphaDivide");
|
||||
while (gs_effect_loop(effect, tech_name)) {
|
||||
gs_effect_set_float(gs_effect_get_param_by_name(effect,
|
||||
"multiplier"),
|
||||
obs_get_video_sdr_white_level() / 10000.f);
|
||||
gs_draw_sprite(tex, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ public:
|
||||
size_t audio_planes;
|
||||
size_t audio_size;
|
||||
int keyerMode;
|
||||
bool force_sdr;
|
||||
|
||||
DeckLinkOutput(obs_output_t *output,
|
||||
DeckLinkDeviceDiscovery *discovery);
|
||||
|
@ -89,3 +89,152 @@ HRESULT OBSVideoFrame::GetBytes(void **buffer)
|
||||
*buffer = this->data;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#define CompareREFIID(iid1, iid2) (memcmp(&iid1, &iid2, sizeof(REFIID)) == 0)
|
||||
|
||||
HDRVideoFrame::HDRVideoFrame(IDeckLinkMutableVideoFrame *frame)
|
||||
: m_videoFrame(frame),
|
||||
m_refCount(1)
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT HDRVideoFrame::QueryInterface(REFIID iid, LPVOID *ppv)
|
||||
{
|
||||
if (ppv == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
CFUUIDBytes unknown = CFUUIDGetUUIDBytes(IUnknownUUID);
|
||||
if (CompareREFIID(iid, unknown))
|
||||
*ppv = static_cast<IDeckLinkVideoFrame *>(this);
|
||||
else if (CompareREFIID(iid, IID_IDeckLinkVideoFrame))
|
||||
*ppv = static_cast<IDeckLinkVideoFrame *>(this);
|
||||
else if (CompareREFIID(iid, IID_IDeckLinkVideoFrameMetadataExtensions))
|
||||
*ppv = static_cast<IDeckLinkVideoFrameMetadataExtensions *>(
|
||||
this);
|
||||
else {
|
||||
*ppv = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG HDRVideoFrame::AddRef(void)
|
||||
{
|
||||
return ++m_refCount;
|
||||
}
|
||||
|
||||
ULONG HDRVideoFrame::Release(void)
|
||||
{
|
||||
ULONG newRefValue = --m_refCount;
|
||||
|
||||
if (newRefValue == 0)
|
||||
delete this;
|
||||
|
||||
return newRefValue;
|
||||
}
|
||||
|
||||
HRESULT HDRVideoFrame::GetInt(BMDDeckLinkFrameMetadataID metadataID,
|
||||
int64_t *value)
|
||||
{
|
||||
HRESULT result = S_OK;
|
||||
|
||||
switch (metadataID) {
|
||||
case bmdDeckLinkFrameMetadataHDRElectroOpticalTransferFunc:
|
||||
*value = 2;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataColorspace:
|
||||
// Colorspace is fixed for this sample
|
||||
*value = bmdColorspaceRec2020;
|
||||
break;
|
||||
|
||||
default:
|
||||
result = E_INVALIDARG;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HRESULT HDRVideoFrame::GetFloat(BMDDeckLinkFrameMetadataID metadataID,
|
||||
double *value)
|
||||
{
|
||||
HRESULT result = S_OK;
|
||||
|
||||
switch (metadataID) {
|
||||
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedX:
|
||||
*value = 0.708;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedY:
|
||||
*value = 0.292;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenX:
|
||||
*value = 0.17;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenY:
|
||||
*value = 0.797;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueX:
|
||||
*value = 0.131;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueY:
|
||||
*value = 0.046;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRWhitePointX:
|
||||
*value = 0.3127;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRWhitePointY:
|
||||
*value = 0.329;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRMaxDisplayMasteringLuminance:
|
||||
*value = obs_get_video_hdr_nominal_peak_level();
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRMinDisplayMasteringLuminance:
|
||||
*value = 0.00001;
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRMaximumContentLightLevel:
|
||||
*value = obs_get_video_hdr_nominal_peak_level();
|
||||
break;
|
||||
|
||||
case bmdDeckLinkFrameMetadataHDRMaximumFrameAverageLightLevel:
|
||||
*value = obs_get_video_hdr_nominal_peak_level();
|
||||
break;
|
||||
|
||||
default:
|
||||
result = E_INVALIDARG;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HRESULT HDRVideoFrame::GetFlag(BMDDeckLinkFrameMetadataID, decklink_bool_t *)
|
||||
{
|
||||
// Not expecting GetFlag
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
HRESULT HDRVideoFrame::GetString(BMDDeckLinkFrameMetadataID,
|
||||
decklink_string_t *)
|
||||
{
|
||||
// Not expecting GetString
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
HRESULT HDRVideoFrame::GetBytes(BMDDeckLinkFrameMetadataID, void *,
|
||||
uint32_t *bufferSize)
|
||||
{
|
||||
*bufferSize = 0;
|
||||
// Not expecting GetBytes
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "platform.hpp"
|
||||
#include "obs.hpp"
|
||||
#include <atomic>
|
||||
|
||||
class OBSVideoFrame : public IDeckLinkMutableVideoFrame {
|
||||
private:
|
||||
@ -75,3 +76,70 @@ public:
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() override { return 1; }
|
||||
virtual ULONG STDMETHODCALLTYPE Release() override { return 1; }
|
||||
};
|
||||
|
||||
class HDRVideoFrame : public IDeckLinkVideoFrame,
|
||||
public IDeckLinkVideoFrameMetadataExtensions {
|
||||
public:
|
||||
HDRVideoFrame(IDeckLinkMutableVideoFrame *frame);
|
||||
virtual ~HDRVideoFrame() {}
|
||||
|
||||
// IUnknown interface
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,
|
||||
LPVOID *ppv);
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef(void);
|
||||
virtual ULONG STDMETHODCALLTYPE Release(void);
|
||||
|
||||
// IDeckLinkVideoFrame interface
|
||||
virtual long STDMETHODCALLTYPE GetWidth(void)
|
||||
{
|
||||
return m_videoFrame->GetWidth();
|
||||
}
|
||||
virtual long STDMETHODCALLTYPE GetHeight(void)
|
||||
{
|
||||
return m_videoFrame->GetHeight();
|
||||
}
|
||||
virtual long STDMETHODCALLTYPE GetRowBytes(void)
|
||||
{
|
||||
return m_videoFrame->GetRowBytes();
|
||||
}
|
||||
virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat(void)
|
||||
{
|
||||
return m_videoFrame->GetPixelFormat();
|
||||
}
|
||||
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags(void)
|
||||
{
|
||||
return m_videoFrame->GetFlags() | bmdFrameContainsHDRMetadata;
|
||||
}
|
||||
virtual HRESULT STDMETHODCALLTYPE GetBytes(void **buffer)
|
||||
{
|
||||
return m_videoFrame->GetBytes(buffer);
|
||||
}
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode **timecode)
|
||||
{
|
||||
return m_videoFrame->GetTimecode(format, timecode);
|
||||
}
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetAncillaryData(IDeckLinkVideoFrameAncillary **ancillary)
|
||||
{
|
||||
return m_videoFrame->GetAncillaryData(ancillary);
|
||||
}
|
||||
|
||||
// IDeckLinkVideoFrameMetadataExtensions interface
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetInt(BMDDeckLinkFrameMetadataID metadataID, int64_t *value);
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetFloat(BMDDeckLinkFrameMetadataID metadataID, double *value);
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetFlag(BMDDeckLinkFrameMetadataID metadataID, decklink_bool_t *value);
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetString(BMDDeckLinkFrameMetadataID metadataID,
|
||||
decklink_string_t *value);
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetBytes(BMDDeckLinkFrameMetadataID metadataID, void *buffer,
|
||||
uint32_t *bufferSize);
|
||||
|
||||
private:
|
||||
ComPtr<IDeckLinkMutableVideoFrame> m_videoFrame;
|
||||
std::atomic<ULONG> m_refCount;
|
||||
};
|
||||
|
@ -11,6 +11,7 @@
|
||||
#define BUFFERING "buffering"
|
||||
#define DEACTIVATE_WNS "deactivate_when_not_showing"
|
||||
#define AUTO_START "auto_start"
|
||||
#define FORCE_SDR "force_sdr"
|
||||
#define KEYER "keyer"
|
||||
#define SWAP "swap"
|
||||
#define ALLOW_10_BIT "allow_10_bit"
|
||||
@ -37,6 +38,7 @@
|
||||
#define TEXT_BUFFERING obs_module_text("Buffering")
|
||||
#define TEXT_DWNS obs_module_text("DeactivateWhenNotShowing")
|
||||
#define TEXT_AUTO_START obs_module_text("AutoStart")
|
||||
#define TEXT_FORCE_SDR obs_module_text("ForceSDR")
|
||||
#define TEXT_ENABLE_KEYER obs_module_text("Keyer")
|
||||
#define TEXT_SWAP obs_module_text("SwapFC-LFE")
|
||||
#define TEXT_SWAP_TOOLTIP obs_module_text("SwapFC-LFE.Tooltip")
|
||||
|
@ -19,8 +19,9 @@ ChannelFormat.5_1ch="5.1ch"
|
||||
ChannelFormat.7_1ch="7.1ch"
|
||||
DeactivateWhenNotShowing="Deactivate when not showing"
|
||||
AutoStart="Auto start on launch"
|
||||
ForceSDR="Force SDR"
|
||||
SwapFC-LFE="Swap FC and LFE"
|
||||
SwapFC-LFE.Tooltip="Swap Front Center Channel and LFE Channel"
|
||||
VideoConnection="Video Connection"
|
||||
AudioConnection="Audio Connection"
|
||||
Allow10Bit="Allow 10 Bit (Required for SDI captions, may cause performance overhead)"
|
||||
Allow10Bit="Allow 10 Bit (Required for SDI captions, may cause performance overhead)"
|
||||
|
@ -626,13 +626,24 @@ bool DeckLinkDeviceInstance::StartOutput(DeckLinkDeviceMode *mode_)
|
||||
}
|
||||
activeBlob = nullptr;
|
||||
|
||||
struct obs_video_info ovi;
|
||||
const enum video_colorspace colorspace =
|
||||
obs_get_video_info(&ovi) ? ovi.colorspace : VIDEO_CS_DEFAULT;
|
||||
const bool source_hdr = (colorspace == VIDEO_CS_2100_PQ) ||
|
||||
(colorspace == VIDEO_CS_2100_HLG);
|
||||
const bool enable_hdr =
|
||||
source_hdr &&
|
||||
(obs_output_get_video_conversion(decklinkOutput->GetOutput())
|
||||
->colorspace == VIDEO_CS_2100_PQ);
|
||||
BMDPixelFormat pixelFormat = enable_hdr ? bmdFormat10BitRGBXLE
|
||||
: bmdFormat8BitBGRA;
|
||||
const int64_t minimumPrerollFrames =
|
||||
std::max(device->GetMinimumPrerollFrames(), INT64_C(3));
|
||||
for (int64_t i = 0; i < minimumPrerollFrames; ++i) {
|
||||
ComPtr<IDeckLinkMutableVideoFrame> decklinkOutputFrame;
|
||||
HRESULT result = output_->CreateVideoFrame(
|
||||
decklinkOutput->GetWidth(), decklinkOutput->GetHeight(),
|
||||
rowSize, bmdFormat8BitBGRA, bmdFrameFlagDefault,
|
||||
rowSize, pixelFormat, bmdFrameFlagDefault,
|
||||
&decklinkOutputFrame);
|
||||
if (result != S_OK) {
|
||||
blog(LOG_ERROR, "failed to create video frame 0x%X",
|
||||
@ -640,7 +651,15 @@ bool DeckLinkDeviceInstance::StartOutput(DeckLinkDeviceMode *mode_)
|
||||
return false;
|
||||
}
|
||||
|
||||
result = output_->ScheduleVideoFrame(decklinkOutputFrame,
|
||||
IDeckLinkVideoFrame *theFrame = decklinkOutputFrame.Get();
|
||||
ComPtr<HDRVideoFrame> decklinkOutputHDRFrame;
|
||||
if (enable_hdr) {
|
||||
*decklinkOutputHDRFrame.Assign() =
|
||||
new HDRVideoFrame(decklinkOutputFrame);
|
||||
theFrame = decklinkOutputHDRFrame.Get();
|
||||
}
|
||||
|
||||
result = output_->ScheduleVideoFrame(theFrame,
|
||||
i * frameDuration,
|
||||
frameDuration,
|
||||
frameTimescale);
|
||||
|
@ -110,6 +110,9 @@ bool DeckLinkDevice::Init()
|
||||
attributes->GetFlag(BMDDeckLinkSupportsInternalKeying,
|
||||
&supportsInternalKeyer);
|
||||
|
||||
attributes->GetFlag(BMDDeckLinkSupportsHDRMetadata,
|
||||
&supportsHDRMetadata);
|
||||
|
||||
// Sub Device Counts
|
||||
attributes->GetInt(BMDDeckLinkSubDeviceIndex, &subDeviceIndex);
|
||||
attributes->GetInt(BMDDeckLinkNumberOfSubDevices, &numSubDevices);
|
||||
@ -250,6 +253,11 @@ bool DeckLinkDevice::GetSupportsInternalKeyer(void) const
|
||||
return supportsInternalKeyer;
|
||||
}
|
||||
|
||||
bool DeckLinkDevice::GetSupportsHDRMetadata(void) const
|
||||
{
|
||||
return supportsHDRMetadata;
|
||||
}
|
||||
|
||||
int64_t DeckLinkDevice::GetSubDeviceCount()
|
||||
{
|
||||
return numSubDevices;
|
||||
|
@ -19,6 +19,7 @@ class DeckLinkDevice {
|
||||
int32_t maxChannel = 0;
|
||||
decklink_bool_t supportsExternalKeyer = false;
|
||||
decklink_bool_t supportsInternalKeyer = false;
|
||||
decklink_bool_t supportsHDRMetadata = false;
|
||||
int64_t subDeviceIndex = 0;
|
||||
int64_t numSubDevices = 0;
|
||||
int64_t minimumPrerollFrames = 3;
|
||||
@ -48,6 +49,7 @@ public:
|
||||
int64_t GetAudioInputConnections();
|
||||
bool GetSupportsExternalKeyer(void) const;
|
||||
bool GetSupportsInternalKeyer(void) const;
|
||||
bool GetSupportsHDRMetadata(void) const;
|
||||
int64_t GetSubDeviceCount();
|
||||
int64_t GetSubDeviceIndex();
|
||||
int64_t GetMinimumPrerollFrames();
|
||||
|
@ -24,6 +24,7 @@ static void *decklink_output_create(obs_data_t *settings, obs_output_t *output)
|
||||
decklinkOutput->deviceHash = obs_data_get_string(settings, DEVICE_HASH);
|
||||
decklinkOutput->modeID = obs_data_get_int(settings, MODE_ID);
|
||||
decklinkOutput->keyerMode = (int)obs_data_get_int(settings, KEYER);
|
||||
decklinkOutput->force_sdr = obs_data_get_bool(settings, FORCE_SDR);
|
||||
|
||||
ComPtr<DeckLinkDevice> device;
|
||||
device.Set(deviceEnum->FindByHash(decklinkOutput->deviceHash));
|
||||
@ -36,9 +37,10 @@ static void *decklink_output_create(obs_data_t *settings, obs_output_t *output)
|
||||
to.width = mode->GetWidth();
|
||||
to.height = mode->GetHeight();
|
||||
to.range = VIDEO_RANGE_FULL;
|
||||
struct obs_video_info ovi;
|
||||
to.colorspace = obs_get_video_info(&ovi) ? ovi.colorspace
|
||||
: VIDEO_CS_DEFAULT;
|
||||
to.colorspace = (device->GetSupportsHDRMetadata() &&
|
||||
!decklinkOutput->force_sdr)
|
||||
? VIDEO_CS_2100_PQ
|
||||
: VIDEO_CS_709;
|
||||
|
||||
obs_output_set_video_conversion(output, &to);
|
||||
}
|
||||
@ -53,6 +55,7 @@ static void decklink_output_update(void *data, obs_data_t *settings)
|
||||
decklink->deviceHash = obs_data_get_string(settings, DEVICE_HASH);
|
||||
decklink->modeID = obs_data_get_int(settings, MODE_ID);
|
||||
decklink->keyerMode = (int)obs_data_get_int(settings, KEYER);
|
||||
decklink->force_sdr = obs_data_get_bool(settings, FORCE_SDR);
|
||||
}
|
||||
|
||||
static bool decklink_output_start(void *data)
|
||||
@ -272,6 +275,7 @@ static obs_properties_t *decklink_output_properties(void *unused)
|
||||
OBS_COMBO_FORMAT_INT);
|
||||
|
||||
obs_properties_add_bool(props, AUTO_START, TEXT_AUTO_START);
|
||||
obs_properties_add_bool(props, FORCE_SDR, TEXT_FORCE_SDR);
|
||||
|
||||
obs_properties_add_list(props, KEYER, TEXT_ENABLE_KEYER,
|
||||
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
||||
|
Loading…
Reference in New Issue
Block a user