|
1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors |
|
2 * |
|
3 * Licensed under the Apache License, Version 2.0 (the "License"); |
|
4 * you may not use this file except in compliance with the License. |
|
5 * You may obtain a copy of the License at |
|
6 * |
|
7 * http://www.apache.org/licenses/LICENSE-2.0 |
|
8 * |
|
9 * Unless required by applicable law or agreed to in writing, software |
|
10 * distributed under the License is distributed on an "AS IS" BASIS, |
|
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
12 * See the License for the specific language governing permissions and |
|
13 * limitations under the License. |
|
14 */ |
|
15 |
|
16 #include <algorithm> |
|
17 #include <endian.h> |
|
18 #include <fcntl.h> |
|
19 #include <string> |
|
20 #include <sys/mman.h> |
|
21 #include <sys/stat.h> |
|
22 #include <vector> |
|
23 #include "mozilla/FileUtils.h" |
|
24 #include "mozilla/NullPtr.h" |
|
25 #include "png.h" |
|
26 |
|
27 #include "android/log.h" |
|
28 #include "GonkDisplay.h" |
|
29 #include "hardware/gralloc.h" |
|
30 |
|
31 #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) |
|
32 #define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "Gonk", ## args) |
|
33 #define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "Gonk", ## args) |
|
34 |
|
35 using namespace mozilla; |
|
36 using namespace std; |
|
37 |
|
38 static pthread_t sAnimationThread; |
|
39 static bool sRunAnimation; |
|
40 |
|
41 /* See http://www.pkware.com/documents/casestudies/APPNOTE.TXT */ |
|
42 struct local_file_header { |
|
43 uint32_t signature; |
|
44 uint16_t min_version; |
|
45 uint16_t general_flag; |
|
46 uint16_t compression; |
|
47 uint16_t lastmod_time; |
|
48 uint16_t lastmod_date; |
|
49 uint32_t crc32; |
|
50 uint32_t compressed_size; |
|
51 uint32_t uncompressed_size; |
|
52 uint16_t filename_size; |
|
53 uint16_t extra_field_size; |
|
54 char data[0]; |
|
55 |
|
56 uint32_t GetDataSize() const |
|
57 { |
|
58 return letoh32(uncompressed_size); |
|
59 } |
|
60 |
|
61 uint32_t GetSize() const |
|
62 { |
|
63 /* XXX account for data descriptor */ |
|
64 return sizeof(local_file_header) + letoh16(filename_size) + |
|
65 letoh16(extra_field_size) + GetDataSize(); |
|
66 } |
|
67 |
|
68 const char * GetData() const |
|
69 { |
|
70 return data + letoh16(filename_size) + letoh16(extra_field_size); |
|
71 } |
|
72 } __attribute__((__packed__)); |
|
73 |
|
74 struct data_descriptor { |
|
75 uint32_t crc32; |
|
76 uint32_t compressed_size; |
|
77 uint32_t uncompressed_size; |
|
78 } __attribute__((__packed__)); |
|
79 |
|
80 struct cdir_entry { |
|
81 uint32_t signature; |
|
82 uint16_t creator_version; |
|
83 uint16_t min_version; |
|
84 uint16_t general_flag; |
|
85 uint16_t compression; |
|
86 uint16_t lastmod_time; |
|
87 uint16_t lastmod_date; |
|
88 uint32_t crc32; |
|
89 uint32_t compressed_size; |
|
90 uint32_t uncompressed_size; |
|
91 uint16_t filename_size; |
|
92 uint16_t extra_field_size; |
|
93 uint16_t file_comment_size; |
|
94 uint16_t disk_num; |
|
95 uint16_t internal_attr; |
|
96 uint32_t external_attr; |
|
97 uint32_t offset; |
|
98 char data[0]; |
|
99 |
|
100 uint32_t GetDataSize() const |
|
101 { |
|
102 return letoh32(compressed_size); |
|
103 } |
|
104 |
|
105 uint32_t GetSize() const |
|
106 { |
|
107 return sizeof(cdir_entry) + letoh16(filename_size) + |
|
108 letoh16(extra_field_size) + letoh16(file_comment_size); |
|
109 } |
|
110 |
|
111 bool Valid() const |
|
112 { |
|
113 return signature == htole32(0x02014b50); |
|
114 } |
|
115 } __attribute__((__packed__)); |
|
116 |
|
117 struct cdir_end { |
|
118 uint32_t signature; |
|
119 uint16_t disk_num; |
|
120 uint16_t cdir_disk; |
|
121 uint16_t disk_entries; |
|
122 uint16_t cdir_entries; |
|
123 uint32_t cdir_size; |
|
124 uint32_t cdir_offset; |
|
125 uint16_t comment_size; |
|
126 char comment[0]; |
|
127 |
|
128 bool Valid() const |
|
129 { |
|
130 return signature == htole32(0x06054b50); |
|
131 } |
|
132 } __attribute__((__packed__)); |
|
133 |
|
134 /* We don't have access to libjar and the zip reader in android |
|
135 * doesn't quite fit what we want to do. */ |
|
136 class ZipReader { |
|
137 const char *mBuf; |
|
138 const cdir_end *mEnd; |
|
139 const char *mCdir_limit; |
|
140 uint32_t mBuflen; |
|
141 |
|
142 public: |
|
143 ZipReader() : mBuf(nullptr) {} |
|
144 ~ZipReader() { |
|
145 if (mBuf) |
|
146 munmap((void *)mBuf, mBuflen); |
|
147 } |
|
148 |
|
149 bool OpenArchive(const char *path) |
|
150 { |
|
151 int fd; |
|
152 do { |
|
153 fd = open(path, O_RDONLY); |
|
154 } while (fd == -1 && errno == EINTR); |
|
155 if (fd == -1) |
|
156 return false; |
|
157 |
|
158 struct stat sb; |
|
159 if (fstat(fd, &sb) == -1 || sb.st_size < sizeof(cdir_end)) { |
|
160 close(fd); |
|
161 return false; |
|
162 } |
|
163 |
|
164 mBuflen = sb.st_size; |
|
165 mBuf = (char *)mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); |
|
166 close(fd); |
|
167 |
|
168 if (!mBuf) { |
|
169 return false; |
|
170 } |
|
171 |
|
172 madvise(mBuf, sb.st_size, MADV_SEQUENTIAL); |
|
173 |
|
174 mEnd = (cdir_end *)(mBuf + mBuflen - sizeof(cdir_end)); |
|
175 while (!mEnd->Valid() && |
|
176 (char *)mEnd > mBuf) { |
|
177 mEnd = (cdir_end *)((char *)mEnd - 1); |
|
178 } |
|
179 |
|
180 mCdir_limit = mBuf + letoh32(mEnd->cdir_offset) + letoh32(mEnd->cdir_size); |
|
181 |
|
182 if (!mEnd->Valid() || mCdir_limit > (char *)mEnd) { |
|
183 munmap((void *)mBuf, mBuflen); |
|
184 mBuf = nullptr; |
|
185 return false; |
|
186 } |
|
187 |
|
188 return true; |
|
189 } |
|
190 |
|
191 /* Pass null to get the first cdir entry */ |
|
192 const cdir_entry * GetNextEntry(const cdir_entry *prev) |
|
193 { |
|
194 const cdir_entry *entry; |
|
195 if (prev) |
|
196 entry = (cdir_entry *)((char *)prev + prev->GetSize()); |
|
197 else |
|
198 entry = (cdir_entry *)(mBuf + letoh32(mEnd->cdir_offset)); |
|
199 |
|
200 if (((char *)entry + entry->GetSize()) > mCdir_limit || |
|
201 !entry->Valid()) |
|
202 return nullptr; |
|
203 return entry; |
|
204 } |
|
205 |
|
206 string GetEntryName(const cdir_entry *entry) |
|
207 { |
|
208 uint16_t len = letoh16(entry->filename_size); |
|
209 |
|
210 string name; |
|
211 name.append(entry->data, len); |
|
212 return name; |
|
213 } |
|
214 |
|
215 const local_file_header * GetLocalEntry(const cdir_entry *entry) |
|
216 { |
|
217 const local_file_header * data = |
|
218 (local_file_header *)(mBuf + letoh32(entry->offset)); |
|
219 if (((char *)data + data->GetSize()) > (char *)mEnd) |
|
220 return nullptr; |
|
221 return data; |
|
222 } |
|
223 }; |
|
224 |
|
225 struct AnimationFrame { |
|
226 char path[256]; |
|
227 char *buf; |
|
228 const local_file_header *file; |
|
229 uint32_t width; |
|
230 uint32_t height; |
|
231 uint16_t bytepp; |
|
232 |
|
233 AnimationFrame() : buf(nullptr) {} |
|
234 AnimationFrame(const AnimationFrame &frame) : buf(nullptr) { |
|
235 strncpy(path, frame.path, sizeof(path)); |
|
236 file = frame.file; |
|
237 } |
|
238 ~AnimationFrame() |
|
239 { |
|
240 if (buf) |
|
241 free(buf); |
|
242 } |
|
243 |
|
244 bool operator<(const AnimationFrame &other) const |
|
245 { |
|
246 return strcmp(path, other.path) < 0; |
|
247 } |
|
248 |
|
249 void ReadPngFrame(int outputFormat); |
|
250 }; |
|
251 |
|
252 struct AnimationPart { |
|
253 int32_t count; |
|
254 int32_t pause; |
|
255 char path[256]; |
|
256 vector<AnimationFrame> frames; |
|
257 }; |
|
258 |
|
259 struct RawReadState { |
|
260 const char *start; |
|
261 uint32_t offset; |
|
262 uint32_t length; |
|
263 }; |
|
264 |
|
265 static void |
|
266 RawReader(png_structp png_ptr, png_bytep data, png_size_t length) |
|
267 { |
|
268 RawReadState *state = (RawReadState *)png_get_io_ptr(png_ptr); |
|
269 if (length > (state->length - state->offset)) |
|
270 png_error(png_ptr, "PNG read overrun"); |
|
271 |
|
272 memcpy(data, state->start + state->offset, length); |
|
273 state->offset += length; |
|
274 } |
|
275 |
|
276 static void |
|
277 TransformTo565(png_structp png_ptr, png_row_infop row_info, png_bytep data) |
|
278 { |
|
279 uint16_t *outbuf = (uint16_t *)data; |
|
280 uint8_t *inbuf = (uint8_t *)data; |
|
281 for (int i = 0; i < row_info->rowbytes; i += 3) { |
|
282 *outbuf++ = ((inbuf[i] & 0xF8) << 8) | |
|
283 ((inbuf[i + 1] & 0xFC) << 3) | |
|
284 ((inbuf[i + 2] ) >> 3); |
|
285 } |
|
286 } |
|
287 |
|
288 void |
|
289 AnimationFrame::ReadPngFrame(int outputFormat) |
|
290 { |
|
291 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED |
|
292 static const png_byte unused_chunks[] = |
|
293 { 98, 75, 71, 68, '\0', /* bKGD */ |
|
294 99, 72, 82, 77, '\0', /* cHRM */ |
|
295 104, 73, 83, 84, '\0', /* hIST */ |
|
296 105, 67, 67, 80, '\0', /* iCCP */ |
|
297 105, 84, 88, 116, '\0', /* iTXt */ |
|
298 111, 70, 70, 115, '\0', /* oFFs */ |
|
299 112, 67, 65, 76, '\0', /* pCAL */ |
|
300 115, 67, 65, 76, '\0', /* sCAL */ |
|
301 112, 72, 89, 115, '\0', /* pHYs */ |
|
302 115, 66, 73, 84, '\0', /* sBIT */ |
|
303 115, 80, 76, 84, '\0', /* sPLT */ |
|
304 116, 69, 88, 116, '\0', /* tEXt */ |
|
305 116, 73, 77, 69, '\0', /* tIME */ |
|
306 122, 84, 88, 116, '\0'}; /* zTXt */ |
|
307 static const png_byte tRNS_chunk[] = |
|
308 {116, 82, 78, 83, '\0'}; /* tRNS */ |
|
309 #endif |
|
310 |
|
311 png_structp pngread = png_create_read_struct(PNG_LIBPNG_VER_STRING, |
|
312 nullptr, nullptr, nullptr); |
|
313 |
|
314 if (!pngread) |
|
315 return; |
|
316 |
|
317 png_infop pnginfo = png_create_info_struct(pngread); |
|
318 |
|
319 if (!pnginfo) { |
|
320 png_destroy_read_struct(&pngread, &pnginfo, nullptr); |
|
321 return; |
|
322 } |
|
323 |
|
324 if (setjmp(png_jmpbuf(pngread))) { |
|
325 // libpng reported an error and longjumped here. Clean up and return. |
|
326 png_destroy_read_struct(&pngread, &pnginfo, nullptr); |
|
327 return; |
|
328 } |
|
329 |
|
330 RawReadState state; |
|
331 state.start = file->GetData(); |
|
332 state.length = file->GetDataSize(); |
|
333 state.offset = 0; |
|
334 |
|
335 png_set_read_fn(pngread, &state, RawReader); |
|
336 |
|
337 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED |
|
338 /* Ignore unused chunks */ |
|
339 png_set_keep_unknown_chunks(pngread, 1, unused_chunks, |
|
340 (int)sizeof(unused_chunks)/5); |
|
341 |
|
342 /* Ignore the tRNS chunk if we only want opaque output */ |
|
343 if (outputFormat == HAL_PIXEL_FORMAT_RGB_888 || |
|
344 outputFormat == HAL_PIXEL_FORMAT_RGB_565) { |
|
345 png_set_keep_unknown_chunks(pngread, 1, tRNS_chunk, 1); |
|
346 } |
|
347 #endif |
|
348 |
|
349 png_read_info(pngread, pnginfo); |
|
350 |
|
351 width = png_get_image_width(pngread, pnginfo); |
|
352 height = png_get_image_height(pngread, pnginfo); |
|
353 |
|
354 switch (outputFormat) { |
|
355 case HAL_PIXEL_FORMAT_BGRA_8888: |
|
356 png_set_bgr(pngread); |
|
357 // FALL THROUGH |
|
358 case HAL_PIXEL_FORMAT_RGBA_8888: |
|
359 case HAL_PIXEL_FORMAT_RGBX_8888: |
|
360 bytepp = 4; |
|
361 png_set_filler(pngread, 0xFF, PNG_FILLER_AFTER); |
|
362 break; |
|
363 case HAL_PIXEL_FORMAT_RGB_888: |
|
364 bytepp = 3; |
|
365 png_set_strip_alpha(pngread); |
|
366 break; |
|
367 default: |
|
368 LOGW("Unknown pixel format %d. Assuming RGB 565.", outputFormat); |
|
369 // FALL THROUGH |
|
370 case HAL_PIXEL_FORMAT_RGB_565: |
|
371 bytepp = 2; |
|
372 png_set_strip_alpha(pngread); |
|
373 png_set_read_user_transform_fn(pngread, TransformTo565); |
|
374 break; |
|
375 } |
|
376 |
|
377 // An extra row is added to give libpng enough space when |
|
378 // decoding 3/4 bytepp inputs for 2 bytepp output surfaces |
|
379 buf = (char *)malloc(width * (height + 1) * bytepp); |
|
380 |
|
381 vector<char *> rows(height + 1); |
|
382 uint32_t stride = width * bytepp; |
|
383 for (int i = 0; i < height; i++) { |
|
384 rows[i] = buf + (stride * i); |
|
385 } |
|
386 rows[height] = nullptr; |
|
387 png_set_strip_16(pngread); |
|
388 png_set_palette_to_rgb(pngread); |
|
389 png_set_gray_to_rgb(pngread); |
|
390 png_read_image(pngread, (png_bytepp)&rows.front()); |
|
391 png_destroy_read_struct(&pngread, &pnginfo, nullptr); |
|
392 } |
|
393 |
|
394 static void * |
|
395 AnimationThread(void *) |
|
396 { |
|
397 ZipReader reader; |
|
398 if (!reader.OpenArchive("/system/media/bootanimation.zip")) { |
|
399 LOGW("Could not open boot animation"); |
|
400 return nullptr; |
|
401 } |
|
402 |
|
403 const cdir_entry *entry = nullptr; |
|
404 const local_file_header *file = nullptr; |
|
405 while ((entry = reader.GetNextEntry(entry))) { |
|
406 string name = reader.GetEntryName(entry); |
|
407 if (!name.compare("desc.txt")) { |
|
408 file = reader.GetLocalEntry(entry); |
|
409 break; |
|
410 } |
|
411 } |
|
412 |
|
413 if (!file) { |
|
414 LOGW("Could not find desc.txt in boot animation"); |
|
415 return nullptr; |
|
416 } |
|
417 |
|
418 GonkDisplay *display = GetGonkDisplay(); |
|
419 int format = display->surfaceformat; |
|
420 |
|
421 hw_module_t const *module; |
|
422 if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) { |
|
423 LOGW("Could not get gralloc module"); |
|
424 return nullptr; |
|
425 } |
|
426 gralloc_module_t const *grmodule = |
|
427 reinterpret_cast<gralloc_module_t const*>(module); |
|
428 |
|
429 string descCopy; |
|
430 descCopy.append(file->GetData(), entry->GetDataSize()); |
|
431 int32_t width, height, fps; |
|
432 const char *line = descCopy.c_str(); |
|
433 const char *end; |
|
434 bool headerRead = true; |
|
435 vector<AnimationPart> parts; |
|
436 |
|
437 /* |
|
438 * bootanimation.zip |
|
439 * |
|
440 * This is the boot animation file format that Android uses. |
|
441 * It's a zip file with a directories containing png frames |
|
442 * and a desc.txt that describes how they should be played. |
|
443 * |
|
444 * desc.txt contains two types of lines |
|
445 * 1. [width] [height] [fps] |
|
446 * There is one of these lines per bootanimation. |
|
447 * If the width and height are smaller than the screen, |
|
448 * the frames are centered on a black background. |
|
449 * XXX: Currently we stretch instead of centering the frame. |
|
450 * 2. p [count] [pause] [path] |
|
451 * This describes one animation part. |
|
452 * Each animation part is played in sequence. |
|
453 * An animation part contains all the files/frames in the |
|
454 * directory specified in [path] |
|
455 * [count] indicates the number of times this part repeats. |
|
456 * [pause] indicates the number of frames that this part |
|
457 * should pause for after playing the full sequence but |
|
458 * before repeating. |
|
459 */ |
|
460 |
|
461 do { |
|
462 end = strstr(line, "\n"); |
|
463 |
|
464 AnimationPart part; |
|
465 if (headerRead && |
|
466 sscanf(line, "%d %d %d", &width, &height, &fps) == 3) { |
|
467 headerRead = false; |
|
468 } else if (sscanf(line, "p %d %d %s", |
|
469 &part.count, &part.pause, part.path)) { |
|
470 parts.push_back(part); |
|
471 } |
|
472 } while (end && *(line = end + 1)); |
|
473 |
|
474 for (uint32_t i = 0; i < parts.size(); i++) { |
|
475 AnimationPart &part = parts[i]; |
|
476 entry = nullptr; |
|
477 char search[256]; |
|
478 snprintf(search, sizeof(search), "%s/", part.path); |
|
479 while ((entry = reader.GetNextEntry(entry))) { |
|
480 string name = reader.GetEntryName(entry); |
|
481 if (name.find(search) || |
|
482 !entry->GetDataSize() || |
|
483 name.length() >= 256) |
|
484 continue; |
|
485 |
|
486 part.frames.push_back(); |
|
487 AnimationFrame &frame = part.frames.back(); |
|
488 strcpy(frame.path, name.c_str()); |
|
489 frame.file = reader.GetLocalEntry(entry); |
|
490 } |
|
491 |
|
492 sort(part.frames.begin(), part.frames.end()); |
|
493 } |
|
494 |
|
495 uint32_t frameDelayUs = 1000000 / fps; |
|
496 |
|
497 for (uint32_t i = 0; i < parts.size(); i++) { |
|
498 AnimationPart &part = parts[i]; |
|
499 |
|
500 uint32_t j = 0; |
|
501 while (sRunAnimation && (!part.count || j++ < part.count)) { |
|
502 for (uint32_t k = 0; k < part.frames.size(); k++) { |
|
503 struct timeval tv1, tv2; |
|
504 gettimeofday(&tv1, nullptr); |
|
505 AnimationFrame &frame = part.frames[k]; |
|
506 if (!frame.buf) { |
|
507 frame.ReadPngFrame(format); |
|
508 } |
|
509 |
|
510 ANativeWindowBuffer *buf = display->DequeueBuffer(); |
|
511 if (!buf) { |
|
512 LOGW("Failed to get an ANativeWindowBuffer"); |
|
513 break; |
|
514 } |
|
515 |
|
516 void *vaddr; |
|
517 if (grmodule->lock(grmodule, buf->handle, |
|
518 GRALLOC_USAGE_SW_READ_NEVER | |
|
519 GRALLOC_USAGE_SW_WRITE_OFTEN | |
|
520 GRALLOC_USAGE_HW_FB, |
|
521 0, 0, width, height, &vaddr)) { |
|
522 LOGW("Failed to lock buffer_handle_t"); |
|
523 display->QueueBuffer(buf); |
|
524 break; |
|
525 } |
|
526 |
|
527 if (buf->height == frame.height && buf->width == frame.width) { |
|
528 memcpy(vaddr, frame.buf, |
|
529 frame.width * frame.height * frame.bytepp); |
|
530 } else if (buf->height >= frame.height && |
|
531 buf->width >= frame.width) { |
|
532 int startx = (buf->width - frame.width) / 2; |
|
533 int starty = (buf->height - frame.height) / 2; |
|
534 |
|
535 int src_stride = frame.width * frame.bytepp; |
|
536 int dst_stride = buf->stride * frame.bytepp; |
|
537 |
|
538 char *src = frame.buf; |
|
539 char *dst = (char *) vaddr + starty * dst_stride + startx * frame.bytepp; |
|
540 |
|
541 for (int i = 0; i < frame.height; i++) { |
|
542 memcpy(dst, src, src_stride); |
|
543 src += src_stride; |
|
544 dst += dst_stride; |
|
545 } |
|
546 } |
|
547 grmodule->unlock(grmodule, buf->handle); |
|
548 |
|
549 gettimeofday(&tv2, nullptr); |
|
550 |
|
551 timersub(&tv2, &tv1, &tv2); |
|
552 |
|
553 if (tv2.tv_usec < frameDelayUs) { |
|
554 usleep(frameDelayUs - tv2.tv_usec); |
|
555 } else { |
|
556 LOGW("Frame delay is %d us but decoding took %d us", |
|
557 frameDelayUs, tv2.tv_usec); |
|
558 } |
|
559 |
|
560 display->QueueBuffer(buf); |
|
561 |
|
562 if (part.count && j >= part.count) { |
|
563 free(frame.buf); |
|
564 frame.buf = nullptr; |
|
565 } |
|
566 } |
|
567 usleep(frameDelayUs * part.pause); |
|
568 } |
|
569 } |
|
570 |
|
571 return nullptr; |
|
572 } |
|
573 |
|
574 void |
|
575 StartBootAnimation() |
|
576 { |
|
577 sRunAnimation = true; |
|
578 pthread_create(&sAnimationThread, nullptr, AnimationThread, nullptr); |
|
579 } |
|
580 |
|
581 |
|
582 void |
|
583 StopBootAnimation() |
|
584 { |
|
585 if (sRunAnimation) { |
|
586 sRunAnimation = false; |
|
587 pthread_join(sAnimationThread, nullptr); |
|
588 } |
|
589 } |