| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| // cribbed from samples/native-audio |
| |
| #include "audioplay.h" |
| |
| #define CHATTY ALOGD |
| #define LOG_TAG "audioplay" |
| |
| #include <string.h> |
| |
| #include <utils/Log.h> |
| |
| // for native audio |
| #include <SLES/OpenSLES.h> |
| #include <SLES/OpenSLES_Android.h> |
| |
| namespace audioplay { |
| namespace { |
| |
| // engine interfaces |
| static SLObjectItf engineObject = NULL; |
| static SLEngineItf engineEngine; |
| |
| // output mix interfaces |
| static SLObjectItf outputMixObject = NULL; |
| |
| // buffer queue player interfaces |
| static SLObjectItf bqPlayerObject = NULL; |
| static SLPlayItf bqPlayerPlay; |
| static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; |
| static SLMuteSoloItf bqPlayerMuteSolo; |
| static SLVolumeItf bqPlayerVolume; |
| |
| // pointer and size of the next player buffer to enqueue, and number of remaining buffers |
| static const uint8_t* nextBuffer; |
| static unsigned nextSize; |
| |
| static const uint32_t ID_RIFF = 0x46464952; |
| static const uint32_t ID_WAVE = 0x45564157; |
| static const uint32_t ID_FMT = 0x20746d66; |
| static const uint32_t ID_DATA = 0x61746164; |
| |
| struct RiffWaveHeader { |
| uint32_t riff_id; |
| uint32_t riff_sz; |
| uint32_t wave_id; |
| }; |
| |
| struct ChunkHeader { |
| uint32_t id; |
| uint32_t sz; |
| }; |
| |
| struct ChunkFormat { |
| uint16_t audio_format; |
| uint16_t num_channels; |
| uint32_t sample_rate; |
| uint32_t byte_rate; |
| uint16_t block_align; |
| uint16_t bits_per_sample; |
| }; |
| |
| // this callback handler is called every time a buffer finishes playing |
| void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { |
| (void)bq; |
| (void)context; |
| audioplay::setPlaying(false); |
| } |
| |
| bool hasPlayer() { |
| return (engineObject != NULL && bqPlayerObject != NULL); |
| } |
| |
| // create the engine and output mix objects |
| bool createEngine() { |
| SLresult result; |
| |
| // create engine |
| result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("slCreateEngine failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // realize the engine |
| result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl engine Realize failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // get the engine interface, which is needed in order to create other objects |
| result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl engine GetInterface failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // create output mix |
| result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl engine CreateOutputMix failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // realize the output mix |
| result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl outputMix Realize failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| return true; |
| } |
| |
| // create buffer queue audio player |
| bool createBufferQueueAudioPlayer(const ChunkFormat* chunkFormat) { |
| SLresult result; |
| |
| // configure audio source |
| SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1}; |
| |
| // Determine channelMask from num_channels |
| SLuint32 channelMask; |
| switch (chunkFormat->num_channels) { |
| case 1: |
| channelMask = SL_SPEAKER_FRONT_CENTER; |
| break; |
| case 2: |
| channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; |
| break; |
| default: |
| // Default of 0 will derive mask from num_channels and log a warning. |
| channelMask = 0; |
| } |
| |
| SLDataFormat_PCM format_pcm = { |
| SL_DATAFORMAT_PCM, |
| chunkFormat->num_channels, |
| chunkFormat->sample_rate * 1000, // convert to milliHz |
| chunkFormat->bits_per_sample, |
| 16, |
| channelMask, |
| SL_BYTEORDER_LITTLEENDIAN |
| }; |
| SLDataSource audioSrc = {&loc_bufq, &format_pcm}; |
| |
| // configure audio sink |
| SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; |
| SLDataSink audioSnk = {&loc_outmix, NULL}; |
| |
| // create audio player |
| const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_ANDROIDCONFIGURATION}; |
| const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; |
| result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, |
| 3, ids, req); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl CreateAudioPlayer failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // Use the System stream for boot sound playback. |
| SLAndroidConfigurationItf playerConfig; |
| result = (*bqPlayerObject)->GetInterface(bqPlayerObject, |
| SL_IID_ANDROIDCONFIGURATION, &playerConfig); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("config GetInterface failed with result %d", result); |
| return false; |
| } |
| SLint32 streamType = SL_ANDROID_STREAM_SYSTEM; |
| result = (*playerConfig)->SetConfiguration(playerConfig, |
| SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("SetConfiguration failed with result %d", result); |
| return false; |
| } |
| // use normal performance mode as low latency is not needed. This is not mandatory so |
| // do not bail if we fail |
| SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_NONE; |
| result = (*playerConfig)->SetConfiguration( |
| playerConfig, SL_ANDROID_KEY_PERFORMANCE_MODE, &performanceMode, sizeof(SLuint32)); |
| ALOGW_IF(result != SL_RESULT_SUCCESS, |
| "could not set performance mode on player, error %d", result); |
| (void)result; |
| |
| // realize the player |
| result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl player Realize failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // get the play interface |
| result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl player GetInterface failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // get the buffer queue interface |
| result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, |
| &bqPlayerBufferQueue); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl playberBufferQueue GetInterface failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // register callback on the buffer queue |
| result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl bqPlayerBufferQueue RegisterCallback failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // get the volume interface |
| result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); |
| if (result != SL_RESULT_SUCCESS) { |
| ALOGE("sl volume GetInterface failed with result %d", result); |
| return false; |
| } |
| (void)result; |
| |
| // set the player's state to playing |
| audioplay::setPlaying(true); |
| CHATTY("Created buffer queue player: %p", bqPlayerBufferQueue); |
| return true; |
| } |
| |
| bool parseClipBuf(const uint8_t* clipBuf, int clipBufSize, const ChunkFormat** oChunkFormat, |
| const uint8_t** oSoundBuf, unsigned* oSoundBufSize) { |
| *oSoundBuf = clipBuf; |
| *oSoundBufSize = clipBufSize; |
| *oChunkFormat = NULL; |
| const RiffWaveHeader* wavHeader = (const RiffWaveHeader*)*oSoundBuf; |
| if (*oSoundBufSize < sizeof(*wavHeader) || (wavHeader->riff_id != ID_RIFF) || |
| (wavHeader->wave_id != ID_WAVE)) { |
| ALOGE("Error: audio file is not a riff/wave file\n"); |
| return false; |
| } |
| *oSoundBuf += sizeof(*wavHeader); |
| *oSoundBufSize -= sizeof(*wavHeader); |
| |
| while (true) { |
| const ChunkHeader* chunkHeader = (const ChunkHeader*)*oSoundBuf; |
| if (*oSoundBufSize < sizeof(*chunkHeader)) { |
| ALOGE("EOF reading chunk headers"); |
| return false; |
| } |
| |
| *oSoundBuf += sizeof(*chunkHeader); |
| *oSoundBufSize -= sizeof(*chunkHeader); |
| |
| bool endLoop = false; |
| switch (chunkHeader->id) { |
| case ID_FMT: |
| *oChunkFormat = (const ChunkFormat*)*oSoundBuf; |
| *oSoundBuf += chunkHeader->sz; |
| *oSoundBufSize -= chunkHeader->sz; |
| break; |
| case ID_DATA: |
| /* Stop looking for chunks */ |
| *oSoundBufSize = chunkHeader->sz; |
| endLoop = true; |
| break; |
| default: |
| /* Unknown chunk, skip bytes */ |
| *oSoundBuf += chunkHeader->sz; |
| *oSoundBufSize -= chunkHeader->sz; |
| } |
| if (endLoop) { |
| break; |
| } |
| } |
| |
| if (*oChunkFormat == NULL) { |
| ALOGE("format not found in WAV file"); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| bool create(const uint8_t* exampleClipBuf, int exampleClipBufSize) { |
| if (!createEngine()) { |
| return false; |
| } |
| |
| // Parse the example clip. |
| const ChunkFormat* chunkFormat; |
| const uint8_t* soundBuf; |
| unsigned soundBufSize; |
| if (!parseClipBuf(exampleClipBuf, exampleClipBufSize, &chunkFormat, &soundBuf, &soundBufSize)) { |
| return false; |
| } |
| |
| // Initialize the BufferQueue based on this clip's format. |
| if (!createBufferQueueAudioPlayer(chunkFormat)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool playClip(const uint8_t* buf, int size) { |
| // Parse the WAV header |
| const ChunkFormat* chunkFormat; |
| if (!parseClipBuf(buf, size, &chunkFormat, &nextBuffer, &nextSize)) { |
| return false; |
| } |
| |
| if (!hasPlayer()) { |
| ALOGD("cannot play clip %p without a player", buf); |
| return false; |
| } |
| |
| CHATTY("playClip on player %p: buf=%p size=%d nextSize %d", |
| bqPlayerBufferQueue, buf, size, nextSize); |
| |
| if (nextSize > 0) { |
| // here we only enqueue one buffer because it is a long clip, |
| // but for streaming playback we would typically enqueue at least 2 buffers to start |
| SLresult result; |
| result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, nextBuffer, nextSize); |
| if (SL_RESULT_SUCCESS != result) { |
| return false; |
| } |
| audioplay::setPlaying(true); |
| } |
| |
| return true; |
| } |
| |
| // set the playing state for the buffer queue audio player |
| void setPlaying(bool isPlaying) { |
| if (!hasPlayer()) return; |
| |
| SLresult result; |
| |
| if (NULL != bqPlayerPlay) { |
| // set the player's state |
| result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, |
| isPlaying ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_STOPPED); |
| } |
| |
| } |
| |
| void destroy() { |
| // destroy buffer queue audio player object, and invalidate all associated interfaces |
| if (bqPlayerObject != NULL) { |
| CHATTY("destroying audio player"); |
| (*bqPlayerObject)->Destroy(bqPlayerObject); |
| bqPlayerObject = NULL; |
| bqPlayerPlay = NULL; |
| bqPlayerBufferQueue = NULL; |
| bqPlayerMuteSolo = NULL; |
| bqPlayerVolume = NULL; |
| } |
| |
| // destroy output mix object, and invalidate all associated interfaces |
| if (outputMixObject != NULL) { |
| (*outputMixObject)->Destroy(outputMixObject); |
| outputMixObject = NULL; |
| } |
| |
| // destroy engine object, and invalidate all associated interfaces |
| if (engineObject != NULL) { |
| CHATTY("destroying audio engine"); |
| (*engineObject)->Destroy(engineObject); |
| engineObject = NULL; |
| engineEngine = NULL; |
| } |
| } |
| |
| } // namespace audioplay |