/* * GStreamer * Copyright 2005 Thomas Vander Stichele * Copyright 2005 Ronald S. Bultje * Copyright 2005 S�bastien Moutte * Copyright 2006 Joni Valtanen * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Alternatively, the contents of this file may be used under the * GNU Lesser General Public License Version 2.1 (the "LGPL"), in * which case the following provisions apply instead of the ones * mentioned above: * * 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. */ /* TODO: add mixer device init for selection by device-guid */ /** * SECTION:element-directsoundsrc * @title: directsoundsrc * * Reads audio data using the DirectSound API. * * ## Example pipelines * |[ * gst-launch-1.0 -v directsoundsrc ! audioconvert ! vorbisenc ! oggmux ! filesink location=dsound.ogg * ]| Record from DirectSound and encode to Ogg/Vorbis. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "gstdirectsoundsrc.h" #include #include #include #include GST_DEBUG_CATEGORY_STATIC (directsoundsrc_debug); #define GST_CAT_DEFAULT directsoundsrc_debug /* defaults here */ #define DEFAULT_DEVICE 0 #define DEFAULT_MUTE FALSE /* properties */ enum { PROP_0, PROP_DEVICE_NAME, PROP_DEVICE, PROP_VOLUME, PROP_MUTE }; static HRESULT (WINAPI * pDSoundCaptureCreate) (LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN); static void gst_directsound_src_finalize (GObject * object); static void gst_directsound_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_directsound_src_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static gboolean gst_directsound_src_open (GstAudioSrc * asrc); static gboolean gst_directsound_src_close (GstAudioSrc * asrc); static gboolean gst_directsound_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec); static gboolean gst_directsound_src_unprepare (GstAudioSrc * asrc); static void gst_directsound_src_reset (GstAudioSrc * asrc); static GstCaps *gst_directsound_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter); static guint gst_directsound_src_read (GstAudioSrc * asrc, gpointer data, guint length, GstClockTime * timestamp); static void gst_directsound_src_dispose (GObject * object); static guint gst_directsound_src_delay (GstAudioSrc * asrc); static gboolean gst_directsound_src_mixer_find (GstDirectSoundSrc * dsoundsrc, MIXERCAPS * mixer_caps); static void gst_directsound_src_mixer_init (GstDirectSoundSrc * dsoundsrc); static gdouble gst_directsound_src_get_volume (GstDirectSoundSrc * dsoundsrc); static void gst_directsound_src_set_volume (GstDirectSoundSrc * dsoundsrc, gdouble volume); static gboolean gst_directsound_src_get_mute (GstDirectSoundSrc * dsoundsrc); static void gst_directsound_src_set_mute (GstDirectSoundSrc * dsoundsrc, gboolean mute); static const gchar *gst_directsound_src_get_device (GstDirectSoundSrc * dsoundsrc); static void gst_directsound_src_set_device (GstDirectSoundSrc * dsoundsrc, const gchar * device_id); static GstStaticPadTemplate directsound_src_src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw, " "format = (string) { S16LE, S8 }, " "layout = (string) interleaved, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]")); #define gst_directsound_src_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstDirectSoundSrc, gst_directsound_src, GST_TYPE_AUDIO_SRC, G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL) ); static void gst_directsound_src_dispose (GObject * object) { G_OBJECT_CLASS (parent_class)->dispose (object); } static void gst_directsound_src_finalize (GObject * object) { GstDirectSoundSrc *dsoundsrc = GST_DIRECTSOUND_SRC (object); g_mutex_clear (&dsoundsrc->dsound_lock); gst_object_unref (dsoundsrc->system_clock); if (dsoundsrc->read_wait_clock_id != NULL) gst_clock_id_unref (dsoundsrc->read_wait_clock_id); g_free (dsoundsrc->device_name); g_free (dsoundsrc->device_id); g_free (dsoundsrc->device_guid); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_directsound_src_class_init (GstDirectSoundSrcClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; GstBaseSrcClass *gstbasesrc_class; GstAudioSrcClass *gstaudiosrc_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstbasesrc_class = (GstBaseSrcClass *) klass; gstaudiosrc_class = (GstAudioSrcClass *) klass; GST_DEBUG_CATEGORY_INIT (directsoundsrc_debug, "directsoundsrc", 0, "DirectSound Src"); GST_DEBUG ("initializing directsoundsrc class"); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_directsound_src_finalize); gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_directsound_src_dispose); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_directsound_src_get_property); gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_directsound_src_set_property); gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_directsound_src_getcaps); gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_directsound_src_open); gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_directsound_src_close); gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_directsound_src_read); gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_directsound_src_prepare); gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_directsound_src_unprepare); gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_directsound_src_delay); gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_directsound_src_reset); gst_element_class_set_static_metadata (gstelement_class, "DirectSound audio source", "Source/Audio", "Capture from a soundcard via DirectSound", "Joni Valtanen "); gst_element_class_add_static_pad_template (gstelement_class, &directsound_src_src_factory); g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, g_param_spec_string ("device-name", "Device name", "Human-readable name of the sound device", NULL, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_DEVICE, g_param_spec_string ("device", "Device", "DirectSound playback device as a GUID string (volume and mute will not work!)", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_VOLUME, g_param_spec_double ("volume", "Volume", "Volume of this stream", 0.0, 1.0, 1.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_MUTE, g_param_spec_boolean ("mute", "Mute", "Mute state of this stream", DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static GstCaps * gst_directsound_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter) { GstCaps *caps = NULL; GST_DEBUG_OBJECT (bsrc, "get caps"); caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc)); return caps; } static void gst_directsound_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstDirectSoundSrc *src = GST_DIRECTSOUND_SRC (object); GST_DEBUG ("set property"); switch (prop_id) { case PROP_DEVICE_NAME: if (src->device_name) { g_free (src->device_name); src->device_name = NULL; } if (g_value_get_string (value)) { src->device_name = g_strdup (g_value_get_string (value)); } break; case PROP_VOLUME: gst_directsound_src_set_volume (src, g_value_get_double (value)); break; case PROP_MUTE: gst_directsound_src_set_mute (src, g_value_get_boolean (value)); break; case PROP_DEVICE: gst_directsound_src_set_device (src, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_directsound_src_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstDirectSoundSrc *src = GST_DIRECTSOUND_SRC (object); GST_DEBUG ("get property"); switch (prop_id) { case PROP_DEVICE_NAME: g_value_set_string (value, src->device_name); break; case PROP_DEVICE: g_value_set_string (value, gst_directsound_src_get_device (src)); break; case PROP_VOLUME: g_value_set_double (value, gst_directsound_src_get_volume (src)); break; case PROP_MUTE: g_value_set_boolean (value, gst_directsound_src_get_mute (src)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /* initialize the new element * instantiate pads and add them to element * set functions * initialize structure */ static void gst_directsound_src_init (GstDirectSoundSrc * src) { GST_DEBUG_OBJECT (src, "initializing directsoundsrc"); g_mutex_init (&src->dsound_lock); src->system_clock = gst_system_clock_obtain (); src->read_wait_clock_id = NULL; src->reset_while_sleeping = FALSE; src->device_guid = NULL; src->device_id = NULL; src->device_name = NULL; src->mixer = NULL; src->control_id_mute = -1; src->control_id_volume = -1; src->volume = 100; src->mute = FALSE; } /* Enumeration callback called by DirectSoundCaptureEnumerate. * Gets the GUID of request audio device */ static BOOL CALLBACK gst_directsound_enum_callback (GUID * pGUID, TCHAR * strDesc, TCHAR * strDrvName, VOID * pContext) { GstDirectSoundSrc *dsoundsrc = GST_DIRECTSOUND_SRC (pContext); gchar *driver, *description; description = g_locale_to_utf8 (strDesc, -1, NULL, NULL, NULL); if (!description) { GST_ERROR_OBJECT (dsoundsrc, "Failed to convert description from locale encoding to UTF8"); return TRUE; } driver = g_locale_to_utf8 (strDrvName, -1, NULL, NULL, NULL); if (pGUID && dsoundsrc && dsoundsrc->device_name && !g_strcmp0 (dsoundsrc->device_name, description)) { g_free (dsoundsrc->device_guid); dsoundsrc->device_guid = (GUID *) g_malloc0 (sizeof (GUID)); memcpy (dsoundsrc->device_guid, pGUID, sizeof (GUID)); GST_INFO_OBJECT (dsoundsrc, "found the requested audio device :%s", dsoundsrc->device_name); g_free (description); g_free (driver); return FALSE; } GST_INFO_OBJECT (dsoundsrc, "sound device names: %s, %s, requested device:%s", description, driver, dsoundsrc->device_name); g_free (description); g_free (driver); return TRUE; } static LPGUID string_to_guid (const gchar * str) { HRESULT ret; gunichar2 *wstr; LPGUID out; wstr = g_utf8_to_utf16 (str, -1, NULL, NULL, NULL); if (!wstr) return NULL; out = g_new (GUID, 1); ret = CLSIDFromString ((LPOLESTR) wstr, out); g_free (wstr); if (ret != NOERROR) { g_free (out); return NULL; } return out; } static gboolean gst_directsound_src_open (GstAudioSrc * asrc) { GstDirectSoundSrc *dsoundsrc; HRESULT hRes; /* Result for windows functions */ GST_DEBUG_OBJECT (asrc, "opening directsoundsrc"); dsoundsrc = GST_DIRECTSOUND_SRC (asrc); /* Open dsound.dll */ dsoundsrc->DSoundDLL = LoadLibrary ("dsound.dll"); if (!dsoundsrc->DSoundDLL) { goto dsound_open; } /* Building the DLL Calls */ pDSoundCaptureCreate = (void *) GetProcAddress (dsoundsrc->DSoundDLL, TEXT ("DirectSoundCaptureCreate")); /* If everything is not ok */ if (!pDSoundCaptureCreate) { goto capture_function; } if (dsoundsrc->device_id) { GST_DEBUG_OBJECT (asrc, "device id set to: %s ", dsoundsrc->device_id); dsoundsrc->device_guid = string_to_guid (dsoundsrc->device_id); if (dsoundsrc->device_guid == NULL) { GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ, ("gst_directsound_src_open: device set, but guid not found: %s", dsoundsrc->device_id), (NULL)); g_free (dsoundsrc->device_guid); return FALSE; } } else { hRes = DirectSoundCaptureEnumerate ((LPDSENUMCALLBACK) gst_directsound_enum_callback, (VOID *) dsoundsrc); if (FAILED (hRes)) { goto capture_enumerate; } } /* Create capture object */ hRes = pDSoundCaptureCreate (dsoundsrc->device_guid, &dsoundsrc->pDSC, NULL); if (FAILED (hRes)) { goto capture_object; } // mixer is only supported when device-id is not set if (!dsoundsrc->device_id) { gst_directsound_src_mixer_init (dsoundsrc); } return TRUE; capture_function: { FreeLibrary (dsoundsrc->DSoundDLL); GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ, ("Unable to get capturecreate function"), (NULL)); return FALSE; } capture_enumerate: { FreeLibrary (dsoundsrc->DSoundDLL); GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ, ("Unable to enumerate audio capture devices"), (NULL)); return FALSE; } capture_object: { FreeLibrary (dsoundsrc->DSoundDLL); GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ, ("Unable to create capture object"), (NULL)); return FALSE; } dsound_open: { DWORD err = GetLastError (); GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ, ("Unable to open dsound.dll"), (NULL)); g_print ("0x%lx\n", HRESULT_FROM_WIN32 (err)); return FALSE; } } static gboolean gst_directsound_src_close (GstAudioSrc * asrc) { GstDirectSoundSrc *dsoundsrc; GST_DEBUG_OBJECT (asrc, "closing directsoundsrc"); dsoundsrc = GST_DIRECTSOUND_SRC (asrc); /* Release capture handler */ IDirectSoundCapture_Release (dsoundsrc->pDSC); /* Close library */ FreeLibrary (dsoundsrc->DSoundDLL); if (dsoundsrc->mixer) mixerClose (dsoundsrc->mixer); return TRUE; } static gboolean gst_directsound_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec) { GstDirectSoundSrc *dsoundsrc; WAVEFORMATEX wfx; /* Wave format structure */ HRESULT hRes; /* Result for windows functions */ DSCBUFFERDESC descSecondary; /* Capturebuffer description */ dsoundsrc = GST_DIRECTSOUND_SRC (asrc); GST_DEBUG_OBJECT (asrc, "preparing directsoundsrc"); /* Define buffer */ memset (&wfx, 0, sizeof (WAVEFORMATEX)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = GST_AUDIO_INFO_CHANNELS (&spec->info); wfx.nSamplesPerSec = GST_AUDIO_INFO_RATE (&spec->info); wfx.wBitsPerSample = GST_AUDIO_INFO_BPF (&spec->info) * 8 / wfx.nChannels; wfx.nBlockAlign = GST_AUDIO_INFO_BPF (&spec->info); wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; /* Ignored for WAVE_FORMAT_PCM. */ wfx.cbSize = 0; if (wfx.wBitsPerSample != 16 && wfx.wBitsPerSample != 8) goto dodgy_width; GST_INFO_OBJECT (asrc, "latency time: %" G_GUINT64_FORMAT " - buffer time: %" G_GUINT64_FORMAT, spec->latency_time, spec->buffer_time); /* Buffer-time should always be >= 2*latency */ if (spec->buffer_time < spec->latency_time * 2) { spec->buffer_time = spec->latency_time * 2; GST_WARNING ("buffer-time was less than 2*latency-time, clamping"); } /* Set the buffer size from our configured buffer time (in microsecs) */ dsoundsrc->buffer_size = gst_util_uint64_scale_int (spec->buffer_time, wfx.nAvgBytesPerSec, GST_SECOND / GST_USECOND); GST_INFO_OBJECT (asrc, "Buffer size: %d", dsoundsrc->buffer_size); spec->segsize = gst_util_uint64_scale (spec->latency_time, wfx.nAvgBytesPerSec, GST_SECOND / GST_USECOND); /* Sanitized segsize */ if (spec->segsize < GST_AUDIO_INFO_BPF (&spec->info)) spec->segsize = GST_AUDIO_INFO_BPF (&spec->info); else if (spec->segsize % GST_AUDIO_INFO_BPF (&spec->info) != 0) spec->segsize = ((spec->segsize + GST_AUDIO_INFO_BPF (&spec->info) - 1) / GST_AUDIO_INFO_BPF (&spec->info)) * GST_AUDIO_INFO_BPF (&spec->info); spec->segtotal = dsoundsrc->buffer_size / spec->segsize; /* The device usually takes time = 1-2 segments to start producing buffers */ spec->seglatency = spec->segtotal + 2; /* Fetch and set the actual latency time that will be used */ dsoundsrc->latency_time = gst_util_uint64_scale (spec->segsize, GST_SECOND / GST_USECOND, GST_AUDIO_INFO_BPF (&spec->info) * GST_AUDIO_INFO_RATE (&spec->info)); GST_INFO_OBJECT (asrc, "actual latency time: %" G_GUINT64_FORMAT, spec->latency_time); /* Init secondary buffer desciption */ memset (&descSecondary, 0, sizeof (DSCBUFFERDESC)); descSecondary.dwSize = sizeof (DSCBUFFERDESC); descSecondary.dwFlags = 0; descSecondary.dwReserved = 0; /* This is not primary buffer so have to set size */ descSecondary.dwBufferBytes = dsoundsrc->buffer_size; descSecondary.lpwfxFormat = &wfx; /* Create buffer */ hRes = IDirectSoundCapture_CreateCaptureBuffer (dsoundsrc->pDSC, &descSecondary, &dsoundsrc->pDSBSecondary, NULL); if (hRes != DS_OK) goto capture_buffer; dsoundsrc->bytes_per_sample = GST_AUDIO_INFO_BPF (&spec->info); GST_INFO_OBJECT (asrc, "bytes/sec: %lu, buffer size: %d, segsize: %d, segtotal: %d", wfx.nAvgBytesPerSec, dsoundsrc->buffer_size, spec->segsize, spec->segtotal); /* Not read anything yet */ dsoundsrc->current_circular_offset = 0; GST_INFO_OBJECT (asrc, "channels: %d, rate: %d, bytes_per_sample: %d" " WAVEFORMATEX.nSamplesPerSec: %ld, WAVEFORMATEX.wBitsPerSample: %d," " WAVEFORMATEX.nBlockAlign: %d, WAVEFORMATEX.nAvgBytesPerSec: %ld", GST_AUDIO_INFO_CHANNELS (&spec->info), GST_AUDIO_INFO_RATE (&spec->info), GST_AUDIO_INFO_BPF (&spec->info), wfx.nSamplesPerSec, wfx.wBitsPerSample, wfx.nBlockAlign, wfx.nAvgBytesPerSec); return TRUE; capture_buffer: { GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ, ("Unable to create capturebuffer"), (NULL)); return FALSE; } dodgy_width: { GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ, ("Unexpected width %d", wfx.wBitsPerSample), (NULL)); return FALSE; } } static gboolean gst_directsound_src_unprepare (GstAudioSrc * asrc) { GstDirectSoundSrc *dsoundsrc; GST_DEBUG_OBJECT (asrc, "unpreparing directsoundsrc"); dsoundsrc = GST_DIRECTSOUND_SRC (asrc); GST_DSOUND_LOCK (dsoundsrc); /* Stop capturing */ IDirectSoundCaptureBuffer_Stop (dsoundsrc->pDSBSecondary); /* Release buffer */ IDirectSoundCaptureBuffer_Release (dsoundsrc->pDSBSecondary); GST_DSOUND_UNLOCK (dsoundsrc); return TRUE; } /* return number of readed bytes */ static guint gst_directsound_src_read (GstAudioSrc * asrc, gpointer data, guint length, GstClockTime * timestamp) { GstDirectSoundSrc *dsoundsrc; guint64 sleep_time_ms, sleep_until; GstClockID clock_id; HRESULT hRes; /* Result for windows functions */ DWORD dwCurrentCaptureCursor = 0; DWORD dwBufferSize = 0; LPVOID pLockedBuffer1 = NULL; LPVOID pLockedBuffer2 = NULL; DWORD dwSizeBuffer1 = 0; DWORD dwSizeBuffer2 = 0; DWORD dwStatus = 0; GST_DEBUG_OBJECT (asrc, "reading directsoundsrc"); dsoundsrc = GST_DIRECTSOUND_SRC (asrc); GST_DSOUND_LOCK (dsoundsrc); /* Get current buffer status */ hRes = IDirectSoundCaptureBuffer_GetStatus (dsoundsrc->pDSBSecondary, &dwStatus); if (FAILED (hRes)) { GST_DSOUND_UNLOCK (dsoundsrc); return -1; } /* Starting capturing if not already */ if (!(dwStatus & DSCBSTATUS_CAPTURING)) { hRes = IDirectSoundCaptureBuffer_Start (dsoundsrc->pDSBSecondary, DSCBSTART_LOOPING); GST_INFO_OBJECT (asrc, "capture started"); } /* Loop till the source has produced bytes equal to or greater than @length. * * DirectSound has a notification-based API that uses Windows CreateEvent() * + WaitForSingleObject(), but it is completely useless for live streams. * * 1. You must schedule all events before starting capture * 2. The events are all fired exactly once * 3. You cannot schedule new events while a capture is running * 4. You cannot stop/schedule/start either * * This means you cannot use the API while doing live looped capture and we * must resort to this. * * However, this is almost as efficient as event-based capture since it's ok * to consistently overwait by a fixed amount; the extra bytes will just end * up being used in the next call, and the extra latency will be constant. */ while (TRUE) { hRes = IDirectSoundCaptureBuffer_GetCurrentPosition (dsoundsrc->pDSBSecondary, &dwCurrentCaptureCursor, NULL); if (FAILED (hRes)) { GST_DSOUND_UNLOCK (dsoundsrc); return -1; } /* calculate the size of the buffer that's been captured while accounting * for wrap-arounds */ if (dwCurrentCaptureCursor < dsoundsrc->current_circular_offset) { dwBufferSize = dsoundsrc->buffer_size - (dsoundsrc->current_circular_offset - dwCurrentCaptureCursor); } else { dwBufferSize = dwCurrentCaptureCursor - dsoundsrc->current_circular_offset; } if (dwBufferSize >= length) { /* Yay, we got all the data we need */ break; } else { GST_DEBUG_OBJECT (asrc, "not enough data, got %lu (want at least %u)", dwBufferSize, length); /* If we didn't get enough data, sleep for a proportionate time */ sleep_time_ms = gst_util_uint64_scale (dsoundsrc->latency_time, length - dwBufferSize, length * 1000); /* Make sure we don't run in a tight loop unnecessarily */ sleep_time_ms = MAX (sleep_time_ms, 10); /* Sleep using gst_clock_id_wait() so that we can be interrupted */ sleep_until = gst_clock_get_time (dsoundsrc->system_clock) + sleep_time_ms * GST_MSECOND; /* Setup the clock id wait */ if (G_UNLIKELY (dsoundsrc->read_wait_clock_id == NULL || gst_clock_single_shot_id_reinit (dsoundsrc->system_clock, dsoundsrc->read_wait_clock_id, sleep_until) == FALSE)) { if (dsoundsrc->read_wait_clock_id != NULL) gst_clock_id_unref (dsoundsrc->read_wait_clock_id); dsoundsrc->read_wait_clock_id = gst_clock_new_single_shot_id (dsoundsrc->system_clock, sleep_until); } clock_id = dsoundsrc->read_wait_clock_id; dsoundsrc->reset_while_sleeping = FALSE; GST_DEBUG_OBJECT (asrc, "waiting %" G_GUINT64_FORMAT "ms for more data", sleep_time_ms); GST_DSOUND_UNLOCK (dsoundsrc); gst_clock_id_wait (clock_id, NULL); GST_DSOUND_LOCK (dsoundsrc); if (dsoundsrc->reset_while_sleeping == TRUE) { GST_DEBUG_OBJECT (asrc, "reset while sleeping, cancelled read"); GST_DSOUND_UNLOCK (dsoundsrc); return -1; } } } GST_DEBUG_OBJECT (asrc, "Got enough data: %lu bytes (wanted at least %u)", dwBufferSize, length); /* Lock the buffer and read only the first @length bytes. Keep the rest in * the capture buffer for the next read. */ hRes = IDirectSoundCaptureBuffer_Lock (dsoundsrc->pDSBSecondary, dsoundsrc->current_circular_offset, length, &pLockedBuffer1, &dwSizeBuffer1, &pLockedBuffer2, &dwSizeBuffer2, 0L); /* NOTE: We now assume that dwSizeBuffer1 + dwSizeBuffer2 == length since the * API is supposed to guarantee that */ /* Copy buffer data to another buffer */ if (hRes == DS_OK) { memcpy (data, pLockedBuffer1, dwSizeBuffer1); } /* ...and if something is in another buffer */ if (pLockedBuffer2 != NULL) { memcpy (((guchar *) data + dwSizeBuffer1), pLockedBuffer2, dwSizeBuffer2); } dsoundsrc->current_circular_offset += dwSizeBuffer1 + dwSizeBuffer2; dsoundsrc->current_circular_offset %= dsoundsrc->buffer_size; IDirectSoundCaptureBuffer_Unlock (dsoundsrc->pDSBSecondary, pLockedBuffer1, dwSizeBuffer1, pLockedBuffer2, dwSizeBuffer2); GST_DSOUND_UNLOCK (dsoundsrc); /* We always read exactly @length data */ return length; } static guint gst_directsound_src_delay (GstAudioSrc * asrc) { GstDirectSoundSrc *dsoundsrc; HRESULT hRes; DWORD dwCurrentCaptureCursor; DWORD dwBytesInQueue = 0; gint nNbSamplesInQueue = 0; GST_INFO_OBJECT (asrc, "Delay"); dsoundsrc = GST_DIRECTSOUND_SRC (asrc); /* evaluate the number of samples in queue in the circular buffer */ hRes = IDirectSoundCaptureBuffer_GetCurrentPosition (dsoundsrc->pDSBSecondary, &dwCurrentCaptureCursor, NULL); /* FIXME: Check is this calculated right */ if (hRes == S_OK) { if (dwCurrentCaptureCursor < dsoundsrc->current_circular_offset) { dwBytesInQueue = dsoundsrc->buffer_size - (dsoundsrc->current_circular_offset - dwCurrentCaptureCursor); } else { dwBytesInQueue = dwCurrentCaptureCursor - dsoundsrc->current_circular_offset; } nNbSamplesInQueue = dwBytesInQueue / dsoundsrc->bytes_per_sample; } GST_INFO_OBJECT (asrc, "Delay is %d samples", nNbSamplesInQueue); return nNbSamplesInQueue; } static void gst_directsound_src_reset (GstAudioSrc * asrc) { GstDirectSoundSrc *dsoundsrc; LPVOID pLockedBuffer = NULL; DWORD dwSizeBuffer = 0; GST_DEBUG_OBJECT (asrc, "reset directsoundsrc"); dsoundsrc = GST_DIRECTSOUND_SRC (asrc); GST_DSOUND_LOCK (dsoundsrc); dsoundsrc->reset_while_sleeping = TRUE; /* Interrupt read sleep if required */ if (dsoundsrc->read_wait_clock_id != NULL) gst_clock_id_unschedule (dsoundsrc->read_wait_clock_id); if (dsoundsrc->pDSBSecondary) { /*stop capturing */ HRESULT hRes = IDirectSoundCaptureBuffer_Stop (dsoundsrc->pDSBSecondary); /*reset position */ /* hRes = IDirectSoundCaptureBuffer_SetCurrentPosition (dsoundsrc->pDSBSecondary, 0); */ /*reset the buffer */ hRes = IDirectSoundCaptureBuffer_Lock (dsoundsrc->pDSBSecondary, dsoundsrc->current_circular_offset, dsoundsrc->buffer_size, pLockedBuffer, &dwSizeBuffer, NULL, NULL, 0L); if (SUCCEEDED (hRes)) { memset (pLockedBuffer, 0, dwSizeBuffer); hRes = IDirectSoundCaptureBuffer_Unlock (dsoundsrc->pDSBSecondary, pLockedBuffer, dwSizeBuffer, NULL, 0); } dsoundsrc->current_circular_offset = 0; } GST_DSOUND_UNLOCK (dsoundsrc); } /* If the PROP_DEVICE_NAME is set, find the mixer related to device; * otherwise we get the default input mixer. */ static gboolean gst_directsound_src_mixer_find (GstDirectSoundSrc * dsoundsrc, MIXERCAPS * mixer_caps) { MMRESULT mmres; guint i, num_mixers; num_mixers = mixerGetNumDevs (); for (i = 0; i < num_mixers; i++) { mmres = mixerOpen (&dsoundsrc->mixer, i, 0L, 0L, MIXER_OBJECTF_MIXER | MIXER_OBJECTF_WAVEIN); if (mmres != MMSYSERR_NOERROR) continue; mmres = mixerGetDevCaps ((UINT_PTR) dsoundsrc->mixer, mixer_caps, sizeof (MIXERCAPS)); if (mmres != MMSYSERR_NOERROR) { mixerClose (dsoundsrc->mixer); continue; } /* Get default mixer */ if (dsoundsrc->device_name == NULL) { GST_DEBUG ("Got default input mixer: %s", mixer_caps->szPname); return TRUE; } if (g_strstr_len (dsoundsrc->device_name, -1, mixer_caps->szPname) != NULL) { GST_DEBUG ("Got requested input mixer: %s", mixer_caps->szPname); return TRUE; } /* Wrong mixer */ mixerClose (dsoundsrc->mixer); } GST_DEBUG ("Can't find input mixer"); return FALSE; } static void gst_directsound_src_mixer_init (GstDirectSoundSrc * dsoundsrc) { gint i, k; gboolean found_mic; MMRESULT mmres; MIXERCAPS mixer_caps; MIXERLINE mixer_line; MIXERLINECONTROLS ml_ctrl; PMIXERCONTROL pamixer_ctrls; if (!gst_directsound_src_mixer_find (dsoundsrc, &mixer_caps)) goto mixer_init_fail; /* Find the MIXERLINE related to MICROPHONE */ found_mic = FALSE; for (i = 0; i < mixer_caps.cDestinations && !found_mic; i++) { gint j, num_connections; mixer_line.cbStruct = sizeof (mixer_line); mixer_line.dwDestination = i; mmres = mixerGetLineInfo ((HMIXEROBJ) dsoundsrc->mixer, &mixer_line, MIXER_GETLINEINFOF_DESTINATION); if (mmres != MMSYSERR_NOERROR) goto mixer_init_fail; num_connections = mixer_line.cConnections; for (j = 0; j < num_connections && !found_mic; j++) { mixer_line.cbStruct = sizeof (mixer_line); mixer_line.dwDestination = i; mixer_line.dwSource = j; mmres = mixerGetLineInfo ((HMIXEROBJ) dsoundsrc->mixer, &mixer_line, MIXER_GETLINEINFOF_SOURCE); if (mmres != MMSYSERR_NOERROR) goto mixer_init_fail; if (mixer_line.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE || mixer_line.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_LINE) found_mic = TRUE; } } if (found_mic == FALSE) { GST_DEBUG ("Can't find mixer line related to input"); goto mixer_init_fail; } /* Get control associated with microphone audio line */ pamixer_ctrls = g_malloc (sizeof (MIXERCONTROL) * mixer_line.cControls); ml_ctrl.cbStruct = sizeof (ml_ctrl); ml_ctrl.dwLineID = mixer_line.dwLineID; ml_ctrl.cControls = mixer_line.cControls; ml_ctrl.cbmxctrl = sizeof (MIXERCONTROL); ml_ctrl.pamxctrl = pamixer_ctrls; mmres = mixerGetLineControls ((HMIXEROBJ) dsoundsrc->mixer, &ml_ctrl, MIXER_GETLINECONTROLSF_ALL); /* Find control associated with volume and mute */ for (k = 0; k < mixer_line.cControls; k++) { if (strstr (pamixer_ctrls[k].szName, "Volume") != NULL) { dsoundsrc->control_id_volume = pamixer_ctrls[k].dwControlID; dsoundsrc->dw_vol_max = pamixer_ctrls[k].Bounds.dwMaximum; dsoundsrc->dw_vol_min = pamixer_ctrls[k].Bounds.dwMinimum; } else if (strstr (pamixer_ctrls[k].szName, "Mute") != NULL) { dsoundsrc->control_id_mute = pamixer_ctrls[k].dwControlID; } else { GST_DEBUG ("Control not handled: %s", pamixer_ctrls[k].szName); } } g_free (pamixer_ctrls); if (dsoundsrc->control_id_volume < 0 && dsoundsrc->control_id_mute < 0) goto mixer_init_fail; /* Save cChannels information to properly changes in volume */ dsoundsrc->mixerline_cchannels = mixer_line.cChannels; return; mixer_init_fail: GST_WARNING ("Failed to get Volume and Mute controls"); if (dsoundsrc->mixer != NULL) { mixerClose (dsoundsrc->mixer); dsoundsrc->mixer = NULL; } } static gdouble gst_directsound_src_get_volume (GstDirectSoundSrc * dsoundsrc) { return (gdouble) dsoundsrc->volume / 100; } static gboolean gst_directsound_src_get_mute (GstDirectSoundSrc * dsoundsrc) { return dsoundsrc->mute; } static void gst_directsound_src_set_volume (GstDirectSoundSrc * dsoundsrc, gdouble volume) { MMRESULT mmres; MIXERCONTROLDETAILS details; MIXERCONTROLDETAILS_UNSIGNED details_unsigned; glong dwvolume; if (dsoundsrc->mixer == NULL || dsoundsrc->control_id_volume < 0) { GST_WARNING ("mixer not initialized"); return; } dwvolume = volume * dsoundsrc->dw_vol_max; dwvolume = CLAMP (dwvolume, dsoundsrc->dw_vol_min, dsoundsrc->dw_vol_max); GST_DEBUG ("max volume %ld | min volume %ld", dsoundsrc->dw_vol_max, dsoundsrc->dw_vol_min); GST_DEBUG ("set volume to %f (%ld)", volume, dwvolume); details.cbStruct = sizeof (details); details.dwControlID = dsoundsrc->control_id_volume; details.cChannels = dsoundsrc->mixerline_cchannels; details.cMultipleItems = 0; details_unsigned.dwValue = dwvolume; details.cbDetails = sizeof (MIXERCONTROLDETAILS_UNSIGNED); details.paDetails = &details_unsigned; mmres = mixerSetControlDetails ((HMIXEROBJ) dsoundsrc->mixer, &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE); if (mmres != MMSYSERR_NOERROR) GST_WARNING ("Failed to set volume"); else dsoundsrc->volume = volume * 100; } static void gst_directsound_src_set_mute (GstDirectSoundSrc * dsoundsrc, gboolean mute) { MMRESULT mmres; MIXERCONTROLDETAILS details; MIXERCONTROLDETAILS_BOOLEAN details_boolean; if (dsoundsrc->mixer == NULL || dsoundsrc->control_id_mute < 0) { GST_WARNING ("mixer not initialized"); return; } details.cbStruct = sizeof (details); details.dwControlID = dsoundsrc->control_id_mute; details.cChannels = dsoundsrc->mixerline_cchannels; details.cMultipleItems = 0; details_boolean.fValue = mute; details.cbDetails = sizeof (MIXERCONTROLDETAILS_BOOLEAN); details.paDetails = &details_boolean; mmres = mixerSetControlDetails ((HMIXEROBJ) dsoundsrc->mixer, &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE); if (mmres != MMSYSERR_NOERROR) GST_WARNING ("Failed to set mute"); else dsoundsrc->mute = mute; } static const gchar * gst_directsound_src_get_device (GstDirectSoundSrc * dsoundsrc) { return dsoundsrc->device_id; } static void gst_directsound_src_set_device (GstDirectSoundSrc * dsoundsrc, const gchar * device_id) { g_free (dsoundsrc->device_id); dsoundsrc->device_id = g_strdup (device_id); }