diff options
Diffstat (limited to 'cmds')
| -rw-r--r-- | cmds/am/src/com/android/commands/am/Am.java | 4 | ||||
| -rw-r--r-- | cmds/app_process/app_main.cpp | 5 | ||||
| -rw-r--r-- | cmds/bootanimation/Android.mk | 7 | ||||
| -rw-r--r-- | cmds/bootanimation/AudioPlayer.cpp | 313 | ||||
| -rw-r--r-- | cmds/bootanimation/AudioPlayer.h | 48 | ||||
| -rw-r--r-- | cmds/bootanimation/BootAnimation.cpp | 182 | ||||
| -rw-r--r-- | cmds/bootanimation/BootAnimation.h | 11 | ||||
| -rw-r--r-- | cmds/bootanimation/FORMAT.md | 101 | ||||
| -rw-r--r-- | cmds/bootanimation/audioplay.cpp | 361 | ||||
| -rw-r--r-- | cmds/bootanimation/audioplay.h | 37 | ||||
| -rw-r--r-- | cmds/bootanimation/bootanim.rc | 1 |
11 files changed, 638 insertions, 432 deletions
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 8ccd5d2ebcba..d6c00589e7c2 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -723,10 +723,10 @@ public class Am extends BaseCommand { System.out.println("Complete"); } mRepeat--; - if (mRepeat > 1) { + if (mRepeat > 0) { mAm.unhandledBack(); } - } while (mRepeat > 1); + } while (mRepeat > 0); } private void runForceStop() throws Exception { diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 2e023825a219..2093579bb274 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -299,8 +299,9 @@ int main(int argc, char* const argv[]) } if (!niceName.isEmpty()) { - runtime.setArgv0(niceName.string()); - set_process_name(niceName.string()); + const char* procName = niceName.string(); + pthread_setname_np(pthread_self(), procName); + runtime.setArgv0(procName); } if (zygote) { diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index 7c8842ca0231..3a92b9e74144 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -3,14 +3,16 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ bootanimation_main.cpp \ - AudioPlayer.cpp \ + audioplay.cpp \ BootAnimation.cpp LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code -LOCAL_C_INCLUDES += external/tinyalsa/include +LOCAL_C_INCLUDES += \ + external/tinyalsa/include \ + frameworks/wilhelm/include LOCAL_SHARED_LIBRARIES := \ libcutils \ @@ -23,6 +25,7 @@ LOCAL_SHARED_LIBRARIES := \ libEGL \ libGLESv1_CM \ libgui \ + libOpenSLES \ libtinyalsa LOCAL_MODULE:= bootanimation diff --git a/cmds/bootanimation/AudioPlayer.cpp b/cmds/bootanimation/AudioPlayer.cpp deleted file mode 100644 index 293213008d58..000000000000 --- a/cmds/bootanimation/AudioPlayer.cpp +++ /dev/null @@ -1,313 +0,0 @@ -/* - * 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 %s", values, 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 %s", values, 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(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; - 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; - config.start_threshold = mPeriodSize / 4; - config.stop_threshold = INT_MAX; - config.avail_min = config.start_threshold; - 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); - delete mCurrentFile; - mCurrentFile = NULL; - return false; -} - -} // namespace android diff --git a/cmds/bootanimation/AudioPlayer.h b/cmds/bootanimation/AudioPlayer.h deleted file mode 100644 index 1def0aeac8d1..000000000000 --- a/cmds/bootanimation/AudioPlayer.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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> -#include <utils/FileMap.h> - -namespace android { - -class AudioPlayer : public Thread -{ -public: - AudioPlayer(); - virtual ~AudioPlayer(); - bool init(const char* config); - - void playFile(FileMap* fileMap); - -private: - virtual bool threadLoop(); - -private: - int mCard; // ALSA card to use - int mDevice; // ALSA device to use - int mPeriodSize; - int mPeriodCount; - - FileMap* mCurrentFile; -}; - -} // namespace android - -#endif // _BOOTANIMATION_AUDIOPLAYER_H diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index e8fcd3b8db5d..e634717cfe32 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -49,8 +49,8 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #include <SkBitmap.h> +#include <SkImage.h> #include <SkStream.h> -#include <SkImageDecoder.h> #pragma GCC diagnostic pop #include <GLES/gl.h> @@ -58,7 +58,7 @@ #include <EGL/eglext.h> #include "BootAnimation.h" -#include "AudioPlayer.h" +#include "audioplay.h" namespace android { @@ -72,6 +72,8 @@ static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change"; static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change"; static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate"; static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate"; +// Java timestamp format. Don't show the clock if the date is before 2000-01-01 00:00:00. +static const long long ACCURATE_TIME_EPOCH = 946684800000; static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; static const int ANIM_ENTRY_NAME_MAX = 256; @@ -106,9 +108,7 @@ 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(); - } + audioplay::destroy(); } status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, @@ -117,8 +117,10 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, if (asset == NULL) return NO_INIT; SkBitmap bitmap; - SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(), - &bitmap, kUnknown_SkColorType, SkImageDecoder::kDecodePixels_Mode); + sk_sp<SkData> data = SkData::MakeWithoutCopy(asset->getBuffer(false), + asset->getLength()); + sk_sp<SkImage> image = SkImage::MakeFromEncoded(data); + image->asLegacyBitmap(&bitmap, SkImage::kRO_LegacyBitmapMode); asset->close(); delete asset; @@ -171,15 +173,10 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) //StopWatch watch("blah"); SkBitmap bitmap; - SkMemoryStream stream(frame.map->getDataPtr(), frame.map->getDataLength()); - SkImageDecoder* codec = SkImageDecoder::Factory(&stream); - if (codec != NULL) { - codec->setDitherImage(false); - codec->decode(&stream, &bitmap, - kN32_SkColorType, - SkImageDecoder::kDecodePixels_Mode); - delete codec; - } + sk_sp<SkData> data = SkData::MakeWithoutCopy(frame.map->getDataPtr(), + frame.map->getDataLength()); + sk_sp<SkImage> image = SkImage::MakeFromEncoded(data); + image->asLegacyBitmap(&bitmap, SkImage::kRO_LegacyBitmapMode); // FileMap memory is never released until application exit. // Release it now as the texture is already loaded and the memory used for @@ -202,25 +199,25 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) switch (bitmap.colorType()) { case kN32_SkColorType: - if (tw != w || th != h) { + if (!mUseNpotTextures && (tw != w || th != h)) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p); } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, p); } break; case kRGB_565_SkColorType: - if (tw != w || th != h) { + if (!mUseNpotTextures && (tw != w || th != h)) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); } break; @@ -400,9 +397,6 @@ void BootAnimation::checkExit() { int exitnow = atoi(value); if (exitnow) { requestExit(); - if (mAudioPlayer != NULL) { - mAudioPlayer->requestExit(); - } } } @@ -524,16 +518,6 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) } char const* s = desString.string(); - // Create and initialize an AudioPlayer if we have an audio_conf.txt file - String8 audioConf; - if (readFile(animation.zip, "audio_conf.txt", audioConf)) { - mAudioPlayer = new AudioPlayer; - if (!mAudioPlayer->init(audioConf.string())) { - ALOGE("mAudioPlayer.init failed"); - mAudioPlayer = NULL; - } - } - // Parse the description file for (;;) { const char* endl = strstr(s, "\n"); @@ -564,7 +548,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) part.pause = pause; part.path = path; part.clockPosY = clockPosY; - part.audioFile = NULL; + part.audioData = NULL; part.animation = NULL; if (!parseColor(color, part.backgroundColor)) { ALOGE("> invalid color '#%s'", color); @@ -580,7 +564,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) part.playUntilComplete = false; part.count = 1; part.pause = 0; - part.audioFile = NULL; + part.audioData = NULL; part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE)); if (part.animation != NULL) animation.parts.add(part); @@ -596,15 +580,16 @@ bool BootAnimation::preloadZip(Animation& animation) // read all the data structures const size_t pcount = animation.parts.size(); void *cookie = NULL; - ZipFileRO* mZip = animation.zip; - if (!mZip->startIteration(&cookie)) { + ZipFileRO* zip = animation.zip; + if (!zip->startIteration(&cookie)) { return false; } + Animation::Part* partWithAudio = NULL; ZipEntryRO entry; char name[ANIM_ENTRY_NAME_MAX]; - while ((entry = mZip->nextEntry(cookie)) != NULL) { - const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX); + while ((entry = zip->nextEntry(cookie)) != NULL) { + const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX); if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) { ALOGE("Error fetching entry file name"); continue; @@ -614,25 +599,36 @@ bool BootAnimation::preloadZip(Animation& animation) const String8 path(entryName.getPathDir()); const String8 leaf(entryName.getPathLeaf()); if (leaf.size() > 0) { - for (size_t j=0 ; j<pcount ; j++) { + for (size_t j = 0; j < pcount; j++) { if (path == animation.parts[j].path) { uint16_t method; // supports only stored png files - if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { + if (zip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { if (method == ZipFileRO::kCompressStored) { - FileMap* map = mZip->createEntryFileMap(entry); + FileMap* map = zip->createEntryFileMap(entry); if (map) { Animation::Part& part(animation.parts.editItemAt(j)); if (leaf == "audio.wav") { // a part may have at most one audio file - part.audioFile = map; + part.audioData = (uint8_t *)map->getDataPtr(); + part.audioLength = map->getDataLength(); + partWithAudio = ∂ + } else if (leaf == "trim.txt") { + part.trimData.setTo((char const*)map->getDataPtr(), + map->getDataLength()); } else { Animation::Frame frame; frame.name = leaf; frame.map = map; + frame.trimWidth = animation.width; + frame.trimHeight = animation.height; + frame.trimX = 0; + frame.trimY = 0; part.frames.add(frame); } } + } else { + ALOGE("bootanimation.zip is compressed; must be only stored"); } } } @@ -640,7 +636,41 @@ bool BootAnimation::preloadZip(Animation& animation) } } - mZip->endIteration(cookie); + // If there is trimData present, override the positioning defaults. + for (Animation::Part& part : animation.parts) { + const char* trimDataStr = part.trimData.string(); + for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) { + const char* endl = strstr(trimDataStr, "\n"); + // No more trimData for this part. + if (endl == NULL) { + break; + } + String8 line(trimDataStr, endl - trimDataStr); + const char* lineStr = line.string(); + trimDataStr = ++endl; + int width = 0, height = 0, x = 0, y = 0; + if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) { + Animation::Frame& frame(part.frames.editItemAt(frameIdx)); + frame.trimWidth = width; + frame.trimHeight = height; + frame.trimX = x; + frame.trimY = y; + } else { + ALOGE("Error parsing trim.txt, line: %s", lineStr); + break; + } + } + } + + // Create and initialize audioplay if there is a wav file in any of the animations. + if (partWithAudio != NULL) { + ALOGD("found audio.wav, creating playback engine"); + if (!audioplay::create(partWithAudio->audioData, partWithAudio->audioLength)) { + return false; + } + } + + zip->endIteration(cookie); return true; } @@ -662,6 +692,20 @@ bool BootAnimation::movie() mClockEnabled = false; } + // Check if npot textures are supported + mUseNpotTextures = false; + String8 gl_extensions; + const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)); + if (!exts) { + glGetError(); + } else { + gl_extensions.setTo(exts); + if ((gl_extensions.find("GL_ARB_texture_non_power_of_two") != -1) || + (gl_extensions.find("GL_OES_texture_npot") != -1)) { + mUseNpotTextures = true; + } + } + // Blend required to draw time on top of animation frames. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glShadeModel(GL_FLAT); @@ -707,12 +751,9 @@ bool BootAnimation::movie() bool BootAnimation::playAnimation(const Animation& animation) { const size_t pcount = animation.parts.size(); - const int xc = (mWidth - animation.width) / 2; - const int yc = ((mHeight - animation.height) / 2); nsecs_t frameDuration = s2ns(1) / animation.fps; - - Region clearReg(Rect(mWidth, mHeight)); - clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height)); + const int animationX = (mWidth - animation.width) / 2; + const int animationY = (mHeight - animation.height) / 2; for (size_t i=0 ; i<pcount ; i++) { const Animation::Part& part(animation.parts[i]); @@ -733,8 +774,9 @@ bool BootAnimation::playAnimation(const Animation& animation) break; // only play audio file the first time we animate the part - if (r == 0 && mAudioPlayer != NULL && part.audioFile) { - mAudioPlayer->playFile(part.audioFile); + if (r == 0 && part.audioData) { + ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); + audioplay::playClip(part.audioData, part.audioLength); } glClearColor( @@ -759,22 +801,25 @@ bool BootAnimation::playAnimation(const Animation& animation) initTexture(frame); } + const int xc = animationX + frame.trimX; + const int yc = animationY + frame.trimY; + Region clearReg(Rect(mWidth, mHeight)); + clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight)); if (!clearReg.isEmpty()) { Region::const_iterator head(clearReg.begin()); Region::const_iterator tail(clearReg.end()); glEnable(GL_SCISSOR_TEST); while (head != tail) { const Rect& r2(*head++); - glScissor(r2.left, mHeight - r2.bottom, - r2.width(), r2.height()); + glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height()); glClear(GL_COLOR_BUFFER_BIT); } glDisable(GL_SCISSOR_TEST); } - // specify the y center as ceiling((mHeight - animation.height) / 2) - // which is equivalent to mHeight - (yc + animation.height) - glDrawTexiOES(xc, mHeight - (yc + animation.height), - 0, animation.width, animation.height); + // specify the y center as ceiling((mHeight - frame.trimHeight) / 2) + // which is equivalent to mHeight - (yc + frame.trimHeight) + glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight), + 0, frame.trimWidth, frame.trimHeight); if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) { drawTime(mClock, part.clockPosY); } @@ -806,14 +851,23 @@ bool BootAnimation::playAnimation(const Animation& animation) break; } - // free the textures for this part + } + + // Free textures created for looping parts now that the animation is done. + for (const Animation::Part& part : animation.parts) { if (part.count != 1) { - for (size_t j=0 ; j<fcount ; j++) { + const size_t fcount = part.frames.size(); + for (size_t j = 0; j < fcount; j++) { const Animation::Frame& frame(part.frames[j]); glDeleteTextures(1, &frame.tid); } } } + + // we've finally played everything we're going to play + audioplay::setPlaying(false); + audioplay::destroy(); + return true; } @@ -849,7 +903,10 @@ BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) mLoadedFiles.add(animation->fileName); parseAnimationDesc(*animation); - preloadZip(*animation); + if (!preloadZip(*animation)) { + return NULL; + } + mLoadedFiles.remove(fn); return animation; @@ -879,8 +936,9 @@ bool BootAnimation::updateIsTimeAccurate() { clock_gettime(CLOCK_REALTIME, &now); // Match the Java timestamp format long long rtcNow = (now.tv_sec * 1000LL) + (now.tv_nsec / 1000000LL); - if (lastChangedTime > rtcNow - MAX_TIME_IN_PAST - && lastChangedTime < rtcNow + MAX_TIME_IN_FUTURE) { + if (ACCURATE_TIME_EPOCH < rtcNow + && lastChangedTime > (rtcNow - MAX_TIME_IN_PAST) + && lastChangedTime < (rtcNow + MAX_TIME_IN_FUTURE)) { mTimeIsAccurate = true; } } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 1c3d53a59bb8..a53216eb0310 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -30,7 +30,6 @@ class SkBitmap; namespace android { -class AudioPlayer; class Surface; class SurfaceComposerClient; class SurfaceControl; @@ -79,6 +78,10 @@ private: struct Frame { String8 name; FileMap* map; + int trimX; + int trimY; + int trimWidth; + int trimHeight; mutable GLuint tid; bool operator < (const Frame& rhs) const { return name < rhs.name; @@ -90,10 +93,12 @@ private: int clockPosY; // The y position of the clock, in pixels, from the bottom of the // display (the clock is centred horizontally). -1 to disable the clock String8 path; + String8 trimData; SortedVector<Frame> frames; bool playUntilComplete; float backgroundColor[3]; - FileMap* audioFile; + uint8_t* audioData; + int audioLength; Animation* animation; }; int fps; @@ -119,12 +124,12 @@ private: void checkExit(); sp<SurfaceComposerClient> mSession; - sp<AudioPlayer> mAudioPlayer; AssetManager mAssets; Texture mAndroid[2]; Texture mClock; int mWidth; int mHeight; + bool mUseNpotTextures = false; EGLDisplay mDisplay; EGLDisplay mContext; EGLDisplay mSurface; diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md new file mode 100644 index 000000000000..9ea6fea966f2 --- /dev/null +++ b/cmds/bootanimation/FORMAT.md @@ -0,0 +1,101 @@ +# bootanimation format + +## zipfile paths + +The system selects a boot animation zipfile from the following locations, in order: + + /system/media/bootanimation-encrypted.zip (if getprop("vold.decrypt") = '1') + /system/media/bootanimation.zip + /oem/media/bootanimation.zip + +## zipfile layout + +The `bootanimation.zip` archive file includes: + + desc.txt - a text file + part0 \ + part1 \ directories full of PNG frames + ... / + partN / + +## desc.txt format + +The first line defines the general parameters of the animation: + + WIDTH HEIGHT FPS + + * **WIDTH:** animation width (pixels) + * **HEIGHT:** animation height (pixels) + * **FPS:** frames per second, e.g. 60 + +It is followed by a number of rows of the form: + + TYPE COUNT PAUSE PATH [#RGBHEX CLOCK] + + * **TYPE:** a single char indicating what type of animation segment this is: + + `p` -- this part will play unless interrupted by the end of the boot + + `c` -- this part will play to completion, no matter what + * **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete + * **PAUSE:** number of FRAMES to delay after this part ends + * **PATH:** directory in which to find the frames for this part (e.g. `part0`) + * **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB` + * **CLOCK:** _(OPTIONAL)_ the y-coordinate at which to draw the current time (for watches) + +There is also a special TYPE, `$SYSTEM`, that loads `/system/media/bootanimation.zip` +and plays that. + +## loading and playing frames + +Each part is scanned and loaded directly from the zip archive. Within a part directory, every file +(except `trim.txt` and `audio.wav`; see next sections) is expected to be a PNG file that represents +one frame in that part (at the specified resolution). For this reason it is important that frames be +named sequentially (e.g. `part000.png`, `part001.png`, ...) and added to the zip archive in that +order. + +## trim.txt + +To save on memory, textures may be trimmed by their background color. trim.txt sequentially lists +the trim output for each frame in its directory, so the frames may be properly positioned. +Output should be of the form: `WxH+X+Y`. Example: + + 713x165+388+914 + 708x152+388+912 + 707x139+388+911 + 649x92+388+910 + +If the file is not present, each frame is assumed to be the same size as the animation. + +## audio.wav + +Each part may optionally play a `wav` sample when it starts. To enable this, add a file +with the name `audio.wav` in the part directory. + +## exiting + +The system will end the boot animation (first completing any incomplete or even entirely unplayed +parts that are of type `c`) when the system is finished booting. (This is accomplished by setting +the system property `service.bootanim.exit` to a nonzero string.) + +## protips + +### PNG compression + +Use `zopflipng` if you have it, otherwise `pngcrush` will do. e.g.: + + for fn in *.png ; do + zopflipng -m ${fn}s ${fn}s.new && mv -f ${fn}s.new ${fn} + # or: pngcrush -q .... + done + +Some animations benefit from being reduced to 256 colors: + + pngquant --force --ext .png *.png + # alternatively: mogrify -colors 256 anim-tmp/*/*.png + +### creating the ZIP archive + + cd <path-to-pieces> + zip -0qry -i \*.txt \*.png \*.wav @ ../bootanimation.zip *.txt part* + +Note that the ZIP archive is not actually compressed! The PNG files are already as compressed +as they can reasonably get, and there is unlikely to be any redundancy between files. diff --git a/cmds/bootanimation/audioplay.cpp b/cmds/bootanimation/audioplay.cpp new file mode 100644 index 000000000000..8a5c2c6d229c --- /dev/null +++ b/cmds/bootanimation/audioplay.cpp @@ -0,0 +1,361 @@ +/* + * 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 + +#include "audioplay.h" + +#define CHATTY ALOGD + +#include <string.h> + +#include <utils/Log.h> + +// for native audio +#include <SLES/OpenSLES.h> +#include <SLES/OpenSLES_Android.h> + +namespace audioplay { +namespace { + +// engine interfaces +static SLObjectItf engineObject = NULL; +static SLEngineItf engineEngine; + +// output mix interfaces +static SLObjectItf outputMixObject = NULL; + +// buffer queue player interfaces +static SLObjectItf bqPlayerObject = NULL; +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 != NULL && bqPlayerObject != NULL); +} + +// create the engine and output mix objects +bool createEngine() { + SLresult result; + + // create engine + result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + 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, with environmental reverb specified as a non-required interface + const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB}; + const SLboolean req[1] = {SL_BOOLEAN_FALSE}; + result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req); + 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}; + + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, + chunkFormat->num_channels, + chunkFormat->sample_rate * 1000, // convert to milliHz + chunkFormat->bits_per_sample, + 16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN + }; + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + + // configure audio sink + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; + SLDataSink audioSnk = {&loc_outmix, NULL}; + + // create audio player + const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME}; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, + 2, ids, req); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl CreateAudioPlayer failed with result %d", result); + return false; + } + (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, NULL); + 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 = NULL; + 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 */ + endLoop = true; + break; + default: + /* Unknown chunk, skip bytes */ + *oSoundBuf += chunkHeader->sz; + *oSoundBufSize -= chunkHeader->sz; + } + if (endLoop) { + break; + } + } + + if (*oChunkFormat == NULL) { + ALOGE("format not found in WAV file"); + return false; + } + return true; +} + +} // 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", bqPlayerBufferQueue, buf, size); + + 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 (NULL != 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 != NULL) { + CHATTY("destroying audio player"); + (*bqPlayerObject)->Destroy(bqPlayerObject); + bqPlayerObject = NULL; + bqPlayerPlay = NULL; + bqPlayerBufferQueue = NULL; + bqPlayerMuteSolo = NULL; + bqPlayerVolume = NULL; + } + + // destroy output mix object, and invalidate all associated interfaces + if (outputMixObject != NULL) { + (*outputMixObject)->Destroy(outputMixObject); + outputMixObject = NULL; + } + + // destroy engine object, and invalidate all associated interfaces + if (engineObject != NULL) { + CHATTY("destroying audio engine"); + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + engineEngine = NULL; + } +} + +} // namespace audioplay diff --git a/cmds/bootanimation/audioplay.h b/cmds/bootanimation/audioplay.h new file mode 100644 index 000000000000..0e5705af0ad0 --- /dev/null +++ b/cmds/bootanimation/audioplay.h @@ -0,0 +1,37 @@ +/* + * 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. + * + */ + +#ifndef AUDIOPLAY_H_ +#define AUDIOPLAY_H_ + +#include <string.h> + +namespace audioplay { + +// Initializes the engine with an example of the type of WAV clip to play. +// All buffers passed to playClip are assumed to be in the same format. +bool create(const uint8_t* exampleClipBuf, int exampleClipBufSize); + +// Plays a WAV contained in buf. +// Should not be called while a clip is still playing. +bool playClip(const uint8_t* buf, int size); +void setPlaying(bool isPlaying); +void destroy(); + +} + +#endif // AUDIOPLAY_H_ diff --git a/cmds/bootanimation/bootanim.rc b/cmds/bootanimation/bootanim.rc index ee0d0b8c042f..7344ba74f70b 100644 --- a/cmds/bootanimation/bootanim.rc +++ b/cmds/bootanimation/bootanim.rc @@ -4,3 +4,4 @@ service bootanim /system/bin/bootanimation group graphics audio disabled oneshot + writepid /dev/stune/top-app/tasks
\ No newline at end of file |