mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 15:45:42 +00:00
1761 lines
56 KiB
C
1761 lines
56 KiB
C
/*
|
|
* Copyright © 2012 Mozilla Foundation
|
|
*
|
|
* This program is made available under an ISC-style license. See the
|
|
* accompanying file LICENSE for details.
|
|
*/
|
|
#undef NDEBUG
|
|
#include <assert.h>
|
|
#include <dlfcn.h>
|
|
#include <stdlib.h>
|
|
#include <pthread.h>
|
|
#include <errno.h>
|
|
#include <SLES/OpenSLES.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#if defined(__ANDROID__)
|
|
#include <dlfcn.h>
|
|
#include <sys/system_properties.h>
|
|
#include "android/sles_definitions.h"
|
|
#include <SLES/OpenSLES_Android.h>
|
|
#include <android/log.h>
|
|
#include <android/api-level.h>
|
|
#endif
|
|
#include "cubeb/cubeb.h"
|
|
#include "cubeb-internal.h"
|
|
#include "cubeb_resampler.h"
|
|
#include "cubeb-sles.h"
|
|
#include "cubeb_array_queue.h"
|
|
#include "android/cubeb-output-latency.h"
|
|
#include "cubeb_android.h"
|
|
|
|
#if defined(__ANDROID__)
|
|
#ifdef LOG
|
|
#undef LOG
|
|
#endif
|
|
//#define LOGGING_ENABLED
|
|
#ifdef LOGGING_ENABLED
|
|
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args)
|
|
#else
|
|
#define LOG(...)
|
|
#endif
|
|
|
|
//#define TIMESTAMP_ENABLED
|
|
#ifdef TIMESTAMP_ENABLED
|
|
#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
|
|
#define LOG_TS(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)" , ## args)
|
|
#define TIMESTAMP(msg) do { \
|
|
struct timeval timestamp; \
|
|
int ts_ret = gettimeofday(×tamp, NULL); \
|
|
if (ts_ret == 0) { \
|
|
LOG_TS("%lld: %s (%s %s:%d)", timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, __FUNCTION__, FILENAME, __LINE__);\
|
|
} else { \
|
|
LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, __LINE__);\
|
|
} \
|
|
} while(0)
|
|
#else
|
|
#define TIMESTAMP(...)
|
|
#endif
|
|
|
|
#define ANDROID_VERSION_GINGERBREAD_MR1 10
|
|
#define ANDROID_VERSION_JELLY_BEAN 18
|
|
#define ANDROID_VERSION_LOLLIPOP 21
|
|
#define ANDROID_VERSION_MARSHMALLOW 23
|
|
#define ANDROID_VERSION_N_MR1 25
|
|
#endif
|
|
|
|
#define DEFAULT_SAMPLE_RATE 48000
|
|
#define DEFAULT_NUM_OF_FRAMES 480
|
|
|
|
static struct cubeb_ops const opensl_ops;
|
|
|
|
struct cubeb {
|
|
struct cubeb_ops const * ops;
|
|
void * lib;
|
|
SLInterfaceID SL_IID_BUFFERQUEUE;
|
|
SLInterfaceID SL_IID_PLAY;
|
|
#if defined(__ANDROID__)
|
|
SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
|
|
SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
|
|
#endif
|
|
SLInterfaceID SL_IID_VOLUME;
|
|
SLInterfaceID SL_IID_RECORD;
|
|
SLObjectItf engObj;
|
|
SLEngineItf eng;
|
|
SLObjectItf outmixObj;
|
|
output_latency_function * p_output_latency_function;
|
|
};
|
|
|
|
#define NELEMS(A) (sizeof(A) / sizeof A[0])
|
|
#define NBUFS 2
|
|
|
|
struct cubeb_stream {
|
|
/* Note: Must match cubeb_stream layout in cubeb.c. */
|
|
cubeb * context;
|
|
void * user_ptr;
|
|
/**/
|
|
pthread_mutex_t mutex;
|
|
SLObjectItf playerObj;
|
|
SLPlayItf play;
|
|
SLBufferQueueItf bufq;
|
|
SLVolumeItf volume;
|
|
void ** queuebuf;
|
|
uint32_t queuebuf_capacity;
|
|
int queuebuf_idx;
|
|
long queuebuf_len;
|
|
long bytespersec;
|
|
long framesize;
|
|
/* Total number of played frames.
|
|
* Synchronized by stream::mutex lock. */
|
|
long written;
|
|
/* Flag indicating draining. Synchronized
|
|
* by stream::mutex lock. */
|
|
int draining;
|
|
/* Flags to determine in/out.*/
|
|
uint32_t input_enabled;
|
|
uint32_t output_enabled;
|
|
/* Recorder abstract object. */
|
|
SLObjectItf recorderObj;
|
|
/* Recorder Itf for input capture. */
|
|
SLRecordItf recorderItf;
|
|
/* Buffer queue for input capture. */
|
|
SLAndroidSimpleBufferQueueItf recorderBufferQueueItf;
|
|
/* Store input buffers. */
|
|
void ** input_buffer_array;
|
|
/* The capacity of the array.
|
|
* On capture only can be small (4).
|
|
* On full duplex is calculated to
|
|
* store 1 sec of data buffers. */
|
|
uint32_t input_array_capacity;
|
|
/* Current filled index of input buffer array.
|
|
* It is initiated to -1 indicating buffering
|
|
* have not started yet. */
|
|
int input_buffer_index;
|
|
/* Length of input buffer.*/
|
|
uint32_t input_buffer_length;
|
|
/* Input frame size */
|
|
uint32_t input_frame_size;
|
|
/* Device sampling rate. If user rate is not
|
|
* accepted an compatible rate is set. If it is
|
|
* accepted this is equal to params.rate. */
|
|
uint32_t input_device_rate;
|
|
/* Exchange input buffers between input
|
|
* and full duplex threads. */
|
|
array_queue * input_queue;
|
|
/* Silent input buffer used on full duplex. */
|
|
void * input_silent_buffer;
|
|
/* Number of input frames from the start of the stream*/
|
|
uint32_t input_total_frames;
|
|
/* Flag to stop the execution of user callback and
|
|
* close all working threads. Synchronized by
|
|
* stream::mutex lock. */
|
|
uint32_t shutdown;
|
|
/* Store user callback. */
|
|
cubeb_data_callback data_callback;
|
|
/* Store state callback. */
|
|
cubeb_state_callback state_callback;
|
|
|
|
cubeb_resampler * resampler;
|
|
unsigned int user_output_rate;
|
|
unsigned int output_configured_rate;
|
|
unsigned int buffer_size_frames;
|
|
// Audio output latency used in cubeb_stream_get_position().
|
|
unsigned int output_latency_ms;
|
|
int64_t lastPosition;
|
|
int64_t lastPositionTimeStamp;
|
|
int64_t lastCompensativePosition;
|
|
int voice_input;
|
|
int voice_output;
|
|
};
|
|
|
|
/* Forward declaration. */
|
|
static int opensl_stop_player(cubeb_stream * stm);
|
|
static int opensl_stop_recorder(cubeb_stream * stm);
|
|
|
|
static int
|
|
opensl_get_draining(cubeb_stream * stm)
|
|
{
|
|
#ifdef DEBUG
|
|
int r = pthread_mutex_trylock(&stm->mutex);
|
|
assert((r == EDEADLK || r == EBUSY) && "get_draining: mutex should be locked but it's not.");
|
|
#endif
|
|
return stm->draining;
|
|
}
|
|
|
|
static void
|
|
opensl_set_draining(cubeb_stream * stm, int value)
|
|
{
|
|
#ifdef DEBUG
|
|
int r = pthread_mutex_trylock(&stm->mutex);
|
|
LOG("set draining try r = %d", r);
|
|
assert((r == EDEADLK || r == EBUSY) && "set_draining: mutex should be locked but it's not.");
|
|
#endif
|
|
assert(value == 0 || value == 1);
|
|
stm->draining = value;
|
|
}
|
|
|
|
static void
|
|
opensl_notify_drained(cubeb_stream * stm)
|
|
{
|
|
assert(stm);
|
|
int r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
int draining = opensl_get_draining(stm);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
if (draining) {
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
|
if (stm->play) {
|
|
LOG("stop player in play_callback");
|
|
r = opensl_stop_player(stm);
|
|
assert(r == CUBEB_OK);
|
|
}
|
|
if (stm->recorderItf) {
|
|
r = opensl_stop_recorder(stm);
|
|
assert(r == CUBEB_OK);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
opensl_get_shutdown(cubeb_stream * stm)
|
|
{
|
|
#ifdef DEBUG
|
|
int r = pthread_mutex_trylock(&stm->mutex);
|
|
assert((r == EDEADLK || r == EBUSY) && "get_shutdown: mutex should be locked but it's not.");
|
|
#endif
|
|
return stm->shutdown;
|
|
}
|
|
|
|
static void
|
|
opensl_set_shutdown(cubeb_stream * stm, uint32_t value)
|
|
{
|
|
#ifdef DEBUG
|
|
int r = pthread_mutex_trylock(&stm->mutex);
|
|
LOG("set shutdown try r = %d", r);
|
|
assert((r == EDEADLK || r == EBUSY) && "set_shutdown: mutex should be locked but it's not.");
|
|
#endif
|
|
assert(value == 0 || value == 1);
|
|
stm->shutdown = value;
|
|
}
|
|
|
|
static void
|
|
play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
|
|
{
|
|
cubeb_stream * stm = user_ptr;
|
|
assert(stm);
|
|
switch (event) {
|
|
case SL_PLAYEVENT_HEADATMARKER:
|
|
opensl_notify_drained(stm);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
recorder_marker_callback (SLRecordItf caller, void * pContext, SLuint32 event)
|
|
{
|
|
cubeb_stream * stm = pContext;
|
|
assert(stm);
|
|
|
|
if (event == SL_RECORDEVENT_HEADATMARKER) {
|
|
int r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
int draining = opensl_get_draining(stm);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
if (draining) {
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
|
if (stm->recorderItf) {
|
|
r = opensl_stop_recorder(stm);
|
|
assert(r == CUBEB_OK);
|
|
}
|
|
if (stm->play) {
|
|
r = opensl_stop_player(stm);
|
|
assert(r == CUBEB_OK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
|
|
{
|
|
cubeb_stream * stm = user_ptr;
|
|
assert(stm);
|
|
SLBufferQueueState state;
|
|
SLresult res;
|
|
long written = 0;
|
|
|
|
res = (*stm->bufq)->GetState(stm->bufq, &state);
|
|
assert(res == SL_RESULT_SUCCESS);
|
|
|
|
if (state.count > 1) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *buf = stm->queuebuf[stm->queuebuf_idx];
|
|
written = 0;
|
|
int r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
int draining = opensl_get_draining(stm);
|
|
uint32_t shutdown = opensl_get_shutdown(stm);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
if (!draining && !shutdown) {
|
|
written = cubeb_resampler_fill(stm->resampler,
|
|
NULL, NULL,
|
|
buf, stm->queuebuf_len / stm->framesize);
|
|
LOG("bufferqueue_callback: resampler fill returned %ld frames", written);
|
|
if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
|
|
r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
opensl_set_shutdown(stm, 1);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
opensl_stop_player(stm);
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Keep sending silent data even in draining mode to prevent the audio
|
|
// back-end from being stopped automatically by OpenSL/ES.
|
|
assert(stm->queuebuf_len >= written * stm->framesize);
|
|
memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize);
|
|
res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
|
|
assert(res == SL_RESULT_SUCCESS);
|
|
stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
|
|
|
|
if (written > 0) {
|
|
pthread_mutex_lock(&stm->mutex);
|
|
stm->written += written;
|
|
pthread_mutex_unlock(&stm->mutex);
|
|
}
|
|
|
|
if (!draining && written * stm->framesize < stm->queuebuf_len) {
|
|
LOG("bufferqueue_callback draining");
|
|
r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
|
|
opensl_set_draining(stm, 1);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
if (written_duration == 0) {
|
|
// since we didn't write any sample, it's not possible to reach the marker
|
|
// time and trigger the callback. We should initiative notify drained.
|
|
opensl_notify_drained(stm);
|
|
} else {
|
|
// Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
|
|
// to make sure all the data has been processed.
|
|
(*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int
|
|
opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer)
|
|
{
|
|
assert(stm);
|
|
|
|
int current_index = stm->input_buffer_index;
|
|
void * last_buffer = NULL;
|
|
|
|
if (current_index < 0) {
|
|
// This is the first enqueue
|
|
current_index = 0;
|
|
} else {
|
|
// The current index hold the last filled buffer get it before advance index.
|
|
last_buffer = stm->input_buffer_array[current_index];
|
|
// Advance to get next available buffer
|
|
current_index = (current_index + 1) % stm->input_array_capacity;
|
|
}
|
|
// enqueue next empty buffer to be filled by the recorder
|
|
SLresult res = (*stm->recorderBufferQueueItf)->Enqueue(stm->recorderBufferQueueItf,
|
|
stm->input_buffer_array[current_index],
|
|
stm->input_buffer_length);
|
|
if (res != SL_RESULT_SUCCESS ) {
|
|
LOG("Enqueue recorder failed. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
// All good, update buffer and index.
|
|
stm->input_buffer_index = current_index;
|
|
if (last_filled_buffer) {
|
|
*last_filled_buffer = last_buffer;
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
// input data callback
|
|
void recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
|
|
{
|
|
assert(context);
|
|
cubeb_stream * stm = context;
|
|
assert(stm->recorderBufferQueueItf);
|
|
|
|
int r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
uint32_t shutdown = opensl_get_shutdown(stm);
|
|
int draining = opensl_get_draining(stm);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
if (shutdown || draining) {
|
|
// According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf
|
|
// page 184, on transition to the SL_RECORDSTATE_STOPPED state,
|
|
// the application should continue to enqueue buffers onto the queue
|
|
// to retrieve the residual recorded data in the system.
|
|
r = opensl_enqueue_recorder(stm, NULL);
|
|
assert(r == CUBEB_OK);
|
|
return;
|
|
}
|
|
|
|
// Enqueue next available buffer and get the last filled buffer.
|
|
void * input_buffer = NULL;
|
|
r = opensl_enqueue_recorder(stm, &input_buffer);
|
|
assert(r == CUBEB_OK);
|
|
assert(input_buffer);
|
|
// Fill resampler with last input
|
|
long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
|
|
long got = cubeb_resampler_fill(stm->resampler,
|
|
input_buffer,
|
|
&input_frame_count,
|
|
NULL,
|
|
0);
|
|
// Error case
|
|
if (got < 0 || got > input_frame_count) {
|
|
r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
opensl_set_shutdown(stm, 1);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
r = opensl_stop_recorder(stm);
|
|
assert(r == CUBEB_OK);
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
|
}
|
|
|
|
// Advance total stream frames
|
|
stm->input_total_frames += got;
|
|
|
|
if (got < input_frame_count) {
|
|
r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
opensl_set_draining(stm, 1);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
int64_t duration = INT64_C(1000) * stm->input_total_frames / stm->input_device_rate;
|
|
(*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
|
|
{
|
|
assert(context);
|
|
cubeb_stream * stm = context;
|
|
assert(stm->recorderBufferQueueItf);
|
|
|
|
int r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
int draining = opensl_get_draining(stm);
|
|
uint32_t shutdown = opensl_get_shutdown(stm);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
if (shutdown || draining) {
|
|
/* On draining and shutdown the recorder should have been stoped from
|
|
* the one set the flags. Accordint to the doc, on transition to
|
|
* the SL_RECORDSTATE_STOPPED state, the application should
|
|
* continue to enqueue buffers onto the queue to retrieve the residual
|
|
* recorded data in the system. */
|
|
LOG("Input shutdown %d or drain %d", shutdown, draining);
|
|
int r = opensl_enqueue_recorder(stm, NULL);
|
|
assert(r == CUBEB_OK);
|
|
return;
|
|
}
|
|
|
|
// Enqueue next available buffer and get the last filled buffer.
|
|
void * input_buffer = NULL;
|
|
r = opensl_enqueue_recorder(stm, &input_buffer);
|
|
assert(r == CUBEB_OK);
|
|
assert(input_buffer);
|
|
|
|
assert(stm->input_queue);
|
|
r = array_queue_push(stm->input_queue, input_buffer);
|
|
if (r == -1) {
|
|
LOG("Input queue is full, drop input ...");
|
|
return;
|
|
}
|
|
|
|
LOG("Input pushed in the queue, input array %zu",
|
|
array_queue_get_size(stm->input_queue));
|
|
}
|
|
|
|
static void
|
|
player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
|
|
{
|
|
TIMESTAMP("ENTER");
|
|
cubeb_stream * stm = user_ptr;
|
|
assert(stm);
|
|
SLresult res;
|
|
|
|
int r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
int draining = opensl_get_draining(stm);
|
|
uint32_t shutdown = opensl_get_shutdown(stm);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
// Get output
|
|
void * output_buffer = NULL;
|
|
r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
output_buffer = stm->queuebuf[stm->queuebuf_idx];
|
|
// Advance the output buffer queue index
|
|
stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
if (shutdown || draining) {
|
|
LOG("Shutdown/draining, send silent");
|
|
// Set silent on buffer
|
|
memset(output_buffer, 0, stm->queuebuf_len);
|
|
|
|
// Enqueue data in player buffer queue
|
|
res = (*stm->bufq)->Enqueue(stm->bufq,
|
|
output_buffer,
|
|
stm->queuebuf_len);
|
|
assert(res == SL_RESULT_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
// Get input.
|
|
void * input_buffer = array_queue_pop(stm->input_queue);
|
|
long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
|
|
long frames_needed = stm->queuebuf_len / stm->framesize;
|
|
if (!input_buffer) {
|
|
LOG("Input hole set silent input buffer");
|
|
input_buffer = stm->input_silent_buffer;
|
|
}
|
|
|
|
long written = 0;
|
|
// Trigger user callback through resampler
|
|
written = cubeb_resampler_fill(stm->resampler,
|
|
input_buffer,
|
|
&input_frame_count,
|
|
output_buffer,
|
|
frames_needed);
|
|
|
|
LOG("Fill: written %ld, frames_needed %ld, input array size %zu",
|
|
written, frames_needed, array_queue_get_size(stm->input_queue));
|
|
|
|
if (written < 0 || written > frames_needed) {
|
|
// Error case
|
|
r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
opensl_set_shutdown(stm, 1);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
opensl_stop_player(stm);
|
|
opensl_stop_recorder(stm);
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
|
memset(output_buffer, 0, stm->queuebuf_len);
|
|
|
|
// Enqueue data in player buffer queue
|
|
res = (*stm->bufq)->Enqueue(stm->bufq,
|
|
output_buffer,
|
|
stm->queuebuf_len);
|
|
assert(res == SL_RESULT_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
// Advance total out written frames counter
|
|
r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
stm->written += written;
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
if ( written < frames_needed) {
|
|
r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
|
|
opensl_set_draining(stm, 1);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
// Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
|
|
// to make sure all the data has been processed.
|
|
(*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
|
|
}
|
|
|
|
// Keep sending silent data even in draining mode to prevent the audio
|
|
// back-end from being stopped automatically by OpenSL/ES.
|
|
memset((uint8_t *)output_buffer + written * stm->framesize, 0,
|
|
stm->queuebuf_len - written * stm->framesize);
|
|
|
|
// Enqueue data in player buffer queue
|
|
res = (*stm->bufq)->Enqueue(stm->bufq,
|
|
output_buffer,
|
|
stm->queuebuf_len);
|
|
assert(res == SL_RESULT_SUCCESS);
|
|
TIMESTAMP("EXIT");
|
|
}
|
|
|
|
static void opensl_destroy(cubeb * ctx);
|
|
|
|
#if defined(__ANDROID__)
|
|
#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
|
|
typedef int (system_property_get)(const char*, char*);
|
|
|
|
static int
|
|
wrap_system_property_get(const char* name, char* value)
|
|
{
|
|
void* libc = dlopen("libc.so", RTLD_LAZY);
|
|
if (!libc) {
|
|
LOG("Failed to open libc.so");
|
|
return -1;
|
|
}
|
|
system_property_get* func = (system_property_get*)
|
|
dlsym(libc, "__system_property_get");
|
|
int ret = -1;
|
|
if (func) {
|
|
ret = func(name, value);
|
|
}
|
|
dlclose(libc);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
get_android_version(void)
|
|
{
|
|
char version_string[PROP_VALUE_MAX];
|
|
|
|
memset(version_string, 0, PROP_VALUE_MAX);
|
|
|
|
#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
|
|
int len = wrap_system_property_get("ro.build.version.sdk", version_string);
|
|
#else
|
|
int len = __system_property_get("ro.build.version.sdk", version_string);
|
|
#endif
|
|
if (len <= 0) {
|
|
LOG("Failed to get Android version!\n");
|
|
return len;
|
|
}
|
|
|
|
int version = (int)strtol(version_string, NULL, 10);
|
|
LOG("Android version %d", version);
|
|
return version;
|
|
}
|
|
#endif
|
|
|
|
/*static*/ int
|
|
opensl_init(cubeb ** context, char const * context_name)
|
|
{
|
|
cubeb * ctx;
|
|
|
|
#if defined(__ANDROID__)
|
|
int android_version = get_android_version();
|
|
if (android_version > 0 && android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
|
|
// Don't even attempt to run on Gingerbread and lower
|
|
return CUBEB_ERROR;
|
|
}
|
|
#endif
|
|
|
|
*context = NULL;
|
|
|
|
ctx = calloc(1, sizeof(*ctx));
|
|
assert(ctx);
|
|
|
|
ctx->ops = &opensl_ops;
|
|
|
|
ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY);
|
|
if (!ctx->lib) {
|
|
free(ctx);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
typedef SLresult (*slCreateEngine_t)(SLObjectItf *,
|
|
SLuint32,
|
|
const SLEngineOption *,
|
|
SLuint32,
|
|
const SLInterfaceID *,
|
|
const SLboolean *);
|
|
slCreateEngine_t f_slCreateEngine =
|
|
(slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
|
|
SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
|
|
SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
|
|
ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME");
|
|
ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
|
|
#if defined(__ANDROID__)
|
|
ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
|
|
ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
|
|
#endif
|
|
ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
|
|
ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD");
|
|
|
|
if (!f_slCreateEngine ||
|
|
!SL_IID_ENGINE ||
|
|
!SL_IID_OUTPUTMIX ||
|
|
!ctx->SL_IID_BUFFERQUEUE ||
|
|
#if defined(__ANDROID__)
|
|
!ctx->SL_IID_ANDROIDCONFIGURATION ||
|
|
!ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE ||
|
|
#endif
|
|
!ctx->SL_IID_PLAY ||
|
|
!ctx->SL_IID_RECORD) {
|
|
opensl_destroy(ctx);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}};
|
|
|
|
SLresult res;
|
|
res = cubeb_get_sles_engine(&ctx->engObj, 1, opt, 0, NULL, NULL);
|
|
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
opensl_destroy(ctx);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
res = cubeb_realize_sles_engine(ctx->engObj);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
opensl_destroy(ctx);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
opensl_destroy(ctx);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX};
|
|
const SLboolean reqom[] = {SL_BOOLEAN_TRUE};
|
|
res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
opensl_destroy(ctx);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
opensl_destroy(ctx);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
ctx->p_output_latency_function = cubeb_output_latency_load_method(android_version);
|
|
if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) {
|
|
LOG("Warning: output latency is not available, cubeb_stream_get_position() is not supported");
|
|
}
|
|
|
|
*context = ctx;
|
|
|
|
LOG("Cubeb init (%p) success", ctx);
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static char const *
|
|
opensl_get_backend_id(cubeb * ctx)
|
|
{
|
|
return "opensl";
|
|
}
|
|
|
|
static int
|
|
opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
|
{
|
|
assert(ctx && max_channels);
|
|
/* The android mixer handles up to two channels, see
|
|
http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
|
|
*max_channels = 2;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static void
|
|
opensl_destroy(cubeb * ctx)
|
|
{
|
|
if (ctx->outmixObj)
|
|
(*ctx->outmixObj)->Destroy(ctx->outmixObj);
|
|
if (ctx->engObj)
|
|
cubeb_destroy_sles_engine(&ctx->engObj);
|
|
dlclose(ctx->lib);
|
|
if (ctx->p_output_latency_function)
|
|
cubeb_output_latency_unload_method(ctx->p_output_latency_function);
|
|
free(ctx);
|
|
}
|
|
|
|
static void opensl_stream_destroy(cubeb_stream * stm);
|
|
|
|
#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
|
|
static int
|
|
opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format, cubeb_stream_params * params)
|
|
{
|
|
assert(format);
|
|
assert(params);
|
|
|
|
format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
|
format->numChannels = params->channels;
|
|
// sampleRate is in milliHertz
|
|
format->sampleRate = params->rate * 1000;
|
|
format->channelMask = params->channels == 1 ?
|
|
SL_SPEAKER_FRONT_CENTER :
|
|
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
|
|
|
switch (params->format) {
|
|
case CUBEB_SAMPLE_S16LE:
|
|
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
|
|
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
|
|
format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
|
|
format->endianness = SL_BYTEORDER_LITTLEENDIAN;
|
|
break;
|
|
case CUBEB_SAMPLE_S16BE:
|
|
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
|
|
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
|
|
format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
|
|
format->endianness = SL_BYTEORDER_BIGENDIAN;
|
|
break;
|
|
case CUBEB_SAMPLE_FLOAT32LE:
|
|
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
|
|
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
|
|
format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
|
format->endianness = SL_BYTEORDER_LITTLEENDIAN;
|
|
break;
|
|
case CUBEB_SAMPLE_FLOAT32BE:
|
|
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
|
|
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
|
|
format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
|
format->endianness = SL_BYTEORDER_BIGENDIAN;
|
|
break;
|
|
default:
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params)
|
|
{
|
|
assert(format);
|
|
assert(params);
|
|
|
|
format->formatType = SL_DATAFORMAT_PCM;
|
|
format->numChannels = params->channels;
|
|
// samplesPerSec is in milliHertz
|
|
format->samplesPerSec = params->rate * 1000;
|
|
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
|
|
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
|
|
format->channelMask = params->channels == 1 ?
|
|
SL_SPEAKER_FRONT_CENTER :
|
|
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
|
|
|
switch (params->format) {
|
|
case CUBEB_SAMPLE_S16LE:
|
|
format->endianness = SL_BYTEORDER_LITTLEENDIAN;
|
|
break;
|
|
case CUBEB_SAMPLE_S16BE:
|
|
format->endianness = SL_BYTEORDER_BIGENDIAN;
|
|
break;
|
|
default:
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
|
|
{
|
|
assert(stm);
|
|
assert(params);
|
|
|
|
SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut;
|
|
lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
|
lDataLocatorOut.numBuffers = NBUFS;
|
|
|
|
SLDataFormat_PCM lDataFormat;
|
|
int r = opensl_set_format(&lDataFormat, params);
|
|
if (r != CUBEB_OK) {
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
/* For now set device rate to params rate. */
|
|
stm->input_device_rate = params->rate;
|
|
|
|
SLDataSink lDataSink;
|
|
lDataSink.pLocator = &lDataLocatorOut;
|
|
lDataSink.pFormat = &lDataFormat;
|
|
|
|
SLDataLocator_IODevice lDataLocatorIn;
|
|
lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE;
|
|
lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT;
|
|
lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
|
|
lDataLocatorIn.device = NULL;
|
|
|
|
SLDataSource lDataSource;
|
|
lDataSource.pLocator = &lDataLocatorIn;
|
|
lDataSource.pFormat = NULL;
|
|
|
|
const SLInterfaceID lSoundRecorderIIDs[] = { stm->context->SL_IID_RECORD,
|
|
stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
|
stm->context->SL_IID_ANDROIDCONFIGURATION };
|
|
|
|
const SLboolean lSoundRecorderReqs[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
|
|
// create the audio recorder abstract object
|
|
SLresult res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng,
|
|
&stm->recorderObj,
|
|
&lDataSource,
|
|
&lDataSink,
|
|
NELEMS(lSoundRecorderIIDs),
|
|
lSoundRecorderIIDs,
|
|
lSoundRecorderReqs);
|
|
// Sample rate not supported. Try again with default sample rate!
|
|
if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
|
|
if (stm->output_enabled && stm->output_configured_rate != 0) {
|
|
// Set the same with the player. Since there is no
|
|
// api for input device this is a safe choice.
|
|
stm->input_device_rate = stm->output_configured_rate;
|
|
} else {
|
|
// The output preferred rate is used for an input only scenario.
|
|
// The default rate expected to be supported from all android devices.
|
|
stm->input_device_rate = DEFAULT_SAMPLE_RATE;
|
|
}
|
|
lDataFormat.samplesPerSec = stm->input_device_rate * 1000;
|
|
res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng,
|
|
&stm->recorderObj,
|
|
&lDataSource,
|
|
&lDataSink,
|
|
NELEMS(lSoundRecorderIIDs),
|
|
lSoundRecorderIIDs,
|
|
lSoundRecorderReqs);
|
|
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to create recorder. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) {
|
|
SLAndroidConfigurationItf recorderConfig;
|
|
res = (*stm->recorderObj)
|
|
->GetInterface(stm->recorderObj,
|
|
stm->context->SL_IID_ANDROIDCONFIGURATION,
|
|
&recorderConfig);
|
|
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to get the android configuration interface for recorder. Error "
|
|
"code: %lu",
|
|
res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
// Voice recognition is the lowest latency, according to the docs. Camcorder
|
|
// uses a microphone that is in the same direction as the camera.
|
|
SLint32 streamType = stm->voice_input ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
|
|
: SL_ANDROID_RECORDING_PRESET_CAMCORDER;
|
|
|
|
res = (*recorderConfig)
|
|
->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET,
|
|
&streamType, sizeof(SLint32));
|
|
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to set the android configuration to VOICE for the recorder. "
|
|
"Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
}
|
|
// realize the audio recorder
|
|
res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to realize recorder. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
// get the record interface
|
|
res = (*stm->recorderObj)->GetInterface(stm->recorderObj,
|
|
stm->context->SL_IID_RECORD,
|
|
&stm->recorderItf);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to get recorder interface. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
res = (*stm->recorderItf)->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to register recorder marker callback. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
(*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0);
|
|
|
|
res = (*stm->recorderItf)->SetCallbackEventsMask(stm->recorderItf, (SLuint32)SL_RECORDEVENT_HEADATMARKER);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to set headatmarker event mask. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
// get the simple android buffer queue interface
|
|
res = (*stm->recorderObj)->GetInterface(stm->recorderObj,
|
|
stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
|
&stm->recorderBufferQueueItf);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to get recorder (android) buffer queue interface. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
// register callback on record (input) buffer queue
|
|
slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback;
|
|
if (stm->output_enabled) {
|
|
// Register full duplex callback instead.
|
|
rec_callback = recorder_fullduplex_callback;
|
|
}
|
|
res = (*stm->recorderBufferQueueItf)->RegisterCallback(stm->recorderBufferQueueItf,
|
|
rec_callback,
|
|
stm);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to register recorder buffer queue callback. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
// Calculate length of input buffer according to requested latency
|
|
stm->input_frame_size = params->channels * sizeof(int16_t);
|
|
stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames);
|
|
|
|
// Calculate the capacity of input array
|
|
stm->input_array_capacity = NBUFS;
|
|
if (stm->output_enabled) {
|
|
// Full duplex, update capacity to hold 1 sec of data
|
|
stm->input_array_capacity = 1 * stm->input_device_rate / stm->input_buffer_length;
|
|
}
|
|
// Allocate input array
|
|
stm->input_buffer_array = (void**)calloc(1, sizeof(void*)*stm->input_array_capacity);
|
|
// Buffering has not started yet.
|
|
stm->input_buffer_index = -1;
|
|
// Prepare input buffers
|
|
for(uint32_t i = 0; i < stm->input_array_capacity; ++i) {
|
|
stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length);
|
|
}
|
|
|
|
// On full duplex allocate input queue and silent buffer
|
|
if (stm->output_enabled) {
|
|
stm->input_queue = array_queue_create(stm->input_array_capacity);
|
|
assert(stm->input_queue);
|
|
stm->input_silent_buffer = calloc(1, stm->input_buffer_length);
|
|
assert(stm->input_silent_buffer);
|
|
}
|
|
|
|
// Enqueue buffer to start rolling once recorder started
|
|
r = opensl_enqueue_recorder(stm, NULL);
|
|
if (r != CUBEB_OK) {
|
|
return r;
|
|
}
|
|
|
|
LOG("Cubeb stream init recorder success");
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
|
|
assert(stm);
|
|
assert(params);
|
|
|
|
stm->user_output_rate = params->rate;
|
|
if(params->format == CUBEB_SAMPLE_S16NE || params->format == CUBEB_SAMPLE_S16BE) {
|
|
stm->framesize = params->channels * sizeof(int16_t);
|
|
} else if(params->format == CUBEB_SAMPLE_FLOAT32NE || params->format == CUBEB_SAMPLE_FLOAT32BE) {
|
|
stm->framesize = params->channels * sizeof(float);
|
|
}
|
|
stm->lastPosition = -1;
|
|
stm->lastPositionTimeStamp = 0;
|
|
stm->lastCompensativePosition = -1;
|
|
|
|
void* format = NULL;
|
|
SLuint32* format_sample_rate = NULL;
|
|
|
|
#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
|
|
SLAndroidDataFormat_PCM_EX pcm_ext_format;
|
|
if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) {
|
|
if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) {
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
format = &pcm_ext_format;
|
|
format_sample_rate = &pcm_ext_format.sampleRate;
|
|
}
|
|
#endif
|
|
|
|
SLDataFormat_PCM pcm_format;
|
|
if(!format) {
|
|
if(opensl_set_format(&pcm_format, params) != CUBEB_OK) {
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
format = &pcm_format;
|
|
format_sample_rate = &pcm_format.samplesPerSec;
|
|
}
|
|
|
|
SLDataLocator_BufferQueue loc_bufq;
|
|
loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
|
|
loc_bufq.numBuffers = NBUFS;
|
|
SLDataSource source;
|
|
source.pLocator = &loc_bufq;
|
|
source.pFormat = format;
|
|
|
|
SLDataLocator_OutputMix loc_outmix;
|
|
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
|
|
loc_outmix.outputMix = stm->context->outmixObj;
|
|
SLDataSink sink;
|
|
sink.pLocator = &loc_outmix;
|
|
sink.pFormat = NULL;
|
|
|
|
#if defined(__ANDROID__)
|
|
const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE,
|
|
stm->context->SL_IID_VOLUME,
|
|
stm->context->SL_IID_ANDROIDCONFIGURATION};
|
|
const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
|
#else
|
|
const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME};
|
|
const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
|
#endif
|
|
assert(NELEMS(ids) == NELEMS(req));
|
|
|
|
uint32_t preferred_sampling_rate = stm->user_output_rate;
|
|
SLresult res = SL_RESULT_CONTENT_UNSUPPORTED;
|
|
if (preferred_sampling_rate) {
|
|
res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng,
|
|
&stm->playerObj,
|
|
&source,
|
|
&sink,
|
|
NELEMS(ids),
|
|
ids,
|
|
req);
|
|
}
|
|
|
|
// Sample rate not supported? Try again with primary sample rate!
|
|
if (res == SL_RESULT_CONTENT_UNSUPPORTED &&
|
|
preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
|
|
preferred_sampling_rate = DEFAULT_SAMPLE_RATE;
|
|
*format_sample_rate = preferred_sampling_rate * 1000;
|
|
res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng,
|
|
&stm->playerObj,
|
|
&source,
|
|
&sink,
|
|
NELEMS(ids),
|
|
ids,
|
|
req);
|
|
}
|
|
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to create audio player. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
stm->output_configured_rate = preferred_sampling_rate;
|
|
stm->bytespersec = stm->output_configured_rate * stm->framesize;
|
|
stm->queuebuf_len = stm->framesize * stm->buffer_size_frames;
|
|
|
|
// Calculate the capacity of input array
|
|
stm->queuebuf_capacity = NBUFS;
|
|
if (stm->output_enabled) {
|
|
// Full duplex, update capacity to hold 1 sec of data
|
|
stm->queuebuf_capacity = 1 * stm->output_configured_rate / stm->queuebuf_len;
|
|
}
|
|
// Allocate input array
|
|
stm->queuebuf = (void**)calloc(1, sizeof(void*) * stm->queuebuf_capacity);
|
|
for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
|
|
stm->queuebuf[i] = calloc(1, stm->queuebuf_len);
|
|
assert(stm->queuebuf[i]);
|
|
}
|
|
|
|
SLAndroidConfigurationItf playerConfig = NULL;
|
|
|
|
if (get_android_version() >= ANDROID_VERSION_N_MR1) {
|
|
res = (*stm->playerObj)
|
|
->GetInterface(stm->playerObj,
|
|
stm->context->SL_IID_ANDROIDCONFIGURATION,
|
|
&playerConfig);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to get Android configuration interface. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
|
|
if (stm->voice_output) {
|
|
streamType = SL_ANDROID_STREAM_VOICE;
|
|
}
|
|
res = (*playerConfig)->SetConfiguration(playerConfig,
|
|
SL_ANDROID_KEY_STREAM_TYPE,
|
|
&streamType,
|
|
sizeof(streamType));
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to set Android configuration to %d Error code: %lu",
|
|
streamType, res);
|
|
}
|
|
|
|
SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY;
|
|
if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) {
|
|
performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
|
|
}
|
|
|
|
res = (*playerConfig)->SetConfiguration(playerConfig,
|
|
SL_ANDROID_KEY_PERFORMANCE_MODE,
|
|
&performanceMode,
|
|
sizeof(performanceMode));
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to set Android performance mode to %d Error code: %lu. This is"
|
|
" not fatal", performanceMode, res);
|
|
}
|
|
}
|
|
|
|
res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to realize player object. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
// There are two ways of getting the audio output latency:
|
|
// - a configuration value, only available on some devices (notably devices
|
|
// running FireOS)
|
|
// - A Java method, that we call using JNI.
|
|
//
|
|
// The first method is prefered, if available, because it can account for more
|
|
// latency causes, and is more precise.
|
|
|
|
// Latency has to be queried after the realization of the interface, when
|
|
// using SL_IID_ANDROIDCONFIGURATION.
|
|
SLuint32 audioLatency = 0;
|
|
SLuint32 paramSize = sizeof(SLuint32);
|
|
// The reported latency is in milliseconds.
|
|
if (playerConfig) {
|
|
res = (*playerConfig)->GetConfiguration(playerConfig,
|
|
(const SLchar *)"androidGetAudioLatency",
|
|
¶mSize,
|
|
&audioLatency);
|
|
if (res == SL_RESULT_SUCCESS) {
|
|
LOG("Got playback latency using android configuration extension");
|
|
stm->output_latency_ms = audioLatency;
|
|
}
|
|
}
|
|
// `playerConfig` is available, but the above failed, or `playerConfig` is not
|
|
// available. In both cases, we need to acquire the output latency by an other
|
|
// mean.
|
|
if ((playerConfig && res != SL_RESULT_SUCCESS) ||
|
|
!playerConfig) {
|
|
if (cubeb_output_latency_method_is_loaded(stm->context->p_output_latency_function)) {
|
|
LOG("Got playback latency using JNI");
|
|
stm->output_latency_ms = cubeb_get_output_latency(stm->context->p_output_latency_function);
|
|
} else {
|
|
LOG("No alternate latency querying method loaded, A/V sync will be off.");
|
|
stm->output_latency_ms = 0;
|
|
}
|
|
}
|
|
|
|
LOG("Audio output latency: %dms", stm->output_latency_ms);
|
|
|
|
res = (*stm->playerObj)->GetInterface(stm->playerObj,
|
|
stm->context->SL_IID_PLAY,
|
|
&stm->play);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to get play interface. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
res = (*stm->playerObj)->GetInterface(stm->playerObj,
|
|
stm->context->SL_IID_BUFFERQUEUE,
|
|
&stm->bufq);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to get bufferqueue interface. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
res = (*stm->playerObj)->GetInterface(stm->playerObj,
|
|
stm->context->SL_IID_VOLUME,
|
|
&stm->volume);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to get volume interface. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to register play callback. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
// Work around wilhelm/AudioTrack badness, bug 1221228
|
|
(*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0);
|
|
|
|
res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to set headatmarker event mask. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
slBufferQueueCallback player_callback = bufferqueue_callback;
|
|
if (stm->input_enabled) {
|
|
player_callback = player_fullduplex_callback;
|
|
}
|
|
res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to register bufferqueue callback. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
{
|
|
// Enqueue a silent frame so once the player becomes playing, the frame
|
|
// will be consumed and kick off the buffer queue callback.
|
|
// Note the duration of a single frame is less than 1ms. We don't bother
|
|
// adjusting the playback position.
|
|
uint8_t *buf = stm->queuebuf[stm->queuebuf_idx++];
|
|
memset(buf, 0, stm->framesize);
|
|
res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize);
|
|
assert(res == SL_RESULT_SUCCESS);
|
|
}
|
|
|
|
LOG("Cubeb stream init playback success");
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_validate_stream_param(cubeb_stream_params * stream_params)
|
|
{
|
|
if ((stream_params &&
|
|
(stream_params->channels < 1 || stream_params->channels > 32))) {
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
}
|
|
if ((stream_params &&
|
|
(stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
|
|
LOG("Loopback is not supported");
|
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
int has_pref_set(cubeb_stream_params* input_params,
|
|
cubeb_stream_params* output_params,
|
|
cubeb_stream_prefs pref)
|
|
{
|
|
return (input_params && input_params->prefs & pref) ||
|
|
(output_params && output_params->prefs & pref);
|
|
}
|
|
|
|
static int
|
|
opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
|
|
cubeb_devid input_device,
|
|
cubeb_stream_params * input_stream_params,
|
|
cubeb_devid output_device,
|
|
cubeb_stream_params * output_stream_params,
|
|
unsigned int latency_frames,
|
|
cubeb_data_callback data_callback, cubeb_state_callback state_callback,
|
|
void * user_ptr)
|
|
{
|
|
cubeb_stream * stm;
|
|
|
|
assert(ctx);
|
|
if (input_device || output_device) {
|
|
LOG("Device selection is not supported in Android. The default will be used");
|
|
}
|
|
|
|
*stream = NULL;
|
|
|
|
int r = opensl_validate_stream_param(output_stream_params);
|
|
if(r != CUBEB_OK) {
|
|
LOG("Output stream params not valid");
|
|
return r;
|
|
}
|
|
r = opensl_validate_stream_param(input_stream_params);
|
|
if(r != CUBEB_OK) {
|
|
LOG("Input stream params not valid");
|
|
return r;
|
|
}
|
|
|
|
stm = calloc(1, sizeof(*stm));
|
|
assert(stm);
|
|
|
|
stm->context = ctx;
|
|
stm->data_callback = data_callback;
|
|
stm->state_callback = state_callback;
|
|
stm->user_ptr = user_ptr;
|
|
stm->buffer_size_frames = latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES;
|
|
stm->input_enabled = (input_stream_params) ? 1 : 0;
|
|
stm->output_enabled = (output_stream_params) ? 1 : 0;
|
|
stm->shutdown = 1;
|
|
stm->voice_input = has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE);
|
|
stm->voice_output = has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE);
|
|
|
|
LOG("cubeb stream prefs: voice_input: %s voice_output: %s", stm->voice_input ? "true" : "false",
|
|
stm->voice_output ? "true" : "false");
|
|
|
|
#ifdef DEBUG
|
|
pthread_mutexattr_t attr;
|
|
pthread_mutexattr_init(&attr);
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
|
|
r = pthread_mutex_init(&stm->mutex, &attr);
|
|
#else
|
|
r = pthread_mutex_init(&stm->mutex, NULL);
|
|
#endif
|
|
assert(r == 0);
|
|
|
|
if (output_stream_params) {
|
|
LOG("Playback params: Rate %d, channels %d, format %d, latency in frames %d.",
|
|
output_stream_params->rate, output_stream_params->channels,
|
|
output_stream_params->format, stm->buffer_size_frames);
|
|
r = opensl_configure_playback(stm, output_stream_params);
|
|
if (r != CUBEB_OK) {
|
|
opensl_stream_destroy(stm);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
if (input_stream_params) {
|
|
LOG("Capture params: Rate %d, channels %d, format %d, latency in frames %d.",
|
|
input_stream_params->rate, input_stream_params->channels,
|
|
input_stream_params->format, stm->buffer_size_frames);
|
|
r = opensl_configure_capture(stm, input_stream_params);
|
|
if (r != CUBEB_OK) {
|
|
opensl_stream_destroy(stm);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/* Configure resampler*/
|
|
uint32_t target_sample_rate;
|
|
if (input_stream_params) {
|
|
target_sample_rate = input_stream_params->rate;
|
|
} else {
|
|
assert(output_stream_params);
|
|
target_sample_rate = output_stream_params->rate;
|
|
}
|
|
|
|
// Use the actual configured rates for input
|
|
// and output.
|
|
cubeb_stream_params input_params;
|
|
if (input_stream_params) {
|
|
input_params = *input_stream_params;
|
|
input_params.rate = stm->input_device_rate;
|
|
}
|
|
cubeb_stream_params output_params;
|
|
if (output_stream_params) {
|
|
output_params = *output_stream_params;
|
|
output_params.rate = stm->output_configured_rate;
|
|
}
|
|
|
|
stm->resampler = cubeb_resampler_create(stm,
|
|
input_stream_params ? &input_params : NULL,
|
|
output_stream_params ? &output_params : NULL,
|
|
target_sample_rate,
|
|
data_callback,
|
|
user_ptr,
|
|
CUBEB_RESAMPLER_QUALITY_DEFAULT);
|
|
if (!stm->resampler) {
|
|
LOG("Failed to create resampler");
|
|
opensl_stream_destroy(stm);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
*stream = stm;
|
|
LOG("Cubeb stream (%p) init success", stm);
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_start_player(cubeb_stream * stm)
|
|
{
|
|
assert(stm->playerObj);
|
|
SLuint32 playerState;
|
|
(*stm->playerObj)->GetState(stm->playerObj, &playerState);
|
|
if (playerState == SL_OBJECT_STATE_REALIZED) {
|
|
SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
|
|
if(res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to start player. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_start_recorder(cubeb_stream * stm)
|
|
{
|
|
assert(stm->recorderObj);
|
|
SLuint32 recorderState;
|
|
(*stm->recorderObj)->GetState(stm->recorderObj, &recorderState);
|
|
if (recorderState == SL_OBJECT_STATE_REALIZED) {
|
|
SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING);
|
|
if(res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to start recorder. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_stream_start(cubeb_stream * stm)
|
|
{
|
|
assert(stm);
|
|
|
|
int r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
opensl_set_shutdown(stm, 0);
|
|
opensl_set_draining(stm, 0);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
if (stm->playerObj) {
|
|
r = opensl_start_player(stm);
|
|
if (r != CUBEB_OK) {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
if (stm->recorderObj) {
|
|
int r = opensl_start_recorder(stm);
|
|
if (r != CUBEB_OK) {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
|
LOG("Cubeb stream (%p) started", stm);
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_stop_player(cubeb_stream * stm)
|
|
{
|
|
assert(stm->playerObj);
|
|
assert(stm->shutdown || stm->draining);
|
|
|
|
SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to stop player. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_stop_recorder(cubeb_stream * stm)
|
|
{
|
|
assert(stm->recorderObj);
|
|
assert(stm->shutdown || stm->draining);
|
|
|
|
SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to stop recorder. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_stream_stop(cubeb_stream * stm)
|
|
{
|
|
assert(stm);
|
|
|
|
int r = pthread_mutex_lock(&stm->mutex);
|
|
assert(r == 0);
|
|
opensl_set_shutdown(stm, 1);
|
|
r = pthread_mutex_unlock(&stm->mutex);
|
|
assert(r == 0);
|
|
|
|
if (stm->playerObj) {
|
|
r = opensl_stop_player(stm);
|
|
if (r != CUBEB_OK) {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
if (stm->recorderObj) {
|
|
int r = opensl_stop_recorder(stm);
|
|
if (r != CUBEB_OK) {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
|
LOG("Cubeb stream (%p) stopped", stm);
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_destroy_recorder(cubeb_stream * stm)
|
|
{
|
|
assert(stm);
|
|
assert(stm->recorderObj);
|
|
|
|
if (stm->recorderBufferQueueItf) {
|
|
SLresult res = (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf);
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
LOG("Failed to clear recorder buffer queue. Error code: %lu", res);
|
|
return CUBEB_ERROR;
|
|
}
|
|
stm->recorderBufferQueueItf = NULL;
|
|
for (uint32_t i = 0; i < stm->input_array_capacity; ++i) {
|
|
free(stm->input_buffer_array[i]);
|
|
}
|
|
}
|
|
|
|
(*stm->recorderObj)->Destroy(stm->recorderObj);
|
|
stm->recorderObj = NULL;
|
|
stm->recorderItf = NULL;
|
|
|
|
if (stm->input_queue) {
|
|
array_queue_destroy(stm->input_queue);
|
|
}
|
|
free(stm->input_silent_buffer);
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static void
|
|
opensl_stream_destroy(cubeb_stream * stm)
|
|
{
|
|
assert(stm->draining || stm->shutdown);
|
|
|
|
if (stm->playerObj) {
|
|
(*stm->playerObj)->Destroy(stm->playerObj);
|
|
stm->playerObj = NULL;
|
|
stm->play = NULL;
|
|
stm->bufq = NULL;
|
|
for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
|
|
free(stm->queuebuf[i]);
|
|
}
|
|
}
|
|
|
|
if (stm->recorderObj) {
|
|
int r = opensl_destroy_recorder(stm);
|
|
assert(r == CUBEB_OK);
|
|
}
|
|
|
|
if (stm->resampler) {
|
|
cubeb_resampler_destroy(stm->resampler);
|
|
}
|
|
|
|
pthread_mutex_destroy(&stm->mutex);
|
|
|
|
LOG("Cubeb stream (%p) destroyed", stm);
|
|
free(stm);
|
|
}
|
|
|
|
static int
|
|
opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
|
|
{
|
|
SLmillisecond msec;
|
|
uint32_t compensation_msec = 0;
|
|
SLresult res;
|
|
|
|
res = (*stm->play)->GetPosition(stm->play, &msec);
|
|
if (res != SL_RESULT_SUCCESS)
|
|
return CUBEB_ERROR;
|
|
|
|
struct timespec t;
|
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
|
if(stm->lastPosition == msec) {
|
|
compensation_msec =
|
|
(t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000;
|
|
} else {
|
|
stm->lastPositionTimeStamp = t.tv_sec*1000000000LL + t.tv_nsec;
|
|
stm->lastPosition = msec;
|
|
}
|
|
|
|
uint64_t samplerate = stm->user_output_rate;
|
|
uint32_t output_latency = stm->output_latency_ms;
|
|
|
|
pthread_mutex_lock(&stm->mutex);
|
|
int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / stm->output_configured_rate;
|
|
pthread_mutex_unlock(&stm->mutex);
|
|
assert(maximum_position >= 0);
|
|
|
|
if (msec > output_latency) {
|
|
int64_t unadjusted_position;
|
|
if (stm->lastCompensativePosition > msec + compensation_msec) {
|
|
// Over compensation, use lastCompensativePosition.
|
|
unadjusted_position =
|
|
samplerate * (stm->lastCompensativePosition - output_latency) / 1000;
|
|
} else {
|
|
unadjusted_position =
|
|
samplerate * (msec - output_latency + compensation_msec) / 1000;
|
|
stm->lastCompensativePosition = msec + compensation_msec;
|
|
}
|
|
*position = unadjusted_position < maximum_position ?
|
|
unadjusted_position : maximum_position;
|
|
} else {
|
|
*position = 0;
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
|
|
{
|
|
assert(stm);
|
|
assert(latency);
|
|
|
|
uint32_t stream_latency_frames =
|
|
stm->user_output_rate * stm->output_latency_ms / 1000;
|
|
|
|
return stream_latency_frames + cubeb_resampler_latency(stm->resampler);
|
|
}
|
|
|
|
int
|
|
opensl_stream_set_volume(cubeb_stream * stm, float volume)
|
|
{
|
|
SLresult res;
|
|
SLmillibel max_level, millibels;
|
|
float unclamped_millibels;
|
|
|
|
res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level);
|
|
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
/* millibels are 100*dB, so the conversion from the volume's linear amplitude
|
|
* is 100 * 20 * log(volume). However we clamp the resulting value before
|
|
* passing it to lroundf() in order to prevent it from silently returning an
|
|
* erroneous value when the unclamped value exceeds the size of a long. */
|
|
unclamped_millibels = 100.0f * 20.0f * log10f(fmaxf(volume, 0.0f));
|
|
unclamped_millibels = fmaxf(unclamped_millibels, SL_MILLIBEL_MIN);
|
|
unclamped_millibels = fminf(unclamped_millibels, max_level);
|
|
|
|
millibels = lroundf(unclamped_millibels);
|
|
|
|
res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels);
|
|
|
|
if (res != SL_RESULT_SUCCESS) {
|
|
return CUBEB_ERROR;
|
|
}
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static struct cubeb_ops const opensl_ops = {
|
|
.init = opensl_init,
|
|
.get_backend_id = opensl_get_backend_id,
|
|
.get_max_channel_count = opensl_get_max_channel_count,
|
|
.get_min_latency = NULL,
|
|
.get_preferred_sample_rate = NULL,
|
|
.enumerate_devices = NULL,
|
|
.device_collection_destroy = NULL,
|
|
.destroy = opensl_destroy,
|
|
.stream_init = opensl_stream_init,
|
|
.stream_destroy = opensl_stream_destroy,
|
|
.stream_start = opensl_stream_start,
|
|
.stream_stop = opensl_stream_stop,
|
|
.stream_reset_default_device = NULL,
|
|
.stream_get_position = opensl_stream_get_position,
|
|
.stream_get_latency = opensl_stream_get_latency,
|
|
.stream_get_input_latency = NULL,
|
|
.stream_set_volume = opensl_stream_set_volume,
|
|
.stream_set_name = NULL,
|
|
.stream_get_current_device = NULL,
|
|
.stream_device_destroy = NULL,
|
|
.stream_register_device_changed_callback = NULL,
|
|
.register_device_collection_changed = NULL
|
|
};
|