aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PREUPLOAD.cfg3
-rw-r--r--tests/Android.mk117
-rw-r--r--tests/c2_comp_intf/Android.mk116
-rw-r--r--tests/c2_comp_intf/C2CompIntfTest.cpp (renamed from tests/C2CompIntfTest.cpp)0
-rw-r--r--tests/c2_comp_intf/C2CompIntfTest.h (renamed from tests/C2CompIntfTest.h)0
-rw-r--r--tests/c2_comp_intf/C2VDACompIntf_test.cpp (renamed from tests/C2VDACompIntf_test.cpp)0
-rw-r--r--tests/c2_comp_intf/C2VDAComponent_test.cpp (renamed from tests/C2VDAComponent_test.cpp)0
-rw-r--r--tests/c2_comp_intf/C2VEACompIntf_test.cpp (renamed from tests/C2VEACompIntf_test.cpp)0
-rw-r--r--tests/c2_comp_intf/data/bear-vp8.webm (renamed from tests/data/bear-vp8.webm)bin145975 -> 145975 bytes
-rw-r--r--tests/c2_comp_intf/data/bear-vp8.webm.md5 (renamed from tests/data/bear-vp8.webm.md5)0
-rw-r--r--tests/c2_comp_intf/data/bear-vp9.webm (renamed from tests/data/bear-vp9.webm)bin67504 -> 67504 bytes
-rw-r--r--tests/c2_comp_intf/data/bear-vp9.webm.md5 (renamed from tests/data/bear-vp9.webm.md5)0
-rw-r--r--tests/c2_comp_intf/data/bear.mp4 (renamed from tests/data/bear.mp4)bin273127 -> 273127 bytes
-rw-r--r--tests/c2_comp_intf/data/bear.mp4.md5 (renamed from tests/data/bear.mp4.md5)0
-rw-r--r--tests/c2_e2e_test/Android.mk26
-rw-r--r--tests/c2_e2e_test/AndroidManifest.xml29
-rw-r--r--tests/c2_e2e_test/README.md49
-rw-r--r--tests/c2_e2e_test/jni/Android.mk42
-rw-r--r--tests/c2_e2e_test/jni/common.cpp192
-rw-r--r--tests/c2_e2e_test/jni/common.h137
-rw-r--r--tests/c2_e2e_test/jni/e2e_test_jni.cpp68
-rw-r--r--tests/c2_e2e_test/jni/e2e_test_jni.h10
-rw-r--r--tests/c2_e2e_test/jni/encoded_data_helper.cpp203
-rw-r--r--tests/c2_e2e_test/jni/encoded_data_helper.h66
-rw-r--r--tests/c2_e2e_test/jni/md5.cpp296
-rw-r--r--tests/c2_e2e_test/jni/md5.h78
-rw-r--r--tests/c2_e2e_test/jni/mediacodec_decoder.cpp385
-rw-r--r--tests/c2_e2e_test/jni/mediacodec_decoder.h124
-rw-r--r--tests/c2_e2e_test/jni/video_decoder_e2e_test.cpp341
-rw-r--r--tests/c2_e2e_test/jni/video_frame.cpp225
-rw-r--r--tests/c2_e2e_test/jni/video_frame.h89
-rw-r--r--tests/c2_e2e_test/res/layout/main_activity.xml14
-rw-r--r--tests/c2_e2e_test/res/values/strings.xml10
-rw-r--r--tests/c2_e2e_test/src/org/chromium/c2/test/E2eTestActivity.java62
34 files changed, 2565 insertions, 117 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index cbdf0f1..3da7fa1 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,6 @@
[Builtin Hooks]
clang_format = true
+google_java_format = true
[Builtin Hooks Options]
-clang_format = --commit ${PREUPLOAD_COMMIT} -- style file --extensions h,cpp
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions h,cpp
diff --git a/tests/Android.mk b/tests/Android.mk
index b54ff56..5053e7d 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -1,116 +1 @@
-# Build the unit tests.
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_MODULE := C2VDACompIntf_test
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
- C2CompIntfTest.cpp \
- C2VDACompIntf_test.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- libchrome \
- libcutils \
- liblog \
- libstagefright_codec2 \
- libstagefright_codec2_vndk \
- libutils \
- libv4l2_codec2 \
- libv4l2_codec2_accel \
-
-LOCAL_C_INCLUDES += \
- $(TOP)/external/v4l2_codec2/accel \
- $(TOP)/external/v4l2_codec2/include \
- $(TOP)/hardware/google/av/codec2/include \
- $(TOP)/hardware/google/av/codec2/vndk/include \
- $(TOP)/hardware/google/av/media/codecs/base/include \
- $(TOP)/vendor/google_arc/libs/codec2/vdastore/include \
-
-LOCAL_CFLAGS += -Werror -Wall -std=c++14
-LOCAL_CLANG := true
-
-LOCAL_LDFLAGS := -Wl,-Bsymbolic
-
-include $(BUILD_NATIVE_TEST)
-
-
-include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_MODULE := C2VEACompIntf_test
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
- C2CompIntfTest.cpp \
- C2VEACompIntf_test.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- libchrome \
- libcutils \
- liblog \
- libstagefright_codec2 \
- libstagefright_codec2_vndk \
- libutils \
- libv4l2_codec2 \
- libv4l2_codec2_accel \
-
-LOCAL_C_INCLUDES += \
- $(TOP)/external/v4l2_codec2/accel \
- $(TOP)/external/v4l2_codec2/include \
- $(TOP)/hardware/google/av/codec2/include \
- $(TOP)/hardware/google/av/codec2/vndk/include \
- $(TOP)/hardware/google/av/media/codecs/base/include \
-
-LOCAL_CFLAGS += -Werror -Wall -std=c++14
-LOCAL_CLANG := true
-
-LOCAL_LDFLAGS := -Wl,-Bsymbolic
-
-include $(BUILD_NATIVE_TEST)
-
-
-include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_MODULE := C2VDAComponent_test
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
- C2VDAComponent_test.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- libchrome \
- libcutils \
- liblog \
- libmedia \
- libstagefright \
- libstagefright_codec2 \
- libstagefright_codec2_vndk \
- libstagefright_foundation \
- libutils \
- libv4l2_codec2 \
- libv4l2_codec2_accel \
- android.hardware.media.bufferpool@1.0 \
-
-LOCAL_C_INCLUDES += \
- $(TOP)/external/libchrome \
- $(TOP)/external/v4l2_codec2/accel \
- $(TOP)/external/v4l2_codec2/include \
- $(TOP)/frameworks/av/media/libstagefright/include \
- $(TOP)/hardware/google/av/codec2/include \
- $(TOP)/hardware/google/av/codec2/vndk/include \
- $(TOP)/hardware/google/av/media/codecs/base/include \
- $(TOP)/vendor/google_arc/libs/codec2/vdastore/include \
-
-# -Wno-unused-parameter is needed for libchrome/base codes
-LOCAL_CFLAGS += -Werror -Wall -Wno-unused-parameter -std=c++14
-LOCAL_CLANG := true
-
-LOCAL_LDFLAGS := -Wl,-Bsymbolic
-
-include $(BUILD_NATIVE_TEST)
+include $(call all-subdir-makefiles)
diff --git a/tests/c2_comp_intf/Android.mk b/tests/c2_comp_intf/Android.mk
new file mode 100644
index 0000000..b54ff56
--- /dev/null
+++ b/tests/c2_comp_intf/Android.mk
@@ -0,0 +1,116 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_MODULE := C2VDACompIntf_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ C2CompIntfTest.cpp \
+ C2VDACompIntf_test.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libchrome \
+ libcutils \
+ liblog \
+ libstagefright_codec2 \
+ libstagefright_codec2_vndk \
+ libutils \
+ libv4l2_codec2 \
+ libv4l2_codec2_accel \
+
+LOCAL_C_INCLUDES += \
+ $(TOP)/external/v4l2_codec2/accel \
+ $(TOP)/external/v4l2_codec2/include \
+ $(TOP)/hardware/google/av/codec2/include \
+ $(TOP)/hardware/google/av/codec2/vndk/include \
+ $(TOP)/hardware/google/av/media/codecs/base/include \
+ $(TOP)/vendor/google_arc/libs/codec2/vdastore/include \
+
+LOCAL_CFLAGS += -Werror -Wall -std=c++14
+LOCAL_CLANG := true
+
+LOCAL_LDFLAGS := -Wl,-Bsymbolic
+
+include $(BUILD_NATIVE_TEST)
+
+
+include $(CLEAR_VARS)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_MODULE := C2VEACompIntf_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ C2CompIntfTest.cpp \
+ C2VEACompIntf_test.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libchrome \
+ libcutils \
+ liblog \
+ libstagefright_codec2 \
+ libstagefright_codec2_vndk \
+ libutils \
+ libv4l2_codec2 \
+ libv4l2_codec2_accel \
+
+LOCAL_C_INCLUDES += \
+ $(TOP)/external/v4l2_codec2/accel \
+ $(TOP)/external/v4l2_codec2/include \
+ $(TOP)/hardware/google/av/codec2/include \
+ $(TOP)/hardware/google/av/codec2/vndk/include \
+ $(TOP)/hardware/google/av/media/codecs/base/include \
+
+LOCAL_CFLAGS += -Werror -Wall -std=c++14
+LOCAL_CLANG := true
+
+LOCAL_LDFLAGS := -Wl,-Bsymbolic
+
+include $(BUILD_NATIVE_TEST)
+
+
+include $(CLEAR_VARS)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_MODULE := C2VDAComponent_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ C2VDAComponent_test.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libchrome \
+ libcutils \
+ liblog \
+ libmedia \
+ libstagefright \
+ libstagefright_codec2 \
+ libstagefright_codec2_vndk \
+ libstagefright_foundation \
+ libutils \
+ libv4l2_codec2 \
+ libv4l2_codec2_accel \
+ android.hardware.media.bufferpool@1.0 \
+
+LOCAL_C_INCLUDES += \
+ $(TOP)/external/libchrome \
+ $(TOP)/external/v4l2_codec2/accel \
+ $(TOP)/external/v4l2_codec2/include \
+ $(TOP)/frameworks/av/media/libstagefright/include \
+ $(TOP)/hardware/google/av/codec2/include \
+ $(TOP)/hardware/google/av/codec2/vndk/include \
+ $(TOP)/hardware/google/av/media/codecs/base/include \
+ $(TOP)/vendor/google_arc/libs/codec2/vdastore/include \
+
+# -Wno-unused-parameter is needed for libchrome/base codes
+LOCAL_CFLAGS += -Werror -Wall -Wno-unused-parameter -std=c++14
+LOCAL_CLANG := true
+
+LOCAL_LDFLAGS := -Wl,-Bsymbolic
+
+include $(BUILD_NATIVE_TEST)
diff --git a/tests/C2CompIntfTest.cpp b/tests/c2_comp_intf/C2CompIntfTest.cpp
index 092abd6..092abd6 100644
--- a/tests/C2CompIntfTest.cpp
+++ b/tests/c2_comp_intf/C2CompIntfTest.cpp
diff --git a/tests/C2CompIntfTest.h b/tests/c2_comp_intf/C2CompIntfTest.h
index 61930d8..61930d8 100644
--- a/tests/C2CompIntfTest.h
+++ b/tests/c2_comp_intf/C2CompIntfTest.h
diff --git a/tests/C2VDACompIntf_test.cpp b/tests/c2_comp_intf/C2VDACompIntf_test.cpp
index 32d4e20..32d4e20 100644
--- a/tests/C2VDACompIntf_test.cpp
+++ b/tests/c2_comp_intf/C2VDACompIntf_test.cpp
diff --git a/tests/C2VDAComponent_test.cpp b/tests/c2_comp_intf/C2VDAComponent_test.cpp
index 4d387d4..4d387d4 100644
--- a/tests/C2VDAComponent_test.cpp
+++ b/tests/c2_comp_intf/C2VDAComponent_test.cpp
diff --git a/tests/C2VEACompIntf_test.cpp b/tests/c2_comp_intf/C2VEACompIntf_test.cpp
index 6b72c76..6b72c76 100644
--- a/tests/C2VEACompIntf_test.cpp
+++ b/tests/c2_comp_intf/C2VEACompIntf_test.cpp
diff --git a/tests/data/bear-vp8.webm b/tests/c2_comp_intf/data/bear-vp8.webm
index 02ae36c..02ae36c 100644
--- a/tests/data/bear-vp8.webm
+++ b/tests/c2_comp_intf/data/bear-vp8.webm
Binary files differ
diff --git a/tests/data/bear-vp8.webm.md5 b/tests/c2_comp_intf/data/bear-vp8.webm.md5
index ca3c179..ca3c179 100644
--- a/tests/data/bear-vp8.webm.md5
+++ b/tests/c2_comp_intf/data/bear-vp8.webm.md5
diff --git a/tests/data/bear-vp9.webm b/tests/c2_comp_intf/data/bear-vp9.webm
index 4f497ae..4f497ae 100644
--- a/tests/data/bear-vp9.webm
+++ b/tests/c2_comp_intf/data/bear-vp9.webm
Binary files differ
diff --git a/tests/data/bear-vp9.webm.md5 b/tests/c2_comp_intf/data/bear-vp9.webm.md5
index cf5ff81..cf5ff81 100644
--- a/tests/data/bear-vp9.webm.md5
+++ b/tests/c2_comp_intf/data/bear-vp9.webm.md5
diff --git a/tests/data/bear.mp4 b/tests/c2_comp_intf/data/bear.mp4
index f1d30fb..f1d30fb 100644
--- a/tests/data/bear.mp4
+++ b/tests/c2_comp_intf/data/bear.mp4
Binary files differ
diff --git a/tests/data/bear.mp4.md5 b/tests/c2_comp_intf/data/bear.mp4.md5
index d8f8c2d..d8f8c2d 100644
--- a/tests/data/bear.mp4.md5
+++ b/tests/c2_comp_intf/data/bear.mp4.md5
diff --git a/tests/c2_e2e_test/Android.mk b/tests/c2_e2e_test/Android.mk
new file mode 100644
index 0000000..c0f43c6
--- /dev/null
+++ b/tests/c2_e2e_test/Android.mk
@@ -0,0 +1,26 @@
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/res \
+
+LOCAL_MULTILIB := both
+LOCAL_PACKAGE_NAME := C2E2ETest
+LOCAL_JNI_SHARED_LIBRARIES := libcodectest
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CXX_STL := libc++_static
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/c2_e2e_test/AndroidManifest.xml b/tests/c2_e2e_test/AndroidManifest.xml
new file mode 100644
index 0000000..3898348
--- /dev/null
+++ b/tests/c2_e2e_test/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Chromium OS Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.chromium.c2.test">
+
+ <uses-sdk
+ android:minSdkVersion="21"
+ android:targetSdkVersion="26" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application
+ android:allowBackup="false"
+ android:label="@string/app_name">
+ <activity android:name=".E2eTestActivity"
+ android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/c2_e2e_test/README.md b/tests/c2_e2e_test/README.md
new file mode 100644
index 0000000..7a585d5
--- /dev/null
+++ b/tests/c2_e2e_test/README.md
@@ -0,0 +1,49 @@
+# Codec2.0 End-to-end Test
+
+## Manually run the test
+
+You need to be running Android with v4l2_codec2 (e.g. ARC++, ARCVM) to run the
+tests. Make sure the device under test (DUT) is connected with adb.
+
+1. Build the e2e test
+
+ ```
+ (From the android tree)
+ $ mmm external/v4l2_codec2/tests/c2_e2e_test
+ ```
+
+2. Install the test app and grant the necessary permissions
+
+ ```
+ (Outside CrOS chroot)
+ $ adb install \
+ out/target/product/bertha_x86_64/testcases/C2E2ETest/x86_64/C2E2ETest.apk
+ $ adb shell pm grant \
+ org.chromium.c2.test android.permission.WRITE_EXTERNAL_STORAGE
+ $ adb shell pm grant \
+ org.chromium.c2.test android.permission.READ_EXTERNAL_STORAGE
+ ```
+
+3. Run E2E tests
+
+ (1) Push the test stream and frame-wise MD5 file to DUT
+
+ ```
+ $ adb push test-25fps.h264 /sdcard/Download
+ $ adb push test-25fps.h264.frames.md5 /sdcard/Download
+ ```
+
+ (2) Run the test binary
+
+ ```
+ $ adb shell am start -W -n \
+ org.chromium.c2.test/.E2eTestActivity \
+ --esa test-args "--test_video_data=/sdcard/Download/test-25fps.h264:320:240:250:258:::1" \
+ --es log-file "/sdcard/Download/gtest_logs.txt"'
+ ```
+
+ (3) View the test output
+
+ ```
+ $ adb shell cat /sdcard/Download/gtest_logs.txt
+ ```
diff --git a/tests/c2_e2e_test/jni/Android.mk b/tests/c2_e2e_test/jni/Android.mk
new file mode 100644
index 0000000..f25fd3f
--- /dev/null
+++ b/tests/c2_e2e_test/jni/Android.mk
@@ -0,0 +1,42 @@
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := \
+ system/core/include \
+
+LOCAL_MULTILIB := both
+
+LOCAL_SRC_FILES := \
+ video_decoder_e2e_test.cpp \
+ e2e_test_jni.cpp \
+ common.cpp \
+ encoded_data_helper.cpp \
+ video_frame.cpp \
+ md5.cpp \
+ mediacodec_decoder.cpp \
+
+# TODO(stevensd): Port encoder tests
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libmediandk \
+ libandroid \
+
+LOCAL_SDK_VERSION := 21
+LOCAL_NDK_STL_VARIANT := c++_static
+
+LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgtest_main_ndk_c++
+
+LOCAL_MODULE := libcodectest
+
+# TODO(stevensd): Fix and reenable warnings
+LOCAL_CFLAGS += -Wno-everything
+
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/c2_e2e_test/jni/common.cpp b/tests/c2_e2e_test/jni/common.cpp
new file mode 100644
index 0000000..5372f24
--- /dev/null
+++ b/tests/c2_e2e_test/jni/common.cpp
@@ -0,0 +1,192 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "Common"
+
+#include "common.h"
+
+#include <strings.h>
+#include <time.h>
+
+#include <algorithm>
+#include <cmath>
+#include <numeric>
+#include <sstream>
+
+#include <utils/Log.h>
+
+namespace android {
+
+InputFile::InputFile(std::string file_path) {
+ file_ = std::ifstream(file_path);
+}
+
+InputFile::InputFile(std::string file_path, std::ios_base::openmode openmode) {
+ file_ = std::ifstream(file_path, openmode);
+}
+
+bool InputFile::IsValid() const {
+ return file_.is_open();
+}
+
+size_t InputFile::GetLength() {
+ int current_pos = file_.tellg();
+
+ file_.seekg(0, file_.end);
+ size_t ret = file_.tellg();
+
+ file_.seekg(current_pos, file_.beg);
+ return ret;
+}
+
+void InputFile::Rewind() {
+ file_.clear();
+ file_.seekg(0);
+}
+
+InputFileStream::InputFileStream(std::string file_path)
+ : InputFile(file_path, std::ifstream::binary) {}
+
+size_t InputFileStream::Read(char* buffer, size_t size) {
+ file_.read(buffer, size);
+ if (file_.fail()) return -1;
+
+ return file_.gcount();
+}
+
+InputFileASCII::InputFileASCII(std::string file_path) : InputFile(file_path) {}
+
+bool InputFileASCII::ReadLine(std::string* line) {
+ std::string read_line;
+ while (std::getline(file_, read_line)) {
+ if (read_line.empty()) // be careful: an empty line might be read
+ continue; // even if none exist.
+ *line = read_line;
+ return true;
+ }
+ return false; // no more lines
+}
+
+bool FPSCalculator::RecordFrameTimeDiff() {
+ int64_t now_us = GetNowUs();
+ if (last_frame_time_us_ != 0) {
+ int64_t frame_diff_us = now_us - last_frame_time_us_;
+ if (frame_diff_us <= 0) return false;
+ frame_time_diffs_us_.push_back(static_cast<double>(frame_diff_us));
+ }
+ last_frame_time_us_ = now_us;
+ return true;
+}
+
+// Reference: (https://cs.corp.google.com/android/cts/common/device-side/util/
+// src/com/android/compatibility/common/util/MediaPerfUtils.java)
+// addPerformanceStatsToLog
+double FPSCalculator::CalculateFPS() const {
+ std::vector<double> moving_avgs = MovingAvgOverSum();
+ std::sort(moving_avgs.begin(), moving_avgs.end());
+
+ int index = static_cast<int>(std::round(kRegardedPercentile * (moving_avgs.size() - 1) / 100));
+ ALOGV("Frame decode time stats (us): { min=%.4f, regarded=%.4f, "
+ "max=%.4f}, window=%.0f",
+ moving_avgs[0], moving_avgs[index], moving_avgs[moving_avgs.size() - 1],
+ kMovingAvgWindowUs);
+
+ return 1E6 / moving_avgs[index];
+}
+
+// Reference: (https://cs.corp.google.com/android/cts/common/device-side/util/
+// src/com/android/compatibility/common/util/MediaUtils.java)
+// movingAverageOverSum
+std::vector<double> FPSCalculator::MovingAvgOverSum() const {
+ std::vector<double> moving_avgs;
+
+ double sum = std::accumulate(frame_time_diffs_us_.begin(), frame_time_diffs_us_.end(), 0.0);
+ int data_size = static_cast<int>(frame_time_diffs_us_.size());
+ double avg = sum / data_size;
+ if (kMovingAvgWindowUs >= sum) {
+ moving_avgs.push_back(avg);
+ return moving_avgs;
+ }
+
+ int samples = static_cast<int>(std::ceil((sum - kMovingAvgWindowUs) / avg));
+ double cumulative_sum = 0;
+ int num = 0;
+ int bi = 0;
+ int ei = 0;
+ double space = kMovingAvgWindowUs;
+ double foot = 0;
+
+ int ix = 0;
+ while (ix < samples) {
+ while (ei < data_size && frame_time_diffs_us_[ei] <= space) {
+ space -= frame_time_diffs_us_[ei];
+ cumulative_sum += frame_time_diffs_us_[ei];
+ num++;
+ ei++;
+ }
+
+ if (num > 0) {
+ moving_avgs.push_back(cumulative_sum / num);
+ } else if (bi > 0 && foot > space) {
+ moving_avgs.push_back(frame_time_diffs_us_[bi - 1]);
+ } else if (ei == data_size) {
+ break;
+ } else {
+ moving_avgs.push_back(frame_time_diffs_us_[ei]);
+ }
+
+ ix++;
+ foot -= avg;
+ space += avg;
+
+ while (bi < ei && foot < 0) {
+ foot += frame_time_diffs_us_[bi];
+ cumulative_sum -= frame_time_diffs_us_[bi];
+ num--;
+ bi++;
+ }
+ }
+ return moving_avgs;
+}
+
+VideoCodecType VideoCodecProfileToType(VideoCodecProfile profile) {
+ if (profile >= H264PROFILE_MIN && profile <= H264PROFILE_MAX) return VideoCodecType::H264;
+ if (profile >= VP8PROFILE_MIN && profile <= VP8PROFILE_MAX) return VideoCodecType::VP8;
+ if (profile >= VP9PROFILE_MIN && profile <= VP9PROFILE_MAX) return VideoCodecType::VP9;
+ return VideoCodecType::UNKNOWN;
+}
+
+std::vector<std::string> SplitString(const std::string& src, char delim) {
+ std::stringstream ss(src);
+ std::string item;
+ std::vector<std::string> ret;
+ while (std::getline(ss, item, delim)) {
+ ret.push_back(item);
+ }
+ return ret;
+}
+
+int64_t GetNowUs() {
+ struct timespec t;
+ t.tv_sec = t.tv_nsec = 0;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ int64_t nsecs = static_cast<int64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec;
+ return nsecs / 1000ll;
+}
+
+const char* GetMimeType(VideoCodecType type) {
+ switch (type) {
+ case VideoCodecType::H264:
+ return "video/avc";
+ case VideoCodecType::VP8:
+ return "video/x-vnd.on2.vp8";
+ case VideoCodecType::VP9:
+ return "video/x-vnd.on2.vp9";
+ default: // unknown type
+ return nullptr;
+ }
+}
+
+} // namespace android
diff --git a/tests/c2_e2e_test/jni/common.h b/tests/c2_e2e_test/jni/common.h
new file mode 100644
index 0000000..b402a8b
--- /dev/null
+++ b/tests/c2_e2e_test/jni/common.h
@@ -0,0 +1,137 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef C2_E2E_TEST_COMMON_H_
+#define C2_E2E_TEST_COMMON_H_
+
+#include <fstream>
+#include <ios>
+#include <string>
+#include <vector>
+
+namespace android {
+
+// The enumeration of video codec profile. This would be better to align with
+// VideoCodecProfile enum in Chromium so we could use the identical test stream
+// data arguments for both ARC end-to-end and Chromium tests.
+enum VideoCodecProfile {
+ VIDEO_CODEC_PROFILE_UNKNOWN = -1,
+ VIDEO_CODEC_PROFILE_MIN = VIDEO_CODEC_PROFILE_UNKNOWN,
+ H264PROFILE_MIN = 0,
+ H264PROFILE_BASELINE = H264PROFILE_MIN,
+ H264PROFILE_MAIN = 1,
+ H264PROFILE_EXTENDED = 2,
+ H264PROFILE_HIGH = 3,
+ H264PROFILE_HIGH10PROFILE = 4,
+ H264PROFILE_HIGH422PROFILE = 5,
+ H264PROFILE_HIGH444PREDICTIVEPROFILE = 6,
+ H264PROFILE_SCALABLEBASELINE = 7,
+ H264PROFILE_SCALABLEHIGH = 8,
+ H264PROFILE_STEREOHIGH = 9,
+ H264PROFILE_MULTIVIEWHIGH = 10,
+ H264PROFILE_MAX = H264PROFILE_MULTIVIEWHIGH,
+ VP8PROFILE_MIN = 11,
+ VP8PROFILE_ANY = VP8PROFILE_MIN,
+ VP8PROFILE_MAX = VP8PROFILE_ANY,
+ VP9PROFILE_MIN = 12,
+ VP9PROFILE_PROFILE0 = VP9PROFILE_MIN,
+ VP9PROFILE_PROFILE1 = 13,
+ VP9PROFILE_PROFILE2 = 14,
+ VP9PROFILE_PROFILE3 = 15,
+ VP9PROFILE_MAX = VP9PROFILE_PROFILE3,
+};
+
+// The enum class of video codec type.
+enum class VideoCodecType {
+ UNKNOWN,
+ H264,
+ VP8,
+ VP9,
+};
+
+// Structure to store resolution.
+struct Size {
+ Size() : width(0), height(0) {}
+ Size(int w, int h) : width(w), height(h) {}
+ bool IsEmpty() const { return width <= 0 || height <= 0; }
+
+ int width;
+ int height;
+};
+
+class InputFile {
+public:
+ explicit InputFile(std::string file_path);
+ InputFile(std::string file_path, std::ios_base::openmode openmode);
+
+ // Check if the file is valid.
+ bool IsValid() const;
+ // Get the size of the file.
+ size_t GetLength();
+ // Set position to the beginning of the file.
+ void Rewind();
+
+protected:
+ std::ifstream file_;
+};
+
+// Wrapper of std::ifstream for reading binary file.
+class InputFileStream : public InputFile {
+public:
+ explicit InputFileStream(std::string file_path);
+
+ // Read the given number of bytes to the buffer. Return the number of bytes
+ // read or -1 on error.
+ size_t Read(char* buffer, size_t size);
+};
+
+// Wrapper of std::ifstream for reading ASCII file.
+class InputFileASCII : public InputFile {
+public:
+ explicit InputFileASCII(std::string file_path);
+
+ // Read one line from the file. Return false if EOF.
+ bool ReadLine(std::string* line);
+};
+
+// The helper class to calculate FPS.
+class FPSCalculator {
+public:
+ // Record the time interval of output buffers. Return false if is invalid.
+ // This should be called per output buffer ready callback.
+ bool RecordFrameTimeDiff();
+
+ // Calucalate FPS value.
+ double CalculateFPS() const;
+
+private:
+ static constexpr double kMovingAvgWindowUs = 1000000;
+ static constexpr double kRegardedPercentile = 95;
+
+ // Return the statistics for the moving average over a window over the
+ // cumulative sum. Basically, moves a window from: [0, window] to
+ // [sum - window, sum] over the cumulative sum, over ((sum - window)/average)
+ // steps, and returns the average value over each window.
+ // This method is used to average time-diff data over a window of a constant
+ // time.
+ std::vector<double> MovingAvgOverSum() const;
+
+ std::vector<double> frame_time_diffs_us_;
+ int64_t last_frame_time_us_ = 0;
+};
+
+// Helper function to get VideoCodecType from |profile|.
+VideoCodecType VideoCodecProfileToType(VideoCodecProfile profile);
+
+// Split the string |src| by the delimiter |delim|.
+std::vector<std::string> SplitString(const std::string& src, char delim);
+
+// Get monotonic timestamp for now in microseconds.
+int64_t GetNowUs();
+
+// Get Mime type name from video codec type.
+const char* GetMimeType(VideoCodecType type);
+
+} // namespace android
+#endif // C2_E2E_TEST_COMMON_H_
diff --git a/tests/c2_e2e_test/jni/e2e_test_jni.cpp b/tests/c2_e2e_test/jni/e2e_test_jni.cpp
new file mode 100644
index 0000000..3f04e65
--- /dev/null
+++ b/tests/c2_e2e_test/jni/e2e_test_jni.cpp
@@ -0,0 +1,68 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "E2E_JNI"
+
+#include <jni.h>
+#include <pthread.h>
+#include <sstream>
+
+#include <android/native_window_jni.h>
+#include <utils/Log.h>
+
+#include "e2e_test_jni.h"
+#include "mediacodec_decoder.h"
+
+extern "C" {
+
+JNIEXPORT jint JNICALL Java_org_chromium_c2_test_E2eTestActivity_c2VideoTest(
+ JNIEnv* env, jobject thiz, jboolean encode, jobjectArray test_args, int test_args_count,
+ jstring tmp_file_path) {
+ const char* log_path = env->GetStringUTFChars(tmp_file_path, nullptr);
+ if (freopen(log_path, "a+", stdout) == NULL) {
+ env->ReleaseStringUTFChars(tmp_file_path, log_path);
+ ALOGE("Failed to redirect stream to file: %s: %s\n", log_path, strerror(errno));
+ return JNI_ERR;
+ }
+ ALOGI("Saving gtest output to %s\n", log_path);
+ env->ReleaseStringUTFChars(tmp_file_path, log_path);
+
+ char** args = new char*[test_args_count];
+ for (int i = 0; i < test_args_count; i++) {
+ jstring string = (jstring)env->GetObjectArrayElement(test_args, i);
+ const char* c_str = env->GetStringUTFChars(string, nullptr);
+ int len = env->GetStringUTFLength(string);
+
+ args[i] = new char[len + 1];
+ memcpy(args[i], c_str, len);
+ args[i][len] = '\0';
+
+ env->ReleaseStringUTFChars(string, c_str);
+ }
+
+ char** final_args = new char*[test_args_count + 1];
+ final_args[0] = "e2e_test_jni";
+ memcpy(final_args + 1, args, sizeof(args[0]) * test_args_count);
+
+ int res;
+ if (encode) {
+ ALOGE("Encoder e2e tests not yet supported");
+ res = JNI_ERR;
+ } else {
+ res = RunDecoderTests(final_args, test_args_count + 1);
+ }
+ delete[] final_args;
+
+ for (int i = 0; i < test_args_count; i++) {
+ delete[] args[i];
+ }
+ delete[] args;
+
+ fflush(stdout);
+ fclose(stdout);
+
+ return res;
+}
+}
diff --git a/tests/c2_e2e_test/jni/e2e_test_jni.h b/tests/c2_e2e_test/jni/e2e_test_jni.h
new file mode 100644
index 0000000..efadadf
--- /dev/null
+++ b/tests/c2_e2e_test/jni/e2e_test_jni.h
@@ -0,0 +1,10 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef C2_E2E_TEST_E2E_TEST_JNI_H_
+#define C2_E2E_TEST_E2E_TEST_JNI_H_
+
+int RunDecoderTests(char** test_args, int test_args_count);
+
+#endif // C2_E2E_TEST_E2E_TEST_JNI_H_
diff --git a/tests/c2_e2e_test/jni/encoded_data_helper.cpp b/tests/c2_e2e_test/jni/encoded_data_helper.cpp
new file mode 100644
index 0000000..d17e7cb
--- /dev/null
+++ b/tests/c2_e2e_test/jni/encoded_data_helper.cpp
@@ -0,0 +1,203 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "EncodedDataHelper"
+
+#include "encoded_data_helper.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <utility>
+
+#include <utils/Log.h>
+
+namespace android {
+
+namespace {
+
+bool IsAnnexb3ByteStartCode(const std::string& data, size_t pos) {
+ // The Annex-B 3-byte start code "\0\0\1" will be prefixed by NALUs per AU
+ // except for the first one.
+ return data[pos] == 0 && data[pos + 1] == 0 && data[pos + 2] == 1;
+}
+
+bool IsAnnexb4ByteStartCode(const std::string& data, size_t pos) {
+ // The Annex-B 4-byte start code "\0\0\0\1" will be prefixed by first NALU per
+ // AU.
+ return data[pos] == 0 && data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 1;
+}
+
+// Get the next position of NALU header byte in |data| from |next_header_pos|,
+// and update to |next_header_pos|. Return true if there is one; false
+// otherwise.
+// Note: this function should be used within an AU.
+bool GetPosForNextNALUHeader(const std::string& data, size_t* next_header_pos) {
+ size_t pos = *next_header_pos;
+
+ // Annex-B 4-byte could be also found by IsAnnexb3ByteStartCode().
+ while (pos + 3 <= data.size() && !IsAnnexb3ByteStartCode(data, pos)) {
+ ++pos;
+ }
+ if (pos + 3 >= data.size()) return false; // No more NALUs
+
+ // NALU header is the first byte after Annex-B start code.
+ *next_header_pos = pos + 3;
+ return true;
+}
+
+// For H264, return data bytes of next AU fragment in |data| from |next_pos|,
+// and update the position to |next_pos|.
+std::string GetBytesForNextAU(const std::string& data, size_t* next_pos) {
+ // Helpful description:
+ // https://en.wikipedia.org/wiki/Network_Abstraction_Layer
+ size_t start_pos = *next_pos;
+ size_t pos = start_pos;
+ if (pos + 4 > data.size()) {
+ ALOGE("Invalid AU: Start code is less than 4 bytes.\n");
+ *next_pos = data.size();
+ return std::string();
+ }
+
+ assert(IsAnnexb4ByteStartCode(data, pos));
+
+ pos += 4;
+ // The first 4 bytes must be Annex-B 4-byte start code for an AU.
+ while (pos + 4 <= data.size() && !IsAnnexb4ByteStartCode(data, pos)) {
+ ++pos;
+ }
+ if (pos + 3 >= data.size()) pos = data.size();
+
+ // Update next_pos.
+ *next_pos = pos;
+ return data.substr(start_pos, pos - start_pos);
+}
+
+// For VP8/9, return data bytes of next frame in |data| from |next_pos|, and
+// update the position to |next_pos|.
+std::string GetBytesForNextFrame(const std::string& data, size_t* next_pos) {
+ // Helpful description: http://wiki.multimedia.cx/index.php?title=IVF
+ size_t pos = *next_pos;
+ std::string bytes;
+ if (pos == 0) pos = 32; // Skip IVF header.
+
+ const uint32_t frame_size = *reinterpret_cast<const uint32_t*>(&data[pos]);
+ pos += 12; // Skip frame header.
+
+ // Update next_pos.
+ *next_pos = pos + frame_size;
+ return data.substr(pos, frame_size);
+}
+
+} // namespace
+
+EncodedDataHelper::EncodedDataHelper(const std::string& file_path, VideoCodecType type)
+ : type_(type) {
+ InputFileStream input(file_path);
+ if (!input.IsValid()) {
+ ALOGE("Failed to open file: %s", file_path.c_str());
+ return;
+ }
+
+ int file_size = input.GetLength();
+ if (file_size <= 0) {
+ ALOGE("Stream byte size (=%d) is invalid", file_size);
+ return;
+ }
+ input.Rewind();
+
+ char* read_bytes = new char[file_size];
+ if (input.Read(read_bytes, file_size) != file_size) {
+ ALOGE("Failed to read input stream from file to buffer.");
+ return;
+ }
+
+ // Note: must assign |file_size| here otherwise the constructor will terminate
+ // copting at the first '\0' in |read_bytes|.
+ std::string data(read_bytes, file_size);
+ delete[] read_bytes;
+
+ SliceToFragments(data);
+}
+
+EncodedDataHelper::~EncodedDataHelper() {}
+
+const EncodedDataHelper::Fragment* const EncodedDataHelper::GetNextFragment() {
+ if (ReachEndOfStream()) return nullptr;
+ return next_fragment_iter_++->get();
+}
+
+bool EncodedDataHelper::AtHeadOfStream() const {
+ return next_fragment_iter_ == fragments_.begin();
+}
+
+bool EncodedDataHelper::ReachEndOfStream() const {
+ return next_fragment_iter_ == fragments_.end();
+}
+
+void EncodedDataHelper::SliceToFragments(const std::string& data) {
+ size_t next_pos = 0;
+ bool seen_csd = false;
+ while (next_pos < data.size()) {
+ std::unique_ptr<Fragment> fragment(new Fragment());
+ switch (type_) {
+ case VideoCodecType::H264:
+ fragment->data = GetBytesForNextAU(data, &next_pos);
+ if (!ParseAUFragmentType(fragment.get())) continue;
+ if (!seen_csd && !fragment->csd_flag)
+ // Skip all AUs beforehand until we get SPS NALU.
+ continue;
+ seen_csd = true;
+ break;
+ case VideoCodecType::VP8:
+ case VideoCodecType::VP9:
+ fragment->data = GetBytesForNextFrame(data, &next_pos);
+ break;
+ default:
+ ALOGE("Unknown video codec type.");
+ return;
+ }
+ fragments_.push_back(std::move(fragment));
+ }
+
+ ALOGD("Total %zu fragments in interest from input stream.", NumValidFragments());
+ next_fragment_iter_ = fragments_.begin();
+}
+
+bool EncodedDataHelper::ParseAUFragmentType(Fragment* fragment) {
+ size_t next_header_pos = 0;
+ while (GetPosForNextNALUHeader(fragment->data, &next_header_pos)) {
+ // Read the NALU header (first byte) which contains unit type.
+ uint8_t nalu_header = static_cast<uint8_t>(fragment->data[next_header_pos]);
+
+ // Check forbidden_zero_bit (MSB of NALU header) is 0;
+ if (nalu_header & 0x80) {
+ ALOGE("NALU header forbidden_zero_bit is 1.");
+ return false;
+ }
+
+ // Check NALU type ([3:7], 5-bit).
+ uint8_t nalu_type = nalu_header & 0x1f;
+ switch (nalu_type) {
+ case NON_IDR_SLICE:
+ case IDR_SLICE:
+ // If AU contains both CSD and VCL NALUs (e.g. PPS + IDR_SLICE), don't
+ // raise csd_flag, treat this fragment as VCL one.
+ fragment->csd_flag = false;
+ return true; // fragment in interest as VCL.
+ case SPS:
+ case PPS:
+ fragment->csd_flag = true;
+ // Continue on finding the subsequent NALUs, it may have VCL data.
+ break;
+ default:
+ // Skip uninterested NALU type.
+ break;
+ }
+ }
+ return fragment->csd_flag; // fragment in interest as CSD.
+}
+
+} // namespace android
diff --git a/tests/c2_e2e_test/jni/encoded_data_helper.h b/tests/c2_e2e_test/jni/encoded_data_helper.h
new file mode 100644
index 0000000..cf107c2
--- /dev/null
+++ b/tests/c2_e2e_test/jni/encoded_data_helper.h
@@ -0,0 +1,66 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef C2_E2E_TEST_ENCODED_DATA_HELPER_H_
+#define C2_E2E_TEST_ENCODED_DATA_HELPER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "common.h"
+
+namespace android {
+
+// Helper class for MediaCodecDecoder to read encoded stream from input file,
+// and slice it into fragments. MediaCodecDecoder could call GetNextFragment()
+// to obtain fragment data sequentially.
+class EncodedDataHelper {
+public:
+ EncodedDataHelper(const std::string& file_path, VideoCodecType type);
+ ~EncodedDataHelper();
+
+ // A fragment will contain the bytes of one AU (H264) or frame (VP8/9) in
+ // |data|, and |csd_flag| indicator for input buffer flag CODEC_CONFIG.
+ struct Fragment {
+ std::string data;
+ bool csd_flag = false;
+ };
+
+ // Return the next fragment to be sent to the decoder, and advance the
+ // iterator to after the returned fragment.
+ const Fragment* const GetNextFragment();
+
+ void Rewind() { next_fragment_iter_ = fragments_.begin(); }
+ bool IsValid() const { return !fragments_.empty(); }
+ size_t NumValidFragments() const { return fragments_.size(); }
+ bool AtHeadOfStream() const;
+ bool ReachEndOfStream() const;
+
+private:
+ // NALU type enumeration as defined in H264 Annex-B. Only interested ones are
+ // listed here.
+ enum NALUType : uint8_t {
+ NON_IDR_SLICE = 0x1,
+ IDR_SLICE = 0x5,
+ SPS = 0x7,
+ PPS = 0x8,
+ };
+
+ // Slice input stream into fragments. This should be done in constructor.
+ void SliceToFragments(const std::string& data);
+
+ // For H264, parse csd_flag from |fragment| data and store inside. Return true
+ // if this fragment is in interest; false otherwise (fragment will be
+ // discarded.)
+ bool ParseAUFragmentType(Fragment* fragment);
+
+ VideoCodecType type_;
+ std::vector<std::unique_ptr<Fragment>> fragments_;
+ std::vector<std::unique_ptr<Fragment>>::iterator next_fragment_iter_;
+};
+
+} // namespace android
+
+#endif // C2_E2E_TEST_ENCODED_DATA_HELPER_H_
diff --git a/tests/c2_e2e_test/jni/md5.cpp b/tests/c2_e2e_test/jni/md5.cpp
new file mode 100644
index 0000000..0bc57c6
--- /dev/null
+++ b/tests/c2_e2e_test/jni/md5.cpp
@@ -0,0 +1,296 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is the leveraged design from Chromium library src/base/hash/md5.cc
+
+// The original file was copied from sqlite, and was in the public domain.
+
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#include "md5.h"
+
+#include <stddef.h>
+
+namespace {
+
+struct Context {
+ uint32_t buf[4];
+ uint32_t bits[2];
+ uint8_t in[64];
+};
+
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse(uint8_t* buf, unsigned longs) {
+ do {
+ uint32_t temp = static_cast<uint32_t>(static_cast<unsigned>(buf[3]) << 8 | buf[2]) << 16 |
+ (static_cast<unsigned>(buf[1]) << 8 | buf[0]);
+ *reinterpret_cast<uint32_t*>(buf) = temp;
+ buf += 4;
+ } while (--longs);
+}
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void MD5Transform(uint32_t buf[4], const uint32_t in[16]) {
+ uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+} // namespace
+
+namespace android {
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(MD5Context* context) {
+ struct Context* ctx = reinterpret_cast<struct Context*>(context);
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void MD5Update(MD5Context* context, const std::string& data) {
+ struct Context* ctx = reinterpret_cast<struct Context*>(context);
+ const uint8_t* buf = reinterpret_cast<const uint8_t*>(data.data());
+ size_t len = data.size();
+
+ /* Update bitcount */
+
+ uint32_t t = ctx->bits[0];
+ if ((ctx->bits[0] = t + (static_cast<uint32_t>(len) << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += static_cast<uint32_t>(len >> 29);
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if (t) {
+ uint8_t* p = static_cast<uint8_t*>(ctx->in + t);
+
+ t = 64 - t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, reinterpret_cast<uint32_t*>(ctx->in));
+ buf += t;
+ len -= t;
+ }
+
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, reinterpret_cast<uint32_t*>(ctx->in));
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void MD5Final(MD5Digest* digest, MD5Context* context) {
+ struct Context* ctx = reinterpret_cast<struct Context*>(context);
+ unsigned count;
+ uint8_t* p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, reinterpret_cast<uint32_t*>(ctx->in));
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ memcpy(&ctx->in[14 * sizeof(ctx->bits[0])], &ctx->bits[0], sizeof(ctx->bits[0]));
+ memcpy(&ctx->in[15 * sizeof(ctx->bits[1])], &ctx->bits[1], sizeof(ctx->bits[1]));
+
+ MD5Transform(ctx->buf, reinterpret_cast<uint32_t*>(ctx->in));
+ byteReverse(reinterpret_cast<uint8_t*>(ctx->buf), 4);
+ memcpy(digest->a, ctx->buf, 16);
+ memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
+}
+
+void MD5IntermediateFinal(MD5Digest* digest, const MD5Context* context) {
+ /* MD5Final mutates the MD5Context*. Make a copy for generating the
+ intermediate value. */
+ MD5Context context_copy;
+ memcpy(&context_copy, context, sizeof(context_copy));
+ MD5Final(digest, &context_copy);
+}
+
+std::string MD5DigestToBase16(const MD5Digest& digest) {
+ static char const zEncode[] = "0123456789abcdef";
+
+ std::string ret;
+ ret.resize(32);
+
+ for (int i = 0, j = 0; i < 16; i++, j += 2) {
+ uint8_t a = digest.a[i];
+ ret[j] = zEncode[(a >> 4) & 0xf];
+ ret[j + 1] = zEncode[a & 0xf];
+ }
+ return ret;
+}
+
+void MD5Sum(const void* data, size_t length, MD5Digest* digest) {
+ MD5Context ctx;
+ MD5Init(&ctx);
+ MD5Update(&ctx, std::string(reinterpret_cast<const char*>(data), length));
+ MD5Final(digest, &ctx);
+}
+
+std::string MD5String(const std::string& str) {
+ MD5Digest digest;
+ MD5Sum(str.data(), str.length(), &digest);
+ return MD5DigestToBase16(digest);
+}
+
+} // namespace android
diff --git a/tests/c2_e2e_test/jni/md5.h b/tests/c2_e2e_test/jni/md5.h
new file mode 100644
index 0000000..3a770b8
--- /dev/null
+++ b/tests/c2_e2e_test/jni/md5.h
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is the leveraged design from Chromium library src/base/hash/md5.h
+
+#ifndef C2_E2E_TEST_MD5_H_
+#define C2_E2E_TEST_MD5_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+namespace android {
+
+// MD5 stands for Message Digest algorithm 5.
+// MD5 is a robust hash function, designed for cyptography, but often used
+// for file checksums. The code is complex and slow, but has few
+// collisions.
+// See Also:
+// http://en.wikipedia.org/wiki/MD5
+
+// These functions perform MD5 operations. The simplest call is MD5Sum() to
+// generate the MD5 sum of the given data.
+//
+// You can also compute the MD5 sum of data incrementally by making multiple
+// calls to MD5Update():
+// MD5Context ctx; // intermediate MD5 data: do not use
+// MD5Init(&ctx);
+// MD5Update(&ctx, data1, length1);
+// MD5Update(&ctx, data2, length2);
+// ...
+//
+// MD5Digest digest; // the result of the computation
+// MD5Final(&digest, &ctx);
+//
+// You can call MD5DigestToBase16() to generate a string of the digest.
+
+// The output of an MD5 operation.
+struct MD5Digest {
+ uint8_t a[16];
+};
+
+// Used for storing intermediate data during an MD5 computation. Callers
+// should not access the data.
+typedef char MD5Context[88];
+
+// Initializes the given MD5 context structure for subsequent calls to
+// MD5Update().
+void MD5Init(MD5Context* context);
+
+// For the given buffer of |data| as a string, updates the given MD5
+// context with the sum of the data. You can call this any number of times
+// during the computation, except that MD5Init() must have been called first.
+void MD5Update(MD5Context* context, const std::string& data);
+
+// Finalizes the MD5 operation and fills the buffer with the digest.
+void MD5Final(MD5Digest* digest, MD5Context* context);
+
+// MD5IntermediateFinal() generates a digest without finalizing the MD5
+// operation. Can be used to generate digests for the input seen thus far,
+// without affecting the digest generated for the entire input.
+void MD5IntermediateFinal(MD5Digest* digest, const MD5Context* context);
+
+// Converts a digest into human-readable hexadecimal.
+std::string MD5DigestToBase16(const MD5Digest& digest);
+
+// Computes the MD5 sum of the given data buffer with the given length.
+// The given 'digest' structure will be filled with the result data.
+void MD5Sum(const void* data, size_t length, MD5Digest* digest);
+
+// Returns the MD5 (in hexadecimal) of a string.
+std::string MD5String(const std::string& str);
+
+} // namespace android
+
+#endif // C2_E2E_TEST_MD5_H_
diff --git a/tests/c2_e2e_test/jni/mediacodec_decoder.cpp b/tests/c2_e2e_test/jni/mediacodec_decoder.cpp
new file mode 100644
index 0000000..003387b
--- /dev/null
+++ b/tests/c2_e2e_test/jni/mediacodec_decoder.cpp
@@ -0,0 +1,385 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaCodecDecoder"
+
+#include "mediacodec_decoder.h"
+
+#include <assert.h>
+#include <inttypes.h>
+
+#include <utility>
+#include <vector>
+
+#include <media/NdkMediaFormat.h>
+#include <utils/Log.h>
+
+namespace android {
+namespace {
+// The timeout of AMediaCodec_dequeueOutputBuffer function calls.
+constexpr int kTimeoutWaitForOutputUs = 1000; // 1 millisecond
+
+// The timeout of AMediaCodec_dequeueInputBuffer function calls.
+constexpr int kTimeoutWaitForInputUs = 1000; // 1 millisecond
+
+// The maximal retry times of doDecode routine.
+// The maximal tolerable interval between two dequeued outputs will be:
+// kTimeoutWaitForOutputUs * kTimeoutMaxRetries = 500 milliseconds
+constexpr size_t kTimeoutMaxRetries = 500;
+
+// The specified framerate for generating input timestamps.
+constexpr int32_t kFrameRate = 25;
+
+// Helper function to get possible decoder names from |type|.
+std::vector<const char*> GetC2VideoDecoderNames(VideoCodecType type) {
+ switch (type) {
+ case VideoCodecType::H264:
+ return {"c2.vda.avc.decoder", "ARC.h264.decode"};
+ case VideoCodecType::VP8:
+ return {"c2.vda.vp8.decoder", "ARC.vp8.decode"};
+ case VideoCodecType::VP9:
+ return {"c2.vda.vp9.decoder", "ARC.vp9.decode"};
+ default: // unknown type
+ return {};
+ }
+}
+
+#if ANDROID_VERSION >= 0x0900
+const uint32_t BUFFER_FLAG_CODEC_CONFIG = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
+const char* FORMAT_KEY_SLICE_HEIGHT = AMEDIAFORMAT_KEY_SLICE_HEIGHT;
+#else
+// Define non-exported constants of MediaCodec NDK interface here for usage of
+// Android Version < Pie.
+const uint32_t BUFFER_FLAG_CODEC_CONFIG = 2;
+const char* FORMAT_KEY_SLICE_HEIGHT = "slice-height";
+#endif
+
+} // namespace
+
+// static
+std::unique_ptr<MediaCodecDecoder> MediaCodecDecoder::Create(const std::string& input_path,
+ VideoCodecProfile profile,
+ const Size& video_size) {
+ if (video_size.IsEmpty()) {
+ ALOGE("Size is not valid: %dx%d", video_size.width, video_size.height);
+ return nullptr;
+ }
+
+ VideoCodecType type = VideoCodecProfileToType(profile);
+
+ std::unique_ptr<EncodedDataHelper> encoded_data_helper(new EncodedDataHelper(input_path, type));
+ if (!encoded_data_helper->IsValid()) {
+ ALOGE("EncodedDataHelper is not created for file: %s", input_path.c_str());
+ return nullptr;
+ }
+
+ AMediaCodec* codec = nullptr;
+ auto decoder_names = GetC2VideoDecoderNames(type);
+ for (const auto& decoder_name : decoder_names) {
+ codec = AMediaCodec_createCodecByName(decoder_name);
+ if (codec) {
+ ALOGD("Created mediacodec decoder by name: %s", decoder_name);
+ break;
+ }
+ }
+ if (!codec) {
+ ALOGE("Failed to create mediacodec decoder.");
+ return nullptr;
+ }
+
+ return std::unique_ptr<MediaCodecDecoder>(
+ new MediaCodecDecoder(codec, std::move(encoded_data_helper), type, video_size));
+}
+
+MediaCodecDecoder::MediaCodecDecoder(AMediaCodec* codec,
+ std::unique_ptr<EncodedDataHelper> encoded_data_helper,
+ VideoCodecType type, const Size& size)
+ : codec_(codec),
+ encoded_data_helper_(std::move(encoded_data_helper)),
+ type_(type),
+ input_visible_size_(size) {}
+
+MediaCodecDecoder::~MediaCodecDecoder() {
+ if (codec_ != nullptr) {
+ AMediaCodec_delete(codec_);
+ }
+}
+
+void MediaCodecDecoder::AddOutputBufferReadyCb(const OutputBufferReadyCb& cb) {
+ output_buffer_ready_cbs_.push_back(cb);
+}
+
+void MediaCodecDecoder::AddOutputFormatChangedCb(const OutputFormatChangedCb& cb) {
+ output_format_changed_cbs_.push_back(cb);
+}
+
+void MediaCodecDecoder::Rewind() {
+ encoded_data_helper_->Rewind();
+ input_fragment_index_ = 0;
+}
+
+bool MediaCodecDecoder::Configure() {
+ ALOGD("configure: mime=%s, width=%d, height=%d", GetMimeType(type_), input_visible_size_.width,
+ input_visible_size_.height);
+ AMediaFormat* format = AMediaFormat_new();
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, GetMimeType(type_));
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, input_visible_size_.width);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, input_visible_size_.height);
+ media_status_t ret = AMediaCodec_configure(codec_, format, nullptr /* surface */,
+ nullptr /* crtpto */, 0 /* flag */);
+ AMediaFormat_delete(format);
+ if (ret != AMEDIA_OK) {
+ ALOGE("Configure return error: %d", ret);
+ return false;
+ }
+ return true;
+}
+
+bool MediaCodecDecoder::Start() {
+ media_status_t ret = AMediaCodec_start(codec_);
+ if (ret != AMEDIA_OK) {
+ ALOGE("Start return error: %d", ret);
+ return false;
+ }
+ return true;
+}
+
+bool MediaCodecDecoder::Decode() {
+ while (!output_done_) {
+ size_t retries = 0;
+ bool success = false;
+
+ // It will keep retrying until one output buffer is dequeued successfully.
+ // On each retry we would like to enqueue input buffers as fast as possible.
+ // The retry loop will break as failure if maxmimum retries are reached or
+ // errors returned from enqueue input buffer or dequeue output buffer.
+ while (retries < kTimeoutMaxRetries && !success) {
+ if (!EnqueueInputBuffers()) return false;
+
+ switch (DequeueOutputBuffer()) {
+ case DequeueStatus::RETRY:
+ retries++;
+ break;
+ case DequeueStatus::SUCCESS:
+ success = true;
+ break;
+ case DequeueStatus::FAILURE:
+ return false;
+ }
+ }
+
+ if (retries >= kTimeoutMaxRetries) {
+ ALOGE("Decoder did not produce an output buffer after %zu retries", kTimeoutMaxRetries);
+ }
+ if (!success) return false;
+ }
+ return true;
+}
+
+bool MediaCodecDecoder::EnqueueInputBuffers() {
+ ssize_t index;
+ while (!input_done_) {
+ index = AMediaCodec_dequeueInputBuffer(codec_, kTimeoutWaitForInputUs);
+ if (index == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
+ return true; // no available input buffers, try next time
+
+ if (index < 0) {
+ ALOGE("Unknown error while dequeueInputBuffer: %zd", index);
+ return false;
+ }
+
+ if (encoded_data_helper_->ReachEndOfStream()) {
+ if (!FeedEOSInputBuffer(index)) return false;
+ input_done_ = true;
+ } else {
+ if (!FeedInputBuffer(index)) return false;
+ }
+ }
+ return true;
+}
+
+MediaCodecDecoder::DequeueStatus MediaCodecDecoder::DequeueOutputBuffer() {
+ AMediaCodecBufferInfo info;
+ ssize_t index = AMediaCodec_dequeueOutputBuffer(codec_, &info, kTimeoutWaitForOutputUs);
+
+ switch (index) {
+ case AMEDIACODEC_INFO_TRY_AGAIN_LATER:
+ ALOGV("Try again later is reported");
+ return DequeueStatus::RETRY;
+ case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED:
+ ALOGV("Output buffers changed");
+ return DequeueStatus::RETRY;
+ case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED:
+ ALOGV("Output format changed");
+ if (GetOutputFormat())
+ return DequeueStatus::SUCCESS;
+ else
+ return DequeueStatus::FAILURE;
+ default:
+ if (index < 0) {
+ ALOGE("Unknown error while dequeueOutputBuffer: %zd", index);
+ return DequeueStatus::FAILURE;
+ }
+ break;
+ }
+
+ if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) output_done_ = true;
+ if (!ReceiveOutputBuffer(index, info)) return DequeueStatus::FAILURE;
+ return DequeueStatus::SUCCESS;
+}
+
+bool MediaCodecDecoder::Stop() {
+ return AMediaCodec_stop(codec_) == AMEDIA_OK;
+}
+
+bool MediaCodecDecoder::FeedInputBuffer(size_t index) {
+ assert(!encoded_data_helper_->ReachEndOfStream());
+
+ size_t buf_size = 0;
+ uint8_t* buf = AMediaCodec_getInputBuffer(codec_, index, &buf_size);
+ if (!buf) {
+ ALOGE("Failed to getInputBuffer: index=%zu", index);
+ return false;
+ }
+
+ auto fragment = encoded_data_helper_->GetNextFragment();
+ assert(fragment);
+
+ if (buf_size < fragment->data.size()) {
+ ALOGE("Input buffer size is not enough: buf_size=%zu, data_size=%zu", buf_size,
+ fragment->data.size());
+ return false;
+ }
+
+ memcpy(reinterpret_cast<char*>(buf), fragment->data.data(), fragment->data.size());
+
+ uint32_t input_flag = 0;
+ if (fragment->csd_flag) input_flag |= BUFFER_FLAG_CODEC_CONFIG;
+
+ uint64_t timestamp_us = input_fragment_index_ * 1000000 / kFrameRate;
+
+ ALOGV("queueInputBuffer(index=%zu, offset=0, size=%zu, time=%" PRIu64 ", flags=%u) #%d", index,
+ fragment->data.size(), timestamp_us, input_flag, input_fragment_index_);
+ media_status_t status = AMediaCodec_queueInputBuffer(
+ codec_, index, 0 /* offset */, fragment->data.size(), timestamp_us, input_flag);
+ if (status != AMEDIA_OK) {
+ ALOGE("Failed to queueInputBuffer: %d", status);
+ return false;
+ }
+ ++input_fragment_index_;
+ return true;
+}
+
+bool MediaCodecDecoder::FeedEOSInputBuffer(size_t index) {
+ // Timestamp of EOS input buffer is undefined, use 0 here to test decoder
+ // robustness.
+ uint64_t timestamp_us = 0;
+
+ ALOGV("queueInputBuffer(index=%zu) EOS", index);
+ media_status_t status =
+ AMediaCodec_queueInputBuffer(codec_, index, 0 /* offset */, 0 /* size */, timestamp_us,
+ AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
+ if (status != AMEDIA_OK) {
+ ALOGE("Failed to queueInputBuffer EOS: %d", status);
+ return false;
+ }
+ return true;
+}
+
+bool MediaCodecDecoder::ReceiveOutputBuffer(size_t index, const AMediaCodecBufferInfo& info) {
+ size_t out_size;
+ uint8_t* buf = AMediaCodec_getOutputBuffer(codec_, index, &out_size);
+ if (!buf) {
+ ALOGE("Failed to getOutputBuffer(index=%zu)", index);
+ return false;
+ }
+
+ received_outputs_++;
+ ALOGV("ReceiveOutputBuffer(index=%zu, size=%d, time=%" PRId64 ", flags=%u) #%d", index,
+ info.size, info.presentationTimeUs, info.flags, received_outputs_);
+
+ // Do not callback for dummy EOS output (info.size == 0)
+ if (info.size > 0) {
+ for (const auto& callback : output_buffer_ready_cbs_)
+ callback(buf, info.size, received_outputs_);
+ }
+
+ media_status_t status = AMediaCodec_releaseOutputBuffer(codec_, index, false /* render */);
+ if (status != AMEDIA_OK) {
+ ALOGE("Failed to releaseOutputBuffer(index=%zu): %d", index, status);
+ return false;
+ }
+ return true;
+}
+
+bool MediaCodecDecoder::GetOutputFormat() {
+ AMediaFormat* format = AMediaCodec_getOutputFormat(codec_);
+ bool success = true;
+
+ // Required formats
+ int32_t width = 0;
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width)) {
+ ALOGE("Cannot find width in format.");
+ success = false;
+ }
+
+ int32_t height = 0;
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height)) {
+ ALOGE("Cannot find height in format.");
+ success = false;
+ }
+
+ int32_t color_format = 0;
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, &color_format)) {
+ ALOGE("Cannot find color-format in format.");
+ success = false;
+ }
+
+ // Optional formats
+ int32_t crop_left = 0;
+ int32_t crop_top = 0;
+ int32_t crop_right = width - 1;
+ int32_t crop_bottom = height - 1;
+#if ANDROID_VERSION >= 0x0900 // Android 9.0 (Pie)
+ // Crop info is only avaiable on NDK version >= Pie.
+ if (!AMediaFormat_getRect(format, AMEDIAFORMAT_KEY_DISPLAY_CROP, &crop_left, &crop_top,
+ &crop_right, &crop_bottom)) {
+ ALOGD("Cannot find crop window in format. Set as large as frame size.");
+ crop_left = 0;
+ crop_top = 0;
+ crop_right = width - 1;
+ crop_bottom = height - 1;
+ }
+#endif
+ // Note: For ARC++N, width and height are set as same as the size of crop
+ // window in ArcCodec. So the values above will be still satisfied in
+ // ARC++N.
+
+ // In current exiting ARC video decoder crop origin is always at (0,0)
+ if (crop_left != 0 || crop_top != 0) {
+ ALOGE("Crop origin is not (0,0): (%d,%d)", crop_left, crop_top);
+ success = false;
+ }
+
+ int32_t stride = 0;
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_STRIDE, &stride)) {
+ ALOGD("Cannot find stride in format. Set as frame width.");
+ stride = width;
+ }
+
+ int32_t slice_height = 0;
+ if (!AMediaFormat_getInt32(format, FORMAT_KEY_SLICE_HEIGHT, &slice_height)) {
+ ALOGD("Cannot find slice-height in format. Set as frame height.");
+ slice_height = height;
+ }
+
+ for (const auto& callback : output_format_changed_cbs_) {
+ callback(Size(stride, slice_height),
+ Size(crop_right - crop_left + 1, crop_bottom - crop_top + 1), color_format);
+ }
+ return success;
+}
+
+} // namespace android
diff --git a/tests/c2_e2e_test/jni/mediacodec_decoder.h b/tests/c2_e2e_test/jni/mediacodec_decoder.h
new file mode 100644
index 0000000..66937b7
--- /dev/null
+++ b/tests/c2_e2e_test/jni/mediacodec_decoder.h
@@ -0,0 +1,124 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef C2_E2E_TEST_MEDIACODEC_DECODER_H_
+#define C2_E2E_TEST_MEDIACODEC_DECODER_H_
+
+#include <memory>
+#include <queue>
+#include <string>
+#include <vector>
+
+#include <media/NdkMediaCodec.h>
+
+#include "common.h"
+#include "encoded_data_helper.h"
+
+namespace android {
+
+// Wrapper class to manipulate a MediaCodec video decoder.
+class MediaCodecDecoder {
+public:
+ // Checks the argument and create MediaCodecDecoder instance.
+ static std::unique_ptr<MediaCodecDecoder> Create(const std::string& input_path,
+ VideoCodecProfile profile,
+ const Size& video_size);
+
+ MediaCodecDecoder() = delete;
+ ~MediaCodecDecoder();
+
+ // The callback function that is called when output buffer is ready.
+ using OutputBufferReadyCb = std::function<void(
+ const uint8_t* /* data */, size_t /* buffer_size */, int /* output_index */)>;
+ void AddOutputBufferReadyCb(const OutputBufferReadyCb& cb);
+
+ // The callback function that is called when output format is changed.
+ using OutputFormatChangedCb =
+ std::function<void(const Size& /* coded_size */, const Size& /* visible_size */,
+ int32_t /* color_format */)>;
+ void AddOutputFormatChangedCb(const OutputFormatChangedCb& cb);
+
+ // Decoder manipulation methods.
+
+ // Rewind the input stream to the first frame as well as frame index.
+ void Rewind();
+
+ // Wrapper of AMediaCodec_configure.
+ bool Configure();
+
+ // Wrapper of AMediaCodec_start.
+ bool Start();
+
+ // Decode the input stream. After decoding, send EOS request to the decoder.
+ // Return true if EOS output buffer is received.
+ bool Decode();
+
+ // Wrapper of AMediaCodec_stop.
+ bool Stop();
+
+private:
+ MediaCodecDecoder(AMediaCodec* codec, std::unique_ptr<EncodedDataHelper> encoded_data_helper,
+ VideoCodecType type, const Size& size);
+
+ // Enum class of the status of dequeueing output buffer.
+ enum class DequeueStatus { RETRY, SUCCESS, FAILURE };
+
+ // Fill all available input buffers and enqueue.
+ bool EnqueueInputBuffers();
+
+ // Try to dequeue one output buffer and return DequeueStatus.
+ DequeueStatus DequeueOutputBuffer();
+
+ // Read the sample data from AMediaExtractor or CSD data and feed into the
+ // input buffer.
+ // |index| is the index of the target input buffer.
+ bool FeedInputBuffer(size_t index);
+
+ // Feed an empty buffer with EOS flag.
+ // |index| is the index of the target input buffer.
+ bool FeedEOSInputBuffer(size_t index);
+
+ // Receive the output buffer and make mOutputBufferReadyCb callback if given.
+ // |index| is the index of the target output buffer.
+ // |info| is the buffer info of the target output buffer.
+ bool ReceiveOutputBuffer(size_t index, const AMediaCodecBufferInfo& info);
+
+ // Get output format by AMediaCodec_getOutputFormat and make
+ // |output_format_changed_cb_| callback if given.
+ // Return false if required information is missing, e.g. width, color format.
+ bool GetOutputFormat();
+
+ // The target mediacodec decoder.
+ AMediaCodec* codec_;
+
+ // The reference of EncodedDataHelper.
+ std::unique_ptr<EncodedDataHelper> encoded_data_helper_;
+
+ // The codec type of decoding.
+ VideoCodecType type_;
+ // The output video visible size.
+ Size input_visible_size_;
+
+ // The list of callback functions which are called in order when a output
+ // buffer is ready.
+ std::vector<OutputBufferReadyCb> output_buffer_ready_cbs_;
+ // The list of callback functions that are called in order when output format
+ // is changed.
+ std::vector<OutputFormatChangedCb> output_format_changed_cbs_;
+
+ // The fragment index that indicates which frame is sent to the decoder at
+ // next round.
+ int input_fragment_index_ = 0;
+ // The total number of received output buffers. Only used for logging.
+ int received_outputs_ = 0;
+
+ // The indication of input done.
+ bool input_done_ = false;
+ // The indication of output done.
+ bool output_done_ = false;
+};
+
+} // namespace android
+
+#endif // C2_E2E_TEST_MEDIACODEC_DECODER_H_
diff --git a/tests/c2_e2e_test/jni/video_decoder_e2e_test.cpp b/tests/c2_e2e_test/jni/video_decoder_e2e_test.cpp
new file mode 100644
index 0000000..e88e3ca
--- /dev/null
+++ b/tests/c2_e2e_test/jni/video_decoder_e2e_test.cpp
@@ -0,0 +1,341 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <getopt.h>
+
+#include <fstream>
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "common.h"
+#include "mediacodec_decoder.h"
+#include "video_frame.h"
+
+namespace android {
+
+// Environment to store test video data for all test cases.
+class C2VideoDecoderTestEnvironment;
+
+namespace {
+C2VideoDecoderTestEnvironment* g_env;
+
+} // namespace
+
+class C2VideoDecoderTestEnvironment : public testing::Environment {
+public:
+ explicit C2VideoDecoderTestEnvironment(const std::string& data,
+ const std::string& output_frames_path)
+ : test_video_data_(data), output_frames_path_(output_frames_path) {}
+
+ void SetUp() override { ParseTestVideoData(); }
+
+ // The syntax of test video data is:
+ // "input_file_path:width:height:num_frames:num_fragments:min_fps_render:
+ // min_fps_no_render:video_codec_profile[:output_file_path]"
+ // - |input_file_path| is compressed video stream in H264 Annex B (NAL) format
+ // (H264) or IVF (VP8/9).
+ // - |width| and |height| are visible frame size in pixels.
+ // - |num_frames| is the number of picture frames for the input stream.
+ // - |num_fragments| is the number of AU (H264) or frame (VP8/9) in the input
+ // stream. (Unused. Test will automatically parse the number.)
+ // - |min_fps_render| and |min_fps_no_render| are minimum frames/second speeds
+ // expected to be achieved with and without rendering respective.
+ // (The former is unused because no rendering case here.)
+ // (The latter is Optional.)
+ // - |video_codec_profile| is the VideoCodecProfile set during Initialization.
+ void ParseTestVideoData() {
+ std::vector<std::string> fields = SplitString(test_video_data_, ':');
+ ASSERT_EQ(fields.size(), 8U)
+ << "The number of fields of test_video_data is not 8: " << test_video_data_;
+
+ input_file_path_ = fields[0];
+ int width = std::stoi(fields[1]);
+ int height = std::stoi(fields[2]);
+ visible_size_ = Size(width, height);
+ ASSERT_FALSE(visible_size_.IsEmpty());
+
+ num_frames_ = std::stoi(fields[3]);
+ ASSERT_GT(num_frames_, 0);
+
+ // Unused fields[4] --> num_fragments
+ // Unused fields[5] --> min_fps_render
+
+ if (!fields[6].empty()) {
+ min_fps_no_render_ = std::stoi(fields[6]);
+ }
+
+ video_codec_profile_ = static_cast<VideoCodecProfile>(std::stoi(fields[7]));
+ ASSERT_NE(VideoCodecProfileToType(video_codec_profile_), VideoCodecType::UNKNOWN);
+ }
+
+ // Get the corresponding frame-wise golden MD5 file path.
+ std::string GoldenMD5FilePath() const { return input_file_path_ + ".frames.md5"; }
+
+ std::string output_frames_path() const { return output_frames_path_; }
+
+ std::string input_file_path() const { return input_file_path_; }
+ Size visible_size() const { return visible_size_; }
+ int num_frames() const { return num_frames_; }
+ int min_fps_no_render() const { return min_fps_no_render_; }
+ VideoCodecProfile video_codec_profile() const { return video_codec_profile_; }
+
+protected:
+ std::string test_video_data_;
+ std::string output_frames_path_;
+
+ std::string input_file_path_;
+ Size visible_size_;
+ int num_frames_ = 0;
+ int min_fps_no_render_ = 0;
+ VideoCodecProfile video_codec_profile_;
+};
+
+// The struct to record output formats.
+struct OutputFormat {
+ Size coded_size;
+ Size visible_size;
+ int32_t color_format = 0;
+};
+
+// The helper class to validate video frame by MD5 and output to I420 raw stream
+// if needed.
+class VideoFrameValidator {
+public:
+ VideoFrameValidator() = default;
+ ~VideoFrameValidator() { output_file_.close(); }
+
+ // Set |md5_golden_path| as the path of golden frame-wise MD5 file. Return
+ // false if the file is failed to read.
+ bool SetGoldenMD5File(const std::string& md5_golden_path) {
+ golden_md5_file_ = std::unique_ptr<InputFileASCII>(new InputFileASCII(md5_golden_path));
+ return golden_md5_file_->IsValid();
+ }
+
+ // Set |output_frames_path| as the path for output raw I420 stream. Return
+ // false if the file is failed to open.
+ bool SetOutputFile(const std::string& output_frames_path) {
+ if (output_frames_path.empty()) return false;
+
+ output_file_.open(output_frames_path, std::ofstream::binary);
+ if (!output_file_.is_open()) {
+ printf("[ERR] Failed to open file: %s\n", output_frames_path.c_str());
+ return false;
+ }
+ printf("[LOG] Decode output to file: %s\n", output_frames_path.c_str());
+ write_to_file_ = true;
+ return true;
+ }
+
+ // Callback function of output buffer ready to validate frame data by
+ // VideoFrameValidator, write into file if needed.
+ void VerifyMD5(const uint8_t* data, size_t buffer_size, int output_index) {
+ std::string golden;
+ ASSERT_TRUE(golden_md5_file_ && golden_md5_file_->IsValid());
+ ASSERT_TRUE(golden_md5_file_->ReadLine(&golden))
+ << "Failed to read golden MD5 at frame#" << output_index;
+
+ std::unique_ptr<VideoFrame> video_frame =
+ VideoFrame::Create(data, buffer_size, output_format_.coded_size,
+ output_format_.visible_size, output_format_.color_format);
+ ASSERT_TRUE(video_frame) << "Failed to create video frame on VerifyMD5 at frame#"
+ << output_index;
+
+ ASSERT_TRUE(video_frame->VerifyMD5(golden)) << "MD5 mismatched at frame#" << output_index;
+
+ // Update color_format.
+ output_format_.color_format = video_frame->color_format();
+ }
+
+ // Callback function of output buffer ready to validate frame data by
+ // VideoFrameValidator, write into file if needed.
+ void OutputToFile(const uint8_t* data, size_t buffer_size, int output_index) {
+ if (!write_to_file_) return;
+
+ std::unique_ptr<VideoFrame> video_frame =
+ VideoFrame::Create(data, buffer_size, output_format_.coded_size,
+ output_format_.visible_size, output_format_.color_format);
+ ASSERT_TRUE(video_frame) << "Failed to create video frame on OutputToFile at frame#"
+ << output_index;
+ if (!video_frame->WriteFrame(&output_file_)) {
+ printf("[ERR] Failed to write output buffer into file.\n");
+ // Stop writing frames to file once it is failed.
+ write_to_file_ = false;
+ }
+ }
+
+ // Callback function of output format changed to update output format.
+ void UpdateOutputFormat(const Size& coded_size, const Size& visible_size,
+ int32_t color_format) {
+ output_format_.coded_size = coded_size;
+ output_format_.visible_size = visible_size;
+ output_format_.color_format = color_format;
+ }
+
+private:
+ // The wrapper of input MD5 golden file.
+ std::unique_ptr<InputFileASCII> golden_md5_file_;
+ // The output file to write the decoded raw video.
+ std::ofstream output_file_;
+
+ // Only output video frame to file if True.
+ bool write_to_file_ = false;
+ // This records output format, color_format might be revised in flexible
+ // format case.
+ OutputFormat output_format_;
+};
+
+class C2VideoDecoderE2ETest : public testing::Test {
+public:
+ // Callback function of output buffer ready to count frame.
+ void CountFrame(const uint8_t* /* data */, size_t /* buffer_size */, int /* output_index */) {
+ decoded_frames_++;
+ }
+
+ // Callback function of output format changed to verify output format.
+ void VerifyOutputFormat(const Size& coded_size, const Size& visible_size,
+ int32_t color_format) {
+ ASSERT_FALSE(coded_size.IsEmpty());
+ ASSERT_FALSE(visible_size.IsEmpty());
+ ASSERT_LE(visible_size.width, coded_size.width);
+ ASSERT_LE(visible_size.height, coded_size.height);
+ printf("[LOG] Got format changed { coded_size: %dx%d, visible_size: %dx%d, "
+ "color_format: 0x%x\n",
+ coded_size.width, coded_size.height, visible_size.width, visible_size.height,
+ color_format);
+ output_format_.coded_size = coded_size;
+ output_format_.visible_size = visible_size;
+ output_format_.color_format = color_format;
+ }
+
+protected:
+ void SetUp() override {
+ decoder_ = MediaCodecDecoder::Create(g_env->input_file_path(), g_env->video_codec_profile(),
+ g_env->visible_size());
+
+ ASSERT_TRUE(decoder_);
+ decoder_->Rewind();
+
+ ASSERT_TRUE(decoder_->Configure());
+ ASSERT_TRUE(decoder_->Start());
+
+ decoder_->AddOutputBufferReadyCb(std::bind(&C2VideoDecoderE2ETest::CountFrame, this,
+ std::placeholders::_1, std::placeholders::_2,
+ std::placeholders::_3));
+ decoder_->AddOutputFormatChangedCb(std::bind(&C2VideoDecoderE2ETest::VerifyOutputFormat,
+ this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
+ }
+
+ void TearDown() override {
+ EXPECT_TRUE(decoder_->Stop());
+
+ EXPECT_EQ(g_env->visible_size().width, output_format_.visible_size.width);
+ EXPECT_EQ(g_env->visible_size().height, output_format_.visible_size.height);
+ EXPECT_EQ(g_env->num_frames(), decoded_frames_);
+
+ decoder_.reset();
+ }
+
+ // The wrapper of the mediacodec decoder.
+ std::unique_ptr<MediaCodecDecoder> decoder_;
+
+ // The counter of obtained decoded output frames.
+ int decoded_frames_ = 0;
+ // This records formats from output format change.
+ OutputFormat output_format_;
+};
+
+TEST_F(C2VideoDecoderE2ETest, TestSimpleDecode) {
+ VideoFrameValidator video_frame_validator;
+
+ ASSERT_TRUE(video_frame_validator.SetGoldenMD5File(g_env->GoldenMD5FilePath()))
+ << "Failed to open MD5 file: " << g_env->GoldenMD5FilePath();
+
+ decoder_->AddOutputBufferReadyCb(std::bind(&VideoFrameValidator::VerifyMD5,
+ &video_frame_validator, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
+
+ if (video_frame_validator.SetOutputFile(g_env->output_frames_path())) {
+ decoder_->AddOutputBufferReadyCb(std::bind(&VideoFrameValidator::OutputToFile,
+ &video_frame_validator, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
+ }
+
+ decoder_->AddOutputFormatChangedCb(std::bind(&VideoFrameValidator::UpdateOutputFormat,
+ &video_frame_validator, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
+
+ EXPECT_TRUE(decoder_->Decode());
+}
+
+TEST_F(C2VideoDecoderE2ETest, TestFPS) {
+ FPSCalculator fps_calculator;
+ auto callback = [&fps_calculator](const uint8_t* /* data */, size_t /* buffer_size */,
+ int /* output_index */) {
+ ASSERT_TRUE(fps_calculator.RecordFrameTimeDiff());
+ };
+
+ decoder_->AddOutputBufferReadyCb(callback);
+
+ EXPECT_TRUE(decoder_->Decode());
+
+ double fps = fps_calculator.CalculateFPS();
+ printf("[LOG] Measured decoder FPS: %.4f\n", fps);
+ EXPECT_GE(fps, static_cast<double>(g_env->min_fps_no_render()));
+}
+
+} // namespace android
+
+bool GetOption(int argc, char** argv, std::string* test_video_data,
+ std::string* output_frames_path) {
+ const char* const optstring = "t:o:";
+ static const struct option opts[] = {
+ {"test_video_data", required_argument, nullptr, 't'},
+ {"output_frames_path", required_argument, nullptr, 'o'},
+ {nullptr, 0, nullptr, 0},
+ };
+
+ int opt;
+ while ((opt = getopt_long(argc, argv, optstring, opts, nullptr)) != -1) {
+ switch (opt) {
+ case 't':
+ *test_video_data = optarg;
+ break;
+ case 'o':
+ *output_frames_path = optarg;
+ break;
+ default:
+ printf("[WARN] Unknown option: getopt_long() returned code 0x%x.\n", opt);
+ break;
+ }
+ }
+
+ if (test_video_data->empty()) {
+ printf("[ERR] Please assign test video data by --test_video_data\n");
+ return false;
+ }
+ return true;
+}
+
+int RunDecoderTests(char** test_args, int test_args_count) {
+ std::string test_video_data;
+ std::string output_frames_path;
+ if (!GetOption(test_args_count, test_args, &test_video_data, &output_frames_path))
+ return EXIT_FAILURE;
+
+ if (android::g_env == nullptr) {
+ android::g_env = reinterpret_cast<android::C2VideoDecoderTestEnvironment*>(
+ testing::AddGlobalTestEnvironment(new android::C2VideoDecoderTestEnvironment(
+ test_video_data, output_frames_path)));
+ } else {
+ return EXIT_FAILURE;
+ }
+ testing::InitGoogleTest(&test_args_count, test_args);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/tests/c2_e2e_test/jni/video_frame.cpp b/tests/c2_e2e_test/jni/video_frame.cpp
new file mode 100644
index 0000000..a15fe27
--- /dev/null
+++ b/tests/c2_e2e_test/jni/video_frame.cpp
@@ -0,0 +1,225 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VideoFrame"
+
+#include <string.h>
+
+#include "video_frame.h"
+
+#include <utils/Log.h>
+
+namespace android {
+
+namespace {
+
+void CopyWindow(const uint8_t* src, uint8_t* dst, size_t stride, size_t width, size_t height,
+ size_t inc) {
+ if (inc == 1 && stride == width) {
+ // Could copy by plane.
+ memcpy(dst, src, width * height);
+ return;
+ }
+
+ if (inc == 1) {
+ // Could copy by row.
+ for (size_t row = 0; row < height; ++row) {
+ memcpy(dst, src, width);
+ dst += width;
+ src += stride;
+ }
+ return;
+ }
+
+ // inc != 1, copy by pixel.
+ for (size_t row = 0; row < height; ++row) {
+ for (size_t col = 0; col < width; ++col) {
+ memcpy(dst, src, 1);
+ dst++;
+ src += inc;
+ }
+ src += (stride - width) * inc;
+ }
+}
+
+} // namespace
+
+// static
+std::unique_ptr<VideoFrame> VideoFrame::Create(const uint8_t* data, size_t data_size,
+ const Size& coded_size, const Size& visible_size,
+ int32_t color_format) {
+ if (coded_size.IsEmpty() || visible_size.IsEmpty() || (visible_size.width > coded_size.width) ||
+ (visible_size.height > coded_size.height) || (coded_size.width % 2 != 0) ||
+ (coded_size.height % 2 != 0) || (visible_size.width % 2 != 0) ||
+ (visible_size.height % 2 != 0)) {
+ ALOGE("Size are not valid: coded: %dx%d, visible: %dx%d", coded_size.width,
+ coded_size.height, visible_size.width, visible_size.height);
+ return nullptr;
+ }
+
+ if (color_format != YUV_420_PLANAR && color_format != YUV_420_FLEXIBLE &&
+ color_format != HAL_PIXEL_FORMAT_YV12 && color_format != HAL_PIXEL_FORMAT_NV12) {
+ ALOGE("color_format is unknown: 0x%x", color_format);
+ return nullptr;
+ }
+
+ if (data_size < coded_size.width * coded_size.height * 3 / 2) {
+ ALOGE("data_size(=%zu) is not enough for coded_size(=%dx%d)", data_size, coded_size.width,
+ coded_size.height);
+ return nullptr;
+ }
+ // We've found in ARC++P H264 decoding, |data_size| of some output buffers are
+ // bigger than the area which |coded_size| needs (not observed on other codec
+ // and ARC++N).
+ // TODO(johnylin): find the root cause (b/130398258)
+ if (data_size > coded_size.width * coded_size.height * 3 / 2) {
+ ALOGV("data_size(=%zu) is bigger than the area coded_size(=%dx%d) needs.", data_size,
+ coded_size.width, coded_size.height);
+ }
+
+ return std::unique_ptr<VideoFrame>(
+ new VideoFrame(data, coded_size, visible_size, color_format));
+}
+
+VideoFrame::VideoFrame(const uint8_t* data, const Size& coded_size, const Size& visible_size,
+ int32_t color_format)
+ : data_(data),
+ coded_size_(coded_size),
+ visible_size_(visible_size),
+ color_format_(color_format) {
+ frame_data_[0] =
+ std::unique_ptr<uint8_t[]>(new uint8_t[visible_size_.width * visible_size_.height]());
+ frame_data_[1] = std::unique_ptr<uint8_t[]>(
+ new uint8_t[visible_size_.width * visible_size_.height / 4]());
+ frame_data_[2] = std::unique_ptr<uint8_t[]>(
+ new uint8_t[visible_size_.width * visible_size_.height / 4]());
+ if (IsFlexibleFormat()) {
+ ALOGV("Cannot convert video frame now, should be done later by matching HAL "
+ "format.");
+ return;
+ }
+ CopyAndConvertToI420Frame(color_format_);
+}
+
+bool VideoFrame::IsFlexibleFormat() const {
+ return color_format_ == YUV_420_FLEXIBLE;
+}
+
+void VideoFrame::CopyAndConvertToI420Frame(int32_t curr_format) {
+ size_t stride = coded_size_.width;
+ size_t slice_height = coded_size_.height;
+ size_t width = visible_size_.width;
+ size_t height = visible_size_.height;
+
+ const uint8_t* src = data_;
+ CopyWindow(src, frame_data_[0].get(), stride, width, height, 1); // copy Y
+ src += stride * slice_height;
+
+ switch (curr_format) {
+ case YUV_420_PLANAR:
+ CopyWindow(src, frame_data_[1].get(), stride / 2, width / 2, height / 2,
+ 1); // copy U
+ src += stride * slice_height / 4;
+ CopyWindow(src, frame_data_[2].get(), stride / 2, width / 2, height / 2,
+ 1); // copy V
+ return;
+ case HAL_PIXEL_FORMAT_NV12:
+ // NV12: semiplanar = true, crcb_swap = false.
+ CopyWindow(src, frame_data_[1].get(), stride / 2, width / 2, height / 2,
+ 2); // copy U
+ src++;
+ CopyWindow(src, frame_data_[2].get(), stride / 2, width / 2, height / 2,
+ 2); // copy V
+ return;
+ case HAL_PIXEL_FORMAT_YV12:
+ // YV12: semiplanar = false, crcb_swap = true.
+ CopyWindow(src, frame_data_[2].get(), stride / 2, width / 2, height / 2,
+ 1); // copy V
+ src += stride * slice_height / 4;
+ CopyWindow(src, frame_data_[1].get(), stride / 2, width / 2, height / 2,
+ 1); // copy U
+ return;
+ default:
+ ALOGE("Unknown format: 0x%x", curr_format);
+ return;
+ }
+}
+
+bool VideoFrame::MatchHalFormatByGoldenMD5(const std::string& golden) {
+ if (!IsFlexibleFormat()) return true;
+
+ // Try to match with HAL_PIXEL_FORMAT_NV12 first.
+ int32_t format_candidates[2] = {HAL_PIXEL_FORMAT_NV12, HAL_PIXEL_FORMAT_YV12};
+ for (int32_t format : format_candidates) {
+ CopyAndConvertToI420Frame(format);
+ color_format_ = format;
+ std::string frame_md5 = ComputeMD5FromFrame();
+ if (!strcmp(frame_md5.c_str(), golden.c_str())) {
+ ALOGV("Matched YUV Flexible to HAL pixel format: 0x%x", format);
+ return true;
+ } else {
+ ALOGV("Tried HAL pixel format: 0x%x un-matched (%s vs %s)", format, frame_md5.c_str(),
+ golden.c_str());
+ }
+ }
+
+ // Change back to flexible format.
+ color_format_ = YUV_420_FLEXIBLE;
+ return false;
+}
+
+std::string VideoFrame::ComputeMD5FromFrame() const {
+ if (IsFlexibleFormat()) {
+ ALOGE("Cannot compute MD5 with format YUV_420_FLEXIBLE");
+ return std::string();
+ }
+
+ MD5Context context;
+ MD5Init(&context);
+ MD5Update(&context, std::string(reinterpret_cast<const char*>(frame_data_[0].get()),
+ visible_size_.width * visible_size_.height));
+ MD5Update(&context, std::string(reinterpret_cast<const char*>(frame_data_[1].get()),
+ visible_size_.width * visible_size_.height / 4));
+ MD5Update(&context, std::string(reinterpret_cast<const char*>(frame_data_[2].get()),
+ visible_size_.width * visible_size_.height / 4));
+ MD5Digest digest;
+ MD5Final(&digest, &context);
+ return MD5DigestToBase16(digest);
+}
+
+bool VideoFrame::VerifyMD5(const std::string& golden) {
+ if (IsFlexibleFormat()) {
+ // Color format is YUV_420_FLEXIBLE and we haven't match its HAL pixel
+ // format yet. Try to match now.
+ if (!MatchHalFormatByGoldenMD5(golden)) {
+ ALOGE("Failed to match any HAL format");
+ return false;
+ }
+ } else {
+ std::string md5 = ComputeMD5FromFrame();
+ if (strcmp(md5.c_str(), golden.c_str())) {
+ ALOGE("MD5 mismatched. expect: %s, got: %s", golden.c_str(), md5.c_str());
+ return false;
+ }
+ }
+ return true;
+}
+
+bool VideoFrame::WriteFrame(std::ofstream* output_file) const {
+ if (IsFlexibleFormat()) {
+ ALOGE("Cannot write frame with format YUV_420_FLEXIBLE");
+ return false;
+ }
+
+ output_file->write(reinterpret_cast<const char*>(frame_data_[0].get()),
+ visible_size_.width * visible_size_.height);
+ output_file->write(reinterpret_cast<const char*>(frame_data_[1].get()),
+ visible_size_.width * visible_size_.height / 4);
+ output_file->write(reinterpret_cast<const char*>(frame_data_[2].get()),
+ visible_size_.width * visible_size_.height / 4);
+ return output_file->good();
+}
+
+} // namespace android
diff --git a/tests/c2_e2e_test/jni/video_frame.h b/tests/c2_e2e_test/jni/video_frame.h
new file mode 100644
index 0000000..7247240
--- /dev/null
+++ b/tests/c2_e2e_test/jni/video_frame.h
@@ -0,0 +1,89 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef C2_E2E_TEST_VIDEO_FRAME_H_
+#define C2_E2E_TEST_VIDEO_FRAME_H_
+
+#include <fstream>
+#include <memory>
+#include <string>
+
+#include "common.h"
+#include "md5.h"
+
+namespace android {
+
+// The helper class to convert video frame data to I420 format and make a copy
+// of planes, cropped within the visible window.
+class VideoFrame {
+public:
+ // Pre-check the validity of input parameters.
+ static std::unique_ptr<VideoFrame> Create(const uint8_t* data, size_t data_size,
+ const Size& coded_size, const Size& visible_size,
+ int32_t color_format);
+ VideoFrame() = delete;
+ ~VideoFrame() = default;
+
+ enum {
+ // Android color format similar to I420.
+ YUV_420_PLANAR = 0x13,
+ // Android color format which is flexible. For Chrome OS devices, it may be
+ // either YV12 or NV12 as HAL pixel format.
+ // Note: This format is not able to parse, client is required to call
+ // MatchHalFormatByGoldenMD5() first to identify the corresponding HAL
+ // pixel format.
+ YUV_420_FLEXIBLE = 0x7f420888,
+
+ // NV12: semiplanar = true, crcb_swap = false.
+ HAL_PIXEL_FORMAT_NV12 = 0x3231564e,
+ // YV12: semiplanar = false, crcb_swap = true.
+ HAL_PIXEL_FORMAT_YV12 = 0x32315659,
+ };
+
+ // Verify the calculated MD5 of video frame by comparing to |golden|.
+ // It will call MatchHalFormatByGoldenMD5() to find corresponding HAL format
+ // if current color format is YUV_420_FLEXIBLE
+ bool VerifyMD5(const std::string& golden);
+
+ // Write video frame planes to |output_file| as I420 format.
+ bool WriteFrame(std::ofstream* output_file) const;
+
+ int32_t color_format() const { return color_format_; }
+
+private:
+ VideoFrame(const uint8_t* data, const Size& coded_size, const Size& visible_size,
+ int32_t color_format);
+
+ // Convert frame data from source |data_| as format |curr_format| to
+ // destination |frame_data_| as I420 and make a copy, with |visible_size_| as
+ // crop window.
+ void CopyAndConvertToI420Frame(int32_t curr_format);
+
+ // Try to match corresponding HAL pixel format by comparing to |golden| MD5.
+ // Return true on found and overwrite |color_format_| to HAL format.
+ bool MatchHalFormatByGoldenMD5(const std::string& golden);
+
+ // Compute and return MD5 for video frame planes as I420 format.
+ std::string ComputeMD5FromFrame() const;
+
+ // Return True if current color format is YUV_420_FLEXIBLE.
+ bool IsFlexibleFormat() const;
+
+ // The frame data returned by decoder.
+ const uint8_t* data_;
+ // The specified coded size from output format.
+ Size coded_size_;
+ // The specified visible size from output format.
+ Size visible_size_;
+ // The specified color format from output format. It may be overwritten by
+ // MatchHalFormatByGoldenMD5().
+ int32_t color_format_;
+
+ // Converted frame data stored by planes. [0]:Y, [1]:U, [2]:V
+ std::unique_ptr<uint8_t[]> frame_data_[3];
+};
+
+} // namespace android
+
+#endif // C2_E2E_TEST_VIDEO_FRAME_H_
diff --git a/tests/c2_e2e_test/res/layout/main_activity.xml b/tests/c2_e2e_test/res/layout/main_activity.xml
new file mode 100644
index 0000000..75e5643
--- /dev/null
+++ b/tests/c2_e2e_test/res/layout/main_activity.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Chromium OS Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/darker_gray"
+ tools:context="org.chromium.c2.test.E2eTestActivity">
+</LinearLayout>
diff --git a/tests/c2_e2e_test/res/values/strings.xml b/tests/c2_e2e_test/res/values/strings.xml
new file mode 100644
index 0000000..2edddb9
--- /dev/null
+++ b/tests/c2_e2e_test/res/values/strings.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Chromium OS Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+<resources>
+ <!-- The name of the app [CHAR LIMIT=32] -->
+ <string name="app_name">C2E2ETest</string>
+</resources>
diff --git a/tests/c2_e2e_test/src/org/chromium/c2/test/E2eTestActivity.java b/tests/c2_e2e_test/src/org/chromium/c2/test/E2eTestActivity.java
new file mode 100644
index 0000000..0174343
--- /dev/null
+++ b/tests/c2_e2e_test/src/org/chromium/c2/test/E2eTestActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+package org.chromium.c2.test;
+
+import android.app.Activity;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+/** Activity responsible for running the native Codec2.0 E2E tests. */
+public class E2eTestActivity extends Activity {
+
+ public final String TAG = "E2eTestActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ System.loadLibrary("codectest");
+
+ boolean encode = getIntent().getBooleanExtra("do-encode", false);
+ String[] testArgs =
+ getIntent().getStringArrayExtra("test-args") != null
+ ? getIntent().getStringArrayExtra("test-args")
+ : new String[0];
+ String logFile = getIntent().getStringExtra("log-file");
+
+ AsyncTask.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ int res = c2VideoTest(encode, testArgs, testArgs.length, logFile);
+ Log.i(TAG, "Test returned result code " + res);
+
+ new Handler(Looper.getMainLooper())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // gtest can't reuse a process
+ System.exit(0);
+ }
+
+ public native int c2VideoTest(
+ boolean encode, String[] testArgs, int testArgsCount, String tmpFile);
+}