• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 //#define LOG_NDEBUG 0
18 #define LOG_TAG "TimedTextUnitTest"
19 #include <utils/Log.h>
20 
21 #include <stdio.h>
22 #include <string.h>
23 #include <sys/stat.h>
24 #include <fstream>
25 
26 #include <binder/Parcel.h>
27 #include <media/stagefright/foundation/AString.h>
28 #include <media/stagefright/foundation/ByteUtils.h>
29 
30 #include <timedtext/TextDescriptions.h>
31 
32 #include "TimedTextTestEnvironment.h"
33 
34 constexpr int32_t kStartTimeMs = 10000;
35 
36 enum {
37     // These keys must be in sync with the keys in
38     // frameworks/av/media/libstagefright/timedtext/TextDescriptions.h
39     KEY_DISPLAY_FLAGS = 1,
40     KEY_STYLE_FLAGS = 2,
41     KEY_BACKGROUND_COLOR_RGBA = 3,
42     KEY_HIGHLIGHT_COLOR_RGBA = 4,
43     KEY_SCROLL_DELAY = 5,
44     KEY_WRAP_TEXT = 6,
45     KEY_START_TIME = 7,
46     KEY_STRUCT_BLINKING_TEXT_LIST = 8,
47     KEY_STRUCT_FONT_LIST = 9,
48     KEY_STRUCT_HIGHLIGHT_LIST = 10,
49     KEY_STRUCT_HYPER_TEXT_LIST = 11,
50     KEY_STRUCT_KARAOKE_LIST = 12,
51     KEY_STRUCT_STYLE_LIST = 13,
52     KEY_STRUCT_TEXT_POS = 14,
53     KEY_STRUCT_JUSTIFICATION = 15,
54     KEY_STRUCT_TEXT = 16,
55 
56     KEY_GLOBAL_SETTING = 101,
57     KEY_LOCAL_SETTING = 102,
58     KEY_START_CHAR = 103,
59     KEY_END_CHAR = 104,
60     KEY_FONT_ID = 105,
61     KEY_FONT_SIZE = 106,
62     KEY_TEXT_COLOR_RGBA = 107,
63 };
64 
65 struct FontInfo {
66     int32_t displayFlag = -1;
67     int32_t horizontalJustification = -1;
68     int32_t verticalJustification = -1;
69     int32_t rgbaBackground = -1;
70     int32_t leftPos = -1;
71     int32_t topPos = -1;
72     int32_t bottomPos = -1;
73     int32_t rightPos = -1;
74     int32_t startchar = -1;
75     int32_t endChar = -1;
76     int32_t fontId = -1;
77     int32_t faceStyle = -1;
78     int32_t fontSize = -1;
79     int32_t rgbaText = -1;
80     int32_t entryCount = -1;
81 };
82 
83 struct FontRecord {
84     int32_t fontID = -1;
85     int32_t fontNameLength = -1;
86     const uint8_t *font = nullptr;
87 };
88 
89 using namespace android;
90 
91 static TimedTextTestEnvironment *gEnv = nullptr;
92 
93 class TimedTextUnitTest : public ::testing::TestWithParam</*filename*/ string> {
94   public:
TimedTextUnitTest()95     TimedTextUnitTest(){};
96 
~TimedTextUnitTest()97     ~TimedTextUnitTest() {
98         if (mEleStream) mEleStream.close();
99     }
100 
SetUp()101     virtual void SetUp() override {
102         mInputFileName = gEnv->getRes() + GetParam();
103         mEleStream.open(mInputFileName, ifstream::binary);
104         ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open " << GetParam();
105 
106         struct stat buf;
107         status_t status = stat(mInputFileName.c_str(), &buf);
108         ASSERT_EQ(status, 0) << "Failed to get properties of input file: " << GetParam();
109         mFileSize = buf.st_size;
110         ALOGI("Size of the input file %s = %zu", GetParam().c_str(), mFileSize);
111     }
112 
113     string mInputFileName;
114     size_t mFileSize;
115     ifstream mEleStream;
116 };
117 
118 class SRTDescriptionTest : public TimedTextUnitTest {
119   public:
SetUp()120     virtual void SetUp() override { TimedTextUnitTest::SetUp(); }
121 };
122 
123 class Text3GPPDescriptionTest : public TimedTextUnitTest {
124   public:
SetUp()125     virtual void SetUp() override { TimedTextUnitTest::SetUp(); }
126 };
127 
TEST_P(SRTDescriptionTest,extractSRTDescriptionTest)128 TEST_P(SRTDescriptionTest, extractSRTDescriptionTest) {
129     char data[mFileSize];
130     mEleStream.read(data, sizeof(data));
131     ASSERT_EQ(mEleStream.gcount(), mFileSize);
132 
133     Parcel parcel;
134     int32_t flag = TextDescriptions::OUT_OF_BAND_TEXT_SRT | TextDescriptions::LOCAL_DESCRIPTIONS;
135     status_t status = TextDescriptions::getParcelOfDescriptions((const uint8_t *)data, mFileSize,
136                                                                 flag, kStartTimeMs, &parcel);
137     ASSERT_EQ(status, 0) << "getParcelOfDescriptions returned error";
138     ALOGI("Size of the Parcel: %zu", parcel.dataSize());
139     ASSERT_GT(parcel.dataSize(), 0) << "Parcel is empty";
140 
141     parcel.setDataPosition(0);
142     int32_t key = parcel.readInt32();
143     ASSERT_EQ(key, KEY_LOCAL_SETTING) << "Parcel has invalid key";
144 
145     key = parcel.readInt32();
146     ASSERT_EQ(key, KEY_START_TIME) << "Parcel has invalid start time key";
147     ASSERT_EQ(parcel.readInt32(), kStartTimeMs) << "Parcel has invalid timings";
148 
149     key = parcel.readInt32();
150     ASSERT_EQ(key, KEY_STRUCT_TEXT) << "Parcel has invalid struct text key";
151     ASSERT_EQ(parcel.readInt32(), mFileSize) << "Parcel has invalid text data";
152     int32_t fileSize = parcel.readInt32();
153     ASSERT_EQ(fileSize, mFileSize) << "Parcel has invalid file size value";
154     uint8_t tmpData[fileSize];
155     status = parcel.read((void *)tmpData, fileSize);
156     ASSERT_EQ(status, 0) << "Failed to read the data from parcel";
157     // To make sure end of parcel is reached
158     ASSERT_EQ(parcel.dataAvail(), 0) << "Parcel has some data left to read";
159 }
160 
161 // This test uses the properties of tx3g box mentioned in 3GPP Timed Text Format
162 // Specification#: 26.245 / Section: 5.16(Sample Description Format)
163 // https://www.3gpp.org/ftp/Specs/archive/26_series/26.245/
164 
TEST_P(Text3GPPDescriptionTest,Text3GPPGlobalDescriptionTest)165 TEST_P(Text3GPPDescriptionTest, Text3GPPGlobalDescriptionTest) {
166     char data[mFileSize];
167     mEleStream.read(data, sizeof(data));
168     ASSERT_EQ(mEleStream.gcount(), mFileSize);
169 
170     const uint8_t *tmpData = (const uint8_t *)data;
171     int32_t remaining = mFileSize;
172     FontInfo fontInfo;
173     vector<FontRecord> fontRecordEntries;
174 
175     // Skipping the bytes containing information about the type of subbox(tx3g)
176     tmpData += 16;
177     remaining -= 16;
178 
179     fontInfo.displayFlag = U32_AT(tmpData);
180     ALOGI("Display flag: %d", fontInfo.displayFlag);
181     fontInfo.horizontalJustification = tmpData[4];
182     ALOGI("Horizontal Justification: %d", fontInfo.horizontalJustification);
183     fontInfo.verticalJustification = tmpData[5];
184     ALOGI("Vertical Justification: %d", fontInfo.verticalJustification);
185     fontInfo.rgbaBackground =
186             *(tmpData + 6) << 24 | *(tmpData + 7) << 16 | *(tmpData + 8) << 8 | *(tmpData + 9);
187     ALOGI("rgba value of background: %d", fontInfo.rgbaBackground);
188 
189     tmpData += 10;
190     remaining -= 10;
191 
192     if (remaining >= 8) {
193         fontInfo.leftPos = U16_AT(tmpData);
194         ALOGI("Left: %d", fontInfo.leftPos);
195         fontInfo.topPos = U16_AT(tmpData + 2);
196         ALOGI("Top: %d", fontInfo.topPos);
197         fontInfo.bottomPos = U16_AT(tmpData + 4);
198         ALOGI("Bottom: %d", fontInfo.bottomPos);
199         fontInfo.rightPos = U16_AT(tmpData + 6);
200         ALOGI("Right: %d", fontInfo.rightPos);
201 
202         tmpData += 8;
203         remaining -= 8;
204 
205         if (remaining >= 12) {
206             fontInfo.startchar = U16_AT(tmpData);
207             ALOGI("Start character: %d", fontInfo.startchar);
208             fontInfo.endChar = U16_AT(tmpData + 2);
209             ALOGI("End character: %d", fontInfo.endChar);
210             fontInfo.fontId = U16_AT(tmpData + 4);
211             ALOGI("Value of font Identifier: %d", fontInfo.fontId);
212             fontInfo.faceStyle = *(tmpData + 6);
213             ALOGI("Face style flag : %d", fontInfo.faceStyle);
214             fontInfo.fontSize = *(tmpData + 7);
215             ALOGI("Size of the font: %d", fontInfo.fontSize);
216             fontInfo.rgbaText = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 | *(tmpData + 10) << 8 |
217                                 *(tmpData + 11);
218             ALOGI("rgba value of the text: %d", fontInfo.rgbaText);
219 
220             tmpData += 12;
221             remaining -= 12;
222 
223             if (remaining >= 10) {
224                 // Skipping the bytes containing information about the type of subbox(ftab)
225                 fontInfo.entryCount = U16_AT(tmpData + 8);
226                 ALOGI("Value of entry count: %d", fontInfo.entryCount);
227 
228                 tmpData += 10;
229                 remaining -= 10;
230 
231                 for (int32_t i = 0; i < fontInfo.entryCount; i++) {
232                     if (remaining < 3) break;
233                     int32_t tempFontID = U16_AT(tmpData);
234                     ALOGI("Font Id: %d", tempFontID);
235                     int32_t tempFontNameLength = *(tmpData + 2);
236                     ALOGI("Length of font name: %d", tempFontNameLength);
237 
238                     tmpData += 3;
239                     remaining -= 3;
240 
241                     if (remaining < tempFontNameLength) break;
242                     const uint8_t *tmpFont = tmpData;
243                     char *tmpFontName = strndup((const char *)tmpFont, tempFontNameLength);
244                     ASSERT_NE(tmpFontName, nullptr) << "Font Name is null";
245                     ALOGI("FontName = %s", tmpFontName);
246                     free(tmpFontName);
247                     tmpData += tempFontNameLength;
248                     remaining -= tempFontNameLength;
249                     fontRecordEntries.push_back({tempFontID, tempFontNameLength, tmpFont});
250                 }
251             }
252         }
253     }
254 
255     Parcel parcel;
256     int32_t flag = TextDescriptions::IN_BAND_TEXT_3GPP | TextDescriptions::GLOBAL_DESCRIPTIONS;
257     status_t status = TextDescriptions::getParcelOfDescriptions((const uint8_t *)data, mFileSize,
258                                                                 flag, kStartTimeMs, &parcel);
259     ASSERT_EQ(status, 0) << "getParcelOfDescriptions returned error";
260     ALOGI("Size of the Parcel: %zu", parcel.dataSize());
261     ASSERT_GT(parcel.dataSize(), 0) << "Parcel is empty";
262 
263     parcel.setDataPosition(0);
264     int32_t key = parcel.readInt32();
265     ASSERT_EQ(key, KEY_GLOBAL_SETTING) << "Parcel has invalid key";
266 
267     key = parcel.readInt32();
268     ASSERT_EQ(key, KEY_DISPLAY_FLAGS) << "Parcel has invalid DISPLAY FLAGS Key";
269     ASSERT_EQ(parcel.readInt32(), fontInfo.displayFlag)
270             << "Parcel has invalid value of display flag";
271 
272     key = parcel.readInt32();
273     ASSERT_EQ(key, KEY_STRUCT_JUSTIFICATION) << "Parcel has invalid STRUCT JUSTIFICATION key";
274     ASSERT_EQ(parcel.readInt32(), fontInfo.horizontalJustification)
275             << "Parcel has invalid value of Horizontal justification";
276     ASSERT_EQ(parcel.readInt32(), fontInfo.verticalJustification)
277             << "Parcel has invalid value of Vertical justification";
278 
279     key = parcel.readInt32();
280     ASSERT_EQ(key, KEY_BACKGROUND_COLOR_RGBA) << "Parcel has invalid BACKGROUND COLOR key";
281     ASSERT_EQ(parcel.readInt32(), fontInfo.rgbaBackground)
282             << "Parcel has invalid rgba background color value";
283 
284     if (parcel.dataAvail() == 0) {
285         ALOGV("Completed reading the parcel");
286         return;
287     }
288 
289     key = parcel.readInt32();
290     ASSERT_EQ(key, KEY_STRUCT_TEXT_POS) << "Parcel has invalid STRUCT TEXT POSITION key";
291     ASSERT_EQ(parcel.readInt32(), fontInfo.leftPos)
292             << "Parcel has invalid rgba background color value";
293     ASSERT_EQ(parcel.readInt32(), fontInfo.topPos)
294             << "Parcel has invalid rgba background color value";
295     ASSERT_EQ(parcel.readInt32(), fontInfo.bottomPos)
296             << "Parcel has invalid rgba background color value";
297     ASSERT_EQ(parcel.readInt32(), fontInfo.rightPos)
298             << "Parcel has invalid rgba background color value";
299 
300     if (parcel.dataAvail() == 0) {
301         ALOGV("Completed reading the parcel");
302         return;
303     }
304 
305     key = parcel.readInt32();
306     ASSERT_EQ(key, KEY_STRUCT_STYLE_LIST) << "Parcel has invalid STRUCT STYLE LIST key";
307 
308     key = parcel.readInt32();
309     ASSERT_EQ(key, KEY_START_CHAR) << "Parcel has invalid START CHAR key";
310     ASSERT_EQ(parcel.readInt32(), fontInfo.startchar)
311             << "Parcel has invalid value of start character";
312 
313     key = parcel.readInt32();
314     ASSERT_EQ(key, KEY_END_CHAR) << "Parcel has invalid END CHAR key";
315     ASSERT_EQ(parcel.readInt32(), fontInfo.endChar) << "Parcel has invalid value of end character";
316 
317     key = parcel.readInt32();
318     ASSERT_EQ(key, KEY_FONT_ID) << "Parcel has invalid FONT ID key";
319     ASSERT_EQ(parcel.readInt32(), fontInfo.fontId) << "Parcel has invalid value of font Id";
320 
321     key = parcel.readInt32();
322     ASSERT_EQ(key, KEY_STYLE_FLAGS) << "Parcel has invalid STYLE FLAGS key";
323     ASSERT_EQ(parcel.readInt32(), fontInfo.faceStyle) << "Parcel has invalid value of style flags";
324 
325     key = parcel.readInt32();
326     ASSERT_EQ(key, KEY_FONT_SIZE) << "Parcel has invalid FONT SIZE key";
327     ASSERT_EQ(parcel.readInt32(), fontInfo.fontSize) << "Parcel has invalid value of font size";
328 
329     key = parcel.readInt32();
330     ASSERT_EQ(key, KEY_TEXT_COLOR_RGBA) << "Parcel has invalid TEXT COLOR RGBA key";
331     ASSERT_EQ(parcel.readInt32(), fontInfo.rgbaText) << "Parcel has invalid rgba text color value";
332 
333     if (parcel.dataAvail() == 0) {
334         ALOGV("Completed reading the parcel");
335         return;
336     }
337 
338     key = parcel.readInt32();
339     ASSERT_EQ(key, KEY_STRUCT_FONT_LIST) << "Parcel has invalid STRUCT FONT LIST key";
340     ASSERT_EQ(parcel.readInt32(), fontInfo.entryCount) << "Parcel has invalid value of entry count";
341     ASSERT_EQ(fontInfo.entryCount, fontRecordEntries.size())
342             << "Array size does not match expected number of entries";
343     for (int32_t i = 0; i < fontInfo.entryCount; i++) {
344         ASSERT_EQ(parcel.readInt32(), fontRecordEntries[i].fontID)
345                 << "Parcel has invalid value of font Id";
346         ASSERT_EQ(parcel.readInt32(), fontRecordEntries[i].fontNameLength)
347                 << "Parcel has invalid value of font name length";
348         uint8_t fontName[fontRecordEntries[i].fontNameLength];
349         // written with writeByteArray() writes count, then the actual data
350         ASSERT_EQ(parcel.readInt32(), fontRecordEntries[i].fontNameLength);
351         status = parcel.read((void *)fontName, fontRecordEntries[i].fontNameLength);
352         ASSERT_EQ(status, 0) << "Failed to read the font name from parcel";
353         ASSERT_EQ(memcmp(fontName, fontRecordEntries[i].font, fontRecordEntries[i].fontNameLength),
354                   0)
355                 << "Parcel has invalid font";
356     }
357     // To make sure end of parcel is reached
358     ASSERT_EQ(parcel.dataAvail(), 0) << "Parcel has some data left to read";
359 }
360 
361 INSTANTIATE_TEST_SUITE_P(TimedTextUnitTestAll, SRTDescriptionTest,
362                          ::testing::Values(("sampleTest1.srt"),
363                                            ("sampleTest2.srt")));
364 
365 INSTANTIATE_TEST_SUITE_P(TimedTextUnitTestAll, Text3GPPDescriptionTest,
366                          ::testing::Values(("tx3gBox1"),
367                                            ("tx3gBox2")));
368 
main(int argc,char ** argv)369 int main(int argc, char **argv) {
370     gEnv = new TimedTextTestEnvironment();
371     ::testing::AddGlobalTestEnvironment(gEnv);
372     ::testing::InitGoogleTest(&argc, argv);
373     int status = gEnv->initFromOptions(argc, argv);
374     if (status == 0) {
375         status = RUN_ALL_TESTS();
376         ALOGV("Test result = %d\n", status);
377     }
378     return status;
379 }
380