| /* |
| * 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 |
| |
| #define CHATTY ALOGD |
| #define LOG_TAG "audioplay" |
| |
| #include "audioplay.h" |
| |
| #include <string.h> |
| |
| #include <utils/Log.h> |
| #include <utils/threads.h> |
| |
| // for native audio |
| #include <SLES/OpenSLES.h> |
| #include <SLES/OpenSLES_Android.h> |
| |
| #include "BootAnimationUtil.h" |
| |
| namespace audioplay { |
| namespace { |
| |
| using namespace android; |
| |
| // engine interfaces |
| static SLObjectItf engineObject = nullptr; |
| static SLEngineItf engineEngine; |
| |
| // output mix interfaces |
| static SLObjectItf outputMixObject = nullptr; |
| |
| // buffer queue player interfaces |
| static SLObjectItf bqPlayerObject = nullptr; |
| 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 != nullptr && bqPlayerObject != nullptr); |
| } |
| |
| // create the engine and output mix objects |
| bool createEngine() { |
| SLresult result; |
| |
| // create engine |
| result = slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr); |
| 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, nullptr, nullptr); |
| 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, nullptr}; |
| |
| // 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, nullptr); |
| 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 = nullptr; |
| 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 == nullptr) { |
| ALOGE("format not found in WAV file"); |
| return false; |
| } |
| return true; |
| } |
| |
| class InitAudioThread : public Thread { |
| public: |
| InitAudioThread(uint8_t* exampleAudioData, int exampleAudioLength) |
| : Thread(false), |
| mExampleAudioData(exampleAudioData), |
| mExampleAudioLength(exampleAudioLength) {} |
| private: |
| virtual bool threadLoop() { |
| audioplay::create(mExampleAudioData, mExampleAudioLength); |
| // Exit immediately |
| return false; |
| } |
| |
| uint8_t* mExampleAudioData; |
| int mExampleAudioLength; |
| }; |
| |
| // Typedef to aid readability. |
| typedef android::BootAnimation::Animation Animation; |
| |
| class AudioAnimationCallbacks : public android::BootAnimation::Callbacks { |
| public: |
| void init(const Vector<Animation::Part>& parts) override { |
| const Animation::Part* partWithAudio = nullptr; |
| for (const Animation::Part& part : parts) { |
| if (part.audioData != nullptr) { |
| partWithAudio = ∂ |
| break; |
| } |
| } |
| |
| if (partWithAudio == nullptr) { |
| return; |
| } |
| |
| ALOGD("found audio.wav, creating playback engine"); |
| // The audioData is used to initialize the audio system. Different data |
| // can be played later for other parts BUT the assumption is that they |
| // will all be the same format and only the format of this audioData |
| // will work correctly. |
| initAudioThread = new InitAudioThread(partWithAudio->audioData, |
| partWithAudio->audioLength); |
| initAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL); |
| }; |
| |
| void playPart(int partNumber, const Animation::Part& part, int playNumber) override { |
| // only play audio file the first time we animate the part |
| if (playNumber == 0 && part.audioData && playSoundsAllowed()) { |
| ALOGD("playing clip for part%d, size=%d", |
| partNumber, part.audioLength); |
| // Block until the audio engine is finished initializing. |
| if (initAudioThread != nullptr) { |
| initAudioThread->join(); |
| } |
| audioplay::playClip(part.audioData, part.audioLength); |
| } |
| }; |
| |
| void shutdown() override { |
| // we've finally played everything we're going to play |
| audioplay::setPlaying(false); |
| audioplay::destroy(); |
| }; |
| |
| private: |
| sp<InitAudioThread> initAudioThread = nullptr; |
| }; |
| |
| } // 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 (nullptr != 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 != nullptr) { |
| CHATTY("destroying audio player"); |
| (*bqPlayerObject)->Destroy(bqPlayerObject); |
| bqPlayerObject = nullptr; |
| bqPlayerPlay = nullptr; |
| bqPlayerBufferQueue = nullptr; |
| bqPlayerMuteSolo = nullptr; |
| bqPlayerVolume = nullptr; |
| } |
| |
| // destroy output mix object, and invalidate all associated interfaces |
| if (outputMixObject != nullptr) { |
| (*outputMixObject)->Destroy(outputMixObject); |
| outputMixObject = nullptr; |
| } |
| |
| // destroy engine object, and invalidate all associated interfaces |
| if (engineObject != nullptr) { |
| CHATTY("destroying audio engine"); |
| (*engineObject)->Destroy(engineObject); |
| engineObject = nullptr; |
| engineEngine = nullptr; |
| } |
| } |
| |
| sp<BootAnimation::Callbacks> createAnimationCallbacks() { |
| return new AudioAnimationCallbacks(); |
| } |
| |
| } // namespace audioplay |