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
parent
47570f4153
commit
f50aa5e01b
|
@ -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"
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 8af4281cce18f6c8cc9c4e3a87ca0b7afc006ce3
|
||||
Subproject commit 194f85f6380fc805f439eb87388588c99425c5ab
|
|
@ -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<VideoDevice> devices;
|
||||
vector<AudioDevice> 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<PropertiesData*>(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;
|
||||
|
|
Loading…
Reference in New Issue