0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-19 20:32:15 +02:00

decklink: Add HDR playback support

This commit is contained in:
jpark37 2023-04-01 17:24:14 -07:00 committed by Lain
parent 40edda536d
commit bb12fe9db5
10 changed files with 275 additions and 16 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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")

View File

@ -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)"

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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);