From fd69382e3789f0be891bed86f92a9ad99b283490 Mon Sep 17 00:00:00 2001 From: Tom Yan Date: Mon, 23 Jul 2018 21:23:58 +0800 Subject: [PATCH] libpulseaudio: rewrite sles sink This should work way better than the old code, as it makes use of the buffer queue callback to do Enqueue(), which might be the only nice/right way to use OpenSLES on Android. CPU usage is low and RAM usage seems reasonable. No memory leak noticed. Tested with mpv on my Oreo phone (wired and Bluetooth). The latency was chosen base on Bluetooth audio requirement on Oreo. Shouldn't be hard to make it configurable as a module param in the future. The new code has a known downside though, that is it doesn't really support sink suspension, as in, silence will kept being written to the audio device even when the sink is suspended, which may have certain impact to battery time. It's probably possible to catch the state change of the sink and notify the buffer queue about it. It's just I don't want to bother digging further at the moment. --- packages/libpulseaudio/module-sles-sink.c | 406 ++++++++-------------- 1 file changed, 147 insertions(+), 259 deletions(-) diff --git a/packages/libpulseaudio/module-sles-sink.c b/packages/libpulseaudio/module-sles-sink.c index 31f8d264d..70c11307a 100644 --- a/packages/libpulseaudio/module-sles-sink.c +++ b/packages/libpulseaudio/module-sles-sink.c @@ -42,54 +42,38 @@ #include #include +#include -//Only certain interfaces are supported by the fast mixer. These are: -//SL_IID_ANDROIDSIMPLEBUFFERQUEUE -//SL_IID_VOLUME -//SL_IID_MUTESOLO #define USE_ANDROID_SIMPLE_BUFFER_QUEUE #ifdef USE_ANDROID_SIMPLE_BUFFER_QUEUE - #include - #define DATALOCATOR_BUFFERQUEUE SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE - #define IID_BUFFERQUEUE SL_IID_ANDROIDSIMPLEBUFFERQUEUE - #define BufferQueueItf SLAndroidSimpleBufferQueueItf - #define BufferQueueState SLAndroidSimpleBufferQueueState - #define IID_BUFFERQUEUE_USED SL_IID_ANDROIDSIMPLEBUFFERQUEUE - #define INDEX index + #define DATALOCATOR_BUFFERQUEUE SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE + #define IID_BUFFERQUEUE SL_IID_ANDROIDSIMPLEBUFFERQUEUE + #define BufferQueueItf SLAndroidSimpleBufferQueueItf + #define BufferQueueState SLAndroidSimpleBufferQueueState + #define IID_BUFFERQUEUE_USED SL_IID_ANDROIDSIMPLEBUFFERQUEUE + #define INDEX index #else - #define DATALOCATOR_BUFFERQUEUE SL_DATALOCATOR_BUFFERQUEUE - #define IID_BUFFERQUEUE SL_IID_BUFFERQUEUE - #define BufferQueueItf SLBufferQueueItf - #define BufferQueueState SLBufferQueueState - #define IID_BUFFERQUEUE_USED IID_BUFFERQUEUE - #define INDEX playIndex + #define DATALOCATOR_BUFFERQUEUE SL_DATALOCATOR_BUFFERQUEUE + #define IID_BUFFERQUEUE SL_IID_BUFFERQUEUE + #define BufferQueueItf SLBufferQueueItf + #define BufferQueueState SLBufferQueueState + #define IID_BUFFERQUEUE_USED IID_BUFFERQUEUE + #define INDEX playIndex #endif -#define checkResult(r) do { \ - if ((r) != SL_RESULT_SUCCESS) { \ - if ((r) == SL_RESULT_PARAMETER_INVALID) fprintf(stderr, "error SL_RESULT_PARAMETER_INVALID at %s:%d\n", __FILE__, __LINE__); \ - else if ((r) == SL_RESULT_PRECONDITIONS_VIOLATED ) fprintf(stderr, "error SL_RESULT_PRECONDITIONS_VIOLATED at %s:%d\n", __FILE__, __LINE__); \ - else fprintf(stderr, "error %d at %s:%d\n", (int) r, __FILE__, __LINE__); \ - } \ - } while (0) - PA_MODULE_AUTHOR("Lennart Poettering, Nathan Martynov"); PA_MODULE_DESCRIPTION("Android OpenSL ES sink"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(false); PA_MODULE_USAGE( - "sink_name= " - "sink_properties= " - "rate= "); + "sink_name= " + "sink_properties= " + "rate= " +); #define DEFAULT_SINK_NAME "OpenSL ES sink" -#define BLOCK_USEC (PA_USEC_PER_SEC * 2) - -typedef struct pa_memblock_queue_t { - pa_memblock *memblock; - struct pa_memblock_queue_t* next; -} pa_memblock_queue; +#define BLOCK_USEC (PA_USEC_PER_MSEC * 125) struct userdata { pa_core *core; @@ -101,23 +85,19 @@ struct userdata { pa_rtpoll *rtpoll; pa_usec_t block_usec; - pa_usec_t timestamp; - + pa_memchunk memchunk; - - SLObjectItf engineObject; - SLEngineItf engineEngine; - - // output mix interfaces - SLObjectItf outputMixObject; - - // buffer queue player interfaces - SLObjectItf bqPlayerObject; - SLPlayItf bqPlayerPlay; - BufferQueueItf bqPlayerBufferQueue; - - pa_memblock_queue* current; - pa_memblock_queue* last; + + SLObjectItf engineObject; + SLEngineItf engineEngine; + + // output mix interfaces + SLObjectItf outputMixObject; + + // buffer queue player interfaces + SLObjectItf bqPlayerObject; + SLPlayItf bqPlayerPlay; + BufferQueueItf bqPlayerBufferQueue; }; static const char* const valid_modargs[] = { @@ -127,174 +107,111 @@ static const char* const valid_modargs[] = { NULL }; -static int sink_process_msg( - pa_msgobject *o, - int code, - void *data, - int64_t offset, - pa_memchunk *chunk) { +static void process_render(BufferQueueItf bq, void *context) { + struct userdata* u = (struct userdata*) context; + void *p; - struct userdata *u = PA_SINK(o)->userdata; + pa_assert(u); + //pa_log_debug("Called\n"); - switch (code) { - case PA_SINK_MESSAGE_SET_STATE: - - if (pa_sink_get_state(u->sink) == PA_SINK_SUSPENDED || pa_sink_get_state(u->sink) == PA_SINK_INIT) { - if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING || PA_PTR_TO_UINT(data) == PA_SINK_IDLE) - u->timestamp = pa_rtclock_now(); - } - - break; - - case PA_SINK_MESSAGE_GET_LATENCY: { - pa_usec_t now; - - now = pa_rtclock_now(); - *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0ULL; - - return 0; - } + if (!pa_thread_mq_get()) { + pa_log_debug("Thread starting up"); + pa_thread_mq_install(&u->thread_mq); } - return pa_sink_process_msg(o, code, data, offset, chunk); + if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) { + //pa_log_debug("Rewinded\n"); + pa_sink_process_rewind(u->sink, 0); + } + + pa_sink_render(u->sink, u->sink->thread_info.max_request, &u->memchunk); + p = pa_memblock_acquire_chunk(&u->memchunk); + (*bq)->Enqueue(bq, p, u->memchunk.length); + //pa_log_debug("Written: %zu\n", u->memchunk.length); + pa_memblock_release(u->memchunk.memblock); + pa_memblock_unref(u->memchunk.memblock); } -static void sink_update_requested_latency_cb(pa_sink *s) { - struct userdata *u; - size_t nbytes; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - u->block_usec = pa_sink_get_requested_latency_within_thread(s); - - if (u->block_usec == (pa_usec_t) -1) - u->block_usec = s->thread_info.max_latency; - - nbytes = pa_usec_to_bytes(u->block_usec, &s->sample_spec); - pa_sink_set_max_rewind_within_thread(s, nbytes); - pa_sink_set_max_request_within_thread(s, nbytes); -} - -static void pa_sles_callback(BufferQueueItf bq, void *context){ - struct userdata* s = (struct userdata*) context; - pa_memblock_queue* next; - if (s->current != NULL){ - if (s->current->memblock != NULL) pa_memblock_unref(s->current->memblock); - next = s->current->next; - free(s->current); - s->current = next; - } +#define CHK(stmt) { \ + SLresult res = stmt; \ + if (res != SL_RESULT_SUCCESS) { \ + fprintf(stderr, "error %d at %s:%d\n", res, __FILE__, __LINE__); \ + goto fail; \ + } \ } static int pa_init_sles_player(struct userdata *s, SLint32 sl_rate) { - if (s == NULL) return -1; - SLresult result; - - // create engine - result = slCreateEngine(&(s->engineObject), 0, NULL, 0, NULL, NULL); checkResult(result); - result = (*s->engineObject)->Realize(s->engineObject, SL_BOOLEAN_FALSE); checkResult(result); - - result = (*s->engineObject)->GetInterface(s->engineObject, SL_IID_ENGINE, &(s->engineEngine)); checkResult(result); - - // create output mix - result = (*s->engineEngine)->CreateOutputMix(s->engineEngine, &(s->outputMixObject), 0, NULL, NULL); checkResult(result); - result = (*s->outputMixObject)->Realize(s->outputMixObject, SL_BOOLEAN_FALSE); checkResult(result); - - // create audio player - - SLDataLocator_OutputMix locator_outputmix; - locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - locator_outputmix.outputMix = s->outputMixObject; - - SLDataLocator_BufferQueue locator_bufferqueue; - locator_bufferqueue.locatorType = DATALOCATOR_BUFFERQUEUE; - locator_bufferqueue.numBuffers = 50; - - if (sl_rate < SL_SAMPLINGRATE_8 || sl_rate > SL_SAMPLINGRATE_192) { - pa_log("Incompatible sample rate"); - return -1; - } - - SLDataFormat_PCM pcm; - pcm.formatType = SL_DATAFORMAT_PCM; - pcm.numChannels = 2; - pcm.samplesPerSec = sl_rate; - pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - pcm.containerSize = 16; - pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; - - SLDataSource audiosrc; - audiosrc.pLocator = &locator_bufferqueue; - audiosrc.pFormat = &pcm; - - SLDataSink audiosnk; - audiosnk.pLocator = &locator_outputmix; - audiosnk.pFormat = NULL; - - SLInterfaceID ids[1] = {IID_BUFFERQUEUE}; - SLboolean flags[1] = {SL_BOOLEAN_TRUE}; - result = (*s->engineEngine)->CreateAudioPlayer(s->engineEngine, &s->bqPlayerObject, &audiosrc, &audiosnk, 1, ids, flags); checkResult(result); - result = (*s->bqPlayerObject)->Realize(s->bqPlayerObject, SL_BOOLEAN_FALSE); checkResult(result); - - result = (*s->bqPlayerObject)->GetInterface(s->bqPlayerObject, SL_IID_PLAY, &s->bqPlayerPlay); checkResult(result); - result = (*s->bqPlayerObject)->GetInterface(s->bqPlayerObject, IID_BUFFERQUEUE_USED, &s->bqPlayerBufferQueue); checkResult(result); - - result = (*s->bqPlayerBufferQueue)->RegisterCallback(s->bqPlayerBufferQueue, pa_sles_callback, s); checkResult(result); - - result = (*s->bqPlayerPlay)->SetPlayState(s->bqPlayerPlay, SL_PLAYSTATE_PLAYING); checkResult(result); - - return 0; + if (s == NULL) return -1; + + // create engine + CHK(slCreateEngine(&(s->engineObject), 0, NULL, 0, NULL, NULL)); + CHK((*s->engineObject)->Realize(s->engineObject, SL_BOOLEAN_FALSE)); + + CHK((*s->engineObject)->GetInterface(s->engineObject, SL_IID_ENGINE, &(s->engineEngine))); + + // create output mix + CHK((*s->engineEngine)->CreateOutputMix(s->engineEngine, &(s->outputMixObject), 0, NULL, NULL)); + CHK((*s->outputMixObject)->Realize(s->outputMixObject, SL_BOOLEAN_FALSE)); + + // create audio player + + SLDataLocator_OutputMix locator_outputmix; + locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; + locator_outputmix.outputMix = s->outputMixObject; + + SLDataLocator_BufferQueue locator_bufferqueue; + locator_bufferqueue.locatorType = DATALOCATOR_BUFFERQUEUE; + locator_bufferqueue.numBuffers = 1; + + if (sl_rate < 8000 || sl_rate > 192000) { + pa_log("Incompatible sample rate"); + return -1; + } + + SLDataFormat_PCM pcm; + pcm.formatType = SL_DATAFORMAT_PCM; + pcm.numChannels = 2; + pcm.samplesPerSec = sl_rate * 1000; + pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + pcm.containerSize = 16; + pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + SLDataSource audiosrc; + audiosrc.pLocator = &locator_bufferqueue; + audiosrc.pFormat = &pcm; + + SLDataSink audiosnk; + audiosnk.pLocator = &locator_outputmix; + audiosnk.pFormat = NULL; + + SLInterfaceID ids[1] = {IID_BUFFERQUEUE}; + SLboolean flags[1] = {SL_BOOLEAN_TRUE}; + CHK((*s->engineEngine)->CreateAudioPlayer(s->engineEngine, &s->bqPlayerObject, &audiosrc, &audiosnk, 1, ids, flags)); + CHK((*s->bqPlayerObject)->Realize(s->bqPlayerObject, SL_BOOLEAN_FALSE)); + + CHK((*s->bqPlayerObject)->GetInterface(s->bqPlayerObject, SL_IID_PLAY, &s->bqPlayerPlay)); + CHK((*s->bqPlayerObject)->GetInterface(s->bqPlayerObject, IID_BUFFERQUEUE_USED, &s->bqPlayerBufferQueue)); + + CHK((*s->bqPlayerBufferQueue)->RegisterCallback(s->bqPlayerBufferQueue, process_render, s)); + + CHK((*s->bqPlayerPlay)->SetPlayState(s->bqPlayerPlay, SL_PLAYSTATE_PLAYING)); + + return 0; + +fail: + return -1; } +#undef CHK + static void pa_destroy_sles_player(struct userdata *s){ - if (s == NULL) return; - (*s->bqPlayerPlay)->SetPlayState(s->bqPlayerPlay, SL_PLAYSTATE_STOPPED); - (*s->bqPlayerObject)->Destroy(s->bqPlayerObject); - (*s->outputMixObject)->Destroy(s->outputMixObject); - (*s->engineObject)->Destroy(s->engineObject); -} - -static void process_render(struct userdata *u, pa_usec_t now) { - pa_memblock_queue* current_block; - size_t ate = 0; - - pa_assert(u); - - /* This is the configured latency. Sink inputs connected to us - might not have a single frame more than the maxrequest value - queued. Hence: at maximum read this many bytes from the sink - inputs. */ - - /* Fill the buffer up the latency size */ - while (u->timestamp < now + u->block_usec) { - void *p; - - pa_sink_render(u->sink, u->sink->thread_info.max_request, &u->memchunk); - p = pa_memblock_acquire(u->memchunk.memblock); - (*u->bqPlayerBufferQueue)->Enqueue(u->bqPlayerBufferQueue, (uint8_t*) p + u->memchunk.index, u->memchunk.length); - pa_memblock_release(u->memchunk.memblock); - - u->timestamp += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); - ate += u->memchunk.length; - - current_block = malloc(sizeof(pa_memblock_queue)); - memset(current_block, 0, sizeof(pa_memblock_queue)); - - current_block->memblock = u->memchunk.memblock; - if (u->current == NULL) { u->current = current_block; } - if (u->last == NULL) { u->last = current_block; } - else { - u->last->next = current_block; - u->last = current_block; - } - - //pa_memblock_unref(u->memchunk.memblock); - pa_memchunk_reset(&u->memchunk); - if (ate >= u->sink->thread_info.max_request) break; - } + if (s == NULL) return; + (*s->bqPlayerPlay)->SetPlayState(s->bqPlayerPlay, SL_PLAYSTATE_STOPPED); + (*s->bqPlayerObject)->Destroy(s->bqPlayerObject); + (*s->outputMixObject)->Destroy(s->outputMixObject); + (*s->engineObject)->Destroy(s->engineObject); } static void thread_func(void *userdata) { @@ -302,30 +219,25 @@ static void thread_func(void *userdata) { pa_assert(u); - pa_log_debug("Thread starting up"); - - pa_thread_mq_install(&u->thread_mq); - - u->timestamp = pa_rtclock_now(); - for (;;) { - pa_usec_t now = 0; int ret; - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) - now = pa_rtclock_now(); - - if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) - pa_sink_process_rewind(u->sink, 0); - /* Render some data and drop it immediately */ if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - if (u->timestamp <= now) - process_render(u, now); + process_render(u->bqPlayerBufferQueue, u); + break; + } - pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp); - } else - pa_rtpoll_set_timer_disabled(u->rtpoll); + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + + for (;;) { + int ret; /* Hmm, nothing to do. Let's sleep */ if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) @@ -345,13 +257,6 @@ finish: pa_log_debug("Thread shutting down"); } -static int getenv_int(const char * env, size_t min_len){ - char * got_env = getenv(env); - int ret = 0; - if (got_env != NULL && strlen(got_env) >= min_len) ret = atoi(got_env); //"8000" is 4 symbols - return ret; -} - int pa__init(pa_module*m) { struct userdata *u = NULL; pa_sample_spec ss; @@ -367,12 +272,6 @@ int pa__init(pa_module*m) { goto fail; } - // High rate causes glitches on some devices, this is needed to prevent it - //ss.rate = 32000; - //ss.channels = 2; - //ss.format = PA_SAMPLE_S16LE; - - //OK. That will allow users to define sampling rate under his responsibility ss = m->core->default_sample_spec; map = m->core->default_channel_map; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { @@ -380,28 +279,18 @@ int pa__init(pa_module*m) { goto fail; } - //Needed. Don't touch - ss.channels = 2; + ss.channels = 2; ss.format = PA_SAMPLE_S16LE; - + m->userdata = u = pa_xnew0(struct userdata, 1); - - int forceFormat = getenv_int("PROPERTY_OUTPUT_SAMPLE_RATE", 4); //"8000" is 4 symbols - if (forceFormat >= 8000 && forceFormat <= 192000) { - ss.rate = forceFormat; - pa_log_info("Sample rate was forced to be %u\n", ss.rate); - } - + u->core = m->core; u->module = m; u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - - //Pulseaudio uses samples per sec but OpenSL ES uses samples per ms - if (pa_init_sles_player(u, ss.rate * 1000) < 0) - goto fail; - //int buff[2] = {0, 0}; - //(*u->bqPlayerBufferQueue)->Enqueue(u->bqPlayerBufferQueue, buff, 1); + + if (pa_init_sles_player(u, ss.rate) < 0) + goto fail; pa_sink_new_data_init(&data); data.driver = __FILE__; @@ -418,7 +307,7 @@ int pa__init(pa_module*m) { goto fail; } - u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); + u->sink = pa_sink_new(m->core, &data, 0); pa_sink_new_data_done(&data); if (!u->sink) { @@ -426,8 +315,7 @@ int pa__init(pa_module*m) { goto fail; } - u->sink->parent.process_msg = sink_process_msg; - u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->parent.process_msg = pa_sink_process_msg; u->sink->userdata = u; pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); @@ -443,7 +331,7 @@ int pa__init(pa_module*m) { goto fail; } - pa_sink_set_latency_range(u->sink, 0, BLOCK_USEC); + pa_sink_set_fixed_latency(u->sink, u->block_usec); pa_sink_put(u->sink); @@ -484,10 +372,10 @@ void pa__done(pa_module*m) { pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); pa_thread_free(u->thread); } - + if (u->engineObject){ - pa_destroy_sles_player(u); - } + pa_destroy_sles_player(u); + } pa_thread_mq_done(&u->thread_mq);