mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-22 16:26:39 +00:00
af1d49d039
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.
1011 lines
31 KiB
C
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;
|
|
}
|