gstreamer/sys/directsound/gstdirectsoundsink.c
Nirbheek Chauhan af1d49d039 directsound: port away from old DirectX API
D3DX has been deprecated for the last 4 years and latest versions of
Windows no longer ship headers for it. This is fine as long as you're
building with Cerbero's Wine-based DirectX headers, but sucks if you
want to build against the actual Windows SDK.

We were just using it to get error strings anyway, so just use the
generic error string API.
2016-08-18 20:20:15 +01:00

1011 lines
31 KiB
C

/* GStreamer
* Copyright (C) 2005 Sebastien Moutte <sebastien@moutte.net>
* Copyright (C) 2007 Pioneers of the Inevitable <songbird@songbirdnest.com>
* Copyright (C) 2010 Fluendo S.A. <support@fluendo.com>
*
* gstdirectsoundsink.c:
*
* 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.
*
*
* The development of this code was made possible due to the involvement
* of Pioneers of the Inevitable, the creators of the Songbird Music player
*
*/
/**
* SECTION:element-directsoundsink
*
* This element lets you output sound using the DirectSound API.
*
* Note that you should almost always use generic audio conversion elements
* like audioconvert and audioresample in front of an audiosink to make sure
* your pipeline works under all circumstances (those conversion elements will
* act in passthrough-mode if no conversion is necessary).
*
* <refsect2>
* <title>Example pipelines</title>
* |[
* gst-launch-1.0 -v audiotestsrc ! audioconvert ! volume volume=0.1 ! directsoundsink
* ]| will output a sine wave (continuous beep sound) to your sound card (with
* a very low volume as precaution).
* |[
* gst-launch-1.0 -v filesrc location=music.ogg ! decodebin ! audioconvert ! audioresample ! directsoundsink
* ]| will play an Ogg/Vorbis audio file and output it.
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/base/gstbasesink.h>
#include "gstdirectsoundsink.h"
#include <gst/audio/gstaudioiec61937.h>
#include <math.h>
#ifdef __CYGWIN__
#include <unistd.h>
#ifndef _swab
#define _swab swab
#endif
#endif
#define DEFAULT_MUTE FALSE
GST_DEBUG_CATEGORY_STATIC (directsoundsink_debug);
#define GST_CAT_DEFAULT directsoundsink_debug
static void gst_directsound_sink_finalize (GObject * object);
static void gst_directsound_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_directsound_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstCaps *gst_directsound_sink_getcaps (GstBaseSink * bsink,
GstCaps * filter);
static GstBuffer *gst_directsound_sink_payload (GstAudioBaseSink * sink,
GstBuffer * buf);
static gboolean gst_directsound_sink_prepare (GstAudioSink * asink,
GstAudioRingBufferSpec * spec);
static gboolean gst_directsound_sink_unprepare (GstAudioSink * asink);
static gboolean gst_directsound_sink_open (GstAudioSink * asink);
static gboolean gst_directsound_sink_close (GstAudioSink * asink);
static gint gst_directsound_sink_write (GstAudioSink * asink,
gpointer data, guint length);
static guint gst_directsound_sink_delay (GstAudioSink * asink);
static void gst_directsound_sink_reset (GstAudioSink * asink);
static GstCaps *gst_directsound_probe_supported_formats (GstDirectSoundSink *
dsoundsink, const GstCaps * template_caps);
static gboolean gst_directsound_sink_query (GstBaseSink * pad,
GstQuery * query);
static void gst_directsound_sink_set_volume (GstDirectSoundSink * sink,
gdouble volume, gboolean store);
static gdouble gst_directsound_sink_get_volume (GstDirectSoundSink * sink);
static void gst_directsound_sink_set_mute (GstDirectSoundSink * sink,
gboolean mute);
static gboolean gst_directsound_sink_get_mute (GstDirectSoundSink * sink);
static const gchar *gst_directsound_sink_get_device (GstDirectSoundSink *
dsoundsink);
static void gst_directsound_sink_set_device (GstDirectSoundSink * dsoundsink,
const gchar * device_id);
static gboolean gst_directsound_sink_is_spdif_format (GstAudioRingBufferSpec *
spec);
static gchar *gst_hres_to_string (HRESULT hRes);
static GstStaticPadTemplate directsoundsink_sink_factory =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw, "
"format = (string) S16LE, "
"layout = (string) interleaved, "
"rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]; "
"audio/x-raw, "
"format = (string) U8, "
"layout = (string) interleaved, "
"rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ];"
"audio/x-ac3, framed = (boolean) true;"
"audio/x-dts, framed = (boolean) true;"));
enum
{
PROP_0,
PROP_VOLUME,
PROP_MUTE,
PROP_DEVICE
};
#define gst_directsound_sink_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstDirectSoundSink, gst_directsound_sink,
GST_TYPE_AUDIO_SINK, G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL)
);
static void
gst_directsound_sink_finalize (GObject * object)
{
GstDirectSoundSink *dsoundsink = GST_DIRECTSOUND_SINK (object);
g_free (dsoundsink->device_id);
dsoundsink->device_id = NULL;
g_mutex_clear (&dsoundsink->dsound_lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_directsound_sink_class_init (GstDirectSoundSinkClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
GstAudioSinkClass *gstaudiosink_class = GST_AUDIO_SINK_CLASS (klass);
GstAudioBaseSinkClass *gstaudiobasesink_class =
GST_AUDIO_BASE_SINK_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (directsoundsink_debug, "directsoundsink", 0,
"DirectSound sink");
gobject_class->finalize = gst_directsound_sink_finalize;
gobject_class->set_property = gst_directsound_sink_set_property;
gobject_class->get_property = gst_directsound_sink_get_property;
gstbasesink_class->get_caps =
GST_DEBUG_FUNCPTR (gst_directsound_sink_getcaps);
gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_directsound_sink_query);
gstaudiobasesink_class->payload =
GST_DEBUG_FUNCPTR (gst_directsound_sink_payload);
gstaudiosink_class->prepare =
GST_DEBUG_FUNCPTR (gst_directsound_sink_prepare);
gstaudiosink_class->unprepare =
GST_DEBUG_FUNCPTR (gst_directsound_sink_unprepare);
gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_directsound_sink_open);
gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_directsound_sink_close);
gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_directsound_sink_write);
gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_directsound_sink_delay);
gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_directsound_sink_reset);
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));
g_object_class_install_property (gobject_class,
PROP_DEVICE,
g_param_spec_string ("device", "Device",
"DirectSound playback device as a GUID string",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gst_element_class_set_static_metadata (element_class,
"Direct Sound Audio Sink", "Sink/Audio",
"Output to a sound card via Direct Sound",
"Sebastien Moutte <sebastien@moutte.net>");
gst_element_class_add_static_pad_template (element_class,
&directsoundsink_sink_factory);
}
static void
gst_directsound_sink_init (GstDirectSoundSink * dsoundsink)
{
dsoundsink->volume = 100;
dsoundsink->mute = FALSE;
dsoundsink->device_id = NULL;
dsoundsink->pDS = NULL;
dsoundsink->cached_caps = NULL;
dsoundsink->pDSBSecondary = NULL;
dsoundsink->current_circular_offset = 0;
dsoundsink->buffer_size = DSBSIZE_MIN;
dsoundsink->volume = 100;
g_mutex_init (&dsoundsink->dsound_lock);
dsoundsink->first_buffer_after_reset = FALSE;
}
static void
gst_directsound_sink_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstDirectSoundSink *sink = GST_DIRECTSOUND_SINK (object);
switch (prop_id) {
case PROP_VOLUME:
gst_directsound_sink_set_volume (sink, g_value_get_double (value), TRUE);
break;
case PROP_MUTE:
gst_directsound_sink_set_mute (sink, g_value_get_boolean (value));
break;
case PROP_DEVICE:
gst_directsound_sink_set_device (sink, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_directsound_sink_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstDirectSoundSink *sink = GST_DIRECTSOUND_SINK (object);
switch (prop_id) {
case PROP_VOLUME:
g_value_set_double (value, gst_directsound_sink_get_volume (sink));
break;
case PROP_MUTE:
g_value_set_boolean (value, gst_directsound_sink_get_mute (sink));
break;
case PROP_DEVICE:
g_value_set_string (value, gst_directsound_sink_get_device (sink));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstCaps *
gst_directsound_sink_getcaps (GstBaseSink * bsink, GstCaps * filter)
{
GstElementClass *element_class;
GstPadTemplate *pad_template;
GstDirectSoundSink *dsoundsink = GST_DIRECTSOUND_SINK (bsink);
GstCaps *caps;
if (dsoundsink->pDS == NULL) {
GST_DEBUG_OBJECT (dsoundsink, "device not open, using template caps");
return NULL; /* base class will get template caps for us */
}
if (dsoundsink->cached_caps) {
caps = gst_caps_ref (dsoundsink->cached_caps);
} else {
element_class = GST_ELEMENT_GET_CLASS (dsoundsink);
pad_template = gst_element_class_get_pad_template (element_class, "sink");
g_return_val_if_fail (pad_template != NULL, NULL);
caps = gst_directsound_probe_supported_formats (dsoundsink,
gst_pad_template_get_caps (pad_template));
if (caps)
dsoundsink->cached_caps = gst_caps_ref (caps);
}
if (caps && filter) {
GstCaps *tmp =
gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = tmp;
}
if (caps) {
gchar *caps_string = gst_caps_to_string (caps);
GST_DEBUG_OBJECT (dsoundsink, "returning caps %s", caps_string);
g_free (caps_string);
}
return caps;
}
static gboolean
gst_directsound_sink_acceptcaps (GstBaseSink * sink, GstQuery * query)
{
GstDirectSoundSink *dsink = GST_DIRECTSOUND_SINK (sink);
GstPad *pad;
GstCaps *caps;
GstCaps *pad_caps;
GstStructure *st;
gboolean ret = FALSE;
GstAudioRingBufferSpec spec = { 0 };
if (G_UNLIKELY (dsink == NULL))
return FALSE;
pad = sink->sinkpad;
gst_query_parse_accept_caps (query, &caps);
GST_DEBUG_OBJECT (pad, "caps %" GST_PTR_FORMAT, caps);
pad_caps = gst_pad_query_caps (pad, NULL);
if (pad_caps) {
gboolean cret = gst_caps_is_subset (caps, pad_caps);
gst_caps_unref (pad_caps);
if (!cret) {
GST_DEBUG_OBJECT (dsink,
"Caps are not a subset of the pad caps, not accepting caps");
goto done;
}
}
/* If we've not got fixed caps, creating a stream might fail, so let's just
* return from here with default acceptcaps behaviour */
if (!gst_caps_is_fixed (caps)) {
GST_DEBUG_OBJECT (dsink, "Caps are not fixed, not accepting caps");
goto done;
}
spec.latency_time = GST_SECOND;
if (!gst_audio_ring_buffer_parse_caps (&spec, caps)) {
GST_DEBUG_OBJECT (dsink, "Failed to parse caps, not accepting");
goto done;
}
/* Make sure input is framed (one frame per buffer) and can be payloaded */
switch (spec.type) {
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3:
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS:
{
gboolean framed = FALSE, parsed = FALSE;
st = gst_caps_get_structure (caps, 0);
gst_structure_get_boolean (st, "framed", &framed);
gst_structure_get_boolean (st, "parsed", &parsed);
if ((!framed && !parsed) || gst_audio_iec61937_frame_size (&spec) <= 0) {
GST_DEBUG_OBJECT (dsink, "Wrong AC3/DTS caps, not accepting");
goto done;
}
}
default:
break;
}
ret = TRUE;
GST_DEBUG_OBJECT (dsink, "Accepting caps");
done:
gst_query_set_accept_caps_result (query, ret);
return TRUE;
}
static gboolean
gst_directsound_sink_query (GstBaseSink * sink, GstQuery * query)
{
gboolean res = TRUE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_ACCEPT_CAPS:
res = gst_directsound_sink_acceptcaps (sink, query);
break;
default:
res = GST_BASE_SINK_CLASS (parent_class)->query (sink, query);
}
return res;
}
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_sink_open (GstAudioSink * asink)
{
GstDirectSoundSink *dsoundsink;
HRESULT hRes;
LPGUID lpGuid = NULL;
dsoundsink = GST_DIRECTSOUND_SINK (asink);
if (dsoundsink->device_id) {
lpGuid = string_to_guid (dsoundsink->device_id);
if (lpGuid == NULL) {
GST_ELEMENT_ERROR (dsoundsink, RESOURCE, OPEN_READ,
("gst_directsound_sink_open: device set, but guid not found: %s",
dsoundsink->device_id), (NULL));
return FALSE;
}
}
/* create and initialize a DirecSound object */
if (FAILED (hRes = DirectSoundCreate (lpGuid, &dsoundsink->pDS, NULL))) {
gchar *error_text = gst_hres_to_string (hRes);
GST_ELEMENT_ERROR (dsoundsink, RESOURCE, OPEN_READ,
("gst_directsound_sink_open: DirectSoundCreate: %s",
error_text), (NULL));
g_free (lpGuid);
g_free (error_text);
return FALSE;
}
g_free (lpGuid);
if (FAILED (hRes = IDirectSound_SetCooperativeLevel (dsoundsink->pDS,
GetDesktopWindow (), DSSCL_PRIORITY))) {
gchar *error_text = gst_hres_to_string (hRes);
GST_ELEMENT_ERROR (dsoundsink, RESOURCE, OPEN_READ,
("gst_directsound_sink_open: IDirectSound_SetCooperativeLevel: %s",
error_text), (NULL));
g_free (error_text);
return FALSE;
}
return TRUE;
}
static gboolean
gst_directsound_sink_is_spdif_format (GstAudioRingBufferSpec * spec)
{
return spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3 ||
spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS;
}
static gboolean
gst_directsound_sink_prepare (GstAudioSink * asink,
GstAudioRingBufferSpec * spec)
{
GstDirectSoundSink *dsoundsink;
HRESULT hRes;
DSBUFFERDESC descSecondary;
WAVEFORMATEX wfx;
dsoundsink = GST_DIRECTSOUND_SINK (asink);
/*save number of bytes per sample and buffer format */
dsoundsink->bytes_per_sample = spec->info.bpf;
dsoundsink->type = spec->type;
/* fill the WAVEFORMATEX structure with spec params */
memset (&wfx, 0, sizeof (wfx));
if (!gst_directsound_sink_is_spdif_format (spec)) {
wfx.cbSize = sizeof (wfx);
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = spec->info.channels;
wfx.nSamplesPerSec = spec->info.rate;
wfx.wBitsPerSample = (spec->info.bpf * 8) / wfx.nChannels;
wfx.nBlockAlign = spec->info.bpf;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
/* Create directsound buffer with size based on our configured
* buffer_size (which is 200 ms by default) */
dsoundsink->buffer_size =
gst_util_uint64_scale_int (wfx.nAvgBytesPerSec, spec->buffer_time,
GST_MSECOND);
/* Make sure we make those numbers multiple of our sample size in bytes */
dsoundsink->buffer_size -= dsoundsink->buffer_size % spec->info.bpf;
spec->segsize =
gst_util_uint64_scale_int (wfx.nAvgBytesPerSec, spec->latency_time,
GST_MSECOND);
spec->segsize -= spec->segsize % spec->info.bpf;
spec->segtotal = dsoundsink->buffer_size / spec->segsize;
} else {
#ifdef WAVE_FORMAT_DOLBY_AC3_SPDIF
wfx.cbSize = 0;
wfx.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 48000;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
spec->segsize = 6144;
spec->segtotal = 10;
#else
g_assert_not_reached ();
#endif
}
// Make the final buffer size be an integer number of segments
dsoundsink->buffer_size = spec->segsize * spec->segtotal;
GST_INFO_OBJECT (dsoundsink,
"GstAudioRingBufferSpec->channels: %d, GstAudioRingBufferSpec->rate: %d, GstAudioRingBufferSpec->bytes_per_sample: %d\n"
"WAVEFORMATEX.nSamplesPerSec: %ld, WAVEFORMATEX.wBitsPerSample: %d, WAVEFORMATEX.nBlockAlign: %d, WAVEFORMATEX.nAvgBytesPerSec: %ld\n"
"Size of dsound circular buffer=>%d\n", spec->info.channels,
spec->info.rate, spec->info.bpf, wfx.nSamplesPerSec, wfx.wBitsPerSample,
wfx.nBlockAlign, wfx.nAvgBytesPerSec, dsoundsink->buffer_size);
/* create a secondary directsound buffer */
memset (&descSecondary, 0, sizeof (DSBUFFERDESC));
descSecondary.dwSize = sizeof (DSBUFFERDESC);
descSecondary.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
if (!gst_directsound_sink_is_spdif_format (spec))
descSecondary.dwFlags |= DSBCAPS_CTRLVOLUME;
descSecondary.dwBufferBytes = dsoundsink->buffer_size;
descSecondary.lpwfxFormat = (WAVEFORMATEX *) & wfx;
hRes = IDirectSound_CreateSoundBuffer (dsoundsink->pDS, &descSecondary,
&dsoundsink->pDSBSecondary, NULL);
if (FAILED (hRes)) {
gchar *error_text = gst_hres_to_string (hRes);
GST_ELEMENT_ERROR (dsoundsink, RESOURCE, OPEN_READ,
("gst_directsound_sink_prepare: IDirectSound_CreateSoundBuffer: %s",
error_text), (NULL));
g_free (error_text);
return FALSE;
}
gst_directsound_sink_set_volume (dsoundsink, dsoundsink->volume, FALSE);
gst_directsound_sink_set_mute (dsoundsink, dsoundsink->mute);
return TRUE;
}
static gboolean
gst_directsound_sink_unprepare (GstAudioSink * asink)
{
GstDirectSoundSink *dsoundsink;
dsoundsink = GST_DIRECTSOUND_SINK (asink);
/* release secondary DirectSound buffer */
if (dsoundsink->pDSBSecondary) {
IDirectSoundBuffer_Release (dsoundsink->pDSBSecondary);
dsoundsink->pDSBSecondary = NULL;
}
return TRUE;
}
static gboolean
gst_directsound_sink_close (GstAudioSink * asink)
{
GstDirectSoundSink *dsoundsink = NULL;
dsoundsink = GST_DIRECTSOUND_SINK (asink);
/* release DirectSound object */
g_return_val_if_fail (dsoundsink->pDS != NULL, FALSE);
IDirectSound_Release (dsoundsink->pDS);
dsoundsink->pDS = NULL;
gst_caps_replace (&dsoundsink->cached_caps, NULL);
return TRUE;
}
static gint
gst_directsound_sink_write (GstAudioSink * asink, gpointer data, guint length)
{
GstDirectSoundSink *dsoundsink;
DWORD dwStatus = 0;
HRESULT hRes, hRes2;
LPVOID pLockedBuffer1 = NULL, pLockedBuffer2 = NULL;
DWORD dwSizeBuffer1, dwSizeBuffer2;
DWORD dwCurrentPlayCursor;
dsoundsink = GST_DIRECTSOUND_SINK (asink);
GST_DSOUND_LOCK (dsoundsink);
/* get current buffer status */
hRes = IDirectSoundBuffer_GetStatus (dsoundsink->pDSBSecondary, &dwStatus);
/* get current play cursor position */
hRes2 = IDirectSoundBuffer_GetCurrentPosition (dsoundsink->pDSBSecondary,
&dwCurrentPlayCursor, NULL);
if (SUCCEEDED (hRes) && SUCCEEDED (hRes2) && (dwStatus & DSBSTATUS_PLAYING)) {
DWORD dwFreeBufferSize = 0;
DWORD sleepTime = 0;
calculate_freesize:
/* calculate the free size of the circular buffer */
if (dwCurrentPlayCursor < dsoundsink->current_circular_offset)
dwFreeBufferSize =
dsoundsink->buffer_size - (dsoundsink->current_circular_offset -
dwCurrentPlayCursor);
else
dwFreeBufferSize =
dwCurrentPlayCursor - dsoundsink->current_circular_offset;
if (length >= dwFreeBufferSize) {
sleepTime =
((length -
dwFreeBufferSize) * 1000) / (dsoundsink->bytes_per_sample *
GST_AUDIO_BASE_SINK (asink)->ringbuffer->spec.info.rate);
if (sleepTime > 0) {
GST_DEBUG_OBJECT (dsoundsink,
"gst_directsound_sink_write: length:%i, FreeBufSiz: %ld, sleepTime: %ld, bps: %i, rate: %i",
length, dwFreeBufferSize, sleepTime, dsoundsink->bytes_per_sample,
GST_AUDIO_BASE_SINK (asink)->ringbuffer->spec.info.rate);
Sleep (sleepTime);
}
hRes = IDirectSoundBuffer_GetCurrentPosition (dsoundsink->pDSBSecondary,
&dwCurrentPlayCursor, NULL);
hRes2 =
IDirectSoundBuffer_GetStatus (dsoundsink->pDSBSecondary, &dwStatus);
if (SUCCEEDED (hRes) && SUCCEEDED (hRes2)
&& (dwStatus & DSBSTATUS_PLAYING))
goto calculate_freesize;
else {
gchar *err1, *err2;
dsoundsink->first_buffer_after_reset = FALSE;
GST_DSOUND_UNLOCK (dsoundsink);
err1 = gst_hres_to_string (hRes);
err2 = gst_hres_to_string (hRes2);
GST_ELEMENT_ERROR (dsoundsink, RESOURCE, OPEN_WRITE,
("gst_directsound_sink_write: IDirectSoundBuffer_GetStatus %s, "
"IDirectSoundBuffer_GetCurrentPosition: %s, dwStatus: %lu",
err2, err1, dwStatus), (NULL));
g_free (err1);
g_free (err2);
return -1;
}
}
}
if (dwStatus & DSBSTATUS_BUFFERLOST) {
hRes = IDirectSoundBuffer_Restore (dsoundsink->pDSBSecondary); /*need a loop waiting the buffer is restored?? */
dsoundsink->current_circular_offset = 0;
}
hRes = IDirectSoundBuffer_Lock (dsoundsink->pDSBSecondary,
dsoundsink->current_circular_offset, length, &pLockedBuffer1,
&dwSizeBuffer1, &pLockedBuffer2, &dwSizeBuffer2, 0L);
if (SUCCEEDED (hRes)) {
// Write to pointers without reordering.
memcpy (pLockedBuffer1, data, dwSizeBuffer1);
if (pLockedBuffer2 != NULL)
memcpy (pLockedBuffer2, (LPBYTE) data + dwSizeBuffer1, dwSizeBuffer2);
// Update where the buffer will lock (for next time)
dsoundsink->current_circular_offset += dwSizeBuffer1 + dwSizeBuffer2;
dsoundsink->current_circular_offset %= dsoundsink->buffer_size; /* Circular buffer */
hRes = IDirectSoundBuffer_Unlock (dsoundsink->pDSBSecondary, pLockedBuffer1,
dwSizeBuffer1, pLockedBuffer2, dwSizeBuffer2);
}
/* if the buffer was not in playing state yet, call play on the buffer
except if this buffer is the fist after a reset (base class call reset and write a buffer when setting the sink to pause) */
if (!(dwStatus & DSBSTATUS_PLAYING) &&
dsoundsink->first_buffer_after_reset == FALSE) {
hRes = IDirectSoundBuffer_Play (dsoundsink->pDSBSecondary, 0, 0,
DSBPLAY_LOOPING);
}
dsoundsink->first_buffer_after_reset = FALSE;
GST_DSOUND_UNLOCK (dsoundsink);
return length;
}
static guint
gst_directsound_sink_delay (GstAudioSink * asink)
{
GstDirectSoundSink *dsoundsink;
HRESULT hRes;
DWORD dwCurrentPlayCursor;
DWORD dwBytesInQueue = 0;
gint nNbSamplesInQueue = 0;
DWORD dwStatus;
dsoundsink = GST_DIRECTSOUND_SINK (asink);
/* get current buffer status */
hRes = IDirectSoundBuffer_GetStatus (dsoundsink->pDSBSecondary, &dwStatus);
if (SUCCEEDED (hRes) && (dwStatus & DSBSTATUS_PLAYING)) {
/*evaluate the number of samples in queue in the circular buffer */
hRes = IDirectSoundBuffer_GetCurrentPosition (dsoundsink->pDSBSecondary,
&dwCurrentPlayCursor, NULL);
if (hRes == S_OK) {
if (dwCurrentPlayCursor < dsoundsink->current_circular_offset)
dwBytesInQueue =
dsoundsink->current_circular_offset - dwCurrentPlayCursor;
else
dwBytesInQueue =
dsoundsink->current_circular_offset + (dsoundsink->buffer_size -
dwCurrentPlayCursor);
nNbSamplesInQueue = dwBytesInQueue / dsoundsink->bytes_per_sample;
}
}
return nNbSamplesInQueue;
}
static void
gst_directsound_sink_reset (GstAudioSink * asink)
{
GstDirectSoundSink *dsoundsink;
LPVOID pLockedBuffer = NULL;
DWORD dwSizeBuffer = 0;
dsoundsink = GST_DIRECTSOUND_SINK (asink);
GST_DSOUND_LOCK (dsoundsink);
if (dsoundsink->pDSBSecondary) {
/*stop playing */
HRESULT hRes = IDirectSoundBuffer_Stop (dsoundsink->pDSBSecondary);
/*reset position */
hRes = IDirectSoundBuffer_SetCurrentPosition (dsoundsink->pDSBSecondary, 0);
dsoundsink->current_circular_offset = 0;
/*reset the buffer */
hRes = IDirectSoundBuffer_Lock (dsoundsink->pDSBSecondary,
dsoundsink->current_circular_offset, dsoundsink->buffer_size,
&pLockedBuffer, &dwSizeBuffer, NULL, NULL, 0L);
if (SUCCEEDED (hRes)) {
memset (pLockedBuffer, 0, dwSizeBuffer);
hRes =
IDirectSoundBuffer_Unlock (dsoundsink->pDSBSecondary, pLockedBuffer,
dwSizeBuffer, NULL, 0);
}
}
dsoundsink->first_buffer_after_reset = TRUE;
GST_DSOUND_UNLOCK (dsoundsink);
}
/*
* gst_directsound_probe_supported_formats:
*
* Takes the template caps and returns the subset which is actually
* supported by this device.
*
*/
static GstCaps *
gst_directsound_probe_supported_formats (GstDirectSoundSink * dsoundsink,
const GstCaps * template_caps)
{
HRESULT hRes;
DSBUFFERDESC descSecondary;
WAVEFORMATEX wfx;
GstCaps *caps;
GstCaps *tmp, *tmp2;
LPDIRECTSOUNDBUFFER tmpBuffer;
caps = gst_caps_copy (template_caps);
/*
* Check availability of digital output by trying to create an SPDIF buffer
*/
#ifdef WAVE_FORMAT_DOLBY_AC3_SPDIF
/* fill the WAVEFORMATEX structure with some standard AC3 over SPDIF params */
memset (&wfx, 0, sizeof (wfx));
wfx.cbSize = 0;
wfx.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 48000;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = 4;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
// create a secondary directsound buffer
memset (&descSecondary, 0, sizeof (DSBUFFERDESC));
descSecondary.dwSize = sizeof (DSBUFFERDESC);
descSecondary.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
descSecondary.dwBufferBytes = 6144;
descSecondary.lpwfxFormat = &wfx;
hRes = IDirectSound_CreateSoundBuffer (dsoundsink->pDS, &descSecondary,
&tmpBuffer, NULL);
if (FAILED (hRes)) {
gchar *error_text = gst_hres_to_string (hRes);
GST_INFO_OBJECT (dsoundsink, "AC3 passthrough not supported "
"(IDirectSound_CreateSoundBuffer returned: %s)\n", error_text);
g_free (error_text);
tmp = gst_caps_new_empty_simple ("audio/x-ac3");
tmp2 = gst_caps_subtract (caps, tmp);
gst_caps_unref (tmp);
gst_caps_unref (caps);
caps = tmp2;
tmp = gst_caps_new_empty_simple ("audio/x-dts");
tmp2 = gst_caps_subtract (caps, tmp);
gst_caps_unref (tmp);
gst_caps_unref (caps);
caps = tmp2;
} else {
GST_INFO_OBJECT (dsoundsink, "AC3 passthrough supported");
hRes = IDirectSoundBuffer_Release (tmpBuffer);
if (FAILED (hRes)) {
gchar *error_text = gst_hres_to_string (hRes);
GST_DEBUG_OBJECT (dsoundsink,
"(IDirectSoundBuffer_Release returned: %s)\n", error_text);
g_free (error_text);
}
}
#else
tmp = gst_caps_new_empty_simple ("audio/x-ac3");
tmp2 = gst_caps_subtract (caps, tmp);
gst_caps_unref (tmp);
gst_caps_unref (caps);
caps = tmp2;
tmp = gst_caps_new_empty_simple ("audio/x-dts");
tmp2 = gst_caps_subtract (caps, tmp);
gst_caps_unref (tmp);
gst_caps_unref (caps);
caps = tmp2;
#endif
return caps;
}
static GstBuffer *
gst_directsound_sink_payload (GstAudioBaseSink * sink, GstBuffer * buf)
{
if (gst_directsound_sink_is_spdif_format (&sink->ringbuffer->spec)) {
gint framesize = gst_audio_iec61937_frame_size (&sink->ringbuffer->spec);
GstBuffer *out;
GstMapInfo infobuf, infoout;
gboolean success;
if (framesize <= 0)
return NULL;
out = gst_buffer_new_and_alloc (framesize);
if (!gst_buffer_map (buf, &infobuf, GST_MAP_READWRITE)) {
gst_buffer_unref (out);
return NULL;
}
if (!gst_buffer_map (out, &infoout, GST_MAP_READWRITE)) {
gst_buffer_unmap (buf, &infobuf);
gst_buffer_unref (out);
return NULL;
}
success = gst_audio_iec61937_payload (infobuf.data, infobuf.size,
infoout.data, infoout.size, &sink->ringbuffer->spec, G_BYTE_ORDER);
if (!success) {
gst_buffer_unmap (out, &infoout);
gst_buffer_unmap (buf, &infobuf);
gst_buffer_unref (out);
return NULL;
}
gst_buffer_copy_into (out, buf, GST_BUFFER_COPY_ALL, 0, -1);
/* Fix endianness */
_swab ((gchar *) infoout.data, (gchar *) infoout.data, infobuf.size);
gst_buffer_unmap (out, &infoout);
gst_buffer_unmap (buf, &infobuf);
return out;
} else
return gst_buffer_ref (buf);
}
static void
gst_directsound_sink_set_volume (GstDirectSoundSink * dsoundsink,
gdouble dvolume, gboolean store)
{
glong volume;
volume = dvolume * 100;
if (store)
dsoundsink->volume = volume;
if (dsoundsink->pDSBSecondary) {
/* DirectSound controls volume using units of 100th of a decibel,
* ranging from -10000 to 0. We use a linear scale of 0 - 100
* here, so remap.
*/
long dsVolume;
if (volume == 0 || dsoundsink->mute)
dsVolume = -10000;
else
dsVolume = 100 * (long) (20 * log10 ((double) volume / 100.));
dsVolume = CLAMP (dsVolume, -10000, 0);
GST_DEBUG_OBJECT (dsoundsink,
"Setting volume on secondary buffer to %d from %d", (int) dsVolume,
(int) volume);
IDirectSoundBuffer_SetVolume (dsoundsink->pDSBSecondary, dsVolume);
}
}
gdouble
gst_directsound_sink_get_volume (GstDirectSoundSink * dsoundsink)
{
return (gdouble) dsoundsink->volume / 100;
}
static void
gst_directsound_sink_set_mute (GstDirectSoundSink * dsoundsink, gboolean mute)
{
if (mute) {
gst_directsound_sink_set_volume (dsoundsink, 0, FALSE);
dsoundsink->mute = TRUE;
} else {
gst_directsound_sink_set_volume (dsoundsink,
gst_directsound_sink_get_volume (dsoundsink), FALSE);
dsoundsink->mute = FALSE;
}
}
static gboolean
gst_directsound_sink_get_mute (GstDirectSoundSink * dsoundsink)
{
return dsoundsink->mute;
}
static const gchar *
gst_directsound_sink_get_device (GstDirectSoundSink * dsoundsink)
{
return dsoundsink->device_id;
}
static void
gst_directsound_sink_set_device (GstDirectSoundSink * dsoundsink,
const gchar * device_id)
{
g_free (dsoundsink->device_id);
dsoundsink->device_id = g_strdup (device_id);
}
/* Converts a HRESULT error to a text string
* LPTSTR is either a */
static gchar *
gst_hres_to_string (HRESULT hRes)
{
DWORD flags;
gchar *ret_text;
LPTSTR error_text = NULL;
flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_IGNORE_INSERTS;
FormatMessage (flags, NULL, hRes, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) & error_text, 0, NULL);
#ifdef UNICODE
/* If UNICODE is defined, LPTSTR is LPWSTR which is UTF-16 */
ret_text = g_utf16_to_utf8 (error_text, 0, NULL, NULL, NULL);
#else
ret_text = g_strdup (error_text);
#endif
LocalFree (error_text);
return ret_text;
}