/* * 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 #include #include // for native audio #include #include #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& 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 = 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 createAnimationCallbacks() { return new AudioAnimationCallbacks(); } } // namespace audioplay