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