Android开机动画视频
来源:互联网 发布:免费下载淘宝返还网 编辑:程序博客网 时间:2024/05/21 22:22
BootAnimation.cpp
/*
* Copyright (C) 2007 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_TAG "BootAnimation"
#include <stdint.h>
#include <sys/types.h>
#include <math.h>
#include <fcntl.h>
#include <utils/misc.h>
#include <signal.h>
#include <cutils/properties.h>
#include <androidfw/AssetManager.h>
#include <binder/IPCThreadState.h>
#include <utils/Atomic.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <utils/threads.h>
#include <ui/PixelFormat.h>
#include <ui/Rect.h>
#include <ui/Region.h>
#include <ui/DisplayInfo.h>
#include <ui/FramebufferNativeWindow.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <core/SkBitmap.h>
#include <core/SkStream.h>
//#include <images/SkImageDecoder.h>
#include <core/SkCanvas.h>
#include <core/SkScalar.h>
#include <core/SkImageDecoder.h>
#include <GLES/gl.h>
#include <GLES/glext.h>
#include <EGL/eglext.h>
#include <stdio.h>
#include <string.h>
#include <cutils/properties.h>
#include <libgen.h>
#include "BootAnimation.h"
#include "AlarmTimer.h"
#define USER_BOOTANIMATION_FILE "/data/local/bootanimation.zip"
#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip"
#define USER_SHUTDOWN_BOOTANIMATION_FILE "/data/local/shutdownanimation.zip"
#define SYSTEM_SHUTDOWN_BOOTANIMATION_FILE "/system/media/shutdownanimation.zip"
#define EXIT_PROP_NAME "service.bootanim.exit"
#define RUNNING_PROP_NAME "svc.bootanim"
#define DISPLAY_EDP_WIDTH 2048
#define DISPLAY_EDP_HEIGHT 1536
extern "C" int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *request,
struct timespec *remain);
extern bool gUseBootVideo ;
extern bool gUseBootAdv;
namespace android {
// ---------------------------------------------------------------------------
status_t BootAnimation::CheckBootAnimation()
{
ZipFileRO& zip(mZip);
ALOGI("ENTERN CheckBootAnimation \n");
size_t numEntries = zip.getNumEntries();
ZipEntryRO desc = zip.findEntryByName("desc.txt");
FileMap* descMap = zip.createEntryFileMap(desc);
ALOGE_IF(!descMap, "descMap is null");
if (!descMap) {
return false;
}
String8 desString((char const*)descMap->getDataPtr(),
descMap->getDataLength());
char const* s = desString.string();
Animation animation;
// Parse the description file
for (;;) {
const char* endl = strstr(s, "\n");
if (!endl) break;
String8 line(s, endl - s);
const char* l = line.string();
int fps, width, height, count, pause;
char path[256];
char pathType;
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
//LOGD("> w=%d, h=%d, fps=%d", width, height, fps);
animation.width = width;
animation.height = height;
animation.fps = fps;
}
else if (sscanf(l, " %c %d %d %s", &pathType, &count, &pause, path) == 4) {
//LOGD("> type=%c, count=%d, pause=%d, path=%s", pathType, count, pause, path);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
part.count = count;
part.pause = pause;
part.path = path;
animation.parts.add(part);
}
s = ++endl;
}
//return animation.parts.size();
ALOGI("*********animation.parts.size= %d \n",animation.parts.size());
if (animation.parts.size()==0)
{
return animation.parts.size();
}
else
{
const size_t pcount = animation.parts.size();
//ALOGI("*************numEntries= %d *********************\n", numEntries);
for (size_t i=0 ; i<numEntries ; i++) {
char name[256];
char file_path[256]={0};
ZipEntryRO entry = zip.findEntryByIndex(i);
if (zip.getEntryFileName(entry, name, 256) == 0) {
const String8 entryName(name);
const String8 path(entryName.getPathDir());
const String8 leaf(entryName.getPathLeaf());
// ALOGI("*************path= %s path.size =%d\n", path.string(),path.size());
//ALOGI("*************leaf= %s leaf.size =%d \n", leaf.string(),leaf.size());
if (leaf.size() > 0&&0!=path.size()) {
char const *pBuff=NULL;
sprintf(file_path,"%s/%s",(char const*)path.string(),(char const*)leaf.string());
ZipEntryRO desc= zip.findEntryByName(file_path);
FileMap* descMap = zip.createEntryFileMap(desc);
ALOGE_IF(!descMap, "descMap is null");
if (!descMap) {
return 0;
}
String8 desString((char const*)descMap->getDataPtr(),
descMap->getDataLength());
char const* s = desString.string();
//ALOGI("*********desString= %s \n",desString.string());
pBuff=(char const*)descMap->getDataPtr();
if((0xFF == *(pBuff)) && (0xD8 == *(pBuff+1)) && (0xFF == *(pBuff+2)))
{
ALOGI("It's a jpg picture!");
}
else if((0x89 == *(pBuff)) && (0x50 == *(pBuff+1)) && (0x4E == *(pBuff+2))
&&(0x47 == *(pBuff+3)) && (0x0D == *(pBuff+4)) && (0x0A == *(pBuff+5))
&&(0x1A == *(pBuff+6)) && (0x0A == *(pBuff+7)))
{
ALOGI("It's a png picture! ");
// return animation.parts.size();
}
else if((0x42 == *(pBuff)) && (0x4D == *(pBuff+1)))
{
ALOGI("It's a bmp picture! ");
}else
{
return 0;
}
}
}
}
return animation.parts.size();
}
}
static bool movieTimeout = false;
static bool videoEndBeforeLauncher = false;
BootAnimation::BootAnimation() : Thread(false),mRotation(0)
{
mSession = new SurfaceComposerClient();
mShutdown = false;
char prop[PROPERTY_VALUE_MAX];
/*通过属性设置是否旋转,如横屏*/
if (property_get("ro.bootanimation.rotation", prop, "0") > 0) {
int value = atoi(prop);
if( 0 == value || 90 == value || 180 == value || 270 == value ){
mRotation = value;
}
}
initAnimInfo();
}
BootAnimation::~BootAnimation() {
}
void BootAnimation::initAnimInfo() {
char value[PROPERTY_VALUE_MAX];
memset(value, 0, sizeof(value));
property_get("persist.sys.bootadv.type", value, "NULL");
if (!strcmp("NULL", value) || !strcmp("0", value)) {
mAnimInfo.setAnimType(AnimInfo::VIDEO_THEN_MOVIE);
} else if (!strcmp("1", value)) {
mAnimInfo.setAnimType(AnimInfo::VIDEO_ONLY);
} else if (!strcmp("2", value)) {
mAnimInfo.setAnimType(AnimInfo::MOVIE_ONLY);
}
}
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
if (err == NO_ERROR) {
run("BootAnimation", PRIORITY_DISPLAY);
}
}
void BootAnimation::setBootVolume()
{
int index = 11;
if(NO_ERROR != AudioSystem::setStreamVolumeIndex(AUDIO_STREAM_MUSIC, index, AUDIO_DEVICE_OUT_DEFAULT))
{
ALOGE("ERROR::Fail to set bootup volume!!");
}
}
sp<SurfaceComposerClient> BootAnimation::session() const {
return mSession;
}
void BootAnimation::isShutdown(bool shutdown)
{
mShutdown = shutdown;
}
void BootAnimation::binderDied(const wp<IBinder>& who)
{
// woah, surfaceflinger died!
ALOGD("SurfaceFlinger died, exiting...");
// calling requestExit() is not enough here because the Surface code
// might be blocked on a condition variable that will never be updated.
kill( getpid(), SIGKILL );
requestExit();
}
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
const char* name) {
Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
if (!asset)
return NO_INIT;
SkBitmap bitmap;
if(mRotation != 0){
SkBitmap origbitmap;
SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(),
&origbitmap, SkBitmap::kNo_Config, SkImageDecoder::kDecodePixels_Mode);
asset->close();
delete asset;
// create a bitmap with rotated dimensions and draw the rotated original bitmap
if( 90 == mRotation ){
bitmap.setConfig(origbitmap.config(), origbitmap.height(), origbitmap.width());
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.translate(SkIntToScalar(bitmap.width()), 0);
canvas.rotate(SkIntToScalar(mRotation));
canvas.drawBitmap(origbitmap, 0, 0, NULL);
}else if( 180== mRotation ){
bitmap.setConfig(origbitmap.config(), origbitmap.width(), origbitmap.height() );
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.translate(SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()));
canvas.rotate(SkIntToScalar(mRotation));
canvas.drawBitmap(origbitmap, 0, 0, NULL);
}else if( 270 == mRotation ){
bitmap.setConfig(origbitmap.config(), origbitmap.height(), origbitmap.width());
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.translate(0, SkIntToScalar(bitmap.height()));
canvas.rotate(SkIntToScalar(mRotation));
canvas.drawBitmap(origbitmap, 0, 0, NULL);
}
}else{
SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(),
&bitmap, SkBitmap::kNo_Config, SkImageDecoder::kDecodePixels_Mode);
asset->close();
delete asset;
}
// ensure we can call getPixels(). No need to call unlock, since the
// bitmap will go out of scope when we return from this method.
bitmap.lockPixels();
const int w = bitmap.width();
const int h = bitmap.height();
const void* p = bitmap.getPixels();
GLint crop[4] = { 0, h, w, -h };
texture->w = w;
texture->h = h;
glGenTextures(1, &texture->name);
glBindTexture(GL_TEXTURE_2D, texture->name);
switch (bitmap.getConfig()) {
case SkBitmap::kA8_Config:
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA,
GL_UNSIGNED_BYTE, p);
break;
case SkBitmap::kARGB_4444_Config:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
GL_UNSIGNED_SHORT_4_4_4_4, p);
break;
case SkBitmap::kARGB_8888_Config:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
GL_UNSIGNED_BYTE, p);
break;
case SkBitmap::kRGB_565_Config:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB,
GL_UNSIGNED_SHORT_5_6_5, p);
break;
default:
break;
}
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
return NO_ERROR;
}
status_t BootAnimation::initTexture(void* buffer, size_t len)
{
//StopWatch watch("blah");
SkBitmap bitmap;
SkMemoryStream stream(buffer, len);
SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
codec->setDitherImage(false);
if (codec) {
if(mRotation != 0){
SkBitmap origbitmap;
codec->decode(&stream, &origbitmap,
SkBitmap::kRGB_565_Config,
SkImageDecoder::kDecodePixels_Mode);
// create a bitmap with rotated dimensions and draw the rotated original bitmap
if( 90 == mRotation ){
bitmap.setConfig(origbitmap.config(), origbitmap.height(), origbitmap.width());
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.translate(SkIntToScalar(bitmap.width()), 0);
canvas.rotate(SkIntToScalar(mRotation));
canvas.drawBitmap(origbitmap, 0, 0, NULL);
}else if( 180== mRotation ){
bitmap.setConfig(origbitmap.config(), origbitmap.width(), origbitmap.height() );
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.translate(SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()));
canvas.rotate(SkIntToScalar(mRotation));
canvas.drawBitmap(origbitmap, 0, 0, NULL);
}else if( 270 == mRotation ){
bitmap.setConfig(origbitmap.config(), origbitmap.height(), origbitmap.width());
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.translate(0, SkIntToScalar(bitmap.height()));
canvas.rotate(SkIntToScalar(mRotation));
canvas.drawBitmap(origbitmap, 0, 0, NULL);
}
}else{
codec->decode(&stream, &bitmap,
SkBitmap::kARGB_8888_Config,
SkImageDecoder::kDecodePixels_Mode);
}
delete codec;
}
// ensure we can call getPixels(). No need to call unlock, since the
// bitmap will go out of scope when we return from this method.
bitmap.lockPixels();
const int w = bitmap.width();
const int h = bitmap.height();
const void* p = bitmap.getPixels();
int skiaformat = SkBitmap::kARGB_8888_Config;
SkBitmap *devBitmap = NULL;
skiaformat = bitmap.getConfig();
if ((skiaformat != SkBitmap::kARGB_8888_Config)&&(skiaformat != SkBitmap::kRGB_565_Config)) {
devBitmap = new SkBitmap();
SkCanvas *canvas = NULL;
devBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
devBitmap->allocPixels();
devBitmap->eraseARGB(255, 0, 0, 0);
canvas = new SkCanvas(*devBitmap);
SkPaint paint;
paint.setFilterLevel(SkPaint::kLow_FilterLevel);
SkRect dst = SkRect::MakeXYWH(0, 0, w, h);
SkRect src = SkRect::MakeXYWH(0, 0, w, h);
canvas->drawBitmapRectToRect(bitmap, &src, dst, &paint);
delete canvas;
p = devBitmap->getPixels();
skiaformat = devBitmap->getConfig();
}
GLint crop[4] = { 0, h, w, -h };
int tw = 1 << (31 - __builtin_clz(w));
int th = 1 << (31 - __builtin_clz(h));
if (tw < w) tw <<= 1;
if (th < h) th <<= 1;
switch (skiaformat) {
case SkBitmap::kARGB_8888_Config:
if (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,
GL_UNSIGNED_BYTE, p);
}
break;
case SkBitmap::kRGB_565_Config:
if (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,
GL_UNSIGNED_SHORT_5_6_5, p);
}
break;
default:
break;
}
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
if (devBitmap!=NULL) {
delete devBitmap;
}
return NO_ERROR;
}
status_t BootAnimation::readyToRun() {
/*
readyToRun函数主要是为了得到EGLDisplay mDisplay和EGLDisplay mSurface对象,这两个对象后面再介绍,最后函数还会查询是否存在bootanimation.zip并初始化mAndroidAnimation的
*/
mAssets.addDefaultAssets();
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain));
DisplayInfo dinfo;
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
if (status)
return -1;
// create the native surface
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565,ISurfaceComposerClient::eOpaque);
SurfaceComposerClient::openGlobalTransaction();
control->setLayer(0x40000000);
SurfaceComposerClient::closeGlobalTransaction();
sp<Surface> s = control->getSurface();
// initialize opengl and egl
const EGLint attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_NONE
};
EGLint w, h, dummy;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
context = eglCreateContext(display, config, NULL, NULL);
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
return NO_INIT;
mDisplay = display;
mContext = context;
mSurface = surface;
mWidth = w;
mHeight = h;
mFlingerSurfaceControl = control;
mFlingerSurface = s;
mAndroidAnimation = true;
// If the device has encryption turned on or is in process
// of being encrypted we show the encrypted boot animation.
char decrypt[PROPERTY_VALUE_MAX];
property_get("vold.decrypt", decrypt, "");
bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
if(!mShutdown){
if ((encryptedAnimation &&
(access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
(mZip.open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE) == NO_ERROR)) ||
((access(USER_BOOTANIMATION_FILE, R_OK) == 0) &&
(mZip.open(USER_BOOTANIMATION_FILE) == NO_ERROR)) ||
((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
(mZip.open(SYSTEM_BOOTANIMATION_FILE) == NO_ERROR))) {
mAndroidAnimation = false;
mAndroidAnimation = false;
ZipFileRO& zip(mZip);
ZipEntryRO desc = zip.findEntryByName("desc.txt");
FileMap* descMap = zip.createEntryFileMap(desc);
if (!descMap) {
mZip.open(SYSTEM_BOOTANIMATION_FILE);
}
}
}else{
if( ((access(USER_SHUTDOWN_BOOTANIMATION_FILE, R_OK) == 0) &&
(mZip.open(USER_SHUTDOWN_BOOTANIMATION_FILE) == NO_ERROR))||
((access(SYSTEM_SHUTDOWN_BOOTANIMATION_FILE, R_OK) == 0) &&
(mZip.open(SYSTEM_SHUTDOWN_BOOTANIMATION_FILE) == NO_ERROR))){
mAndroidAnimation=false;
}
mShutdown = false;
}
return NO_ERROR;
}
static void handleMovieTimout() {
movieTimeout = true;
return;
}
bool BootAnimation::videoEnd() {
char value[PROPERTY_VALUE_MAX];
memset(value, 0, sizeof(value));
property_get("init.svc.bootvideo", value, "NULL");
if (!strcmp(value, "stopped"))
return true;
return false;
}
bool BootAnimation::launcherDisplayedNow() {
bool displayed = false;
char value[PROPERTY_VALUE_MAX];
memset(value, 0, sizeof(value));
property_get("sys.app.launcher.displayed", value, "NULL");
if (!strcmp("1", value))
displayed = true;
ALOGE("launcherDisplayed:%d", displayed ? 1 : 0);
return displayed;
}
bool BootAnimation::threadLoop()
{
bool r;
/*
BootAnimation::readyToRun中会检查是否存在bootanimation.zip,如果存在bootanimation.zip这里就执行movie(),否则执行android()。movie()为逐帧动画的实现,android()为opengl动画的实现
*/
if(!gUseBootAdv)
{
if(!mAndroidAnimation && !gUseBootVideo && CheckBootAnimation()) { //ztebsp: use mp4 and android() will clear screen
r = movie();
} else {
r = android();
}
}else {
AnimInfo::AnimType type = mAnimInfo.getAnimType();
if (type == AnimInfo::VIDEO_THEN_MOVIE) {
property_set("ctl.start", "bootvideo");
usleep(10 * 1000);
do {
usleep(10 * 1000);
} while(!videoEnd());
if (launcherDisplayedNow()) {
videoEndBeforeLauncher = false;
} else {
videoEndBeforeLauncher = true;
}
ALOGE("Now video videoEndBeforeLauncher:%d", videoEndBeforeLauncher ? 1 : 0);
AlarmTimer timer;
timer.setTimer(5, handleMovieTimout);
movie();
} else if (type == AnimInfo::VIDEO_ONLY) {
property_set("ctl.start", "bootvideo");
usleep(10 * 1000);
do {
usleep(10 * 1000);
} while(!videoEnd());
checkExit();
} else if (type == AnimInfo::MOVIE_ONLY) {
movie();
}
}
// No need to force exit anymore
property_set(EXIT_PROP_NAME, "0");
/*
显示完成
销毁前面所创建的EGLContext对象mContext、EGLSurface对象mSurface,以及EGLDisplay对象mDisplay等。
*/
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(mDisplay, mContext);
eglDestroySurface(mDisplay, mSurface);
mFlingerSurface.clear();
mFlingerSurfaceControl.clear();
eglTerminate(mDisplay);
IPCThreadState::self()->stopProcess();
return r;
}
bool BootAnimation::android()
{
/**
安卓默认开机动画使用两幅图片混合渲染实现,实现方式是OpenGL动画
OpenGL(英语:Open Graphics Library)是个定义了一个跨编程语言、跨平台的应用程序接口(API)的规范,它用于生成二维、三维图像。这个接口由近三百五十个不同的函数调用组成,用来从简单的图形比特绘制复杂的三维景象。比较两种方式我们不难发现,电视平台上和手机有所不同,特别是开机广告,1920*1080分辨率的图片一张就几百KB,由于大小的限制导致动画帧数很少,所以电视平台采用“逐帧动画”方法无法做出复杂而流畅的动画,本文将主要讨论OpenGL的实现方式。
*/
ALOGE("android , display w:%d, h:%d", mWidth, mHeight);
/*安卓默认开机动画,由两幅图片组成,这两幅图保存于,frameworks/base/core/res/assets/images,最终编译到framework-res模块(frameworks/base/core/res)中,即编译在framework-res.apk文件中。
编译在framework-res模块中的资源文件可以通过AssetManager类来访问。
*/
if(!gUseBootVideo)
{ /*BootAnimation类的成员函数android首先调用另外一个成员函数initTexture来将根据图片android-logo-mask.png和android-logo-shine.png的内容来分别创建两个纹理对象,这两个纹理对象就分别保存在BootAnima tion类的成员变量mAndroid所描述的一个数组中。通过混合渲染这两个纹理对象,我们就可以得到一个开机动画,这是通过中间的while循环语句来实现的。
*/
if ((DISPLAY_EDP_WIDTH == mWidth) && (DISPLAY_EDP_HEIGHT == mHeight)){
initTexture(&mAndroid[0], mAssets, "images/android-logo-edp-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-edp-shine.png");
}
else{
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
}
}
// clear screen
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
#ifdef TV_FASTBOOTING_FUNCTION
glClearColor(0,0,0,0);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const GLint xc = mWidth - mAndroid[0].w;
const GLint yc = 0;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
glScissor(xc, yc, updateRect.width(),
updateRect.height());
#else
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const GLint xc = (mWidth - mAndroid[0].w) / 2;
const GLint yc = (mHeight - mAndroid[0].h) / 2;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
updateRect.height());
#endif
// Blend state
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
if(!gUseBootVideo)
{
property_set(RUNNING_PROP_NAME,"running");
}
const nsecs_t startTime = systemTime();
/* 图片android-logo-mask.png用作动画前景,它是一个镂空的“ANDROID”图像。图片android-logo-shine.png用作动画背景,它的中间包含有一个高亮的呈45度角的条纹。在每一次循环中,图片android-logo-shine.png被划分成左右两部分内容来显示。左右两个部分的图像宽度随着时间的推移而此消彼长,这样就可以使得图片android-logo-shine.png中间高亮的条纹好像在移动一样。另一方面,在每一次循环中,图片android-logo-shine.png都作为一个整体来渲染,而且它的位置是恒定不变的。由于它是一个镂空的“ANDROID”图像,因此,我们就可以通过它的镂空来看到它背后的图片android-logo-shine.png的条纹一闪一闪地划过。
*/
/*
BootAnimation::android函数包含了整个opengl绘图的过程,因为android使用的是标准opengl es api,所以opengl的初始化和绘图过程这里就不详细介绍了,这里需要注意的是eglSwapBuffers(mDisplay, mSurface),这个方法将mSruface投递到屏幕,这里用到了两个EGLDisplay对象,是由于使用了双缓冲机制,配合sleep实现稳定的刷新率。最后当检测到系统初始化完成时退出程序。
*/
do {
nsecs_t now = systemTime();
double time = now - startTime;
GLint x,y;
if( 90==mRotation || 270==mRotation){
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].h;
//LOGD("time=%f t=%f floorf(t)=%f", time, t, floorf(t));
GLint offset = (t - floorf(t)) * mAndroid[1].h;
y = yc - offset;
}else{
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
x = xc - offset;
}
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
if( 90==mRotation || 270==mRotation){
glDrawTexiOES(xc, y, 0, mAndroid[1].w, mAndroid[1].h);
glDrawTexiOES(xc, y + mAndroid[1].h, 0, mAndroid[1].w, mAndroid[1].h);
}else{
glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
}
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
break;
// 12fps: don't animate too fast to preserve CPU
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
if (sleepTime > 0)
usleep(sleepTime);
checkExit();
} while (!exitPending());
glDeleteTextures(1, &mAndroid[0].name);
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
char delayExitValue[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, "0");
property_get("service.bootanim.shouldfinish", delayExitValue, "0");
int exitnow = atoi(value);
int delayExit = atoi(delayExitValue);
ALOGI("exitnow = %d, delayExit = %d, gUseBootVideo = %d\n", exitnow, delayExit, gUseBootVideo);
if(gUseBootVideo){
do{
usleep(10*1000);
memset(value,0,sizeof(value));
property_get(EXIT_PROP_NAME, value, "0");
exitnow = atoi(value);
}while(!exitnow);
}
if(!gUseBootVideo){
if(delayExit){
exitnow=1;
property_set("service.bootanim.exit", "1");
ALOGI("BootAnimation service.bootanim.shouldfinish =1;set exitnow =1;set service.bootanim.exit=1\n");
ALOGI("Now exitnow = %d, delayExit = %d, gUseBootVideo = %d\n", exitnow, delayExit, gUseBootVideo);
}
}
if (exitnow) {
ALOGE("Now enter exitnow !!!!");
if(gUseBootVideo){
do {
usleep(10 * 1000);
memset(value, 0, sizeof(value));
property_get("init.svc.bootvideo", value, "NULL");
ALOGE("Now init.svc.bootvideo:%s", value);
} while(!strcmp(value,"running"));
property_set("service.bootvideo.exit", "1");
ALOGE("Now before requestExit()!!!!");
requestExit();
}else if (gUseBootAdv) {
if (mAnimInfo.getAnimType() == AnimInfo::VIDEO_THEN_MOVIE) {
if (videoEndBeforeLauncher && launcherDisplayedNow()) {
requestExit();
}
if (!movieTimeout)
return;
} else if (mAnimInfo.getAnimType() == AnimInfo::VIDEO_ONLY) {
do {
usleep(10 * 1000);
memset(value, 0, sizeof(value));
property_get("init.svc.bootvideo", value, "NULL");
ALOGE("Now init.svc.bootvideo:%s", value);
} while(!strcmp(value,"running"));
property_set("service.bootvideo.exit", "1");
}
ALOGE("Now before requestExit()!!!!");
requestExit();
ALOGD("[checkExit] Close BootAnimation for count launcher display time\n");//这条日志不要删,日志过滤工具会据此统计开机时间
}
if(delayExit)
{
requestExit();
ALOGD("[checkExit] Close BootAnimation for count launcher display time\n");//这条日志不要删,日志过滤工具会据此统计开机时间
}
}
}
void BootAnimation::checkOnePartExit() {
if (!videoEndBeforeLauncher && launcherDisplayedNow())
requestExit();
}
bool BootAnimation::movie()
{
ZipFileRO& zip(mZip);
/*
自定义动画采用逐帧动画方式
逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。 因为逐帧动画的帧序列内容不一样,不但给制作增加了负担而且最终输出的文件量也很大,但它的优势也很明显:逐帧动画具有非常大的灵活性,几乎可以表现任何想表现的内容,而它类似与电影的播放模式,很适合于表演细腻的动画。逐帧动画是网上广泛流传的一种实现方法。实现原理是将一系列图片打包成bootanimation.zip放入/system/media/目录,系统将图片一帧一帧循环播放形成一个动画效果。理论上讲这种方法应该是可以实现一切动画需求的,但是实践后你会发现当bootanimation.zip大于5M的时候,动画将有明显卡顿,文件越大动画越不流畅。所以细心的同学会发现手机上的开机动画大多都是只有中间一小部分在变化,而四周全是黑色,这样做是为了使得可以采用100*50(甚至更小)分辨率的图片,这样100帧也才几M的大小。
*/
/*
从前面BootAnimation类的成员函数readyToRun的实现可以知道,如果目标设备上存在压缩文件/data/local/bootanimation.zip,那么BootAnimation类的成员变量mZip就会指向它,否则的话,就会指向目标设备上的压缩文件/system/media/bootanimation.zip。无论BootAnimation类的成员变量mZip指向的是哪一个压缩文件,这个压缩文件都必须包含有一个名称为“desc.txt”的文件,用来描述用户自定义的开机动画是如何显示的。
文件desc.txt的内容格式如下面的例子所示:
600 480 24
p 1 0 part1
p 0 10 part2
第一行的三个数字分别表示开机动画在屏幕中的显示宽度、高度以及帧速(fps)。剩余的每一行都用来描述一个动画片断,这些行必须要以字符“p”来开头,后面紧跟着两个数字以及一个文件目录路径名称。第一个数字表示一个片断的循环显示次数,如果它的值等于0,那么就表示无限循环地显示该动画片断。第二个数字表示每一个片断在两次循环显示之间的时间间隔。这个时间间隔是以一个帧的时间为单位的。文件目录下面保存的是一系列png文件,这些png文件会被依次显示在屏幕中。
以上面这个desct.txt文件的内容为例,它描述了一个大小为600 x 480的开机动画,动画的显示速度为24帧每秒。这个开机动画包含有两个片断part1和part2。片断part1只显示一次,它对应的png图片保存在目录part1中。片断part2无限循环地显示,其中,每两次循环显示的时间间隔为10 x (1 / 24)秒,它对应的png图片保存在目录part2中。
上面的for循环语句分析完成desc.txt文件的内容后,就得到了开机动画的显示大小、速度以及片断信息。这些信息都保存在Animation对象animation中,其中,每一个动画片断都使用一个Animation::Part对象来描述,并且保存在Animation对象animation的成员变量parts所描述的一个片断列表中。
接下来,BootAnimation类的成员函数movie再断续将每一个片断所对应的png图片读取出来,如下所示:
*/
size_t numEntries = zip.getNumEntries();
ZipEntryRO desc = zip.findEntryByName("desc.txt");
String8 rpath_m = String8("");
if (!desc) {
for (size_t i=0 ; i<numEntries ; i++) {
char name_m[256];
ZipEntryRO entry_m = zip.findEntryByIndex(i);
if (zip.getEntryFileName(entry_m, name_m, 256) == 0 && !strcmp(basename(name_m), "desc.txt")) {
desc = entry_m;
rpath_m = String8(dirname(name_m)) + String8("/");
ALOGE("ENTERN rpath_m %s\n", rpath_m.string());
break;
}
}
}
//end here
int method;
size_t UncompLen;
size_t CompLen;
Animation animation;
zip.getEntryInfo(desc, &method, &UncompLen, &CompLen, 0, 0, 0);
char outBuf[UncompLen];
char proj_type[PROPERTY_VALUE_MAX];
property_get("sys.proj.type", proj_type, "ott");
char uimode[PROPERTY_VALUE_MAX];
property_get("ubootenv.var.uimode", uimode, "0");
if(method == ZipFileRO::kCompressStored){
ALOGE("ENTERN ZipFileRO: kCompressStored");
FileMap* descMap = zip.createEntryFileMap(desc);
ALOGE_IF(!descMap, "descMap is null");
if (!descMap) {
return false;
}
String8 desString((char const*)descMap->getDataPtr(),
descMap->getDataLength());
char const* s = desString.string();
ALOGE("Stored s : %s\n",s);
// Parse the description file
for (;;) {
const char* endl = strstr(s, "\n");
if (!endl) break;
String8 line(s, endl - s);
const char* l = line.string();
int fps, width, height, count, pause;
char path[256];
char pathType;
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3){
if(!strncmp(uimode,"1080p",5)){
if (width == 1280 && height == 720){
width = 1920;
height= 1080;
ALOGI("rejuage width & height");
}
}
ALOGE("BootAnimation::movie> w=%d, h=%d, fps=%d", width, height, fps);
if( 90==mRotation || 270==mRotation){
animation.width = height;
animation.height = width;
}else{
animation.width = width;
animation.height = height;
}
if(fps<=0)
{
fps=1;
}
animation.fps = fps;
}else if (sscanf(l, " %c %d %d %s", &pathType, &count, &pause, path) == 4) {
ALOGE("BootAnimation::movie> type=%c, count=%d, pause=%d, path=%s", pathType, count, pause, path);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
part.count = count;
if (pause < 0) {
part.pause = 0;
}
else {
part.pause = pause;
}
part.path = rpath_m + path;
animation.parts.add(part);
}
s = ++endl;
}
}else if(method == ZipFileRO::kCompressDeflated){
ALOGE("ENTERN ZipFileRO: kCompressDeflated");
if (true == zip.uncompressEntry(desc, outBuf)){
ALOGE("uncompressEntry UncompLen : %d\n",UncompLen);
}
char * s = (char *)malloc(UncompLen+1);
memset(s, 0, UncompLen+1);
memcpy(s, outBuf, UncompLen);
for (;;) {
char* endl = strstr(s, "\n");
if (!endl) break;
String8 line(s, endl - s);
const char* l = line.string();
int fps, width, height, count, pause;
char path[256];
char pathType;
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
if(!strncmp(uimode,"1080p",5)){
if (width == 1280 && height == 720){
width = 1920;
height= 1080;
ALOGI("rejuage width & height");
}
}
if( 90==mRotation || 270==mRotation){
animation.width = height;
animation.height = width;
}else{
animation.width = width;
animation.height = height;
}
animation.fps = fps;
}
else if (sscanf(l, " %c %d %d %s", &pathType, &count, &pause, path) == 4) {
ALOGE("BootAnimation::movie>>> type=%c, count=%d, pause=%d, path=%s", pathType, count, pause, path);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
part.count = count;
if (pause < 0) {
part.pause = 0;
}
else {
part.pause = pause;
}
part.path = rpath_m + path;
animation.parts.add(part);
}
s = ++endl;
}
}
// read all the data structures
const size_t pcount = animation.parts.size();
for (size_t i=0 ; i<numEntries ; i++) {
char name[256];
ZipEntryRO entry = zip.findEntryByIndex(i);
if (zip.getEntryFileName(entry, name, 256) == 0) {
const String8 entryName(name);
const String8 path(entryName.getPathDir());
const String8 leaf(entryName.getPathLeaf());
if (leaf.size() > 0) {
for (int j=0 ; j<pcount ; j++) {
if (path == animation.parts[j].path) {
int method;
size_t UncompLen2;
size_t CompLen2;
// supports only stored png files
if (zip.getEntryInfo(entry, &method, &UncompLen2, &CompLen2, 0, 0, 0)) {
if (method == ZipFileRO::kCompressStored) {
FileMap* map = zip.createEntryFileMap(entry);
if (map) {
Animation::Frame frame;
frame.name = leaf;
frame.map = map;
Animation::Part& part(animation.parts.editItemAt(j));
part.frames.add(frame);
}
}else if(method == ZipFileRO::kCompressDeflated){
ALOGE("This is CompressDeflated entry\n");
char outBuf2[UncompLen2];
if (true == zip.uncompressEntry(entry, outBuf2)){
Animation::Frame frame;
frame.name = leaf;
frame.pbuf = (char*)malloc(UncompLen2);
memcpy(frame.pbuf , outBuf2, UncompLen2);
frame.psize = UncompLen2;
Animation::Part& part(animation.parts.editItemAt(j));
part.frames.add(frame);
}
}
}
}
}
}
}
}
/*
每一个png图片都表示一个动画帧,使用一个Animation::Frame对象来描述,并且保存在对应的Animation::Part对象的成员变量frames所描述的一个帧列表中。
获得了开机动画的所有信息之后,接下来BootAnimation类的成员函数movie就准备开始显示开机动画了
*/
// clear screen
/*一系列gl函数首先用来清理屏幕*/
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
/*一系列gl函数用来设置OpenGL的纹理显示方式*/
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);
/*变量xc和yc的值用来描述开机动画的显示位置,即需要在屏幕中间显示开机动画*/
int xc, yc;
if(gUseBootVideo){
animation.width=0;
animation.height=0;
}
if( 90==mRotation || 270==mRotation){
xc = ((mHeight - animation.height) / 2);
yc = ((mWidth - animation.width) / 2);
}else{
xc = (mWidth - animation.width) / 2;
yc = ((mHeight - animation.height) / 2);
}
nsecs_t lastFrame = systemTime();
/*
变量frameDuration的值用来描述每一帧的显示时间,它是以纳秒为单位的。
*/
nsecs_t frameDuration = s2ns(1) / animation.fps;
/*Region对象clearReg用来描述屏幕中除了开机动画之外的其它区域,它是用整个屏幕区域减去开机动画所点据的区域来得到的。*/
Region clearReg(Rect(mWidth, mHeight));
clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));
int switch_logo_flag = 0;
/*准备好开机动画的显示参数之后,开始执行显示开机动画的操作*/
/* 第一层for循环用来显示每一个动画片断,第二层的for循环用来循环显示每一个动画片断,第三层的for循环用来显示每一个动画片断所对应的png图片。这些png图片以纹理的方式来显示在屏幕中。注意,如果一个动画片断的循环显示次数不等于1,那么就说明这个动画片断中的png图片需要重复地显示在屏幕中。由于每一个png图片都需要转换为一个纹理对象之后才能显示在屏幕中,因此,为了避免重复地为同一个png图片创建纹理对象,第三层的for循环在第一次显示一个png图片的时候,会调用函数glGenTextures来为这个png图片创建一个纹理对象,并且将这个纹理对象的名称保存在对应的Animation::Frame对象的成员变量tid中,这样,下次再显示相同的图片时,就可以使用前面已经创建好了的纹理对象,即调用函数glBindTexture来指定当前要操作的纹理对象。如果Region对象clearReg所包含的区域不为空,那么在调用函数glDrawTexiOES和eglSwapBuffers来显示每一个png图片之前,首先要将它所包含的区域裁剪掉,避免开机动画可以显示在指定的位置以及大小中。每当显示完成一个png图片之后,都要将变量frameDuration的值从纳秒转换为毫秒。如果转换后的值大小于,那么就需要调用函数usleep函数来让线程睡眠一下,以保证每一个png图片,即每一帧动画都按照预先指定好的速度来显示。注意,函数usleep指定的睡眠时间只能精确到毫秒,因此,如果预先指定的帧显示时间小于1毫秒,那么BootAnimation类的成员函数movie是无法精确地控制地每一帧的显示时间的。
还有另外一个地方需要注意的是,每当循环显示完成一个片断时,需要调用usleep函数来使得线程睡眠part.pause * ns2us(frameDuration)毫秒,以便可以按照预先设定的节奏来显示开机动画。
最后一个if语句判断一个动画片断是否是循环显示的,即循环次数不等于1。如果是的话,那么就说明前面为它所对应的每一个png图片都创建过一个纹理对象。现在既然这个片断的显示过程已经结束了,因此,就需要释放前面为它所创建的纹理对象。
至此,第三个开机画面的显示过程就分析完成了。
*/
for (int 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;
for (int j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
const Animation::Frame& frame(part.frames[j]);
nsecs_t lastFrame = systemTime();
#ifndef LARGE_BOOTANIMATE
if (r > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
#endif
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);
}
if(method == ZipFileRO::kCompressStored){
initTexture(
frame.map->getDataPtr(),
frame.map->getDataLength());
}else if(method == ZipFileRO::kCompressDeflated){
initTexture(frame.pbuf, frame.psize);
}
#ifndef LARGE_BOOTANIMATE
}
#endif
if (!clearReg.isEmpty()) {
Region::const_iterator head(clearReg.begin());
Region::const_iterator tail(clearReg.end());
glEnable(GL_SCISSOR_TEST);
while (head != tail) {
const Rect& r(*head++);
glScissor(r.left, mHeight - r.bottom,
r.width(), r.height());
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
if (animation.width == 1280 || animation.width == 1920)
glDrawTexiOES(0, 0, 0, mWidth, mHeight);
else
glDrawTexiOES(xc, yc, 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);
}
if (switch_logo_flag == 0){
if(!gUseBootVideo)
{
property_set(RUNNING_PROP_NAME,"running");
}
switch_logo_flag = 1;
}
if(!strncmp(proj_type, "mobile", 6)) {
if(i < (pcount-1)){
ALOGD("The last frame isn't shown,break!!");
break;
}
}
#ifdef LARGE_BOOTANIMATE
glDeleteTextures(1, &frame.tid);
#endif
checkExit();
}
usleep(part.pause * ns2us(frameDuration));
// For infinite parts, we've now played them at least once, so perhaps exit
if(exitPending() && !part.count)
break;
}
#ifndef LARGE_BOOTANIMATE
// free the textures for this part
if (part.count != 1) {
for (int j=0 ; j<fcount ; j++) {
const Animation::Frame& frame(part.frames[j]);
glDeleteTextures(1, &frame.tid);
if(method == ZipFileRO::kCompressDeflated && frame.pbuf != NULL){
free(frame.pbuf);
}
}
}
#endif
}
return false;
}
// ---------------------------------------------------------------------------
}
; // namespace android
bootanimation_main.cpp
/*
* Copyright (C) 2007 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_TAG "BootAnimation"
#include <cutils/properties.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <utils/threads.h>
#if defined(HAVE_PTHREADS)
# include <pthread.h>
# include <sys/resource.h>
#endif
#include "BootAnimation.h"
#include "unistd.h"
bool gUseBootVideo = false;
bool gUseBootAdv = false;
using namespace android;
// ---------------------------------------------------------------------------
int main(int argc, char** argv)
{
#if defined(HAVE_PTHREADS)
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
#endif
char value[PROPERTY_VALUE_MAX];
/*
通过属性设置关闭开机动画
*/
property_get("debug.sf.nobootanimation", value, "0");
int noBootAnimation = atoi(value);
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
/*
开启线程池
*/
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
/*
实例化开机动画对象
*/
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation();
//add video boot
memset(value,0,sizeof(value));
property_get("persist.service.bootvideo", value, "2");
if(atoi(value)!=2)
gUseBootVideo = (atoi(value) == 1 ? true : false);
else{
memset(value,0,sizeof(value));
ALOGE("chenwh0 service.bootvideo=%d",atoi(value));
property_get("service.bootvideo", value, "0");
ALOGE("chenwh1 service.bootvideo=%d",atoi(value));
gUseBootVideo = (atoi(value) == 1 ? true : false);
}
if(gUseBootVideo){
//
// boot->setBootVolume();
property_set("service.bootvideo", "2");
}else {
//add adv boot
memset(value,0,sizeof(value));
property_get("persist.service.bootadv", value, "2");
if (atoi(value) != 2)
gUseBootAdv = (atoi(value) == 1 ? true : false);
else {
memset(value, 0, sizeof(value));
property_get("service.bootadv", value, "0");
gUseBootAdv = (atoi(value) == 1 ? true : false);
}
if(gUseBootAdv){
// boot->setBootVolume();
property_set("service.bootadv", "2");
}
}
//judge whether is shutdown bootanimation
if(argc>1){
if(strcmp(argv[1],"-shutdown")==0){
boot->isShutdown(true);
}
}
/*
将开机动画对象抛入线程池
ThreadPool继承binder类
startThreadPool()和joinThreadPool()的用法可以参考android的binder机制。
*/
IPCThreadState::self()->joinThreadPool();
}
return 0;
}
BootAnimation.h
/*
* Copyright (C) 2007 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 ANDROID_BOOTANIMATION_H
#define ANDROID_BOOTANIMATION_H
#include <stdint.h>
#include <sys/types.h>
#include <androidfw/AssetManager.h>
#include <utils/threads.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <media/AudioSystem.h>
#include "AnimInfo.h"
class SkBitmap;
namespace android {
class Surface;
class SurfaceComposerClient;
class SurfaceControl;
// ---------------------------------------------------------------------------
/*
BootAnimation类继承了Thread类和IBinder::DeathRecipient类,几个override函数的作用如下:
onFirstRef()属于其父类RefBase,该函数在强引用sp新增引用计数時调用,就是当 有sp包装的类初始化的时候调用。
binderDied(),当对象死掉时或者其他情况导致该Binder发生结束了,就会回调binderDied()方法;
readyToRun()定义Thread执行前的初始化工作;
threadLoop()是每个线程类都要实现的,在这里定义thread的执行内容,这个函数如果返回true,则函数会不停地执行threadloop中的内容,如果这个函数返回false,则threadloop中的内容仅仅执行一次线程就会退出;
*/
class BootAnimation : public Thread, public IBinder::DeathRecipient
{
public:
BootAnimation();
virtual ~BootAnimation();
sp<SurfaceComposerClient> session() const;
//add for shutdown animation
bool mShutdown;
void isShutdown(bool shutdown);
void setBootVolume();
private:
virtual bool threadLoop();
virtual status_t readyToRun();
virtual void onFirstRef();
virtual void binderDied(const wp<IBinder>& who);
struct Texture {
GLint w;
GLint h;
GLuint name;
};
struct Animation {
struct Frame {
String8 name;
FileMap* map;
char* pbuf;
size_t psize;
mutable GLuint tid;
bool operator < (const Frame& rhs) const {
return name < rhs.name;
}
};
struct Part {
int count;
int pause;
String8 path;
SortedVector<Frame> frames;
bool playUntilComplete;
};
int fps;
int width;
int height;
Vector<Part> parts;
};
status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
status_t initTexture(void* buffer, size_t len);
bool android();
bool movie();
void checkExit();
status_t CheckBootAnimation();
void initAnimInfo();
bool videoEnd();
bool launcherDisplayedNow();
void checkOnePartExit();
sp<SurfaceComposerClient> mSession;
AssetManager mAssets;
Texture mAndroid[2];
int mWidth;
int mHeight;
EGLDisplay mDisplay;
EGLDisplay mContext;
EGLDisplay mSurface;
sp<SurfaceControl> mFlingerSurfaceControl;
sp<Surface> mFlingerSurface;
bool mAndroidAnimation;
ZipFileRO mZip;
AnimInfo mAnimInfo;
int mRotation;
};
// ---------------------------------------------------------------------------
}; // namespace android
#endif // ANDROID_BOOTANIMATION_H
- Android开机动画视频
- [Android]添加自定义开机动画(视频)并去黑屏
- [Android]添加自定义开机动画(视频)并去黑屏
- [Android]添加自定义开机动画(视频)并去黑屏
- android 开机logo 开机动画
- Android 开机动画
- Android 开机动画
- android 开机动画
- android 开机动画
- android开机动画
- android 开机动画修改
- Android开机动画
- Android开机动画过程
- android开机动画
- Android修改开机动画
- Android开机动画详解
- Android 开机logo动画
- Android开机动画过程
- OpenCV实现图像识别
- 递归和尾递归
- HTC VIVE 常见问题集合
- GitBash基本操作
- android 悬浮窗口的权限
- Android开机动画视频
- JSON语法规则
- 错误1error C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead.
- 颜色树
- windows下python3.5.x中tesorflow的安装
- 线段树模板+详解
- C#调用cmd命令
- NYOJ 502 筹建工程
- 16. 3Sum Closest