diff options
| author | 2014-10-06 23:51:00 +0000 | |
|---|---|---|
| committer | 2014-10-06 23:51:00 +0000 | |
| commit | bb68c263620fe91eefb241efaf099742b3e6069c (patch) | |
| tree | dd74e12b4048e139a21024321d00ccf4abbbb40e | |
| parent | 5ade30e82e05c783d74e3a52316be4c3ed11239c (diff) | |
| parent | 3df6b76c5f6ca38f986305c805c9d90e0b861a41 (diff) | |
am 3df6b76c: am 56b7d562: Merge "Add support for playing audio during bootanimation" into lmp-dev
* commit '3df6b76c5f6ca38f986305c805c9d90e0b861a41':
Add support for playing audio during bootanimation
| -rw-r--r-- | cmds/bootanimation/Android.mk | 6 | ||||
| -rw-r--r-- | cmds/bootanimation/AudioPlayer.cpp | 311 | ||||
| -rw-r--r-- | cmds/bootanimation/AudioPlayer.h | 47 | ||||
| -rw-r--r-- | cmds/bootanimation/BootAnimation.cpp | 69 | ||||
| -rw-r--r-- | cmds/bootanimation/BootAnimation.h | 6 |
5 files changed, 421 insertions, 18 deletions
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index c4fe6cf2df5f..d6ecbe31f5e8 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -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 diff --git a/cmds/bootanimation/AudioPlayer.cpp b/cmds/bootanimation/AudioPlayer.cpp new file mode 100644 index 000000000000..a2ee7eadcbb6 --- /dev/null +++ b/cmds/bootanimation/AudioPlayer.cpp @@ -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 diff --git a/cmds/bootanimation/AudioPlayer.h b/cmds/bootanimation/AudioPlayer.h new file mode 100644 index 000000000000..7e82a07bc660 --- /dev/null +++ b/cmds/bootanimation/AudioPlayer.h @@ -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 diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 08923119823d..b2474f2f18bc 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -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::movie() +bool BootAnimation::readFile(const char* name, String8& outString) { - ZipEntryRO desc = mZip->findEntryByName("desc.txt"); - ALOGE_IF(!desc, "couldn't find desc.txt"); - if (!desc) { + ZipEntryRO entry = mZip->findEntryByName(name); + ALOGE_IF(!entry, "couldn't find %s", name); + if (!entry) { return false; } - FileMap* descMap = mZip->createEntryFileMap(desc); - mZip->releaseEntry(desc); - ALOGE_IF(!descMap, "descMap is null"); - if (!descMap) { + FileMap* entryMap = mZip->createEntryFileMap(entry); + mZip->releaseEntry(entry); + ALOGE_IF(!entryMap, "entryMap is null"); + if (!entryMap) { return false; } - String8 desString((char const*)descMap->getDataPtr(), - descMap->getDataLength()); - descMap->release(); + outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength()); + entryMap->release(); + return true; +} + +bool BootAnimation::movie() +{ + String8 desString; + + if (!readFile("desc.txt", desString)) { + return false; + } 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], diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 72cd62b150a6..f968b255d37a 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -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; |