/* GStreamer * Copyright (C) 2012 Fluendo S.A. * * 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 #endif #include #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; /* 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 id[1] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; const SLboolean req[1] = { SL_BOOLEAN_TRUE }; /* 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, 1, id, req); if (result != SL_RESULT_SUCCESS) { GST_ERROR_OBJECT (thiz, "engine.CreateAudioRecorder failed(0x%08x)", (guint32) result); goto failed; } /* 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; /* Configure audio source */ SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, MIN (32, (spec->segtotal >> 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[2] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME }; const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; /* 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) { GST_ERROR_OBJECT (thiz, "engine.CreateAudioPlayer failed(0x%08x)", (guint32) result); goto failed; } /* 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; gint i; /* 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 buffers */ if (!g_atomic_int_get (&thiz->is_prerolled)) { for (i = 0; i < thiz->data_segtotal; i++) { _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 = 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); 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; 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; }