blob: b670a5faa7680ead8e5c84373ade1284c9269b55 [file] [log] [blame]
/* GStreamer
* Copyright (C) 2005 Sebastien Moutte <sebastien@moutte.net>
*
* gstwaveformsink.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.
*/
/**
* SECTION:element-waveformsink
*
* This element lets you output sound using the Windows WaveForm 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 ! waveformsink
* ]| 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 ! waveformsink
* ]| will play an Ogg/Vorbis audio file and output it.
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstwaveformsink.h"
GST_DEBUG_CATEGORY_STATIC (waveformsink_debug);
static void gst_waveform_sink_finalise (GObject * object);
static void gst_waveform_sink_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_waveform_sink_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static GstCaps *gst_waveform_sink_getcaps (GstBaseSink * bsink,
GstCaps * filter);
/************************************************************************/
/* GstAudioSink functions */
/************************************************************************/
static gboolean gst_waveform_sink_prepare (GstAudioSink * asink,
GstAudioRingBufferSpec * spec);
static gboolean gst_waveform_sink_unprepare (GstAudioSink * asink);
static gboolean gst_waveform_sink_open (GstAudioSink * asink);
static gboolean gst_waveform_sink_close (GstAudioSink * asink);
static gint gst_waveform_sink_write (GstAudioSink * asink, gpointer data,
guint length);
static guint gst_waveform_sink_delay (GstAudioSink * asink);
static void gst_waveform_sink_reset (GstAudioSink * asink);
/************************************************************************/
/* Utils */
/************************************************************************/
GstCaps *gst_waveform_sink_create_caps (gint rate, gint channels,
const gchar * format);
WAVEHDR *bufferpool_get_buffer (GstWaveFormSink * wfsink, gpointer data,
guint length);
void CALLBACK waveOutProc (HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2);
static GstStaticPadTemplate waveformsink_sink_factory =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw, "
"format = (string) { " GST_AUDIO_NE (S16) ", S8 }, "
"layout = (string) interleaved, "
"rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]"));
#define gst_waveform_sink_parent_class parent_class
G_DEFINE_TYPE (GstWaveFormSink, gst_waveform_sink, GST_TYPE_AUDIO_SINK);
static void
gst_waveform_sink_class_init (GstWaveFormSinkClass * klass)
{
GObjectClass *gobject_class;
GstBaseSinkClass *gstbasesink_class;
GstAudioSinkClass *gstaudiosink_class;
GstElementClass *element_class;
gobject_class = (GObjectClass *) klass;
gstbasesink_class = (GstBaseSinkClass *) klass;
gstaudiosink_class = (GstAudioSinkClass *) klass;
element_class = GST_ELEMENT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
gobject_class->finalize = gst_waveform_sink_finalise;
gobject_class->get_property = gst_waveform_sink_get_property;
gobject_class->set_property = gst_waveform_sink_set_property;
gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_waveform_sink_getcaps);
gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_waveform_sink_prepare);
gstaudiosink_class->unprepare =
GST_DEBUG_FUNCPTR (gst_waveform_sink_unprepare);
gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_waveform_sink_open);
gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_waveform_sink_close);
gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_waveform_sink_write);
gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_waveform_sink_delay);
gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_waveform_sink_reset);
GST_DEBUG_CATEGORY_INIT (waveformsink_debug, "waveformsink", 0,
"Waveform sink");
gst_element_class_set_static_metadata (element_class, "WaveForm Audio Sink",
"Sink/Audio",
"Output to a sound card via WaveForm API",
"Sebastien Moutte <sebastien@moutte.net>");
gst_element_class_add_static_pad_template (element_class,
&waveformsink_sink_factory);
}
static void
gst_waveform_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
/* GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (object); */
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_waveform_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
/* GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (object); */
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_waveform_sink_init (GstWaveFormSink * wfsink)
{
/* initialize members */
wfsink->hwaveout = NULL;
wfsink->cached_caps = NULL;
wfsink->wave_buffers = NULL;
wfsink->write_buffer = 0;
wfsink->buffer_count = BUFFER_COUNT;
wfsink->buffer_size = BUFFER_SIZE;
wfsink->free_buffers_count = wfsink->buffer_count;
wfsink->bytes_in_queue = 0;
InitializeCriticalSection (&wfsink->critic_wave);
}
static void
gst_waveform_sink_finalise (GObject * object)
{
GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (object);
if (wfsink->cached_caps) {
gst_caps_unref (wfsink->cached_caps);
wfsink->cached_caps = NULL;
}
DeleteCriticalSection (&wfsink->critic_wave);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static GstCaps *
gst_waveform_sink_getcaps (GstBaseSink * bsink, GstCaps * filter)
{
GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (bsink);
MMRESULT mmresult;
WAVEOUTCAPS wocaps;
GstCaps *caps, *caps_temp;
/* return the cached caps if already defined */
if (wfsink->cached_caps) {
return gst_caps_ref (wfsink->cached_caps);
}
/* get the default device caps */
mmresult = waveOutGetDevCaps (WAVE_MAPPER, &wocaps, sizeof (wocaps));
if (mmresult != MMSYSERR_NOERROR) {
waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
GST_ELEMENT_ERROR (wfsink, RESOURCE, SETTINGS,
("gst_waveform_sink_getcaps: waveOutGetDevCaps failed error=>%s",
wfsink->error_string), (NULL));
return NULL;
}
caps = gst_caps_new_empty ();
/* create a caps for all wave formats supported by the device
starting by the best quality format */
if (wocaps.dwFormats & WAVE_FORMAT_96S16) {
caps_temp = gst_waveform_sink_create_caps (96000, 2, GST_AUDIO_NE (S16));
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_96S08) {
caps_temp = gst_waveform_sink_create_caps (96000, 2, "S8");
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_96M16) {
caps_temp = gst_waveform_sink_create_caps (96000, 1, GST_AUDIO_NE (S16));
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_96M08) {
caps_temp = gst_waveform_sink_create_caps (96000, 1, "S8");
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_4S16) {
caps_temp = gst_waveform_sink_create_caps (44100, 2, GST_AUDIO_NE (S16));
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_4S08) {
caps_temp = gst_waveform_sink_create_caps (44100, 2, "S8");
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_4M16) {
caps_temp = gst_waveform_sink_create_caps (44100, 1, GST_AUDIO_NE (S16));
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_4M08) {
caps_temp = gst_waveform_sink_create_caps (44100, 1, "S8");
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_2S16) {
caps_temp = gst_waveform_sink_create_caps (22050, 2, GST_AUDIO_NE (S16));
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_2S08) {
caps_temp = gst_waveform_sink_create_caps (22050, 2, "S8");
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_2M16) {
caps_temp = gst_waveform_sink_create_caps (22050, 1, GST_AUDIO_NE (S16));
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_2M08) {
caps_temp = gst_waveform_sink_create_caps (22050, 1, "S8");
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_1S16) {
caps_temp = gst_waveform_sink_create_caps (11025, 2, GST_AUDIO_NE (S16));
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_1S08) {
caps_temp = gst_waveform_sink_create_caps (11025, 2, "S8");
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_1M16) {
caps_temp = gst_waveform_sink_create_caps (11025, 1, GST_AUDIO_NE (S16));
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (wocaps.dwFormats & WAVE_FORMAT_1M08) {
caps_temp = gst_waveform_sink_create_caps (11025, 1, "S8");
if (caps_temp) {
gst_caps_append (caps, caps_temp);
}
}
if (gst_caps_is_empty (caps)) {
gst_caps_unref (caps);
caps = NULL;
} else {
wfsink->cached_caps = gst_caps_ref (caps);
}
GST_CAT_LOG_OBJECT (waveformsink_debug, wfsink,
"Returning caps %" GST_PTR_FORMAT, caps);
return caps;
}
static gboolean
gst_waveform_sink_open (GstAudioSink * asink)
{
/* GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink); */
/* nothing to do here as the device needs to be opened with the format we will use */
return TRUE;
}
static gboolean
gst_waveform_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
{
GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
WAVEFORMATEX wfx;
MMRESULT mmresult;
guint index;
/* setup waveformex struture with the input ringbuffer specs */
memset (&wfx, 0, sizeof (wfx));
wfx.cbSize = 0;
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;
/* save bytes per sample to use it in delay */
wfsink->bytes_per_sample = spec->info.bpf;
/* open the default audio device with the given caps */
mmresult = waveOutOpen (&wfsink->hwaveout, WAVE_MAPPER,
&wfx, (DWORD_PTR) waveOutProc, (DWORD_PTR) wfsink, CALLBACK_FUNCTION);
if (mmresult != MMSYSERR_NOERROR) {
waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
GST_ELEMENT_ERROR (wfsink, RESOURCE, OPEN_WRITE,
("gst_waveform_sink_prepare: waveOutOpen failed error=>%s",
wfsink->error_string), (NULL));
return FALSE;
}
/* evaluate the buffer size and the number of buffers needed */
wfsink->free_buffers_count = wfsink->buffer_count;
/* allocate wave buffers */
wfsink->wave_buffers = (WAVEHDR *) g_new0 (WAVEHDR, wfsink->buffer_count);
if (!wfsink->wave_buffers) {
GST_ELEMENT_ERROR (wfsink, RESOURCE, OPEN_WRITE,
("gst_waveform_sink_prepare: Failed to allocate wave buffer headers (buffer count=%d)",
wfsink->buffer_count), (NULL));
return FALSE;
}
memset (wfsink->wave_buffers, 0, sizeof (WAVEHDR) * wfsink->buffer_count);
/* setup headers */
for (index = 0; index < wfsink->buffer_count; index++) {
wfsink->wave_buffers[index].dwBufferLength = wfsink->buffer_size;
wfsink->wave_buffers[index].lpData = g_new0 (gchar, wfsink->buffer_size);
}
return TRUE;
}
static gboolean
gst_waveform_sink_unprepare (GstAudioSink * asink)
{
GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
/* free wave buffers */
if (wfsink->wave_buffers) {
guint index;
for (index = 0; index < wfsink->buffer_count; index++) {
if (wfsink->wave_buffers[index].dwFlags & WHDR_PREPARED) {
MMRESULT mmresult = waveOutUnprepareHeader (wfsink->hwaveout,
&wfsink->wave_buffers[index], sizeof (WAVEHDR));
if (mmresult != MMSYSERR_NOERROR) {
waveOutGetErrorText (mmresult, wfsink->error_string,
ERROR_LENGTH - 1);
GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
"gst_waveform_sink_unprepare: Error unpreparing buffer => %s",
wfsink->error_string);
}
}
g_free (wfsink->wave_buffers[index].lpData);
}
g_free (wfsink->wave_buffers);
wfsink->wave_buffers = NULL;
}
/* close waveform-audio output device */
if (wfsink->hwaveout) {
waveOutClose (wfsink->hwaveout);
wfsink->hwaveout = NULL;
}
return TRUE;
}
static gboolean
gst_waveform_sink_close (GstAudioSink * asink)
{
/* GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink); */
return TRUE;
}
static gint
gst_waveform_sink_write (GstAudioSink * asink, gpointer data, guint length)
{
GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
WAVEHDR *waveheader;
MMRESULT mmresult;
guint bytes_to_write = length;
guint remaining_length = length;
wfsink->bytes_in_queue += length;
while (remaining_length > 0) {
if (wfsink->free_buffers_count == 0) {
/* no free buffer available, wait for one */
Sleep (10);
continue;
}
/* get the current write buffer header */
waveheader = &wfsink->wave_buffers[wfsink->write_buffer];
/* unprepare the header if needed */
if (waveheader->dwFlags & WHDR_PREPARED) {
mmresult =
waveOutUnprepareHeader (wfsink->hwaveout, waveheader,
sizeof (WAVEHDR));
if (mmresult != MMSYSERR_NOERROR) {
waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
"Error unpreparing buffer => %s", wfsink->error_string);
}
}
if (wfsink->buffer_size - waveheader->dwUser >= remaining_length)
bytes_to_write = remaining_length;
else
bytes_to_write = wfsink->buffer_size - waveheader->dwUser;
memcpy (waveheader->lpData + waveheader->dwUser, data, bytes_to_write);
waveheader->dwUser += bytes_to_write;
remaining_length -= bytes_to_write;
data = (guint8 *) data + bytes_to_write;
if (waveheader->dwUser == wfsink->buffer_size) {
/* we have filled a buffer, let's prepare it and next write it to the device */
mmresult =
waveOutPrepareHeader (wfsink->hwaveout, waveheader, sizeof (WAVEHDR));
if (mmresult != MMSYSERR_NOERROR) {
waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
"gst_waveform_sink_write: Error preparing header => %s",
wfsink->error_string);
}
mmresult = waveOutWrite (wfsink->hwaveout, waveheader, sizeof (WAVEHDR));
if (mmresult != MMSYSERR_NOERROR) {
waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
"gst_waveform_sink_write: Error writting buffer to the device => %s",
wfsink->error_string);
}
EnterCriticalSection (&wfsink->critic_wave);
wfsink->free_buffers_count--;
LeaveCriticalSection (&wfsink->critic_wave);
wfsink->write_buffer++;
wfsink->write_buffer %= wfsink->buffer_count;
waveheader->dwUser = 0;
wfsink->bytes_in_queue = 0;
GST_CAT_LOG_OBJECT (waveformsink_debug, wfsink,
"gst_waveform_sink_write: Writting a buffer to the device (free buffers remaining=%d, write buffer=%d)",
wfsink->free_buffers_count, wfsink->write_buffer);
}
}
return length;
}
static guint
gst_waveform_sink_delay (GstAudioSink * asink)
{
/* return the number of samples in queue (device+internal queue) */
GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
guint bytes_in_device =
(wfsink->buffer_count - wfsink->free_buffers_count) * wfsink->buffer_size;
guint delay =
(bytes_in_device + wfsink->bytes_in_queue) / wfsink->bytes_per_sample;
return delay;
}
static void
gst_waveform_sink_reset (GstAudioSink * asink)
{
GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
MMRESULT mmresult = waveOutReset (wfsink->hwaveout);
if (mmresult != MMSYSERR_NOERROR) {
waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
"gst_waveform_sink_reset: Error reseting waveform-audio device => %s",
wfsink->error_string);
}
}
GstCaps *
gst_waveform_sink_create_caps (gint rate, gint channels, const gchar * format)
{
GstCaps *caps = NULL;
caps = gst_caps_new_simple ("audio/x-raw",
"format", G_TYPE_STRING, format,
"layout", G_TYPE_STRING, "interleaved",
"channels", G_TYPE_INT, channels, "rate", G_TYPE_INT, rate, NULL);
return caps;
}
void CALLBACK
waveOutProc (HWAVEOUT hwo,
UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
GstWaveFormSink *wfsink = (GstWaveFormSink *) dwInstance;
if (uMsg == WOM_DONE) {
EnterCriticalSection (&wfsink->critic_wave);
wfsink->free_buffers_count++;
LeaveCriticalSection (&wfsink->critic_wave);
}
}