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