From f50aa5e01b4f476545d0e5c652c824f4223b1394 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Thu, 28 Aug 2014 18:27:58 -0700 Subject: [PATCH] win-dshow: Add audio support This implements audio support, allowing not only the ability to capture the built-in audio from the video device's audio capture pin, but also the ability to override the default audio with a custom audio device. The DShowInput::Update function was split up and refactored a bit, as it was getting a bit large and messy. --- plugins/win-dshow/data/locale/en-US.ini | 3 + plugins/win-dshow/libdshowcapture | 2 +- plugins/win-dshow/win-dshow.cpp | 180 +++++++++++++++++++++--- 3 files changed, 165 insertions(+), 20 deletions(-) diff --git a/plugins/win-dshow/data/locale/en-US.ini b/plugins/win-dshow/data/locale/en-US.ini index 92a0b5dca..e97cf7155 100644 --- a/plugins/win-dshow/data/locale/en-US.ini +++ b/plugins/win-dshow/data/locale/en-US.ini @@ -1,5 +1,6 @@ VideoCaptureDevice="Video Capture Device" Device="Device" +ConfigureAudio="Configure Audio" ConfigureVideo="Configure Video" ConfigureCrossbar="Configure Crossbar" ResFPSType="Resolution/FPS Type" @@ -11,3 +12,5 @@ Resolution="Resolution" VideoFormat="Video Format" VideoFormat.Any="Any" VideoFormat.Unknown="Unknown (%1)" +UseCustomAudioDevice="Use custom audio device" +AudioDevice="Audio Device" diff --git a/plugins/win-dshow/libdshowcapture b/plugins/win-dshow/libdshowcapture index 8af4281cc..194f85f63 160000 --- a/plugins/win-dshow/libdshowcapture +++ b/plugins/win-dshow/libdshowcapture @@ -1 +1 @@ -Subproject commit 8af4281cce18f6c8cc9c4e3a87ca0b7afc006ce3 +Subproject commit 194f85f6380fc805f439eb87388588c99425c5ab diff --git a/plugins/win-dshow/win-dshow.cpp b/plugins/win-dshow/win-dshow.cpp index e6d7c581b..1a13e5165 100644 --- a/plugins/win-dshow/win-dshow.cpp +++ b/plugins/win-dshow/win-dshow.cpp @@ -33,6 +33,8 @@ using namespace DShow; #define VIDEO_FORMAT "video_format" #define LAST_VIDEO_DEV_ID "last_video_device_id" #define LAST_RESOLUTION "last_resolution" +#define USE_CUSTOM_AUDIO "use_custom_audio_device" +#define AUDIO_DEVICE_ID "audio_device_id" #define TEXT_INPUT_NAME obs_module_text("VideoCaptureDevice") #define TEXT_DEVICE obs_module_text("Device") @@ -46,6 +48,8 @@ using namespace DShow; #define TEXT_RESOLUTION obs_module_text("Resolution") #define TEXT_VIDEO_FORMAT obs_module_text("VideoFormat") #define TEXT_FORMAT_UNKNOWN obs_module_text("VideoFormat.Unknown") +#define TEXT_CUSTOM_AUDIO obs_module_text("UseCustomAudioDevice") +#define TEXT_AUDIO_DEVICE obs_module_text("AudioDevice") enum ResType { ResType_Preferred, @@ -56,11 +60,13 @@ struct DShowInput { obs_source_t source; Device device; bool comInitialized; + bool deviceHasAudio; VideoConfig videoConfig; AudioConfig audioConfig; obs_source_frame frame; + obs_source_audio audio; inline DShowInput(obs_source_t source_) : source (source_), @@ -70,7 +76,11 @@ struct DShowInput { void OnVideoData(unsigned char *data, size_t size, long long startTime, long long endTime); + void OnAudioData(unsigned char *data, size_t size, + long long startTime, long long endTime); + bool UpdateVideoConfig(obs_data_t settings); + bool UpdateAudioConfig(obs_data_t settings); void Update(obs_data_t settings); }; @@ -129,11 +139,21 @@ static inline video_format ConvertVideoFormat(VideoFormat format) case VideoFormat::YVYU: return VIDEO_FORMAT_UYVY; case VideoFormat::YUY2: return VIDEO_FORMAT_YUY2; case VideoFormat::UYVY: return VIDEO_FORMAT_YVYU; + case VideoFormat::HDYC: return VIDEO_FORMAT_UYVY; case VideoFormat::MJPEG: return VIDEO_FORMAT_YUY2; default: return VIDEO_FORMAT_NONE; } } +static inline audio_format ConvertAudioFormat(AudioFormat format) +{ + switch (format) { + case AudioFormat::Wave16bit: return AUDIO_FORMAT_16BIT; + case AudioFormat::WaveFloat: return AUDIO_FORMAT_FLOAT; + default: return AUDIO_FORMAT_UNKNOWN; + } +} + void DShowInput::OnVideoData(unsigned char *data, size_t size, long long startTime, long long endTime) { @@ -149,6 +169,7 @@ void DShowInput::OnVideoData(unsigned char *data, size_t size, } else if (videoConfig.format == VideoFormat::YVYU || videoConfig.format == VideoFormat::YUY2 || + videoConfig.format == VideoFormat::HDYC || videoConfig.format == VideoFormat::UYVY) { frame.data[0] = data; frame.linesize[0] = cx * 2; @@ -172,6 +193,25 @@ void DShowInput::OnVideoData(unsigned char *data, size_t size, UNUSED_PARAMETER(size); } +void DShowInput::OnAudioData(unsigned char *data, size_t size, + long long startTime, long long endTime) +{ + if (audio.format == AUDIO_FORMAT_UNKNOWN) + return; + + size_t block_size = get_audio_bytes_per_channel(audio.format) * + get_audio_channels(audio.speakers); + + audio.data[0] = data; + audio.frames = (uint32_t)(size / block_size); + audio.timestamp = (uint64_t)startTime * 100; + + obs_source_output_audio(source, &audio); + + UNUSED_PARAMETER(endTime); + UNUSED_PARAMETER(size); +} + static bool DecodeDeviceId(DStr &name, DStr &path, const char *device_id) { const char *path_str; @@ -216,6 +256,7 @@ static bool DecodeDeviceId(DeviceId &out, const char *device_id) struct PropertiesData { vector devices; + vector audioDevices; bool GetDevice(VideoDevice &device, const char *encoded_id) const { @@ -366,27 +407,19 @@ static bool DetermineResolution(int &cx, int &cy, obs_data_t settings, static long long GetOBSFPS(); -void DShowInput::Update(obs_data_t settings) +bool DShowInput::UpdateVideoConfig(obs_data_t settings) { string video_device_id = obs_data_get_string(settings, VIDEO_DEVICE_ID); - if (!comInitialized) { - CoInitialize(nullptr); - comInitialized = true; - } - - if (!device.ResetGraph()) - return; - DeviceId id; if (!DecodeDeviceId(id, video_device_id.c_str())) - return; + return false; PropertiesData data; Device::EnumVideoDevices(data.devices); VideoDevice dev; if (!data.GetDevice(dev, video_device_id.c_str())) - return; + return false; int resType = (int)obs_data_get_int(settings, RES_TYPE); int cx = 0, cy = 0; @@ -397,7 +430,7 @@ void DShowInput::Update(obs_data_t settings) bool has_autosel_val; string resolution = obs_data_get_string(settings, RESOLUTION); if (!ResolutionValid(resolution, cx, cy)) - return; + return false; has_autosel_val = obs_data_has_autoselect_value(settings, FRAME_INTERVAL); @@ -417,7 +450,7 @@ void DShowInput::Update(obs_data_t settings) VideoFormatMatcher(format, video_format_match), ClosestFrameRateSelector(interval, best_interval), FrameRateMatcher(interval)) && !video_format_match) - return; + return false; interval = best_interval; blog(LOG_INFO, "%s: Using interval %lld", @@ -432,6 +465,8 @@ void DShowInput::Update(obs_data_t settings) videoConfig.frameInterval = interval; videoConfig.internalFormat = format; + deviceHasAudio = dev.audioAttached; + videoConfig.callback = std::bind(&DShowInput::OnVideoData, this, placeholders::_1, placeholders::_2, placeholders::_3, placeholders::_4); @@ -439,13 +474,64 @@ void DShowInput::Update(obs_data_t settings) if (videoConfig.internalFormat != VideoFormat::MJPEG) videoConfig.format = videoConfig.internalFormat; - device.SetVideoConfig(&videoConfig); + if (!device.SetVideoConfig(&videoConfig)) + return false; if (videoConfig.internalFormat == VideoFormat::MJPEG) { - videoConfig.format = VideoFormat::XRGB; - device.SetVideoConfig(&videoConfig); + videoConfig.format = VideoFormat::XRGB; + if (!device.SetVideoConfig(&videoConfig)) + return false; } + return true; +} + +bool DShowInput::UpdateAudioConfig(obs_data_t settings) +{ + string audio_device_id = obs_data_get_string(settings, AUDIO_DEVICE_ID); + bool useCustomAudio = obs_data_get_bool(settings, USE_CUSTOM_AUDIO); + + if (useCustomAudio) { + DeviceId id; + if (!DecodeDeviceId(id, audio_device_id.c_str())) + return false; + + audioConfig.name = id.name.c_str(); + audioConfig.path = id.path.c_str(); + + } else if (!deviceHasAudio) { + return true; + } + + audioConfig.useVideoDevice = !useCustomAudio; + + audioConfig.callback = std::bind(&DShowInput::OnAudioData, this, + placeholders::_1, placeholders::_2, + placeholders::_3, placeholders::_4); + + return device.SetAudioConfig(&audioConfig); +} + +void DShowInput::Update(obs_data_t settings) +{ + if (!comInitialized) { + CoInitialize(nullptr); + comInitialized = true; + } + + if (!device.ResetGraph()) + return; + + if (!UpdateVideoConfig(settings)) { + blog(LOG_WARNING, "%s: Video configuration failed", + obs_source_get_name(source)); + return; + } + + if (!UpdateAudioConfig(settings)) + blog(LOG_WARNING, "%s: Audio configuration failed, ignoring " + "audio", obs_source_get_name(source)); + if (!device.ConnectFilters()) return; @@ -459,7 +545,14 @@ void DShowInput::Update(obs_data_t settings) frame.flip = (videoConfig.format == VideoFormat::XRGB || videoConfig.format == VideoFormat::ARGB); - if (!video_format_get_parameters(VIDEO_CS_601, VIDEO_RANGE_PARTIAL, + audio.speakers = (enum speaker_layout)audioConfig.channels; + audio.format = ConvertAudioFormat(audioConfig.format); + audio.samples_per_sec = (uint32_t)audioConfig.sampleRate; + + enum video_colorspace cs = (videoConfig.format == VideoFormat::HDYC) ? + VIDEO_CS_709 : VIDEO_CS_601; + + if (!video_format_get_parameters(cs, VIDEO_RANGE_PARTIAL, frame.color_matrix, frame.color_range_min, frame.color_range_max)) { @@ -661,7 +754,7 @@ static const VideoFormatName videoFormatNames[] = { {VideoFormat::YVYU, "YVYU"}, {VideoFormat::YUY2, "YUY2"}, {VideoFormat::UYVY, "UYVY"}, - {VideoFormat::HDYC, "HDYV"}, + {VideoFormat::HDYC, "HDYC"}, /* encoded formats */ {VideoFormat::MPEG2, "MPEG2"}, @@ -821,6 +914,26 @@ static bool AddDevice(obs_property_t device_list, const VideoDevice &device) return true; } +static bool AddAudioDevice(obs_property_t device_list, + const AudioDevice &device) +{ + DStr name, path, device_id; + + dstr_from_wcs(name, device.name.c_str()); + dstr_from_wcs(path, device.path.c_str()); + + encode_dstr(path); + + dstr_copy_dstr(device_id, name); + encode_dstr(device_id); + dstr_cat(device_id, ":"); + dstr_cat_dstr(device_id, path); + + obs_property_list_add_string(device_list, name, device_id); + + return true; +} + static void PropertiesDataDestroy(void *data) { delete reinterpret_cast(data); @@ -1122,6 +1235,15 @@ static bool VideoFormatChanged(obs_properties_t props, obs_property_t p, return true; } +static bool CustomAudioClicked(obs_properties_t props, obs_property_t p, + obs_data_t settings) +{ + bool useCustomAudio = obs_data_get_bool(settings, USE_CUSTOM_AUDIO); + p = obs_properties_get(props, AUDIO_DEVICE_ID); + obs_property_set_visible(p, useCustomAudio); + return true; +} + static obs_properties_t GetDShowProperties(void) { obs_properties_t ppts = obs_properties_create(); @@ -1145,6 +1267,7 @@ static obs_properties_t GetDShowProperties(void) CrossbarConfigClicked); /* ------------------------------------- */ + /* video settings */ p = obs_properties_add_list(ppts, RES_TYPE, TEXT_RES_FPS_TYPE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); @@ -1169,6 +1292,23 @@ static obs_properties_t GetDShowProperties(void) obs_property_set_modified_callback(p, VideoFormatChanged); + /* ------------------------------------- */ + /* audio settings */ + + Device::EnumAudioDevices(data->audioDevices); + if (!data->audioDevices.size()) + return ppts; + + p = obs_properties_add_bool(ppts, USE_CUSTOM_AUDIO, TEXT_CUSTOM_AUDIO); + + obs_property_set_modified_callback(p, CustomAudioClicked); + + p = obs_properties_add_list(ppts, AUDIO_DEVICE_ID, TEXT_AUDIO_DEVICE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + + for (const AudioDevice &device : data->audioDevices) + AddAudioDevice(p, device); + return ppts; } @@ -1201,7 +1341,9 @@ bool obs_module_load(void) obs_source_info info = {}; info.id = "dshow_input"; info.type = OBS_SOURCE_TYPE_INPUT; - info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_ASYNC; + info.output_flags = OBS_SOURCE_VIDEO | + OBS_SOURCE_AUDIO | + OBS_SOURCE_ASYNC; info.get_name = GetDShowInputName; info.create = CreateDShowInput; info.destroy = DestroyDShowInput;