1 /*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "TextDescriptions.h"
18 #include <media/stagefright/foundation/ByteUtils.h>
19 #include <media/stagefright/MediaErrors.h>
20
21 namespace android {
22
TextDescriptions()23 TextDescriptions::TextDescriptions() {
24 }
25
getParcelOfDescriptions(const uint8_t * data,ssize_t size,uint32_t flags,int timeMs,Parcel * parcel)26 status_t TextDescriptions::getParcelOfDescriptions(
27 const uint8_t *data, ssize_t size,
28 uint32_t flags, int timeMs, Parcel *parcel) {
29 parcel->freeData();
30
31 if (flags & IN_BAND_TEXT_3GPP) {
32 if (flags & GLOBAL_DESCRIPTIONS) {
33 return extract3GPPGlobalDescriptions(data, size, parcel);
34 } else if (flags & LOCAL_DESCRIPTIONS) {
35 return extract3GPPLocalDescriptions(data, size, timeMs, parcel);
36 }
37 } else if (flags & OUT_OF_BAND_TEXT_SRT) {
38 if (flags & LOCAL_DESCRIPTIONS) {
39 return extractSRTLocalDescriptions(data, size, timeMs, parcel);
40 }
41 }
42
43 return ERROR_UNSUPPORTED;
44 }
45
46 // Parse the SRT text sample, and store the timing and text sample in a Parcel.
47 // The Parcel will be sent to MediaPlayer.java through event, and will be
48 // parsed in TimedText.java.
extractSRTLocalDescriptions(const uint8_t * data,ssize_t size,int timeMs,Parcel * parcel)49 status_t TextDescriptions::extractSRTLocalDescriptions(
50 const uint8_t *data, ssize_t size, int timeMs, Parcel *parcel) {
51 parcel->writeInt32(KEY_LOCAL_SETTING);
52 parcel->writeInt32(KEY_START_TIME);
53 parcel->writeInt32(timeMs);
54
55 parcel->writeInt32(KEY_STRUCT_TEXT);
56 // write the size of the text sample
57 parcel->writeInt32(size);
58 // write the text sample as a byte array
59 parcel->writeInt32(size);
60 parcel->write(data, size);
61
62 return OK;
63 }
64
65 // Extract the local 3GPP display descriptions. 3GPP local descriptions
66 // are appended to the text sample if any. The descriptions could include
67 // information such as text styles, highlights, karaoke and so on. They
68 // are contained in different boxes, such as 'styl' box contains text
69 // styles, and 'krok' box contains karaoke timing and positions.
extract3GPPLocalDescriptions(const uint8_t * data,ssize_t size,int timeMs,Parcel * parcel)70 status_t TextDescriptions::extract3GPPLocalDescriptions(
71 const uint8_t *data, ssize_t size,
72 int timeMs, Parcel *parcel) {
73
74 parcel->writeInt32(KEY_LOCAL_SETTING);
75
76 // write start time to display this text sample
77 parcel->writeInt32(KEY_START_TIME);
78 parcel->writeInt32(timeMs);
79
80 if (size < 2) {
81 return OK;
82 }
83 ssize_t textLen = (*data) << 8 | (*(data + 1));
84
85 if (size < textLen + 2) {
86 return OK;
87 }
88
89 // write text sample length and text sample itself
90 parcel->writeInt32(KEY_STRUCT_TEXT);
91 parcel->writeInt32(textLen);
92 parcel->writeInt32(textLen);
93 parcel->write(data + 2, textLen);
94
95 if (size > textLen + 2) {
96 data += (textLen + 2);
97 size -= (textLen + 2);
98 } else {
99 return OK;
100 }
101
102 while (size >= 8) {
103 const uint8_t *tmpData = data;
104 ssize_t chunkSize = U32_AT(tmpData); // size includes size and type
105 uint32_t chunkType = U32_AT(tmpData + 4);
106
107 if (chunkSize <= 8 || chunkSize > size) {
108 return OK;
109 }
110
111 size_t remaining = chunkSize - 8;
112
113 tmpData += 8;
114
115 switch(chunkType) {
116 // 'styl' box specifies the style of the text.
117 case FOURCC('s', 't', 'y', 'l'):
118 {
119 if (remaining < 2) {
120 return OK;
121 }
122 size_t dataPos = parcel->dataPosition();
123 uint16_t count = U16_AT(tmpData);
124
125 tmpData += 2;
126 remaining -= 2;
127
128 for (int i = 0; i < count; i++) {
129 if (remaining < 12) {
130 // roll back
131 parcel->setDataPosition(dataPos);
132 return OK;
133 }
134 parcel->writeInt32(KEY_STRUCT_STYLE_LIST);
135 parcel->writeInt32(KEY_START_CHAR);
136 parcel->writeInt32(U16_AT(tmpData));
137
138 parcel->writeInt32(KEY_END_CHAR);
139 parcel->writeInt32(U16_AT(tmpData + 2));
140
141 parcel->writeInt32(KEY_FONT_ID);
142 parcel->writeInt32(U16_AT(tmpData + 4));
143
144 parcel->writeInt32(KEY_STYLE_FLAGS);
145 parcel->writeInt32(*(tmpData + 6));
146
147 parcel->writeInt32(KEY_FONT_SIZE);
148 parcel->writeInt32(*(tmpData + 7));
149
150 parcel->writeInt32(KEY_TEXT_COLOR_RGBA);
151 uint32_t rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16
152 | *(tmpData + 10) << 8 | *(tmpData + 11);
153 parcel->writeInt32(rgba);
154
155 tmpData += 12;
156 remaining -= 12;
157 }
158
159 break;
160 }
161 // 'krok' box. The number of highlight events is specified, and each
162 // event is specified by a starting and ending char offset and an end
163 // time for the event.
164 case FOURCC('k', 'r', 'o', 'k'):
165 {
166 if (remaining < 6) {
167 return OK;
168 }
169 size_t dataPos = parcel->dataPosition();
170
171 parcel->writeInt32(KEY_STRUCT_KARAOKE_LIST);
172
173 int startTime = U32_AT(tmpData);
174 uint16_t count = U16_AT(tmpData + 4);
175 parcel->writeInt32(count);
176
177 tmpData += 6;
178 remaining -= 6;
179 int lastEndTime = 0;
180
181 for (int i = 0; i < count; i++) {
182 if (remaining < 8) {
183 // roll back
184 parcel->setDataPosition(dataPos);
185 return OK;
186 }
187 parcel->writeInt32(startTime + lastEndTime);
188
189 lastEndTime = U32_AT(tmpData);
190 parcel->writeInt32(lastEndTime);
191
192 parcel->writeInt32(U16_AT(tmpData + 4));
193 parcel->writeInt32(U16_AT(tmpData + 6));
194
195 tmpData += 8;
196 remaining -= 8;
197 }
198
199 break;
200 }
201 // 'hlit' box specifies highlighted text
202 case FOURCC('h', 'l', 'i', 't'):
203 {
204 if (remaining < 4) {
205 return OK;
206 }
207
208 parcel->writeInt32(KEY_STRUCT_HIGHLIGHT_LIST);
209
210 // the start char offset to highlight
211 parcel->writeInt32(U16_AT(tmpData));
212 // the last char offset to highlight
213 parcel->writeInt32(U16_AT(tmpData + 2));
214
215 tmpData += 4;
216 remaining -= 4;
217 break;
218 }
219 // 'hclr' box specifies the RGBA color: 8 bits each of
220 // red, green, blue, and an alpha(transparency) value
221 case FOURCC('h', 'c', 'l', 'r'):
222 {
223 if (remaining < 4) {
224 return OK;
225 }
226 parcel->writeInt32(KEY_HIGHLIGHT_COLOR_RGBA);
227
228 uint32_t rgba = *(tmpData) << 24 | *(tmpData + 1) << 16
229 | *(tmpData + 2) << 8 | *(tmpData + 3);
230 parcel->writeInt32(rgba);
231
232 tmpData += 4;
233 remaining -= 4;
234 break;
235 }
236 // 'dlay' box specifies a delay after a scroll in and/or
237 // before scroll out.
238 case FOURCC('d', 'l', 'a', 'y'):
239 {
240 if (remaining < 4) {
241 return OK;
242 }
243 parcel->writeInt32(KEY_SCROLL_DELAY);
244
245 uint32_t delay = *(tmpData) << 24 | *(tmpData + 1) << 16
246 | *(tmpData + 2) << 8 | *(tmpData + 3);
247 parcel->writeInt32(delay);
248
249 tmpData += 4;
250 remaining -= 4;
251 break;
252 }
253 // 'href' box for hyper text link
254 case FOURCC('h', 'r', 'e', 'f'):
255 {
256 if (remaining < 5) {
257 return OK;
258 }
259
260 size_t dataPos = parcel->dataPosition();
261
262 parcel->writeInt32(KEY_STRUCT_HYPER_TEXT_LIST);
263
264 // the start offset of the text to be linked
265 parcel->writeInt32(U16_AT(tmpData));
266 // the end offset of the text
267 parcel->writeInt32(U16_AT(tmpData + 2));
268
269 // the number of bytes in the following URL
270 size_t len = *(tmpData + 4);
271 parcel->writeInt32(len);
272
273 remaining -= 5;
274
275 if (remaining < len) {
276 parcel->setDataPosition(dataPos);
277 return OK;
278 }
279 // the linked-to URL
280 parcel->writeInt32(len);
281 parcel->write(tmpData + 5, len);
282
283 tmpData += (5 + len);
284 remaining -= len;
285
286 if (remaining < 1) {
287 parcel->setDataPosition(dataPos);
288 return OK;
289 }
290
291 // the number of bytes in the following "alt" string
292 len = *tmpData;
293 parcel->writeInt32(len);
294
295 tmpData += 1;
296 remaining -= 1;
297 if (remaining < len) {
298 parcel->setDataPosition(dataPos);
299 return OK;
300 }
301
302 // an "alt" string for user display
303 parcel->writeInt32(len);
304 parcel->write(tmpData, len);
305
306 tmpData += 1;
307 remaining -= 1;
308 break;
309 }
310 // 'tbox' box to indicate the position of the text with values
311 // of top, left, bottom and right
312 case FOURCC('t', 'b', 'o', 'x'):
313 {
314 if (remaining < 8) {
315 return OK;
316 }
317 parcel->writeInt32(KEY_STRUCT_TEXT_POS);
318 parcel->writeInt32(U16_AT(tmpData));
319 parcel->writeInt32(U16_AT(tmpData + 2));
320 parcel->writeInt32(U16_AT(tmpData + 4));
321 parcel->writeInt32(U16_AT(tmpData + 6));
322
323 tmpData += 8;
324 remaining -= 8;
325 break;
326 }
327 // 'blnk' to specify the char range to be blinked
328 case FOURCC('b', 'l', 'n', 'k'):
329 {
330 if (remaining < 4) {
331 return OK;
332 }
333
334 parcel->writeInt32(KEY_STRUCT_BLINKING_TEXT_LIST);
335
336 // start char offset
337 parcel->writeInt32(U16_AT(tmpData));
338 // end char offset
339 parcel->writeInt32(U16_AT(tmpData + 2));
340
341 tmpData += 4;
342 remaining -= 4;
343 break;
344 }
345 // 'twrp' box specifies text wrap behavior. If the value if 0x00,
346 // then no wrap. If it's 0x01, then automatic 'soft' wrap is enabled.
347 // 0x02-0xff are reserved.
348 case FOURCC('t', 'w', 'r', 'p'):
349 {
350 if (remaining < 1) {
351 return OK;
352 }
353 parcel->writeInt32(KEY_WRAP_TEXT);
354 parcel->writeInt32(*tmpData);
355
356 tmpData += 1;
357 remaining -= 1;
358 break;
359 }
360 default:
361 {
362 break;
363 }
364 }
365
366 data += chunkSize;
367 size -= chunkSize;
368 }
369
370 return OK;
371 }
372
373 // To extract box 'tx3g' defined in 3GPP TS 26.245, and store it in a Parcel
extract3GPPGlobalDescriptions(const uint8_t * data,ssize_t size,Parcel * parcel)374 status_t TextDescriptions::extract3GPPGlobalDescriptions(
375 const uint8_t *data, ssize_t size, Parcel *parcel) {
376
377 parcel->writeInt32(KEY_GLOBAL_SETTING);
378
379 while (size >= 8) {
380 ssize_t chunkSize = U32_AT(data);
381 uint32_t chunkType = U32_AT(data + 4);
382 const uint8_t *tmpData = data;
383 tmpData += 8;
384 size_t remaining = size - 8;
385
386 if (chunkSize <= 8 || size < chunkSize) {
387 return OK;
388 }
389 switch(chunkType) {
390 case FOURCC('t', 'x', '3', 'g'):
391 {
392 if (remaining < 18) { // 8 just below, and another 10 a little further down
393 return OK;
394 }
395 tmpData += 8; // skip the first 8 bytes
396 remaining -=8;
397 parcel->writeInt32(KEY_DISPLAY_FLAGS);
398 parcel->writeInt32(U32_AT(tmpData));
399
400 parcel->writeInt32(KEY_STRUCT_JUSTIFICATION);
401 parcel->writeInt32(tmpData[4]);
402 parcel->writeInt32(tmpData[5]);
403
404 parcel->writeInt32(KEY_BACKGROUND_COLOR_RGBA);
405 uint32_t rgba = *(tmpData + 6) << 24 | *(tmpData + 7) << 16
406 | *(tmpData + 8) << 8 | *(tmpData + 9);
407 parcel->writeInt32(rgba);
408
409 tmpData += 10;
410 remaining -= 10;
411
412 if (remaining < 8) {
413 return OK;
414 }
415 parcel->writeInt32(KEY_STRUCT_TEXT_POS);
416 parcel->writeInt32(U16_AT(tmpData));
417 parcel->writeInt32(U16_AT(tmpData + 2));
418 parcel->writeInt32(U16_AT(tmpData + 4));
419 parcel->writeInt32(U16_AT(tmpData + 6));
420
421 tmpData += 8;
422 remaining -= 8;
423
424 if (remaining < 12) {
425 return OK;
426 }
427 parcel->writeInt32(KEY_STRUCT_STYLE_LIST);
428 parcel->writeInt32(KEY_START_CHAR);
429 parcel->writeInt32(U16_AT(tmpData));
430
431 parcel->writeInt32(KEY_END_CHAR);
432 parcel->writeInt32(U16_AT(tmpData + 2));
433
434 parcel->writeInt32(KEY_FONT_ID);
435 parcel->writeInt32(U16_AT(tmpData + 4));
436
437 parcel->writeInt32(KEY_STYLE_FLAGS);
438 parcel->writeInt32(*(tmpData + 6));
439
440 parcel->writeInt32(KEY_FONT_SIZE);
441 parcel->writeInt32(*(tmpData + 7));
442
443 parcel->writeInt32(KEY_TEXT_COLOR_RGBA);
444 rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16
445 | *(tmpData + 10) << 8 | *(tmpData + 11);
446 parcel->writeInt32(rgba);
447
448 // tx3g box contains class FontTableBox() which extends ftab box
449 // This information is part of the 3gpp Timed Text Format
450 // Specification#: 26.245 / Section: 5.16(Sample Description Format)
451 // https://www.3gpp.org/ftp/Specs/archive/26_series/26.245/
452
453 tmpData += 12;
454 remaining -= 12;
455
456 if (remaining < 8) {
457 return OK;
458 }
459
460 size_t subChunkSize = U32_AT(tmpData);
461 if(remaining < subChunkSize) {
462 return OK;
463 }
464
465 uint32_t subChunkType = U32_AT(tmpData + 4);
466
467 if (subChunkType == FOURCC('f', 't', 'a', 'b'))
468 {
469 tmpData += 8;
470 size_t subChunkRemaining = subChunkSize - 8;
471
472 if(subChunkRemaining < 2) {
473 return OK;
474 }
475 size_t dataPos = parcel->dataPosition();
476
477 parcel->writeInt32(KEY_STRUCT_FONT_LIST);
478 uint16_t count = U16_AT(tmpData);
479 parcel->writeInt32(count);
480
481 tmpData += 2;
482 subChunkRemaining -= 2;
483
484 for (int i = 0; i < count; i++) {
485 if (subChunkRemaining < 3) {
486 // roll back
487 parcel->setDataPosition(dataPos);
488 return OK;
489 }
490 // font ID
491 parcel->writeInt32(U16_AT(tmpData));
492
493 // font name length
494 size_t len = *(tmpData + 2);
495
496 parcel->writeInt32(len);
497
498 tmpData += 3;
499 subChunkRemaining -=3;
500
501 if (subChunkRemaining < len) {
502 // roll back
503 parcel->setDataPosition(dataPos);
504 return OK;
505 }
506
507 parcel->writeByteArray(len, tmpData);
508 tmpData += len;
509 subChunkRemaining -= len;
510 }
511 tmpData += subChunkRemaining;
512 remaining -= subChunkSize;
513 } else {
514 tmpData += subChunkSize;
515 remaining -= subChunkSize;
516 }
517 break;
518 }
519 default:
520 {
521 break;
522 }
523 }
524
525 data += chunkSize;
526 size -= chunkSize;
527 }
528
529 return OK;
530 }
531
532 } // namespace android
533