Merge "ExtractorUnitTest: Add ConfigParamValidation test"
diff --git a/media/libstagefright/foundation/tests/Android.bp b/media/libstagefright/foundation/tests/Android.bp
index f2157c9..45e81e8 100644
--- a/media/libstagefright/foundation/tests/Android.bp
+++ b/media/libstagefright/foundation/tests/Android.bp
@@ -25,3 +25,31 @@
"Utils_test.cpp",
],
}
+
+cc_test {
+ name: "MetaDataBaseUnitTest",
+ gtest: true,
+
+ srcs: [
+ "MetaDataBaseUnitTest.cpp",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "liblog",
+ ],
+
+ static_libs: [
+ "libstagefright",
+ "libstagefright_foundation",
+ ],
+
+ header_libs: [
+ "libmedia_headers",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/media/libstagefright/foundation/tests/MetaDataBaseUnitTest.cpp b/media/libstagefright/foundation/tests/MetaDataBaseUnitTest.cpp
new file mode 100644
index 0000000..0aed4d2
--- /dev/null
+++ b/media/libstagefright/foundation/tests/MetaDataBaseUnitTest.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <gtest/gtest.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fstream>
+
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaDataBase.h>
+
+constexpr int32_t kWidth1 = 1920;
+constexpr int32_t kHeight1 = 1080;
+constexpr int32_t kWidth2 = 1280;
+constexpr int32_t kHeight2 = 920;
+constexpr int32_t kWidth3 = 720;
+constexpr int32_t kHeight3 = 480;
+constexpr int32_t kProfile = 1;
+constexpr int32_t kLevel = 1;
+constexpr int32_t kPlatformValue = 1;
+
+// Rectangle margins
+constexpr int32_t kLeft = 100;
+constexpr int32_t kTop = 100;
+constexpr int32_t kRight = 100;
+constexpr int32_t kBottom = 100;
+
+constexpr int64_t kDurationUs = 60000000;
+
+constexpr float kCaptureRate = 30.0;
+
+namespace android {
+
+class MetaDataBaseUnitTest : public ::testing::Test {};
+
+TEST_F(MetaDataBaseUnitTest, CreateMetaDataBaseTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ // Testing copy constructor
+ MetaDataBase *metaDataCopy = metaData;
+ ASSERT_NE(metaDataCopy, nullptr) << "Failed to create meta data copy";
+
+ delete metaData;
+}
+
+TEST_F(MetaDataBaseUnitTest, SetAndFindDataTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ // Setting the different key-value pair type for first time, overwrite
+ // expected to be false
+ bool status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ ASSERT_FALSE(status) << "Initializing kKeyMIMEType, overwrite is expected to be false";
+
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Initializing kKeyWidth, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyHeight, kHeight1);
+ ASSERT_FALSE(status) << "Initializing kKeyHeight, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyVideoProfile, kProfile);
+ ASSERT_FALSE(status) << "Initializing kKeyVideoProfile, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyVideoLevel, kLevel);
+ ASSERT_FALSE(status) << "Initializing kKeyVideoLevel, overwrite is expected to be false";
+
+ status = metaData->setInt64(kKeyDuration, kDurationUs);
+ ASSERT_FALSE(status) << "Initializing kKeyDuration, overwrite is expected to be false";
+
+ status = metaData->setFloat(kKeyCaptureFramerate, kCaptureRate);
+ ASSERT_FALSE(status) << "Initializing kKeyCaptureFramerate, overwrite is expected to be false";
+
+ const int32_t *platform = &kPlatformValue;
+ status = metaData->setPointer(kKeyPlatformPrivate, (void *)platform);
+ ASSERT_FALSE(status) << "Initializing kKeyPlatformPrivate, overwrite is expected to be false";
+
+ status = metaData->setRect(kKeyCropRect, kLeft, kTop, kRight, kBottom);
+ ASSERT_FALSE(status) << "Initializing kKeyCropRect, overwrite is expected to be false";
+
+ // Dump to log for reference
+ metaData->dumpToLog();
+
+ // Find the data which was set
+ const char *mime;
+ status = metaData->findCString(kKeyMIMEType, &mime);
+ ASSERT_TRUE(status) << "kKeyMIMEType key does not exists in metadata";
+ ASSERT_STREQ(mime, MEDIA_MIMETYPE_VIDEO_AVC) << "Incorrect mime type returned";
+
+ int32_t width, height, profile, level;
+ status = metaData->findInt32(kKeyWidth, &width);
+ ASSERT_TRUE(status) << "kKeyWidth key does not exists in metadata";
+ ASSERT_EQ(width, kWidth1) << "Incorrect value of width returned";
+
+ status = metaData->findInt32(kKeyHeight, &height);
+ ASSERT_TRUE(status) << "kKeyHeight key does not exists in metadata";
+ ASSERT_EQ(height, kHeight1) << "Incorrect value of height returned";
+
+ status = metaData->findInt32(kKeyVideoProfile, &profile);
+ ASSERT_TRUE(status) << "kKeyVideoProfile key does not exists in metadata";
+ ASSERT_EQ(profile, kProfile) << "Incorrect value of profile returned";
+
+ status = metaData->findInt32(kKeyVideoLevel, &level);
+ ASSERT_TRUE(status) << "kKeyVideoLevel key does not exists in metadata";
+ ASSERT_EQ(level, kLevel) << "Incorrect value of level returned";
+
+ int64_t duration;
+ status = metaData->findInt64(kKeyDuration, &duration);
+ ASSERT_TRUE(status) << "kKeyDuration key does not exists in metadata";
+ ASSERT_EQ(duration, kDurationUs) << "Incorrect value of duration returned";
+
+ float frameRate;
+ status = metaData->findFloat(kKeyCaptureFramerate, &frameRate);
+ ASSERT_TRUE(status) << "kKeyCaptureFramerate key does not exists in metadata";
+ ASSERT_EQ(frameRate, kCaptureRate) << "Incorrect value of captureFrameRate returned";
+
+ int32_t top, bottom, left, right;
+ status = metaData->findRect(kKeyCropRect, &left, &top, &right, &bottom);
+ ASSERT_TRUE(status) << "kKeyCropRect key does not exists in metadata";
+ ASSERT_EQ(left, kLeft) << "Incorrect value of left margin returned";
+ ASSERT_EQ(top, kTop) << "Incorrect value of top margin returned";
+ ASSERT_EQ(right, kRight) << "Incorrect value of right margin returned";
+ ASSERT_EQ(bottom, kBottom) << "Incorrect value of bottom margin returned";
+
+ void *platformValue;
+ status = metaData->findPointer(kKeyPlatformPrivate, &platformValue);
+ ASSERT_TRUE(status) << "kKeyPlatformPrivate key does not exists in metadata";
+ ASSERT_EQ(platformValue, &kPlatformValue) << "Incorrect value of pointer returned";
+
+ // Check for the key which is not added to metadata
+ int32_t angle;
+ status = metaData->findInt32(kKeyRotation, &angle);
+ ASSERT_FALSE(status) << "Value for an invalid key is returned when the key is not set";
+
+ delete (metaData);
+}
+
+TEST_F(MetaDataBaseUnitTest, OverWriteFunctionalityTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ // set/set/read to check first overwrite operation
+ bool status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ ASSERT_FALSE(status) << "Initializing kKeyMIMEType, overwrite is expected to be false";
+ // Overwrite the value
+ status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+ ASSERT_TRUE(status) << "Setting kKeyMIMEType again, overwrite is expected to be true";
+ // Check the value
+ const char *mime;
+ status = metaData->findCString(kKeyMIMEType, &mime);
+ ASSERT_TRUE(status) << "kKeyMIMEType key does not exists in metadata";
+ ASSERT_STREQ(mime, MEDIA_MIMETYPE_VIDEO_HEVC) << "Mime value is not overwritten";
+
+ // set/set/set/read to check second overwrite operation
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Initializing kKeyWidth, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyHeight, kHeight1);
+ ASSERT_FALSE(status) << "Initializing kKeyHeight, overwrite is expected to be false";
+ // Overwrite the value
+ status = metaData->setInt32(kKeyWidth, kWidth2);
+ ASSERT_TRUE(status) << "Setting kKeyWidth again, overwrite is expected to be true";
+ status = metaData->setInt32(kKeyHeight, kHeight2);
+ ASSERT_TRUE(status) << "Setting kKeyHeight again, overwrite is expected to be true";
+ // Overwrite the value again
+ status = metaData->setInt32(kKeyWidth, kWidth3);
+ ASSERT_TRUE(status) << "Setting kKeyWidth again, overwrite is expected to be true";
+ status = metaData->setInt32(kKeyHeight, kHeight3);
+ ASSERT_TRUE(status) << "Setting kKeyHeight again, overwrite is expected to be true";
+ // Check the value
+ int32_t width, height;
+ status = metaData->findInt32(kKeyWidth, &width);
+ ASSERT_TRUE(status) << "kKeyWidth key does not exists in metadata";
+ ASSERT_EQ(width, kWidth3) << "Value of width is not overwritten";
+
+ status = metaData->findInt32(kKeyHeight, &height);
+ ASSERT_TRUE(status) << "kKeyHeight key does not exists in metadata";
+ ASSERT_EQ(height, kHeight3) << "Value of height is not overwritten";
+
+ delete (metaData);
+}
+
+TEST_F(MetaDataBaseUnitTest, RemoveKeyTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ bool status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ ASSERT_FALSE(status) << "Initializing kKeyMIMEType, overwrite is expected to be false";
+ // Query the key
+ status = metaData->hasData(kKeyMIMEType);
+ ASSERT_TRUE(status) << "MetaData does not have the mime key";
+
+ status = metaData->remove(kKeyMIMEType);
+ ASSERT_TRUE(status) << "Failed to remove the kKeyMIMEType key";
+
+ // Query the key
+ status = metaData->hasData(kKeyMIMEType);
+ ASSERT_FALSE(status) << "MetaData has mime key after removing it, expected to be false";
+
+ // Remove the non existing key
+ status = metaData->remove(kKeyMIMEType);
+ ASSERT_FALSE(status) << "Removed the non existing key";
+
+ // Check overwriting the removed key
+ metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+ ASSERT_FALSE(status) << "Overwrite should be false since the key was removed";
+
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Initializing kKeyWidth, overwrite is expected to be false";
+
+ // Clear whole metadata
+ metaData->clear();
+
+ // Check finding key after clearing the metadata
+ int32_t width;
+ status = metaData->findInt32(kKeyWidth, &width);
+ ASSERT_FALSE(status) << "MetaData found kKeyWidth key after clearing all the items in it, "
+ "expected to be false";
+
+ // Query the key
+ status = metaData->hasData(kKeyWidth);
+ ASSERT_FALSE(status)
+ << "MetaData has width key after clearing all the items in it, expected to be false";
+
+ status = metaData->hasData(kKeyMIMEType);
+ ASSERT_FALSE(status)
+ << "MetaData has mime key after clearing all the items in it, expected to be false";
+
+ // Check removing key after clearing the metadata
+ status = metaData->remove(kKeyMIMEType);
+ ASSERT_FALSE(status) << "Removed the key, after clearing the metadata";
+
+ // Checking set after clearing the metadata
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Overwrite should be false since the metadata was cleared";
+
+ metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+ ASSERT_FALSE(status) << "Overwrite should be false since the metadata was cleared";
+
+ delete (metaData);
+}
+
+TEST_F(MetaDataBaseUnitTest, ConvertToStringTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ String8 info = metaData->toString();
+ ASSERT_EQ(info.length(), 0) << "Empty MetaData length is non-zero: " << info.length();
+
+ bool status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ ASSERT_FALSE(status) << "Initializing kKeyMIMEType, overwrite is expected to be false";
+
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Initializing kKeyWidth, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyHeight, kHeight1);
+ ASSERT_FALSE(status) << "Initializing kKeyHeight, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyVideoProfile, kProfile);
+ ASSERT_FALSE(status) << "Initializing kKeyVideoProfile, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyVideoLevel, kLevel);
+ ASSERT_FALSE(status) << "Initializing kKeyVideoLevel, overwrite is expected to be false";
+
+ info = metaData->toString();
+ ASSERT_GT(info.length(), 0) << "MetaData contains no information";
+
+ // Dump to log for reference
+ metaData->dumpToLog();
+
+ // Clear whole metadata
+ metaData->clear();
+
+ info = metaData->toString();
+ ASSERT_EQ(info.length(), 0) << "MetaData length is non-zero after clearing it: "
+ << info.length();
+
+ delete (metaData);
+}
+
+} // namespace android
diff --git a/media/libstagefright/timedtext/TextDescriptions.cpp b/media/libstagefright/timedtext/TextDescriptions.cpp
index 0dc7722..6c94754 100644
--- a/media/libstagefright/timedtext/TextDescriptions.cpp
+++ b/media/libstagefright/timedtext/TextDescriptions.cpp
@@ -445,51 +445,75 @@
| *(tmpData + 10) << 8 | *(tmpData + 11);
parcel->writeInt32(rgba);
+ // tx3g box contains class FontTableBox() which extends ftab box
+ // This information is part of the 3gpp Timed Text Format
+ // Specification#: 26.245 / Section: 5.16(Sample Description Format)
+ // https://www.3gpp.org/ftp/Specs/archive/26_series/26.245/
+
tmpData += 12;
remaining -= 12;
- if (remaining < 2) {
+ if (remaining < 8) {
return OK;
}
- size_t dataPos = parcel->dataPosition();
-
- parcel->writeInt32(KEY_STRUCT_FONT_LIST);
- uint16_t count = U16_AT(tmpData);
- parcel->writeInt32(count);
-
- tmpData += 2;
- remaining -= 2;
-
- for (int i = 0; i < count; i++) {
- if (remaining < 3) {
- // roll back
- parcel->setDataPosition(dataPos);
- return OK;
- }
- // font ID
- parcel->writeInt32(U16_AT(tmpData));
-
- // font name length
- parcel->writeInt32(*(tmpData + 2));
-
- size_t len = *(tmpData + 2);
-
- tmpData += 3;
- remaining -= 3;
-
- if (remaining < len) {
- // roll back
- parcel->setDataPosition(dataPos);
- return OK;
- }
-
- parcel->write(tmpData, len);
- tmpData += len;
- remaining -= len;
+ size_t subChunkSize = U32_AT(tmpData);
+ if(remaining < subChunkSize) {
+ return OK;
}
- // there is a "DisparityBox" after this according to the spec, but we ignore it
+ uint32_t subChunkType = U32_AT(tmpData + 4);
+
+ if (subChunkType == FOURCC('f', 't', 'a', 'b'))
+ {
+ tmpData += 8;
+ size_t subChunkRemaining = subChunkSize - 8;
+
+ if(subChunkRemaining < 2) {
+ return OK;
+ }
+ size_t dataPos = parcel->dataPosition();
+
+ parcel->writeInt32(KEY_STRUCT_FONT_LIST);
+ uint16_t count = U16_AT(tmpData);
+ parcel->writeInt32(count);
+
+ tmpData += 2;
+ subChunkRemaining -= 2;
+
+ for (int i = 0; i < count; i++) {
+ if (subChunkRemaining < 3) {
+ // roll back
+ parcel->setDataPosition(dataPos);
+ return OK;
+ }
+ // font ID
+ parcel->writeInt32(U16_AT(tmpData));
+
+ // font name length
+ size_t len = *(tmpData + 2);
+
+ parcel->writeInt32(len);
+
+ tmpData += 3;
+ subChunkRemaining -=3;
+
+ if (subChunkRemaining < len) {
+ // roll back
+ parcel->setDataPosition(dataPos);
+ return OK;
+ }
+
+ parcel->write(tmpData, len);
+ tmpData += len;
+ subChunkRemaining -= len;
+ }
+ tmpData += subChunkRemaining;
+ remaining -= subChunkSize;
+ } else {
+ tmpData += subChunkSize;
+ remaining -= subChunkSize;
+ }
break;
}
default:
diff --git a/media/libstagefright/timedtext/test/Android.bp b/media/libstagefright/timedtext/test/Android.bp
new file mode 100644
index 0000000..36f8891
--- /dev/null
+++ b/media/libstagefright/timedtext/test/Android.bp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_test {
+ name: "TimedTextUnitTest",
+ gtest: true,
+
+ srcs: [
+ "TimedTextUnitTest.cpp",
+ ],
+
+ static_libs: [
+ "libstagefright_timedtext",
+ "libstagefright_foundation",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/libstagefright",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libmedia",
+ "libbinder",
+ ],
+
+ cflags: [
+ "-Wno-multichar",
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/timedtext/test/AndroidTest.xml b/media/libstagefright/timedtext/test/AndroidTest.xml
new file mode 100644
index 0000000..8925790
--- /dev/null
+++ b/media/libstagefright/timedtext/test/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Test module config for TimedText unit test">
+ <option name="test-suite-tag" value="TimedTextUnitTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="TimedTextUnitTest->/data/local/tmp/TimedTextUnitTest" />
+ <option name="push-file"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/timedtext/test/TimedTextUnitTest.zip?unzip=true"
+ value="/data/local/tmp/TimedTextUnitTestRes/" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="TimedTextUnitTestt" />
+ <option name="native-test-flag" value="-P /data/local/tmp/TimedTextUnitTestRes/" />
+ </test>
+</configuration>
diff --git a/media/libstagefright/timedtext/test/README.md b/media/libstagefright/timedtext/test/README.md
new file mode 100644
index 0000000..3a774bd
--- /dev/null
+++ b/media/libstagefright/timedtext/test/README.md
@@ -0,0 +1,40 @@
+## Media Testing ##
+---
+#### TimedText Unit Test :
+The TimedText Unit Test Suite validates the TextDescription class available in libstagefright.
+
+Run the following steps to build the test suite:
+```
+m TimedTextUnitTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/TimedTextUnitTest/TimedTextUnitTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/TimedTextUnitTest/TimedTextUnitTest /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/timedtext/test/TimedTextUnitTest.zip).
+Download, unzip and push these files into device for testing.
+
+```
+adb push TimedTextUnitTestRes/. /data/local/tmp/
+```
+
+usage: TimedTextUnitTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/TimedTextUnitTest -P /data/local/tmp/TimedTextUnitTestRes/
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest TimedTextUnitTest -- --enable-module-dynamic-download=true
+```
diff --git a/media/libstagefright/timedtext/test/TimedTextTestEnvironment.h b/media/libstagefright/timedtext/test/TimedTextTestEnvironment.h
new file mode 100644
index 0000000..52280c1
--- /dev/null
+++ b/media/libstagefright/timedtext/test/TimedTextTestEnvironment.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __TIMEDTEXT_TEST_ENVIRONMENT_H__
+#define __TIMEDTEXT_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class TimedTextTestEnvironment : public ::testing::Environment {
+ public:
+ TimedTextTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int TimedTextTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"res", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P':
+ setRes(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __TIMEDTEXT_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/timedtext/test/TimedTextUnitTest.cpp b/media/libstagefright/timedtext/test/TimedTextUnitTest.cpp
new file mode 100644
index 0000000..64379d6
--- /dev/null
+++ b/media/libstagefright/timedtext/test/TimedTextUnitTest.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "TimedTextUnitTest"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fstream>
+
+#include <binder/Parcel.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/ByteUtils.h>
+
+#include "timedtext/TextDescriptions.h"
+
+#include "TimedTextTestEnvironment.h"
+
+constexpr int32_t kStartTimeMs = 10000;
+
+enum {
+ // These keys must be in sync with the keys in
+ // frameworks/av/media/libstagefright/timedtext/TextDescriptions.h
+ KEY_DISPLAY_FLAGS = 1,
+ KEY_STYLE_FLAGS = 2,
+ KEY_BACKGROUND_COLOR_RGBA = 3,
+ KEY_HIGHLIGHT_COLOR_RGBA = 4,
+ KEY_SCROLL_DELAY = 5,
+ KEY_WRAP_TEXT = 6,
+ KEY_START_TIME = 7,
+ KEY_STRUCT_BLINKING_TEXT_LIST = 8,
+ KEY_STRUCT_FONT_LIST = 9,
+ KEY_STRUCT_HIGHLIGHT_LIST = 10,
+ KEY_STRUCT_HYPER_TEXT_LIST = 11,
+ KEY_STRUCT_KARAOKE_LIST = 12,
+ KEY_STRUCT_STYLE_LIST = 13,
+ KEY_STRUCT_TEXT_POS = 14,
+ KEY_STRUCT_JUSTIFICATION = 15,
+ KEY_STRUCT_TEXT = 16,
+
+ KEY_GLOBAL_SETTING = 101,
+ KEY_LOCAL_SETTING = 102,
+ KEY_START_CHAR = 103,
+ KEY_END_CHAR = 104,
+ KEY_FONT_ID = 105,
+ KEY_FONT_SIZE = 106,
+ KEY_TEXT_COLOR_RGBA = 107,
+};
+
+struct FontInfo {
+ int32_t displayFlag = -1;
+ int32_t horizontalJustification = -1;
+ int32_t verticalJustification = -1;
+ int32_t rgbaBackground = -1;
+ int32_t leftPos = -1;
+ int32_t topPos = -1;
+ int32_t bottomPos = -1;
+ int32_t rightPos = -1;
+ int32_t startchar = -1;
+ int32_t endChar = -1;
+ int32_t fontId = -1;
+ int32_t faceStyle = -1;
+ int32_t fontSize = -1;
+ int32_t rgbaText = -1;
+ int32_t entryCount = -1;
+};
+
+struct FontRecord {
+ int32_t fontID = -1;
+ int32_t fontNameLength = -1;
+ const uint8_t *font = nullptr;
+};
+
+using namespace android;
+
+static TimedTextTestEnvironment *gEnv = nullptr;
+
+class TimedTextUnitTest : public ::testing::TestWithParam</*filename*/ string> {
+ public:
+ TimedTextUnitTest(){};
+
+ ~TimedTextUnitTest() {
+ if (mEleStream) mEleStream.close();
+ }
+
+ virtual void SetUp() override {
+ mInputFileName = gEnv->getRes() + GetParam();
+ mEleStream.open(mInputFileName, ifstream::binary);
+ ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open " << GetParam();
+
+ struct stat buf;
+ status_t status = stat(mInputFileName.c_str(), &buf);
+ ASSERT_EQ(status, 0) << "Failed to get properties of input file: " << GetParam();
+ mFileSize = buf.st_size;
+ ALOGI("Size of the input file %s = %zu", GetParam().c_str(), mFileSize);
+ }
+
+ string mInputFileName;
+ size_t mFileSize;
+ ifstream mEleStream;
+};
+
+class SRTDescriptionTest : public TimedTextUnitTest {
+ public:
+ virtual void SetUp() override { TimedTextUnitTest::SetUp(); }
+};
+
+class Text3GPPDescriptionTest : public TimedTextUnitTest {
+ public:
+ virtual void SetUp() override { TimedTextUnitTest::SetUp(); }
+};
+
+TEST_P(SRTDescriptionTest, extractSRTDescriptionTest) {
+ char data[mFileSize];
+ mEleStream.read(data, sizeof(data));
+ ASSERT_EQ(mEleStream.gcount(), mFileSize);
+
+ Parcel parcel;
+ int32_t flag = TextDescriptions::OUT_OF_BAND_TEXT_SRT | TextDescriptions::LOCAL_DESCRIPTIONS;
+ status_t status = TextDescriptions::getParcelOfDescriptions((const uint8_t *)data, mFileSize,
+ flag, kStartTimeMs, &parcel);
+ ASSERT_EQ(status, 0) << "getParcelOfDescriptions returned error";
+ ALOGI("Size of the Parcel: %zu", parcel.dataSize());
+ ASSERT_GT(parcel.dataSize(), 0) << "Parcel is empty";
+
+ parcel.setDataPosition(0);
+ int32_t key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_LOCAL_SETTING) << "Parcel has invalid key";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_START_TIME) << "Parcel has invalid start time key";
+ ASSERT_EQ(parcel.readInt32(), kStartTimeMs) << "Parcel has invalid timings";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_TEXT) << "Parcel has invalid struct text key";
+ ASSERT_EQ(parcel.readInt32(), mFileSize) << "Parcel has invalid text data";
+ int32_t fileSize = parcel.readInt32();
+ ASSERT_EQ(fileSize, mFileSize) << "Parcel has invalid file size value";
+ uint8_t tmpData[fileSize];
+ status = parcel.read((void *)tmpData, fileSize);
+ ASSERT_EQ(status, 0) << "Failed to read the data from parcel";
+ // To make sure end of parcel is reached
+ ASSERT_EQ(parcel.dataAvail(), 0) << "Parcel has some data left to read";
+}
+
+// This test uses the properties of tx3g box mentioned in 3GPP Timed Text Format
+// Specification#: 26.245 / Section: 5.16(Sample Description Format)
+// https://www.3gpp.org/ftp/Specs/archive/26_series/26.245/
+
+TEST_P(Text3GPPDescriptionTest, Text3GPPGlobalDescriptionTest) {
+ char data[mFileSize];
+ mEleStream.read(data, sizeof(data));
+ ASSERT_EQ(mEleStream.gcount(), mFileSize);
+
+ const uint8_t *tmpData = (const uint8_t *)data;
+ int32_t remaining = mFileSize;
+ FontInfo fontInfo;
+ vector<FontRecord> fontRecordEntries;
+
+ // Skipping the bytes containing information about the type of subbox(tx3g)
+ tmpData += 16;
+ remaining -= 16;
+
+ fontInfo.displayFlag = U32_AT(tmpData);
+ ALOGI("Display flag: %d", fontInfo.displayFlag);
+ fontInfo.horizontalJustification = tmpData[4];
+ ALOGI("Horizontal Justification: %d", fontInfo.horizontalJustification);
+ fontInfo.verticalJustification = tmpData[5];
+ ALOGI("Vertical Justification: %d", fontInfo.verticalJustification);
+ fontInfo.rgbaBackground =
+ *(tmpData + 6) << 24 | *(tmpData + 7) << 16 | *(tmpData + 8) << 8 | *(tmpData + 9);
+ ALOGI("rgba value of background: %d", fontInfo.rgbaBackground);
+
+ tmpData += 10;
+ remaining -= 10;
+
+ if (remaining >= 8) {
+ fontInfo.leftPos = U16_AT(tmpData);
+ ALOGI("Left: %d", fontInfo.leftPos);
+ fontInfo.topPos = U16_AT(tmpData + 2);
+ ALOGI("Top: %d", fontInfo.topPos);
+ fontInfo.bottomPos = U16_AT(tmpData + 4);
+ ALOGI("Bottom: %d", fontInfo.bottomPos);
+ fontInfo.rightPos = U16_AT(tmpData + 6);
+ ALOGI("Right: %d", fontInfo.rightPos);
+
+ tmpData += 8;
+ remaining -= 8;
+
+ if (remaining >= 12) {
+ fontInfo.startchar = U16_AT(tmpData);
+ ALOGI("Start character: %d", fontInfo.startchar);
+ fontInfo.endChar = U16_AT(tmpData + 2);
+ ALOGI("End character: %d", fontInfo.endChar);
+ fontInfo.fontId = U16_AT(tmpData + 4);
+ ALOGI("Value of font Identifier: %d", fontInfo.fontId);
+ fontInfo.faceStyle = *(tmpData + 6);
+ ALOGI("Face style flag : %d", fontInfo.faceStyle);
+ fontInfo.fontSize = *(tmpData + 7);
+ ALOGI("Size of the font: %d", fontInfo.fontSize);
+ fontInfo.rgbaText = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 | *(tmpData + 10) << 8 |
+ *(tmpData + 11);
+ ALOGI("rgba value of the text: %d", fontInfo.rgbaText);
+
+ tmpData += 12;
+ remaining -= 12;
+
+ if (remaining >= 10) {
+ // Skipping the bytes containing information about the type of subbox(ftab)
+ fontInfo.entryCount = U16_AT(tmpData + 8);
+ ALOGI("Value of entry count: %d", fontInfo.entryCount);
+
+ tmpData += 10;
+ remaining -= 10;
+
+ for (int32_t i = 0; i < fontInfo.entryCount; i++) {
+ if (remaining < 3) break;
+ int32_t tempFontID = U16_AT(tmpData);
+ ALOGI("Font Id: %d", tempFontID);
+ int32_t tempFontNameLength = *(tmpData + 2);
+ ALOGI("Length of font name: %d", tempFontNameLength);
+
+ tmpData += 3;
+ remaining -= 3;
+
+ if (remaining < tempFontNameLength) break;
+ const uint8_t *tmpFont = tmpData;
+ char *tmpFontName = strndup((const char *)tmpFont, tempFontNameLength);
+ ASSERT_NE(tmpFontName, nullptr) << "Font Name is null";
+ ALOGI("FontName = %s", tmpFontName);
+ free(tmpFontName);
+ tmpData += tempFontNameLength;
+ remaining -= tempFontNameLength;
+ fontRecordEntries.push_back({tempFontID, tempFontNameLength, tmpFont});
+ }
+ }
+ }
+ }
+
+ Parcel parcel;
+ int32_t flag = TextDescriptions::IN_BAND_TEXT_3GPP | TextDescriptions::GLOBAL_DESCRIPTIONS;
+ status_t status = TextDescriptions::getParcelOfDescriptions((const uint8_t *)data, mFileSize,
+ flag, kStartTimeMs, &parcel);
+ ASSERT_EQ(status, 0) << "getParcelOfDescriptions returned error";
+ ALOGI("Size of the Parcel: %zu", parcel.dataSize());
+ ASSERT_GT(parcel.dataSize(), 0) << "Parcel is empty";
+
+ parcel.setDataPosition(0);
+ int32_t key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_GLOBAL_SETTING) << "Parcel has invalid key";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_DISPLAY_FLAGS) << "Parcel has invalid DISPLAY FLAGS Key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.displayFlag)
+ << "Parcel has invalid value of display flag";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_JUSTIFICATION) << "Parcel has invalid STRUCT JUSTIFICATION key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.horizontalJustification)
+ << "Parcel has invalid value of Horizontal justification";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.verticalJustification)
+ << "Parcel has invalid value of Vertical justification";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_BACKGROUND_COLOR_RGBA) << "Parcel has invalid BACKGROUND COLOR key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.rgbaBackground)
+ << "Parcel has invalid rgba background color value";
+
+ if (parcel.dataAvail() == 0) {
+ ALOGV("Completed reading the parcel");
+ return;
+ }
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_TEXT_POS) << "Parcel has invalid STRUCT TEXT POSITION key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.leftPos)
+ << "Parcel has invalid rgba background color value";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.topPos)
+ << "Parcel has invalid rgba background color value";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.bottomPos)
+ << "Parcel has invalid rgba background color value";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.rightPos)
+ << "Parcel has invalid rgba background color value";
+
+ if (parcel.dataAvail() == 0) {
+ ALOGV("Completed reading the parcel");
+ return;
+ }
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_STYLE_LIST) << "Parcel has invalid STRUCT STYLE LIST key";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_START_CHAR) << "Parcel has invalid START CHAR key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.startchar)
+ << "Parcel has invalid value of start character";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_END_CHAR) << "Parcel has invalid END CHAR key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.endChar) << "Parcel has invalid value of end character";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_FONT_ID) << "Parcel has invalid FONT ID key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.fontId) << "Parcel has invalid value of font Id";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STYLE_FLAGS) << "Parcel has invalid STYLE FLAGS key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.faceStyle) << "Parcel has invalid value of style flags";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_FONT_SIZE) << "Parcel has invalid FONT SIZE key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.fontSize) << "Parcel has invalid value of font size";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_TEXT_COLOR_RGBA) << "Parcel has invalid TEXT COLOR RGBA key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.rgbaText) << "Parcel has invalid rgba text color value";
+
+ if (parcel.dataAvail() == 0) {
+ ALOGV("Completed reading the parcel");
+ return;
+ }
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_FONT_LIST) << "Parcel has invalid STRUCT FONT LIST key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.entryCount) << "Parcel has invalid value of entry count";
+ ASSERT_EQ(fontInfo.entryCount, fontRecordEntries.size())
+ << "Array size does not match expected number of entries";
+ for (int32_t i = 0; i < fontInfo.entryCount; i++) {
+ ASSERT_EQ(parcel.readInt32(), fontRecordEntries[i].fontID)
+ << "Parcel has invalid value of font Id";
+ ASSERT_EQ(parcel.readInt32(), fontRecordEntries[i].fontNameLength)
+ << "Parcel has invalid value of font name length";
+ uint8_t fontName[fontRecordEntries[i].fontNameLength];
+ status = parcel.read((void *)fontName, fontRecordEntries[i].fontNameLength);
+ ASSERT_EQ(status, 0) << "Failed to read the font name from parcel";
+ ASSERT_EQ(memcmp(fontName, fontRecordEntries[i].font, fontRecordEntries[i].fontNameLength),
+ 0)
+ << "Parcel has invalid font";
+ }
+ // To make sure end of parcel is reached
+ ASSERT_EQ(parcel.dataAvail(), 0) << "Parcel has some data left to read";
+}
+
+INSTANTIATE_TEST_SUITE_P(TimedTextUnitTestAll, SRTDescriptionTest,
+ ::testing::Values(("sampleTest1.srt"),
+ ("sampleTest2.srt")));
+
+INSTANTIATE_TEST_SUITE_P(TimedTextUnitTestAll, Text3GPPDescriptionTest,
+ ::testing::Values(("tx3gBox1"),
+ ("tx3gBox2")));
+
+int main(int argc, char **argv) {
+ gEnv = new TimedTextTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/utils/TimeCheck.cpp b/media/utils/TimeCheck.cpp
index 4a3e470..59d74de 100644
--- a/media/utils/TimeCheck.cpp
+++ b/media/utils/TimeCheck.cpp
@@ -100,7 +100,6 @@
bool TimeCheck::TimeCheckThread::threadLoop()
{
status_t status = TIMED_OUT;
- const char *tag;
{
AutoMutex _l(mMutex);
@@ -109,6 +108,7 @@
}
nsecs_t endTimeNs = INT64_MAX;
+ const char *tag = "<unspecified>";
// KeyedVector mMonitorRequests is ordered so take first entry as next timeout
if (mMonitorRequests.size() != 0) {
endTimeNs = mMonitorRequests.keyAt(0);