From d6d31064b4c530711f2e382ddc20eab922a24328 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Wed, 24 Jan 2018 08:20:38 +0530 Subject: [PATCH] wasapi: Implement support for >2 channels We need to parse the WAVEFORMATEXTENSIBLE structure, figure out what positions the channels have (if they are positional), and reorder them as necessary. https://bugzilla.gnome.org/show_bug.cgi?id=792897 --- sys/wasapi/gstwasapisink.c | 21 +++++--- sys/wasapi/gstwasapisink.h | 4 ++ sys/wasapi/gstwasapisrc.c | 21 +++++--- sys/wasapi/gstwasapisrc.h | 4 ++ sys/wasapi/gstwasapiutil.c | 123 +++++++++++++++++++++++++++++++++++++++------ sys/wasapi/gstwasapiutil.h | 12 ++++- 6 files changed, 153 insertions(+), 32 deletions(-) diff --git a/sys/wasapi/gstwasapisink.c b/sys/wasapi/gstwasapisink.c index da54d8e5..7a89cca 100644 --- a/sys/wasapi/gstwasapisink.c +++ b/sys/wasapi/gstwasapisink.c @@ -48,10 +48,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_wasapi_sink_debug); static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-raw, " - "format = (string) " GST_AUDIO_FORMATS_ALL ", " - "layout = (string) interleaved, " - "rate = " GST_AUDIO_RATE_RANGE ", channels = (int) [1, 2]")); + GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS)); #define DEFAULT_ROLE GST_WASAPI_DEVICE_ROLE_CONSOLE #define DEFAULT_MUTE FALSE @@ -184,6 +181,7 @@ gst_wasapi_sink_finalize (GObject * object) self->cached_caps = NULL; } + g_clear_pointer (&self->positions, g_free); g_clear_pointer (&self->device, g_free); self->mute = FALSE; @@ -267,14 +265,20 @@ gst_wasapi_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) goto out; } - caps = - gst_wasapi_util_waveformatex_to_caps ((WAVEFORMATEXTENSIBLE *) format, - template_caps); + gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format, + template_caps, &caps, &self->positions); if (caps == NULL) { GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), ("unknown format")); goto out; } + { + gchar *pos_str = gst_audio_channel_positions_to_string (self->positions, + format->nChannels); + GST_INFO_OBJECT (self, "positions are: %s", pos_str); + g_free (pos_str); + } + self->mix_format = format; gst_caps_replace (&self->cached_caps, caps); gst_caps_unref (template_caps); @@ -400,6 +404,9 @@ gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec) self->render_client = render_client; render_client = NULL; + gst_audio_ring_buffer_set_channel_positions ( + GST_AUDIO_BASE_SINK (self)->ringbuffer, self->positions); + res = TRUE; beach: diff --git a/sys/wasapi/gstwasapisink.h b/sys/wasapi/gstwasapisink.h index 4078fb3..8af96c0 100644 --- a/sys/wasapi/gstwasapisink.h +++ b/sys/wasapi/gstwasapisink.h @@ -50,6 +50,10 @@ struct _GstWasapiSink WAVEFORMATEX *mix_format; /* The probed caps that we can accept */ GstCaps *cached_caps; + /* The channel positions in the data to be written to the device we + * will pass this to GstAudioRingbuffer so it can to it translate + * from the native GStreamer channel layout. */ + GstAudioChannelPosition *positions; /* properties */ gint role; diff --git a/sys/wasapi/gstwasapisrc.c b/sys/wasapi/gstwasapisrc.c index 0914d1c..3825cf8 100644 --- a/sys/wasapi/gstwasapisrc.c +++ b/sys/wasapi/gstwasapisrc.c @@ -46,10 +46,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_wasapi_src_debug); static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-raw, " - "format = (string) " GST_AUDIO_FORMATS_ALL ", " - "layout = (string) interleaved, " - "rate = " GST_AUDIO_RATE_RANGE ", channels = (int) [1, 2]")); + GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS)); #define DEFAULT_ROLE GST_WASAPI_DEVICE_ROLE_CONSOLE @@ -185,6 +182,7 @@ gst_wasapi_src_finalize (GObject * object) CoUninitialize (); g_clear_pointer (&self->cached_caps, gst_caps_unref); + g_clear_pointer (&self->positions, g_free); g_clear_pointer (&self->device, g_free); G_OBJECT_CLASS (parent_class)->finalize (object); @@ -261,14 +259,20 @@ gst_wasapi_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) goto out; } - caps = - gst_wasapi_util_waveformatex_to_caps ((WAVEFORMATEXTENSIBLE *) format, - template_caps); + gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format, + template_caps, &caps, &self->positions); if (caps == NULL) { GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), ("unknown format")); goto out; } + { + gchar *pos_str = gst_audio_channel_positions_to_string (self->positions, + format->nChannels); + GST_INFO_OBJECT (self, "positions are: %s", pos_str); + g_free (pos_str); + } + self->mix_format = format; gst_caps_replace (&self->cached_caps, caps); gst_caps_unref (template_caps); @@ -409,6 +413,9 @@ gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec) self->client_clock_freq = client_clock_freq; self->capture_client = capture_client; + gst_audio_ring_buffer_set_channel_positions ( + GST_AUDIO_BASE_SRC (self)->ringbuffer, self->positions); + res = TRUE; beach: diff --git a/sys/wasapi/gstwasapisrc.h b/sys/wasapi/gstwasapisrc.h index 1fa9390..88be532 100644 --- a/sys/wasapi/gstwasapisrc.h +++ b/sys/wasapi/gstwasapisrc.h @@ -52,6 +52,10 @@ struct _GstWasapiSrc WAVEFORMATEX *mix_format; /* The probed caps that we can accept */ GstCaps *cached_caps; + /* The channel positions in the data read from the device + * we will pass this to GstAudioRingbuffer so it can + * translate it to the native GStreamer channel layout. */ + GstAudioChannelPosition *positions; /* properties */ gint role; diff --git a/sys/wasapi/gstwasapiutil.c b/sys/wasapi/gstwasapiutil.c index ed9f508..170de45 100644 --- a/sys/wasapi/gstwasapiutil.c +++ b/sys/wasapi/gstwasapiutil.c @@ -61,6 +61,31 @@ const IID IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, }; #endif +static struct { + guint64 wasapi_pos; + GstAudioChannelPosition gst_pos; +} wasapi_to_gst_pos[] = { + {SPEAKER_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT}, + {SPEAKER_FRONT_RIGHT, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, + {SPEAKER_FRONT_CENTER, GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}, + {SPEAKER_LOW_FREQUENCY, GST_AUDIO_CHANNEL_POSITION_LFE1}, + {SPEAKER_BACK_LEFT, GST_AUDIO_CHANNEL_POSITION_REAR_LEFT}, + {SPEAKER_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, + {SPEAKER_FRONT_LEFT_OF_CENTER, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER}, + {SPEAKER_FRONT_RIGHT_OF_CENTER, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER}, + {SPEAKER_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}, + /* Enum values diverge from this point onwards */ + {SPEAKER_SIDE_LEFT, GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT}, + {SPEAKER_SIDE_RIGHT, GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}, + {SPEAKER_TOP_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_CENTER}, + {SPEAKER_TOP_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT}, + {SPEAKER_TOP_FRONT_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER}, + {SPEAKER_TOP_FRONT_RIGHT, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT}, + {SPEAKER_TOP_BACK_LEFT, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT}, + {SPEAKER_TOP_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER}, + {SPEAKER_TOP_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT}, +}; + GType gst_wasapi_device_role_get_type (void) { @@ -212,7 +237,7 @@ gst_wasapi_util_hresult_to_string (HRESULT hr) gboolean gst_wasapi_util_get_device_client (GstElement * element, - gboolean capture, gint role, const wchar_t * device_name, + gboolean capture, gint role, const wchar_t * device_strid, IAudioClient ** ret_client) { gboolean res = FALSE; @@ -224,12 +249,12 @@ gst_wasapi_util_get_device_client (GstElement * element, hr = CoCreateInstance (&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void **) &enumerator); if (hr != S_OK) { - GST_ERROR_OBJECT (element, "CoCreateInstance (MMDeviceEnumerator) failed:" - "%s", gst_wasapi_util_hresult_to_string (hr)); + GST_ERROR_OBJECT (element, "CoCreateInstance (MMDeviceEnumerator) failed" + ": %s", gst_wasapi_util_hresult_to_string (hr)); goto beach; } - if (!device_name) { + if (!device_strid) { hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator, capture ? eCapture : eRender, role, &device); if (hr != S_OK) { @@ -239,10 +264,10 @@ gst_wasapi_util_get_device_client (GstElement * element, goto beach; } } else { - hr = IMMDeviceEnumerator_GetDevice (enumerator, device_name, &device); + hr = IMMDeviceEnumerator_GetDevice (enumerator, device_strid, &device); if (hr != S_OK) { GST_ERROR_OBJECT (element, "IMMDeviceEnumerator::GetDevice (%S) failed" - ": %s", device_name, gst_wasapi_util_hresult_to_string (hr)); + ": %s", device_strid, gst_wasapi_util_hresult_to_string (hr)); goto beach; } } @@ -379,13 +404,71 @@ gst_waveformatex_to_audio_format (WAVEFORMATEXTENSIBLE * format) return fmt_str; } -GstCaps * -gst_wasapi_util_waveformatex_to_caps (WAVEFORMATEXTENSIBLE * format, - GstCaps * template_caps) +static void +gst_wasapi_util_channel_position_all_none (guint channels, + GstAudioChannelPosition * position) +{ + int ii; + for (ii = 0; ii < channels; ii++) + position[ii] = GST_AUDIO_CHANNEL_POSITION_NONE; +} + +/* Parse WAVEFORMATEX to get the gstreamer channel mask, and the wasapi channel + * positions so GstAudioRingbuffer can reorder the audio data to match the + * gstreamer channel order. */ +static guint64 +gst_wasapi_util_waveformatex_to_channel_mask (WAVEFORMATEXTENSIBLE * format, + GstAudioChannelPosition ** out_position) +{ + int ii; + guint64 mask = 0; + WORD nChannels = format->Format.nChannels; + DWORD dwChannelMask = format->dwChannelMask; + GstAudioChannelPosition *pos = NULL; + + pos = g_new (GstAudioChannelPosition, nChannels); + gst_wasapi_util_channel_position_all_none (nChannels, pos); + + /* Too many channels, have to assume that they are all non-positional */ + if (nChannels > G_N_ELEMENTS (wasapi_to_gst_pos)) { + GST_INFO ("wasapi: got too many (%i) channels, assuming non-positional", + nChannels); + goto out; + } + + /* Too many bits in the channel mask, and the bits don't match nChannels */ + if (dwChannelMask >> (G_N_ELEMENTS (wasapi_to_gst_pos) + 1) != 0) { + GST_WARNING ("wasapi: too many bits in channel mask (%lu), assuming " + "non-positional", dwChannelMask); + goto out; + } + + /* Map WASAPI's channel mask to Gstreamer's channel mask and positions. + * If the no. of bits in the mask > nChannels, we will ignore the extra. */ + for (ii = 0; ii < nChannels; ii++) { + if (!(dwChannelMask & wasapi_to_gst_pos[ii].wasapi_pos)) + /* Non-positional or unknown position, warn? */ + continue; + mask |= G_GUINT64_CONSTANT(1) << wasapi_to_gst_pos[ii].gst_pos; + pos[ii] = wasapi_to_gst_pos[ii].gst_pos; + } + +out: + if (out_position) + *out_position = pos; + return mask; +} + +gboolean +gst_wasapi_util_parse_waveformatex (WAVEFORMATEXTENSIBLE * format, + GstCaps * template_caps, GstCaps ** out_caps, + GstAudioChannelPosition ** out_positions) { int ii; const gchar *afmt; - GstCaps *caps = gst_caps_copy (template_caps); + guint64 channel_mask; + + *out_caps = NULL; /* TODO: handle SPDIF and other encoded formats */ @@ -395,23 +478,31 @@ gst_wasapi_util_waveformatex_to_caps (WAVEFORMATEXTENSIBLE * format, format->Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT && format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) /* Unhandled format tag */ - return NULL; + return FALSE; /* WASAPI can only tell us one canonical mix format that it will accept. The * alternative is calling IsFormatSupported on all combinations of formats. * Instead, it's simpler and faster to require conversion inside gstreamer */ afmt = gst_waveformatex_to_audio_format (format); if (afmt == NULL) - return NULL; + return FALSE; + + *out_caps = gst_caps_copy (template_caps); + + /* This will always return something that might be usable */ + channel_mask = + gst_wasapi_util_waveformatex_to_channel_mask (format, out_positions); - for (ii = 0; ii < gst_caps_get_size (caps); ii++) { - GstStructure *s = gst_caps_get_structure (caps, ii); + for (ii = 0; ii < gst_caps_get_size (*out_caps); ii++) { + GstStructure *s = gst_caps_get_structure (*out_caps, ii); gst_structure_set (s, "format", G_TYPE_STRING, afmt, "channels", G_TYPE_INT, format->Format.nChannels, - "rate", G_TYPE_INT, format->Format.nSamplesPerSec, NULL); + "rate", G_TYPE_INT, format->Format.nSamplesPerSec, + "channel-mask", GST_TYPE_BITMASK, channel_mask, + NULL); } - return caps; + return TRUE; } diff --git a/sys/wasapi/gstwasapiutil.h b/sys/wasapi/gstwasapiutil.h index 0e6dc8b..cf8d884 100644 --- a/sys/wasapi/gstwasapiutil.h +++ b/sys/wasapi/gstwasapiutil.h @@ -27,6 +27,13 @@ #include +/* Static Caps shared between source, sink, and device provider */ +#define GST_WASAPI_STATIC_CAPS "audio/x-raw, " \ + "format = (string) " GST_AUDIO_FORMATS_ALL ", " \ + "layout = (string) interleaved, " \ + "rate = " GST_AUDIO_RATE_RANGE ", " \ + "channels = " GST_AUDIO_CHANNELS_RANGE + /* Device role enum property */ typedef enum { @@ -59,7 +66,8 @@ gboolean gst_wasapi_util_get_capture_client (GstElement * element, gboolean gst_wasapi_util_get_clock (GstElement * element, IAudioClient * client, IAudioClock ** ret_clock); -GstCaps *gst_wasapi_util_waveformatex_to_caps (WAVEFORMATEXTENSIBLE * format, - GstCaps * template_caps); +gboolean gst_wasapi_util_parse_waveformatex (WAVEFORMATEXTENSIBLE * format, + GstCaps * template_caps, GstCaps ** out_caps, + GstAudioChannelPosition ** out_positions); #endif /* __GST_WASAPI_UTIL_H__ */ -- 2.7.4