mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-30 04:00:37 +00:00
0acdcc1b37
This would hint at wrong position reporting, and apparently sometimes happens after a seek.
1073 lines
32 KiB
C
1073 lines
32 KiB
C
/* GStreamer
|
|
* Copyright (C) 2012 Fluendo S.A. <support@fluendo.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "opensles.h"
|
|
#include "openslesringbuffer.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (opensles_ringbuffer_debug);
|
|
#define GST_CAT_DEFAULT opensles_ringbuffer_debug
|
|
|
|
#define _do_init \
|
|
GST_DEBUG_CATEGORY_INIT (opensles_ringbuffer_debug, \
|
|
"opensles_ringbuffer", 0, "OpenSL ES ringbuffer");
|
|
|
|
#define parent_class gst_opensles_ringbuffer_parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstOpenSLESRingBuffer, gst_opensles_ringbuffer,
|
|
GST_TYPE_AUDIO_RING_BUFFER, _do_init);
|
|
|
|
/*
|
|
* Some generic helper functions
|
|
*/
|
|
|
|
static inline SLuint32
|
|
_opensles_sample_rate (guint rate)
|
|
{
|
|
switch (rate) {
|
|
case 8000:
|
|
return SL_SAMPLINGRATE_8;
|
|
case 11025:
|
|
return SL_SAMPLINGRATE_11_025;
|
|
case 12000:
|
|
return SL_SAMPLINGRATE_12;
|
|
case 16000:
|
|
return SL_SAMPLINGRATE_16;
|
|
case 22050:
|
|
return SL_SAMPLINGRATE_22_05;
|
|
case 24000:
|
|
return SL_SAMPLINGRATE_24;
|
|
case 32000:
|
|
return SL_SAMPLINGRATE_32;
|
|
case 44100:
|
|
return SL_SAMPLINGRATE_44_1;
|
|
case 48000:
|
|
return SL_SAMPLINGRATE_48;
|
|
case 64000:
|
|
return SL_SAMPLINGRATE_64;
|
|
case 88200:
|
|
return SL_SAMPLINGRATE_88_2;
|
|
case 96000:
|
|
return SL_SAMPLINGRATE_96;
|
|
case 192000:
|
|
return SL_SAMPLINGRATE_192;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static inline SLuint32
|
|
_opensles_channel_mask (GstAudioRingBufferSpec * spec)
|
|
{
|
|
switch (spec->info.channels) {
|
|
case 1:
|
|
return (SL_SPEAKER_FRONT_CENTER);
|
|
case 2:
|
|
return (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
_opensles_format (GstAudioRingBufferSpec * spec, SLDataFormat_PCM * format)
|
|
{
|
|
format->formatType = SL_DATAFORMAT_PCM;
|
|
format->numChannels = spec->info.channels;
|
|
format->samplesPerSec = _opensles_sample_rate (spec->info.rate);
|
|
format->bitsPerSample = spec->info.finfo->depth;
|
|
format->containerSize = spec->info.finfo->width;
|
|
format->channelMask = _opensles_channel_mask (spec);
|
|
format->endianness =
|
|
((spec->info.finfo->endianness ==
|
|
G_BIG_ENDIAN) ? SL_BYTEORDER_BIGENDIAN : SL_BYTEORDER_LITTLEENDIAN);
|
|
}
|
|
|
|
/*
|
|
* Recorder related functions
|
|
*/
|
|
|
|
static gboolean
|
|
_opensles_recorder_acquire (GstAudioRingBuffer * rb,
|
|
GstAudioRingBufferSpec * spec)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
SLDataFormat_PCM format;
|
|
SLAndroidConfigurationItf config;
|
|
|
|
/* Configure audio source */
|
|
SLDataLocator_IODevice loc_dev = {
|
|
SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
|
|
SL_DEFAULTDEVICEID_AUDIOINPUT, NULL
|
|
};
|
|
SLDataSource audioSrc = { &loc_dev, NULL };
|
|
|
|
/* Configure audio sink */
|
|
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
|
|
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
|
|
};
|
|
SLDataSink audioSink = { &loc_bq, &format };
|
|
|
|
/* Required optional interfaces */
|
|
const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
|
SL_IID_ANDROIDCONFIGURATION
|
|
};
|
|
const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
|
|
|
|
/* Define the audio format in OpenSL ES terminology */
|
|
_opensles_format (spec, &format);
|
|
|
|
/* Create the audio recorder object (requires the RECORD_AUDIO permission) */
|
|
result = (*thiz->engineEngine)->CreateAudioRecorder (thiz->engineEngine,
|
|
&thiz->recorderObject, &audioSrc, &audioSink, 2, ids, req);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "engine.CreateAudioRecorder failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Set the recording preset if we have one */
|
|
if (thiz->preset != GST_OPENSLES_RECORDING_PRESET_NONE) {
|
|
SLint32 preset = gst_to_opensles_recording_preset (thiz->preset);
|
|
|
|
result = (*thiz->recorderObject)->GetInterface (thiz->recorderObject,
|
|
SL_IID_ANDROIDCONFIGURATION, &config);
|
|
|
|
if (result == SL_RESULT_SUCCESS) {
|
|
result = (*config)->SetConfiguration (config,
|
|
SL_ANDROID_KEY_RECORDING_PRESET, &preset, sizeof (preset));
|
|
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_WARNING_OBJECT (thiz, "Failed to set playback stream type (0x%08x)",
|
|
(guint32) result);
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT (thiz,
|
|
"Could not get configuration interface 0x%08x", (guint32) result);
|
|
}
|
|
}
|
|
|
|
/* Realize the audio recorder object */
|
|
result =
|
|
(*thiz->recorderObject)->Realize (thiz->recorderObject, SL_BOOLEAN_FALSE);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "recorder.Realize failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Get the record interface */
|
|
result = (*thiz->recorderObject)->GetInterface (thiz->recorderObject,
|
|
SL_IID_RECORD, &thiz->recorderRecord);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "recorder.GetInterface(Record) failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Get the buffer queue interface */
|
|
result =
|
|
(*thiz->recorderObject)->GetInterface (thiz->recorderObject,
|
|
SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &thiz->bufferQueue);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "recorder.GetInterface(BufferQueue) failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
failed:
|
|
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)
|
|
{
|
|
GstAudioRingBuffer *rb = GST_AUDIO_RING_BUFFER_CAST (context);
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
guint8 *ptr;
|
|
gint seg;
|
|
gint len;
|
|
|
|
/* Advance only when we are called by the callback function */
|
|
if (bufferQueue) {
|
|
gst_audio_ring_buffer_advance (rb, 1);
|
|
}
|
|
|
|
/* Get a segment form the GStreamer ringbuffer to write in */
|
|
if (!gst_audio_ring_buffer_prepare_read (rb, &seg, &ptr, &len)) {
|
|
GST_WARNING_OBJECT (rb, "No segment available");
|
|
return;
|
|
}
|
|
|
|
GST_LOG_OBJECT (thiz, "enqueue: %p size %d segment: %d", ptr, len, seg);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
_opensles_recorder_start (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
|
|
/* Register callback on the buffer queue */
|
|
if (!thiz->is_queue_callback_registered) {
|
|
result = (*thiz->bufferQueue)->RegisterCallback (thiz->bufferQueue,
|
|
_opensles_recorder_cb, rb);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "bufferQueue.RegisterCallback failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
thiz->is_queue_callback_registered = TRUE;
|
|
}
|
|
|
|
/* Preroll one buffer */
|
|
_opensles_recorder_cb (NULL, rb);
|
|
|
|
/* Start recording */
|
|
result =
|
|
(*thiz->recorderRecord)->SetRecordState (thiz->recorderRecord,
|
|
SL_RECORDSTATE_RECORDING);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "recorder.SetRecordState failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_opensles_recorder_stop (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
|
|
/* Stop recording */
|
|
result =
|
|
(*thiz->recorderRecord)->SetRecordState (thiz->recorderRecord,
|
|
SL_RECORDSTATE_STOPPED);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "recorder.SetRecordState failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Unregister callback on the buffer queue */
|
|
result = (*thiz->bufferQueue)->RegisterCallback (thiz->bufferQueue,
|
|
NULL, NULL);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "bufferQueue.RegisterCallback failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
thiz->is_queue_callback_registered = FALSE;
|
|
|
|
/* Reset the queue */
|
|
result = (*thiz->bufferQueue)->Clear (thiz->bufferQueue);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "bufferQueue.Clear failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Player related functions
|
|
*/
|
|
|
|
static gboolean
|
|
_opensles_player_change_volume (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
SLresult result;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
if (thiz->playerVolume) {
|
|
gint millibel = (1.0 - thiz->volume) * -5000.0;
|
|
result =
|
|
(*thiz->playerVolume)->SetVolumeLevel (thiz->playerVolume, millibel);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.SetVolumeLevel failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
GST_DEBUG_OBJECT (thiz, "changed volume to %d", millibel);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_opensles_player_change_mute (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
SLresult result;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
if (thiz->playerVolume) {
|
|
result = (*thiz->playerVolume)->SetMute (thiz->playerVolume, thiz->mute);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.SetMute failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
GST_DEBUG_OBJECT (thiz, "changed mute to %d", thiz->mute);
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (event & SL_PLAYEVENT_HEADATNEWPOS) {
|
|
SLmillisecond position;
|
|
|
|
(*caller)->GetPosition (caller, &position);
|
|
GST_LOG_OBJECT (context, "at position=%u ms", (guint) position);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
_opensles_player_acquire (GstAudioRingBuffer * rb,
|
|
GstAudioRingBufferSpec * spec)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
SLDataFormat_PCM format;
|
|
SLAndroidConfigurationItf config;
|
|
|
|
/* Configure audio source
|
|
* 4 buffers is the "typical" size as optimized inside Android's
|
|
* OpenSL ES, see frameworks/wilhelm/src/itfstruct.h BUFFER_HEADER_TYPICAL
|
|
*
|
|
* Also only use half of our segment size to make sure that there's always
|
|
* some more queued up in our ringbuffer and we don't start to read silence.
|
|
*/
|
|
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
|
|
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, MIN (4, MAX (spec->segtotal >> 1,
|
|
1))
|
|
};
|
|
SLDataSource audioSrc = { &loc_bufq, &format };
|
|
|
|
/* Configure audio sink */
|
|
SLDataLocator_OutputMix loc_outmix = {
|
|
SL_DATALOCATOR_OUTPUTMIX, thiz->outputMixObject
|
|
};
|
|
SLDataSink audioSink = { &loc_outmix, NULL };
|
|
|
|
/* Define the required interfaces */
|
|
const SLInterfaceID ids[3] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME,
|
|
SL_IID_ANDROIDCONFIGURATION
|
|
};
|
|
const SLboolean req[3] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
|
|
SL_BOOLEAN_FALSE
|
|
};
|
|
|
|
/* 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, 3, ids, req);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "engine.CreateAudioPlayer failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Set the stream type if we have one */
|
|
if (thiz->stream_type != GST_OPENSLES_STREAM_TYPE_NONE) {
|
|
SLint32 stream_type = gst_to_opensles_stream_type (thiz->stream_type);
|
|
|
|
result = (*thiz->playerObject)->GetInterface (thiz->playerObject,
|
|
SL_IID_ANDROIDCONFIGURATION, &config);
|
|
|
|
if (result == SL_RESULT_SUCCESS) {
|
|
result = (*config)->SetConfiguration (config,
|
|
SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof (stream_type));
|
|
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_WARNING_OBJECT (thiz, "Failed to set playback stream type (0x%08x)",
|
|
(guint32) result);
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT (thiz,
|
|
"Could not get configuration interface 0x%08x", (guint32) result);
|
|
}
|
|
}
|
|
|
|
/* Realize the player object */
|
|
result =
|
|
(*thiz->playerObject)->Realize (thiz->playerObject, SL_BOOLEAN_FALSE);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.Realize failed(0x%08x)", (guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Get the play interface */
|
|
result = (*thiz->playerObject)->GetInterface (thiz->playerObject,
|
|
SL_IID_PLAY, &thiz->playerPlay);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.GetInterface(Play) failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Get the buffer queue interface */
|
|
result = (*thiz->playerObject)->GetInterface (thiz->playerObject,
|
|
SL_IID_BUFFERQUEUE, &thiz->bufferQueue);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.GetInterface(BufferQueue) failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Get the volume interface */
|
|
result = (*thiz->playerObject)->GetInterface (thiz->playerObject,
|
|
SL_IID_VOLUME, &thiz->playerVolume);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.GetInterface(Volume) failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* 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);
|
|
goto failed;
|
|
}
|
|
|
|
/* Define the event mask to be monitorized */
|
|
result = (*thiz->playerPlay)->SetCallbackEventsMask (thiz->playerPlay,
|
|
SL_PLAYEVENT_HEADATNEWPOS);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.SetCallbackEventsMask failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Register a callback to process the events */
|
|
result = (*thiz->playerPlay)->RegisterCallback (thiz->playerPlay,
|
|
_opensles_player_event_cb, thiz);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.RegisterCallback(event_cb) failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Configure the volume and mute state */
|
|
_opensles_player_change_volume (rb);
|
|
_opensles_player_change_mute (rb);
|
|
|
|
/* Allocate the queue associated ringbuffer memory */
|
|
thiz->data_segtotal = loc_bufq.numBuffers;
|
|
thiz->data_size = spec->segsize * thiz->data_segtotal;
|
|
thiz->data = g_malloc0 (thiz->data_size);
|
|
g_atomic_int_set (&thiz->segqueued, 0);
|
|
g_atomic_int_set (&thiz->is_prerolled, 0);
|
|
thiz->cursor = 0;
|
|
|
|
return TRUE;
|
|
|
|
failed:
|
|
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)
|
|
{
|
|
GstAudioRingBuffer *rb = GST_AUDIO_RING_BUFFER_CAST (context);
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
guint8 *ptr, *cur;
|
|
gint seg;
|
|
gint len;
|
|
|
|
/* Get a segment form the GStreamer ringbuffer to read some samples */
|
|
if (!gst_audio_ring_buffer_prepare_read (rb, &seg, &ptr, &len)) {
|
|
GST_WARNING_OBJECT (rb, "No segment available");
|
|
return;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
GST_LOG_OBJECT (thiz, "enqueue: %p size %d segment: %d in queue[%d]",
|
|
cur, len, seg, thiz->cursor);
|
|
/* 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_audio_ring_buffer_clear (rb, seg);
|
|
/* Make the segment reusable */
|
|
gst_audio_ring_buffer_advance (rb, 1);
|
|
}
|
|
|
|
static gboolean
|
|
_opensles_player_start (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
|
|
/* Register callback on the buffer queue */
|
|
if (!thiz->is_queue_callback_registered) {
|
|
result = (*thiz->bufferQueue)->RegisterCallback (thiz->bufferQueue,
|
|
_opensles_player_cb, rb);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "bufferQueue.RegisterCallback failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
thiz->is_queue_callback_registered = TRUE;
|
|
}
|
|
|
|
/* Fill the queue by enqueing a buffer */
|
|
if (!g_atomic_int_get (&thiz->is_prerolled)) {
|
|
_opensles_player_cb (NULL, rb);
|
|
g_atomic_int_set (&thiz->is_prerolled, 1);
|
|
}
|
|
|
|
/* Change player state into PLAYING */
|
|
result =
|
|
(*thiz->playerPlay)->SetPlayState (thiz->playerPlay,
|
|
SL_PLAYSTATE_PLAYING);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.SetPlayState failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_opensles_player_pause (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
|
|
result =
|
|
(*thiz->playerPlay)->SetPlayState (thiz->playerPlay, SL_PLAYSTATE_PAUSED);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "player.SetPlayState failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_opensles_player_stop (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
SLresult result;
|
|
|
|
/* Change player state into STOPPED */
|
|
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;
|
|
}
|
|
|
|
/* Unregister callback on the buffer queue */
|
|
result = (*thiz->bufferQueue)->RegisterCallback (thiz->bufferQueue,
|
|
NULL, NULL);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "bufferQueue.RegisterCallback failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
thiz->is_queue_callback_registered = FALSE;
|
|
|
|
/* Reset the queue */
|
|
result = (*thiz->bufferQueue)->Clear (thiz->bufferQueue);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "bufferQueue.Clear failed(0x%08x)",
|
|
(guint32) result);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Reset our state */
|
|
g_atomic_int_set (&thiz->segqueued, 0);
|
|
thiz->cursor = 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* OpenSL ES ringbuffer wrapper
|
|
*/
|
|
|
|
GstAudioRingBuffer *
|
|
gst_opensles_ringbuffer_new (RingBufferMode mode)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
|
|
g_return_val_if_fail (mode > RB_MODE_NONE && mode < RB_MODE_LAST, NULL);
|
|
|
|
thiz = g_object_new (GST_TYPE_OPENSLES_RING_BUFFER, NULL);
|
|
|
|
if (thiz) {
|
|
thiz->mode = mode;
|
|
if (mode == RB_MODE_SRC) {
|
|
thiz->acquire = _opensles_recorder_acquire;
|
|
thiz->start = _opensles_recorder_start;
|
|
thiz->pause = _opensles_recorder_stop;
|
|
thiz->stop = _opensles_recorder_stop;
|
|
thiz->change_volume = NULL;
|
|
} else if (mode == RB_MODE_SINK_PCM) {
|
|
thiz->acquire = _opensles_player_acquire;
|
|
thiz->start = _opensles_player_start;
|
|
thiz->pause = _opensles_player_pause;
|
|
thiz->stop = _opensles_player_stop;
|
|
thiz->change_volume = _opensles_player_change_volume;
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (thiz, "ringbuffer created");
|
|
|
|
return GST_AUDIO_RING_BUFFER (thiz);
|
|
}
|
|
|
|
void
|
|
gst_opensles_ringbuffer_set_volume (GstAudioRingBuffer * rb, gfloat volume)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
thiz->volume = volume;
|
|
|
|
if (thiz->change_volume) {
|
|
thiz->change_volume (rb);
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_opensles_ringbuffer_set_mute (GstAudioRingBuffer * rb, gboolean mute)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
thiz->mute = mute;
|
|
|
|
if (thiz->change_mute) {
|
|
thiz->change_mute (rb);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_opensles_ringbuffer_open_device (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
SLresult result;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
/* Create and realize the engine object */
|
|
thiz->engineObject = gst_opensles_get_engine ();
|
|
if (!thiz->engineObject) {
|
|
GST_ERROR_OBJECT (thiz, "Failed to get engine object");
|
|
goto failed;
|
|
}
|
|
|
|
/* 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) {
|
|
GST_ERROR_OBJECT (thiz, "engine.GetInterface(Engine) failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
if (thiz->mode == RB_MODE_SINK_PCM) {
|
|
SLOutputMixItf outputMix;
|
|
|
|
/* Create an output mixer object */
|
|
result = (*thiz->engineEngine)->CreateOutputMix (thiz->engineEngine,
|
|
&thiz->outputMixObject, 0, NULL, NULL);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "engine.CreateOutputMix failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* Realize the output mixer object */
|
|
result = (*thiz->outputMixObject)->Realize (thiz->outputMixObject,
|
|
SL_BOOLEAN_FALSE);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_ERROR_OBJECT (thiz, "outputMix.Realize failed(0x%08x)",
|
|
(guint32) result);
|
|
goto failed;
|
|
}
|
|
|
|
/* 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 = MAX_NUMBER_OUTPUT_DEVICES;
|
|
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);
|
|
for (i = 0; i < numDevices; i++) {
|
|
GST_DEBUG_OBJECT (thiz, " DeviceID: %08x", (guint) deviceIDs[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (thiz, "device opened");
|
|
return TRUE;
|
|
|
|
failed:
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_opensles_ringbuffer_close_device (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
/* Destroy the output mix object */
|
|
if (thiz->outputMixObject) {
|
|
(*thiz->outputMixObject)->Destroy (thiz->outputMixObject);
|
|
thiz->outputMixObject = NULL;
|
|
}
|
|
|
|
/* Destroy the engine object and invalidate all associated interfaces */
|
|
if (thiz->engineObject) {
|
|
gst_opensles_release_engine (thiz->engineObject);
|
|
thiz->engineObject = NULL;
|
|
thiz->engineEngine = NULL;
|
|
}
|
|
|
|
thiz->bufferQueue = NULL;
|
|
|
|
GST_DEBUG_OBJECT (thiz, "device closed");
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_opensles_ringbuffer_acquire (GstAudioRingBuffer * rb,
|
|
GstAudioRingBufferSpec * spec)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
/* Instantiate and configure the OpenSL ES interfaces */
|
|
if (!thiz->acquire (rb, spec)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/* Initialize our ringbuffer memory region */
|
|
rb->size = spec->segtotal * spec->segsize;
|
|
rb->memory = g_malloc0 (rb->size);
|
|
|
|
GST_DEBUG_OBJECT (thiz, "ringbuffer acquired");
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_opensles_ringbuffer_release (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER (rb);
|
|
|
|
/* XXX: We need to sleep a bit before destroying the player object
|
|
* because of a bug in Android in versions < 4.2.
|
|
*
|
|
* OpenSLES is using AudioTrack for rendering the sound. AudioTrack
|
|
* has a thread that pulls raw audio from the buffer queue and then
|
|
* passes it forward to AudioFlinger (AudioTrack::processAudioBuffer()).
|
|
* This thread is calling various callbacks on events, e.g. when
|
|
* an underrun happens or to request data. OpenSLES sets this callback
|
|
* on AudioTrack (audioTrack_callBack_pullFromBuffQueue() from
|
|
* android_AudioPlayer.cpp). Among other things this is taking a lock
|
|
* on the player interface.
|
|
*
|
|
* Now if we destroy the player interface object, it will first of all
|
|
* take the player interface lock (IObject_Destroy()). Then it destroys
|
|
* the audio player instance (android_audioPlayer_destroy()) which then
|
|
* calls stop() on the AudioTrack and deletes it. Now the destructor of
|
|
* AudioTrack will wait until the rendering thread (AudioTrack::processAudioBuffer())
|
|
* has finished.
|
|
*
|
|
* If all this happens with bad timing it can happen that the rendering
|
|
* thread is currently e.g. handling underrun but did not lock the player
|
|
* interface object yet. Then destroying happens and takes the lock and waits
|
|
* for the thread to finish. Then the thread tries to take the lock and waits
|
|
* forever.
|
|
*
|
|
* We wait a bit before destroying the player object to make sure that
|
|
* the rendering thread finished whatever it was doing, and then stops
|
|
* (note: we called gst_opensles_ringbuffer_stop() before this already).
|
|
*/
|
|
|
|
/* Destroy audio player object, and invalidate all associated interfaces */
|
|
if (thiz->playerObject) {
|
|
g_usleep (50000);
|
|
(*thiz->playerObject)->Destroy (thiz->playerObject);
|
|
thiz->playerObject = NULL;
|
|
thiz->playerPlay = NULL;
|
|
thiz->playerVolume = NULL;
|
|
}
|
|
|
|
/* Destroy audio recorder object, and invalidate all associated interfaces */
|
|
if (thiz->recorderObject) {
|
|
g_usleep (50000);
|
|
(*thiz->recorderObject)->Destroy (thiz->recorderObject);
|
|
thiz->recorderObject = NULL;
|
|
thiz->recorderRecord = NULL;
|
|
}
|
|
|
|
if (thiz->data) {
|
|
g_free (thiz->data);
|
|
thiz->data = NULL;
|
|
}
|
|
|
|
if (rb->memory) {
|
|
g_free (rb->memory);
|
|
rb->memory = NULL;
|
|
rb->size = 0;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (thiz, "ringbuffer released");
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_opensles_ringbuffer_start (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
gboolean res;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
res = thiz->start (rb);
|
|
|
|
GST_DEBUG_OBJECT (thiz, "ringbuffer %s started", (res ? "" : "not"));
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_opensles_ringbuffer_pause (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
gboolean res;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
res = thiz->pause (rb);
|
|
|
|
GST_DEBUG_OBJECT (thiz, "ringbuffer %s paused", (res ? "" : "not"));
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_opensles_ringbuffer_stop (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
gboolean res;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
res = thiz->stop (rb);
|
|
|
|
GST_DEBUG_OBJECT (thiz, "ringbuffer %s stopped", (res ? " " : "not"));
|
|
return res;
|
|
}
|
|
|
|
static guint
|
|
gst_opensles_ringbuffer_delay (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
guint res = 0;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
if (thiz->playerPlay) {
|
|
SLuint32 state;
|
|
SLmillisecond position;
|
|
guint64 playedpos = 0, queuedpos = 0;
|
|
(*thiz->playerPlay)->GetPlayState (thiz->playerPlay, &state);
|
|
if (state == SL_PLAYSTATE_PLAYING) {
|
|
(*thiz->playerPlay)->GetPosition (thiz->playerPlay, &position);
|
|
playedpos =
|
|
gst_util_uint64_scale_round (position, rb->spec.info.rate, 1000);
|
|
queuedpos = g_atomic_int_get (&thiz->segqueued) * rb->samples_per_seg;
|
|
if (queuedpos < playedpos) {
|
|
res = 0;
|
|
GST_ERROR_OBJECT (thiz,
|
|
"Queued position smaller than playback position (%" G_GUINT64_FORMAT
|
|
" < %" G_GUINT64_FORMAT ")", queuedpos, playedpos);
|
|
} else {
|
|
res = queuedpos - playedpos;
|
|
}
|
|
}
|
|
|
|
GST_LOG_OBJECT (thiz, "queued samples %" G_GUINT64_FORMAT " position %u ms "
|
|
"(%" G_GUINT64_FORMAT " samples) delay %u samples",
|
|
queuedpos, (guint) position, playedpos, res);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
gst_opensles_ringbuffer_clear_all (GstAudioRingBuffer * rb)
|
|
{
|
|
GstOpenSLESRingBuffer *thiz;
|
|
|
|
thiz = GST_OPENSLES_RING_BUFFER_CAST (rb);
|
|
|
|
if (thiz->data) {
|
|
SLresult result;
|
|
|
|
memset (thiz->data, 0, thiz->data_size);
|
|
g_atomic_int_set (&thiz->segqueued, 0);
|
|
thiz->cursor = 0;
|
|
/* Reset the queue */
|
|
result = (*thiz->bufferQueue)->Clear (thiz->bufferQueue);
|
|
if (result != SL_RESULT_SUCCESS) {
|
|
GST_WARNING_OBJECT (thiz, "bufferQueue.Clear failed(0x%08x)",
|
|
(guint32) result);
|
|
}
|
|
g_atomic_int_set (&thiz->is_prerolled, 0);
|
|
}
|
|
|
|
GST_CALL_PARENT (GST_AUDIO_RING_BUFFER_CLASS, clear_all, (rb));
|
|
}
|
|
|
|
static void
|
|
gst_opensles_ringbuffer_dispose (GObject * object)
|
|
{
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_opensles_ringbuffer_finalize (GObject * object)
|
|
{
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_opensles_ringbuffer_class_init (GstOpenSLESRingBufferClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstAudioRingBufferClass *gstringbuffer_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstringbuffer_class = (GstAudioRingBufferClass *) klass;
|
|
|
|
gobject_class->dispose = gst_opensles_ringbuffer_dispose;
|
|
gobject_class->finalize = gst_opensles_ringbuffer_finalize;
|
|
|
|
gstringbuffer_class->open_device =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_open_device);
|
|
gstringbuffer_class->close_device =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_close_device);
|
|
gstringbuffer_class->acquire =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_acquire);
|
|
gstringbuffer_class->release =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_release);
|
|
gstringbuffer_class->start =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_start);
|
|
gstringbuffer_class->pause =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_pause);
|
|
gstringbuffer_class->resume =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_start);
|
|
gstringbuffer_class->stop = GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_stop);
|
|
gstringbuffer_class->delay =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_delay);
|
|
gstringbuffer_class->clear_all =
|
|
GST_DEBUG_FUNCPTR (gst_opensles_ringbuffer_clear_all);
|
|
}
|
|
|
|
static void
|
|
gst_opensles_ringbuffer_init (GstOpenSLESRingBuffer * thiz)
|
|
{
|
|
thiz->mode = RB_MODE_NONE;
|
|
thiz->engineObject = NULL;
|
|
thiz->outputMixObject = NULL;
|
|
thiz->playerObject = NULL;
|
|
thiz->recorderObject = NULL;
|
|
thiz->is_queue_callback_registered = FALSE;
|
|
}
|