bootanim压缩包源码分析

bootanim.zip文件及源码分析

介绍

  1. bootanim.zip 是安卓开机动画显示的文件
  2. 文件位置在安卓系统目录的 “/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功能,但是重要的解析代码还是可以一览无余的。
    文件解析工作主要由以下几个方面:

    1. 获取zip文件的 desc.txt 准备解析
    2. 根据是否存在 audio_conf.txt 初始化音频操作
    3. 以行数据为单位,三项数据,分别对应: 宽,高,fps
    4. 四项数据及以上,分别对应: 动画类型,循环方式,暂停时间,动画路径
    5. 以 part 为单位,统一保存zip文件夹中的动画内容
    6. 根据 part 内容,循环播放动画(存在音频,仅首次播放一次)
    7. 监测动画结束,关闭动画
    8. 图片内容仅支持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)才能保证动画正常工作。

坚持原创技术分享,您的支持将鼓励我继续创作!