Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright © 2012 Mozilla Foundation |
michael@0 | 3 | * |
michael@0 | 4 | * This program is made available under an ISC-style license. See the |
michael@0 | 5 | * accompanying file LICENSE for details. |
michael@0 | 6 | */ |
michael@0 | 7 | #undef NDEBUG |
michael@0 | 8 | #include <assert.h> |
michael@0 | 9 | #include <dlfcn.h> |
michael@0 | 10 | #include <stdlib.h> |
michael@0 | 11 | #include <SLES/OpenSLES.h> |
michael@0 | 12 | #if defined(__ANDROID__) |
michael@0 | 13 | #include "android/sles_definitions.h" |
michael@0 | 14 | #include <SLES/OpenSLES_Android.h> |
michael@0 | 15 | #include <android/log.h> |
michael@0 | 16 | #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args) |
michael@0 | 17 | #endif |
michael@0 | 18 | #include "cubeb/cubeb.h" |
michael@0 | 19 | #include "cubeb-internal.h" |
michael@0 | 20 | |
michael@0 | 21 | static struct cubeb_ops const opensl_ops; |
michael@0 | 22 | |
michael@0 | 23 | struct cubeb { |
michael@0 | 24 | struct cubeb_ops const * ops; |
michael@0 | 25 | void * lib; |
michael@0 | 26 | void * libmedia; |
michael@0 | 27 | int32_t (* get_output_latency)(uint32_t * latency, int stream_type); |
michael@0 | 28 | SLInterfaceID SL_IID_BUFFERQUEUE; |
michael@0 | 29 | SLInterfaceID SL_IID_PLAY; |
michael@0 | 30 | #if defined(__ANDROID__) |
michael@0 | 31 | SLInterfaceID SL_IID_ANDROIDCONFIGURATION; |
michael@0 | 32 | #endif |
michael@0 | 33 | SLObjectItf engObj; |
michael@0 | 34 | SLEngineItf eng; |
michael@0 | 35 | SLObjectItf outmixObj; |
michael@0 | 36 | }; |
michael@0 | 37 | |
michael@0 | 38 | #define NELEMS(A) (sizeof(A) / sizeof A[0]) |
michael@0 | 39 | #define NBUFS 4 |
michael@0 | 40 | #define AUDIO_STREAM_TYPE_MUSIC 3 |
michael@0 | 41 | |
michael@0 | 42 | struct cubeb_stream { |
michael@0 | 43 | cubeb * context; |
michael@0 | 44 | SLObjectItf playerObj; |
michael@0 | 45 | SLPlayItf play; |
michael@0 | 46 | SLBufferQueueItf bufq; |
michael@0 | 47 | void *queuebuf[NBUFS]; |
michael@0 | 48 | int queuebuf_idx; |
michael@0 | 49 | long queuebuf_len; |
michael@0 | 50 | long bytespersec; |
michael@0 | 51 | long framesize; |
michael@0 | 52 | int draining; |
michael@0 | 53 | cubeb_stream_type stream_type; |
michael@0 | 54 | |
michael@0 | 55 | cubeb_data_callback data_callback; |
michael@0 | 56 | cubeb_state_callback state_callback; |
michael@0 | 57 | void * user_ptr; |
michael@0 | 58 | }; |
michael@0 | 59 | |
michael@0 | 60 | static void |
michael@0 | 61 | bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) |
michael@0 | 62 | { |
michael@0 | 63 | cubeb_stream * stm = user_ptr; |
michael@0 | 64 | SLBufferQueueState state; |
michael@0 | 65 | (*stm->bufq)->GetState(stm->bufq, &state); |
michael@0 | 66 | |
michael@0 | 67 | if (stm->draining) { |
michael@0 | 68 | if (!state.count) { |
michael@0 | 69 | stm->draining = 0; |
michael@0 | 70 | stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); |
michael@0 | 71 | } |
michael@0 | 72 | return; |
michael@0 | 73 | } |
michael@0 | 74 | |
michael@0 | 75 | if (state.count > 1) |
michael@0 | 76 | return; |
michael@0 | 77 | |
michael@0 | 78 | SLuint32 i; |
michael@0 | 79 | for (i = state.count; i < NBUFS; i++) { |
michael@0 | 80 | void *buf = stm->queuebuf[stm->queuebuf_idx]; |
michael@0 | 81 | long written = stm->data_callback(stm, stm->user_ptr, |
michael@0 | 82 | buf, stm->queuebuf_len / stm->framesize); |
michael@0 | 83 | if (written == CUBEB_ERROR) { |
michael@0 | 84 | (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_STOPPED); |
michael@0 | 85 | return; |
michael@0 | 86 | } |
michael@0 | 87 | |
michael@0 | 88 | if (written) { |
michael@0 | 89 | (*stm->bufq)->Enqueue(stm->bufq, buf, written * stm->framesize); |
michael@0 | 90 | stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS; |
michael@0 | 91 | } else if (!i) { |
michael@0 | 92 | stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); |
michael@0 | 93 | return; |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | if ((written * stm->framesize) < stm->queuebuf_len) { |
michael@0 | 97 | stm->draining = 1; |
michael@0 | 98 | return; |
michael@0 | 99 | } |
michael@0 | 100 | } |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | #if defined(__ANDROID__) |
michael@0 | 104 | static SLuint32 |
michael@0 | 105 | convert_stream_type_to_sl_stream(cubeb_stream_type stream_type) |
michael@0 | 106 | { |
michael@0 | 107 | switch(stream_type) { |
michael@0 | 108 | case CUBEB_STREAM_TYPE_SYSTEM: |
michael@0 | 109 | return SL_ANDROID_STREAM_SYSTEM; |
michael@0 | 110 | case CUBEB_STREAM_TYPE_MUSIC: |
michael@0 | 111 | return SL_ANDROID_STREAM_MEDIA; |
michael@0 | 112 | case CUBEB_STREAM_TYPE_NOTIFICATION: |
michael@0 | 113 | return SL_ANDROID_STREAM_NOTIFICATION; |
michael@0 | 114 | case CUBEB_STREAM_TYPE_ALARM: |
michael@0 | 115 | return SL_ANDROID_STREAM_ALARM; |
michael@0 | 116 | case CUBEB_STREAM_TYPE_VOICE_CALL: |
michael@0 | 117 | return SL_ANDROID_STREAM_VOICE; |
michael@0 | 118 | case CUBEB_STREAM_TYPE_RING: |
michael@0 | 119 | return SL_ANDROID_STREAM_RING; |
michael@0 | 120 | case CUBEB_STREAM_TYPE_ENFORCED_AUDIBLE: |
michael@0 | 121 | default: |
michael@0 | 122 | return 0xFFFFFFFF; |
michael@0 | 123 | } |
michael@0 | 124 | } |
michael@0 | 125 | #endif |
michael@0 | 126 | |
michael@0 | 127 | static void opensl_destroy(cubeb * ctx); |
michael@0 | 128 | |
michael@0 | 129 | /*static*/ int |
michael@0 | 130 | opensl_init(cubeb ** context, char const * context_name) |
michael@0 | 131 | { |
michael@0 | 132 | cubeb * ctx; |
michael@0 | 133 | |
michael@0 | 134 | *context = NULL; |
michael@0 | 135 | |
michael@0 | 136 | ctx = calloc(1, sizeof(*ctx)); |
michael@0 | 137 | assert(ctx); |
michael@0 | 138 | |
michael@0 | 139 | ctx->ops = &opensl_ops; |
michael@0 | 140 | |
michael@0 | 141 | ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY); |
michael@0 | 142 | ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY); |
michael@0 | 143 | if (!ctx->lib || !ctx->libmedia) { |
michael@0 | 144 | free(ctx); |
michael@0 | 145 | return CUBEB_ERROR; |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | /* Get the latency, in ms, from AudioFlinger */ |
michael@0 | 149 | /* status_t AudioSystem::getOutputLatency(uint32_t* latency, |
michael@0 | 150 | * audio_stream_type_t streamType) */ |
michael@0 | 151 | /* First, try the most recent signature. */ |
michael@0 | 152 | ctx->get_output_latency = |
michael@0 | 153 | dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); |
michael@0 | 154 | if (!ctx->get_output_latency) { |
michael@0 | 155 | /* in case of failure, try the legacy version. */ |
michael@0 | 156 | /* status_t AudioSystem::getOutputLatency(uint32_t* latency, |
michael@0 | 157 | * int streamType) */ |
michael@0 | 158 | ctx->get_output_latency = |
michael@0 | 159 | dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); |
michael@0 | 160 | if (!ctx->get_output_latency) { |
michael@0 | 161 | opensl_destroy(ctx); |
michael@0 | 162 | return CUBEB_ERROR; |
michael@0 | 163 | } |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | typedef SLresult (*slCreateEngine_t)(SLObjectItf *, |
michael@0 | 167 | SLuint32, |
michael@0 | 168 | const SLEngineOption *, |
michael@0 | 169 | SLuint32, |
michael@0 | 170 | const SLInterfaceID *, |
michael@0 | 171 | const SLboolean *); |
michael@0 | 172 | slCreateEngine_t f_slCreateEngine = |
michael@0 | 173 | (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine"); |
michael@0 | 174 | SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE"); |
michael@0 | 175 | SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX"); |
michael@0 | 176 | ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); |
michael@0 | 177 | #if defined(__ANDROID__) |
michael@0 | 178 | ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); |
michael@0 | 179 | #endif |
michael@0 | 180 | ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); |
michael@0 | 181 | if (!f_slCreateEngine || |
michael@0 | 182 | !SL_IID_ENGINE || |
michael@0 | 183 | !SL_IID_OUTPUTMIX || |
michael@0 | 184 | !ctx->SL_IID_BUFFERQUEUE || |
michael@0 | 185 | #if defined(__ANDROID__) |
michael@0 | 186 | !ctx->SL_IID_ANDROIDCONFIGURATION || |
michael@0 | 187 | #endif |
michael@0 | 188 | !ctx->SL_IID_PLAY) { |
michael@0 | 189 | opensl_destroy(ctx); |
michael@0 | 190 | return CUBEB_ERROR; |
michael@0 | 191 | } |
michael@0 | 192 | |
michael@0 | 193 | const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}}; |
michael@0 | 194 | |
michael@0 | 195 | SLresult res; |
michael@0 | 196 | res = f_slCreateEngine(&ctx->engObj, 1, opt, 0, NULL, NULL); |
michael@0 | 197 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 198 | opensl_destroy(ctx); |
michael@0 | 199 | return CUBEB_ERROR; |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | res = (*ctx->engObj)->Realize(ctx->engObj, SL_BOOLEAN_FALSE); |
michael@0 | 203 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 204 | opensl_destroy(ctx); |
michael@0 | 205 | return CUBEB_ERROR; |
michael@0 | 206 | } |
michael@0 | 207 | |
michael@0 | 208 | res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng); |
michael@0 | 209 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 210 | opensl_destroy(ctx); |
michael@0 | 211 | return CUBEB_ERROR; |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX}; |
michael@0 | 215 | const SLboolean reqom[] = {SL_BOOLEAN_TRUE}; |
michael@0 | 216 | res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom); |
michael@0 | 217 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 218 | opensl_destroy(ctx); |
michael@0 | 219 | return CUBEB_ERROR; |
michael@0 | 220 | } |
michael@0 | 221 | |
michael@0 | 222 | res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE); |
michael@0 | 223 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 224 | opensl_destroy(ctx); |
michael@0 | 225 | return CUBEB_ERROR; |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | *context = ctx; |
michael@0 | 229 | |
michael@0 | 230 | return CUBEB_OK; |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | static char const * |
michael@0 | 234 | opensl_get_backend_id(cubeb * ctx) |
michael@0 | 235 | { |
michael@0 | 236 | return "opensl"; |
michael@0 | 237 | } |
michael@0 | 238 | |
michael@0 | 239 | static int |
michael@0 | 240 | opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) |
michael@0 | 241 | { |
michael@0 | 242 | assert(ctx && max_channels); |
michael@0 | 243 | /* The android mixer handles up to two channels, see |
michael@0 | 244 | http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */ |
michael@0 | 245 | *max_channels = 2; |
michael@0 | 246 | |
michael@0 | 247 | return CUBEB_OK; |
michael@0 | 248 | } |
michael@0 | 249 | |
michael@0 | 250 | static int |
michael@0 | 251 | opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) |
michael@0 | 252 | { |
michael@0 | 253 | /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html |
michael@0 | 254 | * We don't want to deal with JNI here (and we don't have Java on b2g anyways), |
michael@0 | 255 | * so we just dlopen the library and get the two symbols we need. */ |
michael@0 | 256 | int rv; |
michael@0 | 257 | void * libmedia; |
michael@0 | 258 | uint32_t (*get_primary_output_samplingrate)(); |
michael@0 | 259 | uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType); |
michael@0 | 260 | uint32_t primary_sampling_rate; |
michael@0 | 261 | |
michael@0 | 262 | libmedia = dlopen("libmedia.so", RTLD_LAZY); |
michael@0 | 263 | if (!libmedia) { |
michael@0 | 264 | return CUBEB_ERROR; |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */ |
michael@0 | 268 | get_primary_output_samplingrate = |
michael@0 | 269 | dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv"); |
michael@0 | 270 | if (!get_primary_output_samplingrate) { |
michael@0 | 271 | /* fallback to |
michael@0 | 272 | * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType) |
michael@0 | 273 | * if we cannot find getPrimaryOutputSamplingRate. */ |
michael@0 | 274 | get_output_samplingrate = |
michael@0 | 275 | dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t"); |
michael@0 | 276 | if (!get_output_samplingrate) { |
michael@0 | 277 | /* Another signature exists, with a int instead of an audio_stream_type_t */ |
michael@0 | 278 | get_output_samplingrate = |
michael@0 | 279 | dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii"); |
michael@0 | 280 | if (!get_output_samplingrate) { |
michael@0 | 281 | dlclose(libmedia); |
michael@0 | 282 | return CUBEB_ERROR; |
michael@0 | 283 | } |
michael@0 | 284 | } |
michael@0 | 285 | } |
michael@0 | 286 | |
michael@0 | 287 | if (get_primary_output_samplingrate) { |
michael@0 | 288 | *rate = get_primary_output_samplingrate(); |
michael@0 | 289 | } else { |
michael@0 | 290 | /* We don't really know about the type, here, so we just pass music. */ |
michael@0 | 291 | rv = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC); |
michael@0 | 292 | if (rv) { |
michael@0 | 293 | dlclose(libmedia); |
michael@0 | 294 | return CUBEB_ERROR; |
michael@0 | 295 | } |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | dlclose(libmedia); |
michael@0 | 299 | |
michael@0 | 300 | /* Depending on which method we called above, we can get a zero back, yet have |
michael@0 | 301 | * a non-error return value, especially if the audio system is not |
michael@0 | 302 | * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger |
michael@0 | 303 | * thread). */ |
michael@0 | 304 | if (*rate == 0) { |
michael@0 | 305 | return CUBEB_ERROR; |
michael@0 | 306 | } |
michael@0 | 307 | |
michael@0 | 308 | return CUBEB_OK; |
michael@0 | 309 | } |
michael@0 | 310 | |
michael@0 | 311 | static int |
michael@0 | 312 | opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) |
michael@0 | 313 | { |
michael@0 | 314 | /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html |
michael@0 | 315 | * We don't want to deal with JNI here (and we don't have Java on b2g anyways), |
michael@0 | 316 | * so we just dlopen the library and get the two symbols we need. */ |
michael@0 | 317 | |
michael@0 | 318 | int rv; |
michael@0 | 319 | void * libmedia; |
michael@0 | 320 | size_t (*get_primary_output_frame_count)(void); |
michael@0 | 321 | int (*get_output_frame_count)(size_t * frameCount, int streamType); |
michael@0 | 322 | uint32_t primary_sampling_rate; |
michael@0 | 323 | size_t primary_buffer_size; |
michael@0 | 324 | |
michael@0 | 325 | rv = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate); |
michael@0 | 326 | |
michael@0 | 327 | if (rv) { |
michael@0 | 328 | return CUBEB_ERROR; |
michael@0 | 329 | } |
michael@0 | 330 | |
michael@0 | 331 | libmedia = dlopen("libmedia.so", RTLD_LAZY); |
michael@0 | 332 | if (!libmedia) { |
michael@0 | 333 | return CUBEB_ERROR; |
michael@0 | 334 | } |
michael@0 | 335 | |
michael@0 | 336 | /* JB variant */ |
michael@0 | 337 | /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */ |
michael@0 | 338 | get_primary_output_frame_count = |
michael@0 | 339 | dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv"); |
michael@0 | 340 | if (!get_primary_output_frame_count) { |
michael@0 | 341 | /* ICS variant */ |
michael@0 | 342 | /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */ |
michael@0 | 343 | get_output_frame_count = |
michael@0 | 344 | dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii"); |
michael@0 | 345 | if (!get_output_frame_count) { |
michael@0 | 346 | dlclose(libmedia); |
michael@0 | 347 | return CUBEB_ERROR; |
michael@0 | 348 | } |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | if (get_primary_output_frame_count) { |
michael@0 | 352 | primary_buffer_size = get_primary_output_frame_count(); |
michael@0 | 353 | } else { |
michael@0 | 354 | if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) { |
michael@0 | 355 | return CUBEB_ERROR; |
michael@0 | 356 | } |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | /* To get a fast track in Android's mixer, we need to be at the native |
michael@0 | 360 | * samplerate, which is device dependant. Some devices might be able to |
michael@0 | 361 | * resample when playing a fast track, but it's pretty rare. */ |
michael@0 | 362 | *latency_ms = NBUFS * primary_buffer_size / (primary_sampling_rate / 1000); |
michael@0 | 363 | |
michael@0 | 364 | dlclose(libmedia); |
michael@0 | 365 | |
michael@0 | 366 | return CUBEB_OK; |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | static void |
michael@0 | 370 | opensl_destroy(cubeb * ctx) |
michael@0 | 371 | { |
michael@0 | 372 | if (ctx->outmixObj) |
michael@0 | 373 | (*ctx->outmixObj)->Destroy(ctx->outmixObj); |
michael@0 | 374 | if (ctx->engObj) |
michael@0 | 375 | (*ctx->engObj)->Destroy(ctx->engObj); |
michael@0 | 376 | dlclose(ctx->lib); |
michael@0 | 377 | dlclose(ctx->libmedia); |
michael@0 | 378 | free(ctx); |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | static void opensl_stream_destroy(cubeb_stream * stm); |
michael@0 | 382 | |
michael@0 | 383 | static int |
michael@0 | 384 | opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, |
michael@0 | 385 | cubeb_stream_params stream_params, unsigned int latency, |
michael@0 | 386 | cubeb_data_callback data_callback, cubeb_state_callback state_callback, |
michael@0 | 387 | void * user_ptr) |
michael@0 | 388 | { |
michael@0 | 389 | cubeb_stream * stm; |
michael@0 | 390 | |
michael@0 | 391 | assert(ctx); |
michael@0 | 392 | |
michael@0 | 393 | *stream = NULL; |
michael@0 | 394 | |
michael@0 | 395 | if (stream_params.rate < 8000 || stream_params.rate > 88200 || |
michael@0 | 396 | stream_params.channels < 1 || stream_params.channels > 32 || |
michael@0 | 397 | latency < 1 || latency > 2000) { |
michael@0 | 398 | return CUBEB_ERROR_INVALID_FORMAT; |
michael@0 | 399 | } |
michael@0 | 400 | |
michael@0 | 401 | SLDataFormat_PCM format; |
michael@0 | 402 | |
michael@0 | 403 | format.formatType = SL_DATAFORMAT_PCM; |
michael@0 | 404 | format.numChannels = stream_params.channels; |
michael@0 | 405 | // samplesPerSec is in milliHertz |
michael@0 | 406 | format.samplesPerSec = stream_params.rate * 1000; |
michael@0 | 407 | format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; |
michael@0 | 408 | format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; |
michael@0 | 409 | format.channelMask = stream_params.channels == 1 ? |
michael@0 | 410 | SL_SPEAKER_FRONT_CENTER : |
michael@0 | 411 | SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; |
michael@0 | 412 | |
michael@0 | 413 | switch (stream_params.format) { |
michael@0 | 414 | case CUBEB_SAMPLE_S16LE: |
michael@0 | 415 | format.endianness = SL_BYTEORDER_LITTLEENDIAN; |
michael@0 | 416 | break; |
michael@0 | 417 | case CUBEB_SAMPLE_S16BE: |
michael@0 | 418 | format.endianness = SL_BYTEORDER_BIGENDIAN; |
michael@0 | 419 | break; |
michael@0 | 420 | default: |
michael@0 | 421 | return CUBEB_ERROR_INVALID_FORMAT; |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | stm = calloc(1, sizeof(*stm)); |
michael@0 | 425 | assert(stm); |
michael@0 | 426 | |
michael@0 | 427 | stm->context = ctx; |
michael@0 | 428 | stm->data_callback = data_callback; |
michael@0 | 429 | stm->state_callback = state_callback; |
michael@0 | 430 | stm->user_ptr = user_ptr; |
michael@0 | 431 | |
michael@0 | 432 | stm->stream_type = stream_params.stream_type; |
michael@0 | 433 | stm->framesize = stream_params.channels * sizeof(int16_t); |
michael@0 | 434 | stm->bytespersec = stream_params.rate * stm->framesize; |
michael@0 | 435 | stm->queuebuf_len = (stm->bytespersec * latency) / (1000 * NBUFS); |
michael@0 | 436 | // round up to the next multiple of stm->framesize, if needed. |
michael@0 | 437 | if (stm->queuebuf_len % stm->framesize) { |
michael@0 | 438 | stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize); |
michael@0 | 439 | } |
michael@0 | 440 | int i; |
michael@0 | 441 | for (i = 0; i < NBUFS; i++) { |
michael@0 | 442 | stm->queuebuf[i] = malloc(stm->queuebuf_len); |
michael@0 | 443 | assert(stm->queuebuf[i]); |
michael@0 | 444 | } |
michael@0 | 445 | |
michael@0 | 446 | SLDataLocator_BufferQueue loc_bufq; |
michael@0 | 447 | loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; |
michael@0 | 448 | loc_bufq.numBuffers = NBUFS; |
michael@0 | 449 | SLDataSource source; |
michael@0 | 450 | source.pLocator = &loc_bufq; |
michael@0 | 451 | source.pFormat = &format; |
michael@0 | 452 | |
michael@0 | 453 | SLDataLocator_OutputMix loc_outmix; |
michael@0 | 454 | loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; |
michael@0 | 455 | loc_outmix.outputMix = ctx->outmixObj; |
michael@0 | 456 | SLDataSink sink; |
michael@0 | 457 | sink.pLocator = &loc_outmix; |
michael@0 | 458 | sink.pFormat = NULL; |
michael@0 | 459 | |
michael@0 | 460 | #if defined(__ANDROID__) |
michael@0 | 461 | const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_ANDROIDCONFIGURATION}; |
michael@0 | 462 | const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; |
michael@0 | 463 | #else |
michael@0 | 464 | const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE}; |
michael@0 | 465 | const SLboolean req[] = {SL_BOOLEAN_TRUE}; |
michael@0 | 466 | #endif |
michael@0 | 467 | assert(NELEMS(ids) == NELEMS(req)); |
michael@0 | 468 | SLresult res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, |
michael@0 | 469 | &source, &sink, NELEMS(ids), ids, req); |
michael@0 | 470 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 471 | opensl_stream_destroy(stm); |
michael@0 | 472 | return CUBEB_ERROR; |
michael@0 | 473 | } |
michael@0 | 474 | |
michael@0 | 475 | #if defined(__ANDROID__) |
michael@0 | 476 | SLuint32 stream_type = convert_stream_type_to_sl_stream(stream_params.stream_type); |
michael@0 | 477 | if (stream_type != 0xFFFFFFFF) { |
michael@0 | 478 | SLAndroidConfigurationItf playerConfig; |
michael@0 | 479 | res = (*stm->playerObj)->GetInterface(stm->playerObj, |
michael@0 | 480 | ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig); |
michael@0 | 481 | res = (*playerConfig)->SetConfiguration(playerConfig, |
michael@0 | 482 | SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32)); |
michael@0 | 483 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 484 | opensl_stream_destroy(stm); |
michael@0 | 485 | return CUBEB_ERROR; |
michael@0 | 486 | } |
michael@0 | 487 | } |
michael@0 | 488 | #endif |
michael@0 | 489 | |
michael@0 | 490 | res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); |
michael@0 | 491 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 492 | opensl_stream_destroy(stm); |
michael@0 | 493 | return CUBEB_ERROR; |
michael@0 | 494 | } |
michael@0 | 495 | |
michael@0 | 496 | res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play); |
michael@0 | 497 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 498 | opensl_stream_destroy(stm); |
michael@0 | 499 | return CUBEB_ERROR; |
michael@0 | 500 | } |
michael@0 | 501 | |
michael@0 | 502 | res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE, |
michael@0 | 503 | &stm->bufq); |
michael@0 | 504 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 505 | opensl_stream_destroy(stm); |
michael@0 | 506 | return CUBEB_ERROR; |
michael@0 | 507 | } |
michael@0 | 508 | |
michael@0 | 509 | res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm); |
michael@0 | 510 | if (res != SL_RESULT_SUCCESS) { |
michael@0 | 511 | opensl_stream_destroy(stm); |
michael@0 | 512 | return CUBEB_ERROR; |
michael@0 | 513 | } |
michael@0 | 514 | |
michael@0 | 515 | *stream = stm; |
michael@0 | 516 | return CUBEB_OK; |
michael@0 | 517 | } |
michael@0 | 518 | |
michael@0 | 519 | static void |
michael@0 | 520 | opensl_stream_destroy(cubeb_stream * stm) |
michael@0 | 521 | { |
michael@0 | 522 | if (stm->playerObj) |
michael@0 | 523 | (*stm->playerObj)->Destroy(stm->playerObj); |
michael@0 | 524 | int i; |
michael@0 | 525 | for (i = 0; i < NBUFS; i++) { |
michael@0 | 526 | free(stm->queuebuf[i]); |
michael@0 | 527 | } |
michael@0 | 528 | |
michael@0 | 529 | free(stm); |
michael@0 | 530 | } |
michael@0 | 531 | |
michael@0 | 532 | static int |
michael@0 | 533 | opensl_stream_start(cubeb_stream * stm) |
michael@0 | 534 | { |
michael@0 | 535 | /* To refill the queues before starting playback in order to avoid racing |
michael@0 | 536 | * with refills started by SetPlayState on OpenSLES ndk threads. */ |
michael@0 | 537 | bufferqueue_callback(NULL, stm); |
michael@0 | 538 | SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); |
michael@0 | 539 | if (res != SL_RESULT_SUCCESS) |
michael@0 | 540 | return CUBEB_ERROR; |
michael@0 | 541 | stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); |
michael@0 | 542 | return CUBEB_OK; |
michael@0 | 543 | } |
michael@0 | 544 | |
michael@0 | 545 | static int |
michael@0 | 546 | opensl_stream_stop(cubeb_stream * stm) |
michael@0 | 547 | { |
michael@0 | 548 | SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); |
michael@0 | 549 | if (res != SL_RESULT_SUCCESS) |
michael@0 | 550 | return CUBEB_ERROR; |
michael@0 | 551 | stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); |
michael@0 | 552 | return CUBEB_OK; |
michael@0 | 553 | } |
michael@0 | 554 | |
michael@0 | 555 | static int |
michael@0 | 556 | opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) |
michael@0 | 557 | { |
michael@0 | 558 | SLmillisecond msec; |
michael@0 | 559 | uint64_t samplerate; |
michael@0 | 560 | SLresult res; |
michael@0 | 561 | int rv; |
michael@0 | 562 | uint32_t mixer_latency; |
michael@0 | 563 | |
michael@0 | 564 | res = (*stm->play)->GetPosition(stm->play, &msec); |
michael@0 | 565 | if (res != SL_RESULT_SUCCESS) |
michael@0 | 566 | return CUBEB_ERROR; |
michael@0 | 567 | |
michael@0 | 568 | samplerate = stm->bytespersec / stm->framesize; |
michael@0 | 569 | |
michael@0 | 570 | rv = stm->context->get_output_latency(&mixer_latency, stm->stream_type); |
michael@0 | 571 | if (rv) { |
michael@0 | 572 | return CUBEB_ERROR; |
michael@0 | 573 | } |
michael@0 | 574 | |
michael@0 | 575 | if (msec > mixer_latency) { |
michael@0 | 576 | *position = samplerate * (msec - mixer_latency) / 1000; |
michael@0 | 577 | } else { |
michael@0 | 578 | *position = 0; |
michael@0 | 579 | } |
michael@0 | 580 | return CUBEB_OK; |
michael@0 | 581 | } |
michael@0 | 582 | |
michael@0 | 583 | int |
michael@0 | 584 | opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) |
michael@0 | 585 | { |
michael@0 | 586 | int rv; |
michael@0 | 587 | uint32_t mixer_latency; |
michael@0 | 588 | uint32_t samplerate; |
michael@0 | 589 | |
michael@0 | 590 | /* The latency returned by AudioFlinger is in ms, so we have to get |
michael@0 | 591 | * AudioFlinger's samplerate to convert it to frames. */ |
michael@0 | 592 | rv = opensl_get_preferred_sample_rate(stm->context, &samplerate); |
michael@0 | 593 | if (rv) { |
michael@0 | 594 | return CUBEB_ERROR; |
michael@0 | 595 | } |
michael@0 | 596 | |
michael@0 | 597 | /* audio_stream_type_t is an int, so this is okay. */ |
michael@0 | 598 | rv = stm->context->get_output_latency(&mixer_latency, stm->stream_type); |
michael@0 | 599 | if (rv) { |
michael@0 | 600 | return CUBEB_ERROR; |
michael@0 | 601 | } |
michael@0 | 602 | |
michael@0 | 603 | *latency = NBUFS * stm->queuebuf_len / stm->framesize + // OpenSL latency |
michael@0 | 604 | mixer_latency * samplerate / 1000; // AudioFlinger latency |
michael@0 | 605 | |
michael@0 | 606 | return CUBEB_OK; |
michael@0 | 607 | } |
michael@0 | 608 | |
michael@0 | 609 | static struct cubeb_ops const opensl_ops = { |
michael@0 | 610 | .init = opensl_init, |
michael@0 | 611 | .get_backend_id = opensl_get_backend_id, |
michael@0 | 612 | .get_max_channel_count = opensl_get_max_channel_count, |
michael@0 | 613 | .get_min_latency = opensl_get_min_latency, |
michael@0 | 614 | .get_preferred_sample_rate = opensl_get_preferred_sample_rate, |
michael@0 | 615 | .destroy = opensl_destroy, |
michael@0 | 616 | .stream_init = opensl_stream_init, |
michael@0 | 617 | .stream_destroy = opensl_stream_destroy, |
michael@0 | 618 | .stream_start = opensl_stream_start, |
michael@0 | 619 | .stream_stop = opensl_stream_stop, |
michael@0 | 620 | .stream_get_position = opensl_stream_get_position, |
michael@0 | 621 | .stream_get_latency = opensl_stream_get_latency |
michael@0 | 622 | }; |