git.s-ol.nu obs-bmusb / master bmusb-source.cpp
master

Tree @master (Download .tar.gz)

bmusb-source.cpp @masterraw · history · blame

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