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/Utils.h>
19#include <media/stagefright/MediaErrors.h>
20
21namespace android {
22
23TextDescriptions::TextDescriptions() {
24}
25
26status_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.
49status_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.
70status_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
374status_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 (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                tmpData += 12;
449                remaining -= 12;
450
451                if (remaining < 2) {
452                    return OK;
453                }
454
455                size_t dataPos = parcel->dataPosition();
456
457                parcel->writeInt32(KEY_STRUCT_FONT_LIST);
458                uint16_t count = U16_AT(tmpData);
459                parcel->writeInt32(count);
460
461                tmpData += 2;
462                remaining -= 2;
463
464                for (int i = 0; i < count; i++) {
465                    if (remaining < 3) {
466                        // roll back
467                        parcel->setDataPosition(dataPos);
468                        return OK;
469                    }
470                    // font ID
471                    parcel->writeInt32(U16_AT(tmpData));
472
473                    // font name length
474                    parcel->writeInt32(*(tmpData + 2));
475
476                    size_t len = *(tmpData + 2);
477
478                    tmpData += 3;
479                    remaining -= 3;
480
481                    if (remaining < len) {
482                        // roll back
483                        parcel->setDataPosition(dataPos);
484                        return OK;
485                    }
486
487                    parcel->write(tmpData, len);
488                    tmpData += len;
489                    remaining -= len;
490                }
491
492                // there is a "DisparityBox" after this according to the spec, but we ignore it
493                break;
494            }
495            default:
496            {
497                break;
498            }
499        }
500
501        data += chunkSize;
502        size -= chunkSize;
503    }
504
505    return OK;
506}
507
508}  // namespace android
509