#define RECORDER_QUEUE_SIZE 2
-/* Some generic helper functions */
+/*
+ * Some generic helper functions
+ */
static inline SLuint32
_opensles_sample_rate (guint rate)
static inline SLuint32
_opensles_channel_mask (GstRingBufferSpec * spec)
{
- /* FIXME: handle more than two channels */
switch (spec->channels) {
case 1:
return (SL_SPEAKER_FRONT_CENTER);
(spec->bigend ? SL_BYTEORDER_BIGENDIAN : SL_BYTEORDER_LITTLEENDIAN);
}
-/* Recorder related functions */
+/*
+ * Recorder related functions
+ */
static gboolean
_opensles_recorder_acquire (GstRingBuffer * rb, GstRingBufferSpec * spec)
};
SLDataSink audioSink = { &loc_bq, &format };
+ /* Required optional interfaces */
const SLInterfaceID id[1] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
const SLboolean req[1] = { SL_BOOLEAN_TRUE };
- /* Define the format in OpenSL ES terms */
+ /* Define the audio format in OpenSL ES terminology */
_opensles_format (spec, &format);
- /* Create audio recorder (requires the RECORD_AUDIO permission) */
+ /* Create the audio recorder object (requires the RECORD_AUDIO permission) */
result = (*thiz->engineEngine)->CreateAudioRecorder (thiz->engineEngine,
&thiz->recorderObject, &audioSrc, &audioSink, 1, id, req);
if (result != SL_RESULT_SUCCESS) {
goto failed;
}
- /* Realize the audio recorder */
+ /* Realize the audio recorder object */
result =
(*thiz->recorderObject)->Realize (thiz->recorderObject, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
return FALSE;
}
+/* This callback function is executed when the ringbuffer is started to preroll
+ * the output buffer queue with empty buffers, from app thread, and each time
+ * there's a filled buffer, from audio device processing thread,
+ * the callback behaviour.
+ */
static void
_opensles_recorder_cb (SLAndroidSimpleBufferQueueItf bufferQueue, void *context)
{
gint seg;
gint len;
+ /* Get a segment form the GStreamer ringbuffer to write in */
if (!gst_ring_buffer_prepare_read (rb, &seg, &ptr, &len)) {
GST_WARNING_OBJECT (rb, "No segment available");
return;
}
- /* Enqueue a buffer */
GST_LOG_OBJECT (thiz, "enqueue: %p size %d segment: %d", ptr, len, seg);
- result = (*thiz->bufferQueue)->Enqueue (thiz->bufferQueue, ptr, len);
+ /* Enqueue the sefment as buffer to be written */
+ result = (*thiz->bufferQueue)->Enqueue (thiz->bufferQueue, ptr, len);
if (result != SL_RESULT_SUCCESS) {
GST_ERROR_OBJECT (thiz, "bufferQueue.Enqueue failed(0x%08x)",
(guint32) result);
return;
}
+ /* FIXME: we advance here and behaviour might be racy with the reading
+ * thread */
gst_ring_buffer_advance (rb, 1);
}
thiz->is_queue_callback_registered = TRUE;
}
- /* Fill the queue by enqueing buffers */
+ /* Preroll the buffer queue by enqueing segments */
for (i = 0; i < RECORDER_QUEUE_SIZE; i++) {
_opensles_recorder_cb (NULL, rb);
}
(guint32) result);
return FALSE;
}
+
return TRUE;
}
return TRUE;
}
-/* Player related functions */
+/*
+ * Player related functions
+ */
static gboolean
_opensles_player_change_volume (GstRingBuffer * rb)
return TRUE;
}
+/* This is a callback function invoked by the playback device thread and
+ * it's used to monitor position changes */
static void
_opensles_player_event_cb (SLPlayItf caller, void *context, SLuint32 event)
{
(*caller)->GetPosition (caller, &position);
GST_LOG_OBJECT (thiz, "at position=%u ms", (guint) position);
- } else if (event & SL_PLAYEVENT_HEADSTALLED) {
- GST_WARNING_OBJECT (thiz, "head stalled");
}
}
};
SLDataSink audioSink = { &loc_outmix, NULL };
- /* Create an audio player */
+ /* Define the required interfaces */
const SLInterfaceID ids[2] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME };
const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
- /* Define the format in OpenSL ES terms */
+ /* Define the format in OpenSL ES terminology */
_opensles_format (spec, &format);
+ /* Create the player object */
result = (*thiz->engineEngine)->CreateAudioPlayer (thiz->engineEngine,
&thiz->playerObject, &audioSrc, &audioSink, 2, ids, req);
if (result != SL_RESULT_SUCCESS) {
goto failed;
}
- /* Realize the player */
+ /* Realize the player object */
result =
(*thiz->playerObject)->Realize (thiz->playerObject, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
goto failed;
}
- /* Request position update events at each 10 ms */
- result = (*thiz->playerPlay)->SetPositionUpdatePeriod (thiz->playerPlay, 10);
+ /* Request position update events at each 20 ms */
+ result = (*thiz->playerPlay)->SetPositionUpdatePeriod (thiz->playerPlay, 20);
if (result != SL_RESULT_SUCCESS) {
GST_ERROR_OBJECT (thiz, "player.SetPositionUpdatePeriod failed(0x%08x)",
(guint32) result);
/* Define the event mask to be monitorized */
result = (*thiz->playerPlay)->SetCallbackEventsMask (thiz->playerPlay,
- SL_PLAYEVENT_HEADATNEWPOS | SL_PLAYEVENT_HEADSTALLED);
+ SL_PLAYEVENT_HEADATNEWPOS);
if (result != SL_RESULT_SUCCESS) {
GST_ERROR_OBJECT (thiz, "player.SetCallbackEventsMask failed(0x%08x)",
(guint32) result);
_opensles_player_change_volume (rb);
_opensles_player_change_mute (rb);
- /* Define our queue data buffer */
+ /* Allocate the queue associated ringbuffer memory */
thiz->data_segtotal = loc_bufq.numBuffers;
thiz->data = g_malloc (spec->segsize * thiz->data_segtotal);
thiz->cursor = 0;
return FALSE;
}
+/* This callback function is executed when the ringbuffer is started to preroll
+ * the input buffer queue with few buffers, from app thread, and each time
+ * that rendering of one buffer finishes, from audio device processing thread,
+ * the callback behaviour.
+ *
+ * We wrap the queue behaviour with an appropriate chunk of memory (queue len *
+ * ringbuffer segment size) which is used to hold the audio data while it's
+ * being processed in the queue. The memory region is used whit a ringbuffer
+ * behaviour.
+ */
static void
_opensles_player_cb (SLAndroidSimpleBufferQueueItf bufferQueue, void *context)
{
gint seg;
gint len;
+ /* Get a segment form the GStreamer ringbuffer to read some samples */
if (!gst_ring_buffer_prepare_read (rb, &seg, &ptr, &len)) {
GST_WARNING_OBJECT (rb, "No segment available");
return;
}
- /* copy data to our queue ringbuffer */
+ /* copy the segment data to our queue associated ringbuffer memory */
cur = thiz->data + (thiz->cursor * rb->spec.segsize);
memcpy (cur, ptr, len);
g_atomic_int_inc (&thiz->segqueued);
- /* Enqueue a buffer */
GST_LOG_OBJECT (thiz, "enqueue: %p size %d segment: %d in queue[%d]",
cur, len, seg, thiz->cursor);
- result = (*thiz->bufferQueue)->Enqueue (thiz->bufferQueue, cur, len);
+ /* advance the cursor in our queue associated ringbuffer */
thiz->cursor = (thiz->cursor + 1) % thiz->data_segtotal;
+ /* Enqueue the buffer to be rendered */
+ result = (*thiz->bufferQueue)->Enqueue (thiz->bufferQueue, cur, len);
if (result != SL_RESULT_SUCCESS) {
GST_ERROR_OBJECT (thiz, "bufferQueue.Enqueue failed(0x%08x)",
(guint32) result);
return;
}
+ /* Fill with silence samples the segment of the GStreamer ringbuffer */
gst_ring_buffer_clear (rb, seg);
+ /* Make the segment reusable */
gst_ring_buffer_advance (rb, 1);
}
(guint32) result);
return FALSE;
}
+
return TRUE;
}
result =
(*thiz->playerPlay)->SetPlayState (thiz->playerPlay,
SL_PLAYSTATE_STOPPED);
-
if (result != SL_RESULT_SUCCESS) {
GST_ERROR_OBJECT (thiz, "player.SetPlayState failed(0x%08x)",
(guint32) result);
return FALSE;
}
+ /* Reset our state */
g_atomic_int_set (&thiz->segqueued, 0);
thiz->cursor = 0;
return TRUE;
}
-/* OpenSL ES ring buffer wrapper */
+/*
+ * OpenSL ES ringbuffer wrapper
+ */
+
GstRingBuffer *
gst_opensles_ringbuffer_new (RingBufferMode mode)
}
GST_DEBUG_OBJECT (thiz, "ringbuffer created");
+
return GST_RING_BUFFER (thiz);
}
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
- /* Create engine */
+ /* Create the engine object */
result = slCreateEngine (&thiz->engineObject, 0, NULL, 0, NULL, NULL);
if (result != SL_RESULT_SUCCESS) {
GST_ERROR_OBJECT (thiz, "slCreateEngine failed(0x%08x)", (guint32) result);
goto failed;
}
- /* Realize the engine */
+ /* Realize the engine object */
result = (*thiz->engineObject)->Realize (thiz->engineObject,
SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
goto failed;
}
- /* Get the engine interface, which is needed in order to
- * create other objects */
+ /* Get the engine interface, which is needed in order to create other objects */
result = (*thiz->engineObject)->GetInterface (thiz->engineObject,
SL_IID_ENGINE, &thiz->engineEngine);
if (result != SL_RESULT_SUCCESS) {
if (thiz->mode == RB_MODE_SINK_PCM) {
SLOutputMixItf outputMix;
- /* Create an output mixer */
+ /* Create an output mixer object */
result = (*thiz->engineEngine)->CreateOutputMix (thiz->engineEngine,
&thiz->outputMixObject, 0, NULL, NULL);
if (result != SL_RESULT_SUCCESS) {
goto failed;
}
- /* Realize the output mixer */
+ /* Realize the output mixer object */
result = (*thiz->outputMixObject)->Realize (thiz->outputMixObject,
SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
goto failed;
}
- /* Check for output device options */
+ /* Get the mixer interface */
result = (*thiz->outputMixObject)->GetInterface (thiz->outputMixObject,
SL_IID_OUTPUTMIX, &outputMix);
if (result != SL_RESULT_SUCCESS) {
GST_WARNING_OBJECT (thiz, "outputMix.GetInterface failed(0x%08x)",
(guint32) result);
} else {
- SLint32 numDevices;
- SLuint32 deviceIDs[16];
+ SLint32 numDevices = 0;
+ SLuint32 deviceIDs[MAX_NUMBER_OUTPUT_DEVICES];
gint i;
+
+ /* Query the list of output devices */
(*outputMix)->GetDestinationOutputDeviceIDs (outputMix, &numDevices,
deviceIDs);
GST_DEBUG_OBJECT (thiz, "Found %d output devices", (gint) numDevices);
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
- /* Destroy output mix object */
+ /* Destroy the output mix object */
if (thiz->outputMixObject) {
(*thiz->outputMixObject)->Destroy (thiz->outputMixObject);
thiz->outputMixObject = NULL;
}
- /* Destroy engine object, and invalidate all associated interfaces */
+ /* Destroy the engine object and invalidate all associated interfaces */
if (thiz->engineObject) {
(*thiz->engineObject)->Destroy (thiz->engineObject);
thiz->engineObject = NULL;
gst_buffer_unref (rb->data);
rb->data = NULL;
}
+
GST_DEBUG_OBJECT (thiz, "ringbuffer released");
return TRUE;
}
* Boston, MA 02111-1307, USA.
*/
+/**
+ * SECTION:element-openslessink
+ * @see_also: openslessrc
+ *
+ * This element renders raw audio samples using the OpenSL ES API in Android OS.
+ *
+ * <refsect2>
+ * <title>Example pipelines</title>
+ * |[
+ * gst-launch -v filesrc location=music.ogg ! oggdemux ! vorbisdec ! audioconvert ! audioresample ! opeslessink
+ * ]| Play an Ogg/Vorbis file.
+ * </refsect2>
+ *
+ */
+
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
/* According to Android's NDK doc the following are the supported rates */
#define RATES "8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100"
+/* 48000 Hz is also claimed to be supported but the AudioFlinger downsampling
+ * doesn't seems to work properly so we relay GStreamer audioresample element
+ * to cope with this samplerate. */
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
(gint) (aod)->maxSampleRate, (gint) (aod)->isFreqRangeContinuous, \
(gint) (aod)->maxChannels
-/* Next it's not defined in Android */
-#ifndef MAX_NUMBER_OUTPUT_DEVICES
-#define MAX_NUMBER_OUTPUT_DEVICES 16
-#endif
-
static gboolean
_opensles_query_capabilities (GstOpenSLESSink * sink)
{
goto beach;
}
- /* Get the engine interface, which is needed in order to
- * create other objects */
+ /* Get the engine interface, which is needed in order to create other objects */
result = (*engineObject)->GetInterface (engineObject,
SL_IID_AUDIOIODEVICECAPABILITIES, &audioIODeviceCapabilities);
if (result != SL_RESULT_SUCCESS) {
goto beach;
}
+ /* Query the list of available audio outputs */
result = (*audioIODeviceCapabilities)->GetAvailableAudioOutputs
(audioIODeviceCapabilities, &numOutputs, outputDeviceIDs);
if (result != SL_RESULT_SUCCESS) {
res = TRUE;
beach:
- /* Destroy engine object */
+ /* Destroy the engine object */
if (engineObject) {
(*engineObject)->Destroy (engineObject);
}
_opensles_query_capabilities (sink);
gst_base_audio_sink_set_provide_clock (GST_BASE_AUDIO_SINK (sink), TRUE);
+ /* Override some default values to fit on the AudioFlinger behaviour of
+ * processing 20ms buffers as minimum buffer size. */
GST_BASE_AUDIO_SINK (sink)->buffer_time = 400000;
GST_BASE_AUDIO_SINK (sink)->latency_time = 20000;
}