You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
478 lines
15 KiB
478 lines
15 KiB
/*
|
|
* 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
|