diff options
34 files changed, 2565 insertions, 117 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index cbdf0f1..3da7fa1 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,5 +1,6 @@ [Builtin Hooks] clang_format = true +google_java_format = true [Builtin Hooks Options] -clang_format = --commit ${PREUPLOAD_COMMIT} -- style file --extensions h,cpp +clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions h,cpp diff --git a/tests/Android.mk b/tests/Android.mk index b54ff56..5053e7d 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -1,116 +1 @@ -# Build the unit tests. -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk - -LOCAL_MODULE := C2VDACompIntf_test - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := \ - C2CompIntfTest.cpp \ - C2VDACompIntf_test.cpp \ - -LOCAL_SHARED_LIBRARIES := \ - libchrome \ - libcutils \ - liblog \ - libstagefright_codec2 \ - libstagefright_codec2_vndk \ - libutils \ - libv4l2_codec2 \ - libv4l2_codec2_accel \ - -LOCAL_C_INCLUDES += \ - $(TOP)/external/v4l2_codec2/accel \ - $(TOP)/external/v4l2_codec2/include \ - $(TOP)/hardware/google/av/codec2/include \ - $(TOP)/hardware/google/av/codec2/vndk/include \ - $(TOP)/hardware/google/av/media/codecs/base/include \ - $(TOP)/vendor/google_arc/libs/codec2/vdastore/include \ - -LOCAL_CFLAGS += -Werror -Wall -std=c++14 -LOCAL_CLANG := true - -LOCAL_LDFLAGS := -Wl,-Bsymbolic - -include $(BUILD_NATIVE_TEST) - - -include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk - -LOCAL_MODULE := C2VEACompIntf_test - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := \ - C2CompIntfTest.cpp \ - C2VEACompIntf_test.cpp \ - -LOCAL_SHARED_LIBRARIES := \ - libchrome \ - libcutils \ - liblog \ - libstagefright_codec2 \ - libstagefright_codec2_vndk \ - libutils \ - libv4l2_codec2 \ - libv4l2_codec2_accel \ - -LOCAL_C_INCLUDES += \ - $(TOP)/external/v4l2_codec2/accel \ - $(TOP)/external/v4l2_codec2/include \ - $(TOP)/hardware/google/av/codec2/include \ - $(TOP)/hardware/google/av/codec2/vndk/include \ - $(TOP)/hardware/google/av/media/codecs/base/include \ - -LOCAL_CFLAGS += -Werror -Wall -std=c++14 -LOCAL_CLANG := true - -LOCAL_LDFLAGS := -Wl,-Bsymbolic - -include $(BUILD_NATIVE_TEST) - - -include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk - -LOCAL_MODULE := C2VDAComponent_test - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := \ - C2VDAComponent_test.cpp \ - -LOCAL_SHARED_LIBRARIES := \ - libchrome \ - libcutils \ - liblog \ - libmedia \ - libstagefright \ - libstagefright_codec2 \ - libstagefright_codec2_vndk \ - libstagefright_foundation \ - libutils \ - libv4l2_codec2 \ - libv4l2_codec2_accel \ - android.hardware.media.bufferpool@1.0 \ - -LOCAL_C_INCLUDES += \ - $(TOP)/external/libchrome \ - $(TOP)/external/v4l2_codec2/accel \ - $(TOP)/external/v4l2_codec2/include \ - $(TOP)/frameworks/av/media/libstagefright/include \ - $(TOP)/hardware/google/av/codec2/include \ - $(TOP)/hardware/google/av/codec2/vndk/include \ - $(TOP)/hardware/google/av/media/codecs/base/include \ - $(TOP)/vendor/google_arc/libs/codec2/vdastore/include \ - -# -Wno-unused-parameter is needed for libchrome/base codes -LOCAL_CFLAGS += -Werror -Wall -Wno-unused-parameter -std=c++14 -LOCAL_CLANG := true - -LOCAL_LDFLAGS := -Wl,-Bsymbolic - -include $(BUILD_NATIVE_TEST) +include $(call all-subdir-makefiles) diff --git a/tests/c2_comp_intf/Android.mk b/tests/c2_comp_intf/Android.mk new file mode 100644 index 0000000..b54ff56 --- /dev/null +++ b/tests/c2_comp_intf/Android.mk @@ -0,0 +1,116 @@ +# Build the unit tests. +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk + +LOCAL_MODULE := C2VDACompIntf_test + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := \ + C2CompIntfTest.cpp \ + C2VDACompIntf_test.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libchrome \ + libcutils \ + liblog \ + libstagefright_codec2 \ + libstagefright_codec2_vndk \ + libutils \ + libv4l2_codec2 \ + libv4l2_codec2_accel \ + +LOCAL_C_INCLUDES += \ + $(TOP)/external/v4l2_codec2/accel \ + $(TOP)/external/v4l2_codec2/include \ + $(TOP)/hardware/google/av/codec2/include \ + $(TOP)/hardware/google/av/codec2/vndk/include \ + $(TOP)/hardware/google/av/media/codecs/base/include \ + $(TOP)/vendor/google_arc/libs/codec2/vdastore/include \ + +LOCAL_CFLAGS += -Werror -Wall -std=c++14 +LOCAL_CLANG := true + +LOCAL_LDFLAGS := -Wl,-Bsymbolic + +include $(BUILD_NATIVE_TEST) + + +include $(CLEAR_VARS) +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk + +LOCAL_MODULE := C2VEACompIntf_test + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := \ + C2CompIntfTest.cpp \ + C2VEACompIntf_test.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libchrome \ + libcutils \ + liblog \ + libstagefright_codec2 \ + libstagefright_codec2_vndk \ + libutils \ + libv4l2_codec2 \ + libv4l2_codec2_accel \ + +LOCAL_C_INCLUDES += \ + $(TOP)/external/v4l2_codec2/accel \ + $(TOP)/external/v4l2_codec2/include \ + $(TOP)/hardware/google/av/codec2/include \ + $(TOP)/hardware/google/av/codec2/vndk/include \ + $(TOP)/hardware/google/av/media/codecs/base/include \ + +LOCAL_CFLAGS += -Werror -Wall -std=c++14 +LOCAL_CLANG := true + +LOCAL_LDFLAGS := -Wl,-Bsymbolic + +include $(BUILD_NATIVE_TEST) + + +include $(CLEAR_VARS) +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk + +LOCAL_MODULE := C2VDAComponent_test + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := \ + C2VDAComponent_test.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libchrome \ + libcutils \ + liblog \ + libmedia \ + libstagefright \ + libstagefright_codec2 \ + libstagefright_codec2_vndk \ + libstagefright_foundation \ + libutils \ + libv4l2_codec2 \ + libv4l2_codec2_accel \ + android.hardware.media.bufferpool@1.0 \ + +LOCAL_C_INCLUDES += \ + $(TOP)/external/libchrome \ + $(TOP)/external/v4l2_codec2/accel \ + $(TOP)/external/v4l2_codec2/include \ + $(TOP)/frameworks/av/media/libstagefright/include \ + $(TOP)/hardware/google/av/codec2/include \ + $(TOP)/hardware/google/av/codec2/vndk/include \ + $(TOP)/hardware/google/av/media/codecs/base/include \ + $(TOP)/vendor/google_arc/libs/codec2/vdastore/include \ + +# -Wno-unused-parameter is needed for libchrome/base codes +LOCAL_CFLAGS += -Werror -Wall -Wno-unused-parameter -std=c++14 +LOCAL_CLANG := true + +LOCAL_LDFLAGS := -Wl,-Bsymbolic + +include $(BUILD_NATIVE_TEST) diff --git a/tests/C2CompIntfTest.cpp b/tests/c2_comp_intf/C2CompIntfTest.cpp index 092abd6..092abd6 100644 --- a/tests/C2CompIntfTest.cpp +++ b/tests/c2_comp_intf/C2CompIntfTest.cpp diff --git a/tests/C2CompIntfTest.h b/tests/c2_comp_intf/C2CompIntfTest.h index 61930d8..61930d8 100644 --- a/tests/C2CompIntfTest.h +++ b/tests/c2_comp_intf/C2CompIntfTest.h diff --git a/tests/C2VDACompIntf_test.cpp b/tests/c2_comp_intf/C2VDACompIntf_test.cpp index 32d4e20..32d4e20 100644 --- a/tests/C2VDACompIntf_test.cpp +++ b/tests/c2_comp_intf/C2VDACompIntf_test.cpp diff --git a/tests/C2VDAComponent_test.cpp b/tests/c2_comp_intf/C2VDAComponent_test.cpp index 4d387d4..4d387d4 100644 --- a/tests/C2VDAComponent_test.cpp +++ b/tests/c2_comp_intf/C2VDAComponent_test.cpp diff --git a/tests/C2VEACompIntf_test.cpp b/tests/c2_comp_intf/C2VEACompIntf_test.cpp index 6b72c76..6b72c76 100644 --- a/tests/C2VEACompIntf_test.cpp +++ b/tests/c2_comp_intf/C2VEACompIntf_test.cpp diff --git a/tests/data/bear-vp8.webm b/tests/c2_comp_intf/data/bear-vp8.webm Binary files differindex 02ae36c..02ae36c 100644 --- a/tests/data/bear-vp8.webm +++ b/tests/c2_comp_intf/data/bear-vp8.webm 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 Binary files differindex 4f497ae..4f497ae 100644 --- a/tests/data/bear-vp9.webm +++ b/tests/c2_comp_intf/data/bear-vp9.webm 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 Binary files differindex f1d30fb..f1d30fb 100644 --- a/tests/data/bear.mp4 +++ b/tests/c2_comp_intf/data/bear.mp4 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); +} |