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.
This commit is contained in:
Tom Yan 2018-07-23 21:23:58 +08:00 committed by Fredrik Fornwall
parent f06727719f
commit fd69382e37
1 changed files with 147 additions and 259 deletions

View File

@ -42,54 +42,38 @@
#include <pulsecore/rtpoll.h>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
//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 <SLES/OpenSLES_Android.h>
#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=<name for the sink> "
"sink_properties=<properties for the sink> "
"rate=<sampling rate> ");
"sink_name=<name for the sink> "
"sink_properties=<properties for the sink> "
"rate=<sampling 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);