| /* 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; | |
| } |