mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 16:50:47 +00:00
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
This commit is contained in:
parent
f75c7823dd
commit
d6d31064b4
6 changed files with 153 additions and 32 deletions
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
for (ii = 0; ii < gst_caps_get_size (caps); ii++) {
|
||||
GstStructure *s = gst_caps_get_structure (caps, ii);
|
||||
*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 (*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;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,13 @@
|
|||
|
||||
#include <audioclient.h>
|
||||
|
||||
/* 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__ */
|
||||
|
|
Loading…
Reference in a new issue