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:
parent
f06727719f
commit
fd69382e37
@ -42,15 +42,11 @@
|
||||
#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
|
||||
@ -66,14 +62,6 @@
|
||||
#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);
|
||||
@ -81,15 +69,11 @@ PA_MODULE_LOAD_ONCE(false);
|
||||
PA_MODULE_USAGE(
|
||||
"sink_name=<name for the sink> "
|
||||
"sink_properties=<properties for the sink> "
|
||||
"rate=<sampling rate> ");
|
||||
"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,7 +85,6 @@ struct userdata {
|
||||
pa_rtpoll *rtpoll;
|
||||
|
||||
pa_usec_t block_usec;
|
||||
pa_usec_t timestamp;
|
||||
|
||||
pa_memchunk memchunk;
|
||||
|
||||
@ -115,9 +98,6 @@ struct userdata {
|
||||
SLObjectItf bqPlayerObject;
|
||||
SLPlayItf bqPlayerPlay;
|
||||
BufferQueueItf bqPlayerBufferQueue;
|
||||
|
||||
pa_memblock_queue* current;
|
||||
pa_memblock_queue* last;
|
||||
};
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
@ -127,80 +107,52 @@ 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();
|
||||
if (!pa_thread_mq_get()) {
|
||||
pa_log_debug("Thread starting up");
|
||||
pa_thread_mq_install(&u->thread_mq);
|
||||
}
|
||||
|
||||
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_UNLIKELY(u->sink->thread_info.rewind_requested)) {
|
||||
//pa_log_debug("Rewinded\n");
|
||||
pa_sink_process_rewind(u->sink, 0);
|
||||
}
|
||||
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
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);
|
||||
CHK(slCreateEngine(&(s->engineObject), 0, NULL, 0, NULL, NULL));
|
||||
CHK((*s->engineObject)->Realize(s->engineObject, SL_BOOLEAN_FALSE));
|
||||
|
||||
result = (*s->engineObject)->GetInterface(s->engineObject, SL_IID_ENGINE, &(s->engineEngine)); checkResult(result);
|
||||
CHK((*s->engineObject)->GetInterface(s->engineObject, SL_IID_ENGINE, &(s->engineEngine)));
|
||||
|
||||
// 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);
|
||||
CHK((*s->engineEngine)->CreateOutputMix(s->engineEngine, &(s->outputMixObject), 0, NULL, NULL));
|
||||
CHK((*s->outputMixObject)->Realize(s->outputMixObject, SL_BOOLEAN_FALSE));
|
||||
|
||||
// create audio player
|
||||
|
||||
@ -210,9 +162,9 @@ static int pa_init_sles_player(struct userdata *s, SLint32 sl_rate)
|
||||
|
||||
SLDataLocator_BufferQueue locator_bufferqueue;
|
||||
locator_bufferqueue.locatorType = DATALOCATOR_BUFFERQUEUE;
|
||||
locator_bufferqueue.numBuffers = 50;
|
||||
locator_bufferqueue.numBuffers = 1;
|
||||
|
||||
if (sl_rate < SL_SAMPLINGRATE_8 || sl_rate > SL_SAMPLINGRATE_192) {
|
||||
if (sl_rate < 8000 || sl_rate > 192000) {
|
||||
pa_log("Incompatible sample rate");
|
||||
return -1;
|
||||
}
|
||||
@ -220,7 +172,7 @@ static int pa_init_sles_player(struct userdata *s, SLint32 sl_rate)
|
||||
SLDataFormat_PCM pcm;
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
pcm.numChannels = 2;
|
||||
pcm.samplesPerSec = sl_rate;
|
||||
pcm.samplesPerSec = sl_rate * 1000;
|
||||
pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||
pcm.containerSize = 16;
|
||||
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
@ -236,19 +188,24 @@ static int pa_init_sles_player(struct userdata *s, SLint32 sl_rate)
|
||||
|
||||
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);
|
||||
CHK((*s->engineEngine)->CreateAudioPlayer(s->engineEngine, &s->bqPlayerObject, &audiosrc, &audiosnk, 1, ids, flags));
|
||||
CHK((*s->bqPlayerObject)->Realize(s->bqPlayerObject, SL_BOOLEAN_FALSE));
|
||||
|
||||
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);
|
||||
CHK((*s->bqPlayerObject)->GetInterface(s->bqPlayerObject, SL_IID_PLAY, &s->bqPlayerPlay));
|
||||
CHK((*s->bqPlayerObject)->GetInterface(s->bqPlayerObject, IID_BUFFERQUEUE_USED, &s->bqPlayerBufferQueue));
|
||||
|
||||
result = (*s->bqPlayerBufferQueue)->RegisterCallback(s->bqPlayerBufferQueue, pa_sles_callback, s); checkResult(result);
|
||||
CHK((*s->bqPlayerBufferQueue)->RegisterCallback(s->bqPlayerBufferQueue, process_render, s));
|
||||
|
||||
result = (*s->bqPlayerPlay)->SetPlayState(s->bqPlayerPlay, SL_PLAYSTATE_PLAYING); checkResult(result);
|
||||
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);
|
||||
@ -257,75 +214,30 @@ static void pa_destroy_sles_player(struct userdata *s){
|
||||
(*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;
|
||||
}
|
||||
}
|
||||
|
||||
static void thread_func(void *userdata) {
|
||||
struct userdata *u = 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.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)
|
||||
if (pa_init_sles_player(u, ss.rate) < 0)
|
||||
goto fail;
|
||||
//int buff[2] = {0, 0};
|
||||
//(*u->bqPlayerBufferQueue)->Enqueue(u->bqPlayerBufferQueue, buff, 1);
|
||||
|
||||
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user