am bb68c263: am 3df6b76c: am 56b7d562: Merge "Add support for playing audio during bootanimation" into lmp-dev
* commit 'bb68c263620fe91eefb241efaf099742b3e6069c': Add support for playing audio during bootanimation
This commit is contained in:
@@ -3,10 +3,13 @@ include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= \
|
||||
bootanimation_main.cpp \
|
||||
AudioPlayer.cpp \
|
||||
BootAnimation.cpp
|
||||
|
||||
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
|
||||
|
||||
LOCAL_C_INCLUDES += external/tinyalsa/include
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libcutils \
|
||||
liblog \
|
||||
@@ -17,7 +20,8 @@ LOCAL_SHARED_LIBRARIES := \
|
||||
libskia \
|
||||
libEGL \
|
||||
libGLESv1_CM \
|
||||
libgui
|
||||
libgui \
|
||||
libtinyalsa
|
||||
|
||||
LOCAL_MODULE:= bootanimation
|
||||
|
||||
|
||||
311
cmds/bootanimation/AudioPlayer.cpp
Normal file
311
cmds/bootanimation/AudioPlayer.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "BootAnim_AudioPlayer"
|
||||
|
||||
#include "AudioPlayer.h"
|
||||
|
||||
#include <androidfw/ZipFileRO.h>
|
||||
#include <tinyalsa/asoundlib.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
#define ID_RIFF 0x46464952
|
||||
#define ID_WAVE 0x45564157
|
||||
#define ID_FMT 0x20746d66
|
||||
#define ID_DATA 0x61746164
|
||||
|
||||
// Maximum line length for audio_conf.txt
|
||||
// We only accept lines less than this length to avoid overflows using sscanf()
|
||||
#define MAX_LINE_LENGTH 1024
|
||||
|
||||
struct riff_wave_header {
|
||||
uint32_t riff_id;
|
||||
uint32_t riff_sz;
|
||||
uint32_t wave_id;
|
||||
};
|
||||
|
||||
struct chunk_header {
|
||||
uint32_t id;
|
||||
uint32_t sz;
|
||||
};
|
||||
|
||||
struct chunk_fmt {
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
AudioPlayer::AudioPlayer()
|
||||
: mCard(-1),
|
||||
mDevice(-1),
|
||||
mPeriodSize(0),
|
||||
mPeriodCount(0),
|
||||
mCurrentFile(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
AudioPlayer::~AudioPlayer() {
|
||||
}
|
||||
|
||||
static bool setMixerValue(struct mixer* mixer, const char* name, const char* values)
|
||||
{
|
||||
if (!mixer) {
|
||||
ALOGE("no mixer in setMixerValue");
|
||||
return false;
|
||||
}
|
||||
struct mixer_ctl *ctl = mixer_get_ctl_by_name(mixer, name);
|
||||
if (!ctl) {
|
||||
ALOGE("mixer_get_ctl_by_name failed for %s", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
enum mixer_ctl_type type = mixer_ctl_get_type(ctl);
|
||||
int numValues = mixer_ctl_get_num_values(ctl);
|
||||
int intValue;
|
||||
char stringValue[MAX_LINE_LENGTH];
|
||||
|
||||
for (int i = 0; i < numValues && values; i++) {
|
||||
// strip leading space
|
||||
while (*values == ' ') values++;
|
||||
if (*values == 0) break;
|
||||
|
||||
switch (type) {
|
||||
case MIXER_CTL_TYPE_BOOL:
|
||||
case MIXER_CTL_TYPE_INT:
|
||||
if (sscanf(values, "%d", &intValue) == 1) {
|
||||
if (mixer_ctl_set_value(ctl, i, intValue) != 0) {
|
||||
ALOGE("mixer_ctl_set_value failed for %s %d", name, intValue);
|
||||
}
|
||||
} else {
|
||||
ALOGE("Could not parse %s as int for %d", intValue, name);
|
||||
}
|
||||
break;
|
||||
case MIXER_CTL_TYPE_ENUM:
|
||||
if (sscanf(values, "%s", stringValue) == 1) {
|
||||
if (mixer_ctl_set_enum_by_string(ctl, stringValue) != 0) {
|
||||
ALOGE("mixer_ctl_set_enum_by_string failed for %s %%s", name, stringValue);
|
||||
}
|
||||
} else {
|
||||
ALOGE("Could not parse %s as enum for %d", stringValue, name);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ALOGE("unsupported mixer type %d for %s", type, name);
|
||||
break;
|
||||
}
|
||||
|
||||
values = strchr(values, ' ');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Parse the audio configuration file.
|
||||
* The file is named audio_conf.txt and must begin with the following header:
|
||||
*
|
||||
* card=<ALSA card number>
|
||||
* device=<ALSA device number>
|
||||
* period_size=<period size>
|
||||
* period_count=<period count>
|
||||
*
|
||||
* This header is followed by zero or more mixer settings, each with the format:
|
||||
* mixer "<name>" = <value list>
|
||||
* Since mixer names can contain spaces, the name must be enclosed in double quotes.
|
||||
* The values in the value list can be integers, booleans (represented by 0 or 1)
|
||||
* or strings for enum values.
|
||||
*/
|
||||
bool AudioPlayer::init(const char* config)
|
||||
{
|
||||
int tempInt;
|
||||
struct mixer* mixer = NULL;
|
||||
char name[MAX_LINE_LENGTH];
|
||||
|
||||
for (;;) {
|
||||
const char* endl = strstr(config, "\n");
|
||||
if (!endl) break;
|
||||
String8 line(config, endl - config);
|
||||
if (line.length() >= MAX_LINE_LENGTH) {
|
||||
ALOGE("Line too long in audio_conf.txt");
|
||||
return false;
|
||||
}
|
||||
const char* l = line.string();
|
||||
|
||||
if (sscanf(l, "card=%d", &tempInt) == 1) {
|
||||
ALOGD("card=%d", tempInt);
|
||||
mCard = tempInt;
|
||||
|
||||
mixer = mixer_open(mCard);
|
||||
if (!mixer) {
|
||||
ALOGE("could not open mixer for card %d", mCard);
|
||||
return false;
|
||||
}
|
||||
} else if (sscanf(l, "device=%d", &tempInt) == 1) {
|
||||
ALOGD("device=%d", tempInt);
|
||||
mDevice = tempInt;
|
||||
} else if (sscanf(l, "period_size=%d", &tempInt) == 1) {
|
||||
ALOGD("period_size=%d", tempInt);
|
||||
mPeriodSize = tempInt;
|
||||
} else if (sscanf(l, "period_count=%d", &tempInt) == 1) {
|
||||
ALOGD("period_count=%d", tempInt);
|
||||
mPeriodCount = tempInt;
|
||||
} else if (sscanf(l, "mixer \"%[0-9a-zA-Z _]s\"", name) == 1) {
|
||||
const char* values = strchr(l, '=');
|
||||
if (values) {
|
||||
values++; // skip '='
|
||||
ALOGD("name: \"%s\" = %s", name, values);
|
||||
setMixerValue(mixer, name, values);
|
||||
} else {
|
||||
ALOGE("values missing for name: \"%s\"", name);
|
||||
}
|
||||
}
|
||||
config = ++endl;
|
||||
}
|
||||
|
||||
mixer_close(mixer);
|
||||
|
||||
if (mCard >= 0 && mDevice >= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioPlayer::playFile(struct FileMap* fileMap) {
|
||||
// stop any currently playing sound
|
||||
requestExitAndWait();
|
||||
|
||||
mCurrentFile = fileMap;
|
||||
run("bootanim audio", PRIORITY_URGENT_AUDIO);
|
||||
}
|
||||
|
||||
bool AudioPlayer::threadLoop()
|
||||
{
|
||||
struct pcm_config config;
|
||||
struct pcm *pcm = NULL;
|
||||
bool moreChunks = true;
|
||||
const struct chunk_fmt* chunkFmt = NULL;
|
||||
void* buffer = NULL;
|
||||
int bufferSize;
|
||||
const uint8_t* wavData;
|
||||
size_t wavLength;
|
||||
const struct riff_wave_header* wavHeader;
|
||||
|
||||
if (mCurrentFile == NULL) {
|
||||
ALOGE("mCurrentFile is NULL");
|
||||
return false;
|
||||
}
|
||||
|
||||
wavData = (const uint8_t *)mCurrentFile->getDataPtr();
|
||||
if (!wavData) {
|
||||
ALOGE("Could not access WAV file data");
|
||||
goto exit;
|
||||
}
|
||||
wavLength = mCurrentFile->getDataLength();
|
||||
|
||||
wavHeader = (const struct riff_wave_header *)wavData;
|
||||
if (wavLength < sizeof(*wavHeader) || (wavHeader->riff_id != ID_RIFF) ||
|
||||
(wavHeader->wave_id != ID_WAVE)) {
|
||||
ALOGE("Error: audio file is not a riff/wave file\n");
|
||||
goto exit;
|
||||
}
|
||||
wavData += sizeof(*wavHeader);
|
||||
wavLength -= sizeof(*wavHeader);
|
||||
|
||||
do {
|
||||
const struct chunk_header* chunkHeader = (const struct chunk_header*)wavData;
|
||||
if (wavLength < sizeof(*chunkHeader)) {
|
||||
ALOGE("EOF reading chunk headers");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
wavData += sizeof(*chunkHeader);
|
||||
wavLength -= sizeof(*chunkHeader);
|
||||
|
||||
switch (chunkHeader->id) {
|
||||
case ID_FMT:
|
||||
chunkFmt = (const struct chunk_fmt *)wavData;
|
||||
wavData += chunkHeader->sz;
|
||||
wavLength -= chunkHeader->sz;
|
||||
break;
|
||||
case ID_DATA:
|
||||
/* Stop looking for chunks */
|
||||
moreChunks = 0;
|
||||
break;
|
||||
default:
|
||||
/* Unknown chunk, skip bytes */
|
||||
wavData += chunkHeader->sz;
|
||||
wavLength -= chunkHeader->sz;
|
||||
}
|
||||
} while (moreChunks);
|
||||
|
||||
if (!chunkFmt) {
|
||||
ALOGE("format not found in WAV file");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.channels = chunkFmt->num_channels;
|
||||
config.rate = chunkFmt->sample_rate;
|
||||
config.period_size = mPeriodSize;
|
||||
config.period_count = mPeriodCount;
|
||||
if (chunkFmt->bits_per_sample != 16) {
|
||||
ALOGE("only 16 bit WAV files are supported");
|
||||
goto exit;
|
||||
}
|
||||
config.format = PCM_FORMAT_S16_LE;
|
||||
|
||||
pcm = pcm_open(mCard, mDevice, PCM_OUT, &config);
|
||||
if (!pcm || !pcm_is_ready(pcm)) {
|
||||
ALOGE("Unable to open PCM device (%s)\n", pcm_get_error(pcm));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bufferSize = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
|
||||
|
||||
while (wavLength > 0) {
|
||||
if (exitPending()) goto exit;
|
||||
size_t count = bufferSize;
|
||||
if (count > wavLength)
|
||||
count = wavLength;
|
||||
|
||||
if (pcm_write(pcm, wavData, count)) {
|
||||
ALOGE("pcm_write failed (%s)", pcm_get_error(pcm));
|
||||
goto exit;
|
||||
}
|
||||
wavData += count;
|
||||
wavLength -= count;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (pcm)
|
||||
pcm_close(pcm);
|
||||
mCurrentFile->release();
|
||||
mCurrentFile = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
47
cmds/bootanimation/AudioPlayer.h
Normal file
47
cmds/bootanimation/AudioPlayer.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
#ifndef _BOOTANIMATION_AUDIOPLAYER_H
|
||||
#define _BOOTANIMATION_AUDIOPLAYER_H
|
||||
|
||||
#include <utils/Thread.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class AudioPlayer : public Thread
|
||||
{
|
||||
public:
|
||||
AudioPlayer();
|
||||
virtual ~AudioPlayer();
|
||||
bool init(const char* config);
|
||||
|
||||
void playFile(struct FileMap* fileMap);
|
||||
|
||||
private:
|
||||
virtual bool threadLoop();
|
||||
|
||||
private:
|
||||
int mCard; // ALSA card to use
|
||||
int mDevice; // ALSA device to use
|
||||
int mPeriodSize;
|
||||
int mPeriodCount;
|
||||
|
||||
struct FileMap* mCurrentFile;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _BOOTANIMATION_AUDIOPLAYER_H
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "BootAnimation"
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -30,7 +31,6 @@
|
||||
#include <utils/Atomic.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/threads.h>
|
||||
|
||||
#include <ui/PixelFormat.h>
|
||||
#include <ui/Rect.h>
|
||||
@@ -50,6 +50,7 @@
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "BootAnimation.h"
|
||||
#include "AudioPlayer.h"
|
||||
|
||||
#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"
|
||||
#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
|
||||
@@ -99,6 +100,9 @@ void BootAnimation::binderDied(const wp<IBinder>&)
|
||||
// might be blocked on a condition variable that will never be updated.
|
||||
kill( getpid(), SIGKILL );
|
||||
requestExit();
|
||||
if (mAudioPlayer != NULL) {
|
||||
mAudioPlayer->requestExit();
|
||||
}
|
||||
}
|
||||
|
||||
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
|
||||
@@ -394,6 +398,9 @@ void BootAnimation::checkExit() {
|
||||
int exitnow = atoi(value);
|
||||
if (exitnow) {
|
||||
requestExit();
|
||||
if (mAudioPlayer != NULL) {
|
||||
mAudioPlayer->requestExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,26 +429,45 @@ static bool parseColor(const char str[7], float color[3]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BootAnimation::readFile(const char* name, String8& outString)
|
||||
{
|
||||
ZipEntryRO entry = mZip->findEntryByName(name);
|
||||
ALOGE_IF(!entry, "couldn't find %s", name);
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileMap* entryMap = mZip->createEntryFileMap(entry);
|
||||
mZip->releaseEntry(entry);
|
||||
ALOGE_IF(!entryMap, "entryMap is null");
|
||||
if (!entryMap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength());
|
||||
entryMap->release();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BootAnimation::movie()
|
||||
{
|
||||
ZipEntryRO desc = mZip->findEntryByName("desc.txt");
|
||||
ALOGE_IF(!desc, "couldn't find desc.txt");
|
||||
if (!desc) {
|
||||
String8 desString;
|
||||
|
||||
if (!readFile("desc.txt", desString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileMap* descMap = mZip->createEntryFileMap(desc);
|
||||
mZip->releaseEntry(desc);
|
||||
ALOGE_IF(!descMap, "descMap is null");
|
||||
if (!descMap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String8 desString((char const*)descMap->getDataPtr(),
|
||||
descMap->getDataLength());
|
||||
descMap->release();
|
||||
char const* s = desString.string();
|
||||
|
||||
// Create and initialize an AudioPlayer if we have an audio_conf.txt file
|
||||
String8 audioConf;
|
||||
if (readFile("audio_conf.txt", audioConf)) {
|
||||
mAudioPlayer = new AudioPlayer;
|
||||
if (!mAudioPlayer->init(audioConf.string())) {
|
||||
ALOGE("mAudioPlayer.init failed");
|
||||
mAudioPlayer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Animation animation;
|
||||
|
||||
// Parse the description file
|
||||
@@ -468,6 +494,7 @@ bool BootAnimation::movie()
|
||||
part.count = count;
|
||||
part.pause = pause;
|
||||
part.path = path;
|
||||
part.audioFile = NULL;
|
||||
if (!parseColor(color, part.backgroundColor)) {
|
||||
ALOGE("> invalid color '#%s'", color);
|
||||
part.backgroundColor[0] = 0.0f;
|
||||
@@ -508,11 +535,16 @@ bool BootAnimation::movie()
|
||||
if (method == ZipFileRO::kCompressStored) {
|
||||
FileMap* map = mZip->createEntryFileMap(entry);
|
||||
if (map) {
|
||||
Animation::Frame frame;
|
||||
frame.name = leaf;
|
||||
frame.map = map;
|
||||
Animation::Part& part(animation.parts.editItemAt(j));
|
||||
part.frames.add(frame);
|
||||
if (leaf == "audio.wav") {
|
||||
// a part may have at most one audio file
|
||||
part.audioFile = map;
|
||||
} else {
|
||||
Animation::Frame frame;
|
||||
frame.name = leaf;
|
||||
frame.map = map;
|
||||
part.frames.add(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,6 +591,11 @@ bool BootAnimation::movie()
|
||||
if(exitPending() && !part.playUntilComplete)
|
||||
break;
|
||||
|
||||
// only play audio file the first time we animate the part
|
||||
if (r == 0 && mAudioPlayer != NULL && part.audioFile) {
|
||||
mAudioPlayer->playFile(part.audioFile);
|
||||
}
|
||||
|
||||
glClearColor(
|
||||
part.backgroundColor[0],
|
||||
part.backgroundColor[1],
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <utils/threads.h>
|
||||
#include <utils/Thread.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES/gl.h>
|
||||
@@ -30,6 +30,7 @@ class SkBitmap;
|
||||
|
||||
namespace android {
|
||||
|
||||
class AudioPlayer;
|
||||
class Surface;
|
||||
class SurfaceComposerClient;
|
||||
class SurfaceControl;
|
||||
@@ -72,6 +73,7 @@ private:
|
||||
SortedVector<Frame> frames;
|
||||
bool playUntilComplete;
|
||||
float backgroundColor[3];
|
||||
FileMap* audioFile;
|
||||
};
|
||||
int fps;
|
||||
int width;
|
||||
@@ -82,11 +84,13 @@ private:
|
||||
status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
|
||||
status_t initTexture(const Animation::Frame& frame);
|
||||
bool android();
|
||||
bool readFile(const char* name, String8& outString);
|
||||
bool movie();
|
||||
|
||||
void checkExit();
|
||||
|
||||
sp<SurfaceComposerClient> mSession;
|
||||
sp<AudioPlayer> mAudioPlayer;
|
||||
AssetManager mAssets;
|
||||
Texture mAndroid[2];
|
||||
int mWidth;
|
||||
|
||||
Reference in New Issue
Block a user