decklink: Add option to select channel format

Closes jp9000/obs-studio#867
This commit is contained in:
mntone 2017-03-31 13:03:38 +09:00 committed by jp9000
parent 698fd00174
commit c459ade360
14 changed files with 383 additions and 49 deletions

View File

@ -0,0 +1,114 @@
#include "audio-repack.h"
#include <emmintrin.h>
int check_buffer(struct audio_repack *repack,
uint32_t frame_count)
{
const uint32_t new_size = frame_count * repack->base_dst_size
+ repack->extra_dst_size;
if (repack->packet_size < new_size) {
repack->packet_buffer = brealloc(
repack->packet_buffer, new_size);
if (!repack->packet_buffer)
return -1;
repack->packet_size = new_size;
}
return 0;
}
/*
Swap channel between LFE and FC, and
squash data array
| FL | FR |LFE | FC | BL | BR |emp |emp |
| | x | |
| FL | FR | FC |LFE | BL | BR |
*/
int repack_8to6ch_swap23(struct audio_repack *repack,
const uint8_t *bsrc, uint32_t frame_count)
{
if (check_buffer(repack, frame_count) < 0)
return -1;
const uint32_t size = frame_count * repack->base_src_size;
const __m128i *src = (__m128i *)bsrc;
const __m128i *esrc = src + frame_count;
uint32_t *dst = (uint32_t *)repack->packet_buffer;
while (src != esrc) {
__m128i target = _mm_load_si128(src++);
__m128i buf = _mm_shufflelo_epi16(target, _MM_SHUFFLE(2, 3, 1, 0));
_mm_storeu_si128((__m128i *)dst, buf);
dst += 3;
}
return 0;
}
/*
Swap channel between LFE and FC
| FL | FR |LFE | FC | BL | BR |SBL |SBR |
| | x | | | |
| FL | FR | FC |LFE | BL | BR |SBL |SBR |
*/
int repack_8ch_swap23(struct audio_repack *repack,
const uint8_t *bsrc, uint32_t frame_count)
{
if (check_buffer(repack, frame_count) < 0)
return -1;
const uint32_t size = frame_count * repack->base_src_size;
const __m128i *src = (__m128i *)bsrc;
const __m128i *esrc = src + frame_count;
__m128i *dst = (__m128i *)repack->packet_buffer;
while (src != esrc) {
__m128i target = _mm_load_si128(src++);
__m128i buf = _mm_shufflelo_epi16(target, _MM_SHUFFLE(2, 3, 1, 0));
_mm_store_si128(dst++, buf);
}
return 0;
}
int audio_repack_init(struct audio_repack *repack,
audio_repack_mode_t repack_mode, uint8_t sample_bit)
{
memset(repack, 0, sizeof(*repack));
if (sample_bit != 16)
return -1;
switch (repack_mode) {
case repack_mode_8to6ch_swap23:
repack->base_src_size = 8 * (16 / 8);
repack->base_dst_size = 6 * (16 / 8);
repack->extra_dst_size = 2;
repack->repack_func = &repack_8to6ch_swap23;
break;
case repack_mode_8ch_swap23:
repack->base_src_size = 8 * (16 / 8);
repack->base_dst_size = 8 * (16 / 8);
repack->extra_dst_size = 0;
repack->repack_func = &repack_8ch_swap23;
break;
default: return -1;
}
return 0;
}
void audio_repack_free(struct audio_repack *repack)
{
if (repack->packet_buffer)
bfree(repack->packet_buffer);
memset(repack, 0, sizeof(*repack));
}

View File

@ -0,0 +1,41 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <string.h>
#include <obs.h>
struct audio_repack;
typedef int (*audio_repack_func_t)(struct audio_repack *,
const uint8_t *, uint32_t);
struct audio_repack {
uint8_t *packet_buffer;
uint32_t packet_size;
uint32_t base_src_size;
uint32_t base_dst_size;
uint32_t extra_dst_size;
audio_repack_func_t repack_func;
};
enum _audio_repack_mode {
repack_mode_8to6ch_swap23,
repack_mode_8ch_swap23,
};
typedef enum _audio_repack_mode audio_repack_mode_t;
extern int audio_repack_init(struct audio_repack *repack,
audio_repack_mode_t repack_mode, uint8_t sample_bit);
extern void audio_repack_free(struct audio_repack *repack);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,25 @@
#pragma once
#include "audio-repack.h"
class AudioRepacker {
struct audio_repack arepack;
public:
inline AudioRepacker(audio_repack_mode_t repack_mode)
{
audio_repack_init(&arepack, repack_mode, 16);
}
inline ~AudioRepacker()
{
audio_repack_free(&arepack);
}
inline int repack(const uint8_t *src, uint32_t frame_size)
{
return (*arepack.repack_func)(&arepack, src, frame_size);
}
inline operator struct audio_repack*() {return &arepack;}
inline struct audio_repack *operator->() {return &arepack;}
};

View File

@ -3,3 +3,9 @@ Device="Device"
Mode="Mode"
Buffering="Use Buffering"
PixelFormat="Pixel Format"
ChannelFormat="Channel"
ChannelFormat.None="None"
ChannelFormat.2_0ch="2ch"
ChannelFormat.5_1ch="5.1ch"
ChannelFormat.5_1chBack="5.1ch (Back)"
ChannelFormat.7_1ch="7.1ch"

View File

@ -1,4 +1,5 @@
#include "decklink-device-instance.hpp"
#include "audio-repack.hpp"
#include <util/platform.h>
#include <util/threading.h>
@ -8,6 +9,8 @@
#define LOG(level, message, ...) blog(level, "%s: " message, \
obs_source_get_name(this->decklink->GetSource()), ##__VA_ARGS__)
#define ISSTEREO(flag) ((flag) == SPEAKERS_STEREO)
static inline enum video_format ConvertPixelFormat(BMDPixelFormat format)
{
switch (format) {
@ -20,6 +23,36 @@ static inline enum video_format ConvertPixelFormat(BMDPixelFormat format)
return VIDEO_FORMAT_UYVY;
}
static inline int ConvertChannelFormat(speaker_layout format)
{
switch (format) {
case SPEAKERS_5POINT1:
case SPEAKERS_5POINT1_SURROUND:
case SPEAKERS_7POINT1:
return 8;
default:
case SPEAKERS_STEREO:
return 2;
}
}
static inline audio_repack_mode_t ConvertRepackFormat(speaker_layout format)
{
switch (format) {
case SPEAKERS_5POINT1:
case SPEAKERS_5POINT1_SURROUND:
return repack_mode_8to6ch_swap23;
case SPEAKERS_7POINT1:
return repack_mode_8ch_swap23;
default:
assert(false && "No repack requested");
return (audio_repack_mode_t)-1;
}
}
DeckLinkDeviceInstance::DeckLinkDeviceInstance(DeckLink *decklink_,
DeckLinkDevice *device_) :
currentFrame(), currentPacket(), decklink(decklink_), device(device_)
@ -46,10 +79,21 @@ void DeckLinkDeviceInstance::HandleAudioPacket(
return;
}
currentPacket.data[0] = (uint8_t *)bytes;
currentPacket.frames = (uint32_t)audioPacket->GetSampleFrameCount();
const uint32_t frameCount = (uint32_t)audioPacket->GetSampleFrameCount();
currentPacket.frames = frameCount;
currentPacket.timestamp = timestamp;
if (!ISSTEREO(channelFormat)) {
if (audioRepacker->repack((uint8_t *)bytes, frameCount) < 0) {
LOG(LOG_ERROR, "Failed to convert audio packet data");
return;
}
currentPacket.data[0] = (*audioRepacker)->packet_buffer;
} else {
currentPacket.data[0] = (uint8_t *)bytes;
}
obs_source_output_audio(decklink->GetSource(), &currentPacket);
}
@ -78,6 +122,19 @@ void DeckLinkDeviceInstance::HandleVideoFrame(
obs_source_output_video(decklink->GetSource(), &currentFrame);
}
void DeckLinkDeviceInstance::FinalizeStream()
{
input->SetCallback(nullptr);
if (audioRepacker != nullptr)
{
delete audioRepacker;
audioRepacker = nullptr;
}
mode = nullptr;
}
bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_)
{
if (mode != nullptr)
@ -93,8 +150,6 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_)
pixelFormat = decklink->GetPixelFormat();
currentFrame.format = ConvertPixelFormat(pixelFormat);
input->SetCallback(this);
const BMDDisplayMode displayMode = mode_->GetDisplayMode();
const HRESULT videoResult = input->EnableVideoInput(displayMode,
@ -102,22 +157,36 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_)
if (videoResult != S_OK) {
LOG(LOG_ERROR, "Failed to enable video input");
input->SetCallback(nullptr);
return false;
}
channelFormat = decklink->GetChannelFormat();
currentPacket.speakers = channelFormat;
if (channelFormat != SPEAKERS_UNKNOWN) {
const int channel = ConvertChannelFormat(channelFormat);
const HRESULT audioResult = input->EnableAudioInput(
bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger,
2);
channel);
if (audioResult != S_OK)
LOG(LOG_WARNING, "Failed to enable audio input; continuing...");
if (!ISSTEREO(channelFormat)) {
const audio_repack_mode_t repack_mode = ConvertRepackFormat(channelFormat);
audioRepacker = new AudioRepacker(repack_mode);
}
}
if (input->SetCallback(this) != S_OK) {
LOG(LOG_ERROR, "Failed to set callback");
FinalizeStream();
return false;
}
if (input->StartStreams() != S_OK) {
LOG(LOG_ERROR, "Failed to start streams");
input->SetCallback(nullptr);
input->DisableVideoInput();
input->DisableAudioInput();
FinalizeStream();
return false;
}
@ -135,11 +204,7 @@ bool DeckLinkDeviceInstance::StopCapture(void)
GetDevice()->GetDisplayName().c_str());
input->StopStreams();
input->SetCallback(nullptr);
input->DisableVideoInput();
input->DisableAudioInput();
mode = nullptr;
FinalizeStream();
return true;
}

View File

@ -2,6 +2,8 @@
#include "decklink-device.hpp"
class AudioRepacker;
class DeckLinkDeviceInstance : public IDeckLinkInputCallback {
protected:
struct obs_source_frame currentFrame;
@ -13,6 +15,11 @@ protected:
ComPtr<IDeckLinkInput> input;
volatile long refCount = 1;
AudioRepacker *audioRepacker = nullptr;
speaker_layout channelFormat = SPEAKERS_STEREO;
void FinalizeStream();
void HandleAudioPacket(IDeckLinkAudioInputPacket *audioPacket,
const uint64_t timestamp);
void HandleVideoFrame(IDeckLinkVideoInputFrame *videoFrame,
@ -29,6 +36,7 @@ public:
}
inline BMDPixelFormat GetActivePixelFormat() const {return pixelFormat;}
inline speaker_layout GetActiveChannelFormat() const {return channelFormat;}
inline DeckLinkDeviceMode *GetMode() const {return mode;}

View File

@ -72,6 +72,15 @@ bool DeckLinkDevice::Init()
if (result != S_OK)
return true;
int64_t channels;
/* Intensity Shuttle for Thunderbolt return 2; however, it supports 8 channels */
if (name == "Intensity Shuttle Thunderbolt")
maxChannel = 8;
else if (attributes->GetInt(BMDDeckLinkMaximumAudioChannels, &channels) == S_OK)
maxChannel = (int32_t)channels;
else
maxChannel = 2;
/* http://forum.blackmagicdesign.com/viewtopic.php?f=12&t=33967
* BMDDeckLinkTopologicalID for older devices
* BMDDeckLinkPersistentID for newer ones */
@ -118,3 +127,8 @@ const std::string& DeckLinkDevice::GetName(void) const
{
return name;
}
const int32_t DeckLinkDevice::GetMaxChannel(void) const
{
return maxChannel;
}

View File

@ -14,6 +14,7 @@ class DeckLinkDevice {
std::string name;
std::string displayName;
std::string hash;
int32_t maxChannel;
volatile long refCount = 1;
public:
@ -30,6 +31,7 @@ public:
const std::string& GetHash(void) const;
const std::vector<DeckLinkDeviceMode *>& GetModes(void) const;
const std::string& GetName(void) const;
const int32_t GetMaxChannel(void) const;
bool GetInput(IDeckLinkInput **input);

View File

@ -65,7 +65,8 @@ bool DeckLink::Activate(DeckLinkDevice *device, long long modeId)
if (!isActive)
return false;
if (instance->GetActiveModeId() == modeId &&
instance->GetActivePixelFormat() == pixelFormat)
instance->GetActivePixelFormat() == pixelFormat &&
instance->GetActiveChannelFormat() == channelFormat)
return false;
}

View File

@ -22,6 +22,7 @@ protected:
volatile long activateRefs = 0;
std::recursive_mutex deviceMutex;
BMDPixelFormat pixelFormat = bmdFormat8BitYUV;
speaker_layout channelFormat = SPEAKERS_STEREO;
void SaveSettings();
static void DevicesChanged(void *param, DeckLinkDevice *device,
@ -41,6 +42,11 @@ public:
{
pixelFormat = format;
}
inline speaker_layout GetChannelFormat() const {return channelFormat;}
inline void SetChannelFormat(speaker_layout format)
{
channelFormat = format;
}
bool Activate(DeckLinkDevice *device, long long modeId);
void Deactivate();

View File

@ -22,6 +22,8 @@ set(linux-decklink_HEADERS
../decklink-device-discovery.hpp
../decklink-device.hpp
../decklink-device-mode.hpp
../audio-repack.h
../audio-repack.hpp
)
set(linux-decklink_SOURCES
@ -31,6 +33,7 @@ set(linux-decklink_SOURCES
../decklink-device-discovery.cpp
../decklink-device.cpp
../decklink-device-mode.cpp
../audio-repack.c
platform.cpp)
add_library(linux-decklink MODULE

View File

@ -26,6 +26,8 @@ set(mac-decklink_HEADERS
../decklink-device-discovery.hpp
../decklink-device.hpp
../decklink-device-mode.hpp
../audio-repack.h
../audio-repack.hpp
)
set(mac-decklink_SOURCES
@ -35,6 +37,7 @@ set(mac-decklink_SOURCES
../decklink-device-discovery.cpp
../decklink-device.cpp
../decklink-device-mode.cpp
../audio-repack.c
platform.cpp)
add_library(mac-decklink MODULE

View File

@ -7,6 +7,25 @@
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("decklink", "en-US")
#define DEVICE_HASH "device_hash"
#define DEVICE_NAME "device_name"
#define MODE_ID "mode_id"
#define MODE_NAME "mode_name"
#define CHANNEL_FORMAT "channel_format"
#define PIXEL_FORMAT "pixel_format"
#define BUFFERING "buffering"
#define TEXT_DEVICE obs_module_text("Device")
#define TEXT_MODE obs_module_text("Mode")
#define TEXT_PIXEL_FORMAT obs_module_text("PixelFormat")
#define TEXT_CHANNEL_FORMAT obs_module_text("ChannelFormat")
#define TEXT_CHANNEL_FORMAT_NONE obs_module_text("ChannelFormat.None")
#define TEXT_CHANNEL_FORMAT_2_0CH obs_module_text("ChannelFormat.2_0ch")
#define TEXT_CHANNEL_FORMAT_5_1CH obs_module_text("ChannelFormat.5_1ch")
#define TEXT_CHANNEL_FORMAT_5_1CH_BACK obs_module_text("ChannelFormat.5_1chBack")
#define TEXT_CHANNEL_FORMAT_7_1CH obs_module_text("ChannelFormat.7_1ch")
#define TEXT_BUFFERING obs_module_text("Buffering")
static DeckLinkDeviceDiscovery *deviceEnum = nullptr;
static void decklink_enable_buffering(DeckLink *decklink, bool enabled)
@ -25,7 +44,7 @@ static void *decklink_create(obs_data_t *settings, obs_source_t *source)
DeckLink *decklink = new DeckLink(source, deviceEnum);
decklink_enable_buffering(decklink,
obs_data_get_bool(settings, "buffering"));
obs_data_get_bool(settings, BUFFERING));
obs_source_update(source, settings);
return decklink;
@ -40,25 +59,29 @@ static void decklink_destroy(void *data)
static void decklink_update(void *data, obs_data_t *settings)
{
DeckLink *decklink = (DeckLink *)data;
const char *hash = obs_data_get_string(settings, "device_hash");
long long id = obs_data_get_int(settings, "mode_id");
BMDPixelFormat format = (BMDPixelFormat)obs_data_get_int(settings,
"pixel_format");
const char *hash = obs_data_get_string(settings, DEVICE_HASH);
long long id = obs_data_get_int(settings, MODE_ID);
BMDPixelFormat pixelFormat = (BMDPixelFormat)obs_data_get_int(settings,
PIXEL_FORMAT);
speaker_layout channelFormat = (speaker_layout)obs_data_get_int(settings,
CHANNEL_FORMAT);
decklink_enable_buffering(decklink,
obs_data_get_bool(settings, "buffering"));
obs_data_get_bool(settings, BUFFERING));
ComPtr<DeckLinkDevice> device;
device.Set(deviceEnum->FindByHash(hash));
decklink->SetPixelFormat(format);
decklink->SetPixelFormat(pixelFormat);
decklink->SetChannelFormat(channelFormat);
decklink->Activate(device, id);
}
static void decklink_get_defaults(obs_data_t *settings)
{
obs_data_set_default_bool(settings, "buffering", true);
obs_data_set_default_int(settings, "pixel_format", bmdFormat8BitYUV);
obs_data_set_default_bool(settings, BUFFERING, true);
obs_data_set_default_int(settings, PIXEL_FORMAT, bmdFormat8BitYUV);
obs_data_set_default_int(settings, CHANNEL_FORMAT, SPEAKERS_STEREO);
}
static const char *decklink_get_name(void*)
@ -69,10 +92,10 @@ static const char *decklink_get_name(void*)
static bool decklink_device_changed(obs_properties_t *props,
obs_property_t *list, obs_data_t *settings)
{
const char *name = obs_data_get_string(settings, "device_name");
const char *hash = obs_data_get_string(settings, "device_hash");
const char *mode = obs_data_get_string(settings, "mode_name");
long long modeId = obs_data_get_int(settings, "mode_id");
const char *name = obs_data_get_string(settings, DEVICE_NAME);
const char *hash = obs_data_get_string(settings, DEVICE_HASH);
const char *mode = obs_data_get_string(settings, MODE_NAME);
long long modeId = obs_data_get_int(settings, MODE_ID);
size_t itemCount = obs_property_list_item_count(list);
bool itemFound = false;
@ -90,25 +113,41 @@ static bool decklink_device_changed(obs_properties_t *props,
obs_property_list_item_disable(list, 0, true);
}
list = obs_properties_get(props, "mode_id");
obs_property_t *modeList = obs_properties_get(props, MODE_ID);
obs_property_t *channelList = obs_properties_get(props, CHANNEL_FORMAT);
obs_property_list_clear(list);
obs_property_list_clear(modeList);
obs_property_list_clear(channelList);
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_NONE,
SPEAKERS_UNKNOWN);
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_2_0CH,
SPEAKERS_STEREO);
ComPtr<DeckLinkDevice> device;
device.Set(deviceEnum->FindByHash(hash));
if (!device) {
obs_property_list_add_int(list, mode, modeId);
obs_property_list_item_disable(list, 0, true);
obs_property_list_add_int(modeList, mode, modeId);
obs_property_list_item_disable(modeList, 0, true);
} else {
const std::vector<DeckLinkDeviceMode*> &modes =
device->GetModes();
for (DeckLinkDeviceMode *mode : modes) {
obs_property_list_add_int(list,
obs_property_list_add_int(modeList,
mode->GetName().c_str(),
mode->GetId());
}
if (device->GetMaxChannel() >= 8) {
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_5_1CH,
SPEAKERS_5POINT1);
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_5_1CH_BACK,
SPEAKERS_5POINT1_SURROUND);
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_7_1CH,
SPEAKERS_7POINT1);
}
}
return true;
@ -132,26 +171,30 @@ static obs_properties_t *decklink_get_properties(void *data)
{
obs_properties_t *props = obs_properties_create();
obs_property_t *list = obs_properties_add_list(props, "device_hash",
obs_module_text("Device"), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
obs_property_t *list = obs_properties_add_list(props, DEVICE_HASH,
TEXT_DEVICE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
obs_property_set_modified_callback(list, decklink_device_changed);
fill_out_devices(list);
list = obs_properties_add_list(props, "mode_id",
obs_module_text("Mode"), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
list = obs_properties_add_list(props, MODE_ID, TEXT_MODE,
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
list = obs_properties_add_list(props, "pixel_format",
obs_module_text("PixelFormat"), OBS_COMBO_TYPE_LIST,
list = obs_properties_add_list(props, PIXEL_FORMAT,
TEXT_PIXEL_FORMAT, OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(list, "8-bit YUV", bmdFormat8BitYUV);
obs_property_list_add_int(list, "8-bit BGRA", bmdFormat8BitBGRA);
obs_properties_add_bool(props, "buffering",
obs_module_text("Buffering"));
list = obs_properties_add_list(props, CHANNEL_FORMAT,
TEXT_CHANNEL_FORMAT, OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_NONE,
SPEAKERS_UNKNOWN);
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_2_0CH,
SPEAKERS_STEREO);
obs_properties_add_bool(props, BUFFERING, TEXT_BUFFERING);
UNUSED_PARAMETER(data);
return props;

View File

@ -17,6 +17,8 @@ set(win-decklink_HEADERS
../decklink-device-discovery.hpp
../decklink-device.hpp
../decklink-device-mode.hpp
../audio-repack.h
../audio-repack.hpp
)
set(win-decklink_SOURCES
@ -26,6 +28,7 @@ set(win-decklink_SOURCES
../decklink-device-discovery.cpp
../decklink-device.cpp
../decklink-device-mode.cpp
../audio-repack.c
platform.cpp)
add_idl_files(win-decklink-sdk_GENERATED_FILES