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.
master
jp9000 2014-08-28 18:27:58 -07:00
parent 47570f4153
commit f50aa5e01b
3 changed files with 165 additions and 20 deletions

View File

@ -1,5 +1,6 @@
VideoCaptureDevice="Video Capture Device" VideoCaptureDevice="Video Capture Device"
Device="Device" Device="Device"
ConfigureAudio="Configure Audio"
ConfigureVideo="Configure Video" ConfigureVideo="Configure Video"
ConfigureCrossbar="Configure Crossbar" ConfigureCrossbar="Configure Crossbar"
ResFPSType="Resolution/FPS Type" ResFPSType="Resolution/FPS Type"
@ -11,3 +12,5 @@ Resolution="Resolution"
VideoFormat="Video Format" VideoFormat="Video Format"
VideoFormat.Any="Any" VideoFormat.Any="Any"
VideoFormat.Unknown="Unknown (%1)" VideoFormat.Unknown="Unknown (%1)"
UseCustomAudioDevice="Use custom audio device"
AudioDevice="Audio Device"

@ -1 +1 @@
Subproject commit 8af4281cce18f6c8cc9c4e3a87ca0b7afc006ce3 Subproject commit 194f85f6380fc805f439eb87388588c99425c5ab

View File

@ -33,6 +33,8 @@ using namespace DShow;
#define VIDEO_FORMAT "video_format" #define VIDEO_FORMAT "video_format"
#define LAST_VIDEO_DEV_ID "last_video_device_id" #define LAST_VIDEO_DEV_ID "last_video_device_id"
#define LAST_RESOLUTION "last_resolution" #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_INPUT_NAME obs_module_text("VideoCaptureDevice")
#define TEXT_DEVICE obs_module_text("Device") #define TEXT_DEVICE obs_module_text("Device")
@ -46,6 +48,8 @@ using namespace DShow;
#define TEXT_RESOLUTION obs_module_text("Resolution") #define TEXT_RESOLUTION obs_module_text("Resolution")
#define TEXT_VIDEO_FORMAT obs_module_text("VideoFormat") #define TEXT_VIDEO_FORMAT obs_module_text("VideoFormat")
#define TEXT_FORMAT_UNKNOWN obs_module_text("VideoFormat.Unknown") #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 { enum ResType {
ResType_Preferred, ResType_Preferred,
@ -56,11 +60,13 @@ struct DShowInput {
obs_source_t source; obs_source_t source;
Device device; Device device;
bool comInitialized; bool comInitialized;
bool deviceHasAudio;
VideoConfig videoConfig; VideoConfig videoConfig;
AudioConfig audioConfig; AudioConfig audioConfig;
obs_source_frame frame; obs_source_frame frame;
obs_source_audio audio;
inline DShowInput(obs_source_t source_) inline DShowInput(obs_source_t source_)
: source (source_), : source (source_),
@ -70,7 +76,11 @@ struct DShowInput {
void OnVideoData(unsigned char *data, size_t size, void OnVideoData(unsigned char *data, size_t size,
long long startTime, long long endTime); 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); 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::YVYU: return VIDEO_FORMAT_UYVY;
case VideoFormat::YUY2: return VIDEO_FORMAT_YUY2; case VideoFormat::YUY2: return VIDEO_FORMAT_YUY2;
case VideoFormat::UYVY: return VIDEO_FORMAT_YVYU; case VideoFormat::UYVY: return VIDEO_FORMAT_YVYU;
case VideoFormat::HDYC: return VIDEO_FORMAT_UYVY;
case VideoFormat::MJPEG: return VIDEO_FORMAT_YUY2; case VideoFormat::MJPEG: return VIDEO_FORMAT_YUY2;
default: return VIDEO_FORMAT_NONE; 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, void DShowInput::OnVideoData(unsigned char *data, size_t size,
long long startTime, long long endTime) 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 || } else if (videoConfig.format == VideoFormat::YVYU ||
videoConfig.format == VideoFormat::YUY2 || videoConfig.format == VideoFormat::YUY2 ||
videoConfig.format == VideoFormat::HDYC ||
videoConfig.format == VideoFormat::UYVY) { videoConfig.format == VideoFormat::UYVY) {
frame.data[0] = data; frame.data[0] = data;
frame.linesize[0] = cx * 2; frame.linesize[0] = cx * 2;
@ -172,6 +193,25 @@ void DShowInput::OnVideoData(unsigned char *data, size_t size,
UNUSED_PARAMETER(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) static bool DecodeDeviceId(DStr &name, DStr &path, const char *device_id)
{ {
const char *path_str; const char *path_str;
@ -216,6 +256,7 @@ static bool DecodeDeviceId(DeviceId &out, const char *device_id)
struct PropertiesData { struct PropertiesData {
vector<VideoDevice> devices; vector<VideoDevice> devices;
vector<AudioDevice> audioDevices;
bool GetDevice(VideoDevice &device, const char *encoded_id) const 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(); 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); string video_device_id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
if (!comInitialized) {
CoInitialize(nullptr);
comInitialized = true;
}
if (!device.ResetGraph())
return;
DeviceId id; DeviceId id;
if (!DecodeDeviceId(id, video_device_id.c_str())) if (!DecodeDeviceId(id, video_device_id.c_str()))
return; return false;
PropertiesData data; PropertiesData data;
Device::EnumVideoDevices(data.devices); Device::EnumVideoDevices(data.devices);
VideoDevice dev; VideoDevice dev;
if (!data.GetDevice(dev, video_device_id.c_str())) if (!data.GetDevice(dev, video_device_id.c_str()))
return; return false;
int resType = (int)obs_data_get_int(settings, RES_TYPE); int resType = (int)obs_data_get_int(settings, RES_TYPE);
int cx = 0, cy = 0; int cx = 0, cy = 0;
@ -397,7 +430,7 @@ void DShowInput::Update(obs_data_t settings)
bool has_autosel_val; bool has_autosel_val;
string resolution = obs_data_get_string(settings, RESOLUTION); string resolution = obs_data_get_string(settings, RESOLUTION);
if (!ResolutionValid(resolution, cx, cy)) if (!ResolutionValid(resolution, cx, cy))
return; return false;
has_autosel_val = obs_data_has_autoselect_value(settings, has_autosel_val = obs_data_has_autoselect_value(settings,
FRAME_INTERVAL); FRAME_INTERVAL);
@ -417,7 +450,7 @@ void DShowInput::Update(obs_data_t settings)
VideoFormatMatcher(format, video_format_match), VideoFormatMatcher(format, video_format_match),
ClosestFrameRateSelector(interval, best_interval), ClosestFrameRateSelector(interval, best_interval),
FrameRateMatcher(interval)) && !video_format_match) FrameRateMatcher(interval)) && !video_format_match)
return; return false;
interval = best_interval; interval = best_interval;
blog(LOG_INFO, "%s: Using interval %lld", blog(LOG_INFO, "%s: Using interval %lld",
@ -432,6 +465,8 @@ void DShowInput::Update(obs_data_t settings)
videoConfig.frameInterval = interval; videoConfig.frameInterval = interval;
videoConfig.internalFormat = format; videoConfig.internalFormat = format;
deviceHasAudio = dev.audioAttached;
videoConfig.callback = std::bind(&DShowInput::OnVideoData, this, videoConfig.callback = std::bind(&DShowInput::OnVideoData, this,
placeholders::_1, placeholders::_2, placeholders::_1, placeholders::_2,
placeholders::_3, placeholders::_4); placeholders::_3, placeholders::_4);
@ -439,13 +474,64 @@ void DShowInput::Update(obs_data_t settings)
if (videoConfig.internalFormat != VideoFormat::MJPEG) if (videoConfig.internalFormat != VideoFormat::MJPEG)
videoConfig.format = videoConfig.internalFormat; videoConfig.format = videoConfig.internalFormat;
device.SetVideoConfig(&videoConfig); if (!device.SetVideoConfig(&videoConfig))
return false;
if (videoConfig.internalFormat == VideoFormat::MJPEG) { if (videoConfig.internalFormat == VideoFormat::MJPEG) {
videoConfig.format = VideoFormat::XRGB; videoConfig.format = VideoFormat::XRGB;
device.SetVideoConfig(&videoConfig); 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()) if (!device.ConnectFilters())
return; return;
@ -459,7 +545,14 @@ void DShowInput::Update(obs_data_t settings)
frame.flip = (videoConfig.format == VideoFormat::XRGB || frame.flip = (videoConfig.format == VideoFormat::XRGB ||
videoConfig.format == VideoFormat::ARGB); 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_matrix,
frame.color_range_min, frame.color_range_min,
frame.color_range_max)) { frame.color_range_max)) {
@ -661,7 +754,7 @@ static const VideoFormatName videoFormatNames[] = {
{VideoFormat::YVYU, "YVYU"}, {VideoFormat::YVYU, "YVYU"},
{VideoFormat::YUY2, "YUY2"}, {VideoFormat::YUY2, "YUY2"},
{VideoFormat::UYVY, "UYVY"}, {VideoFormat::UYVY, "UYVY"},
{VideoFormat::HDYC, "HDYV"}, {VideoFormat::HDYC, "HDYC"},
/* encoded formats */ /* encoded formats */
{VideoFormat::MPEG2, "MPEG2"}, {VideoFormat::MPEG2, "MPEG2"},
@ -821,6 +914,26 @@ static bool AddDevice(obs_property_t device_list, const VideoDevice &device)
return true; 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) static void PropertiesDataDestroy(void *data)
{ {
delete reinterpret_cast<PropertiesData*>(data); delete reinterpret_cast<PropertiesData*>(data);
@ -1122,6 +1235,15 @@ static bool VideoFormatChanged(obs_properties_t props, obs_property_t p,
return true; 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) static obs_properties_t GetDShowProperties(void)
{ {
obs_properties_t ppts = obs_properties_create(); obs_properties_t ppts = obs_properties_create();
@ -1145,6 +1267,7 @@ static obs_properties_t GetDShowProperties(void)
CrossbarConfigClicked); CrossbarConfigClicked);
/* ------------------------------------- */ /* ------------------------------------- */
/* video settings */
p = obs_properties_add_list(ppts, RES_TYPE, TEXT_RES_FPS_TYPE, p = obs_properties_add_list(ppts, RES_TYPE, TEXT_RES_FPS_TYPE,
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); 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); 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; return ppts;
} }
@ -1201,7 +1341,9 @@ bool obs_module_load(void)
obs_source_info info = {}; obs_source_info info = {};
info.id = "dshow_input"; info.id = "dshow_input";
info.type = OBS_SOURCE_TYPE_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.get_name = GetDShowInputName;
info.create = CreateDShowInput; info.create = CreateDShowInput;
info.destroy = DestroyDShowInput; info.destroy = DestroyDShowInput;