bootanim.zip文件及源码分析
介绍
- bootanim.zip 是安卓开机动画显示的文件
- 文件位置在安卓系统目录的 “/oem/media/bootanimation.zip” OR “/system/media/bootanimation.zip”
- 三方定制开机动画,根据自己的平台,制作bootanim.zip,放置到指定目录下,安卓系统开机自动会播放文件夹中的动画
源码分析
bootanim.cpp 文件中采用三种方式播放动画,对于zip包形式的解析代码如下:
bool BootAnimation::movie() { String8 desString; // 读取文件中的 desc.txt 文件 if (!readFile("desc.txt", desString)) { return false; } char const* s = desString.string(); // 根据 audio_conf.txt 是否初始化音频 String8 audioConf; if (readFile("audio_conf.txt", audioConf)) { mAudioPlayer = new AudioPlayer; if (!mAudioPlayer->init(audioConf.string())) { ALOGE("mAudioPlayer.init failed"); mAudioPlayer = NULL; } } Animation animation; // 解析描述文件 for (;;) { // 读取一行数据 const char* endl = strstr(s, "\n"); if (endl == NULL) break; String8 line(s, endl - s); const char* l = line.string(); int fps, width, height, count, pause; char path[ANIM_ENTRY_NAME_MAX]; char color[7] = "000000"; // default to black if unspecified char pathType; // 数据项3个,分别为 动画宽,高,刷新率fps if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) { ALOGD("> w=%d, h=%d, fps=%d", width, height, fps); animation.width = width; animation.height = height; animation.fps = fps; } // 描述项参数大于等于4,分别为:动画类型,动画是否循环,暂停时间,动画路径 else if (sscanf(l, " %c %d %d %s #%6s", &pathType, &count, &pause, path, color) >= 4) { ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s", pathType, count, pause, path, color); Animation::Part part; part.playUntilComplete = pathType == 'c'; 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; part.backgroundColor[1] = 0.0f; part.backgroundColor[2] = 0.0f; } // 一个part描述一个动画文件夹中的内容 animation.parts.add(part); } s = ++endl; } // 读取动画个数 const size_t pcount = animation.parts.size(); void *cookie = NULL; if (!mZip->startIteration(&cookie)) { return false; } 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); if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) { ALOGE("Error fetching entry file name"); continue; } const String8 entryName(name); const String8 path(entryName.getPathDir()); const String8 leaf(entryName.getPathLeaf()); if (leaf.size() > 0) { for (size_t j=0 ; j<pcount ; j++) { if (path == animation.parts[j].path) { uint16_t method; // MStar Android Patch Begin #ifdef ENABLE_SUPPORT_ETC1_COMPRESS_DATA_IMAGE uint32_t uncompLen; if (mZip->getEntryInfo(entry, &method, &uncompLen, 0, 0, 0, 0)) { if (method == ZipFileRO::kCompressDeflated) { char* buf = new char[uncompLen]; if (!mZip->uncompressEntry(entry, buf,uncompLen)) { delete [] buf; } else { Animation::Part& part(animation.parts.editItemAt(j)); if (leaf == "audio.wav") { ALOGE("audio.wav don't support kCompressDeflated\n"); } else { Animation::Frame frame; frame.name = leaf; frame.data = buf; frame.data_length = uncompLen; part.frames.add(frame); } } } else if (method == ZipFileRO::kCompressStored) { #else // 仅支持png存储的文件 if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { if (method == ZipFileRO::kCompressStored) { #endif // MStar Android Patch End FileMap* map = mZip->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; } else { Animation::Frame frame; frame.name = leaf; // MStar Android Patch Begin #ifdef ENABLE_SUPPORT_ETC1_COMPRESS_DATA_IMAGE frame.data = map->getDataPtr(); frame.data_length = map->getDataLength(); #endif // MStar Android Patch End frame.map = map; part.frames.add(frame); } } } } } } } } mZip->endIteration(cookie); // 清除屏幕 glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); // MStar Android Patch Begin // avoid one black frame which maybe last hundreds of milliseconds glClearColor(0,0,0,0); // MStar Android Patch End glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(mDisplay, mSurface); // MStar Android Patch Begin // Restore the clearcolor to black glClearColor(0,0,0,1); // MStar Android Patch End glBindTexture(GL_TEXTURE_2D, 0); glEnable(GL_TEXTURE_2D); glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 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)); for (size_t i=0 ; i<pcount ; i++) { const Animation::Part& part(animation.parts[i]); const size_t fcount = part.frames.size(); glBindTexture(GL_TEXTURE_2D, 0); // 循环显示文件夹下的图片 for (int r=0 ; !part.count || r<part.count ; r++) { // Exit any non playuntil complete parts immediately 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], part.backgroundColor[2], 1.0f); for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) { const Animation::Frame& frame(part.frames[j]); nsecs_t lastFrame = systemTime(); if (r > 0) { glBindTexture(GL_TEXTURE_2D, frame.tid); } else { if (part.count != 1) { glGenTextures(1, &frame.tid); glBindTexture(GL_TEXTURE_2D, frame.tid); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } initTexture(frame); } 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()); 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); eglSwapBuffers(mDisplay, mSurface); nsecs_t now = systemTime(); nsecs_t delay = frameDuration - (now - lastFrame); //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay)); lastFrame = now; if (delay > 0) { struct timespec spec; spec.tv_sec = (now + delay) / 1000000000; spec.tv_nsec = (now + delay) % 1000000000; int err; do { err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL); } while (err<0 && errno == EINTR); } checkExit(); } // MStar Android Patch Begin #ifdef ENABLE_STR char property[PROPERTY_VALUE_MAX] = {0}; property_get("mstar.str.suspending", property, "0"); int strResume = atoi(property); if (strResume) { for (int n=0; n<64; n++) { checkExit(); if (exitPending()) { ALOGD("STR bootanimation should exit strResume=%d, n=%d", strResume, n); break; } else { usleep((part.pause * ns2us(frameDuration))>>6); } } if (exitPending()) break; } else { usleep(part.pause * ns2us(frameDuration)); // For infinite parts, we've now played them at least once, so perhaps exit if (exitPending() && !part.count) break; } #else usleep(part.pause * ns2us(frameDuration)); // For infinite parts, we've now played them at least once, so perhaps exit if (exitPending() && !part.count) break; #endif // MStar Android Patch End } // 释放内存 if (part.count != 1) { for (size_t j=0 ; j<fcount ; j++) { const Animation::Frame& frame(part.frames[j]); glDeleteTextures(1, &frame.tid); } } } return false; }
代码内容由于是定制的Mstar的电视平台,增加了电视平台上自己定制的播放视屏的代码以及STC功能,但是重要的解析代码还是可以一览无余的。
文件解析工作主要由以下几个方面:- 获取zip文件的 desc.txt 准备解析
- 根据是否存在 audio_conf.txt 初始化音频操作
- 以行数据为单位,三项数据,分别对应: 宽,高,fps
- 四项数据及以上,分别对应: 动画类型,循环方式,暂停时间,动画路径
- 以 part 为单位,统一保存zip文件夹中的动画内容
- 根据 part 内容,循环播放动画(存在音频,仅首次播放一次)
- 监测动画结束,关闭动画
- 图片内容仅支持png存储格式
zip文件解压后文件结构分析
手头上有公司电视平台的bootanim,我们解压来看一下
三个文件:
1. desc.txt 纯文本文档 2. 两个文件夹,part1,part2
desc.txt 文件内容:
1920 1080 3 p 1 0 part1 p 0 0 part2
由代码解析分析可以得出:
宽 1920, 高 1080, fps:3(1s播放3张) p 动画标志符; 1 只播放一次; 0 阶段间隔时间 0; 对应动画资源文件夹为 part1 p 动画标志符; 0 循环播放; 0 阶段间隔时间 0; 对应动画资源文件夹 part2 阶段间隔时间: 单位是一个帧的持续时间,比如帧数是30,那么帧的持续时间就是1秒/30 = 33.3毫秒。阶段切换间隔时间期间开机动画进程进入休眠,把CPU时间让给初始化系统使用。也就是间隔长启动会快,但会影响动画效果
自定义zip包时候,压缩的时候压缩方式要选择存储,意思只是将文件进行打包,不进行实质内容意义上的压缩,否则开机时手机会不认的。
对于做好的zip包,拷贝到指定目录下,需要重新提权(一般是644)才能保证动画正常工作。