| /* | |
| * 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 <binder/IServiceManager.h> | |
| #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() { | |
| if (defaultServiceManager()->checkService(String16("audio")) == nullptr) { | |
| ALOGW("Audio service is not ready yet, ignore creating playback engine"); | |
| return false; | |
| } | |
| 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; | |
| if (!playSoundsAllowed()) { | |
| return; | |
| } | |
| 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) { | |
| if (!hasPlayer()) { | |
| ALOGE("cannot play clip %p without a player", buf); | |
| return false; | |
| } | |
| // Parse the WAV header | |
| const ChunkFormat* chunkFormat; | |
| if (!parseClipBuf(buf, size, &chunkFormat, &nextBuffer, &nextSize)) { | |
| 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; | |
| if (nullptr != bqPlayerPlay) { | |
| // set the player's state | |
| (*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 |