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