diff options
Diffstat (limited to 'sys/wasapi2')
-rw-r--r-- | sys/wasapi2/gstwasapi2client.cpp | 1137 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2client.h | 44 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2device.c | 2 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2ringbuffer.cpp | 1015 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2ringbuffer.h | 55 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2sink.c | 391 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2sink.h | 2 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2src.c | 388 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2src.h | 2 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2util.c | 237 | ||||
-rw-r--r-- | sys/wasapi2/gstwasapi2util.h | 18 | ||||
-rw-r--r-- | sys/wasapi2/meson.build | 4 | ||||
-rw-r--r-- | sys/wasapi2/plugin.c | 18 |
13 files changed, 1629 insertions, 1684 deletions
diff --git a/sys/wasapi2/gstwasapi2client.cpp b/sys/wasapi2/gstwasapi2client.cpp index b7555e4d5..c27d6f8ff 100644 --- a/sys/wasapi2/gstwasapi2client.cpp +++ b/sys/wasapi2/gstwasapi2client.cpp @@ -41,51 +41,6 @@ #include <locale> #include <codecvt> -/* Desktop only defines */ -#ifndef KSAUDIO_SPEAKER_MONO -#define KSAUDIO_SPEAKER_MONO (SPEAKER_FRONT_CENTER) -#endif -#ifndef KSAUDIO_SPEAKER_1POINT1 -#define KSAUDIO_SPEAKER_1POINT1 (SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY) -#endif -#ifndef KSAUDIO_SPEAKER_STEREO -#define KSAUDIO_SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) -#endif -#ifndef KSAUDIO_SPEAKER_2POINT1 -#define KSAUDIO_SPEAKER_2POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY) -#endif -#ifndef KSAUDIO_SPEAKER_3POINT0 -#define KSAUDIO_SPEAKER_3POINT0 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER) -#endif -#ifndef KSAUDIO_SPEAKER_3POINT1 -#define KSAUDIO_SPEAKER_3POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ - SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY) -#endif -#ifndef KSAUDIO_SPEAKER_QUAD -#define KSAUDIO_SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ - SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) -#endif -#define KSAUDIO_SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ - SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER) -#ifndef KSAUDIO_SPEAKER_5POINT0 -#define KSAUDIO_SPEAKER_5POINT0 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | \ - SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) -#endif -#define KSAUDIO_SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ - SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \ - SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) -#ifndef KSAUDIO_SPEAKER_7POINT0 -#define KSAUDIO_SPEAKER_7POINT0 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | \ - SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | \ - SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) -#endif -#ifndef KSAUDIO_SPEAKER_7POINT1 -#define KSAUDIO_SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ - SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \ - SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | \ - SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER) -#endif - /* *INDENT-OFF* */ using namespace ABI::Windows::ApplicationModel::Core; using namespace ABI::Windows::Foundation; @@ -107,8 +62,7 @@ G_END_DECLS static void gst_wasapi2_client_on_device_activated (GstWasapi2Client * client, - IAudioClient3 * audio_client); - + IAudioClient * audio_client); /* *INDENT-OFF* */ class GstWasapiDeviceActivator @@ -150,7 +104,7 @@ public: STDMETHOD(ActivateCompleted) (IActivateAudioInterfaceAsyncOperation *async_op) { - ComPtr<IAudioClient3> audio_client; + ComPtr<IAudioClient> audio_client; HRESULT hr = S_OK; HRESULT hr_async_op = S_OK; ComPtr<IUnknown> audio_interface; @@ -255,6 +209,7 @@ private: ComPtr<ICoreDispatcher> dispatcher_; }; /* *INDENT-ON* */ + typedef enum { GST_WASAPI2_CLIENT_ACTIVATE_FAILED = -1, @@ -270,47 +225,27 @@ enum PROP_DEVICE_NAME, PROP_DEVICE_INDEX, PROP_DEVICE_CLASS, - PROP_LOW_LATENCY, PROP_DISPATCHER, }; #define DEFAULT_DEVICE_INDEX -1 #define DEFAULT_DEVICE_CLASS GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE -#define DEFAULT_LOW_LATENCY FALSE struct _GstWasapi2Client { GstObject parent; GstWasapi2ClientDeviceClass device_class; - gboolean low_latency; gchar *device_id; gchar *device_name; gint device_index; gpointer dispatcher; - IAudioClient3 *audio_client; - IAudioCaptureClient *audio_capture_client; - IAudioRenderClient *audio_render_client; - ISimpleAudioVolume *audio_volume; + IAudioClient *audio_client; GstWasapiDeviceActivator *activator; - WAVEFORMATEX *mix_format; GstCaps *supported_caps; - HANDLE event_handle; - HANDLE cancellable; - gboolean opened; - gboolean running; - - guint32 device_period; - guint32 buffer_frame_count; - - GstAudioChannelPosition *positions; - - /* Used for capture mode */ - GstAdapter *adapter; - GThread *thread; GMutex lock; GCond cond; @@ -330,7 +265,7 @@ gst_wasapi2_client_device_class_get_type (void) static const GEnumValue types[] = { {GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE, "Capture", "capture"}, {GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER, "Render", "render"}, - {0, NULL, NULL} + {0, nullptr, nullptr} }; if (g_once_init_enter (&class_type)) { @@ -342,7 +277,6 @@ gst_wasapi2_client_device_class_get_type (void) } static void gst_wasapi2_client_constructed (GObject * object); -static void gst_wasapi2_client_dispose (GObject * object); static void gst_wasapi2_client_finalize (GObject * object); static void gst_wasapi2_client_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); @@ -365,17 +299,16 @@ gst_wasapi2_client_class_init (GstWasapi2ClientClass * klass) G_PARAM_STATIC_STRINGS); gobject_class->constructed = gst_wasapi2_client_constructed; - gobject_class->dispose = gst_wasapi2_client_dispose; gobject_class->finalize = gst_wasapi2_client_finalize; gobject_class->get_property = gst_wasapi2_client_get_property; gobject_class->set_property = gst_wasapi2_client_set_property; g_object_class_install_property (gobject_class, PROP_DEVICE, g_param_spec_string ("device", "Device", - "WASAPI playback device as a GUID string", NULL, param_flags)); + "WASAPI playback device as a GUID string", nullptr, param_flags)); g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, g_param_spec_string ("device-name", "Device Name", - "The human-readable device name", NULL, param_flags)); + "The human-readable device name", nullptr, param_flags)); g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX, g_param_spec_int ("device-index", "Device Index", "The zero-based device index", -1, G_MAXINT, DEFAULT_DEVICE_INDEX, @@ -384,10 +317,6 @@ gst_wasapi2_client_class_init (GstWasapi2ClientClass * klass) g_param_spec_enum ("device-class", "Device Class", "Device class", GST_TYPE_WASAPI2_CLIENT_DEVICE_CLASS, DEFAULT_DEVICE_CLASS, param_flags)); - g_object_class_install_property (gobject_class, PROP_LOW_LATENCY, - g_param_spec_boolean ("low-latency", "Low latency", - "Optimize all settings for lowest latency. Always safe to enable.", - DEFAULT_LOW_LATENCY, param_flags)); g_object_class_install_property (gobject_class, PROP_DISPATCHER, g_param_spec_pointer ("dispatcher", "Dispatcher", "ICoreDispatcher COM object to use", param_flags)); @@ -398,11 +327,6 @@ gst_wasapi2_client_init (GstWasapi2Client * self) { self->device_index = DEFAULT_DEVICE_INDEX; self->device_class = DEFAULT_DEVICE_CLASS; - self->low_latency = DEFAULT_LOW_LATENCY; - - self->adapter = gst_adapter_new (); - self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL); - self->cancellable = CreateEvent (NULL, TRUE, FALSE, NULL); g_mutex_init (&self->lock); g_cond_init (&self->cond); @@ -438,44 +362,26 @@ gst_wasapi2_client_constructed (GObject * object) } static void -gst_wasapi2_client_dispose (GObject * object) +gst_wasapi2_client_finalize (GObject * object) { GstWasapi2Client *self = GST_WASAPI2_CLIENT (object); - GST_DEBUG_OBJECT (self, "dispose"); - - gst_clear_caps (&self->supported_caps); - if (self->loop) { g_main_loop_quit (self->loop); g_thread_join (self->thread); g_main_context_unref (self->context); g_main_loop_unref (self->loop); - self->thread = NULL; - self->context = NULL; - self->loop = NULL; + self->thread = nullptr; + self->context = nullptr; + self->loop = nullptr; } - g_clear_object (&self->adapter); - - G_OBJECT_CLASS (parent_class)->dispose (object); -} - -static void -gst_wasapi2_client_finalize (GObject * object) -{ - GstWasapi2Client *self = GST_WASAPI2_CLIENT (object); + gst_clear_caps (&self->supported_caps); g_free (self->device_id); g_free (self->device_name); - g_free (self->positions); - - CoTaskMemFree (self->mix_format); - CloseHandle (self->event_handle); - CloseHandle (self->cancellable); - g_mutex_clear (&self->lock); g_cond_clear (&self->cond); @@ -504,9 +410,6 @@ gst_wasapi2_client_get_property (GObject * object, guint prop_id, case PROP_DEVICE_CLASS: g_value_set_enum (value, self->device_class); break; - case PROP_LOW_LATENCY: - g_value_set_boolean (value, self->low_latency); - break; case PROP_DISPATCHER: g_value_set_pointer (value, self->dispatcher); break; @@ -538,9 +441,6 @@ gst_wasapi2_client_set_property (GObject * object, guint prop_id, self->device_class = (GstWasapi2ClientDeviceClass) g_value_get_enum (value); break; - case PROP_LOW_LATENCY: - self->low_latency = g_value_get_boolean (value); - break; case PROP_DISPATCHER: self->dispatcher = g_value_get_pointer (value); break; @@ -564,7 +464,7 @@ gst_wasapi2_client_main_loop_running_cb (GstWasapi2Client * self) static void gst_wasapi2_client_on_device_activated (GstWasapi2Client * self, - IAudioClient3 * audio_client) + IAudioClient * audio_client) { GST_INFO_OBJECT (self, "Device activated"); @@ -912,7 +812,7 @@ run_loop: source = g_idle_source_new (); g_source_set_callback (source, - (GSourceFunc) gst_wasapi2_client_main_loop_running_cb, self, NULL); + (GSourceFunc) gst_wasapi2_client_main_loop_running_cb, self, nullptr); g_source_attach (source, self->context); g_source_unref (source); @@ -922,32 +822,7 @@ run_loop: g_main_context_pop_thread_default (self->context); - gst_wasapi2_client_stop (self); - - if (self->audio_volume) { - /* this mute state seems to be global setting for this device - * Explicitly disable mute for later use of this audio device - * by other application. Otherwise users would blame GStreamer - * if we close audio device with muted state */ - self->audio_volume->SetMute (FALSE, nullptr); - self->audio_volume->Release (); - self->audio_volume = NULL; - } - - if (self->audio_render_client) { - self->audio_render_client->Release (); - self->audio_render_client = NULL; - } - - if (self->audio_capture_client) { - self->audio_capture_client->Release (); - self->audio_capture_client = NULL; - } - - if (self->audio_client) { - self->audio_client->Release (); - self->audio_client = NULL; - } + GST_WASAPI2_CLEAR_COM (self->audio_client); /* Reset explicitly to ensure that it happens before * RoInitializeWrapper dtor is called */ @@ -955,986 +830,48 @@ run_loop: GST_DEBUG_OBJECT (self, "Exit thread function"); - return NULL; -} - -static const gchar * -gst_waveformatex_to_audio_format (WAVEFORMATEXTENSIBLE * format) -{ - const gchar *fmt_str = NULL; - GstAudioFormat fmt = GST_AUDIO_FORMAT_UNKNOWN; - - if (format->Format.wFormatTag == WAVE_FORMAT_PCM) { - fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN, - format->Format.wBitsPerSample, format->Format.wBitsPerSample); - } else if (format->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { - if (format->Format.wBitsPerSample == 32) - fmt = GST_AUDIO_FORMAT_F32LE; - else if (format->Format.wBitsPerSample == 64) - fmt = GST_AUDIO_FORMAT_F64LE; - } else if (format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - if (IsEqualGUID (format->SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) { - fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN, - format->Format.wBitsPerSample, format->Samples.wValidBitsPerSample); - } else if (IsEqualGUID (format->SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { - if (format->Format.wBitsPerSample == 32 - && format->Samples.wValidBitsPerSample == 32) - fmt = GST_AUDIO_FORMAT_F32LE; - else if (format->Format.wBitsPerSample == 64 && - format->Samples.wValidBitsPerSample == 64) - fmt = GST_AUDIO_FORMAT_F64LE; - } - } - - if (fmt != GST_AUDIO_FORMAT_UNKNOWN) - fmt_str = gst_audio_format_to_string (fmt); - - return fmt_str; -} - -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; -} - -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} -}; - -static DWORD default_ch_masks[] = { - 0, - KSAUDIO_SPEAKER_MONO, - /* 2ch */ - KSAUDIO_SPEAKER_STEREO, - /* 2.1ch */ - /* KSAUDIO_SPEAKER_3POINT0 ? */ - KSAUDIO_SPEAKER_2POINT1, - /* 4ch */ - /* KSAUDIO_SPEAKER_3POINT1 or KSAUDIO_SPEAKER_SURROUND ? */ - KSAUDIO_SPEAKER_QUAD, - /* 5ch */ - KSAUDIO_SPEAKER_5POINT0, - /* 5.1ch */ - KSAUDIO_SPEAKER_5POINT1, - /* 7ch */ - KSAUDIO_SPEAKER_7POINT0, - /* 7.1ch */ - KSAUDIO_SPEAKER_7POINT1, -}; - -/* 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_wasapi2_util_waveformatex_to_channel_mask (WORD nChannels, - DWORD dwChannelMask, GstAudioChannelPosition ** out_position) -{ - int ii, ch; - guint64 mask = 0; - 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 ("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 ("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, ch = 0; ii < G_N_ELEMENTS (wasapi_to_gst_pos) && ch < nChannels; - ii++) { - if (!(dwChannelMask & wasapi_to_gst_pos[ii].wasapi_pos)) - /* no match, try next */ - continue; - mask |= G_GUINT64_CONSTANT (1) << wasapi_to_gst_pos[ii].gst_pos; - pos[ch++] = wasapi_to_gst_pos[ii].gst_pos; - } - - /* XXX: Warn if some channel masks couldn't be mapped? */ - - GST_DEBUG ("Converted WASAPI mask 0x%" G_GINT64_MODIFIER "x -> 0x%" - G_GINT64_MODIFIER "x", (guint64) dwChannelMask, (guint64) mask); - -out: - if (out_position) - *out_position = pos; - return mask; -} - -static gboolean -gst_wasapi2_util_parse_waveformatex (WAVEFORMATEXTENSIBLE * format, - GstCaps * template_caps, GstCaps ** out_caps, - GstAudioChannelPosition ** out_positions) -{ - const gchar *afmt; - guint64 channel_mask = 0; - DWORD dwChannelMask = 0; - WORD nChannels; - - *out_caps = NULL; - - /* TODO: handle SPDIF and other encoded formats */ - - /* 1 or 2 channels <= 16 bits sample size OR - * 1 or 2 channels > 16 bits sample size or >2 channels */ - if (format->Format.wFormatTag != WAVE_FORMAT_PCM && - format->Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT && - format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) - /* Unhandled format tag */ - 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 FALSE; - - *out_caps = gst_caps_copy (template_caps); - - nChannels = format->Format.nChannels; - if (format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - dwChannelMask = format->dwChannelMask; - } - - if (nChannels > 2 && !dwChannelMask) { - GST_WARNING ("Unknown channel mask value for %d channel stream", - format->Format.nChannels); - if (nChannels >= G_N_ELEMENTS (default_ch_masks)) { - GST_ERROR ("To may channels %d", nChannels); - return FALSE; - } - - dwChannelMask = default_ch_masks[nChannels]; - } - - channel_mask = - gst_wasapi2_util_waveformatex_to_channel_mask (nChannels, - dwChannelMask, out_positions); - - gst_caps_set_simple (*out_caps, - "format", G_TYPE_STRING, afmt, - "channels", G_TYPE_INT, format->Format.nChannels, - "rate", G_TYPE_INT, format->Format.nSamplesPerSec, NULL); - - if (channel_mask) { - gst_caps_set_simple (*out_caps, - "channel-mask", GST_TYPE_BITMASK, channel_mask, NULL); - } - - return TRUE; + return nullptr; } GstCaps * gst_wasapi2_client_get_caps (GstWasapi2Client * client) { - WAVEFORMATEX *format = NULL; + WAVEFORMATEX *mix_format = nullptr; static GstStaticCaps static_caps = GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS); GstCaps *scaps; HRESULT hr; - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), NULL); + g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), nullptr); if (client->supported_caps) return gst_caps_ref (client->supported_caps); if (!client->audio_client) { GST_WARNING_OBJECT (client, "IAudioClient3 wasn't configured"); - return NULL; + return nullptr; } - CoTaskMemFree (client->mix_format); - client->mix_format = nullptr; - - g_clear_pointer (&client->positions, g_free); - - hr = client->audio_client->GetMixFormat (&format); - if (!gst_wasapi2_result (hr)) - return NULL; + hr = client->audio_client->GetMixFormat (&mix_format); + if (!gst_wasapi2_result (hr)) { + GST_WARNING_OBJECT (client, "Failed to get mix format"); + return nullptr; + } scaps = gst_static_caps_get (&static_caps); - gst_wasapi2_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format, - scaps, &client->supported_caps, &client->positions); + gst_wasapi2_util_parse_waveformatex (mix_format, + scaps, &client->supported_caps, nullptr); gst_caps_unref (scaps); - client->mix_format = format; + CoTaskMemFree (mix_format); if (!client->supported_caps) { GST_ERROR_OBJECT (client, "No caps from subclass"); - return NULL; + return nullptr; } return gst_caps_ref (client->supported_caps); } -static HRESULT -gst_wasapi2_client_initialize_audio_client3 (GstWasapi2Client * self) -{ - HRESULT hr = S_OK; - UINT32 default_period, fundamental_period, min_period, max_period; - /* AUDCLNT_STREAMFLAGS_NOPERSIST is not allowed for - * InitializeSharedAudioStream */ - DWORD stream_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; - WAVEFORMATEX *format = NULL; - UINT32 period; - IAudioClient3 *audio_client = self->audio_client; - - hr = audio_client->GetSharedModeEnginePeriod (self->mix_format, - &default_period, &fundamental_period, &min_period, &max_period); - if (!gst_wasapi2_result (hr)) - goto done; - - GST_INFO_OBJECT (self, "Using IAudioClient3, default period %d frames, " - "fundamental period %d frames, minimum period %d frames, maximum period " - "%d frames", default_period, fundamental_period, min_period, max_period); - - hr = audio_client->InitializeSharedAudioStream (stream_flags, min_period, - self->mix_format, nullptr); - - if (!gst_wasapi2_result (hr)) { - GST_WARNING_OBJECT (self, "Failed to initialize IAudioClient3"); - goto done; - } - - /* query period again to be ensured */ - hr = audio_client->GetCurrentSharedModeEnginePeriod (&format, &period); - if (!gst_wasapi2_result (hr)) { - GST_WARNING_OBJECT (self, "Failed to get current period"); - goto done; - } - - self->device_period = period; - -done: - CoTaskMemFree (format); - - return hr; -} - -static void -gst_wasapi2_util_get_best_buffer_sizes (GstAudioRingBufferSpec * spec, - REFERENCE_TIME default_period, REFERENCE_TIME min_period, - REFERENCE_TIME * ret_period, REFERENCE_TIME * ret_buffer_duration) -{ - REFERENCE_TIME use_period, use_buffer; - - /* Shared mode always runs at the default period, so if we want a larger - * period (for lower CPU usage), we do it as a multiple of that */ - use_period = default_period; - - /* Ensure that the period (latency_time) used is an integral multiple of - * either the default period or the minimum period */ - use_period = use_period * MAX ((spec->latency_time * 10) / use_period, 1); - - /* Ask WASAPI to create a software ringbuffer of at least this size; it may - * be larger so the actual buffer time may be different, which is why after - * initialization we read the buffer duration actually in-use and set - * segsize/segtotal from that. */ - use_buffer = spec->buffer_time * 10; - /* Has to be at least twice the period */ - if (use_buffer < 2 * use_period) - use_buffer = 2 * use_period; - - *ret_period = use_period; - *ret_buffer_duration = use_buffer; -} - -static HRESULT -gst_wasapi2_client_initialize_audio_client (GstWasapi2Client * self, - GstAudioRingBufferSpec * spec) -{ - REFERENCE_TIME default_period, min_period; - REFERENCE_TIME device_period, device_buffer_duration; - guint rate; - DWORD stream_flags = - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST; - HRESULT hr; - IAudioClient3 *audio_client = self->audio_client; - - hr = audio_client->GetDevicePeriod (&default_period, &min_period); - if (!gst_wasapi2_result (hr)) { - GST_WARNING_OBJECT (self, "Couldn't get device period info"); - return hr; - } - - GST_INFO_OBJECT (self, "wasapi2 default period: %" G_GINT64_FORMAT - ", min period: %" G_GINT64_FORMAT, default_period, min_period); - - rate = GST_AUDIO_INFO_RATE (&spec->info); - - if (self->low_latency) { - device_period = default_period; - /* this should be same as hnsPeriodicity - * when AUDCLNT_STREAMFLAGS_EVENTCALLBACK is used - * And in case of shared mode, hnsPeriodicity should be zero, so - * this value should be zero as well */ - device_buffer_duration = 0; - } else { - /* Clamp values to integral multiples of an appropriate period */ - gst_wasapi2_util_get_best_buffer_sizes (spec, - default_period, min_period, &device_period, &device_buffer_duration); - } - - hr = audio_client->Initialize (AUDCLNT_SHAREMODE_SHARED, stream_flags, - device_buffer_duration, - /* This must always be 0 in shared mode */ - 0, self->mix_format, nullptr); - if (!gst_wasapi2_result (hr)) { - GST_WARNING_OBJECT (self, "Couldn't initialize audioclient"); - return hr; - } - - /* device_period can be a non-power-of-10 value so round while converting */ - self->device_period = - gst_util_uint64_scale_round (device_period, rate * 100, GST_SECOND); - - return S_OK; -} - -HRESULT -gst_wasapi2_client_open (GstWasapi2Client * client, - GstAudioRingBufferSpec * spec, GstAudioRingBuffer * buf) -{ - HRESULT hr = E_FAIL; - REFERENCE_TIME latency_rt; - guint bpf, rate; - IAudioClient3 *audio_client; - /* *INDENT-OFF* */ - ComPtr<ISimpleAudioVolume> audio_volume; - /* *INDENT-ON* */ - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - - /* FIXME: Once IAudioClient3 was initialized, we may need to re-open - * IAudioClient3 in order to handle audio format change */ - if (client->opened) { - GST_INFO_OBJECT (client, "IAudioClient3 object is initialized already"); - return S_OK; - } - - audio_client = client->audio_client; - - if (!audio_client) { - GST_ERROR_OBJECT (client, "IAudioClient3 object wasn't configured"); - return E_FAIL; - } - - if (!client->mix_format) { - GST_ERROR_OBJECT (client, "Unknown mix format"); - return E_FAIL; - } - - /* Only use audioclient3 when low-latency is requested because otherwise - * very slow machines and VMs with 1 CPU allocated will get glitches: - * https://bugzilla.gnome.org/show_bug.cgi?id=794497 */ - if (client->low_latency) - hr = gst_wasapi2_client_initialize_audio_client3 (client); - - /* Try again if IAudioClinet3 API is unavailable. - * NOTE: IAudioClinet3:: methods might not be available for default device - * NOTE: The default device is a special device which is needed for supporting - * automatic stream routing - * https://docs.microsoft.com/en-us/windows/win32/coreaudio/automatic-stream-routing - */ - if (FAILED (hr)) - hr = gst_wasapi2_client_initialize_audio_client (client, spec); - - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Failed to initialize audioclient"); - return hr; - } - - bpf = GST_AUDIO_INFO_BPF (&spec->info); - rate = GST_AUDIO_INFO_RATE (&spec->info); - - /* Total size in frames of the allocated buffer that we will read from */ - hr = audio_client->GetBufferSize (&client->buffer_frame_count); - if (!gst_wasapi2_result (hr)) - return hr; - - GST_INFO_OBJECT (client, "buffer size is %i frames, device period is %i " - "frames, bpf is %i bytes, rate is %i Hz", client->buffer_frame_count, - client->device_period, bpf, rate); - - /* Actual latency-time/buffer-time will be different now */ - spec->segsize = client->device_period * bpf; - - /* We need a minimum of 2 segments to ensure glitch-free playback */ - spec->segtotal = MAX (client->buffer_frame_count * bpf / spec->segsize, 2); - - GST_INFO_OBJECT (client, "segsize is %i, segtotal is %i", spec->segsize, - spec->segtotal); - - /* Get WASAPI latency for logging */ - hr = audio_client->GetStreamLatency (&latency_rt); - if (!gst_wasapi2_result (hr)) - return hr; - - GST_INFO_OBJECT (client, "wasapi2 stream latency: %" G_GINT64_FORMAT " (%" - G_GINT64_FORMAT " ms)", latency_rt, latency_rt / 10000); - - /* Set the event handler which will trigger read/write */ - hr = audio_client->SetEventHandle (client->event_handle); - if (!gst_wasapi2_result (hr)) - return hr; - - if (client->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER) { - /* *INDENT-OFF* */ - ComPtr<IAudioRenderClient> render_client; - /* *INDENT-ON* */ - - hr = audio_client->GetService (IID_PPV_ARGS (&render_client)); - if (!gst_wasapi2_result (hr)) - return hr; - - client->audio_render_client = render_client.Detach (); - } else { - /* *INDENT-OFF* */ - ComPtr<IAudioCaptureClient> capture_client; - /* *INDENT-ON* */ - - hr = audio_client->GetService (IID_PPV_ARGS (&capture_client)); - if (!gst_wasapi2_result (hr)) - return hr; - - client->audio_capture_client = capture_client.Detach (); - } - - hr = audio_client->GetService (IID_PPV_ARGS (&audio_volume)); - if (!gst_wasapi2_result (hr)) - return hr; - - client->audio_volume = audio_volume.Detach (); - client->audio_volume->SetMute (FALSE, nullptr); - - gst_audio_ring_buffer_set_channel_positions (buf, client->positions); - - client->opened = TRUE; - - return S_OK; -} - -/* Get the empty space in the buffer that we have to write to */ -static HRESULT -gst_wasapi2_client_get_can_frames (GstWasapi2Client * self, guint32 * n_frames) -{ - HRESULT hr; - UINT32 n_frames_padding; - IAudioClient3 *audio_client = self->audio_client; - - *n_frames = 0; - - if (!audio_client) { - GST_WARNING_OBJECT (self, "IAudioClient3 wasn't configured"); - return E_FAIL; - } - - /* Frames the card hasn't rendered yet */ - hr = audio_client->GetCurrentPadding (&n_frames_padding); - if (!gst_wasapi2_result (hr)) - return hr; - - GST_LOG_OBJECT (self, "%d unread frames (padding)", n_frames_padding); - - /* We can write out these many frames */ - *n_frames = self->buffer_frame_count - n_frames_padding; - - return S_OK; -} - -HRESULT -gst_wasapi2_client_start (GstWasapi2Client * client) -{ - HRESULT hr; - IAudioClient3 *audio_client; - WAVEFORMATEX *mix_format; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - - if (client->running) { - GST_WARNING_OBJECT (client, "IAudioClient3 is running already"); - return S_OK; - } - - audio_client = client->audio_client; - mix_format = client->mix_format; - - if (!audio_client) { - GST_ERROR_OBJECT (client, "IAudioClient object wasn't configured"); - return E_FAIL; - } - - if (!mix_format) { - GST_ERROR_OBJECT (client, "Unknown MixFormat"); - return E_FAIL; - } - - if (client->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE && - !client->audio_capture_client) { - GST_ERROR_OBJECT (client, "IAudioCaptureClient wasn't configured"); - return E_FAIL; - } - - if (client->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER && - !client->audio_render_client) { - GST_ERROR_OBJECT (client, "IAudioRenderClient wasn't configured"); - return E_FAIL; - } - - ResetEvent (client->cancellable); - - /* To avoid start-up glitches, before starting the streaming, we fill the - * buffer with silence as recommended by the documentation: - * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370879%28v=vs.85%29.aspx */ - if (client->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER) { - IAudioRenderClient *render_client = client->audio_render_client; - guint32 n_frames, len; - BYTE *dst = NULL; - - hr = gst_wasapi2_client_get_can_frames (client, &n_frames); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, - "should have more than %i frames to write", n_frames); - return hr; - } - - len = n_frames * mix_format->nBlockAlign; - - hr = render_client->GetBuffer (n_frames, &dst); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Couldn't get buffer"); - return hr; - } - - GST_DEBUG_OBJECT (client, "pre-wrote %i bytes of silence", len); - - hr = render_client->ReleaseBuffer (n_frames, AUDCLNT_BUFFERFLAGS_SILENT); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Couldn't release buffer"); - return hr; - } - } - - hr = audio_client->Start (); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Couldn't start audio client"); - return hr; - } - - client->running = TRUE; - gst_adapter_clear (client->adapter); - - return S_OK; -} - -HRESULT -gst_wasapi2_client_stop (GstWasapi2Client * client) -{ - HRESULT hr; - IAudioClient3 *audio_client; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - - audio_client = client->audio_client; - - if (!client->running) { - GST_DEBUG_OBJECT (client, "We are not running now"); - return S_OK; - } - - if (!client->audio_client) { - GST_ERROR_OBJECT (client, "IAudioClient3 object wasn't configured"); - return E_FAIL; - } - - client->running = FALSE; - SetEvent (client->cancellable); - - hr = audio_client->Stop (); - if (!gst_wasapi2_result (hr)) - return hr; - - /* reset state for reuse case */ - return audio_client->Reset (); -} - -HRESULT -gst_wasapi2_client_read (GstWasapi2Client * client, gpointer data, guint length, - guint * read_length) -{ - IAudioCaptureClient *capture_client; - WAVEFORMATEX *mix_format; - HRESULT hr; - BYTE *from = NULL; - guint wanted = length; - guint bpf; - DWORD flags; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - g_return_val_if_fail (client->audio_capture_client != NULL, E_INVALIDARG); - g_return_val_if_fail (client->mix_format != NULL, E_INVALIDARG); - g_return_val_if_fail (read_length != NULL, E_INVALIDARG); - - *read_length = 0; - - capture_client = client->audio_capture_client; - mix_format = client->mix_format; - - if (!client->running) { - GST_ERROR_OBJECT (client, "client is not running now"); - return E_FAIL; - } - - /* If we've accumulated enough data, return it immediately */ - if (gst_adapter_available (client->adapter) >= wanted) { - memcpy (data, gst_adapter_map (client->adapter, wanted), wanted); - gst_adapter_flush (client->adapter, wanted); - GST_DEBUG_OBJECT (client, "Adapter has enough data, returning %i", wanted); - - *read_length = wanted; - - return S_OK; - } - - bpf = mix_format->nBlockAlign; - - while (wanted > 0) { - DWORD dwWaitResult; - guint got_frames, avail_frames, n_frames, want_frames, read_len; - HANDLE event_handle[2]; - - event_handle[0] = client->event_handle; - event_handle[1] = client->cancellable; - - /* Wait for data to become available */ - dwWaitResult = WaitForMultipleObjects (2, event_handle, FALSE, INFINITE); - if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_OBJECT_0 + 1) { - GST_ERROR_OBJECT (client, "Error waiting for event handle: %x", - (guint) dwWaitResult); - return E_FAIL; - } - - if (!client->running) { - GST_DEBUG_OBJECT (client, "Cancelled"); - return S_OK; - } - - hr = capture_client->GetBuffer (&from, &got_frames, &flags, nullptr, - nullptr); - if (!gst_wasapi2_result (hr)) { - if (hr == AUDCLNT_S_BUFFER_EMPTY) { - GST_INFO_OBJECT (client, "Client buffer is empty, retry"); - return S_OK; - } - - GST_ERROR_OBJECT (client, "Couldn't get buffer from capture client"); - return hr; - } - - if (got_frames == 0) { - GST_DEBUG_OBJECT (client, "No buffer to read"); - capture_client->ReleaseBuffer (got_frames); - return S_OK; - } - - if (G_UNLIKELY (flags != 0)) { - /* https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ne-audioclient-_audclnt_bufferflags */ - if (flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) - GST_DEBUG_OBJECT (client, "WASAPI reported discontinuity (glitch?)"); - if (flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) - GST_DEBUG_OBJECT (client, "WASAPI reported a timestamp error"); - } - - /* Copy all the frames we got into the adapter, and then extract at most - * @wanted size of frames from it. This helps when ::GetBuffer returns more - * data than we can handle right now. */ - { - GstBuffer *tmp = gst_buffer_new_allocate (NULL, got_frames * bpf, NULL); - /* If flags has AUDCLNT_BUFFERFLAGS_SILENT, we will ignore the actual - * data and write out silence, see: - * https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ne-audioclient-_audclnt_bufferflags */ - if (flags & AUDCLNT_BUFFERFLAGS_SILENT) - memset (from, 0, got_frames * bpf); - gst_buffer_fill (tmp, 0, from, got_frames * bpf); - gst_adapter_push (client->adapter, tmp); - } - - /* Release all captured buffers; we copied them above */ - hr = capture_client->ReleaseBuffer (got_frames); - from = NULL; - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Failed to release buffer"); - return hr; - } - - want_frames = wanted / bpf; - avail_frames = gst_adapter_available (client->adapter) / bpf; - - /* Only copy data that will fit into the allocated buffer of size @length */ - n_frames = MIN (avail_frames, want_frames); - read_len = n_frames * bpf; - - if (read_len == 0) { - GST_WARNING_OBJECT (client, "No data to read"); - return S_OK; - } - - GST_LOG_OBJECT (client, "frames captured: %d (%d bytes), " - "can read: %d (%d bytes), will read: %d (%d bytes), " - "adapter has: %d (%d bytes)", got_frames, got_frames * bpf, want_frames, - wanted, n_frames, read_len, avail_frames, avail_frames * bpf); - - memcpy (data, gst_adapter_map (client->adapter, read_len), read_len); - gst_adapter_flush (client->adapter, read_len); - wanted -= read_len; - } - - *read_length = length; - - return S_OK; -} - -HRESULT -gst_wasapi2_client_write (GstWasapi2Client * client, gpointer data, - guint length, guint * write_length) -{ - IAudioRenderClient *render_client; - WAVEFORMATEX *mix_format; - HRESULT hr; - BYTE *dst = nullptr; - DWORD dwWaitResult; - guint can_frames, have_frames, n_frames, write_len = 0; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - g_return_val_if_fail (client->audio_render_client != NULL, E_INVALIDARG); - g_return_val_if_fail (client->mix_format != NULL, E_INVALIDARG); - g_return_val_if_fail (write_length != NULL, E_INVALIDARG); - - *write_length = 0; - - if (!client->running) { - GST_WARNING_OBJECT (client, "client is not running now"); - return -1; - } - - render_client = client->audio_render_client; - mix_format = client->mix_format; - - /* We have N frames to be written out */ - have_frames = length / (mix_format->nBlockAlign); - - /* In shared mode we can write parts of the buffer, so only wait - * in case we can't write anything */ - hr = gst_wasapi2_client_get_can_frames (client, &can_frames); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Error getting frames to write to"); - return hr; - } - - if (can_frames == 0) { - HANDLE event_handle[2]; - - event_handle[0] = client->event_handle; - event_handle[1] = client->cancellable; - - dwWaitResult = WaitForMultipleObjects (2, event_handle, FALSE, INFINITE); - if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_OBJECT_0 + 1) { - GST_ERROR_OBJECT (client, "Error waiting for event handle: %x", - (guint) dwWaitResult); - return E_FAIL; - } - - if (!client->running) { - GST_DEBUG_OBJECT (client, "Cancelled"); - return S_OK; - } - - hr = gst_wasapi2_client_get_can_frames (client, &can_frames); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Error getting frames to write to"); - return hr; - } - } - - /* We will write out these many frames, and this much length */ - n_frames = MIN (can_frames, have_frames); - write_len = n_frames * mix_format->nBlockAlign; - - GST_LOG_OBJECT (client, "total: %d, have_frames: %d (%d bytes), " - "can_frames: %d, will write: %d (%d bytes)", client->buffer_frame_count, - have_frames, length, can_frames, n_frames, write_len); - - hr = render_client->GetBuffer (n_frames, &dst); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Couldn't get buffer from client"); - return hr; - } - - memcpy (dst, data, write_len); - hr = render_client->ReleaseBuffer (n_frames, 0); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (client, "Failed to release buffer"); - return hr; - } - - *write_length = write_len; - - return S_OK; -} - -HRESULT -gst_wasapi2_client_delay (GstWasapi2Client * client, guint32 * delay) -{ - IAudioClient3 *audio_client; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - g_return_val_if_fail (delay != nullptr, E_INVALIDARG); - - *delay = 0; - - audio_client = client->audio_client; - if (!audio_client) { - GST_WARNING_OBJECT (client, "IAudioClient3 wasn't configured"); - return E_FAIL; - } - - return audio_client->GetCurrentPadding (delay); -} - -HRESULT -gst_wasapi2_client_set_mute (GstWasapi2Client * client, gboolean mute) -{ - HRESULT hr; - ISimpleAudioVolume *audio_volume; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - - audio_volume = client->audio_volume; - - if (!audio_volume) { - GST_WARNING_OBJECT (client, "ISimpleAudioVolume object wasn't configured"); - return E_FAIL; - } - - hr = audio_volume->SetMute (mute, nullptr); - GST_DEBUG_OBJECT (client, "Set mute %s, hr: 0x%x", - mute ? "enabled" : "disabled", (gint) hr); - - return hr; -} - -HRESULT -gst_wasapi2_client_get_mute (GstWasapi2Client * client, gboolean * mute) -{ - HRESULT hr; - ISimpleAudioVolume *audio_volume; - BOOL current_mute = FALSE; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - g_return_val_if_fail (mute != NULL, E_INVALIDARG); - - audio_volume = client->audio_volume; - - if (!audio_volume) { - GST_WARNING_OBJECT (client, "ISimpleAudioVolume object wasn't configured"); - return E_FAIL; - } - - hr = audio_volume->GetMute (¤t_mute); - if (!gst_wasapi2_result (hr)) - return hr; - - *mute = (gboolean) current_mute; - - return S_OK; -} - -HRESULT -gst_wasapi2_client_set_volume (GstWasapi2Client * client, gfloat volume) -{ - HRESULT hr; - ISimpleAudioVolume *audio_volume; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - g_return_val_if_fail (volume >= 0 && volume <= 1.0, E_INVALIDARG); - - audio_volume = client->audio_volume; - - if (!audio_volume) { - GST_WARNING_OBJECT (client, "ISimpleAudioVolume object wasn't configured"); - return E_FAIL; - } - - hr = audio_volume->SetMasterVolume (volume, nullptr); - GST_DEBUG_OBJECT (client, "Set volume %.2f hr: 0x%x", volume, (gint) hr); - - return hr; -} - -HRESULT -gst_wasapi2_client_get_volume (GstWasapi2Client * client, gfloat * volume) -{ - HRESULT hr; - ISimpleAudioVolume *audio_volume; - float current_volume = FALSE; - - g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), E_INVALIDARG); - g_return_val_if_fail (volume != NULL, E_INVALIDARG); - - audio_volume = client->audio_volume; - - if (!audio_volume) { - GST_WARNING_OBJECT (client, "ISimpleAudioVolume object wasn't configured"); - return E_FAIL; - } - - hr = audio_volume->GetMasterVolume (¤t_volume); - if (!gst_wasapi2_result (hr)) - return hr; - - *volume = current_volume; - - return S_OK; -} - gboolean gst_wasapi2_client_ensure_activation (GstWasapi2Client * client) { @@ -1980,8 +917,7 @@ find_dispatcher (ICoreDispatcher ** dispatcher) GstWasapi2Client * gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class, - gboolean low_latency, gint device_index, const gchar * device_id, - gpointer dispatcher) + gint device_index, const gchar * device_id, gpointer dispatcher) { GstWasapi2Client *self; /* *INDENT-OFF* */ @@ -2007,9 +943,8 @@ gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class, } self = (GstWasapi2Client *) g_object_new (GST_TYPE_WASAPI2_CLIENT, - "device-class", device_class, "low-latency", low_latency, - "device-index", device_index, "device", device_id, - "dispatcher", dispatcher, NULL); + "device-class", device_class, "device-index", device_index, + "device", device_id, "dispatcher", dispatcher, nullptr); /* Reset explicitly to ensure that it happens before * RoInitializeWrapper dtor is called */ @@ -2017,10 +952,18 @@ gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class, if (self->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_FAILED) { gst_object_unref (self); - return NULL; + return nullptr; } gst_object_ref_sink (self); return self; } + +IAudioClient * +gst_wasapi2_client_get_handle (GstWasapi2Client * client) +{ + g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), nullptr); + + return client->audio_client; +} diff --git a/sys/wasapi2/gstwasapi2client.h b/sys/wasapi2/gstwasapi2client.h index 57b281101..6c8c78e21 100644 --- a/sys/wasapi2/gstwasapi2client.h +++ b/sys/wasapi2/gstwasapi2client.h @@ -39,50 +39,16 @@ GType gst_wasapi2_client_device_class_get_type (void); G_DECLARE_FINAL_TYPE (GstWasapi2Client, gst_wasapi2_client, GST, WASAPI2_CLIENT, GstObject); -GstCaps * gst_wasapi2_client_get_caps (GstWasapi2Client * client); - -HRESULT gst_wasapi2_client_open (GstWasapi2Client * client, - GstAudioRingBufferSpec * spec, - GstAudioRingBuffer * buf); - -HRESULT gst_wasapi2_client_start (GstWasapi2Client * client); - -HRESULT gst_wasapi2_client_stop (GstWasapi2Client * client); - -HRESULT gst_wasapi2_client_read (GstWasapi2Client * client, - gpointer data, - guint length, - guint * read_length); - -HRESULT gst_wasapi2_client_write (GstWasapi2Client * client, - gpointer data, - guint length, - guint * write_length); - -HRESULT gst_wasapi2_client_delay (GstWasapi2Client * client, - guint32 * delay); - -HRESULT gst_wasapi2_client_set_mute (GstWasapi2Client * client, - gboolean mute); - -HRESULT gst_wasapi2_client_get_mute (GstWasapi2Client * client, - gboolean * mute); - -HRESULT gst_wasapi2_client_set_volume (GstWasapi2Client * client, - gfloat volume); - -HRESULT gst_wasapi2_client_get_volume (GstWasapi2Client * client, - gfloat * volume); - -gboolean gst_wasapi2_client_ensure_activation (GstWasapi2Client * client); - GstWasapi2Client * gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class, - gboolean low_latency, gint device_index, const gchar * device_id, gpointer dispatcher); -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstWasapi2Client, gst_object_unref) +gboolean gst_wasapi2_client_ensure_activation (GstWasapi2Client * client); + +IAudioClient * gst_wasapi2_client_get_handle (GstWasapi2Client * client); + +GstCaps * gst_wasapi2_client_get_caps (GstWasapi2Client * client); G_END_DECLS diff --git a/sys/wasapi2/gstwasapi2device.c b/sys/wasapi2/gstwasapi2device.c index 6a36fcecd..4fd9abfaf 100644 --- a/sys/wasapi2/gstwasapi2device.c +++ b/sys/wasapi2/gstwasapi2device.c @@ -183,7 +183,7 @@ gst_wasapi2_device_provider_probe_internal (GstWasapi2DeviceProvider * self, gchar *device_id = NULL; gchar *device_name = NULL; - client = gst_wasapi2_client_new (client_class, FALSE, i, NULL, NULL); + client = gst_wasapi2_client_new (client_class, i, NULL, NULL); if (!client) return; diff --git a/sys/wasapi2/gstwasapi2ringbuffer.cpp b/sys/wasapi2/gstwasapi2ringbuffer.cpp new file mode 100644 index 000000000..5a85458b3 --- /dev/null +++ b/sys/wasapi2/gstwasapi2ringbuffer.cpp @@ -0,0 +1,1015 @@ +/* GStreamer + * Copyright (C) 2021 Seungha Yang <seungha@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "gstwasapi2ringbuffer.h" +#include <string.h> +#include <mfapi.h> +#include <wrl.h> + +GST_DEBUG_CATEGORY_STATIC (gst_wasapi2_ring_buffer_debug); +#define GST_CAT_DEFAULT gst_wasapi2_ring_buffer_debug + +static HRESULT gst_wasapi2_ring_buffer_io_callback (GstWasapi2RingBuffer * buf); + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; + +class GstWasapiAsyncCallback : public IMFAsyncCallback +{ +public: + GstWasapiAsyncCallback(GstWasapi2RingBuffer *listener, DWORD queue_id) + : ref_count_(1) + , queue_id_(queue_id) + { + g_weak_ref_init (&listener_, listener); + } + + virtual ~GstWasapiAsyncCallback () + { + g_weak_ref_set (&listener_, nullptr); + } + + /* IUnknown */ + STDMETHODIMP_ (ULONG) + AddRef (void) + { + GST_TRACE ("%p, %d", this, ref_count_); + return InterlockedIncrement (&ref_count_); + } + STDMETHODIMP_ (ULONG) + Release (void) + { + ULONG ref_count; + + GST_TRACE ("%p, %d", this, ref_count_); + ref_count = InterlockedDecrement (&ref_count_); + + if (ref_count == 0) { + GST_TRACE ("Delete instance %p", this); + delete this; + } + + return ref_count; + } + + STDMETHODIMP + QueryInterface (REFIID riid, void ** object) + { + if (!object) + return E_POINTER; + + if (riid == IID_IUnknown) { + GST_TRACE ("query IUnknown interface %p", this); + *object = static_cast<IUnknown *> (static_cast<GstWasapiAsyncCallback *> (this)); + } else if (riid == __uuidof (IMFAsyncCallback)) { + GST_TRACE ("query IUnknown interface %p", this); + *object = static_cast<IUnknown *> (static_cast<GstWasapiAsyncCallback *> (this)); + } else { + *object = nullptr; + return E_NOINTERFACE; + } + + AddRef (); + + return S_OK; + } + + /* IMFAsyncCallback */ + STDMETHODIMP + GetParameters(DWORD * pdwFlags, DWORD * pdwQueue) + { + *pdwFlags = 0; + *pdwQueue = queue_id_; + + return S_OK; + } + + STDMETHODIMP + Invoke(IMFAsyncResult * pAsyncResult) + { + GstWasapi2RingBuffer *ringbuffer; + HRESULT hr; + + ringbuffer = (GstWasapi2RingBuffer *) g_weak_ref_get (&listener_); + if (!ringbuffer) { + GST_WARNING ("Listener was removed"); + return S_OK; + } + + hr = gst_wasapi2_ring_buffer_io_callback (ringbuffer); + gst_object_unref (ringbuffer); + + return hr; + } + +private: + ULONG ref_count_; + DWORD queue_id_; + GWeakRef listener_; +}; +/* *INDENT-ON* */ + +struct _GstWasapi2RingBuffer +{ + GstAudioRingBuffer parent; + + GstWasapi2ClientDeviceClass device_class; + gchar *device_id; + gboolean low_latency; + gboolean mute; + gdouble volume; + gpointer dispatcher; + + GstWasapi2Client *client; + IAudioCaptureClient *capture_client; + IAudioRenderClient *render_client; + ISimpleAudioVolume *volume_object; + + GstWasapiAsyncCallback *callback_object; + IMFAsyncResult *callback_result; + MFWORKITEM_KEY callback_key; + HANDLE event_handle; + + guint64 expected_position; + gboolean is_first; + gboolean running; + UINT32 buffer_size; + + gint segoffset; + guint64 write_frame_offset; + + GMutex volume_lock; + gboolean mute_changed; + gboolean volume_changed; +}; + +static void gst_wasapi2_ring_buffer_constructed (GObject * object); +static void gst_wasapi2_ring_buffer_dispose (GObject * object); +static void gst_wasapi2_ring_buffer_finalize (GObject * object); + +static gboolean gst_wasapi2_ring_buffer_open_device (GstAudioRingBuffer * buf); +static gboolean gst_wasapi2_ring_buffer_close_device (GstAudioRingBuffer * buf); +static gboolean gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf, + GstAudioRingBufferSpec * spec); +static gboolean gst_wasapi2_ring_buffer_release (GstAudioRingBuffer * buf); +static gboolean gst_wasapi2_ring_buffer_start (GstAudioRingBuffer * buf); +static gboolean gst_wasapi2_ring_buffer_stop (GstAudioRingBuffer * buf); +static guint gst_wasapi2_ring_buffer_delay (GstAudioRingBuffer * buf); + +#define gst_wasapi2_ring_buffer_parent_class parent_class +G_DEFINE_TYPE (GstWasapi2RingBuffer, gst_wasapi2_ring_buffer, + GST_TYPE_AUDIO_RING_BUFFER); + +static void +gst_wasapi2_ring_buffer_class_init (GstWasapi2RingBufferClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstAudioRingBufferClass *ring_buffer_class = + GST_AUDIO_RING_BUFFER_CLASS (klass); + + gobject_class->constructed = gst_wasapi2_ring_buffer_constructed; + gobject_class->dispose = gst_wasapi2_ring_buffer_dispose; + gobject_class->finalize = gst_wasapi2_ring_buffer_finalize; + + ring_buffer_class->open_device = + GST_DEBUG_FUNCPTR (gst_wasapi2_ring_buffer_open_device); + ring_buffer_class->close_device = + GST_DEBUG_FUNCPTR (gst_wasapi2_ring_buffer_close_device); + ring_buffer_class->acquire = + GST_DEBUG_FUNCPTR (gst_wasapi2_ring_buffer_acquire); + ring_buffer_class->release = + GST_DEBUG_FUNCPTR (gst_wasapi2_ring_buffer_release); + ring_buffer_class->start = GST_DEBUG_FUNCPTR (gst_wasapi2_ring_buffer_start); + ring_buffer_class->resume = GST_DEBUG_FUNCPTR (gst_wasapi2_ring_buffer_start); + ring_buffer_class->stop = GST_DEBUG_FUNCPTR (gst_wasapi2_ring_buffer_stop); + ring_buffer_class->delay = GST_DEBUG_FUNCPTR (gst_wasapi2_ring_buffer_delay); + + GST_DEBUG_CATEGORY_INIT (gst_wasapi2_ring_buffer_debug, + "wasapi2ringbuffer", 0, "wasapi2ringbuffer"); +} + +static void +gst_wasapi2_ring_buffer_init (GstWasapi2RingBuffer * self) +{ + self->volume = 1.0f; + self->mute = FALSE; + + self->event_handle = CreateEvent (nullptr, FALSE, FALSE, nullptr); + g_mutex_init (&self->volume_lock); +} + +static void +gst_wasapi2_ring_buffer_constructed (GObject * object) +{ + GstWasapi2RingBuffer *self = GST_WASAPI2_RING_BUFFER (object); + HRESULT hr; + DWORD task_id = 0; + DWORD queue_id = 0; + + hr = MFLockSharedWorkQueue (L"Pro Audio", 0, &task_id, &queue_id); + if (!gst_wasapi2_result (hr)) { + GST_WARNING_OBJECT (self, "Failed to get work queue id"); + goto out; + } + + self->callback_object = new GstWasapiAsyncCallback (self, queue_id); + hr = MFCreateAsyncResult (nullptr, self->callback_object, nullptr, + &self->callback_result); + if (!gst_wasapi2_result (hr)) { + GST_WARNING_OBJECT (self, "Failed to create IAsyncResult"); + GST_WASAPI2_CLEAR_COM (self->callback_object); + } + +out: + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_wasapi2_ring_buffer_dispose (GObject * object) +{ + GstWasapi2RingBuffer *self = GST_WASAPI2_RING_BUFFER (object); + + gst_clear_object (&self->client); + GST_WASAPI2_CLEAR_COM (self->callback_result); + GST_WASAPI2_CLEAR_COM (self->callback_object); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_wasapi2_ring_buffer_finalize (GObject * object) +{ + GstWasapi2RingBuffer *self = GST_WASAPI2_RING_BUFFER (object); + + g_free (self->device_id); + CloseHandle (self->event_handle); + g_mutex_clear (&self->volume_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_wasapi2_ring_buffer_open_device (GstAudioRingBuffer * buf) +{ + GstWasapi2RingBuffer *self = GST_WASAPI2_RING_BUFFER (buf); + + GST_DEBUG_OBJECT (self, "Open"); + + self->client = gst_wasapi2_client_new (self->device_class, + -1, self->device_id, self->dispatcher); + if (!self->client) { + GST_ERROR_OBJECT (self, "Failed to open device"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_wasapi2_ring_buffer_close_device (GstAudioRingBuffer * buf) +{ + GstWasapi2RingBuffer *self = GST_WASAPI2_RING_BUFFER (buf); + + GST_DEBUG_OBJECT (self, "Close"); + + GST_WASAPI2_CLEAR_COM (self->capture_client); + GST_WASAPI2_CLEAR_COM (self->render_client); + + g_mutex_lock (&self->volume_lock); + if (self->volume_object) + self->volume_object->SetMute (FALSE, nullptr); + GST_WASAPI2_CLEAR_COM (self->volume_object); + g_mutex_unlock (&self->volume_lock); + + gst_clear_object (&self->client); + + return TRUE; +} + +static HRESULT +gst_wasapi2_ring_buffer_read (GstWasapi2RingBuffer * self) +{ + GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER_CAST (self); + BYTE *data = nullptr; + UINT32 to_read = 0; + guint32 to_read_bytes; + DWORD flags = 0; + HRESULT hr; + guint64 position; + GstAudioInfo *info = &ringbuffer->spec.info; + IAudioCaptureClient *capture_client = self->capture_client; + guint gap_size = 0; + guint offset = 0; + gint segment; + guint8 *readptr; + gint len; + + if (!capture_client) { + GST_ERROR_OBJECT (self, "IAudioCaptureClient is not available"); + return E_FAIL; + } + + hr = capture_client->GetBuffer (&data, &to_read, &flags, &position, nullptr); + if (hr == AUDCLNT_S_BUFFER_EMPTY || to_read == 0) { + GST_LOG_OBJECT (self, "Empty buffer"); + to_read = 0; + goto out; + } + + to_read_bytes = to_read * GST_AUDIO_INFO_BPF (info); + + GST_TRACE_OBJECT (self, "Reading at %d frames offset %" G_GUINT64_FORMAT + ", expected position %" G_GUINT64_FORMAT, to_read, position, + self->expected_position); + + if (self->is_first) { + self->expected_position = position + to_read; + self->is_first = FALSE; + } else { + if (position > self->expected_position) { + guint gap_frames; + + gap_frames = (guint) (position - self->expected_position); + GST_WARNING_OBJECT (self, "Found %u frames gap", gap_frames); + gap_size = gap_frames * GST_AUDIO_INFO_BPF (info); + } + + self->expected_position = position + to_read; + } + + /* Fill gap data if any */ + while (gap_size > 0) { + if (!gst_audio_ring_buffer_prepare_read (ringbuffer, + &segment, &readptr, &len)) { + GST_INFO_OBJECT (self, "No segment available"); + goto out; + } + + g_assert (self->segoffset >= 0); + + len -= self->segoffset; + if (len > gap_size) + len = gap_size; + + gst_audio_format_info_fill_silence (ringbuffer->spec.info.finfo, + readptr + self->segoffset, len); + + self->segoffset += len; + gap_size -= len; + + if (self->segoffset == ringbuffer->spec.segsize) { + gst_audio_ring_buffer_advance (ringbuffer, 1); + self->segoffset = 0; + } + } + + while (to_read_bytes) { + if (!gst_audio_ring_buffer_prepare_read (ringbuffer, + &segment, &readptr, &len)) { + GST_INFO_OBJECT (self, "No segment available"); + goto out; + } + + len -= self->segoffset; + if (len > to_read_bytes) + len = to_read_bytes; + + memcpy (readptr + self->segoffset, data + offset, len); + + self->segoffset += len; + offset += len; + to_read_bytes -= len; + + if (self->segoffset == ringbuffer->spec.segsize) { + gst_audio_ring_buffer_advance (ringbuffer, 1); + self->segoffset = 0; + } + } + +out: + hr = capture_client->ReleaseBuffer (to_read); + /* For debugging */ + gst_wasapi2_result (hr); + + return S_OK; +} + +static HRESULT +gst_wasapi2_ring_buffer_write (GstWasapi2RingBuffer * self, gboolean preroll) +{ + GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER_CAST (self); + HRESULT hr; + IAudioClient *client_handle; + IAudioRenderClient *render_client; + guint32 padding_frames = 0; + guint32 can_write; + guint32 can_write_bytes; + gint segment; + guint8 *readptr; + gint len; + BYTE *data = nullptr; + + client_handle = gst_wasapi2_client_get_handle (self->client); + if (!client_handle) { + GST_ERROR_OBJECT (self, "IAudioClient is not available"); + return E_FAIL; + } + + render_client = self->render_client; + if (!render_client) { + GST_ERROR_OBJECT (self, "IAudioRenderClient is not available"); + return E_FAIL; + } + + hr = client_handle->GetCurrentPadding (&padding_frames); + if (!gst_wasapi2_result (hr)) + return hr; + + if (padding_frames >= self->buffer_size) { + GST_INFO_OBJECT (self, + "Padding size %d is larger than or equal to buffer size %d", + padding_frames, self->buffer_size); + return S_OK; + } + + can_write = self->buffer_size - padding_frames; + can_write_bytes = can_write * GST_AUDIO_INFO_BPF (&ringbuffer->spec.info); + if (preroll) { + GST_INFO_OBJECT (self, "Pre-fill %d frames with silence", can_write); + + hr = render_client->GetBuffer (can_write, &data); + if (!gst_wasapi2_result (hr)) + return hr; + + hr = render_client->ReleaseBuffer (can_write, AUDCLNT_BUFFERFLAGS_SILENT); + return gst_wasapi2_result (hr); + } + + GST_TRACE_OBJECT (self, "Writing %d frames offset at %" G_GUINT64_FORMAT, + can_write, self->write_frame_offset); + self->write_frame_offset += can_write; + + while (can_write_bytes > 0) { + if (!gst_audio_ring_buffer_prepare_read (ringbuffer, + &segment, &readptr, &len)) { + GST_INFO_OBJECT (self, "No segment available"); + return S_OK; + } + + len -= self->segoffset; + + if (len > can_write_bytes) + len = can_write_bytes; + + can_write = len / GST_AUDIO_INFO_BPF (&ringbuffer->spec.info); + if (can_write == 0) + break; + + hr = render_client->GetBuffer (can_write, &data); + if (!gst_wasapi2_result (hr)) + return hr; + + memcpy (data, readptr + self->segoffset, len); + hr = render_client->ReleaseBuffer (can_write, 0); + + self->segoffset += len; + can_write_bytes -= len; + + if (self->segoffset == ringbuffer->spec.segsize) { + gst_audio_ring_buffer_clear (ringbuffer, segment); + gst_audio_ring_buffer_advance (ringbuffer, 1); + self->segoffset = 0; + } + + if (!gst_wasapi2_result (hr)) { + GST_WARNING_OBJECT (self, "Failed to release buffer"); + break; + } + } + + return S_OK; +} + +static HRESULT +gst_wasapi2_ring_buffer_io_callback (GstWasapi2RingBuffer * self) +{ + HRESULT hr = E_FAIL; + + g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (self), E_FAIL); + + if (!self->running) { + GST_INFO_OBJECT (self, "We are not running now"); + return S_OK; + } + + switch (self->device_class) { + case GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE: + hr = gst_wasapi2_ring_buffer_read (self); + break; + case GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER: + hr = gst_wasapi2_ring_buffer_write (self, FALSE); + break; + default: + g_assert_not_reached (); + break; + } + + if (self->running) { + if (gst_wasapi2_result (hr)) { + hr = MFPutWaitingWorkItem (self->event_handle, 0, self->callback_result, + &self->callback_key); + + if (!gst_wasapi2_result (hr)) { + GstElement *parent = + (GstElement *) gst_object_get_parent (GST_OBJECT_CAST (self)); + + GST_ERROR_OBJECT (self, "Failed to put item"); + if (parent) { + GST_ELEMENT_ERROR (parent, RESOURCE, FAILED, + (nullptr), ("Failed to schedule next I/O")); + gst_object_unref (parent); + } + } + } + } else { + GST_INFO_OBJECT (self, "We are not running now"); + return S_OK; + } + + return hr; +} + +static HRESULT +gst_wasapi2_ring_buffer_initialize_audio_client3 (GstWasapi2RingBuffer * self, + IAudioClient * client_handle, WAVEFORMATEX * mix_format, guint * period) +{ + HRESULT hr = S_OK; + UINT32 default_period, fundamental_period, min_period, max_period; + /* AUDCLNT_STREAMFLAGS_NOPERSIST is not allowed for + * InitializeSharedAudioStream */ + DWORD stream_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; + ComPtr < IAudioClient3 > audio_client; + + hr = client_handle->QueryInterface (IID_PPV_ARGS (&audio_client)); + if (!gst_wasapi2_result (hr)) { + GST_INFO_OBJECT (self, "IAudioClient3 interface is unavailable"); + return hr; + } + + hr = audio_client->GetSharedModeEnginePeriod (mix_format, + &default_period, &fundamental_period, &min_period, &max_period); + if (!gst_wasapi2_result (hr)) { + GST_INFO_OBJECT (self, "Couldn't get period"); + return hr; + } + + GST_INFO_OBJECT (self, "Using IAudioClient3, default period %d frames, " + "fundamental period %d frames, minimum period %d frames, maximum period " + "%d frames", default_period, fundamental_period, min_period, max_period); + + *period = min_period; + + hr = audio_client->InitializeSharedAudioStream (stream_flags, min_period, + mix_format, nullptr); + + if (!gst_wasapi2_result (hr)) + GST_WARNING_OBJECT (self, "Failed to initialize IAudioClient3"); + + return hr; +} + +static HRESULT +gst_wasapi2_ring_buffer_initialize_audio_client (GstWasapi2RingBuffer * self, + IAudioClient * client_handle, WAVEFORMATEX * mix_format, guint * period) +{ + GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER_CAST (self); + REFERENCE_TIME default_period, min_period; + DWORD stream_flags = + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST; + HRESULT hr; + + hr = client_handle->GetDevicePeriod (&default_period, &min_period); + if (!gst_wasapi2_result (hr)) { + GST_WARNING_OBJECT (self, "Couldn't get device period info"); + return hr; + } + + GST_INFO_OBJECT (self, "wasapi2 default period: %" G_GINT64_FORMAT + ", min period: %" G_GINT64_FORMAT, default_period, min_period); + + hr = client_handle->Initialize (AUDCLNT_SHAREMODE_SHARED, stream_flags, + /* hnsBufferDuration should be same as hnsPeriodicity + * when AUDCLNT_STREAMFLAGS_EVENTCALLBACK is used. + * And in case of shared mode, hnsPeriodicity should be zero, so + * this value should be zero as well */ + 0, + /* This must always be 0 in shared mode */ + 0, mix_format, nullptr); + + if (!gst_wasapi2_result (hr)) { + GST_WARNING_OBJECT (self, "Couldn't initialize audioclient"); + return hr; + } + + *period = gst_util_uint64_scale_round (default_period * 100, + GST_AUDIO_INFO_RATE (&ringbuffer->spec.info), GST_SECOND); + + return S_OK; +} + +static gboolean +gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf, + GstAudioRingBufferSpec * spec) +{ + GstWasapi2RingBuffer *self = GST_WASAPI2_RING_BUFFER (buf); + IAudioClient *client_handle; + HRESULT hr; + WAVEFORMATEX *mix_format = nullptr; + ComPtr < ISimpleAudioVolume > audio_volume; + GstAudioChannelPosition *position = nullptr; + guint period = 0; + + GST_DEBUG_OBJECT (buf, "Acquire"); + + if (!self->client) { + GST_ERROR_OBJECT (self, "No configured client object"); + return FALSE; + } + + if (!gst_wasapi2_client_ensure_activation (self->client)) { + GST_ERROR_OBJECT (self, "Failed to activate audio client"); + return FALSE; + } + + client_handle = gst_wasapi2_client_get_handle (self->client); + if (!client_handle) { + GST_ERROR_OBJECT (self, "IAudioClient handle is not available"); + return FALSE; + } + + /* TODO: convert given caps to mix format */ + hr = client_handle->GetMixFormat (&mix_format); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to get mix format"); + return FALSE; + } + + /* Only use audioclient3 when low-latency is requested because otherwise + * very slow machines and VMs with 1 CPU allocated will get glitches: + * https://bugzilla.gnome.org/show_bug.cgi?id=794497 */ + hr = E_FAIL; + if (self->low_latency) { + hr = gst_wasapi2_ring_buffer_initialize_audio_client3 (self, client_handle, + mix_format, &period); + } + + /* Try again if IAudioClinet3 API is unavailable. + * NOTE: IAudioClinet3:: methods might not be available for default device + * NOTE: The default device is a special device which is needed for supporting + * automatic stream routing + * https://docs.microsoft.com/en-us/windows/win32/coreaudio/automatic-stream-routing + */ + if (FAILED (hr)) { + hr = gst_wasapi2_ring_buffer_initialize_audio_client (self, client_handle, + mix_format, &period); + } + + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to initialize audio client"); + return FALSE; + } + + hr = client_handle->SetEventHandle (self->event_handle); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to set event handle"); + return FALSE; + } + + gst_wasapi2_util_waveformatex_to_channel_mask (mix_format, &position); + if (position) + gst_audio_ring_buffer_set_channel_positions (buf, position); + g_free (position); + + CoTaskMemFree (mix_format); + + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to init audio client"); + return FALSE; + } + + hr = client_handle->GetBufferSize (&self->buffer_size); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to query buffer size"); + return FALSE; + } + + g_assert (period > 0); + + spec->segsize = period * GST_AUDIO_INFO_BPF (&buf->spec.info); + spec->segtotal = 2; + + GST_INFO_OBJECT (self, + "Buffer size: %d frames, period: %d frames, segsize: %d bytes", + self->buffer_size, period, spec->segsize); + + if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER) { + ComPtr < IAudioRenderClient > render_client; + + hr = client_handle->GetService (IID_PPV_ARGS (&render_client)); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "IAudioRenderClient is unavailable"); + return FALSE; + } + + self->render_client = render_client.Detach (); + } else { + ComPtr < IAudioCaptureClient > capture_client; + + hr = client_handle->GetService (IID_PPV_ARGS (&capture_client)); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "IAudioCaptureClient is unavailable"); + return FALSE; + } + + self->capture_client = capture_client.Detach (); + } + + hr = client_handle->GetService (IID_PPV_ARGS (&audio_volume)); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "ISimpleAudioVolume is unavailable"); + goto error; + } + + g_mutex_lock (&self->volume_lock); + self->volume_object = audio_volume.Detach (); + + if (self->mute_changed) { + self->volume_object->SetMute (self->mute, nullptr); + self->mute_changed = FALSE; + } else { + self->volume_object->SetMute (FALSE, nullptr); + } + + if (self->volume_changed) { + self->volume_object->SetMasterVolume (self->volume, nullptr); + self->volume_changed = FALSE; + } + g_mutex_unlock (&self->volume_lock); + + buf->size = spec->segtotal * spec->segsize; + buf->memory = (guint8 *) g_malloc (buf->size); + gst_audio_format_info_fill_silence (buf->spec.info.finfo, + buf->memory, buf->size); + + return TRUE; + +error: + GST_WASAPI2_CLEAR_COM (self->render_client); + GST_WASAPI2_CLEAR_COM (self->capture_client); + GST_WASAPI2_CLEAR_COM (self->volume_object); + + return FALSE; +} + +static gboolean +gst_wasapi2_ring_buffer_release (GstAudioRingBuffer * buf) +{ + GST_DEBUG_OBJECT (buf, "Release"); + + g_clear_pointer (&buf->memory, g_free); + + return TRUE; +} + +static gboolean +gst_wasapi2_ring_buffer_start (GstAudioRingBuffer * buf) +{ + GstWasapi2RingBuffer *self = GST_WASAPI2_RING_BUFFER (buf); + IAudioClient *client_handle; + HRESULT hr; + + GST_DEBUG_OBJECT (self, "Start"); + + client_handle = gst_wasapi2_client_get_handle (self->client); + self->is_first = TRUE; + self->running = TRUE; + self->segoffset = 0; + self->write_frame_offset = 0; + + /* render client might read data from buffer immediately once it's prepared. + * Pre-fill with silence in order to start-up glitch */ + if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER) { + hr = gst_wasapi2_ring_buffer_write (self, TRUE); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to pre-fill buffer with silence"); + return FALSE; + } + } + + hr = client_handle->Start (); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to start client"); + self->running = FALSE; + return FALSE; + } + + hr = MFPutWaitingWorkItem (self->event_handle, 0, self->callback_result, + &self->callback_key); + if (!gst_wasapi2_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to put waiting item"); + client_handle->Stop (); + self->running = FALSE; + + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_wasapi2_ring_buffer_stop (GstAudioRingBuffer * buf) +{ + GstWasapi2RingBuffer *self = GST_WASAPI2_RING_BUFFER (buf); + IAudioClient *client_handle; + HRESULT hr; + + GST_DEBUG_OBJECT (buf, "Stop"); + + if (!self->client) { + GST_DEBUG_OBJECT (self, "No configured client"); + return TRUE; + } + + if (!self->running) { + GST_DEBUG_OBJECT (self, "We are not running"); + return TRUE; + } + + client_handle = gst_wasapi2_client_get_handle (self->client); + + self->running = FALSE; + MFCancelWorkItem (self->callback_key); + + hr = client_handle->Stop (); + gst_wasapi2_result (hr); + + /* Call reset for later reuse case */ + hr = client_handle->Reset (); + self->expected_position = 0; + + return TRUE; +} + +static guint +gst_wasapi2_ring_buffer_delay (GstAudioRingBuffer * buf) +{ + /* NOTE: WASAPI supports GetCurrentPadding() method for querying + * currently unread buffer size, but it doesn't seem to be quite useful + * here because: + * + * In case of capture client, GetCurrentPadding() will return the number of + * unread frames which will be identical to pNumFramesToRead value of + * IAudioCaptureClient::GetBuffer()'s return. Since we are running on + * event-driven mode and whenever available, WASAPI will notify signal + * so it's likely zero at this moment. And there is a chance to + * return incorrect value here because our IO callback happens from + * other thread. + * + * And render client's padding size will return the total size of buffer + * which is likely larger than twice of our period. Which doesn't represent + * the amount queued frame size in device correctly + */ + return 0; +} + +GstAudioRingBuffer * +gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass device_class, + gboolean low_latency, const gchar * device_id, gpointer dispatcher, + const gchar * name) +{ + GstWasapi2RingBuffer *self; + + self = (GstWasapi2RingBuffer *) + g_object_new (GST_TYPE_WASAPI2_RING_BUFFER, "name", name, nullptr); + + if (!self->callback_object) { + gst_object_unref (self); + return nullptr; + } + + self->device_class = device_class; + self->low_latency = low_latency; + self->device_id = g_strdup (device_id); + self->dispatcher = dispatcher; + + return GST_AUDIO_RING_BUFFER_CAST (self); +} + +GstCaps * +gst_wasapi2_ring_buffer_get_caps (GstWasapi2RingBuffer * buf) +{ + g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (buf), nullptr); + + if (!buf->client) + return nullptr; + + if (!gst_wasapi2_client_ensure_activation (buf->client)) { + GST_ERROR_OBJECT (buf, "Failed to activate audio client"); + return nullptr; + } + + return gst_wasapi2_client_get_caps (buf->client); +} + +HRESULT +gst_wasapi2_ring_buffer_set_mute (GstWasapi2RingBuffer * buf, gboolean mute) +{ + HRESULT hr = S_OK; + g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (buf), E_INVALIDARG); + + g_mutex_lock (&buf->volume_lock); + buf->mute = mute; + if (buf->volume_object) + hr = buf->volume_object->SetMute (mute, nullptr); + else + buf->volume_changed = TRUE; + g_mutex_unlock (&buf->volume_lock); + + return S_OK; +} + +HRESULT +gst_wasapi2_ring_buffer_get_mute (GstWasapi2RingBuffer * buf, gboolean * mute) +{ + BOOL mute_val; + HRESULT hr = S_OK; + + g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (buf), E_INVALIDARG); + g_return_val_if_fail (mute != nullptr, E_INVALIDARG); + + mute_val = buf->mute; + + g_mutex_lock (&buf->volume_lock); + if (buf->volume_object) + hr = buf->volume_object->GetMute (&mute_val); + g_mutex_unlock (&buf->volume_lock); + + *mute = mute_val ? TRUE : FALSE; + + return hr; +} + +HRESULT +gst_wasapi2_ring_buffer_set_volume (GstWasapi2RingBuffer * buf, gfloat volume) +{ + HRESULT hr = S_OK; + + g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (buf), E_INVALIDARG); + g_return_val_if_fail (volume >= 0 && volume <= 1.0, E_INVALIDARG); + + g_mutex_lock (&buf->volume_lock); + buf->volume = volume; + if (buf->volume_object) + hr = buf->volume_object->SetMasterVolume (volume, nullptr); + else + buf->mute_changed = TRUE; + g_mutex_unlock (&buf->volume_lock); + + return hr; +} + +HRESULT +gst_wasapi2_ring_buffer_get_volume (GstWasapi2RingBuffer * buf, gfloat * volume) +{ + gfloat volume_val; + HRESULT hr = S_OK; + + g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (buf), E_INVALIDARG); + g_return_val_if_fail (volume != nullptr, E_INVALIDARG); + + g_mutex_lock (&buf->volume_lock); + volume_val = buf->volume; + if (buf->volume_object) + hr = buf->volume_object->GetMasterVolume (&volume_val); + g_mutex_unlock (&buf->volume_lock); + + *volume = volume_val; + + return hr; +} diff --git a/sys/wasapi2/gstwasapi2ringbuffer.h b/sys/wasapi2/gstwasapi2ringbuffer.h new file mode 100644 index 000000000..5bbb6e976 --- /dev/null +++ b/sys/wasapi2/gstwasapi2ringbuffer.h @@ -0,0 +1,55 @@ +/* GStreamer + * Copyright (C) 2021 Seungha Yang <seungha@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_WASAPI2_RING_BUFFER_H__ +#define __GST_WASAPI2_RING_BUFFER_H__ + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include "gstwasapi2client.h" + +G_BEGIN_DECLS + +#define GST_TYPE_WASAPI2_RING_BUFFER (gst_wasapi2_ring_buffer_get_type()) +G_DECLARE_FINAL_TYPE (GstWasapi2RingBuffer, gst_wasapi2_ring_buffer, + GST, WASAPI2_RING_BUFFER, GstAudioRingBuffer); + +GstAudioRingBuffer * gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass device_class, + gboolean low_latency, + const gchar *device_id, + gpointer dispatcher, + const gchar * name); + +GstCaps * gst_wasapi2_ring_buffer_get_caps (GstWasapi2RingBuffer * buf); + +HRESULT gst_wasapi2_ring_buffer_set_mute (GstWasapi2RingBuffer * buf, + gboolean mute); + +HRESULT gst_wasapi2_ring_buffer_get_mute (GstWasapi2RingBuffer * buf, + gboolean * mute); + +HRESULT gst_wasapi2_ring_buffer_set_volume (GstWasapi2RingBuffer * buf, + gfloat volume); + +HRESULT gst_wasapi2_ring_buffer_get_volume (GstWasapi2RingBuffer * buf, + gfloat * volume); + +G_END_DECLS + +#endif /* __GST_WASAPI2_RING_BUFFER_H__ */ diff --git a/sys/wasapi2/gstwasapi2sink.c b/sys/wasapi2/gstwasapi2sink.c index cb3fd407f..3bb82529e 100644 --- a/sys/wasapi2/gstwasapi2sink.c +++ b/sys/wasapi2/gstwasapi2sink.c @@ -31,11 +31,11 @@ * * ## Example pipelines * |[ - * gst-launch-1.0 -v audiotestsrc samplesperbuffer=160 ! wasapi2sink + * gst-launch-1.0 -v audiotestsink samplesperbuffer=160 ! wasapi2sink * ]| Generate 20 ms buffers and render to the default audio device. * * |[ - * gst-launch-1.0 -v audiotestsrc samplesperbuffer=160 ! wasapi2sink low-latency=true + * gst-launch-1.0 -v audiotestsink samplesperbuffer=160 ! wasapi2sink low-latency=true * ]| Same as above, but with the minimum possible latency * */ @@ -45,7 +45,7 @@ #include "gstwasapi2sink.h" #include "gstwasapi2util.h" -#include "gstwasapi2client.h" +#include "gstwasapi2ringbuffer.h" GST_DEBUG_CATEGORY_STATIC (gst_wasapi2_sink_debug); #define GST_CAT_DEFAULT gst_wasapi2_sink_debug @@ -59,9 +59,6 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", #define DEFAULT_MUTE FALSE #define DEFAULT_VOLUME 1.0 -#define GST_WASAPI2_SINK_LOCK(s) g_mutex_lock(&(s)->lock) -#define GST_WASAPI2_SINK_UNLOCK(s) g_mutex_unlock(&(s)->lock) - enum { PROP_0, @@ -74,11 +71,7 @@ enum struct _GstWasapi2Sink { - GstAudioSink parent; - - GstWasapi2Client *client; - GstCaps *cached_caps; - gboolean started; + GstAudioBaseSink parent; /* properties */ gchar *device_id; @@ -89,30 +82,21 @@ struct _GstWasapi2Sink gboolean mute_changed; gboolean volume_changed; - - /* to protect audioclient from set/get property */ - GMutex lock; }; -static void gst_wasapi2_sink_dispose (GObject * object); static void gst_wasapi2_sink_finalize (GObject * object); static void gst_wasapi2_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_wasapi2_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); +static GstStateChangeReturn gst_wasapi2_sink_change_state (GstElement * + element, GstStateChange transition); + static GstCaps *gst_wasapi2_sink_get_caps (GstBaseSink * bsink, GstCaps * filter); - -static gboolean gst_wasapi2_sink_prepare (GstAudioSink * asink, - GstAudioRingBufferSpec * spec); -static gboolean gst_wasapi2_sink_unprepare (GstAudioSink * asink); -static gboolean gst_wasapi2_sink_open (GstAudioSink * asink); -static gboolean gst_wasapi2_sink_close (GstAudioSink * asink); -static gint gst_wasapi2_sink_write (GstAudioSink * asink, - gpointer data, guint length); -static guint gst_wasapi2_sink_delay (GstAudioSink * asink); -static void gst_wasapi2_sink_reset (GstAudioSink * asink); +static GstAudioRingBuffer *gst_wasapi2_sink_create_ringbuffer (GstAudioBaseSink + * sink); static void gst_wasapi2_sink_set_mute (GstWasapi2Sink * self, gboolean mute); static gboolean gst_wasapi2_sink_get_mute (GstWasapi2Sink * self); @@ -120,7 +104,8 @@ static void gst_wasapi2_sink_set_volume (GstWasapi2Sink * self, gdouble volume); static gdouble gst_wasapi2_sink_get_volume (GstWasapi2Sink * self); #define gst_wasapi2_sink_parent_class parent_class -G_DEFINE_TYPE_WITH_CODE (GstWasapi2Sink, gst_wasapi2_sink, GST_TYPE_AUDIO_SINK, +G_DEFINE_TYPE_WITH_CODE (GstWasapi2Sink, gst_wasapi2_sink, + GST_TYPE_AUDIO_BASE_SINK, G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL)); static void @@ -129,9 +114,9 @@ gst_wasapi2_sink_class_init (GstWasapi2SinkClass * klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass); - GstAudioSinkClass *audiosink_class = GST_AUDIO_SINK_CLASS (klass); + GstAudioBaseSinkClass *audiobasesink_class = + GST_AUDIO_BASE_SINK_CLASS (klass); - gobject_class->dispose = gst_wasapi2_sink_dispose; gobject_class->finalize = gst_wasapi2_sink_finalize; gobject_class->set_property = gst_wasapi2_sink_set_property; gobject_class->get_property = gst_wasapi2_sink_get_property; @@ -184,15 +169,13 @@ gst_wasapi2_sink_class_init (GstWasapi2SinkClass * klass) "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>, " "Seungha Yang <seungha@centricular.com>"); + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_wasapi2_sink_change_state); + basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_wasapi2_sink_get_caps); - audiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_wasapi2_sink_prepare); - audiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_wasapi2_sink_unprepare); - audiosink_class->open = GST_DEBUG_FUNCPTR (gst_wasapi2_sink_open); - audiosink_class->close = GST_DEBUG_FUNCPTR (gst_wasapi2_sink_close); - audiosink_class->write = GST_DEBUG_FUNCPTR (gst_wasapi2_sink_write); - audiosink_class->delay = GST_DEBUG_FUNCPTR (gst_wasapi2_sink_delay); - audiosink_class->reset = GST_DEBUG_FUNCPTR (gst_wasapi2_sink_reset); + audiobasesink_class->create_ringbuffer = + GST_DEBUG_FUNCPTR (gst_wasapi2_sink_create_ringbuffer); GST_DEBUG_CATEGORY_INIT (gst_wasapi2_sink_debug, "wasapi2sink", 0, "Windows audio session API sink"); @@ -204,21 +187,6 @@ gst_wasapi2_sink_init (GstWasapi2Sink * self) self->low_latency = DEFAULT_LOW_LATENCY; self->mute = DEFAULT_MUTE; self->volume = DEFAULT_VOLUME; - - g_mutex_init (&self->lock); -} - -static void -gst_wasapi2_sink_dispose (GObject * object) -{ - GstWasapi2Sink *self = GST_WASAPI2_SINK (object); - - GST_WASAPI2_SINK_LOCK (self); - gst_clear_object (&self->client); - gst_clear_caps (&self->cached_caps); - GST_WASAPI2_SINK_UNLOCK (self); - - G_OBJECT_CLASS (parent_class)->dispose (object); } static void @@ -227,7 +195,6 @@ gst_wasapi2_sink_finalize (GObject * object) GstWasapi2Sink *self = GST_WASAPI2_SINK (object); g_free (self->device_id); - g_mutex_clear (&self->lock); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -286,29 +253,58 @@ gst_wasapi2_sink_get_property (GObject * object, guint prop_id, } } +static GstStateChangeReturn +gst_wasapi2_sink_change_state (GstElement * element, GstStateChange transition) +{ + GstWasapi2Sink *self = GST_WASAPI2_SINK (element); + GstAudioBaseSink *asink = GST_AUDIO_BASE_SINK_CAST (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* If we have pending volume/mute values to set, do here */ + GST_OBJECT_LOCK (self); + if (asink->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (asink->ringbuffer); + + if (self->volume_changed) { + gst_wasapi2_ring_buffer_set_volume (ringbuffer, self->volume); + self->volume_changed = FALSE; + } + + if (self->mute_changed) { + gst_wasapi2_ring_buffer_set_mute (ringbuffer, self->mute); + self->mute_changed = FALSE; + } + } + GST_OBJECT_UNLOCK (self); + break; + default: + break; + } + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} + static GstCaps * gst_wasapi2_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) { - GstWasapi2Sink *self = GST_WASAPI2_SINK (bsink); + GstAudioBaseSink *asink = GST_AUDIO_BASE_SINK_CAST (bsink); GstCaps *caps = NULL; - /* In case of UWP, device activation might not be finished yet */ - if (self->client && !gst_wasapi2_client_ensure_activation (self->client)) { - GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL), - ("Failed to activate device")); - return NULL; - } + GST_OBJECT_LOCK (bsink); + if (asink->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (asink->ringbuffer); - if (self->client) - caps = gst_wasapi2_client_get_caps (self->client); + gst_object_ref (ringbuffer); + GST_OBJECT_UNLOCK (bsink); - /* store one caps here so that we can return device caps even if - * audioclient was closed due to unprepare() */ - if (!self->cached_caps && caps) - self->cached_caps = gst_caps_ref (caps); - - if (!caps && self->cached_caps) - caps = gst_caps_ref (self->cached_caps); + /* Get caps might be able to block if device is not activated yet */ + caps = gst_wasapi2_ring_buffer_get_caps (ringbuffer); + } else { + GST_OBJECT_UNLOCK (bsink); + } if (!caps) caps = gst_pad_get_pad_template_caps (bsink->sinkpad); @@ -320,238 +316,81 @@ gst_wasapi2_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) caps = filtered; } - GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps); + GST_DEBUG_OBJECT (bsink, "returning caps %" GST_PTR_FORMAT, caps); return caps; } -static gboolean -gst_wasapi2_sink_open_unlocked (GstAudioSink * asink) -{ - GstWasapi2Sink *self = GST_WASAPI2_SINK (asink); - - gst_clear_object (&self->client); - - self->client = - gst_wasapi2_client_new (GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER, - self->low_latency, -1, self->device_id, self->dispatcher); - - if (!self->client) - return FALSE; - - return TRUE; -} - -static gboolean -gst_wasapi2_sink_open (GstAudioSink * asink) -{ - GstWasapi2Sink *self = GST_WASAPI2_SINK (asink); - gboolean ret; - - GST_DEBUG_OBJECT (self, "Opening device"); - - GST_WASAPI2_SINK_LOCK (self); - ret = gst_wasapi2_sink_open_unlocked (asink); - GST_WASAPI2_SINK_UNLOCK (self); - - if (!ret) { - GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL), - ("Failed to open device")); - return FALSE; - } - - return TRUE; -} - -static gboolean -gst_wasapi2_sink_close (GstAudioSink * asink) -{ - GstWasapi2Sink *self = GST_WASAPI2_SINK (asink); - - GST_WASAPI2_SINK_LOCK (self); - - gst_clear_object (&self->client); - gst_clear_caps (&self->cached_caps); - self->started = FALSE; - - GST_WASAPI2_SINK_UNLOCK (self); - - return TRUE; -} - -static gboolean -gst_wasapi2_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec) -{ - GstWasapi2Sink *self = GST_WASAPI2_SINK (asink); - GstAudioBaseSink *bsink = GST_AUDIO_BASE_SINK (asink); - gboolean ret = FALSE; - HRESULT hr; - - GST_WASAPI2_SINK_LOCK (self); - if (!self->client && !gst_wasapi2_sink_open_unlocked (asink)) { - GST_ERROR_OBJECT (self, "No audio client was configured"); - goto done; - } - - if (!gst_wasapi2_client_ensure_activation (self->client)) { - GST_ERROR_OBJECT (self, "Couldn't activate audio device"); - goto done; - } - - hr = gst_wasapi2_client_open (self->client, spec, bsink->ringbuffer); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (self, "Couldn't open audio client"); - goto done; - } - - /* Set mute and volume here again, maybe when "mute" property was set, audioclient - * might not be configured at that moment */ - if (self->mute_changed) { - gst_wasapi2_client_set_mute (self->client, self->mute); - self->mute_changed = FALSE; - } - - if (self->volume_changed) { - gst_wasapi2_client_set_volume (self->client, self->volume); - self->volume_changed = FALSE; - } - - /* Will start IAudioClient on the first write request */ - self->started = FALSE; - ret = TRUE; - -done: - GST_WASAPI2_SINK_UNLOCK (self); - - return ret; -} - -static gboolean -gst_wasapi2_sink_unprepare (GstAudioSink * asink) -{ - GstWasapi2Sink *self = GST_WASAPI2_SINK (asink); - - self->started = FALSE; - - /* Will reopen device later prepare() */ - GST_WASAPI2_SINK_LOCK (self); - if (self->client) { - gst_wasapi2_client_stop (self->client); - gst_clear_object (&self->client); - } - GST_WASAPI2_SINK_UNLOCK (self); - - return TRUE; -} - -static gint -gst_wasapi2_sink_write (GstAudioSink * asink, gpointer data, guint length) +static GstAudioRingBuffer * +gst_wasapi2_sink_create_ringbuffer (GstAudioBaseSink * sink) { - GstWasapi2Sink *self = GST_WASAPI2_SINK (asink); - HRESULT hr; - guint write_len = 0; + GstWasapi2Sink *self = GST_WASAPI2_SINK (sink); + GstAudioRingBuffer *ringbuffer; + gchar *name; - if (!self->client) { - GST_ERROR_OBJECT (self, "No audio client was configured"); - return -1; - } + name = g_strdup_printf ("%s-ringbuffer", GST_OBJECT_NAME (sink)); - if (!self->started) { - HRESULT hr = gst_wasapi2_client_start (self->client); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (self, "Failed to re-start client"); - return -1; - } + ringbuffer = + gst_wasapi2_ring_buffer_new (GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER, + self->low_latency, self->device_id, self->dispatcher, name); - self->started = TRUE; - } + g_free (name); - hr = gst_wasapi2_client_write (self->client, data, length, &write_len); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (self, "Failed to write"); - return -1; - } - - return (gint) write_len; -} - -static guint -gst_wasapi2_sink_delay (GstAudioSink * asink) -{ - GstWasapi2Sink *self = GST_WASAPI2_SINK (asink); - guint32 delay; - HRESULT hr; - - if (!self->client) - return 0; - - hr = gst_wasapi2_client_delay (self->client, &delay); - if (!gst_wasapi2_result (hr)) { - GST_WARNING_OBJECT (self, "Failed to get delay"); - return 0; - } - - return delay; -} - -static void -gst_wasapi2_sink_reset (GstAudioSink * asink) -{ - GstWasapi2Sink *self = GST_WASAPI2_SINK (asink); - - GST_INFO_OBJECT (self, "reset called"); - - self->started = FALSE; - - if (!self->client) - return; - - gst_wasapi2_client_stop (self->client); + return ringbuffer; } static void gst_wasapi2_sink_set_mute (GstWasapi2Sink * self, gboolean mute) { - GST_WASAPI2_SINK_LOCK (self); + GstAudioBaseSink *bsink = GST_AUDIO_BASE_SINK_CAST (self); + HRESULT hr; + + GST_OBJECT_LOCK (self); self->mute = mute; self->mute_changed = TRUE; - if (self->client) { - HRESULT hr = gst_wasapi2_client_set_mute (self->client, mute); + if (bsink->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (bsink->ringbuffer); + + hr = gst_wasapi2_ring_buffer_set_mute (ringbuffer, mute); + if (FAILED (hr)) { GST_INFO_OBJECT (self, "Couldn't set mute"); } else { self->mute_changed = FALSE; } - } else { - GST_DEBUG_OBJECT (self, "audio client is not configured yet"); } - GST_WASAPI2_SINK_UNLOCK (self); + GST_OBJECT_UNLOCK (self); } static gboolean gst_wasapi2_sink_get_mute (GstWasapi2Sink * self) { + GstAudioBaseSink *bsink = GST_AUDIO_BASE_SINK_CAST (self); gboolean mute; + HRESULT hr; - GST_WASAPI2_SINK_LOCK (self); + GST_OBJECT_LOCK (self); mute = self->mute; - if (self->client) { - HRESULT hr = gst_wasapi2_client_get_mute (self->client, &mute); + if (bsink->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (bsink->ringbuffer); + + hr = gst_wasapi2_ring_buffer_get_mute (ringbuffer, &mute); + if (FAILED (hr)) { - GST_INFO_OBJECT (self, "Couldn't get mute state"); + GST_INFO_OBJECT (self, "Couldn't get mute"); } else { self->mute = mute; } - } else { - GST_DEBUG_OBJECT (self, "audio client is not configured yet"); } - GST_WASAPI2_SINK_UNLOCK (self); + GST_OBJECT_UNLOCK (self); return mute; } @@ -559,7 +398,10 @@ gst_wasapi2_sink_get_mute (GstWasapi2Sink * self) static void gst_wasapi2_sink_set_volume (GstWasapi2Sink * self, gdouble volume) { - GST_WASAPI2_SINK_LOCK (self); + GstAudioBaseSink *bsink = GST_AUDIO_BASE_SINK_CAST (self); + HRESULT hr; + + GST_OBJECT_LOCK (self); self->volume = volume; /* clip volume value */ @@ -567,42 +409,47 @@ gst_wasapi2_sink_set_volume (GstWasapi2Sink * self, gdouble volume) self->volume = MIN (1.0, self->volume); self->volume_changed = TRUE; - if (self->client) { - HRESULT hr = - gst_wasapi2_client_set_volume (self->client, (gfloat) self->volume); + if (bsink->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (bsink->ringbuffer); + + hr = gst_wasapi2_ring_buffer_set_volume (ringbuffer, (gfloat) self->volume); + if (FAILED (hr)) { GST_INFO_OBJECT (self, "Couldn't set volume"); } else { self->volume_changed = FALSE; } - } else { - GST_DEBUG_OBJECT (self, "audio client is not configured yet"); } - GST_WASAPI2_SINK_UNLOCK (self); + GST_OBJECT_UNLOCK (self); } static gdouble gst_wasapi2_sink_get_volume (GstWasapi2Sink * self) { + GstAudioBaseSink *bsink = GST_AUDIO_BASE_SINK_CAST (self); gfloat volume; + HRESULT hr; - GST_WASAPI2_SINK_LOCK (self); + GST_OBJECT_LOCK (self); volume = (gfloat) self->volume; - if (self->client) { - HRESULT hr = gst_wasapi2_client_get_volume (self->client, &volume); + if (bsink->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (bsink->ringbuffer); + + hr = gst_wasapi2_ring_buffer_get_volume (ringbuffer, &volume); + if (FAILED (hr)) { - GST_INFO_OBJECT (self, "Couldn't get volume"); + GST_INFO_OBJECT (self, "Couldn't set volume"); } else { self->volume = volume; } - } else { - GST_DEBUG_OBJECT (self, "audio client is not configured yet"); } - GST_WASAPI2_SINK_UNLOCK (self); + GST_OBJECT_UNLOCK (self); volume = MAX (0.0, volume); volume = MIN (1.0, volume); diff --git a/sys/wasapi2/gstwasapi2sink.h b/sys/wasapi2/gstwasapi2sink.h index 433dcb656..5e618379e 100644 --- a/sys/wasapi2/gstwasapi2sink.h +++ b/sys/wasapi2/gstwasapi2sink.h @@ -27,7 +27,7 @@ G_BEGIN_DECLS #define GST_TYPE_WASAPI2_SINK (gst_wasapi2_sink_get_type ()) G_DECLARE_FINAL_TYPE (GstWasapi2Sink, - gst_wasapi2_sink, GST, WASAPI2_SINK, GstAudioSink); + gst_wasapi2_sink, GST, WASAPI2_SINK, GstAudioBaseSink); G_END_DECLS diff --git a/sys/wasapi2/gstwasapi2src.c b/sys/wasapi2/gstwasapi2src.c index fa5616936..446803aac 100644 --- a/sys/wasapi2/gstwasapi2src.c +++ b/sys/wasapi2/gstwasapi2src.c @@ -43,7 +43,7 @@ #include "gstwasapi2src.h" #include "gstwasapi2util.h" -#include "gstwasapi2client.h" +#include "gstwasapi2ringbuffer.h" GST_DEBUG_CATEGORY_STATIC (gst_wasapi2_src_debug); #define GST_CAT_DEFAULT gst_wasapi2_src_debug @@ -57,9 +57,6 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", #define DEFAULT_MUTE FALSE #define DEFAULT_VOLUME 1.0 -#define GST_WASAPI2_SRC_LOCK(s) g_mutex_lock(&(s)->lock) -#define GST_WASAPI2_SRC_UNLOCK(s) g_mutex_unlock(&(s)->lock) - enum { PROP_0, @@ -72,11 +69,7 @@ enum struct _GstWasapi2Src { - GstAudioSrc parent; - - GstWasapi2Client *client; - GstCaps *cached_caps; - gboolean started; + GstAudioBaseSrc parent; /* properties */ gchar *device_id; @@ -87,29 +80,20 @@ struct _GstWasapi2Src gboolean mute_changed; gboolean volume_changed; - - /* to protect audioclient from set/get property */ - GMutex lock; }; -static void gst_wasapi2_src_dispose (GObject * object); static void gst_wasapi2_src_finalize (GObject * object); static void gst_wasapi2_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_wasapi2_src_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); -static GstCaps *gst_wasapi2_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter); +static GstStateChangeReturn gst_wasapi2_src_change_state (GstElement * + element, GstStateChange transition); -static gboolean gst_wasapi2_src_open (GstAudioSrc * asrc); -static gboolean gst_wasapi2_src_close (GstAudioSrc * asrc); -static gboolean gst_wasapi2_src_prepare (GstAudioSrc * asrc, - GstAudioRingBufferSpec * spec); -static gboolean gst_wasapi2_src_unprepare (GstAudioSrc * asrc); -static guint gst_wasapi2_src_read (GstAudioSrc * asrc, gpointer data, - guint length, GstClockTime * timestamp); -static guint gst_wasapi2_src_delay (GstAudioSrc * asrc); -static void gst_wasapi2_src_reset (GstAudioSrc * asrc); +static GstCaps *gst_wasapi2_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter); +static GstAudioRingBuffer *gst_wasapi2_src_create_ringbuffer (GstAudioBaseSrc * + src); static void gst_wasapi2_src_set_mute (GstWasapi2Src * self, gboolean mute); static gboolean gst_wasapi2_src_get_mute (GstWasapi2Src * self); @@ -117,7 +101,8 @@ static void gst_wasapi2_src_set_volume (GstWasapi2Src * self, gdouble volume); static gdouble gst_wasapi2_src_get_volume (GstWasapi2Src * self); #define gst_wasapi2_src_parent_class parent_class -G_DEFINE_TYPE_WITH_CODE (GstWasapi2Src, gst_wasapi2_src, GST_TYPE_AUDIO_SRC, +G_DEFINE_TYPE_WITH_CODE (GstWasapi2Src, gst_wasapi2_src, + GST_TYPE_AUDIO_BASE_SRC, G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL)); static void @@ -126,9 +111,8 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); GstBaseSrcClass *basesrc_class = GST_BASE_SRC_CLASS (klass); - GstAudioSrcClass *audiosrc_class = GST_AUDIO_SRC_CLASS (klass); + GstAudioBaseSrcClass *audiobasesrc_class = GST_AUDIO_BASE_SRC_CLASS (klass); - gobject_class->dispose = gst_wasapi2_src_dispose; gobject_class->finalize = gst_wasapi2_src_finalize; gobject_class->set_property = gst_wasapi2_src_set_property; gobject_class->get_property = gst_wasapi2_src_get_property; @@ -181,15 +165,13 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass) "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>, " "Seungha Yang <seungha@centricular.com>"); + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_wasapi2_src_change_state); + basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_wasapi2_src_get_caps); - audiosrc_class->open = GST_DEBUG_FUNCPTR (gst_wasapi2_src_open); - audiosrc_class->close = GST_DEBUG_FUNCPTR (gst_wasapi2_src_close); - audiosrc_class->read = GST_DEBUG_FUNCPTR (gst_wasapi2_src_read); - audiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_wasapi2_src_prepare); - audiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_wasapi2_src_unprepare); - audiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_wasapi2_src_delay); - audiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_wasapi2_src_reset); + audiobasesrc_class->create_ringbuffer = + GST_DEBUG_FUNCPTR (gst_wasapi2_src_create_ringbuffer); GST_DEBUG_CATEGORY_INIT (gst_wasapi2_src_debug, "wasapi2src", 0, "Windows audio session API source"); @@ -201,21 +183,6 @@ gst_wasapi2_src_init (GstWasapi2Src * self) self->mute = DEFAULT_MUTE; self->volume = DEFAULT_VOLUME; self->low_latency = DEFAULT_LOW_LATENCY; - - g_mutex_init (&self->lock); -} - -static void -gst_wasapi2_src_dispose (GObject * object) -{ - GstWasapi2Src *self = GST_WASAPI2_SRC (object); - - GST_WASAPI2_SRC_LOCK (self); - gst_clear_object (&self->client); - gst_clear_caps (&self->cached_caps); - GST_WASAPI2_SRC_UNLOCK (self); - - G_OBJECT_CLASS (parent_class)->dispose (object); } static void @@ -224,7 +191,6 @@ gst_wasapi2_src_finalize (GObject * object) GstWasapi2Src *self = GST_WASAPI2_SRC (object); g_free (self->device_id); - g_mutex_clear (&self->lock); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -283,29 +249,59 @@ gst_wasapi2_src_get_property (GObject * object, guint prop_id, } } +static GstStateChangeReturn +gst_wasapi2_src_change_state (GstElement * element, GstStateChange transition) +{ + GstWasapi2Src *self = GST_WASAPI2_SRC (element); + GstAudioBaseSrc *asrc = GST_AUDIO_BASE_SRC_CAST (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* If we have pending volume/mute values to set, do here */ + GST_OBJECT_LOCK (self); + if (asrc->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (asrc->ringbuffer); + + if (self->volume_changed) { + gst_wasapi2_ring_buffer_set_volume (ringbuffer, self->volume); + self->volume_changed = FALSE; + } + + if (self->mute_changed) { + gst_wasapi2_ring_buffer_set_mute (ringbuffer, self->mute); + self->mute_changed = FALSE; + } + } + GST_OBJECT_UNLOCK (self); + break; + default: + break; + } + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} + static GstCaps * gst_wasapi2_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) { - GstWasapi2Src *self = GST_WASAPI2_SRC (bsrc); + GstAudioBaseSrc *asrc = GST_AUDIO_BASE_SRC_CAST (bsrc); GstCaps *caps = NULL; - /* In case of UWP, device activation might not be finished yet */ - if (self->client && !gst_wasapi2_client_ensure_activation (self->client)) { - GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL), - ("Failed to activate device")); - return NULL; - } + GST_OBJECT_LOCK (bsrc); + if (asrc->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (asrc->ringbuffer); - if (self->client) - caps = gst_wasapi2_client_get_caps (self->client); + gst_object_ref (ringbuffer); + GST_OBJECT_UNLOCK (bsrc); - /* store one caps here so that we can return device caps even if - * audioclient was closed due to unprepare() */ - if (!self->cached_caps && caps) - self->cached_caps = gst_caps_ref (caps); - - if (!caps && self->cached_caps) - caps = gst_caps_ref (self->cached_caps); + /* Get caps might be able to block if device is not activated yet */ + caps = gst_wasapi2_ring_buffer_get_caps (ringbuffer); + gst_object_unref (ringbuffer); + } else { + GST_OBJECT_UNLOCK (bsrc); + } if (!caps) caps = gst_pad_get_pad_template_caps (bsrc->srcpad); @@ -317,239 +313,79 @@ gst_wasapi2_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) caps = filtered; } - GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps); + GST_DEBUG_OBJECT (bsrc, "returning caps %" GST_PTR_FORMAT, caps); return caps; } -static gboolean -gst_wasapi2_src_open_unlocked (GstAudioSrc * asrc) -{ - GstWasapi2Src *self = GST_WASAPI2_SRC (asrc); - - gst_clear_object (&self->client); - - self->client = - gst_wasapi2_client_new (GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE, - self->low_latency, -1, self->device_id, self->dispatcher); - - if (!self->client) - return FALSE; - - return TRUE; -} - -static gboolean -gst_wasapi2_src_open (GstAudioSrc * asrc) -{ - GstWasapi2Src *self = GST_WASAPI2_SRC (asrc); - gboolean ret; - - GST_DEBUG_OBJECT (self, "Opening device"); - - GST_WASAPI2_SRC_LOCK (self); - ret = gst_wasapi2_src_open_unlocked (asrc); - GST_WASAPI2_SRC_UNLOCK (self); - - if (!ret) { - GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL), - ("Failed to open device")); - return FALSE; - } - - return TRUE; -} - -static gboolean -gst_wasapi2_src_close (GstAudioSrc * asrc) -{ - GstWasapi2Src *self = GST_WASAPI2_SRC (asrc); - - GST_WASAPI2_SRC_LOCK (self); - - gst_clear_object (&self->client); - gst_clear_caps (&self->cached_caps); - self->started = FALSE; - - GST_WASAPI2_SRC_UNLOCK (self); - - return TRUE; -} - -static gboolean -gst_wasapi2_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec) -{ - GstWasapi2Src *self = GST_WASAPI2_SRC (asrc); - GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC (asrc); - gboolean ret = FALSE; - HRESULT hr; - - GST_WASAPI2_SRC_LOCK (self); - if (!self->client && !gst_wasapi2_src_open_unlocked (asrc)) { - GST_ERROR_OBJECT (self, "No audio client was configured"); - goto done; - } - - if (!gst_wasapi2_client_ensure_activation (self->client)) { - GST_ERROR_OBJECT (self, "Couldn't activate audio device"); - goto done; - } - - hr = gst_wasapi2_client_open (self->client, spec, bsrc->ringbuffer); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (self, "Couldn't open audio client"); - goto done; - } - - /* Set mute and volume here again, maybe when "mute" property was set, audioclient - * might not be configured at that moment */ - if (self->mute_changed) { - gst_wasapi2_client_set_mute (self->client, self->mute); - self->mute_changed = FALSE; - } - - if (self->volume_changed) { - gst_wasapi2_client_set_volume (self->client, self->volume); - self->volume_changed = FALSE; - } - - /* Will start IAudioClient on the first read request */ - self->started = FALSE; - ret = TRUE; - -done: - GST_WASAPI2_SRC_UNLOCK (self); - - return ret; -} - -static gboolean -gst_wasapi2_src_unprepare (GstAudioSrc * asrc) -{ - GstWasapi2Src *self = GST_WASAPI2_SRC (asrc); - - self->started = FALSE; - - /* Will reopen device later prepare() */ - GST_WASAPI2_SRC_LOCK (self); - if (self->client) { - gst_wasapi2_client_stop (self->client); - gst_clear_object (&self->client); - } - GST_WASAPI2_SRC_UNLOCK (self); - - return TRUE; -} - -static guint -gst_wasapi2_src_read (GstAudioSrc * asrc, gpointer data, guint length, - GstClockTime * timestamp) +static GstAudioRingBuffer * +gst_wasapi2_src_create_ringbuffer (GstAudioBaseSrc * src) { - GstWasapi2Src *self = GST_WASAPI2_SRC (asrc); - HRESULT hr; - guint read_len = 0; + GstWasapi2Src *self = GST_WASAPI2_SRC (src); + GstAudioRingBuffer *ringbuffer; + gchar *name; - if (!self->client) { - GST_ERROR_OBJECT (self, "No audio client was configured"); - return -1; - } - - if (!self->started) { - HRESULT hr = gst_wasapi2_client_start (self->client); - if (!gst_wasapi2_result (hr)) { - GST_ERROR_OBJECT (self, "Failed to re-start client"); - return -1; - } + name = g_strdup_printf ("%s-ringbuffer", GST_OBJECT_NAME (src)); - self->started = TRUE; - } + ringbuffer = + gst_wasapi2_ring_buffer_new (GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE, + self->low_latency, self->device_id, self->dispatcher, name); + g_free (name); - hr = gst_wasapi2_client_read (self->client, data, length, &read_len); - if (!gst_wasapi2_result (hr)) { - GST_WARNING_OBJECT (self, "Failed to read data"); - return -1; - } - - return read_len; -} - -static guint -gst_wasapi2_src_delay (GstAudioSrc * asrc) -{ - GstWasapi2Src *self = GST_WASAPI2_SRC (asrc); - guint32 delay; - HRESULT hr; - - if (!self->client) - return 0; - - hr = gst_wasapi2_client_delay (self->client, &delay); - if (!gst_wasapi2_result (hr)) { - GST_WARNING_OBJECT (self, "Failed to get delay"); - return 0; - } - - return delay; -} - -static void -gst_wasapi2_src_reset (GstAudioSrc * asrc) -{ - GstWasapi2Src *self = GST_WASAPI2_SRC (asrc); - - GST_DEBUG_OBJECT (self, "reset called"); - - self->started = FALSE; - - if (!self->client) - return; - - gst_wasapi2_client_stop (self->client); + return ringbuffer; } static void gst_wasapi2_src_set_mute (GstWasapi2Src * self, gboolean mute) { - GST_WASAPI2_SRC_LOCK (self); + GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC_CAST (self); + HRESULT hr; + + GST_OBJECT_LOCK (self); self->mute = mute; self->mute_changed = TRUE; - if (self->client) { - HRESULT hr = gst_wasapi2_client_set_mute (self->client, mute); + if (bsrc->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (bsrc->ringbuffer); + + hr = gst_wasapi2_ring_buffer_set_mute (ringbuffer, mute); if (FAILED (hr)) { GST_INFO_OBJECT (self, "Couldn't set mute"); } else { self->mute_changed = FALSE; } - } else { - GST_DEBUG_OBJECT (self, "audio client is not configured yet"); } - GST_WASAPI2_SRC_UNLOCK (self); + GST_OBJECT_UNLOCK (self); } static gboolean gst_wasapi2_src_get_mute (GstWasapi2Src * self) { + GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC_CAST (self); gboolean mute; + HRESULT hr; - GST_WASAPI2_SRC_LOCK (self); + GST_OBJECT_LOCK (self); mute = self->mute; - if (self->client) { - HRESULT hr = gst_wasapi2_client_get_mute (self->client, &mute); + if (bsrc->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (bsrc->ringbuffer); + + hr = gst_wasapi2_ring_buffer_get_mute (ringbuffer, &mute); + if (FAILED (hr)) { - GST_INFO_OBJECT (self, "Couldn't get mute state"); + GST_INFO_OBJECT (self, "Couldn't get mute"); } else { self->mute = mute; } - } else { - GST_DEBUG_OBJECT (self, "audio client is not configured yet"); } - GST_WASAPI2_SRC_UNLOCK (self); + GST_OBJECT_UNLOCK (self); return mute; } @@ -557,7 +393,10 @@ gst_wasapi2_src_get_mute (GstWasapi2Src * self) static void gst_wasapi2_src_set_volume (GstWasapi2Src * self, gdouble volume) { - GST_WASAPI2_SRC_LOCK (self); + GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC_CAST (self); + HRESULT hr; + + GST_OBJECT_LOCK (self); self->volume = volume; /* clip volume value */ @@ -565,42 +404,47 @@ gst_wasapi2_src_set_volume (GstWasapi2Src * self, gdouble volume) self->volume = MIN (1.0, self->volume); self->volume_changed = TRUE; - if (self->client) { - HRESULT hr = - gst_wasapi2_client_set_volume (self->client, (gfloat) self->volume); + if (bsrc->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (bsrc->ringbuffer); + + hr = gst_wasapi2_ring_buffer_set_volume (ringbuffer, (gfloat) self->volume); + if (FAILED (hr)) { GST_INFO_OBJECT (self, "Couldn't set volume"); } else { self->volume_changed = FALSE; } - } else { - GST_DEBUG_OBJECT (self, "audio client is not configured yet"); } - GST_WASAPI2_SRC_UNLOCK (self); + GST_OBJECT_UNLOCK (self); } static gdouble gst_wasapi2_src_get_volume (GstWasapi2Src * self) { + GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC_CAST (self); gfloat volume; + HRESULT hr; - GST_WASAPI2_SRC_LOCK (self); + GST_OBJECT_LOCK (self); volume = (gfloat) self->volume; - if (self->client) { - HRESULT hr = gst_wasapi2_client_get_volume (self->client, &volume); + if (bsrc->ringbuffer) { + GstWasapi2RingBuffer *ringbuffer = + GST_WASAPI2_RING_BUFFER (bsrc->ringbuffer); + + hr = gst_wasapi2_ring_buffer_get_volume (ringbuffer, &volume); + if (FAILED (hr)) { - GST_INFO_OBJECT (self, "Couldn't get volume"); + GST_INFO_OBJECT (self, "Couldn't set volume"); } else { self->volume = volume; } - } else { - GST_DEBUG_OBJECT (self, "audio client is not configured yet"); } - GST_WASAPI2_SRC_UNLOCK (self); + GST_OBJECT_UNLOCK (self); volume = MAX (0.0, volume); volume = MIN (1.0, volume); diff --git a/sys/wasapi2/gstwasapi2src.h b/sys/wasapi2/gstwasapi2src.h index c832db5e8..2b3a0a0d6 100644 --- a/sys/wasapi2/gstwasapi2src.h +++ b/sys/wasapi2/gstwasapi2src.h @@ -27,7 +27,7 @@ G_BEGIN_DECLS #define GST_TYPE_WASAPI2_SRC (gst_wasapi2_src_get_type ()) G_DECLARE_FINAL_TYPE (GstWasapi2Src, - gst_wasapi2_src, GST, WASAPI2_SRC, GstAudioSrc); + gst_wasapi2_src, GST, WASAPI2_SRC, GstAudioBaseSrc); G_END_DECLS diff --git a/sys/wasapi2/gstwasapi2util.c b/sys/wasapi2/gstwasapi2util.c index 81109d020..d19c96633 100644 --- a/sys/wasapi2/gstwasapi2util.c +++ b/sys/wasapi2/gstwasapi2util.c @@ -25,10 +25,56 @@ #include "gstwasapi2util.h" #include <audioclient.h> +#include <mmdeviceapi.h> GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_debug); #define GST_CAT_DEFAULT gst_wasapi2_debug +/* Desktop only defines */ +#ifndef KSAUDIO_SPEAKER_MONO +#define KSAUDIO_SPEAKER_MONO (SPEAKER_FRONT_CENTER) +#endif +#ifndef KSAUDIO_SPEAKER_1POINT1 +#define KSAUDIO_SPEAKER_1POINT1 (SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY) +#endif +#ifndef KSAUDIO_SPEAKER_STEREO +#define KSAUDIO_SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) +#endif +#ifndef KSAUDIO_SPEAKER_2POINT1 +#define KSAUDIO_SPEAKER_2POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY) +#endif +#ifndef KSAUDIO_SPEAKER_3POINT0 +#define KSAUDIO_SPEAKER_3POINT0 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER) +#endif +#ifndef KSAUDIO_SPEAKER_3POINT1 +#define KSAUDIO_SPEAKER_3POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY) +#endif +#ifndef KSAUDIO_SPEAKER_QUAD +#define KSAUDIO_SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ + SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) +#endif +#define KSAUDIO_SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER) +#ifndef KSAUDIO_SPEAKER_5POINT0 +#define KSAUDIO_SPEAKER_5POINT0 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | \ + SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) +#endif +#define KSAUDIO_SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) +#ifndef KSAUDIO_SPEAKER_7POINT0 +#define KSAUDIO_SPEAKER_7POINT0 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | \ + SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | \ + SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) +#endif +#ifndef KSAUDIO_SPEAKER_7POINT1 +#define KSAUDIO_SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \ + SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \ + SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | \ + SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER) +#endif + /* *INDENT-OFF* */ static struct { @@ -57,6 +103,27 @@ static struct {SPEAKER_TOP_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER}, {SPEAKER_TOP_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT} }; + +static DWORD default_ch_masks[] = { + 0, + KSAUDIO_SPEAKER_MONO, + /* 2ch */ + KSAUDIO_SPEAKER_STEREO, + /* 2.1ch */ + /* KSAUDIO_SPEAKER_3POINT0 ? */ + KSAUDIO_SPEAKER_2POINT1, + /* 4ch */ + /* KSAUDIO_SPEAKER_3POINT1 or KSAUDIO_SPEAKER_SURROUND ? */ + KSAUDIO_SPEAKER_QUAD, + /* 5ch */ + KSAUDIO_SPEAKER_5POINT0, + /* 5.1ch */ + KSAUDIO_SPEAKER_5POINT1, + /* 7ch */ + KSAUDIO_SPEAKER_7POINT0, + /* 7.1ch */ + KSAUDIO_SPEAKER_7POINT1, +}; /* *INDENT-ON* */ static const gchar * @@ -192,3 +259,173 @@ _gst_wasapi2_result (HRESULT hr, GstDebugCategory * cat, const gchar * file, return SUCCEEDED (hr); #endif } + +static void +gst_wasapi_util_channel_position_all_none (guint channels, + GstAudioChannelPosition * position) +{ + guint i; + + for (i = 0; i < channels; i++) + position[i] = GST_AUDIO_CHANNEL_POSITION_NONE; +} + +guint64 +gst_wasapi2_util_waveformatex_to_channel_mask (WAVEFORMATEX * format, + GstAudioChannelPosition ** out_position) +{ + guint i, ch; + guint64 mask = 0; + GstAudioChannelPosition *pos = NULL; + WORD nChannels = 0; + DWORD dwChannelMask = 0; + + nChannels = format->nChannels; + if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE *extensible = (WAVEFORMATEXTENSIBLE *) format; + dwChannelMask = extensible->dwChannelMask; + } + + if (out_position) + *out_position = NULL; + + if (nChannels > 2 && !dwChannelMask) { + GST_WARNING ("Unknown channel mask value for %d channel stream", nChannels); + + if (nChannels >= G_N_ELEMENTS (default_ch_masks)) { + GST_ERROR ("To may channels %d", nChannels); + return 0; + } + + dwChannelMask = default_ch_masks[nChannels]; + } + + 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 ("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 ("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 (i = 0, ch = 0; i < G_N_ELEMENTS (wasapi_to_gst_pos) && ch < nChannels; + i++) { + if (!(dwChannelMask & wasapi_to_gst_pos[i].wasapi_pos)) + /* no match, try next */ + continue; + mask |= G_GUINT64_CONSTANT (1) << wasapi_to_gst_pos[i].gst_pos; + pos[ch++] = wasapi_to_gst_pos[i].gst_pos; + } + + /* XXX: Warn if some channel masks couldn't be mapped? */ + + GST_DEBUG ("Converted WASAPI mask 0x%" G_GINT64_MODIFIER "x -> 0x%" + G_GINT64_MODIFIER "x", (guint64) dwChannelMask, (guint64) mask); + +out: + if (out_position) { + *out_position = pos; + } else { + g_free (pos); + } + + return mask; +} + +const gchar * +gst_wasapi2_util_waveformatex_to_audio_format (WAVEFORMATEX * format) +{ + const gchar *fmt_str = NULL; + GstAudioFormat fmt = GST_AUDIO_FORMAT_UNKNOWN; + + switch (format->wFormatTag) { + case WAVE_FORMAT_PCM: + fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN, + format->wBitsPerSample, format->wBitsPerSample); + break; + case WAVE_FORMAT_IEEE_FLOAT: + if (format->wBitsPerSample == 32) + fmt = GST_AUDIO_FORMAT_F32LE; + else if (format->wBitsPerSample == 64) + fmt = GST_AUDIO_FORMAT_F64LE; + break; + case WAVE_FORMAT_EXTENSIBLE: + { + WAVEFORMATEXTENSIBLE *ex = (WAVEFORMATEXTENSIBLE *) format; + if (IsEqualGUID (&ex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { + fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN, + format->wBitsPerSample, ex->Samples.wValidBitsPerSample); + } else if (IsEqualGUID (&ex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { + if (format->wBitsPerSample == 32 + && ex->Samples.wValidBitsPerSample == 32) + fmt = GST_AUDIO_FORMAT_F32LE; + else if (format->wBitsPerSample == 64 && + ex->Samples.wValidBitsPerSample == 64) + fmt = GST_AUDIO_FORMAT_F64LE; + } + break; + } + default: + break; + } + + if (fmt != GST_AUDIO_FORMAT_UNKNOWN) + fmt_str = gst_audio_format_to_string (fmt); + + return fmt_str; +} + +gboolean +gst_wasapi2_util_parse_waveformatex (WAVEFORMATEX * format, + GstCaps * template_caps, GstCaps ** out_caps, + GstAudioChannelPosition ** out_positions) +{ + const gchar *afmt; + guint64 channel_mask; + + *out_caps = NULL; + + /* TODO: handle SPDIF and other encoded formats */ + + /* 1 or 2 channels <= 16 bits sample size OR + * 1 or 2 channels > 16 bits sample size or >2 channels */ + if (format->wFormatTag != WAVE_FORMAT_PCM && + format->wFormatTag != WAVE_FORMAT_IEEE_FLOAT && + format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) + /* Unhandled format tag */ + 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_wasapi2_util_waveformatex_to_audio_format (format); + if (afmt == NULL) + return FALSE; + + *out_caps = gst_caps_copy (template_caps); + + channel_mask = gst_wasapi2_util_waveformatex_to_channel_mask (format, + out_positions); + + gst_caps_set_simple (*out_caps, + "format", G_TYPE_STRING, afmt, + "channels", G_TYPE_INT, format->nChannels, + "rate", G_TYPE_INT, format->nSamplesPerSec, NULL); + + if (channel_mask) { + gst_caps_set_simple (*out_caps, + "channel-mask", GST_TYPE_BITMASK, channel_mask, NULL); + } + + return TRUE; +} diff --git a/sys/wasapi2/gstwasapi2util.h b/sys/wasapi2/gstwasapi2util.h index 67156f952..cb977fb94 100644 --- a/sys/wasapi2/gstwasapi2util.h +++ b/sys/wasapi2/gstwasapi2util.h @@ -23,6 +23,7 @@ #include <gst/gst.h> #include <gst/audio/audio.h> #include <windows.h> +#include <initguid.h> #include <audioclient.h> G_BEGIN_DECLS @@ -34,6 +35,13 @@ G_BEGIN_DECLS "rate = " GST_AUDIO_RATE_RANGE ", " \ "channels = " GST_AUDIO_CHANNELS_RANGE +#define GST_WASAPI2_CLEAR_COM(obj) G_STMT_START { \ + if (obj) { \ + (obj)->Release (); \ + (obj) = NULL; \ + } \ + } G_STMT_END + gboolean _gst_wasapi2_result (HRESULT hr, GstDebugCategory * cat, const gchar * file, @@ -43,6 +51,16 @@ gboolean _gst_wasapi2_result (HRESULT hr, #define gst_wasapi2_result(result) \ _gst_wasapi2_result (result, GST_CAT_DEFAULT, __FILE__, GST_FUNCTION, __LINE__) +guint64 gst_wasapi2_util_waveformatex_to_channel_mask (WAVEFORMATEX * format, + GstAudioChannelPosition ** out_position); + +const gchar * gst_wasapi2_util_waveformatex_to_audio_format (WAVEFORMATEX * format); + +gboolean gst_wasapi2_util_parse_waveformatex (WAVEFORMATEX * format, + GstCaps * template_caps, + GstCaps ** out_caps, + GstAudioChannelPosition ** out_positions); + G_END_DECLS #endif /* __GST_WASAPI_UTIL_H__ */ diff --git a/sys/wasapi2/meson.build b/sys/wasapi2/meson.build index 84d37c9ee..5edbfff2d 100644 --- a/sys/wasapi2/meson.build +++ b/sys/wasapi2/meson.build @@ -4,6 +4,7 @@ wasapi2_sources = [ 'gstwasapi2util.c', 'gstwasapi2client.cpp', 'gstwasapi2device.c', + 'gstwasapi2ringbuffer.cpp', 'plugin.c', ] @@ -26,7 +27,8 @@ ole32_dep = cc.find_library('ole32', required : get_option('wasapi2')) ksuser_dep = cc.find_library('ksuser', required : get_option('wasapi2')) runtimeobject_dep = cc.find_library('runtimeobject', required : get_option('wasapi2')) mmdeviceapi_dep = cc.find_library('mmdevapi', required : get_option('wasapi2')) -wasapi2_dep = [ole32_dep, ksuser_dep, runtimeobject_dep, mmdeviceapi_dep] +mfplat_dep = cc.find_library('mfplat', required : get_option('wasapi2')) +wasapi2_dep = [ole32_dep, ksuser_dep, runtimeobject_dep, mmdeviceapi_dep, mfplat_dep] extra_args = [] foreach dep: wasapi2_dep diff --git a/sys/wasapi2/plugin.c b/sys/wasapi2/plugin.c index 18394ee0a..39cdf4c14 100644 --- a/sys/wasapi2/plugin.c +++ b/sys/wasapi2/plugin.c @@ -26,14 +26,23 @@ #include "gstwasapi2sink.h" #include "gstwasapi2src.h" #include "gstwasapi2device.h" +#include "gstwasapi2util.h" +#include <mfapi.h> GST_DEBUG_CATEGORY (gst_wasapi2_debug); GST_DEBUG_CATEGORY (gst_wasapi2_client_debug); +static void +plugin_deinit (gpointer data) +{ + MFShutdown (); +} + static gboolean plugin_init (GstPlugin * plugin) { GstRank rank = GST_RANK_SECONDARY; + HRESULT hr; /** * plugin-wasapi2: @@ -41,6 +50,11 @@ plugin_init (GstPlugin * plugin) * Since: 1.18 */ + hr = MFStartup (MF_VERSION, MFSTARTUP_NOSOCKET); + if (!gst_wasapi2_result (hr)) { + GST_WARNING ("MFStartup failure, hr: 0x%x", hr); + return TRUE; + } #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) /* If we are building for UWP, wasapi2 plugin should have the highest rank */ rank = GST_RANK_PRIMARY + 1; @@ -56,6 +70,10 @@ plugin_init (GstPlugin * plugin) gst_device_provider_register (plugin, "wasapi2deviceprovider", rank, GST_TYPE_WASAPI2_DEVICE_PROVIDER); + g_object_set_data_full (G_OBJECT (plugin), + "plugin-wasapi2-shutdown", "shutdown-data", + (GDestroyNotify) plugin_deinit); + return TRUE; } |