aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorDavid Stevens <stevensd@google.com>2019-11-18 16:53:42 +0900
committerPin-chih Lin <johnylin@google.com>2019-11-27 16:00:34 +0800
commitc096c30eeb2d358809e71b41154e1b7872a84d72 (patch)
treeddf57e9691035386a9cbb247096bd0f2bca2524f /tests
parent05ec8a80a4da99f4afac3991540b768d9540ac06 (diff)
downloadv4l2_codec2-c096c30eeb2d358809e71b41154e1b7872a84d72.tar.gz
Initial port of arc codec-test from chromeos tree
This change ports the decoder portion of the arc codec-tests [1] into an Android application. The codec-test source is copied with minimal changes - just renaming some header includes, replacing main() with a RunDecoderTests function, removing most references to 'arc', and auto formatting. The new code is the Android activity and the jni glue. This change also enables presubmit format hooks for java and fixes a typo in the clang_format hook configuration. [1] https://chromium.googlesource.com/chromiumos/platform2/+/57c0c80bdb0d671f4fb2293c4c5fa7b5ed9dddf0/arc/codec-test/ Test: existing decoder tests pass when following instructions in readme Bug: 142423642 Bug: 143584325 Bug: 144681449 Change-Id: I31e02cae5c10bb9936769c75efafe1fa9971bdbb (cherry picked from commit 46344d0a85479760c5294f21de2efc106ab54048)
Diffstat (limited to 'tests')
-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
33 files changed, 2563 insertions, 116 deletions
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);
+}