开发平台:君正M200S
安卓系统:Android5.1
一、前言
这几天做了一个自定义开机音乐动画的任务,再此记录一下,方便日后复习。若是哪里有误,敬请指教。
二、代码分析
开机动画的源码在ingenic-android/frameworks/base/cmds/bootanimation这个目录下,这个目录有这几个文件:
Android.mk,AudioPlayer.cpp,AudioPlayer.h,BootAnimation.cpp,BootAnimation.h,bootanimation_main.cpp
首先看下bootanimation_main.cpp,这个文件就一个mian函数,
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();
IPCThreadState::self()->joinThreadPool();
}
return 0;
}
这里创建了一个bootanimation的对象,然后进入BootAnimation.cpp
BootAnimation::BootAnimation() : Thread(false), mZip(NULL)
{
mSession = new SurfaceComposerClient();
}
在这里构造函数里创建了SurfaceComposerClient这个类,bootanimation通过SurfaceComposerClient类来与SurfaceFlinger服务建立连接。
class BootAnimation : public Thread, public IBinder::DeathRecipient
{
public:
BootAnimation();
virtual ~BootAnimation();
sp<SurfaceComposerClient> session() const;
private:
virtual bool threadLoop();
virtual status_t readyToRun();
virtual void onFirstRef();
virtual void binderDied(const wp<IBinder>& who);
接着从BootAnimation.h里看到BootAnimation有个Thread的父类,会运行readyToRun()这个方法
status_t BootAnimation::readyToRun() {
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);
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;
// 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);
ZipFileRO* zipFile = NULL;
if ((encryptedAnimation &&
(access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||
((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) ||
((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {
mZip = zipFile;
}
return NO_ERROR;
}
这个方法主要就是检测bootanimation.zip是否存在,然后运行threadLoop()
bool BootAnimation::threadLoop()
{
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
if (mZip == NULL) {
r = android();
} else {
r = movie();
}
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;
}
在这里可以看到如果没有bootanimation.zip,则运行android()启动安卓自带的开机画面,如果存在就运行movie();
bool BootAnimation::movie()
{
String8 desString;
if (!readFile("desc.txt", desString)) {
ALOGE("readFile desc.txt failed");
return false;
}
char const* s = desString.string();
// Create and initialize an AudioPlayer if we have an audio_conf.txt file
String8 audioConf;
if (readFile("audio_conf.txt", audioConf)) {
mAudioPlayer = new AudioPlayer;
if (!mAudioPlayer->init(audioConf.string())) {
ALOGE("mAudioPlayer.init failed");
mAudioPlayer = NULL;
}
}
Animation animation;
// Parse the description file
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[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified
char pathType;
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;
}
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;
}
animation.parts.add(part);
}
s = ++endl;
}
// read all the data structures
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) {
int method;
// supports only stored png files
if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {
if (method == ZipFileRO::kCompressStored) {
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;
frame.map = map;
part.frames.add(frame);
}
}
}
}
}
}
}
}
mZip->endIteration(cookie);
// clear screen
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);
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 lastFrame = systemTime();
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) {
ALOGE("part.audioFile play srart");
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& r(*head++);
glScissor(r.left, mHeight - r.bottom,
r.width(), r.height());
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
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);
}
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;
}
// free the textures for this part
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;
}
然后解析zip文件中的内容,先是处理desc.txt和audio_conf.txt。
接下来是读取音乐和图片信息
if (leaf == "audio.wav") {
// a part may have at most one audio file
part.audioFile = map;
} else {
Animation::Frame frame;
frame.name = leaf;
frame.map = map;
part.frames.add(frame);
}
leaf是part0里的读到的文件,如果读到的是"audio.wav",则记录在part.audioFile这个变量中,所以音乐文件的名字和格式必须是audio.wav,此外我还放了3张图片,else里记录的就是这几张图片的信息。注意这些文件一定要在part0中。
if (r == 0 && mAudioPlayer != NULL && part.audioFile) {
ALOGE("part.audioFile play srart");
mAudioPlayer->playFile(part.audioFile);
}
这里是播放音乐
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& r(*head++);
glScissor(r.left, mHeight - r.bottom,
r.width(), r.height());
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
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);
}
checkExit();
}
这里就是处理图片的代码。
三、BootAnimation.zip制作
BootAnimation.zip里面有这么几个文件,desc.txt、audio_conf.txt和part0文件夹,part0文件夹里有需要播放的audio.wav音频文件和1.png、2.png、3.png这4个图片文件。
desc.txt里内容为
400 400 1
p 0 0 part0
第一位和第二位表示400X400的分辨率,第三位为帧率,意思是1秒播放一张图片;
p可能是固定格式吧,第二位代表循环播放,如果只播放1次可以给1,第三位是播放间隔,第四位是文件夹名称。
audio_conf.txt是音频的参数配置
card=0
device=0
period_size=1024
period_count=4
mixer "Digital Playback Volume"=31 31
mixer "Headphone Volume"=15 15
mixer "Digital Capture Volume"=15 15
mixer "Mic1 Volume"=5
mixer "Mic2 Volume"=0
mixer "Bypass1 Volume"=0
mixer "Bypass2 Volume"=0
mixer "ADC High Pass Filter Switch"=On
mixer "ADC Wind Noise Filter Switch"=Inactive
mixer "Mic1 Diff Switch"=On
mixer "Mic Stereo Switch"=Off
mixer "ADC Stereo Switch"=On
mixer "Bypass1 Diff Switch"=Off
mixer "Mic1 Bias Switch"=Off
mixer "Mic2 Bias Switch"=Off
mixer "AILatt Mux"=AIPN1
mixer "AIL Mux"=AIPN1
mixer "AOLO Vmux"=LO-ON
mixer "AOHP Vmux"=HP-ON
mixer "AOLO Mux"=DACL/R
mixer "AOHP Mux"=DACL/R
mixer "ADC Mux"=AIL/L
不同芯片配置名称和参数可能不一样,可以在adb中输入tinymix查询ctl。
准备好这些文件后,打开ubuntu终端,制作bootanimation.zip
#cd bootanimation/
#zip -r -X -Z store ../bootanimation ./
接着打开安卓adb终端,将bootanimation.zip放入安卓系统
#adb remount //开发系统修改权限
#adb push ../bootanimation.zip /system/media/ //push文件
然后重启验证效果。
PS:desc.txt该文件内容要在linux环境下编写,windows下编写解析失败,大概是win下的换行为/n/r,而linux换行为/n的缘故吧。
四、参考文献
最后感谢各位前辈的分享
https://blog.csdn.net/briblue/article/details/51104230 //Android Framework中的线程Thread及它的threadLoop方法
https://blog.csdn.net/kris_fei/article/details/78130095 //[RK3288][Android6.0] 调试笔记 --- RT5640的ctl name列表
https://blog.csdn.net/ni357103403/article/details/50970303 //android 开机动画desc.txt格式介绍
https://blog.csdn.net/luoshengyang/article/details/7857163 //Android应用程序与SurfaceFlinger服务的连接过程分析
版权声明:本文为博主原创文章,未经博主允许不得转载。