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