#include <stdlib.h>
#include <obs.h>
#include <util/platform.h>
#include <obs-module.h>
#include <bmusb/bmusb.h>
#include <bmusb/fake_capture.h>
#include <iostream>
#define COLOR_SPACE "color_space"
#define COLOR_RANGE "color_range"
#define TEXT_COLOR_SPACE obs_module_text("ColorSpace")
#define TEXT_COLOR_SPACE_DEFAULT obs_module_text("ColorSpace.Default")
#define TEXT_COLOR_RANGE obs_module_text("ColorRange")
#define TEXT_COLOR_RANGE_DEFAULT obs_module_text("ColorRange.Default")
#define TEXT_COLOR_RANGE_PARTIAL obs_module_text("ColorRange.Partial")
#define TEXT_COLOR_RANGE_FULL obs_module_text("ColorRange.Full")
namespace b = bmusb;
struct bmusb_inst {
obs_source_t *source;
b::CaptureInterface *capture;
bool initialized;
video_colorspace space = VIDEO_CS_DEFAULT;
video_range_type range = VIDEO_RANGE_DEFAULT;
};
static const char *bmusb_get_name(void *unused)
{
UNUSED_PARAMETER(unused);
return "Blackmagic USB3 source (bmusb)";
}
static void *bmusb_create(obs_data_t *settings, obs_source_t *source)
{
struct bmusb_inst *rt = (bmusb_inst *) bzalloc(sizeof(struct bmusb_inst));
*rt = {
.source = source,
.capture = new b::BMUSBCapture(0), // @TODO select card
};
rt->capture->set_pixel_format(b::PixelFormat_8BitYCbCr);
rt->capture->set_frame_callback(
[rt](uint16_t timecode,
b::FrameAllocator::Frame video_frame, size_t video_offset, b::VideoFormat video_format,
b::FrameAllocator::Frame audio_frame, size_t audio_offset, b::AudioFormat audio_format
) {
uint64_t cur_time = os_gettime_ns();
if (!video_format.has_signal) {
std::cerr << "scanning" << std::endl;
video_frame.owner->release_frame(video_frame);
audio_frame.owner->release_frame(audio_frame);
return;
}
if (video_frame.data == nullptr) {
std::cerr << "oh no, data is NULL" << std::endl;
video_frame.owner->release_frame(video_frame);
audio_frame.owner->release_frame(audio_frame);
return;
}
struct obs_source_frame frame;
frame.width = video_format.width;
frame.height = video_format.height;
frame.format = VIDEO_FORMAT_UYVY;
frame.linesize[0] = video_format.stride;
frame.data[0] = video_frame.data + video_offset;
frame.timestamp = cur_time;
frame.flip = 0;
frame.full_range = rt->range == VIDEO_RANGE_FULL;
video_format_get_parameters(rt->space, rt->range,
frame.color_matrix, frame.color_range_min, frame.color_range_max);
obs_source_output_video(rt->source, &frame);
video_frame.owner->release_frame(video_frame);
#ifdef BMUSB_AUDIO
struct obs_source_audio audio;
audio.samples_per_sec = audio_format.sample_rate;
audio.frames = (audio_frame.len > audio_offset) ? (audio_frame.len - audio_offset) / audio_format.num_channels / (audio_format.bits_per_sample / 8) : 0;
audio.format = AUDIO_FORMAT_32BIT;
audio.data[0] = audio_frame.data + audio_offset;
audio.timestamp = cur_time;
switch (audio_format.num_channels) {
case 1: audio.speakers = SPEAKERS_MONO; break;
case 2: audio.speakers = SPEAKERS_STEREO; break;
case 3: audio.speakers = SPEAKERS_2POINT1; break;
case 4: audio.speakers = SPEAKERS_4POINT0; break;
case 5: audio.speakers = SPEAKERS_4POINT1; break;
case 6: audio.speakers = SPEAKERS_5POINT1; break;
case 8: audio.speakers = SPEAKERS_7POINT1; break;
default: audio.speakers = SPEAKERS_UNKNOWN; break;
}
obs_source_output_audio(rt->source, &audio);
#else
UNUSED_PARAMETER(audio_offset);
UNUSED_PARAMETER(audio_format);
#endif
audio_frame.owner->release_frame(audio_frame);
UNUSED_PARAMETER(timecode);
}
);
rt->capture->configure_card();
b::BMUSBCapture::start_bm_thread();
rt->capture->start_bm_capture();
rt->initialized = true;
UNUSED_PARAMETER(settings);
UNUSED_PARAMETER(source);
return rt;
}
static void bmusb_destroy(void *data)
{
struct bmusb_inst *rt = (bmusb_inst *) data;
if (rt) {
if (rt->initialized) {
delete rt->capture;
b::BMUSBCapture::stop_bm_thread();
}
bfree(rt);
}
}
static void bmusb_update(void *data, obs_data_t *settings)
{
struct bmusb_inst *rt = (bmusb_inst *) data;
rt->space = (video_colorspace)obs_data_get_int(settings, COLOR_SPACE);
rt->range = (video_range_type)obs_data_get_int(settings, COLOR_RANGE);
}
static void bmusb_get_defaults(obs_data_t *settings)
{
obs_data_set_default_int(settings, COLOR_SPACE, VIDEO_CS_DEFAULT);
obs_data_set_default_int(settings, COLOR_RANGE, VIDEO_RANGE_DEFAULT);
}
static obs_properties_t *bmusb_get_properties(void *data)
{
obs_properties_t *props = obs_properties_create();
obs_property_t *list = obs_properties_add_list(props, COLOR_SPACE, TEXT_COLOR_SPACE,
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(list, TEXT_COLOR_SPACE_DEFAULT,
VIDEO_CS_DEFAULT);
obs_property_list_add_int(list, "BT.601", VIDEO_CS_601);
obs_property_list_add_int(list, "BT.709", VIDEO_CS_709);
list = obs_properties_add_list(props, COLOR_RANGE, TEXT_COLOR_RANGE,
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(list, TEXT_COLOR_RANGE_DEFAULT,
VIDEO_RANGE_DEFAULT);
obs_property_list_add_int(list, TEXT_COLOR_RANGE_PARTIAL,
VIDEO_RANGE_PARTIAL);
obs_property_list_add_int(list, TEXT_COLOR_RANGE_FULL,
VIDEO_RANGE_FULL);
UNUSED_PARAMETER(data);
return props;
}
struct obs_source_info bmusb_source_info = {
.id = "bmusb",
.type = OBS_SOURCE_TYPE_INPUT,
#ifdef BMUSB_AUDIO
.output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO,
#else
.output_flags = OBS_SOURCE_ASYNC_VIDEO,
#endif
.get_name = bmusb_get_name,
.create = bmusb_create,
.destroy = bmusb_destroy,
.get_defaults = bmusb_get_defaults,
.get_properties = bmusb_get_properties,
.update = bmusb_update,
};
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("decklink", "en-US")
extern "C" bool obs_module_load(void)
{
obs_register_source(&bmusb_source_info);
return true;
}