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