diff --git a/app/build.gradle b/app/build.gradle index 7f2ca35..31dc2bd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 25 - buildToolsVersion '23.0.3' + compileSdkVersion 26 + buildToolsVersion '26.0.2' defaultConfig { applicationId "com.tangxiaolv.telegramgallery.app" minSdkVersion 14 - targetSdkVersion 23 + targetSdkVersion 26 versionCode 1 versionName "1.0" } @@ -20,8 +20,8 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:23.3.0' -// compile project(':telegramgallery') - debugCompile project(path:':telegramgallery',configuration:'debug') - releaseCompile project(path:':telegramgallery',configuration: 'release') + compile 'com.android.support:appcompat-v7:26.1.0' + compile project(':telegramgallery') +// debugCompile project(path: ':telegramgallery', configuration: 'debug') +// releaseCompile project(path: ':telegramgallery', configuration: 'release') } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 02a7007..e8fc775 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,16 +1,16 @@ - + + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/Theme.AppCompat.NoActionBar"> @@ -18,7 +18,9 @@ - + + \ No newline at end of file diff --git a/app/src/main/java/com/tangxiaolv/telegramgallery/app/MainActivity.java b/app/src/main/java/com/tangxiaolv/telegramgallery/app/MainActivity.java index d61cb8a..f12879e 100644 --- a/app/src/main/java/com/tangxiaolv/telegramgallery/app/MainActivity.java +++ b/app/src/main/java/com/tangxiaolv/telegramgallery/app/MainActivity.java @@ -56,8 +56,7 @@ public long getItemId(int position) { public View getView(int position, View convertView, ViewGroup parent) { ImageView view = new ImageView(MainActivity.this); view.setScaleType(ImageView.ScaleType.CENTER_CROP); - view.setLayoutParams(new GridView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - 256)); + view.setLayoutParams(new GridView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 256)); String path = (String) getItem(position); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inPreferredConfig = Bitmap.Config.ARGB_4444; @@ -73,10 +72,12 @@ public View getView(int position, View convertView, ViewGroup parent) { @Override public void onClick(View v) { GalleryConfig config = new GalleryConfig.Build() - .limitPickPhoto(3) - .singlePhoto(false) - .hintOfPick("this is pick hint") - .filterMimeTypes(new String[]{}) + .setLimitPickPhoto(9) + .setSinglePhoto(false) + .setHasOriginalPic(true) + .setHasVideo(true) + .setMaxImageSize(1024 * 1024 * 5) + .setMaxVideoTime(8) .build(); GalleryActivity.openActivity(MainActivity.this, reqCode, config); } @@ -86,7 +87,10 @@ public void onClick(View v) { @Override public void onClick(View v) { GalleryConfig config = new GalleryConfig.Build() - .singlePhoto(true).build(); + .setSinglePhoto(true) + .setHasOriginalPic(true) + .setHasVideo(true) + .build(); GalleryActivity.openActivity(MainActivity.this, reqCode, config); } }); diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5885930..54b60ab 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,4 +1,4 @@ - + + diff --git a/build.gradle b/build.gradle index f361d10..9b56009 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' // NOTE: Do not place your application dependencies here; they belong @@ -15,6 +15,7 @@ buildscript { allprojects { repositories { + maven { url 'https://maven.google.com' } jcenter() } } diff --git a/telegramgallery/build.gradle b/telegramgallery/build.gradle index e39ccf9..d5cb9a8 100644 --- a/telegramgallery/build.gradle +++ b/telegramgallery/build.gradle @@ -28,17 +28,17 @@ ext { allLicenses = ["Apache-2.0"] } android { - compileSdkVersion 25 - buildToolsVersion '23.0.3' + compileSdkVersion 26 + buildToolsVersion '26.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 23 + targetSdkVersion 26 versionCode 1 versionName "1.0" ndk { - abiFilters 'armeabi'//,'x86'//, 'armeabi-v7a'//, 'x86'//, 'x86_6 4', 'arm64-v8a' + abiFilters 'armeabi-v7a'//,'x86'//, 'armeabi-v7a'//, 'x86'//, 'x86_6 4', 'arm64-v8a' } externalNativeBuild { @@ -56,7 +56,7 @@ android { } } - publishNonDefault true + //publishNonDefault true externalNativeBuild { cmake { //path 'src/main/cpp/CMakeLists.txt' @@ -74,6 +74,8 @@ android { } dependencies { + compile 'com.android.support:support-annotations:26.1.0' + compile 'com.googlecode.mp4parser:isoparser:1.0.6' } // Place it at the end of the file diff --git a/telegramgallery/src/main/cpp/libyuv/AUTHORS b/telegramgallery/src/main/cpp/libyuv/AUTHORS deleted file mode 100644 index 9686ac1..0000000 --- a/telegramgallery/src/main/cpp/libyuv/AUTHORS +++ /dev/null @@ -1,4 +0,0 @@ -# Names should be added to this file like so: -# Name or Organization - -Google Inc. diff --git a/telegramgallery/src/main/cpp/libyuv/CMakeLists.txt b/telegramgallery/src/main/cpp/libyuv/CMakeLists.txt deleted file mode 100644 index fe084e3..0000000 --- a/telegramgallery/src/main/cpp/libyuv/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -cmake_minimum_required(VERSION 3.4.1) -project(yuv C CXX) - -aux_source_directory(${LIBYUV}/source SOURCE_YUV) - -INCLUDE_DIRECTORIES(${LIBYUV}/include) -INCLUDE_DIRECTORIES(${LIBYUV}/include/libyuv) -INCLUDE_DIRECTORIES(${LIBYUV}/mbedtls) - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -Wl") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wl") - -add_library(yuv STATIC ${SOURCE_YUV}) \ No newline at end of file diff --git a/telegramgallery/src/main/cpp/libyuv/LICENSE b/telegramgallery/src/main/cpp/libyuv/LICENSE deleted file mode 100644 index c911747..0000000 --- a/telegramgallery/src/main/cpp/libyuv/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Copyright 2011 The LibYuv Project Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/telegramgallery/src/main/cpp/libyuv/LICENSE_THIRD_PARTY b/telegramgallery/src/main/cpp/libyuv/LICENSE_THIRD_PARTY deleted file mode 100644 index a71591e..0000000 --- a/telegramgallery/src/main/cpp/libyuv/LICENSE_THIRD_PARTY +++ /dev/null @@ -1,8 +0,0 @@ -This source tree contains third party source code which is governed by third -party licenses. This file contains references to files which are under other -licenses than the one provided in the LICENSE file in the root of the source -tree. - -Files governed by third party licenses: -source/x86inc.asm - diff --git a/telegramgallery/src/main/cpp/libyuv/PATENTS b/telegramgallery/src/main/cpp/libyuv/PATENTS deleted file mode 100644 index 64aa5c9..0000000 --- a/telegramgallery/src/main/cpp/libyuv/PATENTS +++ /dev/null @@ -1,24 +0,0 @@ -Additional IP Rights Grant (Patents) - -"This implementation" means the copyrightable works distributed by -Google as part of the LibYuv code package. - -Google hereby grants to you a perpetual, worldwide, non-exclusive, -no-charge, irrevocable (except as stated in this section) patent -license to make, have made, use, offer to sell, sell, import, -transfer, and otherwise run, modify and propagate the contents of this -implementation of the LibYuv code package, where such license applies -only to those patent claims, both currently owned by Google and -acquired in the future, licensable by Google that are necessarily -infringed by this implementation of the LibYuv code package. This -grant does not include claims that would be infringed only as a -consequence of further modification of this implementation. If you or -your agent or exclusive licensee institute or order or agree to the -institution of patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that this -implementation of the LibYuv code package or any code incorporated -within this implementation of the LibYuv code package constitutes -direct or contributory patent infringement, or inducement of patent -infringement, then any patent rights granted to you under this License -for this implementation of the LibYuv code package shall terminate as -of the date such litigation is filed. \ No newline at end of file diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv.h deleted file mode 100644 index de65283..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_H_ // NOLINT -#define INCLUDE_LIBYUV_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/compare.h" -#include "libyuv/convert.h" -#include "libyuv/convert_argb.h" -#include "libyuv/convert_from.h" -#include "libyuv/convert_from_argb.h" -#include "libyuv/cpu_id.h" -#include "libyuv/mjpeg_decoder.h" -#include "libyuv/planar_functions.h" -#include "libyuv/rotate.h" -#include "libyuv/rotate_argb.h" -#include "libyuv/row.h" -#include "libyuv/scale.h" -#include "libyuv/scale_argb.h" -#include "libyuv/scale_row.h" -#include "libyuv/version.h" -#include "libyuv/video_common.h" - -#endif // INCLUDE_LIBYUV_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/basic_types.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/basic_types.h deleted file mode 100644 index beb750b..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/basic_types.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_BASIC_TYPES_H_ // NOLINT -#define INCLUDE_LIBYUV_BASIC_TYPES_H_ - -#include // for NULL, size_t - -#if defined(__ANDROID__) || (defined(_MSC_VER) && (_MSC_VER < 1600)) -#include // for uintptr_t on x86 -#else -#include // for uintptr_t -#endif - -#ifndef GG_LONGLONG -#ifndef INT_TYPES_DEFINED -#define INT_TYPES_DEFINED -#ifdef COMPILER_MSVC -typedef unsigned __int64 uint64; -typedef __int64 int64; -#ifndef INT64_C -#define INT64_C(x) x ## I64 -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## UI64 -#endif -#define INT64_F "I64" -#else // COMPILER_MSVC -#if defined(__LP64__) && !defined(__OpenBSD__) && !defined(__APPLE__) -typedef unsigned long uint64; // NOLINT -typedef long int64; // NOLINT -#ifndef INT64_C -#define INT64_C(x) x ## L -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## UL -#endif -#define INT64_F "l" -#else // defined(__LP64__) && !defined(__OpenBSD__) && !defined(__APPLE__) -typedef unsigned long long uint64; // NOLINT -typedef long long int64; // NOLINT -#ifndef INT64_C -#define INT64_C(x) x ## LL -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## ULL -#endif -#define INT64_F "ll" -#endif // __LP64__ -#endif // COMPILER_MSVC -typedef unsigned int uint32; -typedef int int32; -typedef unsigned short uint16; // NOLINT -typedef short int16; // NOLINT -typedef unsigned char uint8; -typedef signed char int8; -#endif // INT_TYPES_DEFINED -#endif // GG_LONGLONG - -// Detect compiler is for x86 or x64. -#if defined(__x86_64__) || defined(_M_X64) || \ - defined(__i386__) || defined(_M_IX86) -#define CPU_X86 1 -#endif -// Detect compiler is for ARM. -#if defined(__arm__) || defined(_M_ARM) -#define CPU_ARM 1 -#endif - -#ifndef ALIGNP -#ifdef __cplusplus -#define ALIGNP(p, t) \ - (reinterpret_cast(((reinterpret_cast(p) + \ - ((t) - 1)) & ~((t) - 1)))) -#else -#define ALIGNP(p, t) \ - ((uint8*)((((uintptr_t)(p) + ((t) - 1)) & ~((t) - 1)))) /* NOLINT */ -#endif -#endif - -#if !defined(LIBYUV_API) -#if defined(_WIN32) || defined(__CYGWIN__) -#if defined(LIBYUV_BUILDING_SHARED_LIBRARY) -#define LIBYUV_API __declspec(dllexport) -#elif defined(LIBYUV_USING_SHARED_LIBRARY) -#define LIBYUV_API __declspec(dllimport) -#else -#define LIBYUV_API -#endif // LIBYUV_BUILDING_SHARED_LIBRARY -#elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__APPLE__) && \ - (defined(LIBYUV_BUILDING_SHARED_LIBRARY) || \ - defined(LIBYUV_USING_SHARED_LIBRARY)) -#define LIBYUV_API __attribute__ ((visibility ("default"))) -#else -#define LIBYUV_API -#endif // __GNUC__ -#endif // LIBYUV_API - -#define LIBYUV_BOOL int -#define LIBYUV_FALSE 0 -#define LIBYUV_TRUE 1 - -// Visual C x86 or GCC little endian. -#if defined(__x86_64__) || defined(_M_X64) || \ - defined(__i386__) || defined(_M_IX86) || \ - defined(__arm__) || defined(_M_ARM) || \ - (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -#define LIBYUV_LITTLE_ENDIAN -#endif - -#endif // INCLUDE_LIBYUV_BASIC_TYPES_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/compare.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/compare.h deleted file mode 100644 index 08b2bb2..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/compare.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_COMPARE_H_ // NOLINT -#define INCLUDE_LIBYUV_COMPARE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Compute a hash for specified memory. Seed of 5381 recommended. -LIBYUV_API -uint32 HashDjb2(const uint8* src, uint64 count, uint32 seed); - -// Scan an opaque argb image and return fourcc based on alpha offset. -// Returns FOURCC_ARGB, FOURCC_BGRA, or 0 if unknown. -LIBYUV_API -uint32 ARGBDetect(const uint8* argb, int stride_argb, int width, int height); - -// Sum Square Error - used to compute Mean Square Error or PSNR. -LIBYUV_API -uint64 ComputeSumSquareError(const uint8* src_a, - const uint8* src_b, int count); - -LIBYUV_API -uint64 ComputeSumSquareErrorPlane(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -static const int kMaxPsnr = 128; - -LIBYUV_API -double SumSquareErrorToPsnr(uint64 sse, uint64 count); - -LIBYUV_API -double CalcFramePsnr(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -LIBYUV_API -double I420Psnr(const uint8* src_y_a, int stride_y_a, - const uint8* src_u_a, int stride_u_a, - const uint8* src_v_a, int stride_v_a, - const uint8* src_y_b, int stride_y_b, - const uint8* src_u_b, int stride_u_b, - const uint8* src_v_b, int stride_v_b, - int width, int height); - -LIBYUV_API -double CalcFrameSsim(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -LIBYUV_API -double I420Ssim(const uint8* src_y_a, int stride_y_a, - const uint8* src_u_a, int stride_u_a, - const uint8* src_v_a, int stride_v_a, - const uint8* src_y_b, int stride_y_b, - const uint8* src_u_b, int stride_u_b, - const uint8* src_v_b, int stride_v_b, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_COMPARE_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/compare_row.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/compare_row.h deleted file mode 100644 index 38a957b..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/compare_row.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_COMPARE_ROW_H_ // NOLINT -#define INCLUDE_LIBYUV_COMPARE_ROW_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -#if !defined(LIBYUV_DISABLE_X86) && \ - defined(_M_IX86) && (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_HASHDJB2_AVX2 -#endif - -// The following are available for Visual C and GCC: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) || defined(_M_IX86))) -#define HAS_HASHDJB2_SSE41 -#define HAS_SUMSQUAREERROR_SSE2 -#endif - -// The following are available for Visual C and clangcl 32 bit: -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) && \ - (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_HASHDJB2_AVX2 -#define HAS_SUMSQUAREERROR_AVX2 -#endif - -// The following are available for Neon: -#if !defined(LIBYUV_DISABLE_NEON) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_SUMSQUAREERROR_NEON -#endif - -uint32 SumSquareError_C(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_SSE2(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_AVX2(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_NEON(const uint8* src_a, const uint8* src_b, int count); - -uint32 HashDjb2_C(const uint8* src, int count, uint32 seed); -uint32 HashDjb2_SSE41(const uint8* src, int count, uint32 seed); -uint32 HashDjb2_AVX2(const uint8* src, int count, uint32 seed); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_COMPARE_ROW_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert.h deleted file mode 100644 index a2cdc57..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert.h +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_H_ // NOLINT -#define INCLUDE_LIBYUV_CONVERT_H_ - -#include "libyuv/basic_types.h" - -#include "libyuv/rotate.h" // For enum RotationMode. - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Convert I444 to I420. -LIBYUV_API -int I444ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I422 to I420. -LIBYUV_API -int I422ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I411 to I420. -LIBYUV_API -int I411ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy I420 to I420. -#define I420ToI420 I420Copy -LIBYUV_API -int I420Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I400 (grey) to I420. -LIBYUV_API -int I400ToI420(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -#define J400ToJ420 I400ToI420 - -// Convert NV12 to I420. -LIBYUV_API -int NV12ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert NV21 to I420. -LIBYUV_API -int NV21ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_vu, int src_stride_vu, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert YUY2 to I420. -LIBYUV_API -int YUY2ToI420(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert UYVY to I420. -LIBYUV_API -int UYVYToI420(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert M420 to I420. -LIBYUV_API -int M420ToI420(const uint8* src_m420, int src_stride_m420, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// ARGB little endian (bgra in memory) to I420. -LIBYUV_API -int ARGBToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// BGRA little endian (argb in memory) to I420. -LIBYUV_API -int BGRAToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// ABGR little endian (rgba in memory) to I420. -LIBYUV_API -int ABGRToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGBA little endian (abgr in memory) to I420. -LIBYUV_API -int RGBAToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB little endian (bgr in memory) to I420. -LIBYUV_API -int RGB24ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB big endian (rgb in memory) to I420. -LIBYUV_API -int RAWToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB16 (RGBP fourcc) little endian to I420. -LIBYUV_API -int RGB565ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB15 (RGBO fourcc) little endian to I420. -LIBYUV_API -int ARGB1555ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB12 (R444 fourcc) little endian to I420. -LIBYUV_API -int ARGB4444ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -#ifdef HAVE_JPEG -// src_width/height provided by capture. -// dst_width/height for clipping determine final size. -LIBYUV_API -int MJPGToI420(const uint8* sample, size_t sample_size, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, - int dst_width, int dst_height); - -// Query size of MJPG in pixels. -LIBYUV_API -int MJPGSize(const uint8* sample, size_t sample_size, - int* width, int* height); -#endif - -// Convert camera sample to I420 with cropping, rotation and vertical flip. -// "src_size" is needed to parse MJPG. -// "dst_stride_y" number of bytes in a row of the dst_y plane. -// Normally this would be the same as dst_width, with recommended alignment -// to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. The caller should -// allocate the I420 buffer according to rotation. -// "dst_stride_u" number of bytes in a row of the dst_u plane. -// Normally this would be the same as (dst_width + 1) / 2, with -// recommended alignment to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. -// "crop_x" and "crop_y" are starting position for cropping. -// To center, crop_x = (src_width - dst_width) / 2 -// crop_y = (src_height - dst_height) / 2 -// "src_width" / "src_height" is size of src_frame in pixels. -// "src_height" can be negative indicating a vertically flipped image source. -// "crop_width" / "crop_height" is the size to crop the src to. -// Must be less than or equal to src_width/src_height -// Cropping parameters are pre-rotation. -// "rotation" can be 0, 90, 180 or 270. -// "format" is a fourcc. ie 'I420', 'YUY2' -// Returns 0 for successful; -1 for invalid parameter. Non-zero for failure. -LIBYUV_API -int ConvertToI420(const uint8* src_frame, size_t src_size, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int crop_x, int crop_y, - int src_width, int src_height, - int crop_width, int crop_height, - enum RotationMode rotation, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_argb.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_argb.h deleted file mode 100644 index 079d273..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_argb.h +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_ARGB_H_ // NOLINT -#define INCLUDE_LIBYUV_CONVERT_ARGB_H_ - -#include "libyuv/basic_types.h" - -#include "libyuv/rotate.h" // For enum RotationMode. - -// TODO(fbarchard): This set of functions should exactly match convert.h -// TODO(fbarchard): Add tests. Create random content of right size and convert -// with C vs Opt and or to I420 and compare. -// TODO(fbarchard): Some of these functions lack parameter setting. - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Alias. -#define ARGBToARGB ARGBCopy - -// Copy ARGB to ARGB. -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I420 to ARGB. -LIBYUV_API -int I420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I422 to ARGB. -LIBYUV_API -int I422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I444 to ARGB. -LIBYUV_API -int I444ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J444 to ARGB. -LIBYUV_API -int J444ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I444 to ABGR. -LIBYUV_API -int I444ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert I411 to ARGB. -LIBYUV_API -int I411ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I420 with Alpha to preattenuated ARGB. -LIBYUV_API -int I420AlphaToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_argb, int dst_stride_argb, - int width, int height, int attenuate); - -// Convert I420 with Alpha to preattenuated ABGR. -LIBYUV_API -int I420AlphaToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height, int attenuate); - -// Convert I400 (grey) to ARGB. Reverse of ARGBToI400. -LIBYUV_API -int I400ToARGB(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J400 (jpeg grey) to ARGB. -LIBYUV_API -int J400ToARGB(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Alias. -#define YToARGB I400ToARGB - -// Convert NV12 to ARGB. -LIBYUV_API -int NV12ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert NV21 to ARGB. -LIBYUV_API -int NV21ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_vu, int src_stride_vu, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert M420 to ARGB. -LIBYUV_API -int M420ToARGB(const uint8* src_m420, int src_stride_m420, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert YUY2 to ARGB. -LIBYUV_API -int YUY2ToARGB(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert UYVY to ARGB. -LIBYUV_API -int UYVYToARGB(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J420 to ARGB. -LIBYUV_API -int J420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J422 to ARGB. -LIBYUV_API -int J422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J420 to ABGR. -LIBYUV_API -int J420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert J422 to ABGR. -LIBYUV_API -int J422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert H420 to ARGB. -LIBYUV_API -int H420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert H422 to ARGB. -LIBYUV_API -int H422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert H420 to ABGR. -LIBYUV_API -int H420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert H422 to ABGR. -LIBYUV_API -int H422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// BGRA little endian (argb in memory) to ARGB. -LIBYUV_API -int BGRAToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// ABGR little endian (rgba in memory) to ARGB. -LIBYUV_API -int ABGRToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGBA little endian (abgr in memory) to ARGB. -LIBYUV_API -int RGBAToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Deprecated function name. -#define BG24ToARGB RGB24ToARGB - -// RGB little endian (bgr in memory) to ARGB. -LIBYUV_API -int RGB24ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB big endian (rgb in memory) to ARGB. -LIBYUV_API -int RAWToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB16 (RGBP fourcc) little endian to ARGB. -LIBYUV_API -int RGB565ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB15 (RGBO fourcc) little endian to ARGB. -LIBYUV_API -int ARGB1555ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB12 (R444 fourcc) little endian to ARGB. -LIBYUV_API -int ARGB4444ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -#ifdef HAVE_JPEG -// src_width/height provided by capture -// dst_width/height for clipping determine final size. -LIBYUV_API -int MJPGToARGB(const uint8* sample, size_t sample_size, - uint8* dst_argb, int dst_stride_argb, - int src_width, int src_height, - int dst_width, int dst_height); -#endif - -// Convert camera sample to ARGB with cropping, rotation and vertical flip. -// "src_size" is needed to parse MJPG. -// "dst_stride_argb" number of bytes in a row of the dst_argb plane. -// Normally this would be the same as dst_width, with recommended alignment -// to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. The caller should -// allocate the I420 buffer according to rotation. -// "dst_stride_u" number of bytes in a row of the dst_u plane. -// Normally this would be the same as (dst_width + 1) / 2, with -// recommended alignment to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. -// "crop_x" and "crop_y" are starting position for cropping. -// To center, crop_x = (src_width - dst_width) / 2 -// crop_y = (src_height - dst_height) / 2 -// "src_width" / "src_height" is size of src_frame in pixels. -// "src_height" can be negative indicating a vertically flipped image source. -// "crop_width" / "crop_height" is the size to crop the src to. -// Must be less than or equal to src_width/src_height -// Cropping parameters are pre-rotation. -// "rotation" can be 0, 90, 180 or 270. -// "format" is a fourcc. ie 'I420', 'YUY2' -// Returns 0 for successful; -1 for invalid parameter. Non-zero for failure. -LIBYUV_API -int ConvertToARGB(const uint8* src_frame, size_t src_size, - uint8* dst_argb, int dst_stride_argb, - int crop_x, int crop_y, - int src_width, int src_height, - int crop_width, int crop_height, - enum RotationMode rotation, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_ARGB_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_from.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_from.h deleted file mode 100644 index 39e1578..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_from.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_FROM_H_ // NOLINT -#define INCLUDE_LIBYUV_CONVERT_FROM_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/rotate.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// See Also convert.h for conversions from formats to I420. - -// I420Copy in convert to I420ToI420. - -LIBYUV_API -int I420ToI422(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int I420ToI444(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int I420ToI411(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy to I400. Source can be I420, I422, I444, I400, NV12 or NV21. -LIBYUV_API -int I400Copy(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -LIBYUV_API -int I420ToNV12(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -LIBYUV_API -int I420ToNV21(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -LIBYUV_API -int I420ToYUY2(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToUYVY(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToBGRA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToRGBA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -LIBYUV_API -int I420ToRGB24(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToRAW(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToRGB565(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I420 To RGB565 with 4x4 dither matrix (16 bytes). -// Values in dither matrix from 0 to 7 recommended. -// The order of the dither matrix is first byte is upper left. - -LIBYUV_API -int I420ToRGB565Dither(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - const uint8* dither4x4, int width, int height); - -LIBYUV_API -int I420ToARGB1555(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToARGB4444(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I420 to specified format. -// "dst_sample_stride" is bytes in a row for the destination. Pass 0 if the -// buffer has contiguous rows. Can be negative. A multiple of 16 is optimal. -LIBYUV_API -int ConvertFromI420(const uint8* y, int y_stride, - const uint8* u, int u_stride, - const uint8* v, int v_stride, - uint8* dst_sample, int dst_sample_stride, - int width, int height, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_FROM_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_from_argb.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_from_argb.h deleted file mode 100644 index 1df5320..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/convert_from_argb.h +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ // NOLINT -#define INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Copy ARGB to ARGB. -#define ARGBToARGB ARGBCopy -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert ARGB To BGRA. -LIBYUV_API -int ARGBToBGRA(const uint8* src_argb, int src_stride_argb, - uint8* dst_bgra, int dst_stride_bgra, - int width, int height); - -// Convert ARGB To ABGR. -LIBYUV_API -int ARGBToABGR(const uint8* src_argb, int src_stride_argb, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert ARGB To RGBA. -LIBYUV_API -int ARGBToRGBA(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -// Convert ARGB To RGB24. -LIBYUV_API -int ARGBToRGB24(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height); - -// Convert ARGB To RAW. -LIBYUV_API -int ARGBToRAW(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb, int dst_stride_rgb, - int width, int height); - -// Convert ARGB To RGB565. -LIBYUV_API -int ARGBToRGB565(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height); - -// Convert ARGB To RGB565 with 4x4 dither matrix (16 bytes). -// Values in dither matrix from 0 to 7 recommended. -// The order of the dither matrix is first byte is upper left. -// TODO(fbarchard): Consider pointer to 2d array for dither4x4. -// const uint8(*dither)[4][4]; -LIBYUV_API -int ARGBToRGB565Dither(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb565, int dst_stride_rgb565, - const uint8* dither4x4, int width, int height); - -// Convert ARGB To ARGB1555. -LIBYUV_API -int ARGBToARGB1555(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb1555, int dst_stride_argb1555, - int width, int height); - -// Convert ARGB To ARGB4444. -LIBYUV_API -int ARGBToARGB4444(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb4444, int dst_stride_argb4444, - int width, int height); - -// Convert ARGB To I444. -LIBYUV_API -int ARGBToI444(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I422. -LIBYUV_API -int ARGBToI422(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I420. (also in convert.h) -LIBYUV_API -int ARGBToI420(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J420. (JPeg full range I420). -LIBYUV_API -int ARGBToJ420(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J422. -LIBYUV_API -int ARGBToJ422(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I411. -LIBYUV_API -int ARGBToI411(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J400. (JPeg full range). -LIBYUV_API -int ARGBToJ400(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - int width, int height); - -// Convert ARGB to I400. -LIBYUV_API -int ARGBToI400(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Convert ARGB to G. (Reverse of J400toARGB, which replicates G back to ARGB) -LIBYUV_API -int ARGBToG(const uint8* src_argb, int src_stride_argb, - uint8* dst_g, int dst_stride_g, - int width, int height); - -// Convert ARGB To NV12. -LIBYUV_API -int ARGBToNV12(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -// Convert ARGB To NV21. -LIBYUV_API -int ARGBToNV21(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -// Convert ARGB To NV21. -LIBYUV_API -int ARGBToNV21(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -// Convert ARGB To YUY2. -LIBYUV_API -int ARGBToYUY2(const uint8* src_argb, int src_stride_argb, - uint8* dst_yuy2, int dst_stride_yuy2, - int width, int height); - -// Convert ARGB To UYVY. -LIBYUV_API -int ARGBToUYVY(const uint8* src_argb, int src_stride_argb, - uint8* dst_uyvy, int dst_stride_uyvy, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/cpu_id.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/cpu_id.h deleted file mode 100644 index dfb7445..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/cpu_id.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CPU_ID_H_ // NOLINT -#define INCLUDE_LIBYUV_CPU_ID_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Internal flag to indicate cpuid requires initialization. -static const int kCpuInitialized = 0x1; - -// These flags are only valid on ARM processors. -static const int kCpuHasARM = 0x2; -static const int kCpuHasNEON = 0x4; -// 0x8 reserved for future ARM flag. - -// These flags are only valid on x86 processors. -static const int kCpuHasX86 = 0x10; -static const int kCpuHasSSE2 = 0x20; -static const int kCpuHasSSSE3 = 0x40; -static const int kCpuHasSSE41 = 0x80; -static const int kCpuHasSSE42 = 0x100; -static const int kCpuHasAVX = 0x200; -static const int kCpuHasAVX2 = 0x400; -static const int kCpuHasERMS = 0x800; -static const int kCpuHasFMA3 = 0x1000; -static const int kCpuHasAVX3 = 0x2000; -// 0x2000, 0x4000, 0x8000 reserved for future X86 flags. - -// These flags are only valid on MIPS processors. -static const int kCpuHasMIPS = 0x10000; -static const int kCpuHasDSPR2 = 0x20000; - -// Internal function used to auto-init. -LIBYUV_API -int InitCpuFlags(void); - -// Internal function for parsing /proc/cpuinfo. -LIBYUV_API -int ArmCpuCaps(const char* cpuinfo_name); - -// Detect CPU has SSE2 etc. -// Test_flag parameter should be one of kCpuHas constants above. -// returns non-zero if instruction set is detected -static __inline int TestCpuFlag(int test_flag) { - LIBYUV_API extern int cpu_info_; - return (!cpu_info_ ? InitCpuFlags() : cpu_info_) & test_flag; -} - -// For testing, allow CPU flags to be disabled. -// ie MaskCpuFlags(~kCpuHasSSSE3) to disable SSSE3. -// MaskCpuFlags(-1) to enable all cpu specific optimizations. -// MaskCpuFlags(1) to disable all cpu specific optimizations. -LIBYUV_API -void MaskCpuFlags(int enable_flags); - -// Low level cpuid for X86. Returns zeros on other CPUs. -// eax is the info type that you want. -// ecx is typically the cpu number, and should normally be zero. -LIBYUV_API -void CpuId(uint32 eax, uint32 ecx, uint32* cpu_info); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CPU_ID_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/mjpeg_decoder.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/mjpeg_decoder.h deleted file mode 100644 index 8423121..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/mjpeg_decoder.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_MJPEG_DECODER_H_ // NOLINT -#define INCLUDE_LIBYUV_MJPEG_DECODER_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -// NOTE: For a simplified public API use convert.h MJPGToI420(). - -struct jpeg_common_struct; -struct jpeg_decompress_struct; -struct jpeg_source_mgr; - -namespace libyuv { - -#ifdef __cplusplus -extern "C" { -#endif - -LIBYUV_BOOL ValidateJpeg(const uint8* sample, size_t sample_size); - -#ifdef __cplusplus -} // extern "C" -#endif - -static const uint32 kUnknownDataSize = 0xFFFFFFFF; - -enum JpegSubsamplingType { - kJpegYuv420, - kJpegYuv422, - kJpegYuv411, - kJpegYuv444, - kJpegYuv400, - kJpegUnknown -}; - -struct Buffer { - const uint8* data; - int len; -}; - -struct BufferVector { - Buffer* buffers; - int len; - int pos; -}; - -struct SetJmpErrorMgr; - -// MJPEG ("Motion JPEG") is a pseudo-standard video codec where the frames are -// simply independent JPEG images with a fixed huffman table (which is omitted). -// It is rarely used in video transmission, but is common as a camera capture -// format, especially in Logitech devices. This class implements a decoder for -// MJPEG frames. -// -// See http://tools.ietf.org/html/rfc2435 -class LIBYUV_API MJpegDecoder { - public: - typedef void (*CallbackFunction)(void* opaque, - const uint8* const* data, - const int* strides, - int rows); - - static const int kColorSpaceUnknown; - static const int kColorSpaceGrayscale; - static const int kColorSpaceRgb; - static const int kColorSpaceYCbCr; - static const int kColorSpaceCMYK; - static const int kColorSpaceYCCK; - - MJpegDecoder(); - ~MJpegDecoder(); - - // Loads a new frame, reads its headers, and determines the uncompressed - // image format. - // Returns LIBYUV_TRUE if image looks valid and format is supported. - // If return value is LIBYUV_TRUE, then the values for all the following - // getters are populated. - // src_len is the size of the compressed mjpeg frame in bytes. - LIBYUV_BOOL LoadFrame(const uint8* src, size_t src_len); - - // Returns width of the last loaded frame in pixels. - int GetWidth(); - - // Returns height of the last loaded frame in pixels. - int GetHeight(); - - // Returns format of the last loaded frame. The return value is one of the - // kColorSpace* constants. - int GetColorSpace(); - - // Number of color components in the color space. - int GetNumComponents(); - - // Sample factors of the n-th component. - int GetHorizSampFactor(int component); - - int GetVertSampFactor(int component); - - int GetHorizSubSampFactor(int component); - - int GetVertSubSampFactor(int component); - - // Public for testability. - int GetImageScanlinesPerImcuRow(); - - // Public for testability. - int GetComponentScanlinesPerImcuRow(int component); - - // Width of a component in bytes. - int GetComponentWidth(int component); - - // Height of a component. - int GetComponentHeight(int component); - - // Width of a component in bytes with padding for DCTSIZE. Public for testing. - int GetComponentStride(int component); - - // Size of a component in bytes. - int GetComponentSize(int component); - - // Call this after LoadFrame() if you decide you don't want to decode it - // after all. - LIBYUV_BOOL UnloadFrame(); - - // Decodes the entire image into a one-buffer-per-color-component format. - // dst_width must match exactly. dst_height must be <= to image height; if - // less, the image is cropped. "planes" must have size equal to at least - // GetNumComponents() and they must point to non-overlapping buffers of size - // at least GetComponentSize(i). The pointers in planes are incremented - // to point to after the end of the written data. - // TODO(fbarchard): Add dst_x, dst_y to allow specific rect to be decoded. - LIBYUV_BOOL DecodeToBuffers(uint8** planes, int dst_width, int dst_height); - - // Decodes the entire image and passes the data via repeated calls to a - // callback function. Each call will get the data for a whole number of - // image scanlines. - // TODO(fbarchard): Add dst_x, dst_y to allow specific rect to be decoded. - LIBYUV_BOOL DecodeToCallback(CallbackFunction fn, void* opaque, - int dst_width, int dst_height); - - // The helper function which recognizes the jpeg sub-sampling type. - static JpegSubsamplingType JpegSubsamplingTypeHelper( - int* subsample_x, int* subsample_y, int number_of_components); - - private: - void AllocOutputBuffers(int num_outbufs); - void DestroyOutputBuffers(); - - LIBYUV_BOOL StartDecode(); - LIBYUV_BOOL FinishDecode(); - - void SetScanlinePointers(uint8** data); - LIBYUV_BOOL DecodeImcuRow(); - - int GetComponentScanlinePadding(int component); - - // A buffer holding the input data for a frame. - Buffer buf_; - BufferVector buf_vec_; - - jpeg_decompress_struct* decompress_struct_; - jpeg_source_mgr* source_mgr_; - SetJmpErrorMgr* error_mgr_; - - // LIBYUV_TRUE iff at least one component has scanline padding. (i.e., - // GetComponentScanlinePadding() != 0.) - LIBYUV_BOOL has_scanline_padding_; - - // Temporaries used to point to scanline outputs. - int num_outbufs_; // Outermost size of all arrays below. - uint8*** scanlines_; - int* scanlines_sizes_; - // Temporary buffer used for decoding when we can't decode directly to the - // output buffers. Large enough for just one iMCU row. - uint8** databuf_; - int* databuf_strides_; -}; - -} // namespace libyuv - -#endif // __cplusplus -#endif // INCLUDE_LIBYUV_MJPEG_DECODER_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/planar_functions.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/planar_functions.h deleted file mode 100644 index 881b0c5..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/planar_functions.h +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ // NOLINT -#define INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ - -#include "libyuv/basic_types.h" - -// TODO(fbarchard): Remove the following headers includes. -#include "libyuv/convert.h" -#include "libyuv/convert_argb.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Copy a plane of data. -LIBYUV_API -void CopyPlane(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -LIBYUV_API -void CopyPlane_16(const uint16* src_y, int src_stride_y, - uint16* dst_y, int dst_stride_y, - int width, int height); - -// Set a plane of data to a 32 bit value. -LIBYUV_API -void SetPlane(uint8* dst_y, int dst_stride_y, - int width, int height, - uint32 value); - -// Copy I400. Supports inverting. -LIBYUV_API -int I400ToI400(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -#define J400ToJ400 I400ToI400 - -// Copy I422 to I422. -#define I422ToI422 I422Copy -LIBYUV_API -int I422Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy I444 to I444. -#define I444ToI444 I444Copy -LIBYUV_API -int I444Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert YUY2 to I422. -LIBYUV_API -int YUY2ToI422(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert UYVY to I422. -LIBYUV_API -int UYVYToI422(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int YUY2ToNV12(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -LIBYUV_API -int UYVYToNV12(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -// Convert I420 to I400. (calls CopyPlane ignoring u/v). -LIBYUV_API -int I420ToI400(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alias -#define J420ToJ400 I420ToI400 -#define I420ToI420Mirror I420Mirror - -// I420 mirror. -LIBYUV_API -int I420Mirror(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Alias -#define I400ToI400Mirror I400Mirror - -// I400 mirror. A single plane is mirrored horizontally. -// Pass negative height to achieve 180 degree rotation. -LIBYUV_API -int I400Mirror(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alias -#define ARGBToARGBMirror ARGBMirror - -// ARGB mirror. -LIBYUV_API -int ARGBMirror(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert NV12 to RGB565. -LIBYUV_API -int NV12ToRGB565(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height); - -// I422ToARGB is in convert_argb.h -// Convert I422 to BGRA. -LIBYUV_API -int I422ToBGRA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_bgra, int dst_stride_bgra, - int width, int height); - -// Convert I422 to ABGR. -LIBYUV_API -int I422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert I422 to RGBA. -LIBYUV_API -int I422ToRGBA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -// Alias -#define RGB24ToRAW RAWToRGB24 - -LIBYUV_API -int RAWToRGB24(const uint8* src_raw, int src_stride_raw, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height); - -// Draw a rectangle into I420. -LIBYUV_API -int I420Rect(uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int x, int y, int width, int height, - int value_y, int value_u, int value_v); - -// Draw a rectangle into ARGB. -LIBYUV_API -int ARGBRect(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height, uint32 value); - -// Convert ARGB to gray scale ARGB. -LIBYUV_API -int ARGBGrayTo(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Make a rectangle of ARGB gray scale. -LIBYUV_API -int ARGBGray(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height); - -// Make a rectangle of ARGB Sepia tone. -LIBYUV_API -int ARGBSepia(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height); - -// Apply a matrix rotation to each ARGB pixel. -// matrix_argb is 4 signed ARGB values. -128 to 127 representing -2 to 2. -// The first 4 coefficients apply to B, G, R, A and produce B of the output. -// The next 4 coefficients apply to B, G, R, A and produce G of the output. -// The next 4 coefficients apply to B, G, R, A and produce R of the output. -// The last 4 coefficients apply to B, G, R, A and produce A of the output. -LIBYUV_API -int ARGBColorMatrix(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const int8* matrix_argb, - int width, int height); - -// Deprecated. Use ARGBColorMatrix instead. -// Apply a matrix rotation to each ARGB pixel. -// matrix_argb is 3 signed ARGB values. -128 to 127 representing -1 to 1. -// The first 4 coefficients apply to B, G, R, A and produce B of the output. -// The next 4 coefficients apply to B, G, R, A and produce G of the output. -// The last 4 coefficients apply to B, G, R, A and produce R of the output. -LIBYUV_API -int RGBColorMatrix(uint8* dst_argb, int dst_stride_argb, - const int8* matrix_rgb, - int x, int y, int width, int height); - -// Apply a color table each ARGB pixel. -// Table contains 256 ARGB values. -LIBYUV_API -int ARGBColorTable(uint8* dst_argb, int dst_stride_argb, - const uint8* table_argb, - int x, int y, int width, int height); - -// Apply a color table each ARGB pixel but preserve destination alpha. -// Table contains 256 ARGB values. -LIBYUV_API -int RGBColorTable(uint8* dst_argb, int dst_stride_argb, - const uint8* table_argb, - int x, int y, int width, int height); - -// Apply a luma/color table each ARGB pixel but preserve destination alpha. -// Table contains 32768 values indexed by [Y][C] where 7 it 7 bit luma from -// RGB (YJ style) and C is an 8 bit color component (R, G or B). -LIBYUV_API -int ARGBLumaColorTable(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const uint8* luma_rgb_table, - int width, int height); - -// Apply a 3 term polynomial to ARGB values. -// poly points to a 4x4 matrix. The first row is constants. The 2nd row is -// coefficients for b, g, r and a. The 3rd row is coefficients for b squared, -// g squared, r squared and a squared. The 4rd row is coefficients for b to -// the 3, g to the 3, r to the 3 and a to the 3. The values are summed and -// result clamped to 0 to 255. -// A polynomial approximation can be dirived using software such as 'R'. - -LIBYUV_API -int ARGBPolynomial(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const float* poly, - int width, int height); - -// Quantize a rectangle of ARGB. Alpha unaffected. -// scale is a 16 bit fractional fixed point scaler between 0 and 65535. -// interval_size should be a value between 1 and 255. -// interval_offset should be a value between 0 and 255. -LIBYUV_API -int ARGBQuantize(uint8* dst_argb, int dst_stride_argb, - int scale, int interval_size, int interval_offset, - int x, int y, int width, int height); - -// Copy ARGB to ARGB. -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Copy Alpha channel of ARGB to alpha of ARGB. -LIBYUV_API -int ARGBCopyAlpha(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Extract the alpha channel from ARGB. -LIBYUV_API -int ARGBExtractAlpha(const uint8* src_argb, int src_stride_argb, - uint8* dst_a, int dst_stride_a, - int width, int height); - -// Copy Y channel to Alpha of ARGB. -LIBYUV_API -int ARGBCopyYToAlpha(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -typedef void (*ARGBBlendRow)(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width); - -// Get function to Alpha Blend ARGB pixels and store to destination. -LIBYUV_API -ARGBBlendRow GetARGBBlend(); - -// Alpha Blend ARGB images and store to destination. -// Source is pre-multiplied by alpha using ARGBAttenuate. -// Alpha of destination is set to 255. -LIBYUV_API -int ARGBBlend(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Alpha Blend plane and store to destination. -// Source is not pre-multiplied by alpha. -LIBYUV_API -int BlendPlane(const uint8* src_y0, int src_stride_y0, - const uint8* src_y1, int src_stride_y1, - const uint8* alpha, int alpha_stride, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alpha Blend YUV images and store to destination. -// Source is not pre-multiplied by alpha. -// Alpha is full width x height and subsampled to half size to apply to UV. -LIBYUV_API -int I420Blend(const uint8* src_y0, int src_stride_y0, - const uint8* src_u0, int src_stride_u0, - const uint8* src_v0, int src_stride_v0, - const uint8* src_y1, int src_stride_y1, - const uint8* src_u1, int src_stride_u1, - const uint8* src_v1, int src_stride_v1, - const uint8* alpha, int alpha_stride, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Multiply ARGB image by ARGB image. Shifted down by 8. Saturates to 255. -LIBYUV_API -int ARGBMultiply(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Add ARGB image with ARGB image. Saturates to 255. -LIBYUV_API -int ARGBAdd(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Subtract ARGB image (argb1) from ARGB image (argb0). Saturates to 0. -LIBYUV_API -int ARGBSubtract(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I422 to YUY2. -LIBYUV_API -int I422ToYUY2(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I422 to UYVY. -LIBYUV_API -int I422ToUYVY(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert unattentuated ARGB to preattenuated ARGB. -LIBYUV_API -int ARGBAttenuate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert preattentuated ARGB to unattenuated ARGB. -LIBYUV_API -int ARGBUnattenuate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Internal function - do not call directly. -// Computes table of cumulative sum for image where the value is the sum -// of all values above and to the left of the entry. Used by ARGBBlur. -LIBYUV_API -int ARGBComputeCumulativeSum(const uint8* src_argb, int src_stride_argb, - int32* dst_cumsum, int dst_stride32_cumsum, - int width, int height); - -// Blur ARGB image. -// dst_cumsum table of width * (height + 1) * 16 bytes aligned to -// 16 byte boundary. -// dst_stride32_cumsum is number of ints in a row (width * 4). -// radius is number of pixels around the center. e.g. 1 = 3x3. 2=5x5. -// Blur is optimized for radius of 5 (11x11) or less. -LIBYUV_API -int ARGBBlur(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int32* dst_cumsum, int dst_stride32_cumsum, - int width, int height, int radius); - -// Multiply ARGB image by ARGB value. -LIBYUV_API -int ARGBShade(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height, uint32 value); - -// Interpolate between two images using specified amount of interpolation -// (0 to 255) and store to destination. -// 'interpolation' is specified as 8 bit fraction where 0 means 100% src0 -// and 255 means 1% src0 and 99% src1. -LIBYUV_API -int InterpolatePlane(const uint8* src0, int src_stride0, - const uint8* src1, int src_stride1, - uint8* dst, int dst_stride, - int width, int height, int interpolation); - -// Interpolate between two ARGB images using specified amount of interpolation -// Internally calls InterpolatePlane with width * 4 (bpp). -LIBYUV_API -int ARGBInterpolate(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height, int interpolation); - -// Interpolate between two YUV images using specified amount of interpolation -// Internally calls InterpolatePlane on each plane where the U and V planes -// are half width and half height. -LIBYUV_API -int I420Interpolate(const uint8* src0_y, int src0_stride_y, - const uint8* src0_u, int src0_stride_u, - const uint8* src0_v, int src0_stride_v, - const uint8* src1_y, int src1_stride_y, - const uint8* src1_u, int src1_stride_u, - const uint8* src1_v, int src1_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height, int interpolation); - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -#define HAS_ARGBAFFINEROW_SSE2 -#endif - -// Row function for copying pixels from a source with a slope to a row -// of destination. Useful for scaling, rotation, mirror, texture mapping. -LIBYUV_API -void ARGBAffineRow_C(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); -LIBYUV_API -void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); - -// Shuffle ARGB channel order. e.g. BGRA to ARGB. -// shuffler is 16 bytes and must be aligned. -LIBYUV_API -int ARGBShuffle(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_argb, int dst_stride_argb, - const uint8* shuffler, int width, int height); - -// Sobel ARGB effect with planar output. -LIBYUV_API -int ARGBSobelToPlane(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Sobel ARGB effect. -LIBYUV_API -int ARGBSobel(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Sobel ARGB effect w/ Sobel X, Sobel, Sobel Y in ARGB. -LIBYUV_API -int ARGBSobelXY(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate.h deleted file mode 100644 index 8af60b8..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_H_ // NOLINT -#define INCLUDE_LIBYUV_ROTATE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Supported rotation. -typedef enum RotationMode { - kRotate0 = 0, // No rotation. - kRotate90 = 90, // Rotate 90 degrees clockwise. - kRotate180 = 180, // Rotate 180 degrees. - kRotate270 = 270, // Rotate 270 degrees clockwise. - - // Deprecated. - kRotateNone = 0, - kRotateClockwise = 90, - kRotateCounterClockwise = 270, -} RotationModeEnum; - -// Rotate I420 frame. -LIBYUV_API -int I420Rotate(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, enum RotationMode mode); - -// Rotate NV12 input and store in I420. -LIBYUV_API -int NV12ToI420Rotate(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, enum RotationMode mode); - -// Rotate a plane by 0, 90, 180, or 270. -LIBYUV_API -int RotatePlane(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int src_width, int src_height, enum RotationMode mode); - -// Rotate planes by 90, 180, 270. Deprecated. -LIBYUV_API -void RotatePlane90(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotatePlane180(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotatePlane270(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotateUV90(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -// Rotations for when U and V are interleaved. -// These functions take one input pointer and -// split the data into two buffers while -// rotating them. Deprecated. -LIBYUV_API -void RotateUV180(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -LIBYUV_API -void RotateUV270(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -// The 90 and 270 functions are based on transposes. -// Doing a transpose with reversing the read/write -// order will result in a rotation by +- 90 degrees. -// Deprecated. -LIBYUV_API -void TransposePlane(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void TransposeUV(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate_argb.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate_argb.h deleted file mode 100644 index 660ff55..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate_argb.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_ARGB_H_ // NOLINT -#define INCLUDE_LIBYUV_ROTATE_ARGB_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/rotate.h" // For RotationMode. - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Rotate ARGB frame -LIBYUV_API -int ARGBRotate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int src_width, int src_height, enum RotationMode mode); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_ARGB_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate_row.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate_row.h deleted file mode 100644 index ebc487f..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/rotate_row.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_ROW_H_ // NOLINT -#define INCLUDE_LIBYUV_ROTATE_ROW_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// The following are available for Visual C and clangcl 32 bit: -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) -#define HAS_TRANSPOSEWX8_SSSE3 -#define HAS_TRANSPOSEUVWX8_SSE2 -#endif - -// The following are available for GCC 32 or 64 bit but not NaCL for 64 bit: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__i386__) || (defined(__x86_64__) && !defined(__native_client__))) -#define HAS_TRANSPOSEWX8_SSSE3 -#endif - -// The following are available for 64 bit GCC but not NaCL: -#if !defined(LIBYUV_DISABLE_X86) && !defined(__native_client__) && \ - defined(__x86_64__) -#define HAS_TRANSPOSEWX8_FAST_SSSE3 -#define HAS_TRANSPOSEUVWX8_SSE2 -#endif - -#if !defined(LIBYUV_DISABLE_NEON) && !defined(__native_client__) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_TRANSPOSEWX8_NEON -#define HAS_TRANSPOSEUVWX8_NEON -#endif - -#if !defined(LIBYUV_DISABLE_MIPS) && !defined(__native_client__) && \ - defined(__mips__) && \ - defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_TRANSPOSEWX8_DSPR2 -#define HAS_TRANSPOSEUVWX8_DSPR2 -#endif // defined(__mips__) - -void TransposeWxH_C(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width, int height); - -void TransposeWx8_C(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_NEON(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); - -void TransposeWx8_Any_NEON(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Any_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_Any_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Any_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); - -void TransposeUVWxH_C(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -void TransposeUVWx8_C(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_SSE2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_NEON(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); - -void TransposeUVWx8_Any_SSE2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_Any_NEON(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_Any_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_ROW_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/row.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/row.h deleted file mode 100644 index 8dae601..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/row.h +++ /dev/null @@ -1,1938 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROW_H_ // NOLINT -#define INCLUDE_LIBYUV_ROW_H_ - -#include // For malloc. - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#define IS_ALIGNED(p, a) (!((uintptr_t)(p) & ((a) - 1))) - -#ifdef __cplusplus -#define align_buffer_64(var, size) \ - uint8* var##_mem = reinterpret_cast(malloc((size) + 63)); \ - uint8* var = reinterpret_cast \ - ((reinterpret_cast(var##_mem) + 63) & ~63) -#else -#define align_buffer_64(var, size) \ - uint8* var##_mem = (uint8*)(malloc((size) + 63)); /* NOLINT */ \ - uint8* var = (uint8*)(((intptr_t)(var##_mem) + 63) & ~63) /* NOLINT */ -#endif - -#define free_aligned_buffer_64(var) \ - free(var##_mem); \ - var = 0 - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// True if compiling for SSSE3 as a requirement. -#if defined(__SSSE3__) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 3)) -#define LIBYUV_SSSE3_ONLY -#endif - -#if defined(__native_client__) -#define LIBYUV_DISABLE_NEON -#endif -// clang >= 3.5.0 required for Arm64. -#if defined(__clang__) && defined(__aarch64__) && !defined(LIBYUV_DISABLE_NEON) -#if (__clang_major__ < 3) || (__clang_major__ == 3 && (__clang_minor__ < 5)) -#define LIBYUV_DISABLE_NEON -#endif // clang >= 3.5 -#endif // __clang__ - -// GCC >= 4.7.0 required for AVX2. -#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) -#if (__GNUC__ > 4) || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 7)) -#define GCC_HAS_AVX2 1 -#endif // GNUC >= 4.7 -#endif // __GNUC__ - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -// Conversions: -#define HAS_ABGRTOUVROW_SSSE3 -#define HAS_ABGRTOYROW_SSSE3 -#define HAS_ARGB1555TOARGBROW_SSE2 -#define HAS_ARGB4444TOARGBROW_SSE2 -#define HAS_ARGBSETROW_X86 -#define HAS_ARGBSHUFFLEROW_SSE2 -#define HAS_ARGBSHUFFLEROW_SSSE3 -#define HAS_ARGBTOARGB1555ROW_SSE2 -#define HAS_ARGBTOARGB4444ROW_SSE2 -#define HAS_ARGBTORAWROW_SSSE3 -#define HAS_ARGBTORGB24ROW_SSSE3 -#define HAS_ARGBTORGB565DITHERROW_SSE2 -#define HAS_ARGBTORGB565ROW_SSE2 -#define HAS_ARGBTOUV444ROW_SSSE3 -#define HAS_ARGBTOUVJROW_SSSE3 -#define HAS_ARGBTOUVROW_SSSE3 -#define HAS_ARGBTOYJROW_SSSE3 -#define HAS_ARGBTOYROW_SSSE3 -#define HAS_ARGBEXTRACTALPHAROW_SSE2 -#define HAS_BGRATOUVROW_SSSE3 -#define HAS_BGRATOYROW_SSSE3 -#define HAS_COPYROW_ERMS -#define HAS_COPYROW_SSE2 -#define HAS_H422TOARGBROW_SSSE3 -#define HAS_I400TOARGBROW_SSE2 -#define HAS_I422TOARGB1555ROW_SSSE3 -#define HAS_I422TOARGB4444ROW_SSSE3 -#define HAS_I422TOARGBROW_SSSE3 -#define HAS_I422TORGB24ROW_SSSE3 -#define HAS_I422TORGB565ROW_SSSE3 -#define HAS_I422TORGBAROW_SSSE3 -#define HAS_I422TOUYVYROW_SSE2 -#define HAS_I422TOYUY2ROW_SSE2 -#define HAS_I444TOARGBROW_SSSE3 -#define HAS_J400TOARGBROW_SSE2 -#define HAS_J422TOARGBROW_SSSE3 -#define HAS_MERGEUVROW_SSE2 -#define HAS_MIRRORROW_SSSE3 -#define HAS_MIRRORUVROW_SSSE3 -#define HAS_NV12TOARGBROW_SSSE3 -#define HAS_NV12TORGB565ROW_SSSE3 -#define HAS_NV21TOARGBROW_SSSE3 -#define HAS_RAWTOARGBROW_SSSE3 -#define HAS_RAWTORGB24ROW_SSSE3 -#define HAS_RAWTOYROW_SSSE3 -#define HAS_RGB24TOARGBROW_SSSE3 -#define HAS_RGB24TOYROW_SSSE3 -#define HAS_RGB565TOARGBROW_SSE2 -#define HAS_RGBATOUVROW_SSSE3 -#define HAS_RGBATOYROW_SSSE3 -#define HAS_SETROW_ERMS -#define HAS_SETROW_X86 -#define HAS_SPLITUVROW_SSE2 -#define HAS_UYVYTOARGBROW_SSSE3 -#define HAS_UYVYTOUV422ROW_SSE2 -#define HAS_UYVYTOUVROW_SSE2 -#define HAS_UYVYTOYROW_SSE2 -#define HAS_YUY2TOARGBROW_SSSE3 -#define HAS_YUY2TOUV422ROW_SSE2 -#define HAS_YUY2TOUVROW_SSE2 -#define HAS_YUY2TOYROW_SSE2 - -// Effects: -#define HAS_ARGBADDROW_SSE2 -#define HAS_ARGBAFFINEROW_SSE2 -#define HAS_ARGBATTENUATEROW_SSSE3 -#define HAS_ARGBBLENDROW_SSSE3 -#define HAS_ARGBCOLORMATRIXROW_SSSE3 -#define HAS_ARGBCOLORTABLEROW_X86 -#define HAS_ARGBCOPYALPHAROW_SSE2 -#define HAS_ARGBCOPYYTOALPHAROW_SSE2 -#define HAS_ARGBGRAYROW_SSSE3 -#define HAS_ARGBLUMACOLORTABLEROW_SSSE3 -#define HAS_ARGBMIRRORROW_SSE2 -#define HAS_ARGBMULTIPLYROW_SSE2 -#define HAS_ARGBPOLYNOMIALROW_SSE2 -#define HAS_ARGBQUANTIZEROW_SSE2 -#define HAS_ARGBSEPIAROW_SSSE3 -#define HAS_ARGBSHADEROW_SSE2 -#define HAS_ARGBSUBTRACTROW_SSE2 -#define HAS_ARGBUNATTENUATEROW_SSE2 -#define HAS_BLENDPLANEROW_SSSE3 -#define HAS_COMPUTECUMULATIVESUMROW_SSE2 -#define HAS_CUMULATIVESUMTOAVERAGEROW_SSE2 -#define HAS_INTERPOLATEROW_SSSE3 -#define HAS_RGBCOLORTABLEROW_X86 -#define HAS_SOBELROW_SSE2 -#define HAS_SOBELTOPLANEROW_SSE2 -#define HAS_SOBELXROW_SSE2 -#define HAS_SOBELXYROW_SSE2 -#define HAS_SOBELYROW_SSE2 - -// The following functions fail on gcc/clang 32 bit with fpic and framepointer. -// caveat: clangcl uses row_win.cc which works. -#if !defined(x86fix) -// TODO(fbarchard): fix build error on x86 debug -// https://code.google.com/p/libyuv/issues/detail?id=524 -#define HAS_I411TOARGBROW_SSSE3 -// TODO(fbarchard): fix build error on android_full_debug=1 -// https://code.google.com/p/libyuv/issues/detail?id=517 -#define HAS_I422ALPHATOARGBROW_SSSE3 -#endif -#endif - -// The following are available on all x86 platforms, but -// require VS2012, clang 3.4 or gcc 4.7. -// The code supports NaCL but requires a new compiler and validator. -#if !defined(LIBYUV_DISABLE_X86) && (defined(VISUALC_HAS_AVX2) || \ - defined(CLANG_HAS_AVX2) || defined(GCC_HAS_AVX2)) -#define HAS_ARGBCOPYALPHAROW_AVX2 -#define HAS_ARGBCOPYYTOALPHAROW_AVX2 -#define HAS_ARGBMIRRORROW_AVX2 -#define HAS_ARGBPOLYNOMIALROW_AVX2 -#define HAS_ARGBSHUFFLEROW_AVX2 -#define HAS_ARGBTORGB565DITHERROW_AVX2 -#define HAS_ARGBTOUVJROW_AVX2 -#define HAS_ARGBTOUVROW_AVX2 -#define HAS_ARGBTOYJROW_AVX2 -#define HAS_ARGBTOYROW_AVX2 -#define HAS_COPYROW_AVX -#define HAS_H422TOARGBROW_AVX2 -#define HAS_I400TOARGBROW_AVX2 -#if !defined(x86fix) -// TODO(fbarchard): fix build error on android_full_debug=1 -// https://code.google.com/p/libyuv/issues/detail?id=517 -#define HAS_I422ALPHATOARGBROW_AVX2 -#endif -#define HAS_I411TOARGBROW_AVX2 -#define HAS_I422TOARGB1555ROW_AVX2 -#define HAS_I422TOARGB4444ROW_AVX2 -#define HAS_I422TOARGBROW_AVX2 -#define HAS_I422TORGB24ROW_AVX2 -#define HAS_I422TORGB565ROW_AVX2 -#define HAS_I422TORGBAROW_AVX2 -#define HAS_I444TOARGBROW_AVX2 -#define HAS_INTERPOLATEROW_AVX2 -#define HAS_J422TOARGBROW_AVX2 -#define HAS_MERGEUVROW_AVX2 -#define HAS_MIRRORROW_AVX2 -#define HAS_NV12TOARGBROW_AVX2 -#define HAS_NV12TORGB565ROW_AVX2 -#define HAS_NV21TOARGBROW_AVX2 -#define HAS_SPLITUVROW_AVX2 -#define HAS_UYVYTOARGBROW_AVX2 -#define HAS_UYVYTOUV422ROW_AVX2 -#define HAS_UYVYTOUVROW_AVX2 -#define HAS_UYVYTOYROW_AVX2 -#define HAS_YUY2TOARGBROW_AVX2 -#define HAS_YUY2TOUV422ROW_AVX2 -#define HAS_YUY2TOUVROW_AVX2 -#define HAS_YUY2TOYROW_AVX2 - -// Effects: -#define HAS_ARGBADDROW_AVX2 -#define HAS_ARGBATTENUATEROW_AVX2 -#define HAS_ARGBMULTIPLYROW_AVX2 -#define HAS_ARGBSUBTRACTROW_AVX2 -#define HAS_ARGBUNATTENUATEROW_AVX2 -#define HAS_BLENDPLANEROW_AVX2 -#endif - -// The following are available for AVX2 Visual C and clangcl 32 bit: -// TODO(fbarchard): Port to gcc. -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) && \ - (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_ARGB1555TOARGBROW_AVX2 -#define HAS_ARGB4444TOARGBROW_AVX2 -#define HAS_ARGBTOARGB1555ROW_AVX2 -#define HAS_ARGBTOARGB4444ROW_AVX2 -#define HAS_ARGBTORGB565ROW_AVX2 -#define HAS_J400TOARGBROW_AVX2 -#define HAS_RGB565TOARGBROW_AVX2 -#endif - -// The following are also available on x64 Visual C. -#if !defined(LIBYUV_DISABLE_X86) && defined (_M_X64) && \ - (!defined(__clang__) || defined(__SSSE3__)) -#define HAS_I422ALPHATOARGBROW_SSSE3 -#define HAS_I422TOARGBROW_SSSE3 -#endif - -// The following are available on Neon platforms: -#if !defined(LIBYUV_DISABLE_NEON) && \ - (defined(__aarch64__) || defined(__ARM_NEON__) || defined(LIBYUV_NEON)) -#define HAS_ABGRTOUVROW_NEON -#define HAS_ABGRTOYROW_NEON -#define HAS_ARGB1555TOARGBROW_NEON -#define HAS_ARGB1555TOUVROW_NEON -#define HAS_ARGB1555TOYROW_NEON -#define HAS_ARGB4444TOARGBROW_NEON -#define HAS_ARGB4444TOUVROW_NEON -#define HAS_ARGB4444TOYROW_NEON -#define HAS_ARGBSETROW_NEON -#define HAS_ARGBTOARGB1555ROW_NEON -#define HAS_ARGBTOARGB4444ROW_NEON -#define HAS_ARGBTORAWROW_NEON -#define HAS_ARGBTORGB24ROW_NEON -#define HAS_ARGBTORGB565DITHERROW_NEON -#define HAS_ARGBTORGB565ROW_NEON -#define HAS_ARGBTOUV411ROW_NEON -#define HAS_ARGBTOUV444ROW_NEON -#define HAS_ARGBTOUVJROW_NEON -#define HAS_ARGBTOUVROW_NEON -#define HAS_ARGBTOYJROW_NEON -#define HAS_ARGBTOYROW_NEON -#define HAS_ARGBEXTRACTALPHAROW_NEON -#define HAS_BGRATOUVROW_NEON -#define HAS_BGRATOYROW_NEON -#define HAS_COPYROW_NEON -#define HAS_I400TOARGBROW_NEON -#define HAS_I411TOARGBROW_NEON -#define HAS_I422ALPHATOARGBROW_NEON -#define HAS_I422TOARGB1555ROW_NEON -#define HAS_I422TOARGB4444ROW_NEON -#define HAS_I422TOARGBROW_NEON -#define HAS_I422TORGB24ROW_NEON -#define HAS_I422TORGB565ROW_NEON -#define HAS_I422TORGBAROW_NEON -#define HAS_I422TOUYVYROW_NEON -#define HAS_I422TOYUY2ROW_NEON -#define HAS_I444TOARGBROW_NEON -#define HAS_J400TOARGBROW_NEON -#define HAS_MERGEUVROW_NEON -#define HAS_MIRRORROW_NEON -#define HAS_MIRRORUVROW_NEON -#define HAS_NV12TOARGBROW_NEON -#define HAS_NV12TORGB565ROW_NEON -#define HAS_NV21TOARGBROW_NEON -#define HAS_RAWTOARGBROW_NEON -#define HAS_RAWTORGB24ROW_NEON -#define HAS_RAWTOUVROW_NEON -#define HAS_RAWTOYROW_NEON -#define HAS_RGB24TOARGBROW_NEON -#define HAS_RGB24TOUVROW_NEON -#define HAS_RGB24TOYROW_NEON -#define HAS_RGB565TOARGBROW_NEON -#define HAS_RGB565TOUVROW_NEON -#define HAS_RGB565TOYROW_NEON -#define HAS_RGBATOUVROW_NEON -#define HAS_RGBATOYROW_NEON -#define HAS_SETROW_NEON -#define HAS_SPLITUVROW_NEON -#define HAS_UYVYTOARGBROW_NEON -#define HAS_UYVYTOUV422ROW_NEON -#define HAS_UYVYTOUVROW_NEON -#define HAS_UYVYTOYROW_NEON -#define HAS_YUY2TOARGBROW_NEON -#define HAS_YUY2TOUV422ROW_NEON -#define HAS_YUY2TOUVROW_NEON -#define HAS_YUY2TOYROW_NEON - -// Effects: -#define HAS_ARGBADDROW_NEON -#define HAS_ARGBATTENUATEROW_NEON -#define HAS_ARGBBLENDROW_NEON -#define HAS_ARGBCOLORMATRIXROW_NEON -#define HAS_ARGBGRAYROW_NEON -#define HAS_ARGBMIRRORROW_NEON -#define HAS_ARGBMULTIPLYROW_NEON -#define HAS_ARGBQUANTIZEROW_NEON -#define HAS_ARGBSEPIAROW_NEON -#define HAS_ARGBSHADEROW_NEON -#define HAS_ARGBSHUFFLEROW_NEON -#define HAS_ARGBSUBTRACTROW_NEON -#define HAS_INTERPOLATEROW_NEON -#define HAS_SOBELROW_NEON -#define HAS_SOBELTOPLANEROW_NEON -#define HAS_SOBELXROW_NEON -#define HAS_SOBELXYROW_NEON -#define HAS_SOBELYROW_NEON -#endif - -// The following are available on Mips platforms: -#if !defined(LIBYUV_DISABLE_MIPS) && defined(__mips__) && \ - (_MIPS_SIM == _MIPS_SIM_ABI32) && (__mips_isa_rev < 6) -#define HAS_COPYROW_MIPS -#if defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_I422TOARGBROW_DSPR2 -#define HAS_INTERPOLATEROW_DSPR2 -#define HAS_MIRRORROW_DSPR2 -#define HAS_MIRRORUVROW_DSPR2 -#define HAS_SPLITUVROW_DSPR2 -#endif -#endif - -#if defined(_MSC_VER) && !defined(__CLR_VER) -#define SIMD_ALIGNED(var) __declspec(align(16)) var -#define SIMD_ALIGNED32(var) __declspec(align(64)) var -typedef __declspec(align(16)) int16 vec16[8]; -typedef __declspec(align(16)) int32 vec32[4]; -typedef __declspec(align(16)) int8 vec8[16]; -typedef __declspec(align(16)) uint16 uvec16[8]; -typedef __declspec(align(16)) uint32 uvec32[4]; -typedef __declspec(align(16)) uint8 uvec8[16]; -typedef __declspec(align(32)) int16 lvec16[16]; -typedef __declspec(align(32)) int32 lvec32[8]; -typedef __declspec(align(32)) int8 lvec8[32]; -typedef __declspec(align(32)) uint16 ulvec16[16]; -typedef __declspec(align(32)) uint32 ulvec32[8]; -typedef __declspec(align(32)) uint8 ulvec8[32]; -#elif defined(__GNUC__) && !defined(__pnacl__) -// Caveat GCC 4.2 to 4.7 have a known issue using vectors with const. -#define SIMD_ALIGNED(var) var __attribute__((aligned(16))) -#define SIMD_ALIGNED32(var) var __attribute__((aligned(64))) -typedef int16 __attribute__((vector_size(16))) vec16; -typedef int32 __attribute__((vector_size(16))) vec32; -typedef int8 __attribute__((vector_size(16))) vec8; -typedef uint16 __attribute__((vector_size(16))) uvec16; -typedef uint32 __attribute__((vector_size(16))) uvec32; -typedef uint8 __attribute__((vector_size(16))) uvec8; -typedef int16 __attribute__((vector_size(32))) lvec16; -typedef int32 __attribute__((vector_size(32))) lvec32; -typedef int8 __attribute__((vector_size(32))) lvec8; -typedef uint16 __attribute__((vector_size(32))) ulvec16; -typedef uint32 __attribute__((vector_size(32))) ulvec32; -typedef uint8 __attribute__((vector_size(32))) ulvec8; -#else -#define SIMD_ALIGNED(var) var -#define SIMD_ALIGNED32(var) var -typedef int16 vec16[8]; -typedef int32 vec32[4]; -typedef int8 vec8[16]; -typedef uint16 uvec16[8]; -typedef uint32 uvec32[4]; -typedef uint8 uvec8[16]; -typedef int16 lvec16[16]; -typedef int32 lvec32[8]; -typedef int8 lvec8[32]; -typedef uint16 ulvec16[16]; -typedef uint32 ulvec32[8]; -typedef uint8 ulvec8[32]; -#endif - -#if defined(__aarch64__) -// This struct is for Arm64 color conversion. -struct YuvConstants { - uvec16 kUVToRB; - uvec16 kUVToRB2; - uvec16 kUVToG; - uvec16 kUVToG2; - vec16 kUVBiasBGR; - vec32 kYToRgb; -}; -#elif defined(__arm__) -// This struct is for ArmV7 color conversion. -struct YuvConstants { - uvec8 kUVToRB; - uvec8 kUVToG; - vec16 kUVBiasBGR; - vec32 kYToRgb; -}; -#else -// This struct is for Intel color conversion. -struct YuvConstants { - lvec8 kUVToB; - lvec8 kUVToG; - lvec8 kUVToR; - lvec16 kUVBiasB; - lvec16 kUVBiasG; - lvec16 kUVBiasR; - lvec16 kYToRgb; -}; - -// Offsets into YuvConstants structure -#define KUVTOB 0 -#define KUVTOG 32 -#define KUVTOR 64 -#define KUVBIASB 96 -#define KUVBIASG 128 -#define KUVBIASR 160 -#define KYTORGB 192 -#endif - -// Conversion matrix for YUV to RGB -extern const struct YuvConstants kYuvI601Constants; // BT.601 -extern const struct YuvConstants kYuvJPEGConstants; // JPeg color space -extern const struct YuvConstants kYuvH709Constants; // BT.709 - -// Conversion matrix for YVU to BGR -extern const struct YuvConstants kYvuI601Constants; // BT.601 -extern const struct YuvConstants kYvuJPEGConstants; // JPeg color space -extern const struct YuvConstants kYvuH709Constants; // BT.709 - -#if defined(__APPLE__) || defined(__x86_64__) || defined(__llvm__) -#define OMITFP -#else -#define OMITFP __attribute__((optimize("omit-frame-pointer"))) -#endif - -// NaCL macros for GCC x86 and x64. -#if defined(__native_client__) -#define LABELALIGN ".p2align 5\n" -#else -#define LABELALIGN -#endif -#if defined(__native_client__) && defined(__x86_64__) -// r14 is used for MEMOP macros. -#define NACL_R14 "r14", -#define BUNDLELOCK ".bundle_lock\n" -#define BUNDLEUNLOCK ".bundle_unlock\n" -#define MEMACCESS(base) "%%nacl:(%%r15,%q" #base ")" -#define MEMACCESS2(offset, base) "%%nacl:" #offset "(%%r15,%q" #base ")" -#define MEMLEA(offset, base) #offset "(%q" #base ")" -#define MEMLEA3(offset, index, scale) \ - #offset "(,%q" #index "," #scale ")" -#define MEMLEA4(offset, base, index, scale) \ - #offset "(%q" #base ",%q" #index "," #scale ")" -#define MEMMOVESTRING(s, d) "%%nacl:(%q" #s "),%%nacl:(%q" #d "), %%r15" -#define MEMSTORESTRING(reg, d) "%%" #reg ",%%nacl:(%q" #d "), %%r15" -#define MEMOPREG(opcode, offset, base, index, scale, reg) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%%" #reg "\n" \ - BUNDLEUNLOCK -#define MEMOPMEM(opcode, reg, offset, base, index, scale) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " %%" #reg ",(%%r15,%%r14)\n" \ - BUNDLEUNLOCK -#define MEMOPARG(opcode, offset, base, index, scale, arg) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%" #arg "\n" \ - BUNDLEUNLOCK -#define VMEMOPREG(opcode, offset, base, index, scale, reg1, reg2) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%%" #reg1 ",%%" #reg2 "\n" \ - BUNDLEUNLOCK -#define VEXTOPMEM(op, sel, reg, offset, base, index, scale) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #op " $" #sel ",%%" #reg ",(%%r15,%%r14)\n" \ - BUNDLEUNLOCK -#else // defined(__native_client__) && defined(__x86_64__) -#define NACL_R14 -#define BUNDLEALIGN -#define MEMACCESS(base) "(%" #base ")" -#define MEMACCESS2(offset, base) #offset "(%" #base ")" -#define MEMLEA(offset, base) #offset "(%" #base ")" -#define MEMLEA3(offset, index, scale) \ - #offset "(,%" #index "," #scale ")" -#define MEMLEA4(offset, base, index, scale) \ - #offset "(%" #base ",%" #index "," #scale ")" -#define MEMMOVESTRING(s, d) -#define MEMSTORESTRING(reg, d) -#define MEMOPREG(opcode, offset, base, index, scale, reg) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%%" #reg "\n" -#define MEMOPMEM(opcode, reg, offset, base, index, scale) \ - #opcode " %%" #reg ","#offset "(%" #base ",%" #index "," #scale ")\n" -#define MEMOPARG(opcode, offset, base, index, scale, arg) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%" #arg "\n" -#define VMEMOPREG(opcode, offset, base, index, scale, reg1, reg2) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%%" #reg1 ",%%" \ - #reg2 "\n" -#define VEXTOPMEM(op, sel, reg, offset, base, index, scale) \ - #op " $" #sel ",%%" #reg ","#offset "(%" #base ",%" #index "," #scale ")\n" -#endif // defined(__native_client__) && defined(__x86_64__) - -#if defined(__arm__) || defined(__aarch64__) -#undef MEMACCESS -#if defined(__native_client__) -#define MEMACCESS(base) ".p2align 3\nbic %" #base ", #0xc0000000\n" -#else -#define MEMACCESS(base) -#endif -#endif - -void I444ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_NEON(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb1555, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_NEON(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_NEON(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_NEON(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void ARGBToYRow_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYRow_Any_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_SSSE3(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_SSSE3(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_SSSE3(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_SSSE3(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_SSSE3(const uint8* src_raw, uint8* dst_y, int width); -void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUVRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_NEON(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_NEON(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_NEON(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_NEON(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_NEON(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_NEON(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_NEON(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_NEON(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToYRow_NEON(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_NEON(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_NEON(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_NEON(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_NEON(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_NEON(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_NEON(const uint8* src_argb1555, uint8* dst_y, int width); -void ARGB4444ToYRow_NEON(const uint8* src_argb4444, uint8* dst_y, int width); -void ARGBToYRow_C(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_C(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_C(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_C(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_C(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_C(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_C(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_C(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_C(const uint8* src_argb1555, uint8* dst_y, int width); -void ARGB4444ToYRow_C(const uint8* src_argb4444, uint8* dst_y, int width); -void ARGBToYRow_Any_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_Any_SSSE3(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_Any_SSSE3(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_Any_SSSE3(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_Any_SSSE3(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_Any_SSSE3(const uint8* src_raw, uint8* dst_y, int width); -void ARGBToYRow_Any_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_NEON(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_Any_NEON(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_Any_NEON(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_Any_NEON(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_Any_NEON(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_Any_NEON(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_Any_NEON(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_Any_NEON(const uint8* src_argb1555, uint8* dst_y, - int width); -void ARGB4444ToYRow_Any_NEON(const uint8* src_argb4444, uint8* dst_y, - int width); - -void ARGBToUVRow_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_SSSE3(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_SSSE3(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_SSSE3(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_Any_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_Any_SSSE3(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_Any_SSSE3(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_Any_SSSE3(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV444Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUV411Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUVRow_Any_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_Any_NEON(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_Any_NEON(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_Any_NEON(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_Any_NEON(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_Any_NEON(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_Any_NEON(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_Any_NEON(const uint8* src_argb1555, - int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_Any_NEON(const uint8* src_argb4444, - int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_C(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_C(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_C(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_C(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_C(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_C(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_C(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_C(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_C(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_C(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); - -void ARGBToUV444Row_SSSE3(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV444Row_Any_SSSE3(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); - -void ARGBToUV444Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV411Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); - -void MirrorRow_AVX2(const uint8* src, uint8* dst, int width); -void MirrorRow_SSSE3(const uint8* src, uint8* dst, int width); -void MirrorRow_NEON(const uint8* src, uint8* dst, int width); -void MirrorRow_DSPR2(const uint8* src, uint8* dst, int width); -void MirrorRow_C(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_AVX2(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_SSSE3(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_SSE2(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_NEON(const uint8* src, uint8* dst, int width); - -void MirrorUVRow_SSSE3(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); - -void ARGBMirrorRow_AVX2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_SSE2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_NEON(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_C(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_AVX2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_SSE2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_NEON(const uint8* src, uint8* dst, int width); - -void SplitUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); -void SplitUVRow_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); - -void MergeUVRow_C(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_SSE2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_AVX2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_SSE2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_AVX2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); - -void CopyRow_SSE2(const uint8* src, uint8* dst, int count); -void CopyRow_AVX(const uint8* src, uint8* dst, int count); -void CopyRow_ERMS(const uint8* src, uint8* dst, int count); -void CopyRow_NEON(const uint8* src, uint8* dst, int count); -void CopyRow_MIPS(const uint8* src, uint8* dst, int count); -void CopyRow_C(const uint8* src, uint8* dst, int count); -void CopyRow_Any_SSE2(const uint8* src, uint8* dst, int count); -void CopyRow_Any_AVX(const uint8* src, uint8* dst, int count); -void CopyRow_Any_NEON(const uint8* src, uint8* dst, int count); - -void CopyRow_16_C(const uint16* src, uint16* dst, int count); - -void ARGBCopyAlphaRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBCopyAlphaRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); - -void ARGBExtractAlphaRow_C(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_SSE2(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_NEON(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_Any_SSE2(const uint8* src_argb, uint8* dst_a, - int width); -void ARGBExtractAlphaRow_Any_NEON(const uint8* src_argb, uint8* dst_a, - int width); - -void ARGBCopyYToAlphaRow_C(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, - int width); -void ARGBCopyYToAlphaRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, - int width); - -void SetRow_C(uint8* dst, uint8 v8, int count); -void SetRow_X86(uint8* dst, uint8 v8, int count); -void SetRow_ERMS(uint8* dst, uint8 v8, int count); -void SetRow_NEON(uint8* dst, uint8 v8, int count); -void SetRow_Any_X86(uint8* dst, uint8 v8, int count); -void SetRow_Any_NEON(uint8* dst, uint8 v8, int count); - -void ARGBSetRow_C(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_X86(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_NEON(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_Any_NEON(uint8* dst_argb, uint32 v32, int count); - -// ARGBShufflers for BGRAToARGB etc. -void ARGBShuffleRow_C(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_SSE2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_AVX2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_NEON(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_SSSE3(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_NEON(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); - -void RGB24ToARGBRow_SSSE3(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_SSSE3(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_SSSE3(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_SSE2(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_SSE2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_SSE2(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB565ToARGBRow_AVX2(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_AVX2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_AVX2(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void RGB24ToARGBRow_NEON(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_NEON(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_NEON(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_NEON(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_NEON(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_NEON(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB24ToARGBRow_C(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_C(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_C(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_C(const uint8* src_rgb, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGB4444ToARGBRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void RGB24ToARGBRow_Any_SSSE3(const uint8* src_rgb24, uint8* dst_argb, - int width); -void RAWToARGBRow_Any_SSSE3(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_Any_SSSE3(const uint8* src_raw, uint8* dst_rgb24, int width); - -void RGB565ToARGBRow_Any_SSE2(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_SSE2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_SSE2(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB565ToARGBRow_Any_AVX2(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_AVX2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_AVX2(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void RGB24ToARGBRow_Any_NEON(const uint8* src_rgb24, uint8* dst_argb, - int width); -void RAWToARGBRow_Any_NEON(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_Any_NEON(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_Any_NEON(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_NEON(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_NEON(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void ARGBToRGB24Row_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); - -void ARGBToRGB565DitherRow_C(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_SSE2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_AVX2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGB565Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); - -void ARGBToRGB24Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565DitherRow_NEON(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGBARow_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB24Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_C(const uint8* src_argb, uint8* dst_rgb, int width); - -void J400ToARGBRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_NEON(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_C(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_NEON(const uint8* src_y, uint8* dst_argb, int width); - -void I444ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_C(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_C(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_C(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_SSSE3(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_SSSE3(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_AVX2(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_AVX2(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_SSSE3(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_SSSE3(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_AVX2(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_AVX2(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void I400ToARGBRow_C(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_NEON(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_NEON(const uint8* src_y, uint8* dst_argb, int width); - -// ARGB preattenuated alpha blend. -void ARGBBlendRow_SSSE3(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBBlendRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBBlendRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// Unattenuated planar alpha blend. -void BlendPlaneRow_SSSE3(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_Any_SSSE3(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_AVX2(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_Any_AVX2(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_C(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); - -// ARGB multiply images. Same API as Blend, but these require -// pointer and width alignment for SSE2. -void ARGBMultiplyRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// ARGB add images. -void ARGBAddRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// ARGB subtract images. Same API as Blend, but these require -// pointer and width alignment for SSE2. -void ARGBSubtractRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -void ARGBToRGB24Row_Any_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_Any_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - int width); - -void ARGBToRGB565DitherRow_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGB565Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - int width); - -void ARGBToRGB24Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToRGB565DitherRow_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void I444ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - const uint8* src_a, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_NEON(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_NEON(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_DSPR2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_DSPR2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void YUY2ToYRow_AVX2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_AVX2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_AVX2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_SSE2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_SSE2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_SSE2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_NEON(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_NEON(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_NEON(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_C(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_C(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_C(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_AVX2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_AVX2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_AVX2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_SSE2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_SSE2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_SSE2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_NEON(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_NEON(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_NEON(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_SSE2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_SSE2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_SSE2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_NEON(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_NEON(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_NEON(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); - -void UYVYToYRow_C(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_C(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_C(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_SSE2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_SSE2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_SSE2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_NEON(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_NEON(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_NEON(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); - -void I422ToYUY2Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_Any_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_Any_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); - -// Effects related row functions. -void ARGBAttenuateRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_NEON(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_SSSE3(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_NEON(const uint8* src_argb, uint8* dst_argb, - int width); - -// Inverse table for unattenuate, shared by C and SSE2. -extern const uint32 fixed_invtbl8[256]; -void ARGBUnattenuateRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBUnattenuateRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); - -void ARGBGrayRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBGrayRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBGrayRow_NEON(const uint8* src_argb, uint8* dst_argb, int width); - -void ARGBSepiaRow_C(uint8* dst_argb, int width); -void ARGBSepiaRow_SSSE3(uint8* dst_argb, int width); -void ARGBSepiaRow_NEON(uint8* dst_argb, int width); - -void ARGBColorMatrixRow_C(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); -void ARGBColorMatrixRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); -void ARGBColorMatrixRow_NEON(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); - -void ARGBColorTableRow_C(uint8* dst_argb, const uint8* table_argb, int width); -void ARGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width); - -void RGBColorTableRow_C(uint8* dst_argb, const uint8* table_argb, int width); -void RGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width); - -void ARGBQuantizeRow_C(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); -void ARGBQuantizeRow_SSE2(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); -void ARGBQuantizeRow_NEON(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); - -void ARGBShadeRow_C(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); -void ARGBShadeRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); -void ARGBShadeRow_NEON(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); - -// Used for blur. -void CumulativeSumToAverageRow_SSE2(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, int count); -void ComputeCumulativeSumRow_SSE2(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width); - -void CumulativeSumToAverageRow_C(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, int count); -void ComputeCumulativeSumRow_C(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width); - -LIBYUV_API -void ARGBAffineRow_C(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); -LIBYUV_API -void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); - -// Used for I420Scale, ARGBScale, and ARGBInterpolate. -void InterpolateRow_C(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, - int width, int source_y_fraction); -void InterpolateRow_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_AVX2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_NEON(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_NEON(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_AVX2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); - -void InterpolateRow_16_C(uint16* dst_ptr, const uint16* src_ptr, - ptrdiff_t src_stride_ptr, - int width, int source_y_fraction); - -// Sobel images. -void SobelXRow_C(const uint8* src_y0, const uint8* src_y1, const uint8* src_y2, - uint8* dst_sobelx, int width); -void SobelXRow_SSE2(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width); -void SobelXRow_NEON(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width); -void SobelYRow_C(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelYRow_SSE2(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelYRow_NEON(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelToPlaneRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelXYRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelToPlaneRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelXYRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); - -void ARGBPolynomialRow_C(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); -void ARGBPolynomialRow_SSE2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); -void ARGBPolynomialRow_AVX2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); - -void ARGBLumaColorTableRow_C(const uint8* src_argb, uint8* dst_argb, int width, - const uint8* luma, uint32 lumacoeff); -void ARGBLumaColorTableRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - int width, - const uint8* luma, uint32 lumacoeff); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROW_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale.h deleted file mode 100644 index 102158d..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_H_ // NOLINT -#define INCLUDE_LIBYUV_SCALE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Supported filtering. -typedef enum FilterMode { - kFilterNone = 0, // Point sample; Fastest. - kFilterLinear = 1, // Filter horizontally only. - kFilterBilinear = 2, // Faster than box, but lower quality scaling down. - kFilterBox = 3 // Highest quality. -} FilterModeEnum; - -// Scale a YUV plane. -LIBYUV_API -void ScalePlane(const uint8* src, int src_stride, - int src_width, int src_height, - uint8* dst, int dst_stride, - int dst_width, int dst_height, - enum FilterMode filtering); - -LIBYUV_API -void ScalePlane_16(const uint16* src, int src_stride, - int src_width, int src_height, - uint16* dst, int dst_stride, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Scales a YUV 4:2:0 image from the src width and height to the -// dst width and height. -// If filtering is kFilterNone, a simple nearest-neighbor algorithm is -// used. This produces basic (blocky) quality at the fastest speed. -// If filtering is kFilterBilinear, interpolation is used to produce a better -// quality image, at the expense of speed. -// If filtering is kFilterBox, averaging is used to produce ever better -// quality image, at further expense of speed. -// Returns 0 if successful. - -LIBYUV_API -int I420Scale(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - int src_width, int src_height, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int dst_width, int dst_height, - enum FilterMode filtering); - -LIBYUV_API -int I420Scale_16(const uint16* src_y, int src_stride_y, - const uint16* src_u, int src_stride_u, - const uint16* src_v, int src_stride_v, - int src_width, int src_height, - uint16* dst_y, int dst_stride_y, - uint16* dst_u, int dst_stride_u, - uint16* dst_v, int dst_stride_v, - int dst_width, int dst_height, - enum FilterMode filtering); - -#ifdef __cplusplus -// Legacy API. Deprecated. -LIBYUV_API -int Scale(const uint8* src_y, const uint8* src_u, const uint8* src_v, - int src_stride_y, int src_stride_u, int src_stride_v, - int src_width, int src_height, - uint8* dst_y, uint8* dst_u, uint8* dst_v, - int dst_stride_y, int dst_stride_u, int dst_stride_v, - int dst_width, int dst_height, - LIBYUV_BOOL interpolate); - -// Legacy API. Deprecated. -LIBYUV_API -int ScaleOffset(const uint8* src_i420, int src_width, int src_height, - uint8* dst_i420, int dst_width, int dst_height, int dst_yoffset, - LIBYUV_BOOL interpolate); - -// For testing, allow disabling of specialized scalers. -LIBYUV_API -void SetUseReferenceImpl(LIBYUV_BOOL use); -#endif // __cplusplus - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale_argb.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale_argb.h deleted file mode 100644 index b56cf52..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale_argb.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_ARGB_H_ // NOLINT -#define INCLUDE_LIBYUV_SCALE_ARGB_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/scale.h" // For FilterMode - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -LIBYUV_API -int ARGBScale(const uint8* src_argb, int src_stride_argb, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Clipped scale takes destination rectangle coordinates for clip values. -LIBYUV_API -int ARGBScaleClip(const uint8* src_argb, int src_stride_argb, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering); - -// Scale with YUV conversion to ARGB and clipping. -LIBYUV_API -int YUVToARGBScaleClip(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint32 src_fourcc, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - uint32 dst_fourcc, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_ARGB_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale_row.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale_row.h deleted file mode 100644 index df699e6..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/scale_row.h +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_ROW_H_ // NOLINT -#define INCLUDE_LIBYUV_SCALE_ROW_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/scale.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif - -// GCC >= 4.7.0 required for AVX2. -#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) -#if (__GNUC__ > 4) || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 7)) -#define GCC_HAS_AVX2 1 -#endif // GNUC >= 4.7 -#endif // __GNUC__ - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -#define HAS_FIXEDDIV1_X86 -#define HAS_FIXEDDIV_X86 -#define HAS_SCALEARGBCOLS_SSE2 -#define HAS_SCALEARGBCOLSUP2_SSE2 -#define HAS_SCALEARGBFILTERCOLS_SSSE3 -#define HAS_SCALEARGBROWDOWN2_SSE2 -#define HAS_SCALEARGBROWDOWNEVEN_SSE2 -#define HAS_SCALECOLSUP2_SSE2 -#define HAS_SCALEFILTERCOLS_SSSE3 -#define HAS_SCALEROWDOWN2_SSSE3 -#define HAS_SCALEROWDOWN34_SSSE3 -#define HAS_SCALEROWDOWN38_SSSE3 -#define HAS_SCALEROWDOWN4_SSSE3 -#define HAS_SCALEADDROW_SSE2 -#endif - -// The following are available on all x86 platforms, but -// require VS2012, clang 3.4 or gcc 4.7. -// The code supports NaCL but requires a new compiler and validator. -#if !defined(LIBYUV_DISABLE_X86) && (defined(VISUALC_HAS_AVX2) || \ - defined(CLANG_HAS_AVX2) || defined(GCC_HAS_AVX2)) -#define HAS_SCALEADDROW_AVX2 -#define HAS_SCALEROWDOWN2_AVX2 -#define HAS_SCALEROWDOWN4_AVX2 -#endif - -// The following are available on Neon platforms: -#if !defined(LIBYUV_DISABLE_NEON) && !defined(__native_client__) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_SCALEARGBCOLS_NEON -#define HAS_SCALEARGBROWDOWN2_NEON -#define HAS_SCALEARGBROWDOWNEVEN_NEON -#define HAS_SCALEFILTERCOLS_NEON -#define HAS_SCALEROWDOWN2_NEON -#define HAS_SCALEROWDOWN34_NEON -#define HAS_SCALEROWDOWN38_NEON -#define HAS_SCALEROWDOWN4_NEON -#define HAS_SCALEARGBFILTERCOLS_NEON -#endif - -// The following are available on Mips platforms: -#if !defined(LIBYUV_DISABLE_MIPS) && !defined(__native_client__) && \ - defined(__mips__) && defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_SCALEROWDOWN2_DSPR2 -#define HAS_SCALEROWDOWN4_DSPR2 -#define HAS_SCALEROWDOWN34_DSPR2 -#define HAS_SCALEROWDOWN38_DSPR2 -#endif - -// Scale ARGB vertically with bilinear interpolation. -void ScalePlaneVertical(int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int y, int dy, - int bpp, enum FilterMode filtering); - -void ScalePlaneVertical_16(int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_argb, uint16* dst_argb, - int x, int y, int dy, - int wpp, enum FilterMode filtering); - -// Simplify the filtering based on scale factors. -enum FilterMode ScaleFilterReduce(int src_width, int src_height, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Divide num by div and return as 16.16 fixed point result. -int FixedDiv_C(int num, int div); -int FixedDiv_X86(int num, int div); -// Divide num - 1 by div - 1 and return as 16.16 fixed point result. -int FixedDiv1_C(int num, int div); -int FixedDiv1_X86(int num, int div); -#ifdef HAS_FIXEDDIV_X86 -#define FixedDiv FixedDiv_X86 -#define FixedDiv1 FixedDiv1_X86 -#else -#define FixedDiv FixedDiv_C -#define FixedDiv1 FixedDiv1_C -#endif - -// Compute slope values for stepping. -void ScaleSlope(int src_width, int src_height, - int dst_width, int dst_height, - enum FilterMode filtering, - int* x, int* y, int* dx, int* dy); - -void ScaleRowDown2_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown2Linear_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown2Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Odd_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown4_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown4Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown34_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown34_0_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_0_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* d, int dst_width); -void ScaleRowDown34_1_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_1_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* d, int dst_width); -void ScaleCols_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleCols_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleColsUp2_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int, int); -void ScaleColsUp2_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int, int); -void ScaleFilterCols_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols64_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols64_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleRowDown38_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown38_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown38_3_Box_C(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_16_C(const uint16* src_ptr, - ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); -void ScaleAddRow_C(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_16_C(const uint16* src_ptr, uint32* dst_ptr, int src_width); -void ScaleARGBRowDown2_C(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_C(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_C(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_C(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_C(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBCols_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols64_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBColsUp2_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int, int); -void ScaleARGBFilterCols_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols64_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); - -// Specialized scalers for x86. -void ScaleRowDown2_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown34_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Odd_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Odd_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown34_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleAddRow_SSE2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_SSE2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width); - -void ScaleFilterCols_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleColsUp2_SSE2(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - - -// ARGB Column functions -void ScaleARGBCols_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_SSSE3(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBColsUp2_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_Any_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols_Any_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); - -// ARGB Row functions -void ScaleARGBRowDown2_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2Linear_NEON(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_Any_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2Linear_Any_NEON(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); - -void ScaleARGBRowDownEven_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_Any_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_Any_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_Any_NEON(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); - -// ScaleRowDown2Box also used by planar functions -// NEON downscalers with interpolation. - -// Note - not static due to reuse in convert for 444 to 420. -void ScaleRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); - -void ScaleRowDown4_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -// Down scale from 4 to 3 pixels. Use the neon multilane read/write -// to load up the every 4th pixel into a 4 different registers. -// Point samples 32 pixels to 24 pixels. -void ScaleRowDown34_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -// 32 -> 12 -void ScaleRowDown38_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x3 -> 12x1 -void ScaleRowDown38_3_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x2 -> 12x1 -void ScaleRowDown38_2_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown2_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Odd_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32 -> 12 -void ScaleRowDown38_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x3 -> 12x1 -void ScaleRowDown38_3_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x2 -> 12x1 -void ScaleRowDown38_2_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleAddRow_NEON(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_NEON(const uint8* src_ptr, uint16* dst_ptr, int src_width); - -void ScaleFilterCols_NEON(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - -void ScaleFilterCols_Any_NEON(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - -void ScaleRowDown2_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_0_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_1_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown38_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown38_2_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_ROW_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/version.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/version.h deleted file mode 100644 index 3a6bbe3..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/version.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_VERSION_H_ // NOLINT -#define INCLUDE_LIBYUV_VERSION_H_ - -#define LIBYUV_VERSION 1597 - -#endif // INCLUDE_LIBYUV_VERSION_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/include/libyuv/video_common.h b/telegramgallery/src/main/cpp/libyuv/include/libyuv/video_common.h deleted file mode 100644 index ad934e4..0000000 --- a/telegramgallery/src/main/cpp/libyuv/include/libyuv/video_common.h +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -// Common definitions for video, including fourcc and VideoFormat. - -#ifndef INCLUDE_LIBYUV_VIDEO_COMMON_H_ // NOLINT -#define INCLUDE_LIBYUV_VIDEO_COMMON_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -////////////////////////////////////////////////////////////////////////////// -// Definition of FourCC codes -////////////////////////////////////////////////////////////////////////////// - -// Convert four characters to a FourCC code. -// Needs to be a macro otherwise the OS X compiler complains when the kFormat* -// constants are used in a switch. -#ifdef __cplusplus -#define FOURCC(a, b, c, d) ( \ - (static_cast(a)) | (static_cast(b) << 8) | \ - (static_cast(c) << 16) | (static_cast(d) << 24)) -#else -#define FOURCC(a, b, c, d) ( \ - ((uint32)(a)) | ((uint32)(b) << 8) | /* NOLINT */ \ - ((uint32)(c) << 16) | ((uint32)(d) << 24)) /* NOLINT */ -#endif - -// Some pages discussing FourCC codes: -// http://www.fourcc.org/yuv.php -// http://v4l2spec.bytesex.org/spec/book1.htm -// http://developer.apple.com/quicktime/icefloe/dispatch020.html -// http://msdn.microsoft.com/library/windows/desktop/dd206750.aspx#nv12 -// http://people.xiph.org/~xiphmont/containers/nut/nut4cc.txt - -// FourCC codes grouped according to implementation efficiency. -// Primary formats should convert in 1 efficient step. -// Secondary formats are converted in 2 steps. -// Auxilliary formats call primary converters. -enum FourCC { - // 9 Primary YUV formats: 5 planar, 2 biplanar, 2 packed. - FOURCC_I420 = FOURCC('I', '4', '2', '0'), - FOURCC_I422 = FOURCC('I', '4', '2', '2'), - FOURCC_I444 = FOURCC('I', '4', '4', '4'), - FOURCC_I411 = FOURCC('I', '4', '1', '1'), - FOURCC_I400 = FOURCC('I', '4', '0', '0'), - FOURCC_NV21 = FOURCC('N', 'V', '2', '1'), - FOURCC_NV12 = FOURCC('N', 'V', '1', '2'), - FOURCC_YUY2 = FOURCC('Y', 'U', 'Y', '2'), - FOURCC_UYVY = FOURCC('U', 'Y', 'V', 'Y'), - - // 2 Secondary YUV formats: row biplanar. - FOURCC_M420 = FOURCC('M', '4', '2', '0'), - FOURCC_Q420 = FOURCC('Q', '4', '2', '0'), // deprecated. - - // 9 Primary RGB formats: 4 32 bpp, 2 24 bpp, 3 16 bpp. - FOURCC_ARGB = FOURCC('A', 'R', 'G', 'B'), - FOURCC_BGRA = FOURCC('B', 'G', 'R', 'A'), - FOURCC_ABGR = FOURCC('A', 'B', 'G', 'R'), - FOURCC_24BG = FOURCC('2', '4', 'B', 'G'), - FOURCC_RAW = FOURCC('r', 'a', 'w', ' '), - FOURCC_RGBA = FOURCC('R', 'G', 'B', 'A'), - FOURCC_RGBP = FOURCC('R', 'G', 'B', 'P'), // rgb565 LE. - FOURCC_RGBO = FOURCC('R', 'G', 'B', 'O'), // argb1555 LE. - FOURCC_R444 = FOURCC('R', '4', '4', '4'), // argb4444 LE. - - // 4 Secondary RGB formats: 4 Bayer Patterns. deprecated. - FOURCC_RGGB = FOURCC('R', 'G', 'G', 'B'), - FOURCC_BGGR = FOURCC('B', 'G', 'G', 'R'), - FOURCC_GRBG = FOURCC('G', 'R', 'B', 'G'), - FOURCC_GBRG = FOURCC('G', 'B', 'R', 'G'), - - // 1 Primary Compressed YUV format. - FOURCC_MJPG = FOURCC('M', 'J', 'P', 'G'), - - // 5 Auxiliary YUV variations: 3 with U and V planes are swapped, 1 Alias. - FOURCC_YV12 = FOURCC('Y', 'V', '1', '2'), - FOURCC_YV16 = FOURCC('Y', 'V', '1', '6'), - FOURCC_YV24 = FOURCC('Y', 'V', '2', '4'), - FOURCC_YU12 = FOURCC('Y', 'U', '1', '2'), // Linux version of I420. - FOURCC_J420 = FOURCC('J', '4', '2', '0'), - FOURCC_J400 = FOURCC('J', '4', '0', '0'), // unofficial fourcc - FOURCC_H420 = FOURCC('H', '4', '2', '0'), // unofficial fourcc - - // 14 Auxiliary aliases. CanonicalFourCC() maps these to canonical fourcc. - FOURCC_IYUV = FOURCC('I', 'Y', 'U', 'V'), // Alias for I420. - FOURCC_YU16 = FOURCC('Y', 'U', '1', '6'), // Alias for I422. - FOURCC_YU24 = FOURCC('Y', 'U', '2', '4'), // Alias for I444. - FOURCC_YUYV = FOURCC('Y', 'U', 'Y', 'V'), // Alias for YUY2. - FOURCC_YUVS = FOURCC('y', 'u', 'v', 's'), // Alias for YUY2 on Mac. - FOURCC_HDYC = FOURCC('H', 'D', 'Y', 'C'), // Alias for UYVY. - FOURCC_2VUY = FOURCC('2', 'v', 'u', 'y'), // Alias for UYVY on Mac. - FOURCC_JPEG = FOURCC('J', 'P', 'E', 'G'), // Alias for MJPG. - FOURCC_DMB1 = FOURCC('d', 'm', 'b', '1'), // Alias for MJPG on Mac. - FOURCC_BA81 = FOURCC('B', 'A', '8', '1'), // Alias for BGGR. - FOURCC_RGB3 = FOURCC('R', 'G', 'B', '3'), // Alias for RAW. - FOURCC_BGR3 = FOURCC('B', 'G', 'R', '3'), // Alias for 24BG. - FOURCC_CM32 = FOURCC(0, 0, 0, 32), // Alias for BGRA kCMPixelFormat_32ARGB - FOURCC_CM24 = FOURCC(0, 0, 0, 24), // Alias for RAW kCMPixelFormat_24RGB - FOURCC_L555 = FOURCC('L', '5', '5', '5'), // Alias for RGBO. - FOURCC_L565 = FOURCC('L', '5', '6', '5'), // Alias for RGBP. - FOURCC_5551 = FOURCC('5', '5', '5', '1'), // Alias for RGBO. - - // 1 Auxiliary compressed YUV format set aside for capturer. - FOURCC_H264 = FOURCC('H', '2', '6', '4'), - - // Match any fourcc. - FOURCC_ANY = -1, -}; - -enum FourCCBpp { - // Canonical fourcc codes used in our code. - FOURCC_BPP_I420 = 12, - FOURCC_BPP_I422 = 16, - FOURCC_BPP_I444 = 24, - FOURCC_BPP_I411 = 12, - FOURCC_BPP_I400 = 8, - FOURCC_BPP_NV21 = 12, - FOURCC_BPP_NV12 = 12, - FOURCC_BPP_YUY2 = 16, - FOURCC_BPP_UYVY = 16, - FOURCC_BPP_M420 = 12, - FOURCC_BPP_Q420 = 12, - FOURCC_BPP_ARGB = 32, - FOURCC_BPP_BGRA = 32, - FOURCC_BPP_ABGR = 32, - FOURCC_BPP_RGBA = 32, - FOURCC_BPP_24BG = 24, - FOURCC_BPP_RAW = 24, - FOURCC_BPP_RGBP = 16, - FOURCC_BPP_RGBO = 16, - FOURCC_BPP_R444 = 16, - FOURCC_BPP_RGGB = 8, - FOURCC_BPP_BGGR = 8, - FOURCC_BPP_GRBG = 8, - FOURCC_BPP_GBRG = 8, - FOURCC_BPP_YV12 = 12, - FOURCC_BPP_YV16 = 16, - FOURCC_BPP_YV24 = 24, - FOURCC_BPP_YU12 = 12, - FOURCC_BPP_J420 = 12, - FOURCC_BPP_J400 = 8, - FOURCC_BPP_H420 = 12, - FOURCC_BPP_MJPG = 0, // 0 means unknown. - FOURCC_BPP_H264 = 0, - FOURCC_BPP_IYUV = 12, - FOURCC_BPP_YU16 = 16, - FOURCC_BPP_YU24 = 24, - FOURCC_BPP_YUYV = 16, - FOURCC_BPP_YUVS = 16, - FOURCC_BPP_HDYC = 16, - FOURCC_BPP_2VUY = 16, - FOURCC_BPP_JPEG = 1, - FOURCC_BPP_DMB1 = 1, - FOURCC_BPP_BA81 = 8, - FOURCC_BPP_RGB3 = 24, - FOURCC_BPP_BGR3 = 24, - FOURCC_BPP_CM32 = 32, - FOURCC_BPP_CM24 = 24, - - // Match any fourcc. - FOURCC_BPP_ANY = 0, // 0 means unknown. -}; - -// Converts fourcc aliases into canonical ones. -LIBYUV_API uint32 CanonicalFourCC(uint32 fourcc); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_VIDEO_COMMON_H_ NOLINT diff --git a/telegramgallery/src/main/cpp/libyuv/source/compare.cc b/telegramgallery/src/main/cpp/libyuv/source/compare.cc deleted file mode 100644 index e3846bd..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/compare.cc +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/compare.h" - -#include -#include -#ifdef _OPENMP -#include -#endif - -#include "libyuv/basic_types.h" -#include "libyuv/compare_row.h" -#include "libyuv/cpu_id.h" -#include "libyuv/row.h" -#include "libyuv/video_common.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// hash seed of 5381 recommended. -LIBYUV_API -uint32 HashDjb2(const uint8* src, uint64 count, uint32 seed) { - const int kBlockSize = 1 << 15; // 32768; - int remainder; - uint32 (*HashDjb2_SSE)(const uint8* src, int count, uint32 seed) = - HashDjb2_C; -#if defined(HAS_HASHDJB2_SSE41) - if (TestCpuFlag(kCpuHasSSE41)) { - HashDjb2_SSE = HashDjb2_SSE41; - } -#endif -#if defined(HAS_HASHDJB2_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - HashDjb2_SSE = HashDjb2_AVX2; - } -#endif - - while (count >= (uint64)(kBlockSize)) { - seed = HashDjb2_SSE(src, kBlockSize, seed); - src += kBlockSize; - count -= kBlockSize; - } - remainder = (int)(count) & ~15; - if (remainder) { - seed = HashDjb2_SSE(src, remainder, seed); - src += remainder; - count -= remainder; - } - remainder = (int)(count) & 15; - if (remainder) { - seed = HashDjb2_C(src, remainder, seed); - } - return seed; -} - -static uint32 ARGBDetectRow_C(const uint8* argb, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - if (argb[0] != 255) { // First byte is not Alpha of 255, so not ARGB. - return FOURCC_BGRA; - } - if (argb[3] != 255) { // 4th byte is not Alpha of 255, so not BGRA. - return FOURCC_ARGB; - } - if (argb[4] != 255) { // Second pixel first byte is not Alpha of 255. - return FOURCC_BGRA; - } - if (argb[7] != 255) { // Second pixel 4th byte is not Alpha of 255. - return FOURCC_ARGB; - } - argb += 8; - } - if (width & 1) { - if (argb[0] != 255) { // First byte is not Alpha of 255, so not ARGB. - return FOURCC_BGRA; - } - if (argb[3] != 255) { // 4th byte is not Alpha of 255, so not BGRA. - return FOURCC_ARGB; - } - } - return 0; -} - -// Scan an opaque argb image and return fourcc based on alpha offset. -// Returns FOURCC_ARGB, FOURCC_BGRA, or 0 if unknown. -LIBYUV_API -uint32 ARGBDetect(const uint8* argb, int stride_argb, int width, int height) { - uint32 fourcc = 0; - int h; - - // Coalesce rows. - if (stride_argb == width * 4) { - width *= height; - height = 1; - stride_argb = 0; - } - for (h = 0; h < height && fourcc == 0; ++h) { - fourcc = ARGBDetectRow_C(argb, width); - argb += stride_argb; - } - return fourcc; -} - -// TODO(fbarchard): Refactor into row function. -LIBYUV_API -uint64 ComputeSumSquareError(const uint8* src_a, const uint8* src_b, - int count) { - // SumSquareError returns values 0 to 65535 for each squared difference. - // Up to 65536 of those can be summed and remain within a uint32. - // After each block of 65536 pixels, accumulate into a uint64. - const int kBlockSize = 65536; - int remainder = count & (kBlockSize - 1) & ~31; - uint64 sse = 0; - int i; - uint32 (*SumSquareError)(const uint8* src_a, const uint8* src_b, int count) = - SumSquareError_C; -#if defined(HAS_SUMSQUAREERROR_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SumSquareError = SumSquareError_NEON; - } -#endif -#if defined(HAS_SUMSQUAREERROR_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - // Note only used for multiples of 16 so count is not checked. - SumSquareError = SumSquareError_SSE2; - } -#endif -#if defined(HAS_SUMSQUAREERROR_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - // Note only used for multiples of 32 so count is not checked. - SumSquareError = SumSquareError_AVX2; - } -#endif -#ifdef _OPENMP -#pragma omp parallel for reduction(+: sse) -#endif - for (i = 0; i < (count - (kBlockSize - 1)); i += kBlockSize) { - sse += SumSquareError(src_a + i, src_b + i, kBlockSize); - } - src_a += count & ~(kBlockSize - 1); - src_b += count & ~(kBlockSize - 1); - if (remainder) { - sse += SumSquareError(src_a, src_b, remainder); - src_a += remainder; - src_b += remainder; - } - remainder = count & 31; - if (remainder) { - sse += SumSquareError_C(src_a, src_b, remainder); - } - return sse; -} - -LIBYUV_API -uint64 ComputeSumSquareErrorPlane(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height) { - uint64 sse = 0; - int h; - // Coalesce rows. - if (stride_a == width && - stride_b == width) { - width *= height; - height = 1; - stride_a = stride_b = 0; - } - for (h = 0; h < height; ++h) { - sse += ComputeSumSquareError(src_a, src_b, width); - src_a += stride_a; - src_b += stride_b; - } - return sse; -} - -LIBYUV_API -double SumSquareErrorToPsnr(uint64 sse, uint64 count) { - double psnr; - if (sse > 0) { - double mse = (double)(count) / (double)(sse); - psnr = 10.0 * log10(255.0 * 255.0 * mse); - } else { - psnr = kMaxPsnr; // Limit to prevent divide by 0 - } - - if (psnr > kMaxPsnr) - psnr = kMaxPsnr; - - return psnr; -} - -LIBYUV_API -double CalcFramePsnr(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height) { - const uint64 samples = width * height; - const uint64 sse = ComputeSumSquareErrorPlane(src_a, stride_a, - src_b, stride_b, - width, height); - return SumSquareErrorToPsnr(sse, samples); -} - -LIBYUV_API -double I420Psnr(const uint8* src_y_a, int stride_y_a, - const uint8* src_u_a, int stride_u_a, - const uint8* src_v_a, int stride_v_a, - const uint8* src_y_b, int stride_y_b, - const uint8* src_u_b, int stride_u_b, - const uint8* src_v_b, int stride_v_b, - int width, int height) { - const uint64 sse_y = ComputeSumSquareErrorPlane(src_y_a, stride_y_a, - src_y_b, stride_y_b, - width, height); - const int width_uv = (width + 1) >> 1; - const int height_uv = (height + 1) >> 1; - const uint64 sse_u = ComputeSumSquareErrorPlane(src_u_a, stride_u_a, - src_u_b, stride_u_b, - width_uv, height_uv); - const uint64 sse_v = ComputeSumSquareErrorPlane(src_v_a, stride_v_a, - src_v_b, stride_v_b, - width_uv, height_uv); - const uint64 samples = width * height + 2 * (width_uv * height_uv); - const uint64 sse = sse_y + sse_u + sse_v; - return SumSquareErrorToPsnr(sse, samples); -} - -static const int64 cc1 = 26634; // (64^2*(.01*255)^2 -static const int64 cc2 = 239708; // (64^2*(.03*255)^2 - -static double Ssim8x8_C(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b) { - int64 sum_a = 0; - int64 sum_b = 0; - int64 sum_sq_a = 0; - int64 sum_sq_b = 0; - int64 sum_axb = 0; - - int i; - for (i = 0; i < 8; ++i) { - int j; - for (j = 0; j < 8; ++j) { - sum_a += src_a[j]; - sum_b += src_b[j]; - sum_sq_a += src_a[j] * src_a[j]; - sum_sq_b += src_b[j] * src_b[j]; - sum_axb += src_a[j] * src_b[j]; - } - - src_a += stride_a; - src_b += stride_b; - } - - { - const int64 count = 64; - // scale the constants by number of pixels - const int64 c1 = (cc1 * count * count) >> 12; - const int64 c2 = (cc2 * count * count) >> 12; - - const int64 sum_a_x_sum_b = sum_a * sum_b; - - const int64 ssim_n = (2 * sum_a_x_sum_b + c1) * - (2 * count * sum_axb - 2 * sum_a_x_sum_b + c2); - - const int64 sum_a_sq = sum_a*sum_a; - const int64 sum_b_sq = sum_b*sum_b; - - const int64 ssim_d = (sum_a_sq + sum_b_sq + c1) * - (count * sum_sq_a - sum_a_sq + - count * sum_sq_b - sum_b_sq + c2); - - if (ssim_d == 0.0) { - return DBL_MAX; - } - return ssim_n * 1.0 / ssim_d; - } -} - -// We are using a 8x8 moving window with starting location of each 8x8 window -// on the 4x4 pixel grid. Such arrangement allows the windows to overlap -// block boundaries to penalize blocking artifacts. -LIBYUV_API -double CalcFrameSsim(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height) { - int samples = 0; - double ssim_total = 0; - double (*Ssim8x8)(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b) = Ssim8x8_C; - - // sample point start with each 4x4 location - int i; - for (i = 0; i < height - 8; i += 4) { - int j; - for (j = 0; j < width - 8; j += 4) { - ssim_total += Ssim8x8(src_a + j, stride_a, src_b + j, stride_b); - samples++; - } - - src_a += stride_a * 4; - src_b += stride_b * 4; - } - - ssim_total /= samples; - return ssim_total; -} - -LIBYUV_API -double I420Ssim(const uint8* src_y_a, int stride_y_a, - const uint8* src_u_a, int stride_u_a, - const uint8* src_v_a, int stride_v_a, - const uint8* src_y_b, int stride_y_b, - const uint8* src_u_b, int stride_u_b, - const uint8* src_v_b, int stride_v_b, - int width, int height) { - const double ssim_y = CalcFrameSsim(src_y_a, stride_y_a, - src_y_b, stride_y_b, width, height); - const int width_uv = (width + 1) >> 1; - const int height_uv = (height + 1) >> 1; - const double ssim_u = CalcFrameSsim(src_u_a, stride_u_a, - src_u_b, stride_u_b, - width_uv, height_uv); - const double ssim_v = CalcFrameSsim(src_v_a, stride_v_a, - src_v_b, stride_v_b, - width_uv, height_uv); - return ssim_y * 0.8 + 0.1 * (ssim_u + ssim_v); -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/compare_common.cc b/telegramgallery/src/main/cpp/libyuv/source/compare_common.cc deleted file mode 100644 index 42fc589..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/compare_common.cc +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/basic_types.h" - -#include "libyuv/compare_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -uint32 SumSquareError_C(const uint8* src_a, const uint8* src_b, int count) { - uint32 sse = 0u; - int i; - for (i = 0; i < count; ++i) { - int diff = src_a[i] - src_b[i]; - sse += (uint32)(diff * diff); - } - return sse; -} - -// hash seed of 5381 recommended. -// Internal C version of HashDjb2 with int sized count for efficiency. -uint32 HashDjb2_C(const uint8* src, int count, uint32 seed) { - uint32 hash = seed; - int i; - for (i = 0; i < count; ++i) { - hash += (hash << 5) + src[i]; - } - return hash; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/compare_gcc.cc b/telegramgallery/src/main/cpp/libyuv/source/compare_gcc.cc deleted file mode 100644 index 1b83edb..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/compare_gcc.cc +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/basic_types.h" - -#include "libyuv/compare_row.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC x86 and x64. -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) && !defined(_MSC_VER))) - -uint32 SumSquareError_SSE2(const uint8* src_a, const uint8* src_b, int count) { - uint32 sse; - asm volatile ( - "pxor %%xmm0,%%xmm0 \n" - "pxor %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm1 \n" - "lea " MEMLEA(0x10, 0) ",%0 \n" - "movdqu " MEMACCESS(1) ",%%xmm2 \n" - "lea " MEMLEA(0x10, 1) ",%1 \n" - "movdqa %%xmm1,%%xmm3 \n" - "psubusb %%xmm2,%%xmm1 \n" - "psubusb %%xmm3,%%xmm2 \n" - "por %%xmm2,%%xmm1 \n" - "movdqa %%xmm1,%%xmm2 \n" - "punpcklbw %%xmm5,%%xmm1 \n" - "punpckhbw %%xmm5,%%xmm2 \n" - "pmaddwd %%xmm1,%%xmm1 \n" - "pmaddwd %%xmm2,%%xmm2 \n" - "paddd %%xmm1,%%xmm0 \n" - "paddd %%xmm2,%%xmm0 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - - "pshufd $0xee,%%xmm0,%%xmm1 \n" - "paddd %%xmm1,%%xmm0 \n" - "pshufd $0x1,%%xmm0,%%xmm1 \n" - "paddd %%xmm1,%%xmm0 \n" - "movd %%xmm0,%3 \n" - - : "+r"(src_a), // %0 - "+r"(src_b), // %1 - "+r"(count), // %2 - "=g"(sse) // %3 - :: "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); - return sse; -} - -static uvec32 kHash16x33 = { 0x92d9e201, 0, 0, 0 }; // 33 ^ 16 -static uvec32 kHashMul0 = { - 0x0c3525e1, // 33 ^ 15 - 0xa3476dc1, // 33 ^ 14 - 0x3b4039a1, // 33 ^ 13 - 0x4f5f0981, // 33 ^ 12 -}; -static uvec32 kHashMul1 = { - 0x30f35d61, // 33 ^ 11 - 0x855cb541, // 33 ^ 10 - 0x040a9121, // 33 ^ 9 - 0x747c7101, // 33 ^ 8 -}; -static uvec32 kHashMul2 = { - 0xec41d4e1, // 33 ^ 7 - 0x4cfa3cc1, // 33 ^ 6 - 0x025528a1, // 33 ^ 5 - 0x00121881, // 33 ^ 4 -}; -static uvec32 kHashMul3 = { - 0x00008c61, // 33 ^ 3 - 0x00000441, // 33 ^ 2 - 0x00000021, // 33 ^ 1 - 0x00000001, // 33 ^ 0 -}; - -uint32 HashDjb2_SSE41(const uint8* src, int count, uint32 seed) { - uint32 hash; - asm volatile ( - "movd %2,%%xmm0 \n" - "pxor %%xmm7,%%xmm7 \n" - "movdqa %4,%%xmm6 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm1 \n" - "lea " MEMLEA(0x10, 0) ",%0 \n" - "pmulld %%xmm6,%%xmm0 \n" - "movdqa %5,%%xmm5 \n" - "movdqa %%xmm1,%%xmm2 \n" - "punpcklbw %%xmm7,%%xmm2 \n" - "movdqa %%xmm2,%%xmm3 \n" - "punpcklwd %%xmm7,%%xmm3 \n" - "pmulld %%xmm5,%%xmm3 \n" - "movdqa %6,%%xmm5 \n" - "movdqa %%xmm2,%%xmm4 \n" - "punpckhwd %%xmm7,%%xmm4 \n" - "pmulld %%xmm5,%%xmm4 \n" - "movdqa %7,%%xmm5 \n" - "punpckhbw %%xmm7,%%xmm1 \n" - "movdqa %%xmm1,%%xmm2 \n" - "punpcklwd %%xmm7,%%xmm2 \n" - "pmulld %%xmm5,%%xmm2 \n" - "movdqa %8,%%xmm5 \n" - "punpckhwd %%xmm7,%%xmm1 \n" - "pmulld %%xmm5,%%xmm1 \n" - "paddd %%xmm4,%%xmm3 \n" - "paddd %%xmm2,%%xmm1 \n" - "paddd %%xmm3,%%xmm1 \n" - "pshufd $0xe,%%xmm1,%%xmm2 \n" - "paddd %%xmm2,%%xmm1 \n" - "pshufd $0x1,%%xmm1,%%xmm2 \n" - "paddd %%xmm2,%%xmm1 \n" - "paddd %%xmm1,%%xmm0 \n" - "sub $0x10,%1 \n" - "jg 1b \n" - "movd %%xmm0,%3 \n" - : "+r"(src), // %0 - "+r"(count), // %1 - "+rm"(seed), // %2 - "=g"(hash) // %3 - : "m"(kHash16x33), // %4 - "m"(kHashMul0), // %5 - "m"(kHashMul1), // %6 - "m"(kHashMul2), // %7 - "m"(kHashMul3) // %8 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); - return hash; -} -#endif // defined(__x86_64__) || (defined(__i386__) && !defined(__pic__))) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - diff --git a/telegramgallery/src/main/cpp/libyuv/source/compare_neon.cc b/telegramgallery/src/main/cpp/libyuv/source/compare_neon.cc deleted file mode 100644 index 49aa3b4..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/compare_neon.cc +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/basic_types.h" - -#include "libyuv/compare_row.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if !defined(LIBYUV_DISABLE_NEON) && defined(__ARM_NEON__) && \ - !defined(__aarch64__) - -uint32 SumSquareError_NEON(const uint8* src_a, const uint8* src_b, int count) { - volatile uint32 sse; - asm volatile ( - "vmov.u8 q8, #0 \n" - "vmov.u8 q10, #0 \n" - "vmov.u8 q9, #0 \n" - "vmov.u8 q11, #0 \n" - - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" - MEMACCESS(1) - "vld1.8 {q1}, [%1]! \n" - "subs %2, %2, #16 \n" - "vsubl.u8 q2, d0, d2 \n" - "vsubl.u8 q3, d1, d3 \n" - "vmlal.s16 q8, d4, d4 \n" - "vmlal.s16 q9, d6, d6 \n" - "vmlal.s16 q10, d5, d5 \n" - "vmlal.s16 q11, d7, d7 \n" - "bgt 1b \n" - - "vadd.u32 q8, q8, q9 \n" - "vadd.u32 q10, q10, q11 \n" - "vadd.u32 q11, q8, q10 \n" - "vpaddl.u32 q1, q11 \n" - "vadd.u64 d0, d2, d3 \n" - "vmov.32 %3, d0[0] \n" - : "+r"(src_a), - "+r"(src_b), - "+r"(count), - "=r"(sse) - : - : "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11"); - return sse; -} - -#endif // defined(__ARM_NEON__) && !defined(__aarch64__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/compare_neon64.cc b/telegramgallery/src/main/cpp/libyuv/source/compare_neon64.cc deleted file mode 100644 index f9c7df9..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/compare_neon64.cc +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/basic_types.h" - -#include "libyuv/compare_row.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) - -uint32 SumSquareError_NEON(const uint8* src_a, const uint8* src_b, int count) { - volatile uint32 sse; - asm volatile ( - "eor v16.16b, v16.16b, v16.16b \n" - "eor v18.16b, v18.16b, v18.16b \n" - "eor v17.16b, v17.16b, v17.16b \n" - "eor v19.16b, v19.16b, v19.16b \n" - - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" - MEMACCESS(1) - "ld1 {v1.16b}, [%1], #16 \n" - "subs %w2, %w2, #16 \n" - "usubl v2.8h, v0.8b, v1.8b \n" - "usubl2 v3.8h, v0.16b, v1.16b \n" - "smlal v16.4s, v2.4h, v2.4h \n" - "smlal v17.4s, v3.4h, v3.4h \n" - "smlal2 v18.4s, v2.8h, v2.8h \n" - "smlal2 v19.4s, v3.8h, v3.8h \n" - "b.gt 1b \n" - - "add v16.4s, v16.4s, v17.4s \n" - "add v18.4s, v18.4s, v19.4s \n" - "add v19.4s, v16.4s, v18.4s \n" - "addv s0, v19.4s \n" - "fmov %w3, s0 \n" - : "+r"(src_a), - "+r"(src_b), - "+r"(count), - "=r"(sse) - : - : "cc", "v0", "v1", "v2", "v3", "v16", "v17", "v18", "v19"); - return sse; -} - -#endif // !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/compare_win.cc b/telegramgallery/src/main/cpp/libyuv/source/compare_win.cc deleted file mode 100644 index dc86fe2..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/compare_win.cc +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/basic_types.h" - -#include "libyuv/compare_row.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for 32 bit Visual C x86 and clangcl -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) - -__declspec(naked) -uint32 SumSquareError_SSE2(const uint8* src_a, const uint8* src_b, int count) { - __asm { - mov eax, [esp + 4] // src_a - mov edx, [esp + 8] // src_b - mov ecx, [esp + 12] // count - pxor xmm0, xmm0 - pxor xmm5, xmm5 - - wloop: - movdqu xmm1, [eax] - lea eax, [eax + 16] - movdqu xmm2, [edx] - lea edx, [edx + 16] - movdqa xmm3, xmm1 // abs trick - psubusb xmm1, xmm2 - psubusb xmm2, xmm3 - por xmm1, xmm2 - movdqa xmm2, xmm1 - punpcklbw xmm1, xmm5 - punpckhbw xmm2, xmm5 - pmaddwd xmm1, xmm1 - pmaddwd xmm2, xmm2 - paddd xmm0, xmm1 - paddd xmm0, xmm2 - sub ecx, 16 - jg wloop - - pshufd xmm1, xmm0, 0xee - paddd xmm0, xmm1 - pshufd xmm1, xmm0, 0x01 - paddd xmm0, xmm1 - movd eax, xmm0 - ret - } -} - -// Visual C 2012 required for AVX2. -#if _MSC_VER >= 1700 -// C4752: found Intel(R) Advanced Vector Extensions; consider using /arch:AVX. -#pragma warning(disable: 4752) -__declspec(naked) -uint32 SumSquareError_AVX2(const uint8* src_a, const uint8* src_b, int count) { - __asm { - mov eax, [esp + 4] // src_a - mov edx, [esp + 8] // src_b - mov ecx, [esp + 12] // count - vpxor ymm0, ymm0, ymm0 // sum - vpxor ymm5, ymm5, ymm5 // constant 0 for unpck - sub edx, eax - - wloop: - vmovdqu ymm1, [eax] - vmovdqu ymm2, [eax + edx] - lea eax, [eax + 32] - vpsubusb ymm3, ymm1, ymm2 // abs difference trick - vpsubusb ymm2, ymm2, ymm1 - vpor ymm1, ymm2, ymm3 - vpunpcklbw ymm2, ymm1, ymm5 // u16. mutates order. - vpunpckhbw ymm1, ymm1, ymm5 - vpmaddwd ymm2, ymm2, ymm2 // square + hadd to u32. - vpmaddwd ymm1, ymm1, ymm1 - vpaddd ymm0, ymm0, ymm1 - vpaddd ymm0, ymm0, ymm2 - sub ecx, 32 - jg wloop - - vpshufd ymm1, ymm0, 0xee // 3, 2 + 1, 0 both lanes. - vpaddd ymm0, ymm0, ymm1 - vpshufd ymm1, ymm0, 0x01 // 1 + 0 both lanes. - vpaddd ymm0, ymm0, ymm1 - vpermq ymm1, ymm0, 0x02 // high + low lane. - vpaddd ymm0, ymm0, ymm1 - vmovd eax, xmm0 - vzeroupper - ret - } -} -#endif // _MSC_VER >= 1700 - -uvec32 kHash16x33 = { 0x92d9e201, 0, 0, 0 }; // 33 ^ 16 -uvec32 kHashMul0 = { - 0x0c3525e1, // 33 ^ 15 - 0xa3476dc1, // 33 ^ 14 - 0x3b4039a1, // 33 ^ 13 - 0x4f5f0981, // 33 ^ 12 -}; -uvec32 kHashMul1 = { - 0x30f35d61, // 33 ^ 11 - 0x855cb541, // 33 ^ 10 - 0x040a9121, // 33 ^ 9 - 0x747c7101, // 33 ^ 8 -}; -uvec32 kHashMul2 = { - 0xec41d4e1, // 33 ^ 7 - 0x4cfa3cc1, // 33 ^ 6 - 0x025528a1, // 33 ^ 5 - 0x00121881, // 33 ^ 4 -}; -uvec32 kHashMul3 = { - 0x00008c61, // 33 ^ 3 - 0x00000441, // 33 ^ 2 - 0x00000021, // 33 ^ 1 - 0x00000001, // 33 ^ 0 -}; - -__declspec(naked) -uint32 HashDjb2_SSE41(const uint8* src, int count, uint32 seed) { - __asm { - mov eax, [esp + 4] // src - mov ecx, [esp + 8] // count - movd xmm0, [esp + 12] // seed - - pxor xmm7, xmm7 // constant 0 for unpck - movdqa xmm6, xmmword ptr kHash16x33 - - wloop: - movdqu xmm1, [eax] // src[0-15] - lea eax, [eax + 16] - pmulld xmm0, xmm6 // hash *= 33 ^ 16 - movdqa xmm5, xmmword ptr kHashMul0 - movdqa xmm2, xmm1 - punpcklbw xmm2, xmm7 // src[0-7] - movdqa xmm3, xmm2 - punpcklwd xmm3, xmm7 // src[0-3] - pmulld xmm3, xmm5 - movdqa xmm5, xmmword ptr kHashMul1 - movdqa xmm4, xmm2 - punpckhwd xmm4, xmm7 // src[4-7] - pmulld xmm4, xmm5 - movdqa xmm5, xmmword ptr kHashMul2 - punpckhbw xmm1, xmm7 // src[8-15] - movdqa xmm2, xmm1 - punpcklwd xmm2, xmm7 // src[8-11] - pmulld xmm2, xmm5 - movdqa xmm5, xmmword ptr kHashMul3 - punpckhwd xmm1, xmm7 // src[12-15] - pmulld xmm1, xmm5 - paddd xmm3, xmm4 // add 16 results - paddd xmm1, xmm2 - paddd xmm1, xmm3 - - pshufd xmm2, xmm1, 0x0e // upper 2 dwords - paddd xmm1, xmm2 - pshufd xmm2, xmm1, 0x01 - paddd xmm1, xmm2 - paddd xmm0, xmm1 - sub ecx, 16 - jg wloop - - movd eax, xmm0 // return hash - ret - } -} - -// Visual C 2012 required for AVX2. -#if _MSC_VER >= 1700 -__declspec(naked) -uint32 HashDjb2_AVX2(const uint8* src, int count, uint32 seed) { - __asm { - mov eax, [esp + 4] // src - mov ecx, [esp + 8] // count - vmovd xmm0, [esp + 12] // seed - - wloop: - vpmovzxbd xmm3, [eax] // src[0-3] - vpmulld xmm0, xmm0, xmmword ptr kHash16x33 // hash *= 33 ^ 16 - vpmovzxbd xmm4, [eax + 4] // src[4-7] - vpmulld xmm3, xmm3, xmmword ptr kHashMul0 - vpmovzxbd xmm2, [eax + 8] // src[8-11] - vpmulld xmm4, xmm4, xmmword ptr kHashMul1 - vpmovzxbd xmm1, [eax + 12] // src[12-15] - vpmulld xmm2, xmm2, xmmword ptr kHashMul2 - lea eax, [eax + 16] - vpmulld xmm1, xmm1, xmmword ptr kHashMul3 - vpaddd xmm3, xmm3, xmm4 // add 16 results - vpaddd xmm1, xmm1, xmm2 - vpaddd xmm1, xmm1, xmm3 - vpshufd xmm2, xmm1, 0x0e // upper 2 dwords - vpaddd xmm1, xmm1,xmm2 - vpshufd xmm2, xmm1, 0x01 - vpaddd xmm1, xmm1, xmm2 - vpaddd xmm0, xmm0, xmm1 - sub ecx, 16 - jg wloop - - vmovd eax, xmm0 // return hash - vzeroupper - ret - } -} -#endif // _MSC_VER >= 1700 - -#endif // !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/convert.cc b/telegramgallery/src/main/cpp/libyuv/source/convert.cc deleted file mode 100644 index e332bc5..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/convert.cc +++ /dev/null @@ -1,1389 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/convert.h" - -#include "libyuv/basic_types.h" -#include "libyuv/cpu_id.h" -#include "libyuv/planar_functions.h" -#include "libyuv/rotate.h" -#include "libyuv/scale.h" // For ScalePlane() -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#define SUBSAMPLE(v, a, s) (v < 0) ? (-((-v + a) >> s)) : ((v + a) >> s) -static __inline int Abs(int v) { - return v >= 0 ? v : -v; -} - -// Any I4xx To I420 format with mirroring. -static int I4xxToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_y_width, int src_y_height, - int src_uv_width, int src_uv_height) { - const int dst_y_width = Abs(src_y_width); - const int dst_y_height = Abs(src_y_height); - const int dst_uv_width = SUBSAMPLE(dst_y_width, 1, 1); - const int dst_uv_height = SUBSAMPLE(dst_y_height, 1, 1); - if (src_y_width == 0 || src_y_height == 0 || - src_uv_width == 0 || src_uv_height == 0) { - return -1; - } - ScalePlane(src_y, src_stride_y, src_y_width, src_y_height, - dst_y, dst_stride_y, dst_y_width, dst_y_height, - kFilterBilinear); - ScalePlane(src_u, src_stride_u, src_uv_width, src_uv_height, - dst_u, dst_stride_u, dst_uv_width, dst_uv_height, - kFilterBilinear); - ScalePlane(src_v, src_stride_v, src_uv_width, src_uv_height, - dst_v, dst_stride_v, dst_uv_width, dst_uv_height, - kFilterBilinear); - return 0; -} - -// Copy I420 with optional flipping -// TODO(fbarchard): Use Scale plane which supports mirroring, but ensure -// is does row coalescing. -LIBYUV_API -int I420Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - if (!src_y || !src_u || !src_v || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - halfheight = (height + 1) >> 1; - src_y = src_y + (height - 1) * src_stride_y; - src_u = src_u + (halfheight - 1) * src_stride_u; - src_v = src_v + (halfheight - 1) * src_stride_v; - src_stride_y = -src_stride_y; - src_stride_u = -src_stride_u; - src_stride_v = -src_stride_v; - } - - if (dst_y) { - CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - } - // Copy UV planes. - CopyPlane(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth, halfheight); - CopyPlane(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth, halfheight); - return 0; -} - -// 422 chroma is 1/2 width, 1x height -// 420 chroma is 1/2 width, 1/2 height -LIBYUV_API -int I422ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - const int src_uv_width = SUBSAMPLE(width, 1, 1); - return I4xxToI420(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height, - src_uv_width, height); -} - -// 444 chroma is 1x width, 1x height -// 420 chroma is 1/2 width, 1/2 height -LIBYUV_API -int I444ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - return I4xxToI420(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height, - width, height); -} - -// 411 chroma is 1/4 width, 1x height -// 420 chroma is 1/2 width, 1/2 height -LIBYUV_API -int I411ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - const int src_uv_width = SUBSAMPLE(width, 3, 2); - return I4xxToI420(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height, - src_uv_width, height); -} - -// I400 is greyscale typically used in MJPG -LIBYUV_API -int I400ToI420(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - if (!src_y || !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - halfheight = (height + 1) >> 1; - src_y = src_y + (height - 1) * src_stride_y; - src_stride_y = -src_stride_y; - } - CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - SetPlane(dst_u, dst_stride_u, halfwidth, halfheight, 128); - SetPlane(dst_v, dst_stride_v, halfwidth, halfheight, 128); - return 0; -} - -static void CopyPlane2(const uint8* src, int src_stride_0, int src_stride_1, - uint8* dst, int dst_stride, - int width, int height) { - int y; - void (*CopyRow)(const uint8* src, uint8* dst, int width) = CopyRow_C; -#if defined(HAS_COPYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - CopyRow = IS_ALIGNED(width, 32) ? CopyRow_SSE2 : CopyRow_Any_SSE2; - } -#endif -#if defined(HAS_COPYROW_AVX) - if (TestCpuFlag(kCpuHasAVX)) { - CopyRow = IS_ALIGNED(width, 64) ? CopyRow_AVX : CopyRow_Any_AVX; - } -#endif -#if defined(HAS_COPYROW_ERMS) - if (TestCpuFlag(kCpuHasERMS)) { - CopyRow = CopyRow_ERMS; - } -#endif -#if defined(HAS_COPYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - CopyRow = IS_ALIGNED(width, 32) ? CopyRow_NEON : CopyRow_Any_NEON; - } -#endif -#if defined(HAS_COPYROW_MIPS) - if (TestCpuFlag(kCpuHasMIPS)) { - CopyRow = CopyRow_MIPS; - } -#endif - - // Copy plane - for (y = 0; y < height - 1; y += 2) { - CopyRow(src, dst, width); - CopyRow(src + src_stride_0, dst + dst_stride, width); - src += src_stride_0 + src_stride_1; - dst += dst_stride * 2; - } - if (height & 1) { - CopyRow(src, dst, width); - } -} - -// Support converting from FOURCC_M420 -// Useful for bandwidth constrained transports like USB 1.0 and 2.0 and for -// easy conversion to I420. -// M420 format description: -// M420 is row biplanar 420: 2 rows of Y and 1 row of UV. -// Chroma is half width / half height. (420) -// src_stride_m420 is row planar. Normally this will be the width in pixels. -// The UV plane is half width, but 2 values, so src_stride_m420 applies to -// this as well as the two Y planes. -static int X420ToI420(const uint8* src_y, - int src_stride_y0, int src_stride_y1, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - void (*SplitUVRow)(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) = SplitUVRow_C; - if (!src_y || !src_uv || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - halfheight = (height + 1) >> 1; - dst_y = dst_y + (height - 1) * dst_stride_y; - dst_u = dst_u + (halfheight - 1) * dst_stride_u; - dst_v = dst_v + (halfheight - 1) * dst_stride_v; - dst_stride_y = -dst_stride_y; - dst_stride_u = -dst_stride_u; - dst_stride_v = -dst_stride_v; - } - // Coalesce rows. - if (src_stride_y0 == width && - src_stride_y1 == width && - dst_stride_y == width) { - width *= height; - height = 1; - src_stride_y0 = src_stride_y1 = dst_stride_y = 0; - } - // Coalesce rows. - if (src_stride_uv == halfwidth * 2 && - dst_stride_u == halfwidth && - dst_stride_v == halfwidth) { - halfwidth *= halfheight; - halfheight = 1; - src_stride_uv = dst_stride_u = dst_stride_v = 0; - } -#if defined(HAS_SPLITUVROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - SplitUVRow = SplitUVRow_Any_SSE2; - if (IS_ALIGNED(halfwidth, 16)) { - SplitUVRow = SplitUVRow_SSE2; - } - } -#endif -#if defined(HAS_SPLITUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - SplitUVRow = SplitUVRow_Any_AVX2; - if (IS_ALIGNED(halfwidth, 32)) { - SplitUVRow = SplitUVRow_AVX2; - } - } -#endif -#if defined(HAS_SPLITUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SplitUVRow = SplitUVRow_Any_NEON; - if (IS_ALIGNED(halfwidth, 16)) { - SplitUVRow = SplitUVRow_NEON; - } - } -#endif -#if defined(HAS_SPLITUVROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(src_uv, 4) && IS_ALIGNED(src_stride_uv, 4) && - IS_ALIGNED(dst_u, 4) && IS_ALIGNED(dst_stride_u, 4) && - IS_ALIGNED(dst_v, 4) && IS_ALIGNED(dst_stride_v, 4)) { - SplitUVRow = SplitUVRow_Any_DSPR2; - if (IS_ALIGNED(halfwidth, 16)) { - SplitUVRow = SplitUVRow_DSPR2; - } - } -#endif - - if (dst_y) { - if (src_stride_y0 == src_stride_y1) { - CopyPlane(src_y, src_stride_y0, dst_y, dst_stride_y, width, height); - } else { - CopyPlane2(src_y, src_stride_y0, src_stride_y1, dst_y, dst_stride_y, - width, height); - } - } - - for (y = 0; y < halfheight; ++y) { - // Copy a row of UV. - SplitUVRow(src_uv, dst_u, dst_v, halfwidth); - dst_u += dst_stride_u; - dst_v += dst_stride_v; - src_uv += src_stride_uv; - } - return 0; -} - -// Convert NV12 to I420. -LIBYUV_API -int NV12ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - return X420ToI420(src_y, src_stride_y, src_stride_y, - src_uv, src_stride_uv, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height); -} - -// Convert NV21 to I420. Same as NV12 but u and v pointers swapped. -LIBYUV_API -int NV21ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_vu, int src_stride_vu, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - return X420ToI420(src_y, src_stride_y, src_stride_y, - src_vu, src_stride_vu, - dst_y, dst_stride_y, - dst_v, dst_stride_v, - dst_u, dst_stride_u, - width, height); -} - -// Convert M420 to I420. -LIBYUV_API -int M420ToI420(const uint8* src_m420, int src_stride_m420, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - return X420ToI420(src_m420, src_stride_m420, src_stride_m420 * 2, - src_m420 + src_stride_m420 * 2, src_stride_m420 * 3, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height); -} - -// Convert YUY2 to I420. -LIBYUV_API -int YUY2ToI420(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*YUY2ToUVRow)(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_u, uint8* dst_v, int width) = YUY2ToUVRow_C; - void (*YUY2ToYRow)(const uint8* src_yuy2, - uint8* dst_y, int width) = YUY2ToYRow_C; - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_yuy2 = src_yuy2 + (height - 1) * src_stride_yuy2; - src_stride_yuy2 = -src_stride_yuy2; - } -#if defined(HAS_YUY2TOYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - YUY2ToUVRow = YUY2ToUVRow_Any_SSE2; - YUY2ToYRow = YUY2ToYRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - YUY2ToUVRow = YUY2ToUVRow_SSE2; - YUY2ToYRow = YUY2ToYRow_SSE2; - } - } -#endif -#if defined(HAS_YUY2TOYROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - YUY2ToUVRow = YUY2ToUVRow_Any_AVX2; - YUY2ToYRow = YUY2ToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - YUY2ToUVRow = YUY2ToUVRow_AVX2; - YUY2ToYRow = YUY2ToYRow_AVX2; - } - } -#endif -#if defined(HAS_YUY2TOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - YUY2ToYRow = YUY2ToYRow_Any_NEON; - YUY2ToUVRow = YUY2ToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - YUY2ToYRow = YUY2ToYRow_NEON; - YUY2ToUVRow = YUY2ToUVRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - YUY2ToUVRow(src_yuy2, src_stride_yuy2, dst_u, dst_v, width); - YUY2ToYRow(src_yuy2, dst_y, width); - YUY2ToYRow(src_yuy2 + src_stride_yuy2, dst_y + dst_stride_y, width); - src_yuy2 += src_stride_yuy2 * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { - YUY2ToUVRow(src_yuy2, 0, dst_u, dst_v, width); - YUY2ToYRow(src_yuy2, dst_y, width); - } - return 0; -} - -// Convert UYVY to I420. -LIBYUV_API -int UYVYToI420(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*UYVYToUVRow)(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_u, uint8* dst_v, int width) = UYVYToUVRow_C; - void (*UYVYToYRow)(const uint8* src_uyvy, - uint8* dst_y, int width) = UYVYToYRow_C; - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_uyvy = src_uyvy + (height - 1) * src_stride_uyvy; - src_stride_uyvy = -src_stride_uyvy; - } -#if defined(HAS_UYVYTOYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - UYVYToUVRow = UYVYToUVRow_Any_SSE2; - UYVYToYRow = UYVYToYRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - UYVYToUVRow = UYVYToUVRow_SSE2; - UYVYToYRow = UYVYToYRow_SSE2; - } - } -#endif -#if defined(HAS_UYVYTOYROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - UYVYToUVRow = UYVYToUVRow_Any_AVX2; - UYVYToYRow = UYVYToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - UYVYToUVRow = UYVYToUVRow_AVX2; - UYVYToYRow = UYVYToYRow_AVX2; - } - } -#endif -#if defined(HAS_UYVYTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - UYVYToYRow = UYVYToYRow_Any_NEON; - UYVYToUVRow = UYVYToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - UYVYToYRow = UYVYToYRow_NEON; - UYVYToUVRow = UYVYToUVRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - UYVYToUVRow(src_uyvy, src_stride_uyvy, dst_u, dst_v, width); - UYVYToYRow(src_uyvy, dst_y, width); - UYVYToYRow(src_uyvy + src_stride_uyvy, dst_y + dst_stride_y, width); - src_uyvy += src_stride_uyvy * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { - UYVYToUVRow(src_uyvy, 0, dst_u, dst_v, width); - UYVYToYRow(src_uyvy, dst_y, width); - } - return 0; -} - -// Convert ARGB to I420. -LIBYUV_API -int ARGBToI420(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - if (!src_argb || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVRow = ARGBToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - ARGBToUVRow(src_argb, src_stride_argb, dst_u, dst_v, width); - ARGBToYRow(src_argb, dst_y, width); - ARGBToYRow(src_argb + src_stride_argb, dst_y + dst_stride_y, width); - src_argb += src_stride_argb * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { - ARGBToUVRow(src_argb, 0, dst_u, dst_v, width); - ARGBToYRow(src_argb, dst_y, width); - } - return 0; -} - -// Convert BGRA to I420. -LIBYUV_API -int BGRAToI420(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*BGRAToUVRow)(const uint8* src_bgra0, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width) = BGRAToUVRow_C; - void (*BGRAToYRow)(const uint8* src_bgra, uint8* dst_y, int width) = - BGRAToYRow_C; - if (!src_bgra || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_bgra = src_bgra + (height - 1) * src_stride_bgra; - src_stride_bgra = -src_stride_bgra; - } -#if defined(HAS_BGRATOYROW_SSSE3) && defined(HAS_BGRATOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - BGRAToUVRow = BGRAToUVRow_Any_SSSE3; - BGRAToYRow = BGRAToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - BGRAToUVRow = BGRAToUVRow_SSSE3; - BGRAToYRow = BGRAToYRow_SSSE3; - } - } -#endif -#if defined(HAS_BGRATOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - BGRAToYRow = BGRAToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - BGRAToYRow = BGRAToYRow_NEON; - } - } -#endif -#if defined(HAS_BGRATOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - BGRAToUVRow = BGRAToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - BGRAToUVRow = BGRAToUVRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - BGRAToUVRow(src_bgra, src_stride_bgra, dst_u, dst_v, width); - BGRAToYRow(src_bgra, dst_y, width); - BGRAToYRow(src_bgra + src_stride_bgra, dst_y + dst_stride_y, width); - src_bgra += src_stride_bgra * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { - BGRAToUVRow(src_bgra, 0, dst_u, dst_v, width); - BGRAToYRow(src_bgra, dst_y, width); - } - return 0; -} - -// Convert ABGR to I420. -LIBYUV_API -int ABGRToI420(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*ABGRToUVRow)(const uint8* src_abgr0, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width) = ABGRToUVRow_C; - void (*ABGRToYRow)(const uint8* src_abgr, uint8* dst_y, int width) = - ABGRToYRow_C; - if (!src_abgr || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_abgr = src_abgr + (height - 1) * src_stride_abgr; - src_stride_abgr = -src_stride_abgr; - } -#if defined(HAS_ABGRTOYROW_SSSE3) && defined(HAS_ABGRTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ABGRToUVRow = ABGRToUVRow_Any_SSSE3; - ABGRToYRow = ABGRToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ABGRToUVRow = ABGRToUVRow_SSSE3; - ABGRToYRow = ABGRToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ABGRTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ABGRToYRow = ABGRToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ABGRToYRow = ABGRToYRow_NEON; - } - } -#endif -#if defined(HAS_ABGRTOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ABGRToUVRow = ABGRToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ABGRToUVRow = ABGRToUVRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - ABGRToUVRow(src_abgr, src_stride_abgr, dst_u, dst_v, width); - ABGRToYRow(src_abgr, dst_y, width); - ABGRToYRow(src_abgr + src_stride_abgr, dst_y + dst_stride_y, width); - src_abgr += src_stride_abgr * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { - ABGRToUVRow(src_abgr, 0, dst_u, dst_v, width); - ABGRToYRow(src_abgr, dst_y, width); - } - return 0; -} - -// Convert RGBA to I420. -LIBYUV_API -int RGBAToI420(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*RGBAToUVRow)(const uint8* src_rgba0, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width) = RGBAToUVRow_C; - void (*RGBAToYRow)(const uint8* src_rgba, uint8* dst_y, int width) = - RGBAToYRow_C; - if (!src_rgba || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_rgba = src_rgba + (height - 1) * src_stride_rgba; - src_stride_rgba = -src_stride_rgba; - } -#if defined(HAS_RGBATOYROW_SSSE3) && defined(HAS_RGBATOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - RGBAToUVRow = RGBAToUVRow_Any_SSSE3; - RGBAToYRow = RGBAToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - RGBAToUVRow = RGBAToUVRow_SSSE3; - RGBAToYRow = RGBAToYRow_SSSE3; - } - } -#endif -#if defined(HAS_RGBATOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RGBAToYRow = RGBAToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - RGBAToYRow = RGBAToYRow_NEON; - } - } -#endif -#if defined(HAS_RGBATOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RGBAToUVRow = RGBAToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - RGBAToUVRow = RGBAToUVRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - RGBAToUVRow(src_rgba, src_stride_rgba, dst_u, dst_v, width); - RGBAToYRow(src_rgba, dst_y, width); - RGBAToYRow(src_rgba + src_stride_rgba, dst_y + dst_stride_y, width); - src_rgba += src_stride_rgba * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { - RGBAToUVRow(src_rgba, 0, dst_u, dst_v, width); - RGBAToYRow(src_rgba, dst_y, width); - } - return 0; -} - -// Convert RGB24 to I420. -LIBYUV_API -int RGB24ToI420(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; -#if defined(HAS_RGB24TOYROW_NEON) - void (*RGB24ToUVRow)(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width) = RGB24ToUVRow_C; - void (*RGB24ToYRow)(const uint8* src_rgb24, uint8* dst_y, int width) = - RGB24ToYRow_C; -#else - void (*RGB24ToARGBRow)(const uint8* src_rgb, uint8* dst_argb, int width) = - RGB24ToARGBRow_C; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; -#endif - if (!src_rgb24 || !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_rgb24 = src_rgb24 + (height - 1) * src_stride_rgb24; - src_stride_rgb24 = -src_stride_rgb24; - } - -// Neon version does direct RGB24 to YUV. -#if defined(HAS_RGB24TOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RGB24ToUVRow = RGB24ToUVRow_Any_NEON; - RGB24ToYRow = RGB24ToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - RGB24ToYRow = RGB24ToYRow_NEON; - if (IS_ALIGNED(width, 16)) { - RGB24ToUVRow = RGB24ToUVRow_NEON; - } - } - } -// Other platforms do intermediate conversion from RGB24 to ARGB. -#else -#if defined(HAS_RGB24TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - RGB24ToARGBRow = RGB24ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - RGB24ToARGBRow = RGB24ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif - { - // Allocate 2 rows of ARGB. - const int kRowSize = (width * 4 + 31) & ~31; - align_buffer_64(row, kRowSize * 2); -#endif - - for (y = 0; y < height - 1; y += 2) { -#if defined(HAS_RGB24TOYROW_NEON) - RGB24ToUVRow(src_rgb24, src_stride_rgb24, dst_u, dst_v, width); - RGB24ToYRow(src_rgb24, dst_y, width); - RGB24ToYRow(src_rgb24 + src_stride_rgb24, dst_y + dst_stride_y, width); -#else - RGB24ToARGBRow(src_rgb24, row, width); - RGB24ToARGBRow(src_rgb24 + src_stride_rgb24, row + kRowSize, width); - ARGBToUVRow(row, kRowSize, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); - ARGBToYRow(row + kRowSize, dst_y + dst_stride_y, width); -#endif - src_rgb24 += src_stride_rgb24 * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { -#if defined(HAS_RGB24TOYROW_NEON) - RGB24ToUVRow(src_rgb24, 0, dst_u, dst_v, width); - RGB24ToYRow(src_rgb24, dst_y, width); -#else - RGB24ToARGBRow(src_rgb24, row, width); - ARGBToUVRow(row, 0, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); -#endif - } -#if !defined(HAS_RGB24TOYROW_NEON) - free_aligned_buffer_64(row); - } -#endif - return 0; -} - -// Convert RAW to I420. -LIBYUV_API -int RAWToI420(const uint8* src_raw, int src_stride_raw, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; -#if defined(HAS_RAWTOYROW_NEON) - void (*RAWToUVRow)(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width) = RAWToUVRow_C; - void (*RAWToYRow)(const uint8* src_raw, uint8* dst_y, int width) = - RAWToYRow_C; -#else - void (*RAWToARGBRow)(const uint8* src_rgb, uint8* dst_argb, int width) = - RAWToARGBRow_C; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; -#endif - if (!src_raw || !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_raw = src_raw + (height - 1) * src_stride_raw; - src_stride_raw = -src_stride_raw; - } - -// Neon version does direct RAW to YUV. -#if defined(HAS_RAWTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RAWToUVRow = RAWToUVRow_Any_NEON; - RAWToYRow = RAWToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - RAWToYRow = RAWToYRow_NEON; - if (IS_ALIGNED(width, 16)) { - RAWToUVRow = RAWToUVRow_NEON; - } - } - } -// Other platforms do intermediate conversion from RAW to ARGB. -#else -#if defined(HAS_RAWTOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - RAWToARGBRow = RAWToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - RAWToARGBRow = RAWToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif - { - // Allocate 2 rows of ARGB. - const int kRowSize = (width * 4 + 31) & ~31; - align_buffer_64(row, kRowSize * 2); -#endif - - for (y = 0; y < height - 1; y += 2) { -#if defined(HAS_RAWTOYROW_NEON) - RAWToUVRow(src_raw, src_stride_raw, dst_u, dst_v, width); - RAWToYRow(src_raw, dst_y, width); - RAWToYRow(src_raw + src_stride_raw, dst_y + dst_stride_y, width); -#else - RAWToARGBRow(src_raw, row, width); - RAWToARGBRow(src_raw + src_stride_raw, row + kRowSize, width); - ARGBToUVRow(row, kRowSize, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); - ARGBToYRow(row + kRowSize, dst_y + dst_stride_y, width); -#endif - src_raw += src_stride_raw * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { -#if defined(HAS_RAWTOYROW_NEON) - RAWToUVRow(src_raw, 0, dst_u, dst_v, width); - RAWToYRow(src_raw, dst_y, width); -#else - RAWToARGBRow(src_raw, row, width); - ARGBToUVRow(row, 0, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); -#endif - } -#if !defined(HAS_RAWTOYROW_NEON) - free_aligned_buffer_64(row); - } -#endif - return 0; -} - -// Convert RGB565 to I420. -LIBYUV_API -int RGB565ToI420(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; -#if defined(HAS_RGB565TOYROW_NEON) - void (*RGB565ToUVRow)(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width) = RGB565ToUVRow_C; - void (*RGB565ToYRow)(const uint8* src_rgb565, uint8* dst_y, int width) = - RGB565ToYRow_C; -#else - void (*RGB565ToARGBRow)(const uint8* src_rgb, uint8* dst_argb, int width) = - RGB565ToARGBRow_C; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; -#endif - if (!src_rgb565 || !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_rgb565 = src_rgb565 + (height - 1) * src_stride_rgb565; - src_stride_rgb565 = -src_stride_rgb565; - } - -// Neon version does direct RGB565 to YUV. -#if defined(HAS_RGB565TOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RGB565ToUVRow = RGB565ToUVRow_Any_NEON; - RGB565ToYRow = RGB565ToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - RGB565ToYRow = RGB565ToYRow_NEON; - if (IS_ALIGNED(width, 16)) { - RGB565ToUVRow = RGB565ToUVRow_NEON; - } - } - } -// Other platforms do intermediate conversion from RGB565 to ARGB. -#else -#if defined(HAS_RGB565TOARGBROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - RGB565ToARGBRow = RGB565ToARGBRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - RGB565ToARGBRow = RGB565ToARGBRow_SSE2; - } - } -#endif -#if defined(HAS_RGB565TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - RGB565ToARGBRow = RGB565ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - RGB565ToARGBRow = RGB565ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif - { - // Allocate 2 rows of ARGB. - const int kRowSize = (width * 4 + 31) & ~31; - align_buffer_64(row, kRowSize * 2); -#endif - - for (y = 0; y < height - 1; y += 2) { -#if defined(HAS_RGB565TOYROW_NEON) - RGB565ToUVRow(src_rgb565, src_stride_rgb565, dst_u, dst_v, width); - RGB565ToYRow(src_rgb565, dst_y, width); - RGB565ToYRow(src_rgb565 + src_stride_rgb565, dst_y + dst_stride_y, width); -#else - RGB565ToARGBRow(src_rgb565, row, width); - RGB565ToARGBRow(src_rgb565 + src_stride_rgb565, row + kRowSize, width); - ARGBToUVRow(row, kRowSize, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); - ARGBToYRow(row + kRowSize, dst_y + dst_stride_y, width); -#endif - src_rgb565 += src_stride_rgb565 * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { -#if defined(HAS_RGB565TOYROW_NEON) - RGB565ToUVRow(src_rgb565, 0, dst_u, dst_v, width); - RGB565ToYRow(src_rgb565, dst_y, width); -#else - RGB565ToARGBRow(src_rgb565, row, width); - ARGBToUVRow(row, 0, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); -#endif - } -#if !defined(HAS_RGB565TOYROW_NEON) - free_aligned_buffer_64(row); - } -#endif - return 0; -} - -// Convert ARGB1555 to I420. -LIBYUV_API -int ARGB1555ToI420(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; -#if defined(HAS_ARGB1555TOYROW_NEON) - void (*ARGB1555ToUVRow)(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width) = ARGB1555ToUVRow_C; - void (*ARGB1555ToYRow)(const uint8* src_argb1555, uint8* dst_y, int width) = - ARGB1555ToYRow_C; -#else - void (*ARGB1555ToARGBRow)(const uint8* src_rgb, uint8* dst_argb, int width) = - ARGB1555ToARGBRow_C; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; -#endif - if (!src_argb1555 || !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb1555 = src_argb1555 + (height - 1) * src_stride_argb1555; - src_stride_argb1555 = -src_stride_argb1555; - } - -// Neon version does direct ARGB1555 to YUV. -#if defined(HAS_ARGB1555TOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGB1555ToUVRow = ARGB1555ToUVRow_Any_NEON; - ARGB1555ToYRow = ARGB1555ToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGB1555ToYRow = ARGB1555ToYRow_NEON; - if (IS_ALIGNED(width, 16)) { - ARGB1555ToUVRow = ARGB1555ToUVRow_NEON; - } - } - } -// Other platforms do intermediate conversion from ARGB1555 to ARGB. -#else -#if defined(HAS_ARGB1555TOARGBROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_SSE2; - } - } -#endif -#if defined(HAS_ARGB1555TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif - { - // Allocate 2 rows of ARGB. - const int kRowSize = (width * 4 + 31) & ~31; - align_buffer_64(row, kRowSize * 2); -#endif - - for (y = 0; y < height - 1; y += 2) { -#if defined(HAS_ARGB1555TOYROW_NEON) - ARGB1555ToUVRow(src_argb1555, src_stride_argb1555, dst_u, dst_v, width); - ARGB1555ToYRow(src_argb1555, dst_y, width); - ARGB1555ToYRow(src_argb1555 + src_stride_argb1555, dst_y + dst_stride_y, - width); -#else - ARGB1555ToARGBRow(src_argb1555, row, width); - ARGB1555ToARGBRow(src_argb1555 + src_stride_argb1555, row + kRowSize, - width); - ARGBToUVRow(row, kRowSize, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); - ARGBToYRow(row + kRowSize, dst_y + dst_stride_y, width); -#endif - src_argb1555 += src_stride_argb1555 * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { -#if defined(HAS_ARGB1555TOYROW_NEON) - ARGB1555ToUVRow(src_argb1555, 0, dst_u, dst_v, width); - ARGB1555ToYRow(src_argb1555, dst_y, width); -#else - ARGB1555ToARGBRow(src_argb1555, row, width); - ARGBToUVRow(row, 0, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); -#endif - } -#if !defined(HAS_ARGB1555TOYROW_NEON) - free_aligned_buffer_64(row); - } -#endif - return 0; -} - -// Convert ARGB4444 to I420. -LIBYUV_API -int ARGB4444ToI420(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; -#if defined(HAS_ARGB4444TOYROW_NEON) - void (*ARGB4444ToUVRow)(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width) = ARGB4444ToUVRow_C; - void (*ARGB4444ToYRow)(const uint8* src_argb4444, uint8* dst_y, int width) = - ARGB4444ToYRow_C; -#else - void (*ARGB4444ToARGBRow)(const uint8* src_rgb, uint8* dst_argb, int width) = - ARGB4444ToARGBRow_C; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; -#endif - if (!src_argb4444 || !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb4444 = src_argb4444 + (height - 1) * src_stride_argb4444; - src_stride_argb4444 = -src_stride_argb4444; - } - -// Neon version does direct ARGB4444 to YUV. -#if defined(HAS_ARGB4444TOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGB4444ToUVRow = ARGB4444ToUVRow_Any_NEON; - ARGB4444ToYRow = ARGB4444ToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGB4444ToYRow = ARGB4444ToYRow_NEON; - if (IS_ALIGNED(width, 16)) { - ARGB4444ToUVRow = ARGB4444ToUVRow_NEON; - } - } - } -// Other platforms do intermediate conversion from ARGB4444 to ARGB. -#else -#if defined(HAS_ARGB4444TOARGBROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_SSE2; - } - } -#endif -#if defined(HAS_ARGB4444TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif - { - // Allocate 2 rows of ARGB. - const int kRowSize = (width * 4 + 31) & ~31; - align_buffer_64(row, kRowSize * 2); -#endif - - for (y = 0; y < height - 1; y += 2) { -#if defined(HAS_ARGB4444TOYROW_NEON) - ARGB4444ToUVRow(src_argb4444, src_stride_argb4444, dst_u, dst_v, width); - ARGB4444ToYRow(src_argb4444, dst_y, width); - ARGB4444ToYRow(src_argb4444 + src_stride_argb4444, dst_y + dst_stride_y, - width); -#else - ARGB4444ToARGBRow(src_argb4444, row, width); - ARGB4444ToARGBRow(src_argb4444 + src_stride_argb4444, row + kRowSize, - width); - ARGBToUVRow(row, kRowSize, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); - ARGBToYRow(row + kRowSize, dst_y + dst_stride_y, width); -#endif - src_argb4444 += src_stride_argb4444 * 2; - dst_y += dst_stride_y * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { -#if defined(HAS_ARGB4444TOYROW_NEON) - ARGB4444ToUVRow(src_argb4444, 0, dst_u, dst_v, width); - ARGB4444ToYRow(src_argb4444, dst_y, width); -#else - ARGB4444ToARGBRow(src_argb4444, row, width); - ARGBToUVRow(row, 0, dst_u, dst_v, width); - ARGBToYRow(row, dst_y, width); -#endif - } -#if !defined(HAS_ARGB4444TOYROW_NEON) - free_aligned_buffer_64(row); - } -#endif - return 0; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/convert_argb.cc b/telegramgallery/src/main/cpp/libyuv/source/convert_argb.cc deleted file mode 100644 index fb9582d..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/convert_argb.cc +++ /dev/null @@ -1,1456 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/convert_argb.h" - -#include "libyuv/cpu_id.h" -#ifdef HAVE_JPEG -#include "libyuv/mjpeg_decoder.h" -#endif -#include "libyuv/planar_functions.h" // For CopyPlane and ARGBShuffle. -#include "libyuv/rotate_argb.h" -#include "libyuv/row.h" -#include "libyuv/video_common.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Copy ARGB with optional flipping -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - if (!src_argb || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - - CopyPlane(src_argb, src_stride_argb, dst_argb, dst_stride_argb, - width * 4, height); - return 0; -} - -// Convert I422 to ARGB with matrix -static int I420ToARGBMatrix(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - const struct YuvConstants* yuvconstants, - int width, int height) { - int y; - void (*I422ToARGBRow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToARGBRow_C; - if (!src_y || !src_u || !src_v || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } -#if defined(HAS_I422TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToARGBRow = I422ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToARGBRow = I422ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_I422TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToARGBRow = I422ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToARGBRow = I422ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_I422TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToARGBRow = I422ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToARGBRow = I422ToARGBRow_NEON; - } - } -#endif -#if defined(HAS_I422TOARGBROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && - IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && - IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && - IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && - IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - I422ToARGBRow = I422ToARGBRow_DSPR2; - } -#endif - - for (y = 0; y < height; ++y) { - I422ToARGBRow(src_y, src_u, src_v, dst_argb, yuvconstants, width); - dst_argb += dst_stride_argb; - src_y += src_stride_y; - if (y & 1) { - src_u += src_stride_u; - src_v += src_stride_v; - } - } - return 0; -} - -// Convert I420 to ARGB. -LIBYUV_API -int I420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return I420ToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_argb, dst_stride_argb, - &kYuvI601Constants, - width, height); -} - -// Convert I420 to ABGR. -LIBYUV_API -int I420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height) { - return I420ToARGBMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_abgr, dst_stride_abgr, - &kYvuI601Constants, // Use Yvu matrix - width, height); -} - -// Convert J420 to ARGB. -LIBYUV_API -int J420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return I420ToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_argb, dst_stride_argb, - &kYuvJPEGConstants, - width, height); -} - -// Convert J420 to ABGR. -LIBYUV_API -int J420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height) { - return I420ToARGBMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_abgr, dst_stride_abgr, - &kYvuJPEGConstants, // Use Yvu matrix - width, height); -} - -// Convert H420 to ARGB. -LIBYUV_API -int H420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return I420ToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_argb, dst_stride_argb, - &kYuvH709Constants, - width, height); -} - -// Convert H420 to ABGR. -LIBYUV_API -int H420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height) { - return I420ToARGBMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_abgr, dst_stride_abgr, - &kYvuH709Constants, // Use Yvu matrix - width, height); -} - -// Convert I422 to ARGB with matrix -static int I422ToARGBMatrix(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - const struct YuvConstants* yuvconstants, - int width, int height) { - int y; - void (*I422ToARGBRow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToARGBRow_C; - if (!src_y || !src_u || !src_v || - !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - // Coalesce rows. - if (src_stride_y == width && - src_stride_u * 2 == width && - src_stride_v * 2 == width && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_y = src_stride_u = src_stride_v = dst_stride_argb = 0; - } -#if defined(HAS_I422TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToARGBRow = I422ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToARGBRow = I422ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_I422TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToARGBRow = I422ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToARGBRow = I422ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_I422TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToARGBRow = I422ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToARGBRow = I422ToARGBRow_NEON; - } - } -#endif -#if defined(HAS_I422TOARGBROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && - IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && - IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && - IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && - IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - I422ToARGBRow = I422ToARGBRow_DSPR2; - } -#endif - - for (y = 0; y < height; ++y) { - I422ToARGBRow(src_y, src_u, src_v, dst_argb, yuvconstants, width); - dst_argb += dst_stride_argb; - src_y += src_stride_y; - src_u += src_stride_u; - src_v += src_stride_v; - } - return 0; -} - -// Convert I422 to ARGB. -LIBYUV_API -int I422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return I422ToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_argb, dst_stride_argb, - &kYuvI601Constants, - width, height); -} - -// Convert I422 to ABGR. -LIBYUV_API -int I422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height) { - return I422ToARGBMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_abgr, dst_stride_abgr, - &kYvuI601Constants, // Use Yvu matrix - width, height); -} - -// Convert J422 to ARGB. -LIBYUV_API -int J422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return I422ToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_argb, dst_stride_argb, - &kYuvJPEGConstants, - width, height); -} - -// Convert J422 to ABGR. -LIBYUV_API -int J422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height) { - return I422ToARGBMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_abgr, dst_stride_abgr, - &kYvuJPEGConstants, // Use Yvu matrix - width, height); -} - -// Convert H422 to ARGB. -LIBYUV_API -int H422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return I422ToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_argb, dst_stride_argb, - &kYuvH709Constants, - width, height); -} - -// Convert H422 to ABGR. -LIBYUV_API -int H422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height) { - return I422ToARGBMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_abgr, dst_stride_abgr, - &kYvuH709Constants, // Use Yvu matrix - width, height); -} - -// Convert I444 to ARGB with matrix -static int I444ToARGBMatrix(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - const struct YuvConstants* yuvconstants, - int width, int height) { - int y; - void (*I444ToARGBRow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I444ToARGBRow_C; - if (!src_y || !src_u || !src_v || - !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - // Coalesce rows. - if (src_stride_y == width && - src_stride_u == width && - src_stride_v == width && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_y = src_stride_u = src_stride_v = dst_stride_argb = 0; - } -#if defined(HAS_I444TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I444ToARGBRow = I444ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I444ToARGBRow = I444ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_I444TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I444ToARGBRow = I444ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I444ToARGBRow = I444ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_I444TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I444ToARGBRow = I444ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I444ToARGBRow = I444ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I444ToARGBRow(src_y, src_u, src_v, dst_argb, yuvconstants, width); - dst_argb += dst_stride_argb; - src_y += src_stride_y; - src_u += src_stride_u; - src_v += src_stride_v; - } - return 0; -} - -// Convert I444 to ARGB. -LIBYUV_API -int I444ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return I444ToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_argb, dst_stride_argb, - &kYuvI601Constants, - width, height); -} - -// Convert I444 to ABGR. -LIBYUV_API -int I444ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height) { - return I444ToARGBMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_abgr, dst_stride_abgr, - &kYvuI601Constants, // Use Yvu matrix - width, height); -} - -// Convert J444 to ARGB. -LIBYUV_API -int J444ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return I444ToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_argb, dst_stride_argb, - &kYuvJPEGConstants, - width, height); -} - -// Convert I411 to ARGB. -LIBYUV_API -int I411ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*I411ToARGBRow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I411ToARGBRow_C; - if (!src_y || !src_u || !src_v || - !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - // Coalesce rows. - if (src_stride_y == width && - src_stride_u * 4 == width && - src_stride_v * 4 == width && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_y = src_stride_u = src_stride_v = dst_stride_argb = 0; - } -#if defined(HAS_I411TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I411ToARGBRow = I411ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I411ToARGBRow = I411ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_I411TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I411ToARGBRow = I411ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I411ToARGBRow = I411ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_I411TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I411ToARGBRow = I411ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I411ToARGBRow = I411ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I411ToARGBRow(src_y, src_u, src_v, dst_argb, &kYuvI601Constants, width); - dst_argb += dst_stride_argb; - src_y += src_stride_y; - src_u += src_stride_u; - src_v += src_stride_v; - } - return 0; -} - -// Convert I420 with Alpha to preattenuated ARGB. -static int I420AlphaToARGBMatrix(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_argb, int dst_stride_argb, - const struct YuvConstants* yuvconstants, - int width, int height, int attenuate) { - int y; - void (*I422AlphaToARGBRow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) = I422AlphaToARGBRow_C; - void (*ARGBAttenuateRow)(const uint8* src_argb, uint8* dst_argb, - int width) = ARGBAttenuateRow_C; - if (!src_y || !src_u || !src_v || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } -#if defined(HAS_I422ALPHATOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422AlphaToARGBRow = I422AlphaToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422AlphaToARGBRow = I422AlphaToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_I422ALPHATOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422AlphaToARGBRow = I422AlphaToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422AlphaToARGBRow = I422AlphaToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_I422ALPHATOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422AlphaToARGBRow = I422AlphaToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422AlphaToARGBRow = I422AlphaToARGBRow_NEON; - } - } -#endif -#if defined(HAS_I422ALPHATOARGBROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && - IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && - IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && - IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && - IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - I422AlphaToARGBRow = I422AlphaToARGBRow_DSPR2; - } -#endif -#if defined(HAS_ARGBATTENUATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBAttenuateRow = ARGBAttenuateRow_Any_SSSE3; - if (IS_ALIGNED(width, 4)) { - ARGBAttenuateRow = ARGBAttenuateRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBATTENUATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBAttenuateRow = ARGBAttenuateRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBAttenuateRow = ARGBAttenuateRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBATTENUATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBAttenuateRow = ARGBAttenuateRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBAttenuateRow = ARGBAttenuateRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I422AlphaToARGBRow(src_y, src_u, src_v, src_a, dst_argb, yuvconstants, - width); - if (attenuate) { - ARGBAttenuateRow(dst_argb, dst_argb, width); - } - dst_argb += dst_stride_argb; - src_a += src_stride_a; - src_y += src_stride_y; - if (y & 1) { - src_u += src_stride_u; - src_v += src_stride_v; - } - } - return 0; -} - -// Convert I420 with Alpha to ARGB. -LIBYUV_API -int I420AlphaToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_argb, int dst_stride_argb, - int width, int height, int attenuate) { - return I420AlphaToARGBMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - src_a, src_stride_a, - dst_argb, dst_stride_argb, - &kYuvI601Constants, - width, height, attenuate); -} - -// Convert I420 with Alpha to ABGR. -LIBYUV_API -int I420AlphaToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height, int attenuate) { - return I420AlphaToARGBMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - src_a, src_stride_a, - dst_abgr, dst_stride_abgr, - &kYvuI601Constants, // Use Yvu matrix - width, height, attenuate); -} - -// Convert I400 to ARGB. -LIBYUV_API -int I400ToARGB(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*I400ToARGBRow)(const uint8* y_buf, - uint8* rgb_buf, - int width) = I400ToARGBRow_C; - if (!src_y || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - // Coalesce rows. - if (src_stride_y == width && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_y = dst_stride_argb = 0; - } -#if defined(HAS_I400TOARGBROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - I400ToARGBRow = I400ToARGBRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - I400ToARGBRow = I400ToARGBRow_SSE2; - } - } -#endif -#if defined(HAS_I400TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I400ToARGBRow = I400ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I400ToARGBRow = I400ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_I400TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I400ToARGBRow = I400ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I400ToARGBRow = I400ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I400ToARGBRow(src_y, dst_argb, width); - dst_argb += dst_stride_argb; - src_y += src_stride_y; - } - return 0; -} - -// Convert J400 to ARGB. -LIBYUV_API -int J400ToARGB(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*J400ToARGBRow)(const uint8* src_y, uint8* dst_argb, int width) = - J400ToARGBRow_C; - if (!src_y || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_stride_y = -src_stride_y; - } - // Coalesce rows. - if (src_stride_y == width && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_y = dst_stride_argb = 0; - } -#if defined(HAS_J400TOARGBROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - J400ToARGBRow = J400ToARGBRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - J400ToARGBRow = J400ToARGBRow_SSE2; - } - } -#endif -#if defined(HAS_J400TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - J400ToARGBRow = J400ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - J400ToARGBRow = J400ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_J400TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - J400ToARGBRow = J400ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - J400ToARGBRow = J400ToARGBRow_NEON; - } - } -#endif - for (y = 0; y < height; ++y) { - J400ToARGBRow(src_y, dst_argb, width); - src_y += src_stride_y; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Shuffle table for converting BGRA to ARGB. -static uvec8 kShuffleMaskBGRAToARGB = { - 3u, 2u, 1u, 0u, 7u, 6u, 5u, 4u, 11u, 10u, 9u, 8u, 15u, 14u, 13u, 12u -}; - -// Shuffle table for converting ABGR to ARGB. -static uvec8 kShuffleMaskABGRToARGB = { - 2u, 1u, 0u, 3u, 6u, 5u, 4u, 7u, 10u, 9u, 8u, 11u, 14u, 13u, 12u, 15u -}; - -// Shuffle table for converting RGBA to ARGB. -static uvec8 kShuffleMaskRGBAToARGB = { - 1u, 2u, 3u, 0u, 5u, 6u, 7u, 4u, 9u, 10u, 11u, 8u, 13u, 14u, 15u, 12u -}; - -// Convert BGRA to ARGB. -LIBYUV_API -int BGRAToARGB(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return ARGBShuffle(src_bgra, src_stride_bgra, - dst_argb, dst_stride_argb, - (const uint8*)(&kShuffleMaskBGRAToARGB), - width, height); -} - -// Convert ARGB to BGRA (same as BGRAToARGB). -LIBYUV_API -int ARGBToBGRA(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return ARGBShuffle(src_bgra, src_stride_bgra, - dst_argb, dst_stride_argb, - (const uint8*)(&kShuffleMaskBGRAToARGB), - width, height); -} - -// Convert ABGR to ARGB. -LIBYUV_API -int ABGRToARGB(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return ARGBShuffle(src_abgr, src_stride_abgr, - dst_argb, dst_stride_argb, - (const uint8*)(&kShuffleMaskABGRToARGB), - width, height); -} - -// Convert ARGB to ABGR to (same as ABGRToARGB). -LIBYUV_API -int ARGBToABGR(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return ARGBShuffle(src_abgr, src_stride_abgr, - dst_argb, dst_stride_argb, - (const uint8*)(&kShuffleMaskABGRToARGB), - width, height); -} - -// Convert RGBA to ARGB. -LIBYUV_API -int RGBAToARGB(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - return ARGBShuffle(src_rgba, src_stride_rgba, - dst_argb, dst_stride_argb, - (const uint8*)(&kShuffleMaskRGBAToARGB), - width, height); -} - -// Convert RGB24 to ARGB. -LIBYUV_API -int RGB24ToARGB(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*RGB24ToARGBRow)(const uint8* src_rgb, uint8* dst_argb, int width) = - RGB24ToARGBRow_C; - if (!src_rgb24 || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_rgb24 = src_rgb24 + (height - 1) * src_stride_rgb24; - src_stride_rgb24 = -src_stride_rgb24; - } - // Coalesce rows. - if (src_stride_rgb24 == width * 3 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_rgb24 = dst_stride_argb = 0; - } -#if defined(HAS_RGB24TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - RGB24ToARGBRow = RGB24ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - RGB24ToARGBRow = RGB24ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_RGB24TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RGB24ToARGBRow = RGB24ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - RGB24ToARGBRow = RGB24ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - RGB24ToARGBRow(src_rgb24, dst_argb, width); - src_rgb24 += src_stride_rgb24; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert RAW to ARGB. -LIBYUV_API -int RAWToARGB(const uint8* src_raw, int src_stride_raw, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*RAWToARGBRow)(const uint8* src_rgb, uint8* dst_argb, int width) = - RAWToARGBRow_C; - if (!src_raw || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_raw = src_raw + (height - 1) * src_stride_raw; - src_stride_raw = -src_stride_raw; - } - // Coalesce rows. - if (src_stride_raw == width * 3 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_raw = dst_stride_argb = 0; - } -#if defined(HAS_RAWTOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - RAWToARGBRow = RAWToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - RAWToARGBRow = RAWToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_RAWTOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RAWToARGBRow = RAWToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - RAWToARGBRow = RAWToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - RAWToARGBRow(src_raw, dst_argb, width); - src_raw += src_stride_raw; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert RGB565 to ARGB. -LIBYUV_API -int RGB565ToARGB(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*RGB565ToARGBRow)(const uint8* src_rgb565, uint8* dst_argb, int width) = - RGB565ToARGBRow_C; - if (!src_rgb565 || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_rgb565 = src_rgb565 + (height - 1) * src_stride_rgb565; - src_stride_rgb565 = -src_stride_rgb565; - } - // Coalesce rows. - if (src_stride_rgb565 == width * 2 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_rgb565 = dst_stride_argb = 0; - } -#if defined(HAS_RGB565TOARGBROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - RGB565ToARGBRow = RGB565ToARGBRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - RGB565ToARGBRow = RGB565ToARGBRow_SSE2; - } - } -#endif -#if defined(HAS_RGB565TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - RGB565ToARGBRow = RGB565ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - RGB565ToARGBRow = RGB565ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_RGB565TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RGB565ToARGBRow = RGB565ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - RGB565ToARGBRow = RGB565ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - RGB565ToARGBRow(src_rgb565, dst_argb, width); - src_rgb565 += src_stride_rgb565; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert ARGB1555 to ARGB. -LIBYUV_API -int ARGB1555ToARGB(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGB1555ToARGBRow)(const uint8* src_argb1555, uint8* dst_argb, - int width) = ARGB1555ToARGBRow_C; - if (!src_argb1555 || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb1555 = src_argb1555 + (height - 1) * src_stride_argb1555; - src_stride_argb1555 = -src_stride_argb1555; - } - // Coalesce rows. - if (src_stride_argb1555 == width * 2 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb1555 = dst_stride_argb = 0; - } -#if defined(HAS_ARGB1555TOARGBROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_SSE2; - } - } -#endif -#if defined(HAS_ARGB1555TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_ARGB1555TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGB1555ToARGBRow = ARGB1555ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGB1555ToARGBRow(src_argb1555, dst_argb, width); - src_argb1555 += src_stride_argb1555; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert ARGB4444 to ARGB. -LIBYUV_API -int ARGB4444ToARGB(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGB4444ToARGBRow)(const uint8* src_argb4444, uint8* dst_argb, - int width) = ARGB4444ToARGBRow_C; - if (!src_argb4444 || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb4444 = src_argb4444 + (height - 1) * src_stride_argb4444; - src_stride_argb4444 = -src_stride_argb4444; - } - // Coalesce rows. - if (src_stride_argb4444 == width * 2 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb4444 = dst_stride_argb = 0; - } -#if defined(HAS_ARGB4444TOARGBROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_SSE2; - } - } -#endif -#if defined(HAS_ARGB4444TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_ARGB4444TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGB4444ToARGBRow = ARGB4444ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGB4444ToARGBRow(src_argb4444, dst_argb, width); - src_argb4444 += src_stride_argb4444; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert NV12 to ARGB. -LIBYUV_API -int NV12ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*NV12ToARGBRow)(const uint8* y_buf, - const uint8* uv_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = NV12ToARGBRow_C; - if (!src_y || !src_uv || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } -#if defined(HAS_NV12TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - NV12ToARGBRow = NV12ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - NV12ToARGBRow = NV12ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_NV12TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - NV12ToARGBRow = NV12ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - NV12ToARGBRow = NV12ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_NV12TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - NV12ToARGBRow = NV12ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - NV12ToARGBRow = NV12ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - NV12ToARGBRow(src_y, src_uv, dst_argb, &kYuvI601Constants, width); - dst_argb += dst_stride_argb; - src_y += src_stride_y; - if (y & 1) { - src_uv += src_stride_uv; - } - } - return 0; -} - -// Convert NV21 to ARGB. -LIBYUV_API -int NV21ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*NV21ToARGBRow)(const uint8* y_buf, - const uint8* uv_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = NV21ToARGBRow_C; - if (!src_y || !src_uv || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } -#if defined(HAS_NV21TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - NV21ToARGBRow = NV21ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - NV21ToARGBRow = NV21ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_NV21TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - NV21ToARGBRow = NV21ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - NV21ToARGBRow = NV21ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_NV21TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - NV21ToARGBRow = NV21ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - NV21ToARGBRow = NV21ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - NV21ToARGBRow(src_y, src_uv, dst_argb, &kYuvI601Constants, width); - dst_argb += dst_stride_argb; - src_y += src_stride_y; - if (y & 1) { - src_uv += src_stride_uv; - } - } - return 0; -} - -// Convert M420 to ARGB. -LIBYUV_API -int M420ToARGB(const uint8* src_m420, int src_stride_m420, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*NV12ToARGBRow)(const uint8* y_buf, - const uint8* uv_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = NV12ToARGBRow_C; - if (!src_m420 || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } -#if defined(HAS_NV12TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - NV12ToARGBRow = NV12ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - NV12ToARGBRow = NV12ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_NV12TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - NV12ToARGBRow = NV12ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - NV12ToARGBRow = NV12ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_NV12TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - NV12ToARGBRow = NV12ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - NV12ToARGBRow = NV12ToARGBRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - NV12ToARGBRow(src_m420, src_m420 + src_stride_m420 * 2, dst_argb, - &kYuvI601Constants, width); - NV12ToARGBRow(src_m420 + src_stride_m420, src_m420 + src_stride_m420 * 2, - dst_argb + dst_stride_argb, &kYuvI601Constants, width); - dst_argb += dst_stride_argb * 2; - src_m420 += src_stride_m420 * 3; - } - if (height & 1) { - NV12ToARGBRow(src_m420, src_m420 + src_stride_m420 * 2, dst_argb, - &kYuvI601Constants, width); - } - return 0; -} - -// Convert YUY2 to ARGB. -LIBYUV_API -int YUY2ToARGB(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*YUY2ToARGBRow)(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) = - YUY2ToARGBRow_C; - if (!src_yuy2 || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_yuy2 = src_yuy2 + (height - 1) * src_stride_yuy2; - src_stride_yuy2 = -src_stride_yuy2; - } - // Coalesce rows. - if (src_stride_yuy2 == width * 2 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_yuy2 = dst_stride_argb = 0; - } -#if defined(HAS_YUY2TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - YUY2ToARGBRow = YUY2ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - YUY2ToARGBRow = YUY2ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_YUY2TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - YUY2ToARGBRow = YUY2ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - YUY2ToARGBRow = YUY2ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_YUY2TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - YUY2ToARGBRow = YUY2ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - YUY2ToARGBRow = YUY2ToARGBRow_NEON; - } - } -#endif - for (y = 0; y < height; ++y) { - YUY2ToARGBRow(src_yuy2, dst_argb, &kYuvI601Constants, width); - src_yuy2 += src_stride_yuy2; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert UYVY to ARGB. -LIBYUV_API -int UYVYToARGB(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*UYVYToARGBRow)(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) = - UYVYToARGBRow_C; - if (!src_uyvy || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_uyvy = src_uyvy + (height - 1) * src_stride_uyvy; - src_stride_uyvy = -src_stride_uyvy; - } - // Coalesce rows. - if (src_stride_uyvy == width * 2 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_uyvy = dst_stride_argb = 0; - } -#if defined(HAS_UYVYTOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - UYVYToARGBRow = UYVYToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - UYVYToARGBRow = UYVYToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_UYVYTOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - UYVYToARGBRow = UYVYToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - UYVYToARGBRow = UYVYToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_UYVYTOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - UYVYToARGBRow = UYVYToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - UYVYToARGBRow = UYVYToARGBRow_NEON; - } - } -#endif - for (y = 0; y < height; ++y) { - UYVYToARGBRow(src_uyvy, dst_argb, &kYuvI601Constants, width); - src_uyvy += src_stride_uyvy; - dst_argb += dst_stride_argb; - } - return 0; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/convert_from.cc b/telegramgallery/src/main/cpp/libyuv/source/convert_from.cc deleted file mode 100644 index 46abdeb..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/convert_from.cc +++ /dev/null @@ -1,1166 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/convert_from.h" - -#include "libyuv/basic_types.h" -#include "libyuv/convert.h" // For I420Copy -#include "libyuv/cpu_id.h" -#include "libyuv/planar_functions.h" -#include "libyuv/rotate.h" -#include "libyuv/scale.h" // For ScalePlane() -#include "libyuv/video_common.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#define SUBSAMPLE(v, a, s) (v < 0) ? (-((-v + a) >> s)) : ((v + a) >> s) -static __inline int Abs(int v) { - return v >= 0 ? v : -v; -} - -// I420 To any I4xx YUV format with mirroring. -static int I420ToI4xx(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_y_width, int src_y_height, - int dst_uv_width, int dst_uv_height) { - const int dst_y_width = Abs(src_y_width); - const int dst_y_height = Abs(src_y_height); - const int src_uv_width = SUBSAMPLE(src_y_width, 1, 1); - const int src_uv_height = SUBSAMPLE(src_y_height, 1, 1); - if (src_y_width == 0 || src_y_height == 0 || - dst_uv_width <= 0 || dst_uv_height <= 0) { - return -1; - } - ScalePlane(src_y, src_stride_y, src_y_width, src_y_height, - dst_y, dst_stride_y, dst_y_width, dst_y_height, - kFilterBilinear); - ScalePlane(src_u, src_stride_u, src_uv_width, src_uv_height, - dst_u, dst_stride_u, dst_uv_width, dst_uv_height, - kFilterBilinear); - ScalePlane(src_v, src_stride_v, src_uv_width, src_uv_height, - dst_v, dst_stride_v, dst_uv_width, dst_uv_height, - kFilterBilinear); - return 0; -} - -// 420 chroma is 1/2 width, 1/2 height -// 422 chroma is 1/2 width, 1x height -LIBYUV_API -int I420ToI422(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - const int dst_uv_width = (Abs(width) + 1) >> 1; - const int dst_uv_height = Abs(height); - return I420ToI4xx(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height, - dst_uv_width, dst_uv_height); -} - -// 420 chroma is 1/2 width, 1/2 height -// 444 chroma is 1x width, 1x height -LIBYUV_API -int I420ToI444(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - const int dst_uv_width = Abs(width); - const int dst_uv_height = Abs(height); - return I420ToI4xx(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height, - dst_uv_width, dst_uv_height); -} - -// 420 chroma is 1/2 width, 1/2 height -// 411 chroma is 1/4 width, 1x height -LIBYUV_API -int I420ToI411(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - const int dst_uv_width = (Abs(width) + 3) >> 2; - const int dst_uv_height = Abs(height); - return I420ToI4xx(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height, - dst_uv_width, dst_uv_height); -} - -// Copy to I400. Source can be I420,422,444,400,NV12,NV21 -LIBYUV_API -int I400Copy(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height) { - if (!src_y || !dst_y || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_stride_y = -src_stride_y; - } - CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - return 0; -} - -LIBYUV_API -int I422ToYUY2(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_yuy2, int dst_stride_yuy2, - int width, int height) { - int y; - void (*I422ToYUY2Row)(const uint8* src_y, const uint8* src_u, - const uint8* src_v, uint8* dst_yuy2, int width) = - I422ToYUY2Row_C; - if (!src_y || !src_u || !src_v || !dst_yuy2 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_yuy2 = dst_yuy2 + (height - 1) * dst_stride_yuy2; - dst_stride_yuy2 = -dst_stride_yuy2; - } - // Coalesce rows. - if (src_stride_y == width && - src_stride_u * 2 == width && - src_stride_v * 2 == width && - dst_stride_yuy2 == width * 2) { - width *= height; - height = 1; - src_stride_y = src_stride_u = src_stride_v = dst_stride_yuy2 = 0; - } -#if defined(HAS_I422TOYUY2ROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - I422ToYUY2Row = I422ToYUY2Row_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - I422ToYUY2Row = I422ToYUY2Row_SSE2; - } - } -#endif -#if defined(HAS_I422TOYUY2ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToYUY2Row = I422ToYUY2Row_Any_NEON; - if (IS_ALIGNED(width, 16)) { - I422ToYUY2Row = I422ToYUY2Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I422ToYUY2Row(src_y, src_u, src_v, dst_yuy2, width); - src_y += src_stride_y; - src_u += src_stride_u; - src_v += src_stride_v; - dst_yuy2 += dst_stride_yuy2; - } - return 0; -} - -LIBYUV_API -int I420ToYUY2(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_yuy2, int dst_stride_yuy2, - int width, int height) { - int y; - void (*I422ToYUY2Row)(const uint8* src_y, const uint8* src_u, - const uint8* src_v, uint8* dst_yuy2, int width) = - I422ToYUY2Row_C; - if (!src_y || !src_u || !src_v || !dst_yuy2 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_yuy2 = dst_yuy2 + (height - 1) * dst_stride_yuy2; - dst_stride_yuy2 = -dst_stride_yuy2; - } -#if defined(HAS_I422TOYUY2ROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - I422ToYUY2Row = I422ToYUY2Row_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - I422ToYUY2Row = I422ToYUY2Row_SSE2; - } - } -#endif -#if defined(HAS_I422TOYUY2ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToYUY2Row = I422ToYUY2Row_Any_NEON; - if (IS_ALIGNED(width, 16)) { - I422ToYUY2Row = I422ToYUY2Row_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - I422ToYUY2Row(src_y, src_u, src_v, dst_yuy2, width); - I422ToYUY2Row(src_y + src_stride_y, src_u, src_v, - dst_yuy2 + dst_stride_yuy2, width); - src_y += src_stride_y * 2; - src_u += src_stride_u; - src_v += src_stride_v; - dst_yuy2 += dst_stride_yuy2 * 2; - } - if (height & 1) { - I422ToYUY2Row(src_y, src_u, src_v, dst_yuy2, width); - } - return 0; -} - -LIBYUV_API -int I422ToUYVY(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_uyvy, int dst_stride_uyvy, - int width, int height) { - int y; - void (*I422ToUYVYRow)(const uint8* src_y, const uint8* src_u, - const uint8* src_v, uint8* dst_uyvy, int width) = - I422ToUYVYRow_C; - if (!src_y || !src_u || !src_v || !dst_uyvy || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_uyvy = dst_uyvy + (height - 1) * dst_stride_uyvy; - dst_stride_uyvy = -dst_stride_uyvy; - } - // Coalesce rows. - if (src_stride_y == width && - src_stride_u * 2 == width && - src_stride_v * 2 == width && - dst_stride_uyvy == width * 2) { - width *= height; - height = 1; - src_stride_y = src_stride_u = src_stride_v = dst_stride_uyvy = 0; - } -#if defined(HAS_I422TOUYVYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - I422ToUYVYRow = I422ToUYVYRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - I422ToUYVYRow = I422ToUYVYRow_SSE2; - } - } -#endif -#if defined(HAS_I422TOUYVYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToUYVYRow = I422ToUYVYRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - I422ToUYVYRow = I422ToUYVYRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I422ToUYVYRow(src_y, src_u, src_v, dst_uyvy, width); - src_y += src_stride_y; - src_u += src_stride_u; - src_v += src_stride_v; - dst_uyvy += dst_stride_uyvy; - } - return 0; -} - -LIBYUV_API -int I420ToUYVY(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_uyvy, int dst_stride_uyvy, - int width, int height) { - int y; - void (*I422ToUYVYRow)(const uint8* src_y, const uint8* src_u, - const uint8* src_v, uint8* dst_uyvy, int width) = - I422ToUYVYRow_C; - if (!src_y || !src_u || !src_v || !dst_uyvy || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_uyvy = dst_uyvy + (height - 1) * dst_stride_uyvy; - dst_stride_uyvy = -dst_stride_uyvy; - } -#if defined(HAS_I422TOUYVYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - I422ToUYVYRow = I422ToUYVYRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - I422ToUYVYRow = I422ToUYVYRow_SSE2; - } - } -#endif -#if defined(HAS_I422TOUYVYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToUYVYRow = I422ToUYVYRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - I422ToUYVYRow = I422ToUYVYRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - I422ToUYVYRow(src_y, src_u, src_v, dst_uyvy, width); - I422ToUYVYRow(src_y + src_stride_y, src_u, src_v, - dst_uyvy + dst_stride_uyvy, width); - src_y += src_stride_y * 2; - src_u += src_stride_u; - src_v += src_stride_v; - dst_uyvy += dst_stride_uyvy * 2; - } - if (height & 1) { - I422ToUYVYRow(src_y, src_u, src_v, dst_uyvy, width); - } - return 0; -} - -LIBYUV_API -int I420ToNV12(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height) { - int y; - void (*MergeUVRow_)(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) = MergeUVRow_C; - // Coalesce rows. - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - if (!src_y || !src_u || !src_v || !dst_y || !dst_uv || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - halfheight = (height + 1) >> 1; - dst_y = dst_y + (height - 1) * dst_stride_y; - dst_uv = dst_uv + (halfheight - 1) * dst_stride_uv; - dst_stride_y = -dst_stride_y; - dst_stride_uv = -dst_stride_uv; - } - if (src_stride_y == width && - dst_stride_y == width) { - width *= height; - height = 1; - src_stride_y = dst_stride_y = 0; - } - // Coalesce rows. - if (src_stride_u == halfwidth && - src_stride_v == halfwidth && - dst_stride_uv == halfwidth * 2) { - halfwidth *= halfheight; - halfheight = 1; - src_stride_u = src_stride_v = dst_stride_uv = 0; - } -#if defined(HAS_MERGEUVROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - MergeUVRow_ = MergeUVRow_Any_SSE2; - if (IS_ALIGNED(halfwidth, 16)) { - MergeUVRow_ = MergeUVRow_SSE2; - } - } -#endif -#if defined(HAS_MERGEUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - MergeUVRow_ = MergeUVRow_Any_AVX2; - if (IS_ALIGNED(halfwidth, 32)) { - MergeUVRow_ = MergeUVRow_AVX2; - } - } -#endif -#if defined(HAS_MERGEUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - MergeUVRow_ = MergeUVRow_Any_NEON; - if (IS_ALIGNED(halfwidth, 16)) { - MergeUVRow_ = MergeUVRow_NEON; - } - } -#endif - - CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - for (y = 0; y < halfheight; ++y) { - // Merge a row of U and V into a row of UV. - MergeUVRow_(src_u, src_v, dst_uv, halfwidth); - src_u += src_stride_u; - src_v += src_stride_v; - dst_uv += dst_stride_uv; - } - return 0; -} - -LIBYUV_API -int I420ToNV21(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height) { - return I420ToNV12(src_y, src_stride_y, - src_v, src_stride_v, - src_u, src_stride_u, - dst_y, dst_stride_y, - dst_vu, dst_stride_vu, - width, height); -} - -// Convert I422 to RGBA with matrix -static int I420ToRGBAMatrix(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - const struct YuvConstants* yuvconstants, - int width, int height) { - int y; - void (*I422ToRGBARow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToRGBARow_C; - if (!src_y || !src_u || !src_v || !dst_rgba || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_rgba = dst_rgba + (height - 1) * dst_stride_rgba; - dst_stride_rgba = -dst_stride_rgba; - } -#if defined(HAS_I422TORGBAROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToRGBARow = I422ToRGBARow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToRGBARow = I422ToRGBARow_SSSE3; - } - } -#endif -#if defined(HAS_I422TORGBAROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToRGBARow = I422ToRGBARow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToRGBARow = I422ToRGBARow_AVX2; - } - } -#endif -#if defined(HAS_I422TORGBAROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToRGBARow = I422ToRGBARow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToRGBARow = I422ToRGBARow_NEON; - } - } -#endif -#if defined(HAS_I422TORGBAROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && - IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && - IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && - IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && - IS_ALIGNED(dst_rgba, 4) && IS_ALIGNED(dst_stride_rgba, 4)) { - I422ToRGBARow = I422ToRGBARow_DSPR2; - } -#endif - - for (y = 0; y < height; ++y) { - I422ToRGBARow(src_y, src_u, src_v, dst_rgba, yuvconstants, width); - dst_rgba += dst_stride_rgba; - src_y += src_stride_y; - if (y & 1) { - src_u += src_stride_u; - src_v += src_stride_v; - } - } - return 0; -} - -// Convert I420 to RGBA. -LIBYUV_API -int I420ToRGBA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height) { - return I420ToRGBAMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_rgba, dst_stride_rgba, - &kYuvI601Constants, - width, height); -} - -// Convert I420 to BGRA. -LIBYUV_API -int I420ToBGRA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_bgra, int dst_stride_bgra, - int width, int height) { - return I420ToRGBAMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_bgra, dst_stride_bgra, - &kYvuI601Constants, // Use Yvu matrix - width, height); -} - -// Convert I420 to RGB24 with matrix -static int I420ToRGB24Matrix(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgb24, int dst_stride_rgb24, - const struct YuvConstants* yuvconstants, - int width, int height) { - int y; - void (*I422ToRGB24Row)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToRGB24Row_C; - if (!src_y || !src_u || !src_v || !dst_rgb24 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_rgb24 = dst_rgb24 + (height - 1) * dst_stride_rgb24; - dst_stride_rgb24 = -dst_stride_rgb24; - } -#if defined(HAS_I422TORGB24ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToRGB24Row = I422ToRGB24Row_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToRGB24Row = I422ToRGB24Row_SSSE3; - } - } -#endif -#if defined(HAS_I422TORGB24ROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToRGB24Row = I422ToRGB24Row_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToRGB24Row = I422ToRGB24Row_AVX2; - } - } -#endif -#if defined(HAS_I422TORGB24ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToRGB24Row = I422ToRGB24Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToRGB24Row = I422ToRGB24Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I422ToRGB24Row(src_y, src_u, src_v, dst_rgb24, yuvconstants, width); - dst_rgb24 += dst_stride_rgb24; - src_y += src_stride_y; - if (y & 1) { - src_u += src_stride_u; - src_v += src_stride_v; - } - } - return 0; -} - -// Convert I420 to RGB24. -LIBYUV_API -int I420ToRGB24(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height) { - return I420ToRGB24Matrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_rgb24, dst_stride_rgb24, - &kYuvI601Constants, - width, height); -} - -// Convert I420 to RAW. -LIBYUV_API -int I420ToRAW(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_raw, int dst_stride_raw, - int width, int height) { - return I420ToRGB24Matrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_raw, dst_stride_raw, - &kYvuI601Constants, // Use Yvu matrix - width, height); -} - -// Convert I420 to ARGB1555. -LIBYUV_API -int I420ToARGB1555(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb1555, int dst_stride_argb1555, - int width, int height) { - int y; - void (*I422ToARGB1555Row)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToARGB1555Row_C; - if (!src_y || !src_u || !src_v || !dst_argb1555 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb1555 = dst_argb1555 + (height - 1) * dst_stride_argb1555; - dst_stride_argb1555 = -dst_stride_argb1555; - } -#if defined(HAS_I422TOARGB1555ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToARGB1555Row = I422ToARGB1555Row_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToARGB1555Row = I422ToARGB1555Row_SSSE3; - } - } -#endif -#if defined(HAS_I422TOARGB1555ROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToARGB1555Row = I422ToARGB1555Row_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToARGB1555Row = I422ToARGB1555Row_AVX2; - } - } -#endif -#if defined(HAS_I422TOARGB1555ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToARGB1555Row = I422ToARGB1555Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToARGB1555Row = I422ToARGB1555Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I422ToARGB1555Row(src_y, src_u, src_v, dst_argb1555, &kYuvI601Constants, - width); - dst_argb1555 += dst_stride_argb1555; - src_y += src_stride_y; - if (y & 1) { - src_u += src_stride_u; - src_v += src_stride_v; - } - } - return 0; -} - - -// Convert I420 to ARGB4444. -LIBYUV_API -int I420ToARGB4444(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb4444, int dst_stride_argb4444, - int width, int height) { - int y; - void (*I422ToARGB4444Row)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToARGB4444Row_C; - if (!src_y || !src_u || !src_v || !dst_argb4444 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb4444 = dst_argb4444 + (height - 1) * dst_stride_argb4444; - dst_stride_argb4444 = -dst_stride_argb4444; - } -#if defined(HAS_I422TOARGB4444ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToARGB4444Row = I422ToARGB4444Row_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToARGB4444Row = I422ToARGB4444Row_SSSE3; - } - } -#endif -#if defined(HAS_I422TOARGB4444ROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToARGB4444Row = I422ToARGB4444Row_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToARGB4444Row = I422ToARGB4444Row_AVX2; - } - } -#endif -#if defined(HAS_I422TOARGB4444ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToARGB4444Row = I422ToARGB4444Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToARGB4444Row = I422ToARGB4444Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I422ToARGB4444Row(src_y, src_u, src_v, dst_argb4444, &kYuvI601Constants, - width); - dst_argb4444 += dst_stride_argb4444; - src_y += src_stride_y; - if (y & 1) { - src_u += src_stride_u; - src_v += src_stride_v; - } - } - return 0; -} - -// Convert I420 to RGB565. -LIBYUV_API -int I420ToRGB565(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height) { - int y; - void (*I422ToRGB565Row)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToRGB565Row_C; - if (!src_y || !src_u || !src_v || !dst_rgb565 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_rgb565 = dst_rgb565 + (height - 1) * dst_stride_rgb565; - dst_stride_rgb565 = -dst_stride_rgb565; - } -#if defined(HAS_I422TORGB565ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToRGB565Row = I422ToRGB565Row_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToRGB565Row = I422ToRGB565Row_SSSE3; - } - } -#endif -#if defined(HAS_I422TORGB565ROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToRGB565Row = I422ToRGB565Row_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToRGB565Row = I422ToRGB565Row_AVX2; - } - } -#endif -#if defined(HAS_I422TORGB565ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToRGB565Row = I422ToRGB565Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToRGB565Row = I422ToRGB565Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - I422ToRGB565Row(src_y, src_u, src_v, dst_rgb565, &kYuvI601Constants, width); - dst_rgb565 += dst_stride_rgb565; - src_y += src_stride_y; - if (y & 1) { - src_u += src_stride_u; - src_v += src_stride_v; - } - } - return 0; -} - -// Ordered 8x8 dither for 888 to 565. Values from 0 to 7. -static const uint8 kDither565_4x4[16] = { - 0, 4, 1, 5, - 6, 2, 7, 3, - 1, 5, 0, 4, - 7, 3, 6, 2, -}; - -// Convert I420 to RGB565 with dithering. -LIBYUV_API -int I420ToRGB565Dither(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgb565, int dst_stride_rgb565, - const uint8* dither4x4, int width, int height) { - int y; - void (*I422ToARGBRow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToARGBRow_C; - void (*ARGBToRGB565DitherRow)(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width) = ARGBToRGB565DitherRow_C; - if (!src_y || !src_u || !src_v || !dst_rgb565 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_rgb565 = dst_rgb565 + (height - 1) * dst_stride_rgb565; - dst_stride_rgb565 = -dst_stride_rgb565; - } - if (!dither4x4) { - dither4x4 = kDither565_4x4; - } -#if defined(HAS_I422TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToARGBRow = I422ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToARGBRow = I422ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_I422TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToARGBRow = I422ToARGBRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToARGBRow = I422ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_I422TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToARGBRow = I422ToARGBRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToARGBRow = I422ToARGBRow_NEON; - } - } -#endif -#if defined(HAS_I422TOARGBROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && - IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && - IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && - IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2)) { - I422ToARGBRow = I422ToARGBRow_DSPR2; - } -#endif -#if defined(HAS_ARGBTORGB565DITHERROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBTORGB565DITHERROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTORGB565DITHERROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_NEON; - } - } -#endif - { - // Allocate a row of argb. - align_buffer_64(row_argb, width * 4); - for (y = 0; y < height; ++y) { - I422ToARGBRow(src_y, src_u, src_v, row_argb, &kYuvI601Constants, width); - ARGBToRGB565DitherRow(row_argb, dst_rgb565, - *(uint32*)(dither4x4 + ((y & 3) << 2)), width); - dst_rgb565 += dst_stride_rgb565; - src_y += src_stride_y; - if (y & 1) { - src_u += src_stride_u; - src_v += src_stride_v; - } - } - free_aligned_buffer_64(row_argb); - } - return 0; -} - -// Convert I420 to specified format -LIBYUV_API -int ConvertFromI420(const uint8* y, int y_stride, - const uint8* u, int u_stride, - const uint8* v, int v_stride, - uint8* dst_sample, int dst_sample_stride, - int width, int height, - uint32 fourcc) { - uint32 format = CanonicalFourCC(fourcc); - int r = 0; - if (!y || !u|| !v || !dst_sample || - width <= 0 || height == 0) { - return -1; - } - switch (format) { - // Single plane formats - case FOURCC_YUY2: - r = I420ToYUY2(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 2, - width, height); - break; - case FOURCC_UYVY: - r = I420ToUYVY(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 2, - width, height); - break; - case FOURCC_RGBP: - r = I420ToRGB565(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 2, - width, height); - break; - case FOURCC_RGBO: - r = I420ToARGB1555(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 2, - width, height); - break; - case FOURCC_R444: - r = I420ToARGB4444(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 2, - width, height); - break; - case FOURCC_24BG: - r = I420ToRGB24(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 3, - width, height); - break; - case FOURCC_RAW: - r = I420ToRAW(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 3, - width, height); - break; - case FOURCC_ARGB: - r = I420ToARGB(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 4, - width, height); - break; - case FOURCC_BGRA: - r = I420ToBGRA(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 4, - width, height); - break; - case FOURCC_ABGR: - r = I420ToABGR(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 4, - width, height); - break; - case FOURCC_RGBA: - r = I420ToRGBA(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width * 4, - width, height); - break; - case FOURCC_I400: - r = I400Copy(y, y_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width, - width, height); - break; - case FOURCC_NV12: { - uint8* dst_uv = dst_sample + width * height; - r = I420ToNV12(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width, - dst_uv, - dst_sample_stride ? dst_sample_stride : width, - width, height); - break; - } - case FOURCC_NV21: { - uint8* dst_vu = dst_sample + width * height; - r = I420ToNV21(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, - dst_sample_stride ? dst_sample_stride : width, - dst_vu, - dst_sample_stride ? dst_sample_stride : width, - width, height); - break; - } - // TODO(fbarchard): Add M420. - // Triplanar formats - // TODO(fbarchard): halfstride instead of halfwidth - case FOURCC_I420: - case FOURCC_YV12: { - int halfwidth = (width + 1) / 2; - int halfheight = (height + 1) / 2; - uint8* dst_u; - uint8* dst_v; - if (format == FOURCC_YV12) { - dst_v = dst_sample + width * height; - dst_u = dst_v + halfwidth * halfheight; - } else { - dst_u = dst_sample + width * height; - dst_v = dst_u + halfwidth * halfheight; - } - r = I420Copy(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, width, - dst_u, halfwidth, - dst_v, halfwidth, - width, height); - break; - } - case FOURCC_I422: - case FOURCC_YV16: { - int halfwidth = (width + 1) / 2; - uint8* dst_u; - uint8* dst_v; - if (format == FOURCC_YV16) { - dst_v = dst_sample + width * height; - dst_u = dst_v + halfwidth * height; - } else { - dst_u = dst_sample + width * height; - dst_v = dst_u + halfwidth * height; - } - r = I420ToI422(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, width, - dst_u, halfwidth, - dst_v, halfwidth, - width, height); - break; - } - case FOURCC_I444: - case FOURCC_YV24: { - uint8* dst_u; - uint8* dst_v; - if (format == FOURCC_YV24) { - dst_v = dst_sample + width * height; - dst_u = dst_v + width * height; - } else { - dst_u = dst_sample + width * height; - dst_v = dst_u + width * height; - } - r = I420ToI444(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, width, - dst_u, width, - dst_v, width, - width, height); - break; - } - case FOURCC_I411: { - int quarterwidth = (width + 3) / 4; - uint8* dst_u = dst_sample + width * height; - uint8* dst_v = dst_u + quarterwidth * height; - r = I420ToI411(y, y_stride, - u, u_stride, - v, v_stride, - dst_sample, width, - dst_u, quarterwidth, - dst_v, quarterwidth, - width, height); - break; - } - - // Formats not supported - MJPG, biplanar, some rgb formats. - default: - return -1; // unknown fourcc - return failure code. - } - return r; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/convert_from_argb.cc b/telegramgallery/src/main/cpp/libyuv/source/convert_from_argb.cc deleted file mode 100644 index 2a8682b..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/convert_from_argb.cc +++ /dev/null @@ -1,1286 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/convert_from_argb.h" - -#include "libyuv/basic_types.h" -#include "libyuv/cpu_id.h" -#include "libyuv/planar_functions.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// ARGB little endian (bgra in memory) to I444 -LIBYUV_API -int ARGBToI444(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - void (*ARGBToUV444Row)(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) = ARGBToUV444Row_C; - if (!src_argb || !dst_y || !dst_u || !dst_v || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_y == width && - dst_stride_u == width && - dst_stride_v == width) { - width *= height; - height = 1; - src_stride_argb = dst_stride_y = dst_stride_u = dst_stride_v = 0; - } -#if defined(HAS_ARGBTOUV444ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUV444Row = ARGBToUV444Row_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUV444Row = ARGBToUV444Row_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOUV444ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUV444Row = ARGBToUV444Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToUV444Row = ARGBToUV444Row_NEON; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToUV444Row(src_argb, dst_u, dst_v, width); - ARGBToYRow(src_argb, dst_y, width); - src_argb += src_stride_argb; - dst_y += dst_stride_y; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - return 0; -} - -// ARGB little endian (bgra in memory) to I422 -LIBYUV_API -int ARGBToI422(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - if (!src_argb || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_y == width && - dst_stride_u * 2 == width && - dst_stride_v * 2 == width) { - width *= height; - height = 1; - src_stride_argb = dst_stride_y = dst_stride_u = dst_stride_v = 0; - } -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVRow = ARGBToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToUVRow(src_argb, 0, dst_u, dst_v, width); - ARGBToYRow(src_argb, dst_y, width); - src_argb += src_stride_argb; - dst_y += dst_stride_y; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - return 0; -} - -// ARGB little endian (bgra in memory) to I411 -LIBYUV_API -int ARGBToI411(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*ARGBToUV411Row)(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) = ARGBToUV411Row_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - if (!src_argb || !dst_y || !dst_u || !dst_v || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_y == width && - dst_stride_u * 4 == width && - dst_stride_v * 4 == width) { - width *= height; - height = 1; - src_stride_argb = dst_stride_y = dst_stride_u = dst_stride_v = 0; - } -#if defined(HAS_ARGBTOYROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUV411ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUV411Row = ARGBToUV411Row_Any_NEON; - if (IS_ALIGNED(width, 32)) { - ARGBToUV411Row = ARGBToUV411Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToUV411Row(src_argb, dst_u, dst_v, width); - ARGBToYRow(src_argb, dst_y, width); - src_argb += src_stride_argb; - dst_y += dst_stride_y; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - return 0; -} - -LIBYUV_API -int ARGBToNV12(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height) { - int y; - int halfwidth = (width + 1) >> 1; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - void (*MergeUVRow_)(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) = MergeUVRow_C; - if (!src_argb || - !dst_y || !dst_uv || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVRow = ARGBToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_NEON; - } - } -#endif -#if defined(HAS_MERGEUVROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - MergeUVRow_ = MergeUVRow_Any_SSE2; - if (IS_ALIGNED(halfwidth, 16)) { - MergeUVRow_ = MergeUVRow_SSE2; - } - } -#endif -#if defined(HAS_MERGEUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - MergeUVRow_ = MergeUVRow_Any_AVX2; - if (IS_ALIGNED(halfwidth, 32)) { - MergeUVRow_ = MergeUVRow_AVX2; - } - } -#endif -#if defined(HAS_MERGEUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - MergeUVRow_ = MergeUVRow_Any_NEON; - if (IS_ALIGNED(halfwidth, 16)) { - MergeUVRow_ = MergeUVRow_NEON; - } - } -#endif - { - // Allocate a rows of uv. - align_buffer_64(row_u, ((halfwidth + 31) & ~31) * 2); - uint8* row_v = row_u + ((halfwidth + 31) & ~31); - - for (y = 0; y < height - 1; y += 2) { - ARGBToUVRow(src_argb, src_stride_argb, row_u, row_v, width); - MergeUVRow_(row_u, row_v, dst_uv, halfwidth); - ARGBToYRow(src_argb, dst_y, width); - ARGBToYRow(src_argb + src_stride_argb, dst_y + dst_stride_y, width); - src_argb += src_stride_argb * 2; - dst_y += dst_stride_y * 2; - dst_uv += dst_stride_uv; - } - if (height & 1) { - ARGBToUVRow(src_argb, 0, row_u, row_v, width); - MergeUVRow_(row_u, row_v, dst_uv, halfwidth); - ARGBToYRow(src_argb, dst_y, width); - } - free_aligned_buffer_64(row_u); - } - return 0; -} - -// Same as NV12 but U and V swapped. -LIBYUV_API -int ARGBToNV21(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height) { - int y; - int halfwidth = (width + 1) >> 1; - void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - void (*MergeUVRow_)(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) = MergeUVRow_C; - if (!src_argb || - !dst_y || !dst_uv || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVRow = ARGBToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_NEON; - } - } -#endif -#if defined(HAS_MERGEUVROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - MergeUVRow_ = MergeUVRow_Any_SSE2; - if (IS_ALIGNED(halfwidth, 16)) { - MergeUVRow_ = MergeUVRow_SSE2; - } - } -#endif -#if defined(HAS_MERGEUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - MergeUVRow_ = MergeUVRow_Any_AVX2; - if (IS_ALIGNED(halfwidth, 32)) { - MergeUVRow_ = MergeUVRow_AVX2; - } - } -#endif -#if defined(HAS_MERGEUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - MergeUVRow_ = MergeUVRow_Any_NEON; - if (IS_ALIGNED(halfwidth, 16)) { - MergeUVRow_ = MergeUVRow_NEON; - } - } -#endif - { - // Allocate a rows of uv. - align_buffer_64(row_u, ((halfwidth + 31) & ~31) * 2); - uint8* row_v = row_u + ((halfwidth + 31) & ~31); - - for (y = 0; y < height - 1; y += 2) { - ARGBToUVRow(src_argb, src_stride_argb, row_u, row_v, width); - MergeUVRow_(row_v, row_u, dst_uv, halfwidth); - ARGBToYRow(src_argb, dst_y, width); - ARGBToYRow(src_argb + src_stride_argb, dst_y + dst_stride_y, width); - src_argb += src_stride_argb * 2; - dst_y += dst_stride_y * 2; - dst_uv += dst_stride_uv; - } - if (height & 1) { - ARGBToUVRow(src_argb, 0, row_u, row_v, width); - MergeUVRow_(row_v, row_u, dst_uv, halfwidth); - ARGBToYRow(src_argb, dst_y, width); - } - free_aligned_buffer_64(row_u); - } - return 0; -} - -// Convert ARGB to YUY2. -LIBYUV_API -int ARGBToYUY2(const uint8* src_argb, int src_stride_argb, - uint8* dst_yuy2, int dst_stride_yuy2, - int width, int height) { - int y; - void (*ARGBToUVRow)(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - void (*I422ToYUY2Row)(const uint8* src_y, const uint8* src_u, - const uint8* src_v, uint8* dst_yuy2, int width) = I422ToYUY2Row_C; - - if (!src_argb || !dst_yuy2 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_yuy2 = dst_yuy2 + (height - 1) * dst_stride_yuy2; - dst_stride_yuy2 = -dst_stride_yuy2; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_yuy2 == width * 2) { - width *= height; - height = 1; - src_stride_argb = dst_stride_yuy2 = 0; - } -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVRow = ARGBToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_NEON; - } - } -#endif -#if defined(HAS_I422TOYUY2ROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - I422ToYUY2Row = I422ToYUY2Row_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - I422ToYUY2Row = I422ToYUY2Row_SSE2; - } - } -#endif -#if defined(HAS_I422TOYUY2ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToYUY2Row = I422ToYUY2Row_Any_NEON; - if (IS_ALIGNED(width, 16)) { - I422ToYUY2Row = I422ToYUY2Row_NEON; - } - } -#endif - - { - // Allocate a rows of yuv. - align_buffer_64(row_y, ((width + 63) & ~63) * 2); - uint8* row_u = row_y + ((width + 63) & ~63); - uint8* row_v = row_u + ((width + 63) & ~63) / 2; - - for (y = 0; y < height; ++y) { - ARGBToUVRow(src_argb, 0, row_u, row_v, width); - ARGBToYRow(src_argb, row_y, width); - I422ToYUY2Row(row_y, row_u, row_v, dst_yuy2, width); - src_argb += src_stride_argb; - dst_yuy2 += dst_stride_yuy2; - } - - free_aligned_buffer_64(row_y); - } - return 0; -} - -// Convert ARGB to UYVY. -LIBYUV_API -int ARGBToUYVY(const uint8* src_argb, int src_stride_argb, - uint8* dst_uyvy, int dst_stride_uyvy, - int width, int height) { - int y; - void (*ARGBToUVRow)(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - void (*I422ToUYVYRow)(const uint8* src_y, const uint8* src_u, - const uint8* src_v, uint8* dst_uyvy, int width) = I422ToUYVYRow_C; - - if (!src_argb || !dst_uyvy || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_uyvy = dst_uyvy + (height - 1) * dst_stride_uyvy; - dst_stride_uyvy = -dst_stride_uyvy; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_uyvy == width * 2) { - width *= height; - height = 1; - src_stride_argb = dst_stride_uyvy = 0; - } -#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVRow = ARGBToUVRow_Any_SSSE3; - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_SSSE3; - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToUVRow = ARGBToUVRow_Any_AVX2; - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToUVRow = ARGBToUVRow_AVX2; - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVRow = ARGBToUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVRow = ARGBToUVRow_NEON; - } - } -#endif -#if defined(HAS_I422TOUYVYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - I422ToUYVYRow = I422ToUYVYRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - I422ToUYVYRow = I422ToUYVYRow_SSE2; - } - } -#endif -#if defined(HAS_I422TOUYVYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToUYVYRow = I422ToUYVYRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - I422ToUYVYRow = I422ToUYVYRow_NEON; - } - } -#endif - - { - // Allocate a rows of yuv. - align_buffer_64(row_y, ((width + 63) & ~63) * 2); - uint8* row_u = row_y + ((width + 63) & ~63); - uint8* row_v = row_u + ((width + 63) & ~63) / 2; - - for (y = 0; y < height; ++y) { - ARGBToUVRow(src_argb, 0, row_u, row_v, width); - ARGBToYRow(src_argb, row_y, width); - I422ToUYVYRow(row_y, row_u, row_v, dst_uyvy, width); - src_argb += src_stride_argb; - dst_uyvy += dst_stride_uyvy; - } - - free_aligned_buffer_64(row_y); - } - return 0; -} - -// Convert ARGB to I400. -LIBYUV_API -int ARGBToI400(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - int width, int height) { - int y; - void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = - ARGBToYRow_C; - if (!src_argb || !dst_y || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_y == width) { - width *= height; - height = 1; - src_stride_argb = dst_stride_y = 0; - } -#if defined(HAS_ARGBTOYROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToYRow = ARGBToYRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToYRow = ARGBToYRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToYRow = ARGBToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToYRow = ARGBToYRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYRow = ARGBToYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYRow = ARGBToYRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToYRow(src_argb, dst_y, width); - src_argb += src_stride_argb; - dst_y += dst_stride_y; - } - return 0; -} - -// Shuffle table for converting ARGB to RGBA. -static uvec8 kShuffleMaskARGBToRGBA = { - 3u, 0u, 1u, 2u, 7u, 4u, 5u, 6u, 11u, 8u, 9u, 10u, 15u, 12u, 13u, 14u -}; - -// Convert ARGB to RGBA. -LIBYUV_API -int ARGBToRGBA(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height) { - return ARGBShuffle(src_argb, src_stride_argb, - dst_rgba, dst_stride_rgba, - (const uint8*)(&kShuffleMaskARGBToRGBA), - width, height); -} - -// Convert ARGB To RGB24. -LIBYUV_API -int ARGBToRGB24(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height) { - int y; - void (*ARGBToRGB24Row)(const uint8* src_argb, uint8* dst_rgb, int width) = - ARGBToRGB24Row_C; - if (!src_argb || !dst_rgb24 || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_rgb24 == width * 3) { - width *= height; - height = 1; - src_stride_argb = dst_stride_rgb24 = 0; - } -#if defined(HAS_ARGBTORGB24ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToRGB24Row = ARGBToRGB24Row_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToRGB24Row = ARGBToRGB24Row_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTORGB24ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToRGB24Row = ARGBToRGB24Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToRGB24Row = ARGBToRGB24Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToRGB24Row(src_argb, dst_rgb24, width); - src_argb += src_stride_argb; - dst_rgb24 += dst_stride_rgb24; - } - return 0; -} - -// Convert ARGB To RAW. -LIBYUV_API -int ARGBToRAW(const uint8* src_argb, int src_stride_argb, - uint8* dst_raw, int dst_stride_raw, - int width, int height) { - int y; - void (*ARGBToRAWRow)(const uint8* src_argb, uint8* dst_rgb, int width) = - ARGBToRAWRow_C; - if (!src_argb || !dst_raw || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_raw == width * 3) { - width *= height; - height = 1; - src_stride_argb = dst_stride_raw = 0; - } -#if defined(HAS_ARGBTORAWROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToRAWRow = ARGBToRAWRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToRAWRow = ARGBToRAWRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTORAWROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToRAWRow = ARGBToRAWRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToRAWRow = ARGBToRAWRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToRAWRow(src_argb, dst_raw, width); - src_argb += src_stride_argb; - dst_raw += dst_stride_raw; - } - return 0; -} - -// Ordered 8x8 dither for 888 to 565. Values from 0 to 7. -static const uint8 kDither565_4x4[16] = { - 0, 4, 1, 5, - 6, 2, 7, 3, - 1, 5, 0, 4, - 7, 3, 6, 2, -}; - -// Convert ARGB To RGB565 with 4x4 dither matrix (16 bytes). -LIBYUV_API -int ARGBToRGB565Dither(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb565, int dst_stride_rgb565, - const uint8* dither4x4, int width, int height) { - int y; - void (*ARGBToRGB565DitherRow)(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width) = ARGBToRGB565DitherRow_C; - if (!src_argb || !dst_rgb565 || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - if (!dither4x4) { - dither4x4 = kDither565_4x4; - } -#if defined(HAS_ARGBTORGB565DITHERROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBTORGB565DITHERROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTORGB565DITHERROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToRGB565DitherRow = ARGBToRGB565DitherRow_NEON; - } - } -#endif - for (y = 0; y < height; ++y) { - ARGBToRGB565DitherRow(src_argb, dst_rgb565, - *(uint32*)(dither4x4 + ((y & 3) << 2)), width); - src_argb += src_stride_argb; - dst_rgb565 += dst_stride_rgb565; - } - return 0; -} - -// Convert ARGB To RGB565. -// TODO(fbarchard): Consider using dither function low level with zeros. -LIBYUV_API -int ARGBToRGB565(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height) { - int y; - void (*ARGBToRGB565Row)(const uint8* src_argb, uint8* dst_rgb, int width) = - ARGBToRGB565Row_C; - if (!src_argb || !dst_rgb565 || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_rgb565 == width * 2) { - width *= height; - height = 1; - src_stride_argb = dst_stride_rgb565 = 0; - } -#if defined(HAS_ARGBTORGB565ROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBToRGB565Row = ARGBToRGB565Row_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBToRGB565Row = ARGBToRGB565Row_SSE2; - } - } -#endif -#if defined(HAS_ARGBTORGB565ROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToRGB565Row = ARGBToRGB565Row_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBToRGB565Row = ARGBToRGB565Row_AVX2; - } - } -#endif -#if defined(HAS_ARGBTORGB565ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToRGB565Row = ARGBToRGB565Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToRGB565Row = ARGBToRGB565Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToRGB565Row(src_argb, dst_rgb565, width); - src_argb += src_stride_argb; - dst_rgb565 += dst_stride_rgb565; - } - return 0; -} - -// Convert ARGB To ARGB1555. -LIBYUV_API -int ARGBToARGB1555(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb1555, int dst_stride_argb1555, - int width, int height) { - int y; - void (*ARGBToARGB1555Row)(const uint8* src_argb, uint8* dst_rgb, int width) = - ARGBToARGB1555Row_C; - if (!src_argb || !dst_argb1555 || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb1555 == width * 2) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb1555 = 0; - } -#if defined(HAS_ARGBTOARGB1555ROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBToARGB1555Row = ARGBToARGB1555Row_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBToARGB1555Row = ARGBToARGB1555Row_SSE2; - } - } -#endif -#if defined(HAS_ARGBTOARGB1555ROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToARGB1555Row = ARGBToARGB1555Row_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBToARGB1555Row = ARGBToARGB1555Row_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOARGB1555ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToARGB1555Row = ARGBToARGB1555Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToARGB1555Row = ARGBToARGB1555Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToARGB1555Row(src_argb, dst_argb1555, width); - src_argb += src_stride_argb; - dst_argb1555 += dst_stride_argb1555; - } - return 0; -} - -// Convert ARGB To ARGB4444. -LIBYUV_API -int ARGBToARGB4444(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb4444, int dst_stride_argb4444, - int width, int height) { - int y; - void (*ARGBToARGB4444Row)(const uint8* src_argb, uint8* dst_rgb, int width) = - ARGBToARGB4444Row_C; - if (!src_argb || !dst_argb4444 || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb4444 == width * 2) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb4444 = 0; - } -#if defined(HAS_ARGBTOARGB4444ROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBToARGB4444Row = ARGBToARGB4444Row_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBToARGB4444Row = ARGBToARGB4444Row_SSE2; - } - } -#endif -#if defined(HAS_ARGBTOARGB4444ROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToARGB4444Row = ARGBToARGB4444Row_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBToARGB4444Row = ARGBToARGB4444Row_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOARGB4444ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToARGB4444Row = ARGBToARGB4444Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToARGB4444Row = ARGBToARGB4444Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToARGB4444Row(src_argb, dst_argb4444, width); - src_argb += src_stride_argb; - dst_argb4444 += dst_stride_argb4444; - } - return 0; -} - -// Convert ARGB to J420. (JPeg full range I420). -LIBYUV_API -int ARGBToJ420(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*ARGBToUVJRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVJRow_C; - void (*ARGBToYJRow)(const uint8* src_argb, uint8* dst_yj, int width) = - ARGBToYJRow_C; - if (!src_argb || - !dst_yj || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } -#if defined(HAS_ARGBTOYJROW_SSSE3) && defined(HAS_ARGBTOUVJROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVJRow = ARGBToUVJRow_Any_SSSE3; - ARGBToYJRow = ARGBToYJRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVJRow = ARGBToUVJRow_SSSE3; - ARGBToYJRow = ARGBToYJRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYJROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToYJRow = ARGBToYJRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToYJRow = ARGBToYJRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYJROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYJRow = ARGBToYJRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYJRow = ARGBToYJRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUVJROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVJRow = ARGBToUVJRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVJRow = ARGBToUVJRow_NEON; - } - } -#endif - - for (y = 0; y < height - 1; y += 2) { - ARGBToUVJRow(src_argb, src_stride_argb, dst_u, dst_v, width); - ARGBToYJRow(src_argb, dst_yj, width); - ARGBToYJRow(src_argb + src_stride_argb, dst_yj + dst_stride_yj, width); - src_argb += src_stride_argb * 2; - dst_yj += dst_stride_yj * 2; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - if (height & 1) { - ARGBToUVJRow(src_argb, 0, dst_u, dst_v, width); - ARGBToYJRow(src_argb, dst_yj, width); - } - return 0; -} - -// Convert ARGB to J422. (JPeg full range I422). -LIBYUV_API -int ARGBToJ422(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*ARGBToUVJRow)(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) = ARGBToUVJRow_C; - void (*ARGBToYJRow)(const uint8* src_argb, uint8* dst_yj, int width) = - ARGBToYJRow_C; - if (!src_argb || - !dst_yj || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_yj == width && - dst_stride_u * 2 == width && - dst_stride_v * 2 == width) { - width *= height; - height = 1; - src_stride_argb = dst_stride_yj = dst_stride_u = dst_stride_v = 0; - } -#if defined(HAS_ARGBTOYJROW_SSSE3) && defined(HAS_ARGBTOUVJROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVJRow = ARGBToUVJRow_Any_SSSE3; - ARGBToYJRow = ARGBToYJRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVJRow = ARGBToUVJRow_SSSE3; - ARGBToYJRow = ARGBToYJRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYJROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToYJRow = ARGBToYJRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToYJRow = ARGBToYJRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYJROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYJRow = ARGBToYJRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYJRow = ARGBToYJRow_NEON; - } - } -#endif -#if defined(HAS_ARGBTOUVJROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVJRow = ARGBToUVJRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVJRow = ARGBToUVJRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToUVJRow(src_argb, 0, dst_u, dst_v, width); - ARGBToYJRow(src_argb, dst_yj, width); - src_argb += src_stride_argb; - dst_yj += dst_stride_yj; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - return 0; -} - -// Convert ARGB to J400. -LIBYUV_API -int ARGBToJ400(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - int width, int height) { - int y; - void (*ARGBToYJRow)(const uint8* src_argb, uint8* dst_yj, int width) = - ARGBToYJRow_C; - if (!src_argb || !dst_yj || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_yj == width) { - width *= height; - height = 1; - src_stride_argb = dst_stride_yj = 0; - } -#if defined(HAS_ARGBTOYJROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToYJRow = ARGBToYJRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToYJRow = ARGBToYJRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYJROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToYJRow = ARGBToYJRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToYJRow = ARGBToYJRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYJROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYJRow = ARGBToYJRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYJRow = ARGBToYJRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBToYJRow(src_argb, dst_yj, width); - src_argb += src_stride_argb; - dst_yj += dst_stride_yj; - } - return 0; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/convert_jpeg.cc b/telegramgallery/src/main/cpp/libyuv/source/convert_jpeg.cc deleted file mode 100644 index 90f550a..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/convert_jpeg.cc +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/convert.h" -#include "libyuv/convert_argb.h" - -#ifdef HAVE_JPEG -#include "libyuv/mjpeg_decoder.h" -#endif - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#ifdef HAVE_JPEG -struct I420Buffers { - uint8* y; - int y_stride; - uint8* u; - int u_stride; - uint8* v; - int v_stride; - int w; - int h; -}; - -static void JpegCopyI420(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - I420Buffers* dest = (I420Buffers*)(opaque); - I420Copy(data[0], strides[0], - data[1], strides[1], - data[2], strides[2], - dest->y, dest->y_stride, - dest->u, dest->u_stride, - dest->v, dest->v_stride, - dest->w, rows); - dest->y += rows * dest->y_stride; - dest->u += ((rows + 1) >> 1) * dest->u_stride; - dest->v += ((rows + 1) >> 1) * dest->v_stride; - dest->h -= rows; -} - -static void JpegI422ToI420(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - I420Buffers* dest = (I420Buffers*)(opaque); - I422ToI420(data[0], strides[0], - data[1], strides[1], - data[2], strides[2], - dest->y, dest->y_stride, - dest->u, dest->u_stride, - dest->v, dest->v_stride, - dest->w, rows); - dest->y += rows * dest->y_stride; - dest->u += ((rows + 1) >> 1) * dest->u_stride; - dest->v += ((rows + 1) >> 1) * dest->v_stride; - dest->h -= rows; -} - -static void JpegI444ToI420(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - I420Buffers* dest = (I420Buffers*)(opaque); - I444ToI420(data[0], strides[0], - data[1], strides[1], - data[2], strides[2], - dest->y, dest->y_stride, - dest->u, dest->u_stride, - dest->v, dest->v_stride, - dest->w, rows); - dest->y += rows * dest->y_stride; - dest->u += ((rows + 1) >> 1) * dest->u_stride; - dest->v += ((rows + 1) >> 1) * dest->v_stride; - dest->h -= rows; -} - -static void JpegI411ToI420(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - I420Buffers* dest = (I420Buffers*)(opaque); - I411ToI420(data[0], strides[0], - data[1], strides[1], - data[2], strides[2], - dest->y, dest->y_stride, - dest->u, dest->u_stride, - dest->v, dest->v_stride, - dest->w, rows); - dest->y += rows * dest->y_stride; - dest->u += ((rows + 1) >> 1) * dest->u_stride; - dest->v += ((rows + 1) >> 1) * dest->v_stride; - dest->h -= rows; -} - -static void JpegI400ToI420(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - I420Buffers* dest = (I420Buffers*)(opaque); - I400ToI420(data[0], strides[0], - dest->y, dest->y_stride, - dest->u, dest->u_stride, - dest->v, dest->v_stride, - dest->w, rows); - dest->y += rows * dest->y_stride; - dest->u += ((rows + 1) >> 1) * dest->u_stride; - dest->v += ((rows + 1) >> 1) * dest->v_stride; - dest->h -= rows; -} - -// Query size of MJPG in pixels. -LIBYUV_API -int MJPGSize(const uint8* sample, size_t sample_size, - int* width, int* height) { - MJpegDecoder mjpeg_decoder; - LIBYUV_BOOL ret = mjpeg_decoder.LoadFrame(sample, sample_size); - if (ret) { - *width = mjpeg_decoder.GetWidth(); - *height = mjpeg_decoder.GetHeight(); - } - mjpeg_decoder.UnloadFrame(); - return ret ? 0 : -1; // -1 for runtime failure. -} - -// MJPG (Motion JPeg) to I420 -// TODO(fbarchard): review w and h requirement. dw and dh may be enough. -LIBYUV_API -int MJPGToI420(const uint8* sample, - size_t sample_size, - uint8* y, int y_stride, - uint8* u, int u_stride, - uint8* v, int v_stride, - int w, int h, - int dw, int dh) { - if (sample_size == kUnknownDataSize) { - // ERROR: MJPEG frame size unknown - return -1; - } - - // TODO(fbarchard): Port MJpeg to C. - MJpegDecoder mjpeg_decoder; - LIBYUV_BOOL ret = mjpeg_decoder.LoadFrame(sample, sample_size); - if (ret && (mjpeg_decoder.GetWidth() != w || - mjpeg_decoder.GetHeight() != h)) { - // ERROR: MJPEG frame has unexpected dimensions - mjpeg_decoder.UnloadFrame(); - return 1; // runtime failure - } - if (ret) { - I420Buffers bufs = { y, y_stride, u, u_stride, v, v_stride, dw, dh }; - // YUV420 - if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceYCbCr && - mjpeg_decoder.GetNumComponents() == 3 && - mjpeg_decoder.GetVertSampFactor(0) == 2 && - mjpeg_decoder.GetHorizSampFactor(0) == 2 && - mjpeg_decoder.GetVertSampFactor(1) == 1 && - mjpeg_decoder.GetHorizSampFactor(1) == 1 && - mjpeg_decoder.GetVertSampFactor(2) == 1 && - mjpeg_decoder.GetHorizSampFactor(2) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegCopyI420, &bufs, dw, dh); - // YUV422 - } else if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceYCbCr && - mjpeg_decoder.GetNumComponents() == 3 && - mjpeg_decoder.GetVertSampFactor(0) == 1 && - mjpeg_decoder.GetHorizSampFactor(0) == 2 && - mjpeg_decoder.GetVertSampFactor(1) == 1 && - mjpeg_decoder.GetHorizSampFactor(1) == 1 && - mjpeg_decoder.GetVertSampFactor(2) == 1 && - mjpeg_decoder.GetHorizSampFactor(2) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI422ToI420, &bufs, dw, dh); - // YUV444 - } else if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceYCbCr && - mjpeg_decoder.GetNumComponents() == 3 && - mjpeg_decoder.GetVertSampFactor(0) == 1 && - mjpeg_decoder.GetHorizSampFactor(0) == 1 && - mjpeg_decoder.GetVertSampFactor(1) == 1 && - mjpeg_decoder.GetHorizSampFactor(1) == 1 && - mjpeg_decoder.GetVertSampFactor(2) == 1 && - mjpeg_decoder.GetHorizSampFactor(2) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI444ToI420, &bufs, dw, dh); - // YUV411 - } else if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceYCbCr && - mjpeg_decoder.GetNumComponents() == 3 && - mjpeg_decoder.GetVertSampFactor(0) == 1 && - mjpeg_decoder.GetHorizSampFactor(0) == 4 && - mjpeg_decoder.GetVertSampFactor(1) == 1 && - mjpeg_decoder.GetHorizSampFactor(1) == 1 && - mjpeg_decoder.GetVertSampFactor(2) == 1 && - mjpeg_decoder.GetHorizSampFactor(2) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI411ToI420, &bufs, dw, dh); - // YUV400 - } else if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceGrayscale && - mjpeg_decoder.GetNumComponents() == 1 && - mjpeg_decoder.GetVertSampFactor(0) == 1 && - mjpeg_decoder.GetHorizSampFactor(0) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI400ToI420, &bufs, dw, dh); - } else { - // TODO(fbarchard): Implement conversion for any other colorspace/sample - // factors that occur in practice. 411 is supported by libjpeg - // ERROR: Unable to convert MJPEG frame because format is not supported - mjpeg_decoder.UnloadFrame(); - return 1; - } - } - return ret ? 0 : 1; -} - -#ifdef HAVE_JPEG -struct ARGBBuffers { - uint8* argb; - int argb_stride; - int w; - int h; -}; - -static void JpegI420ToARGB(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - ARGBBuffers* dest = (ARGBBuffers*)(opaque); - I420ToARGB(data[0], strides[0], - data[1], strides[1], - data[2], strides[2], - dest->argb, dest->argb_stride, - dest->w, rows); - dest->argb += rows * dest->argb_stride; - dest->h -= rows; -} - -static void JpegI422ToARGB(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - ARGBBuffers* dest = (ARGBBuffers*)(opaque); - I422ToARGB(data[0], strides[0], - data[1], strides[1], - data[2], strides[2], - dest->argb, dest->argb_stride, - dest->w, rows); - dest->argb += rows * dest->argb_stride; - dest->h -= rows; -} - -static void JpegI444ToARGB(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - ARGBBuffers* dest = (ARGBBuffers*)(opaque); - I444ToARGB(data[0], strides[0], - data[1], strides[1], - data[2], strides[2], - dest->argb, dest->argb_stride, - dest->w, rows); - dest->argb += rows * dest->argb_stride; - dest->h -= rows; -} - -static void JpegI411ToARGB(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - ARGBBuffers* dest = (ARGBBuffers*)(opaque); - I411ToARGB(data[0], strides[0], - data[1], strides[1], - data[2], strides[2], - dest->argb, dest->argb_stride, - dest->w, rows); - dest->argb += rows * dest->argb_stride; - dest->h -= rows; -} - -static void JpegI400ToARGB(void* opaque, - const uint8* const* data, - const int* strides, - int rows) { - ARGBBuffers* dest = (ARGBBuffers*)(opaque); - I400ToARGB(data[0], strides[0], - dest->argb, dest->argb_stride, - dest->w, rows); - dest->argb += rows * dest->argb_stride; - dest->h -= rows; -} - -// MJPG (Motion JPeg) to ARGB -// TODO(fbarchard): review w and h requirement. dw and dh may be enough. -LIBYUV_API -int MJPGToARGB(const uint8* sample, - size_t sample_size, - uint8* argb, int argb_stride, - int w, int h, - int dw, int dh) { - if (sample_size == kUnknownDataSize) { - // ERROR: MJPEG frame size unknown - return -1; - } - - // TODO(fbarchard): Port MJpeg to C. - MJpegDecoder mjpeg_decoder; - LIBYUV_BOOL ret = mjpeg_decoder.LoadFrame(sample, sample_size); - if (ret && (mjpeg_decoder.GetWidth() != w || - mjpeg_decoder.GetHeight() != h)) { - // ERROR: MJPEG frame has unexpected dimensions - mjpeg_decoder.UnloadFrame(); - return 1; // runtime failure - } - if (ret) { - ARGBBuffers bufs = { argb, argb_stride, dw, dh }; - // YUV420 - if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceYCbCr && - mjpeg_decoder.GetNumComponents() == 3 && - mjpeg_decoder.GetVertSampFactor(0) == 2 && - mjpeg_decoder.GetHorizSampFactor(0) == 2 && - mjpeg_decoder.GetVertSampFactor(1) == 1 && - mjpeg_decoder.GetHorizSampFactor(1) == 1 && - mjpeg_decoder.GetVertSampFactor(2) == 1 && - mjpeg_decoder.GetHorizSampFactor(2) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI420ToARGB, &bufs, dw, dh); - // YUV422 - } else if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceYCbCr && - mjpeg_decoder.GetNumComponents() == 3 && - mjpeg_decoder.GetVertSampFactor(0) == 1 && - mjpeg_decoder.GetHorizSampFactor(0) == 2 && - mjpeg_decoder.GetVertSampFactor(1) == 1 && - mjpeg_decoder.GetHorizSampFactor(1) == 1 && - mjpeg_decoder.GetVertSampFactor(2) == 1 && - mjpeg_decoder.GetHorizSampFactor(2) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI422ToARGB, &bufs, dw, dh); - // YUV444 - } else if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceYCbCr && - mjpeg_decoder.GetNumComponents() == 3 && - mjpeg_decoder.GetVertSampFactor(0) == 1 && - mjpeg_decoder.GetHorizSampFactor(0) == 1 && - mjpeg_decoder.GetVertSampFactor(1) == 1 && - mjpeg_decoder.GetHorizSampFactor(1) == 1 && - mjpeg_decoder.GetVertSampFactor(2) == 1 && - mjpeg_decoder.GetHorizSampFactor(2) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI444ToARGB, &bufs, dw, dh); - // YUV411 - } else if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceYCbCr && - mjpeg_decoder.GetNumComponents() == 3 && - mjpeg_decoder.GetVertSampFactor(0) == 1 && - mjpeg_decoder.GetHorizSampFactor(0) == 4 && - mjpeg_decoder.GetVertSampFactor(1) == 1 && - mjpeg_decoder.GetHorizSampFactor(1) == 1 && - mjpeg_decoder.GetVertSampFactor(2) == 1 && - mjpeg_decoder.GetHorizSampFactor(2) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI411ToARGB, &bufs, dw, dh); - // YUV400 - } else if (mjpeg_decoder.GetColorSpace() == - MJpegDecoder::kColorSpaceGrayscale && - mjpeg_decoder.GetNumComponents() == 1 && - mjpeg_decoder.GetVertSampFactor(0) == 1 && - mjpeg_decoder.GetHorizSampFactor(0) == 1) { - ret = mjpeg_decoder.DecodeToCallback(&JpegI400ToARGB, &bufs, dw, dh); - } else { - // TODO(fbarchard): Implement conversion for any other colorspace/sample - // factors that occur in practice. 411 is supported by libjpeg - // ERROR: Unable to convert MJPEG frame because format is not supported - mjpeg_decoder.UnloadFrame(); - return 1; - } - } - return ret ? 0 : 1; -} -#endif - -#endif - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/convert_to_argb.cc b/telegramgallery/src/main/cpp/libyuv/source/convert_to_argb.cc deleted file mode 100644 index aecdc80..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/convert_to_argb.cc +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/convert_argb.h" - -#include "libyuv/cpu_id.h" -#ifdef HAVE_JPEG -#include "libyuv/mjpeg_decoder.h" -#endif -#include "libyuv/rotate_argb.h" -#include "libyuv/row.h" -#include "libyuv/video_common.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Convert camera sample to ARGB with cropping, rotation and vertical flip. -// src_width is used for source stride computation -// src_height is used to compute location of planes, and indicate inversion -// sample_size is measured in bytes and is the size of the frame. -// With MJPEG it is the compressed size of the frame. -LIBYUV_API -int ConvertToARGB(const uint8* sample, size_t sample_size, - uint8* crop_argb, int argb_stride, - int crop_x, int crop_y, - int src_width, int src_height, - int crop_width, int crop_height, - enum RotationMode rotation, - uint32 fourcc) { - uint32 format = CanonicalFourCC(fourcc); - int aligned_src_width = (src_width + 1) & ~1; - const uint8* src; - const uint8* src_uv; - int abs_src_height = (src_height < 0) ? -src_height : src_height; - int inv_crop_height = (crop_height < 0) ? -crop_height : crop_height; - int r = 0; - - // One pass rotation is available for some formats. For the rest, convert - // to I420 (with optional vertical flipping) into a temporary I420 buffer, - // and then rotate the I420 to the final destination buffer. - // For in-place conversion, if destination crop_argb is same as source sample, - // also enable temporary buffer. - LIBYUV_BOOL need_buf = (rotation && format != FOURCC_ARGB) || - crop_argb == sample; - uint8* dest_argb = crop_argb; - int dest_argb_stride = argb_stride; - uint8* rotate_buffer = NULL; - int abs_crop_height = (crop_height < 0) ? -crop_height : crop_height; - - if (crop_argb == NULL || sample == NULL || - src_width <= 0 || crop_width <= 0 || - src_height == 0 || crop_height == 0) { - return -1; - } - if (src_height < 0) { - inv_crop_height = -inv_crop_height; - } - - if (need_buf) { - int argb_size = crop_width * 4 * abs_crop_height; - rotate_buffer = (uint8*)malloc(argb_size); - if (!rotate_buffer) { - return 1; // Out of memory runtime error. - } - crop_argb = rotate_buffer; - argb_stride = crop_width * 4; - } - - switch (format) { - // Single plane formats - case FOURCC_YUY2: - src = sample + (aligned_src_width * crop_y + crop_x) * 2; - r = YUY2ToARGB(src, aligned_src_width * 2, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_UYVY: - src = sample + (aligned_src_width * crop_y + crop_x) * 2; - r = UYVYToARGB(src, aligned_src_width * 2, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_24BG: - src = sample + (src_width * crop_y + crop_x) * 3; - r = RGB24ToARGB(src, src_width * 3, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_RAW: - src = sample + (src_width * crop_y + crop_x) * 3; - r = RAWToARGB(src, src_width * 3, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_ARGB: - src = sample + (src_width * crop_y + crop_x) * 4; - r = ARGBToARGB(src, src_width * 4, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_BGRA: - src = sample + (src_width * crop_y + crop_x) * 4; - r = BGRAToARGB(src, src_width * 4, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_ABGR: - src = sample + (src_width * crop_y + crop_x) * 4; - r = ABGRToARGB(src, src_width * 4, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_RGBA: - src = sample + (src_width * crop_y + crop_x) * 4; - r = RGBAToARGB(src, src_width * 4, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_RGBP: - src = sample + (src_width * crop_y + crop_x) * 2; - r = RGB565ToARGB(src, src_width * 2, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_RGBO: - src = sample + (src_width * crop_y + crop_x) * 2; - r = ARGB1555ToARGB(src, src_width * 2, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_R444: - src = sample + (src_width * crop_y + crop_x) * 2; - r = ARGB4444ToARGB(src, src_width * 2, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_I400: - src = sample + src_width * crop_y + crop_x; - r = I400ToARGB(src, src_width, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - - // Biplanar formats - case FOURCC_NV12: - src = sample + (src_width * crop_y + crop_x); - src_uv = sample + aligned_src_width * (src_height + crop_y / 2) + crop_x; - r = NV12ToARGB(src, src_width, - src_uv, aligned_src_width, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_NV21: - src = sample + (src_width * crop_y + crop_x); - src_uv = sample + aligned_src_width * (src_height + crop_y / 2) + crop_x; - // Call NV12 but with u and v parameters swapped. - r = NV21ToARGB(src, src_width, - src_uv, aligned_src_width, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - case FOURCC_M420: - src = sample + (src_width * crop_y) * 12 / 8 + crop_x; - r = M420ToARGB(src, src_width, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - // Triplanar formats - case FOURCC_I420: - case FOURCC_YV12: { - const uint8* src_y = sample + (src_width * crop_y + crop_x); - const uint8* src_u; - const uint8* src_v; - int halfwidth = (src_width + 1) / 2; - int halfheight = (abs_src_height + 1) / 2; - if (format == FOURCC_YV12) { - src_v = sample + src_width * abs_src_height + - (halfwidth * crop_y + crop_x) / 2; - src_u = sample + src_width * abs_src_height + - halfwidth * (halfheight + crop_y / 2) + crop_x / 2; - } else { - src_u = sample + src_width * abs_src_height + - (halfwidth * crop_y + crop_x) / 2; - src_v = sample + src_width * abs_src_height + - halfwidth * (halfheight + crop_y / 2) + crop_x / 2; - } - r = I420ToARGB(src_y, src_width, - src_u, halfwidth, - src_v, halfwidth, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - } - - case FOURCC_J420: { - const uint8* src_y = sample + (src_width * crop_y + crop_x); - const uint8* src_u; - const uint8* src_v; - int halfwidth = (src_width + 1) / 2; - int halfheight = (abs_src_height + 1) / 2; - src_u = sample + src_width * abs_src_height + - (halfwidth * crop_y + crop_x) / 2; - src_v = sample + src_width * abs_src_height + - halfwidth * (halfheight + crop_y / 2) + crop_x / 2; - r = J420ToARGB(src_y, src_width, - src_u, halfwidth, - src_v, halfwidth, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - } - - case FOURCC_I422: - case FOURCC_YV16: { - const uint8* src_y = sample + src_width * crop_y + crop_x; - const uint8* src_u; - const uint8* src_v; - int halfwidth = (src_width + 1) / 2; - if (format == FOURCC_YV16) { - src_v = sample + src_width * abs_src_height + - halfwidth * crop_y + crop_x / 2; - src_u = sample + src_width * abs_src_height + - halfwidth * (abs_src_height + crop_y) + crop_x / 2; - } else { - src_u = sample + src_width * abs_src_height + - halfwidth * crop_y + crop_x / 2; - src_v = sample + src_width * abs_src_height + - halfwidth * (abs_src_height + crop_y) + crop_x / 2; - } - r = I422ToARGB(src_y, src_width, - src_u, halfwidth, - src_v, halfwidth, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - } - case FOURCC_I444: - case FOURCC_YV24: { - const uint8* src_y = sample + src_width * crop_y + crop_x; - const uint8* src_u; - const uint8* src_v; - if (format == FOURCC_YV24) { - src_v = sample + src_width * (abs_src_height + crop_y) + crop_x; - src_u = sample + src_width * (abs_src_height * 2 + crop_y) + crop_x; - } else { - src_u = sample + src_width * (abs_src_height + crop_y) + crop_x; - src_v = sample + src_width * (abs_src_height * 2 + crop_y) + crop_x; - } - r = I444ToARGB(src_y, src_width, - src_u, src_width, - src_v, src_width, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - } - case FOURCC_I411: { - int quarterwidth = (src_width + 3) / 4; - const uint8* src_y = sample + src_width * crop_y + crop_x; - const uint8* src_u = sample + src_width * abs_src_height + - quarterwidth * crop_y + crop_x / 4; - const uint8* src_v = sample + src_width * abs_src_height + - quarterwidth * (abs_src_height + crop_y) + crop_x / 4; - r = I411ToARGB(src_y, src_width, - src_u, quarterwidth, - src_v, quarterwidth, - crop_argb, argb_stride, - crop_width, inv_crop_height); - break; - } -#ifdef HAVE_JPEG - case FOURCC_MJPG: - r = MJPGToARGB(sample, sample_size, - crop_argb, argb_stride, - src_width, abs_src_height, crop_width, inv_crop_height); - break; -#endif - default: - r = -1; // unknown fourcc - return failure code. - } - - if (need_buf) { - if (!r) { - r = ARGBRotate(crop_argb, argb_stride, - dest_argb, dest_argb_stride, - crop_width, abs_crop_height, rotation); - } - free(rotate_buffer); - } - - return r; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/convert_to_i420.cc b/telegramgallery/src/main/cpp/libyuv/source/convert_to_i420.cc deleted file mode 100644 index e5f307c..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/convert_to_i420.cc +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include - -#include "libyuv/convert.h" - -#include "libyuv/video_common.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Convert camera sample to I420 with cropping, rotation and vertical flip. -// src_width is used for source stride computation -// src_height is used to compute location of planes, and indicate inversion -// sample_size is measured in bytes and is the size of the frame. -// With MJPEG it is the compressed size of the frame. -LIBYUV_API -int ConvertToI420(const uint8* sample, - size_t sample_size, - uint8* y, int y_stride, - uint8* u, int u_stride, - uint8* v, int v_stride, - int crop_x, int crop_y, - int src_width, int src_height, - int crop_width, int crop_height, - enum RotationMode rotation, - uint32 fourcc) { - uint32 format = CanonicalFourCC(fourcc); - int aligned_src_width = (src_width + 1) & ~1; - const uint8* src; - const uint8* src_uv; - const int abs_src_height = (src_height < 0) ? -src_height : src_height; - // TODO(nisse): Why allow crop_height < 0? - const int abs_crop_height = (crop_height < 0) ? -crop_height : crop_height; - int r = 0; - LIBYUV_BOOL need_buf = (rotation && format != FOURCC_I420 && - format != FOURCC_NV12 && format != FOURCC_NV21 && - format != FOURCC_YV12) || y == sample; - uint8* tmp_y = y; - uint8* tmp_u = u; - uint8* tmp_v = v; - int tmp_y_stride = y_stride; - int tmp_u_stride = u_stride; - int tmp_v_stride = v_stride; - uint8* rotate_buffer = NULL; - const int inv_crop_height = - (src_height < 0) ? -abs_crop_height : abs_crop_height; - - if (!y || !u || !v || !sample || - src_width <= 0 || crop_width <= 0 || - src_height == 0 || crop_height == 0) { - return -1; - } - - // One pass rotation is available for some formats. For the rest, convert - // to I420 (with optional vertical flipping) into a temporary I420 buffer, - // and then rotate the I420 to the final destination buffer. - // For in-place conversion, if destination y is same as source sample, - // also enable temporary buffer. - if (need_buf) { - int y_size = crop_width * abs_crop_height; - int uv_size = ((crop_width + 1) / 2) * ((abs_crop_height + 1) / 2); - rotate_buffer = (uint8*)malloc(y_size + uv_size * 2); - if (!rotate_buffer) { - return 1; // Out of memory runtime error. - } - y = rotate_buffer; - u = y + y_size; - v = u + uv_size; - y_stride = crop_width; - u_stride = v_stride = ((crop_width + 1) / 2); - } - - switch (format) { - // Single plane formats - case FOURCC_YUY2: - src = sample + (aligned_src_width * crop_y + crop_x) * 2; - r = YUY2ToI420(src, aligned_src_width * 2, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_UYVY: - src = sample + (aligned_src_width * crop_y + crop_x) * 2; - r = UYVYToI420(src, aligned_src_width * 2, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_RGBP: - src = sample + (src_width * crop_y + crop_x) * 2; - r = RGB565ToI420(src, src_width * 2, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_RGBO: - src = sample + (src_width * crop_y + crop_x) * 2; - r = ARGB1555ToI420(src, src_width * 2, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_R444: - src = sample + (src_width * crop_y + crop_x) * 2; - r = ARGB4444ToI420(src, src_width * 2, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_24BG: - src = sample + (src_width * crop_y + crop_x) * 3; - r = RGB24ToI420(src, src_width * 3, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_RAW: - src = sample + (src_width * crop_y + crop_x) * 3; - r = RAWToI420(src, src_width * 3, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_ARGB: - src = sample + (src_width * crop_y + crop_x) * 4; - r = ARGBToI420(src, src_width * 4, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_BGRA: - src = sample + (src_width * crop_y + crop_x) * 4; - r = BGRAToI420(src, src_width * 4, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_ABGR: - src = sample + (src_width * crop_y + crop_x) * 4; - r = ABGRToI420(src, src_width * 4, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_RGBA: - src = sample + (src_width * crop_y + crop_x) * 4; - r = RGBAToI420(src, src_width * 4, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - case FOURCC_I400: - src = sample + src_width * crop_y + crop_x; - r = I400ToI420(src, src_width, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - // Biplanar formats - case FOURCC_NV12: - src = sample + (src_width * crop_y + crop_x); - src_uv = sample + (src_width * src_height) + - ((crop_y / 2) * aligned_src_width) + ((crop_x / 2) * 2); - r = NV12ToI420Rotate(src, src_width, - src_uv, aligned_src_width, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height, rotation); - break; - case FOURCC_NV21: - src = sample + (src_width * crop_y + crop_x); - src_uv = sample + (src_width * src_height) + - ((crop_y / 2) * aligned_src_width) + ((crop_x / 2) * 2); - // Call NV12 but with u and v parameters swapped. - r = NV12ToI420Rotate(src, src_width, - src_uv, aligned_src_width, - y, y_stride, - v, v_stride, - u, u_stride, - crop_width, inv_crop_height, rotation); - break; - case FOURCC_M420: - src = sample + (src_width * crop_y) * 12 / 8 + crop_x; - r = M420ToI420(src, src_width, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - // Triplanar formats - case FOURCC_I420: - case FOURCC_YV12: { - const uint8* src_y = sample + (src_width * crop_y + crop_x); - const uint8* src_u; - const uint8* src_v; - int halfwidth = (src_width + 1) / 2; - int halfheight = (abs_src_height + 1) / 2; - if (format == FOURCC_YV12) { - src_v = sample + src_width * abs_src_height + - (halfwidth * crop_y + crop_x) / 2; - src_u = sample + src_width * abs_src_height + - halfwidth * (halfheight + crop_y / 2) + crop_x / 2; - } else { - src_u = sample + src_width * abs_src_height + - (halfwidth * crop_y + crop_x) / 2; - src_v = sample + src_width * abs_src_height + - halfwidth * (halfheight + crop_y / 2) + crop_x / 2; - } - r = I420Rotate(src_y, src_width, - src_u, halfwidth, - src_v, halfwidth, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height, rotation); - break; - } - case FOURCC_I422: - case FOURCC_YV16: { - const uint8* src_y = sample + src_width * crop_y + crop_x; - const uint8* src_u; - const uint8* src_v; - int halfwidth = (src_width + 1) / 2; - if (format == FOURCC_YV16) { - src_v = sample + src_width * abs_src_height + - halfwidth * crop_y + crop_x / 2; - src_u = sample + src_width * abs_src_height + - halfwidth * (abs_src_height + crop_y) + crop_x / 2; - } else { - src_u = sample + src_width * abs_src_height + - halfwidth * crop_y + crop_x / 2; - src_v = sample + src_width * abs_src_height + - halfwidth * (abs_src_height + crop_y) + crop_x / 2; - } - r = I422ToI420(src_y, src_width, - src_u, halfwidth, - src_v, halfwidth, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - } - case FOURCC_I444: - case FOURCC_YV24: { - const uint8* src_y = sample + src_width * crop_y + crop_x; - const uint8* src_u; - const uint8* src_v; - if (format == FOURCC_YV24) { - src_v = sample + src_width * (abs_src_height + crop_y) + crop_x; - src_u = sample + src_width * (abs_src_height * 2 + crop_y) + crop_x; - } else { - src_u = sample + src_width * (abs_src_height + crop_y) + crop_x; - src_v = sample + src_width * (abs_src_height * 2 + crop_y) + crop_x; - } - r = I444ToI420(src_y, src_width, - src_u, src_width, - src_v, src_width, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - } - case FOURCC_I411: { - int quarterwidth = (src_width + 3) / 4; - const uint8* src_y = sample + src_width * crop_y + crop_x; - const uint8* src_u = sample + src_width * abs_src_height + - quarterwidth * crop_y + crop_x / 4; - const uint8* src_v = sample + src_width * abs_src_height + - quarterwidth * (abs_src_height + crop_y) + crop_x / 4; - r = I411ToI420(src_y, src_width, - src_u, quarterwidth, - src_v, quarterwidth, - y, y_stride, - u, u_stride, - v, v_stride, - crop_width, inv_crop_height); - break; - } -#ifdef HAVE_JPEG - case FOURCC_MJPG: - r = MJPGToI420(sample, sample_size, - y, y_stride, - u, u_stride, - v, v_stride, - src_width, abs_src_height, crop_width, inv_crop_height); - break; -#endif - default: - r = -1; // unknown fourcc - return failure code. - } - - if (need_buf) { - if (!r) { - r = I420Rotate(y, y_stride, - u, u_stride, - v, v_stride, - tmp_y, tmp_y_stride, - tmp_u, tmp_u_stride, - tmp_v, tmp_v_stride, - crop_width, abs_crop_height, rotation); - } - free(rotate_buffer); - } - - return r; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/cpu_id.cc b/telegramgallery/src/main/cpp/libyuv/source/cpu_id.cc deleted file mode 100644 index 84927eb..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/cpu_id.cc +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/cpu_id.h" - -#if defined(_MSC_VER) -#include // For __cpuidex() -#endif -#if !defined(__pnacl__) && !defined(__CLR_VER) && \ - !defined(__native_client__) && (defined(_M_IX86) || defined(_M_X64)) && \ - defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 160040219) -#include // For _xgetbv() -#endif - -#if !defined(__native_client__) -#include // For getenv() -#endif - -// For ArmCpuCaps() but unittested on all platforms -#include -#include - -#include "libyuv/basic_types.h" // For CPU_X86 - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// For functions that use the stack and have runtime checks for overflow, -// use SAFEBUFFERS to avoid additional check. -#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 160040219) && \ - !defined(__clang__) -#define SAFEBUFFERS __declspec(safebuffers) -#else -#define SAFEBUFFERS -#endif - -// Low level cpuid for X86. -#if (defined(_M_IX86) || defined(_M_X64) || \ - defined(__i386__) || defined(__x86_64__)) && \ - !defined(__pnacl__) && !defined(__CLR_VER) -LIBYUV_API -void CpuId(uint32 info_eax, uint32 info_ecx, uint32* cpu_info) { -#if defined(_MSC_VER) -// Visual C version uses intrinsic or inline x86 assembly. -#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 160040219) - __cpuidex((int*)(cpu_info), info_eax, info_ecx); -#elif defined(_M_IX86) - __asm { - mov eax, info_eax - mov ecx, info_ecx - mov edi, cpu_info - cpuid - mov [edi], eax - mov [edi + 4], ebx - mov [edi + 8], ecx - mov [edi + 12], edx - } -#else // Visual C but not x86 - if (info_ecx == 0) { - __cpuid((int*)(cpu_info), info_eax); - } else { - cpu_info[3] = cpu_info[2] = cpu_info[1] = cpu_info[0] = 0; - } -#endif -// GCC version uses inline x86 assembly. -#else // defined(_MSC_VER) - uint32 info_ebx, info_edx; - asm volatile ( -#if defined( __i386__) && defined(__PIC__) - // Preserve ebx for fpic 32 bit. - "mov %%ebx, %%edi \n" - "cpuid \n" - "xchg %%edi, %%ebx \n" - : "=D" (info_ebx), -#else - "cpuid \n" - : "=b" (info_ebx), -#endif // defined( __i386__) && defined(__PIC__) - "+a" (info_eax), "+c" (info_ecx), "=d" (info_edx)); - cpu_info[0] = info_eax; - cpu_info[1] = info_ebx; - cpu_info[2] = info_ecx; - cpu_info[3] = info_edx; -#endif // defined(_MSC_VER) -} -#else // (defined(_M_IX86) || defined(_M_X64) ... -LIBYUV_API -void CpuId(uint32 eax, uint32 ecx, uint32* cpu_info) { - cpu_info[0] = cpu_info[1] = cpu_info[2] = cpu_info[3] = 0; -} -#endif - -// For VS2010 and earlier emit can be used: -// _asm _emit 0x0f _asm _emit 0x01 _asm _emit 0xd0 // For VS2010 and earlier. -// __asm { -// xor ecx, ecx // xcr 0 -// xgetbv -// mov xcr0, eax -// } -// For VS2013 and earlier 32 bit, the _xgetbv(0) optimizer produces bad code. -// https://code.google.com/p/libyuv/issues/detail?id=529 -#if defined(_M_IX86) && (_MSC_VER < 1900) -#pragma optimize("g", off) -#endif -#if (defined(_M_IX86) || defined(_M_X64) || \ - defined(__i386__) || defined(__x86_64__)) && \ - !defined(__pnacl__) && !defined(__CLR_VER) && !defined(__native_client__) -#define HAS_XGETBV -// X86 CPUs have xgetbv to detect OS saves high parts of ymm registers. -int GetXCR0() { - uint32 xcr0 = 0u; -#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 160040219) - xcr0 = (uint32)(_xgetbv(0)); // VS2010 SP1 required. -#elif defined(__i386__) || defined(__x86_64__) - asm(".byte 0x0f, 0x01, 0xd0" : "=a" (xcr0) : "c" (0) : "%edx"); -#endif // defined(__i386__) || defined(__x86_64__) - return xcr0; -} -#endif // defined(_M_IX86) || defined(_M_X64) .. -// Return optimization to previous setting. -#if defined(_M_IX86) && (_MSC_VER < 1900) -#pragma optimize("g", on) -#endif - -// based on libvpx arm_cpudetect.c -// For Arm, but public to allow testing on any CPU -LIBYUV_API SAFEBUFFERS -int ArmCpuCaps(const char* cpuinfo_name) { - char cpuinfo_line[512]; - FILE* f = fopen(cpuinfo_name, "r"); - if (!f) { - // Assume Neon if /proc/cpuinfo is unavailable. - // This will occur for Chrome sandbox for Pepper or Render process. - return kCpuHasNEON; - } - while (fgets(cpuinfo_line, sizeof(cpuinfo_line) - 1, f)) { - if (memcmp(cpuinfo_line, "Features", 8) == 0) { - char* p = strstr(cpuinfo_line, " neon"); - if (p && (p[5] == ' ' || p[5] == '\n')) { - fclose(f); - return kCpuHasNEON; - } - // aarch64 uses asimd for Neon. - p = strstr(cpuinfo_line, " asimd"); - if (p && (p[6] == ' ' || p[6] == '\n')) { - fclose(f); - return kCpuHasNEON; - } - } - } - fclose(f); - return 0; -} - -// CPU detect function for SIMD instruction sets. -LIBYUV_API -int cpu_info_ = 0; // cpu_info is not initialized yet. - -// Test environment variable for disabling CPU features. Any non-zero value -// to disable. Zero ignored to make it easy to set the variable on/off. -#if !defined(__native_client__) && !defined(_M_ARM) - -static LIBYUV_BOOL TestEnv(const char* name) { - const char* var = getenv(name); - if (var) { - if (var[0] != '0') { - return LIBYUV_TRUE; - } - } - return LIBYUV_FALSE; -} -#else // nacl does not support getenv(). -static LIBYUV_BOOL TestEnv(const char*) { - return LIBYUV_FALSE; -} -#endif - -LIBYUV_API SAFEBUFFERS -int InitCpuFlags(void) { - // TODO(fbarchard): swap kCpuInit logic so 0 means uninitialized. - int cpu_info = 0; -#if !defined(__pnacl__) && !defined(__CLR_VER) && defined(CPU_X86) - uint32 cpu_info0[4] = { 0, 0, 0, 0 }; - uint32 cpu_info1[4] = { 0, 0, 0, 0 }; - uint32 cpu_info7[4] = { 0, 0, 0, 0 }; - CpuId(0, 0, cpu_info0); - CpuId(1, 0, cpu_info1); - if (cpu_info0[0] >= 7) { - CpuId(7, 0, cpu_info7); - } - cpu_info = ((cpu_info1[3] & 0x04000000) ? kCpuHasSSE2 : 0) | - ((cpu_info1[2] & 0x00000200) ? kCpuHasSSSE3 : 0) | - ((cpu_info1[2] & 0x00080000) ? kCpuHasSSE41 : 0) | - ((cpu_info1[2] & 0x00100000) ? kCpuHasSSE42 : 0) | - ((cpu_info7[1] & 0x00000200) ? kCpuHasERMS : 0) | - ((cpu_info1[2] & 0x00001000) ? kCpuHasFMA3 : 0) | - kCpuHasX86; - -#ifdef HAS_XGETBV - // AVX requires CPU has AVX, XSAVE and OSXSave for xgetbv - if (((cpu_info1[2] & 0x1c000000) == 0x1c000000) && // AVX and OSXSave - ((GetXCR0() & 6) == 6)) { // Test OS saves YMM registers - cpu_info |= ((cpu_info7[1] & 0x00000020) ? kCpuHasAVX2 : 0) | kCpuHasAVX; - - // Detect AVX512bw - if ((GetXCR0() & 0xe0) == 0xe0) { - cpu_info |= (cpu_info7[1] & 0x40000000) ? kCpuHasAVX3 : 0; - } - } -#endif - - // Environment variable overrides for testing. - if (TestEnv("LIBYUV_DISABLE_X86")) { - cpu_info &= ~kCpuHasX86; - } - if (TestEnv("LIBYUV_DISABLE_SSE2")) { - cpu_info &= ~kCpuHasSSE2; - } - if (TestEnv("LIBYUV_DISABLE_SSSE3")) { - cpu_info &= ~kCpuHasSSSE3; - } - if (TestEnv("LIBYUV_DISABLE_SSE41")) { - cpu_info &= ~kCpuHasSSE41; - } - if (TestEnv("LIBYUV_DISABLE_SSE42")) { - cpu_info &= ~kCpuHasSSE42; - } - if (TestEnv("LIBYUV_DISABLE_AVX")) { - cpu_info &= ~kCpuHasAVX; - } - if (TestEnv("LIBYUV_DISABLE_AVX2")) { - cpu_info &= ~kCpuHasAVX2; - } - if (TestEnv("LIBYUV_DISABLE_ERMS")) { - cpu_info &= ~kCpuHasERMS; - } - if (TestEnv("LIBYUV_DISABLE_FMA3")) { - cpu_info &= ~kCpuHasFMA3; - } - if (TestEnv("LIBYUV_DISABLE_AVX3")) { - cpu_info &= ~kCpuHasAVX3; - } -#endif -#if defined(__mips__) && defined(__linux__) -#if defined(__mips_dspr2) - cpu_info |= kCpuHasDSPR2; -#endif - cpu_info |= kCpuHasMIPS; - if (getenv("LIBYUV_DISABLE_DSPR2")) { - cpu_info &= ~kCpuHasDSPR2; - } -#endif -#if defined(__arm__) || defined(__aarch64__) -// gcc -mfpu=neon defines __ARM_NEON__ -// __ARM_NEON__ generates code that requires Neon. NaCL also requires Neon. -// For Linux, /proc/cpuinfo can be tested but without that assume Neon. -#if defined(__ARM_NEON__) || defined(__native_client__) || !defined(__linux__) - cpu_info = kCpuHasNEON; -// For aarch64(arm64), /proc/cpuinfo's feature is not complete, e.g. no neon -// flag in it. -// So for aarch64, neon enabling is hard coded here. -#endif -#if defined(__aarch64__) - cpu_info = kCpuHasNEON; -#else - // Linux arm parse text file for neon detect. - cpu_info = ArmCpuCaps("/proc/cpuinfo"); -#endif - cpu_info |= kCpuHasARM; - if (TestEnv("LIBYUV_DISABLE_NEON")) { - cpu_info &= ~kCpuHasNEON; - } -#endif // __arm__ - if (TestEnv("LIBYUV_DISABLE_ASM")) { - cpu_info = 0; - } - cpu_info |= kCpuInitialized; - cpu_info_ = cpu_info; - return cpu_info; -} - -// Note that use of this function is not thread safe. -LIBYUV_API -void MaskCpuFlags(int enable_flags) { - cpu_info_ = InitCpuFlags() & enable_flags; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/mjpeg_decoder.cc b/telegramgallery/src/main/cpp/libyuv/source/mjpeg_decoder.cc deleted file mode 100644 index 5081841..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/mjpeg_decoder.cc +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/mjpeg_decoder.h" - -#ifdef HAVE_JPEG -#include - -#if !defined(__pnacl__) && !defined(__CLR_VER) && \ - !defined(COVERAGE_ENABLED) && !defined(TARGET_IPHONE_SIMULATOR) -// Must be included before jpeglib. -#include -#define HAVE_SETJMP - -#if defined(_MSC_VER) -// disable warning 4324: structure was padded due to __declspec(align()) -#pragma warning(disable:4324) -#endif - -#endif -struct FILE; // For jpeglib.h. - -// C++ build requires extern C for jpeg internals. -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#ifdef __cplusplus -} // extern "C" -#endif - -#include "libyuv/planar_functions.h" // For CopyPlane(). - -namespace libyuv { - -#ifdef HAVE_SETJMP -struct SetJmpErrorMgr { - jpeg_error_mgr base; // Must be at the top - jmp_buf setjmp_buffer; -}; -#endif - -const int MJpegDecoder::kColorSpaceUnknown = JCS_UNKNOWN; -const int MJpegDecoder::kColorSpaceGrayscale = JCS_GRAYSCALE; -const int MJpegDecoder::kColorSpaceRgb = JCS_RGB; -const int MJpegDecoder::kColorSpaceYCbCr = JCS_YCbCr; -const int MJpegDecoder::kColorSpaceCMYK = JCS_CMYK; -const int MJpegDecoder::kColorSpaceYCCK = JCS_YCCK; - -// Methods that are passed to jpeglib. -boolean fill_input_buffer(jpeg_decompress_struct* cinfo); -void init_source(jpeg_decompress_struct* cinfo); -void skip_input_data(jpeg_decompress_struct* cinfo, long num_bytes); // NOLINT -void term_source(jpeg_decompress_struct* cinfo); -void ErrorHandler(jpeg_common_struct* cinfo); - -MJpegDecoder::MJpegDecoder() - : has_scanline_padding_(LIBYUV_FALSE), - num_outbufs_(0), - scanlines_(NULL), - scanlines_sizes_(NULL), - databuf_(NULL), - databuf_strides_(NULL) { - decompress_struct_ = new jpeg_decompress_struct; - source_mgr_ = new jpeg_source_mgr; -#ifdef HAVE_SETJMP - error_mgr_ = new SetJmpErrorMgr; - decompress_struct_->err = jpeg_std_error(&error_mgr_->base); - // Override standard exit()-based error handler. - error_mgr_->base.error_exit = &ErrorHandler; -#endif - decompress_struct_->client_data = NULL; - source_mgr_->init_source = &init_source; - source_mgr_->fill_input_buffer = &fill_input_buffer; - source_mgr_->skip_input_data = &skip_input_data; - source_mgr_->resync_to_restart = &jpeg_resync_to_restart; - source_mgr_->term_source = &term_source; - jpeg_create_decompress(decompress_struct_); - decompress_struct_->src = source_mgr_; - buf_vec_.buffers = &buf_; - buf_vec_.len = 1; -} - -MJpegDecoder::~MJpegDecoder() { - jpeg_destroy_decompress(decompress_struct_); - delete decompress_struct_; - delete source_mgr_; -#ifdef HAVE_SETJMP - delete error_mgr_; -#endif - DestroyOutputBuffers(); -} - -LIBYUV_BOOL MJpegDecoder::LoadFrame(const uint8* src, size_t src_len) { - if (!ValidateJpeg(src, src_len)) { - return LIBYUV_FALSE; - } - - buf_.data = src; - buf_.len = static_cast(src_len); - buf_vec_.pos = 0; - decompress_struct_->client_data = &buf_vec_; -#ifdef HAVE_SETJMP - if (setjmp(error_mgr_->setjmp_buffer)) { - // We called jpeg_read_header, it experienced an error, and we called - // longjmp() and rewound the stack to here. Return error. - return LIBYUV_FALSE; - } -#endif - if (jpeg_read_header(decompress_struct_, TRUE) != JPEG_HEADER_OK) { - // ERROR: Bad MJPEG header - return LIBYUV_FALSE; - } - AllocOutputBuffers(GetNumComponents()); - for (int i = 0; i < num_outbufs_; ++i) { - int scanlines_size = GetComponentScanlinesPerImcuRow(i); - if (scanlines_sizes_[i] != scanlines_size) { - if (scanlines_[i]) { - delete scanlines_[i]; - } - scanlines_[i] = new uint8* [scanlines_size]; - scanlines_sizes_[i] = scanlines_size; - } - - // We allocate padding for the final scanline to pad it up to DCTSIZE bytes - // to avoid memory errors, since jpeglib only reads full MCUs blocks. For - // the preceding scanlines, the padding is not needed/wanted because the - // following addresses will already be valid (they are the initial bytes of - // the next scanline) and will be overwritten when jpeglib writes out that - // next scanline. - int databuf_stride = GetComponentStride(i); - int databuf_size = scanlines_size * databuf_stride; - if (databuf_strides_[i] != databuf_stride) { - if (databuf_[i]) { - delete databuf_[i]; - } - databuf_[i] = new uint8[databuf_size]; - databuf_strides_[i] = databuf_stride; - } - - if (GetComponentStride(i) != GetComponentWidth(i)) { - has_scanline_padding_ = LIBYUV_TRUE; - } - } - return LIBYUV_TRUE; -} - -static int DivideAndRoundUp(int numerator, int denominator) { - return (numerator + denominator - 1) / denominator; -} - -static int DivideAndRoundDown(int numerator, int denominator) { - return numerator / denominator; -} - -// Returns width of the last loaded frame. -int MJpegDecoder::GetWidth() { - return decompress_struct_->image_width; -} - -// Returns height of the last loaded frame. -int MJpegDecoder::GetHeight() { - return decompress_struct_->image_height; -} - -// Returns format of the last loaded frame. The return value is one of the -// kColorSpace* constants. -int MJpegDecoder::GetColorSpace() { - return decompress_struct_->jpeg_color_space; -} - -// Number of color components in the color space. -int MJpegDecoder::GetNumComponents() { - return decompress_struct_->num_components; -} - -// Sample factors of the n-th component. -int MJpegDecoder::GetHorizSampFactor(int component) { - return decompress_struct_->comp_info[component].h_samp_factor; -} - -int MJpegDecoder::GetVertSampFactor(int component) { - return decompress_struct_->comp_info[component].v_samp_factor; -} - -int MJpegDecoder::GetHorizSubSampFactor(int component) { - return decompress_struct_->max_h_samp_factor / - GetHorizSampFactor(component); -} - -int MJpegDecoder::GetVertSubSampFactor(int component) { - return decompress_struct_->max_v_samp_factor / - GetVertSampFactor(component); -} - -int MJpegDecoder::GetImageScanlinesPerImcuRow() { - return decompress_struct_->max_v_samp_factor * DCTSIZE; -} - -int MJpegDecoder::GetComponentScanlinesPerImcuRow(int component) { - int vs = GetVertSubSampFactor(component); - return DivideAndRoundUp(GetImageScanlinesPerImcuRow(), vs); -} - -int MJpegDecoder::GetComponentWidth(int component) { - int hs = GetHorizSubSampFactor(component); - return DivideAndRoundUp(GetWidth(), hs); -} - -int MJpegDecoder::GetComponentHeight(int component) { - int vs = GetVertSubSampFactor(component); - return DivideAndRoundUp(GetHeight(), vs); -} - -// Get width in bytes padded out to a multiple of DCTSIZE -int MJpegDecoder::GetComponentStride(int component) { - return (GetComponentWidth(component) + DCTSIZE - 1) & ~(DCTSIZE - 1); -} - -int MJpegDecoder::GetComponentSize(int component) { - return GetComponentWidth(component) * GetComponentHeight(component); -} - -LIBYUV_BOOL MJpegDecoder::UnloadFrame() { -#ifdef HAVE_SETJMP - if (setjmp(error_mgr_->setjmp_buffer)) { - // We called jpeg_abort_decompress, it experienced an error, and we called - // longjmp() and rewound the stack to here. Return error. - return LIBYUV_FALSE; - } -#endif - jpeg_abort_decompress(decompress_struct_); - return LIBYUV_TRUE; -} - -// TODO(fbarchard): Allow rectangle to be specified: x, y, width, height. -LIBYUV_BOOL MJpegDecoder::DecodeToBuffers( - uint8** planes, int dst_width, int dst_height) { - if (dst_width != GetWidth() || - dst_height > GetHeight()) { - // ERROR: Bad dimensions - return LIBYUV_FALSE; - } -#ifdef HAVE_SETJMP - if (setjmp(error_mgr_->setjmp_buffer)) { - // We called into jpeglib, it experienced an error sometime during this - // function call, and we called longjmp() and rewound the stack to here. - // Return error. - return LIBYUV_FALSE; - } -#endif - if (!StartDecode()) { - return LIBYUV_FALSE; - } - SetScanlinePointers(databuf_); - int lines_left = dst_height; - // Compute amount of lines to skip to implement vertical crop. - // TODO(fbarchard): Ensure skip is a multiple of maximum component - // subsample. ie 2 - int skip = (GetHeight() - dst_height) / 2; - if (skip > 0) { - // There is no API to skip lines in the output data, so we read them - // into the temp buffer. - while (skip >= GetImageScanlinesPerImcuRow()) { - if (!DecodeImcuRow()) { - FinishDecode(); - return LIBYUV_FALSE; - } - skip -= GetImageScanlinesPerImcuRow(); - } - if (skip > 0) { - // Have a partial iMCU row left over to skip. Must read it and then - // copy the parts we want into the destination. - if (!DecodeImcuRow()) { - FinishDecode(); - return LIBYUV_FALSE; - } - for (int i = 0; i < num_outbufs_; ++i) { - // TODO(fbarchard): Compute skip to avoid this - assert(skip % GetVertSubSampFactor(i) == 0); - int rows_to_skip = - DivideAndRoundDown(skip, GetVertSubSampFactor(i)); - int scanlines_to_copy = GetComponentScanlinesPerImcuRow(i) - - rows_to_skip; - int data_to_skip = rows_to_skip * GetComponentStride(i); - CopyPlane(databuf_[i] + data_to_skip, GetComponentStride(i), - planes[i], GetComponentWidth(i), - GetComponentWidth(i), scanlines_to_copy); - planes[i] += scanlines_to_copy * GetComponentWidth(i); - } - lines_left -= (GetImageScanlinesPerImcuRow() - skip); - } - } - - // Read full MCUs but cropped horizontally - for (; lines_left > GetImageScanlinesPerImcuRow(); - lines_left -= GetImageScanlinesPerImcuRow()) { - if (!DecodeImcuRow()) { - FinishDecode(); - return LIBYUV_FALSE; - } - for (int i = 0; i < num_outbufs_; ++i) { - int scanlines_to_copy = GetComponentScanlinesPerImcuRow(i); - CopyPlane(databuf_[i], GetComponentStride(i), - planes[i], GetComponentWidth(i), - GetComponentWidth(i), scanlines_to_copy); - planes[i] += scanlines_to_copy * GetComponentWidth(i); - } - } - - if (lines_left > 0) { - // Have a partial iMCU row left over to decode. - if (!DecodeImcuRow()) { - FinishDecode(); - return LIBYUV_FALSE; - } - for (int i = 0; i < num_outbufs_; ++i) { - int scanlines_to_copy = - DivideAndRoundUp(lines_left, GetVertSubSampFactor(i)); - CopyPlane(databuf_[i], GetComponentStride(i), - planes[i], GetComponentWidth(i), - GetComponentWidth(i), scanlines_to_copy); - planes[i] += scanlines_to_copy * GetComponentWidth(i); - } - } - return FinishDecode(); -} - -LIBYUV_BOOL MJpegDecoder::DecodeToCallback(CallbackFunction fn, void* opaque, - int dst_width, int dst_height) { - if (dst_width != GetWidth() || - dst_height > GetHeight()) { - // ERROR: Bad dimensions - return LIBYUV_FALSE; - } -#ifdef HAVE_SETJMP - if (setjmp(error_mgr_->setjmp_buffer)) { - // We called into jpeglib, it experienced an error sometime during this - // function call, and we called longjmp() and rewound the stack to here. - // Return error. - return LIBYUV_FALSE; - } -#endif - if (!StartDecode()) { - return LIBYUV_FALSE; - } - SetScanlinePointers(databuf_); - int lines_left = dst_height; - // TODO(fbarchard): Compute amount of lines to skip to implement vertical crop - int skip = (GetHeight() - dst_height) / 2; - if (skip > 0) { - while (skip >= GetImageScanlinesPerImcuRow()) { - if (!DecodeImcuRow()) { - FinishDecode(); - return LIBYUV_FALSE; - } - skip -= GetImageScanlinesPerImcuRow(); - } - if (skip > 0) { - // Have a partial iMCU row left over to skip. - if (!DecodeImcuRow()) { - FinishDecode(); - return LIBYUV_FALSE; - } - for (int i = 0; i < num_outbufs_; ++i) { - // TODO(fbarchard): Compute skip to avoid this - assert(skip % GetVertSubSampFactor(i) == 0); - int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i)); - int data_to_skip = rows_to_skip * GetComponentStride(i); - // Change our own data buffer pointers so we can pass them to the - // callback. - databuf_[i] += data_to_skip; - } - int scanlines_to_copy = GetImageScanlinesPerImcuRow() - skip; - (*fn)(opaque, databuf_, databuf_strides_, scanlines_to_copy); - // Now change them back. - for (int i = 0; i < num_outbufs_; ++i) { - int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i)); - int data_to_skip = rows_to_skip * GetComponentStride(i); - databuf_[i] -= data_to_skip; - } - lines_left -= scanlines_to_copy; - } - } - // Read full MCUs until we get to the crop point. - for (; lines_left >= GetImageScanlinesPerImcuRow(); - lines_left -= GetImageScanlinesPerImcuRow()) { - if (!DecodeImcuRow()) { - FinishDecode(); - return LIBYUV_FALSE; - } - (*fn)(opaque, databuf_, databuf_strides_, GetImageScanlinesPerImcuRow()); - } - if (lines_left > 0) { - // Have a partial iMCU row left over to decode. - if (!DecodeImcuRow()) { - FinishDecode(); - return LIBYUV_FALSE; - } - (*fn)(opaque, databuf_, databuf_strides_, lines_left); - } - return FinishDecode(); -} - -void init_source(j_decompress_ptr cinfo) { - fill_input_buffer(cinfo); -} - -boolean fill_input_buffer(j_decompress_ptr cinfo) { - BufferVector* buf_vec = reinterpret_cast(cinfo->client_data); - if (buf_vec->pos >= buf_vec->len) { - assert(0 && "No more data"); - // ERROR: No more data - return FALSE; - } - cinfo->src->next_input_byte = buf_vec->buffers[buf_vec->pos].data; - cinfo->src->bytes_in_buffer = buf_vec->buffers[buf_vec->pos].len; - ++buf_vec->pos; - return TRUE; -} - -void skip_input_data(j_decompress_ptr cinfo, long num_bytes) { // NOLINT - cinfo->src->next_input_byte += num_bytes; -} - -void term_source(j_decompress_ptr cinfo) { - // Nothing to do. -} - -#ifdef HAVE_SETJMP -void ErrorHandler(j_common_ptr cinfo) { - // This is called when a jpeglib command experiences an error. Unfortunately - // jpeglib's error handling model is not very flexible, because it expects the - // error handler to not return--i.e., it wants the program to terminate. To - // recover from errors we use setjmp() as shown in their example. setjmp() is - // C's implementation for the "call with current continuation" functionality - // seen in some functional programming languages. - // A formatted message can be output, but is unsafe for release. -#ifdef DEBUG - char buf[JMSG_LENGTH_MAX]; - (*cinfo->err->format_message)(cinfo, buf); - // ERROR: Error in jpeglib: buf -#endif - - SetJmpErrorMgr* mgr = reinterpret_cast(cinfo->err); - // This rewinds the call stack to the point of the corresponding setjmp() - // and causes it to return (for a second time) with value 1. - longjmp(mgr->setjmp_buffer, 1); -} -#endif - -void MJpegDecoder::AllocOutputBuffers(int num_outbufs) { - if (num_outbufs != num_outbufs_) { - // We could perhaps optimize this case to resize the output buffers without - // necessarily having to delete and recreate each one, but it's not worth - // it. - DestroyOutputBuffers(); - - scanlines_ = new uint8** [num_outbufs]; - scanlines_sizes_ = new int[num_outbufs]; - databuf_ = new uint8* [num_outbufs]; - databuf_strides_ = new int[num_outbufs]; - - for (int i = 0; i < num_outbufs; ++i) { - scanlines_[i] = NULL; - scanlines_sizes_[i] = 0; - databuf_[i] = NULL; - databuf_strides_[i] = 0; - } - - num_outbufs_ = num_outbufs; - } -} - -void MJpegDecoder::DestroyOutputBuffers() { - for (int i = 0; i < num_outbufs_; ++i) { - delete [] scanlines_[i]; - delete [] databuf_[i]; - } - delete [] scanlines_; - delete [] databuf_; - delete [] scanlines_sizes_; - delete [] databuf_strides_; - scanlines_ = NULL; - databuf_ = NULL; - scanlines_sizes_ = NULL; - databuf_strides_ = NULL; - num_outbufs_ = 0; -} - -// JDCT_IFAST and do_block_smoothing improve performance substantially. -LIBYUV_BOOL MJpegDecoder::StartDecode() { - decompress_struct_->raw_data_out = TRUE; - decompress_struct_->dct_method = JDCT_IFAST; // JDCT_ISLOW is default - decompress_struct_->dither_mode = JDITHER_NONE; - // Not applicable to 'raw': - decompress_struct_->do_fancy_upsampling = (boolean)(LIBYUV_FALSE); - // Only for buffered mode: - decompress_struct_->enable_2pass_quant = (boolean)(LIBYUV_FALSE); - // Blocky but fast: - decompress_struct_->do_block_smoothing = (boolean)(LIBYUV_FALSE); - - if (!jpeg_start_decompress(decompress_struct_)) { - // ERROR: Couldn't start JPEG decompressor"; - return LIBYUV_FALSE; - } - return LIBYUV_TRUE; -} - -LIBYUV_BOOL MJpegDecoder::FinishDecode() { - // jpeglib considers it an error if we finish without decoding the whole - // image, so we call "abort" rather than "finish". - jpeg_abort_decompress(decompress_struct_); - return LIBYUV_TRUE; -} - -void MJpegDecoder::SetScanlinePointers(uint8** data) { - for (int i = 0; i < num_outbufs_; ++i) { - uint8* data_i = data[i]; - for (int j = 0; j < scanlines_sizes_[i]; ++j) { - scanlines_[i][j] = data_i; - data_i += GetComponentStride(i); - } - } -} - -inline LIBYUV_BOOL MJpegDecoder::DecodeImcuRow() { - return (unsigned int)(GetImageScanlinesPerImcuRow()) == - jpeg_read_raw_data(decompress_struct_, - scanlines_, - GetImageScanlinesPerImcuRow()); -} - -// The helper function which recognizes the jpeg sub-sampling type. -JpegSubsamplingType MJpegDecoder::JpegSubsamplingTypeHelper( - int* subsample_x, int* subsample_y, int number_of_components) { - if (number_of_components == 3) { // Color images. - if (subsample_x[0] == 1 && subsample_y[0] == 1 && - subsample_x[1] == 2 && subsample_y[1] == 2 && - subsample_x[2] == 2 && subsample_y[2] == 2) { - return kJpegYuv420; - } else if (subsample_x[0] == 1 && subsample_y[0] == 1 && - subsample_x[1] == 2 && subsample_y[1] == 1 && - subsample_x[2] == 2 && subsample_y[2] == 1) { - return kJpegYuv422; - } else if (subsample_x[0] == 1 && subsample_y[0] == 1 && - subsample_x[1] == 1 && subsample_y[1] == 1 && - subsample_x[2] == 1 && subsample_y[2] == 1) { - return kJpegYuv444; - } - } else if (number_of_components == 1) { // Grey-scale images. - if (subsample_x[0] == 1 && subsample_y[0] == 1) { - return kJpegYuv400; - } - } - return kJpegUnknown; -} - -} // namespace libyuv -#endif // HAVE_JPEG - diff --git a/telegramgallery/src/main/cpp/libyuv/source/mjpeg_validate.cc b/telegramgallery/src/main/cpp/libyuv/source/mjpeg_validate.cc deleted file mode 100644 index 9c48832..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/mjpeg_validate.cc +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/mjpeg_decoder.h" - -#include // For memchr. - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Helper function to scan for EOI marker (0xff 0xd9). -static LIBYUV_BOOL ScanEOI(const uint8* sample, size_t sample_size) { - if (sample_size >= 2) { - const uint8* end = sample + sample_size - 1; - const uint8* it = sample; - while (it < end) { - // TODO(fbarchard): scan for 0xd9 instead. - it = static_cast(memchr(it, 0xff, end - it)); - if (it == NULL) { - break; - } - if (it[1] == 0xd9) { - return LIBYUV_TRUE; // Success: Valid jpeg. - } - ++it; // Skip over current 0xff. - } - } - // ERROR: Invalid jpeg end code not found. Size sample_size - return LIBYUV_FALSE; -} - -// Helper function to validate the jpeg appears intact. -LIBYUV_BOOL ValidateJpeg(const uint8* sample, size_t sample_size) { - // Maximum size that ValidateJpeg will consider valid. - const size_t kMaxJpegSize = 0x7fffffffull; - const size_t kBackSearchSize = 1024; - if (sample_size < 64 || sample_size > kMaxJpegSize || !sample) { - // ERROR: Invalid jpeg size: sample_size - return LIBYUV_FALSE; - } - if (sample[0] != 0xff || sample[1] != 0xd8) { // SOI marker - // ERROR: Invalid jpeg initial start code - return LIBYUV_FALSE; - } - - // Look for the End Of Image (EOI) marker near the end of the buffer. - if (sample_size > kBackSearchSize) { - if (ScanEOI(sample + sample_size - kBackSearchSize, kBackSearchSize)) { - return LIBYUV_TRUE; // Success: Valid jpeg. - } - // Reduce search size for forward search. - sample_size = sample_size - kBackSearchSize + 1; - } - // Step over SOI marker and scan for EOI. - return ScanEOI(sample + 2, sample_size - 2); -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - diff --git a/telegramgallery/src/main/cpp/libyuv/source/planar_functions.cc b/telegramgallery/src/main/cpp/libyuv/source/planar_functions.cc deleted file mode 100644 index 237ab68..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/planar_functions.cc +++ /dev/null @@ -1,2671 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/planar_functions.h" - -#include // for memset() - -#include "libyuv/cpu_id.h" -#ifdef HAVE_JPEG -#include "libyuv/mjpeg_decoder.h" -#endif -#include "libyuv/row.h" -#include "libyuv/scale_row.h" // for ScaleRowDown2 - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Copy a plane of data -LIBYUV_API -void CopyPlane(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height) { - int y; - void (*CopyRow)(const uint8* src, uint8* dst, int width) = CopyRow_C; - // Coalesce rows. - if (src_stride_y == width && - dst_stride_y == width) { - width *= height; - height = 1; - src_stride_y = dst_stride_y = 0; - } - // Nothing to do. - if (src_y == dst_y && src_stride_y == dst_stride_y) { - return; - } -#if defined(HAS_COPYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - CopyRow = IS_ALIGNED(width, 32) ? CopyRow_SSE2 : CopyRow_Any_SSE2; - } -#endif -#if defined(HAS_COPYROW_AVX) - if (TestCpuFlag(kCpuHasAVX)) { - CopyRow = IS_ALIGNED(width, 64) ? CopyRow_AVX : CopyRow_Any_AVX; - } -#endif -#if defined(HAS_COPYROW_ERMS) - if (TestCpuFlag(kCpuHasERMS)) { - CopyRow = CopyRow_ERMS; - } -#endif -#if defined(HAS_COPYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - CopyRow = IS_ALIGNED(width, 32) ? CopyRow_NEON : CopyRow_Any_NEON; - } -#endif -#if defined(HAS_COPYROW_MIPS) - if (TestCpuFlag(kCpuHasMIPS)) { - CopyRow = CopyRow_MIPS; - } -#endif - - // Copy plane - for (y = 0; y < height; ++y) { - CopyRow(src_y, dst_y, width); - src_y += src_stride_y; - dst_y += dst_stride_y; - } -} - -LIBYUV_API -void CopyPlane_16(const uint16* src_y, int src_stride_y, - uint16* dst_y, int dst_stride_y, - int width, int height) { - int y; - void (*CopyRow)(const uint16* src, uint16* dst, int width) = CopyRow_16_C; - // Coalesce rows. - if (src_stride_y == width && - dst_stride_y == width) { - width *= height; - height = 1; - src_stride_y = dst_stride_y = 0; - } -#if defined(HAS_COPYROW_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(width, 32)) { - CopyRow = CopyRow_16_SSE2; - } -#endif -#if defined(HAS_COPYROW_16_ERMS) - if (TestCpuFlag(kCpuHasERMS)) { - CopyRow = CopyRow_16_ERMS; - } -#endif -#if defined(HAS_COPYROW_16_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 32)) { - CopyRow = CopyRow_16_NEON; - } -#endif -#if defined(HAS_COPYROW_16_MIPS) - if (TestCpuFlag(kCpuHasMIPS)) { - CopyRow = CopyRow_16_MIPS; - } -#endif - - // Copy plane - for (y = 0; y < height; ++y) { - CopyRow(src_y, dst_y, width); - src_y += src_stride_y; - dst_y += dst_stride_y; - } -} - -// Copy I422. -LIBYUV_API -int I422Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int halfwidth = (width + 1) >> 1; - if (!src_y || !src_u || !src_v || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_u = src_u + (height - 1) * src_stride_u; - src_v = src_v + (height - 1) * src_stride_v; - src_stride_y = -src_stride_y; - src_stride_u = -src_stride_u; - src_stride_v = -src_stride_v; - } - CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - CopyPlane(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth, height); - CopyPlane(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth, height); - return 0; -} - -// Copy I444. -LIBYUV_API -int I444Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - if (!src_y || !src_u || !src_v || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_u = src_u + (height - 1) * src_stride_u; - src_v = src_v + (height - 1) * src_stride_v; - src_stride_y = -src_stride_y; - src_stride_u = -src_stride_u; - src_stride_v = -src_stride_v; - } - - CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - CopyPlane(src_u, src_stride_u, dst_u, dst_stride_u, width, height); - CopyPlane(src_v, src_stride_v, dst_v, dst_stride_v, width, height); - return 0; -} - -// Copy I400. -LIBYUV_API -int I400ToI400(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height) { - if (!src_y || !dst_y || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_stride_y = -src_stride_y; - } - CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - return 0; -} - -// Convert I420 to I400. -LIBYUV_API -int I420ToI400(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - int width, int height) { - if (!src_y || !dst_y || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_stride_y = -src_stride_y; - } - CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - return 0; -} - -// Mirror a plane of data. -void MirrorPlane(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height) { - int y; - void (*MirrorRow)(const uint8* src, uint8* dst, int width) = MirrorRow_C; - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_stride_y = -src_stride_y; - } -#if defined(HAS_MIRRORROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - MirrorRow = MirrorRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - MirrorRow = MirrorRow_NEON; - } - } -#endif -#if defined(HAS_MIRRORROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - MirrorRow = MirrorRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - MirrorRow = MirrorRow_SSSE3; - } - } -#endif -#if defined(HAS_MIRRORROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - MirrorRow = MirrorRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - MirrorRow = MirrorRow_AVX2; - } - } -#endif -// TODO(fbarchard): Mirror on mips handle unaligned memory. -#if defined(HAS_MIRRORROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && - IS_ALIGNED(dst_y, 4) && IS_ALIGNED(dst_stride_y, 4)) { - MirrorRow = MirrorRow_DSPR2; - } -#endif - - // Mirror plane - for (y = 0; y < height; ++y) { - MirrorRow(src_y, dst_y, width); - src_y += src_stride_y; - dst_y += dst_stride_y; - } -} - -// Convert YUY2 to I422. -LIBYUV_API -int YUY2ToI422(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*YUY2ToUV422Row)(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width) = - YUY2ToUV422Row_C; - void (*YUY2ToYRow)(const uint8* src_yuy2, uint8* dst_y, int width) = - YUY2ToYRow_C; - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_yuy2 = src_yuy2 + (height - 1) * src_stride_yuy2; - src_stride_yuy2 = -src_stride_yuy2; - } - // Coalesce rows. - if (src_stride_yuy2 == width * 2 && - dst_stride_y == width && - dst_stride_u * 2 == width && - dst_stride_v * 2 == width) { - width *= height; - height = 1; - src_stride_yuy2 = dst_stride_y = dst_stride_u = dst_stride_v = 0; - } -#if defined(HAS_YUY2TOYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - YUY2ToUV422Row = YUY2ToUV422Row_Any_SSE2; - YUY2ToYRow = YUY2ToYRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - YUY2ToUV422Row = YUY2ToUV422Row_SSE2; - YUY2ToYRow = YUY2ToYRow_SSE2; - } - } -#endif -#if defined(HAS_YUY2TOYROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - YUY2ToUV422Row = YUY2ToUV422Row_Any_AVX2; - YUY2ToYRow = YUY2ToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - YUY2ToUV422Row = YUY2ToUV422Row_AVX2; - YUY2ToYRow = YUY2ToYRow_AVX2; - } - } -#endif -#if defined(HAS_YUY2TOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - YUY2ToYRow = YUY2ToYRow_Any_NEON; - if (width >= 16) { - YUY2ToUV422Row = YUY2ToUV422Row_Any_NEON; - } - if (IS_ALIGNED(width, 16)) { - YUY2ToYRow = YUY2ToYRow_NEON; - YUY2ToUV422Row = YUY2ToUV422Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - YUY2ToUV422Row(src_yuy2, dst_u, dst_v, width); - YUY2ToYRow(src_yuy2, dst_y, width); - src_yuy2 += src_stride_yuy2; - dst_y += dst_stride_y; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - return 0; -} - -// Convert UYVY to I422. -LIBYUV_API -int UYVYToI422(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - void (*UYVYToUV422Row)(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width) = - UYVYToUV422Row_C; - void (*UYVYToYRow)(const uint8* src_uyvy, - uint8* dst_y, int width) = UYVYToYRow_C; - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_uyvy = src_uyvy + (height - 1) * src_stride_uyvy; - src_stride_uyvy = -src_stride_uyvy; - } - // Coalesce rows. - if (src_stride_uyvy == width * 2 && - dst_stride_y == width && - dst_stride_u * 2 == width && - dst_stride_v * 2 == width) { - width *= height; - height = 1; - src_stride_uyvy = dst_stride_y = dst_stride_u = dst_stride_v = 0; - } -#if defined(HAS_UYVYTOYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - UYVYToUV422Row = UYVYToUV422Row_Any_SSE2; - UYVYToYRow = UYVYToYRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - UYVYToUV422Row = UYVYToUV422Row_SSE2; - UYVYToYRow = UYVYToYRow_SSE2; - } - } -#endif -#if defined(HAS_UYVYTOYROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - UYVYToUV422Row = UYVYToUV422Row_Any_AVX2; - UYVYToYRow = UYVYToYRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - UYVYToUV422Row = UYVYToUV422Row_AVX2; - UYVYToYRow = UYVYToYRow_AVX2; - } - } -#endif -#if defined(HAS_UYVYTOYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - UYVYToYRow = UYVYToYRow_Any_NEON; - if (width >= 16) { - UYVYToUV422Row = UYVYToUV422Row_Any_NEON; - } - if (IS_ALIGNED(width, 16)) { - UYVYToYRow = UYVYToYRow_NEON; - UYVYToUV422Row = UYVYToUV422Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - UYVYToUV422Row(src_uyvy, dst_u, dst_v, width); - UYVYToYRow(src_uyvy, dst_y, width); - src_uyvy += src_stride_uyvy; - dst_y += dst_stride_y; - dst_u += dst_stride_u; - dst_v += dst_stride_v; - } - return 0; -} - -// Mirror I400 with optional flipping -LIBYUV_API -int I400Mirror(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height) { - if (!src_y || !dst_y || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_stride_y = -src_stride_y; - } - - MirrorPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - return 0; -} - -// Mirror I420 with optional flipping -LIBYUV_API -int I420Mirror(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - if (!src_y || !src_u || !src_v || !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - halfheight = (height + 1) >> 1; - src_y = src_y + (height - 1) * src_stride_y; - src_u = src_u + (halfheight - 1) * src_stride_u; - src_v = src_v + (halfheight - 1) * src_stride_v; - src_stride_y = -src_stride_y; - src_stride_u = -src_stride_u; - src_stride_v = -src_stride_v; - } - - if (dst_y) { - MirrorPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height); - } - MirrorPlane(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth, halfheight); - MirrorPlane(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth, halfheight); - return 0; -} - -// ARGB mirror. -LIBYUV_API -int ARGBMirror(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBMirrorRow)(const uint8* src, uint8* dst, int width) = - ARGBMirrorRow_C; - if (!src_argb || !dst_argb || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } -#if defined(HAS_ARGBMIRRORROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBMirrorRow = ARGBMirrorRow_Any_NEON; - if (IS_ALIGNED(width, 4)) { - ARGBMirrorRow = ARGBMirrorRow_NEON; - } - } -#endif -#if defined(HAS_ARGBMIRRORROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBMirrorRow = ARGBMirrorRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBMirrorRow = ARGBMirrorRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBMIRRORROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBMirrorRow = ARGBMirrorRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBMirrorRow = ARGBMirrorRow_AVX2; - } - } -#endif - - // Mirror plane - for (y = 0; y < height; ++y) { - ARGBMirrorRow(src_argb, dst_argb, width); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Get a blender that optimized for the CPU and pixel count. -// As there are 6 blenders to choose from, the caller should try to use -// the same blend function for all pixels if possible. -LIBYUV_API -ARGBBlendRow GetARGBBlend() { - void (*ARGBBlendRow)(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width) = ARGBBlendRow_C; -#if defined(HAS_ARGBBLENDROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBBlendRow = ARGBBlendRow_SSSE3; - return ARGBBlendRow; - } -#endif -#if defined(HAS_ARGBBLENDROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBBlendRow = ARGBBlendRow_NEON; - } -#endif - return ARGBBlendRow; -} - -// Alpha Blend 2 ARGB images and store to destination. -LIBYUV_API -int ARGBBlend(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBBlendRow)(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width) = GetARGBBlend(); - if (!src_argb0 || !src_argb1 || !dst_argb || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - // Coalesce rows. - if (src_stride_argb0 == width * 4 && - src_stride_argb1 == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb0 = src_stride_argb1 = dst_stride_argb = 0; - } - - for (y = 0; y < height; ++y) { - ARGBBlendRow(src_argb0, src_argb1, dst_argb, width); - src_argb0 += src_stride_argb0; - src_argb1 += src_stride_argb1; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Alpha Blend plane and store to destination. -LIBYUV_API -int BlendPlane(const uint8* src_y0, int src_stride_y0, - const uint8* src_y1, int src_stride_y1, - const uint8* alpha, int alpha_stride, - uint8* dst_y, int dst_stride_y, - int width, int height) { - int y; - void (*BlendPlaneRow)(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width) = BlendPlaneRow_C; - if (!src_y0 || !src_y1 || !alpha || !dst_y || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_y = dst_y + (height - 1) * dst_stride_y; - dst_stride_y = -dst_stride_y; - } - - // Coalesce rows for Y plane. - if (src_stride_y0 == width && - src_stride_y1 == width && - alpha_stride == width && - dst_stride_y == width) { - width *= height; - height = 1; - src_stride_y0 = src_stride_y1 = alpha_stride = dst_stride_y = 0; - } - -#if defined(HAS_BLENDPLANEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - BlendPlaneRow = BlendPlaneRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - BlendPlaneRow = BlendPlaneRow_SSSE3; - } - } -#endif -#if defined(HAS_BLENDPLANEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - BlendPlaneRow = BlendPlaneRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - BlendPlaneRow = BlendPlaneRow_AVX2; - } - } -#endif - - for (y = 0; y < height; ++y) { - BlendPlaneRow(src_y0, src_y1, alpha, dst_y, width); - src_y0 += src_stride_y0; - src_y1 += src_stride_y1; - alpha += alpha_stride; - dst_y += dst_stride_y; - } - return 0; -} - -#define MAXTWIDTH 2048 -// Alpha Blend YUV images and store to destination. -LIBYUV_API -int I420Blend(const uint8* src_y0, int src_stride_y0, - const uint8* src_u0, int src_stride_u0, - const uint8* src_v0, int src_stride_v0, - const uint8* src_y1, int src_stride_y1, - const uint8* src_u1, int src_stride_u1, - const uint8* src_v1, int src_stride_v1, - const uint8* alpha, int alpha_stride, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height) { - int y; - // Half width/height for UV. - int halfwidth = (width + 1) >> 1; - void (*BlendPlaneRow)(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width) = BlendPlaneRow_C; - void (*ScaleRowDown2)(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) = ScaleRowDown2Box_C; - if (!src_y0 || !src_u0 || !src_v0 || !src_y1 || !src_u1 || !src_v1 || - !alpha || !dst_y || !dst_u || !dst_v || width <= 0 || height == 0) { - return -1; - } - - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_y = dst_y + (height - 1) * dst_stride_y; - dst_stride_y = -dst_stride_y; - } - - // Blend Y plane. - BlendPlane(src_y0, src_stride_y0, - src_y1, src_stride_y1, - alpha, alpha_stride, - dst_y, dst_stride_y, - width, height); - -#if defined(HAS_BLENDPLANEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - BlendPlaneRow = BlendPlaneRow_Any_SSSE3; - if (IS_ALIGNED(halfwidth, 8)) { - BlendPlaneRow = BlendPlaneRow_SSSE3; - } - } -#endif -#if defined(HAS_BLENDPLANEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - BlendPlaneRow = BlendPlaneRow_Any_AVX2; - if (IS_ALIGNED(halfwidth, 32)) { - BlendPlaneRow = BlendPlaneRow_AVX2; - } - } -#endif - if (!IS_ALIGNED(width, 2)) { - ScaleRowDown2 = ScaleRowDown2Box_Odd_C; - } -#if defined(HAS_SCALEROWDOWN2_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleRowDown2 = ScaleRowDown2Box_Odd_NEON; - if (IS_ALIGNED(width, 2)) { - ScaleRowDown2 = ScaleRowDown2Box_Any_NEON; - if (IS_ALIGNED(halfwidth, 16)) { - ScaleRowDown2 = ScaleRowDown2Box_NEON; - } - } - } -#endif -#if defined(HAS_SCALEROWDOWN2_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ScaleRowDown2 = ScaleRowDown2Box_Odd_SSSE3; - if (IS_ALIGNED(width, 2)) { - ScaleRowDown2 = ScaleRowDown2Box_Any_SSSE3; - if (IS_ALIGNED(halfwidth, 16)) { - ScaleRowDown2 = ScaleRowDown2Box_SSSE3; - } - } - } -#endif -#if defined(HAS_SCALEROWDOWN2_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ScaleRowDown2 = ScaleRowDown2Box_Odd_AVX2; - if (IS_ALIGNED(width, 2)) { - ScaleRowDown2 = ScaleRowDown2Box_Any_AVX2; - if (IS_ALIGNED(halfwidth, 32)) { - ScaleRowDown2 = ScaleRowDown2Box_AVX2; - } - } - } -#endif - - // Row buffer for intermediate alpha pixels. - align_buffer_64(halfalpha, halfwidth); - for (y = 0; y < height; y += 2) { - // last row of odd height image use 1 row of alpha instead of 2. - if (y == (height - 1)) { - alpha_stride = 0; - } - // Subsample 2 rows of UV to half width and half height. - ScaleRowDown2(alpha, alpha_stride, halfalpha, halfwidth); - alpha += alpha_stride * 2; - BlendPlaneRow(src_u0, src_u1, halfalpha, dst_u, halfwidth); - BlendPlaneRow(src_v0, src_v1, halfalpha, dst_v, halfwidth); - src_u0 += src_stride_u0; - src_u1 += src_stride_u1; - dst_u += dst_stride_u; - src_v0 += src_stride_v0; - src_v1 += src_stride_v1; - dst_v += dst_stride_v; - } - free_aligned_buffer_64(halfalpha); - return 0; -} - -// Multiply 2 ARGB images and store to destination. -LIBYUV_API -int ARGBMultiply(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBMultiplyRow)(const uint8* src0, const uint8* src1, uint8* dst, - int width) = ARGBMultiplyRow_C; - if (!src_argb0 || !src_argb1 || !dst_argb || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - // Coalesce rows. - if (src_stride_argb0 == width * 4 && - src_stride_argb1 == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb0 = src_stride_argb1 = dst_stride_argb = 0; - } -#if defined(HAS_ARGBMULTIPLYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBMultiplyRow = ARGBMultiplyRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBMultiplyRow = ARGBMultiplyRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBMULTIPLYROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBMultiplyRow = ARGBMultiplyRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBMultiplyRow = ARGBMultiplyRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBMULTIPLYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBMultiplyRow = ARGBMultiplyRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBMultiplyRow = ARGBMultiplyRow_NEON; - } - } -#endif - - // Multiply plane - for (y = 0; y < height; ++y) { - ARGBMultiplyRow(src_argb0, src_argb1, dst_argb, width); - src_argb0 += src_stride_argb0; - src_argb1 += src_stride_argb1; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Add 2 ARGB images and store to destination. -LIBYUV_API -int ARGBAdd(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBAddRow)(const uint8* src0, const uint8* src1, uint8* dst, - int width) = ARGBAddRow_C; - if (!src_argb0 || !src_argb1 || !dst_argb || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - // Coalesce rows. - if (src_stride_argb0 == width * 4 && - src_stride_argb1 == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb0 = src_stride_argb1 = dst_stride_argb = 0; - } -#if defined(HAS_ARGBADDROW_SSE2) && (defined(_MSC_VER) && !defined(__clang__)) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBAddRow = ARGBAddRow_SSE2; - } -#endif -#if defined(HAS_ARGBADDROW_SSE2) && !(defined(_MSC_VER) && !defined(__clang__)) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBAddRow = ARGBAddRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBAddRow = ARGBAddRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBADDROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBAddRow = ARGBAddRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBAddRow = ARGBAddRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBADDROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBAddRow = ARGBAddRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBAddRow = ARGBAddRow_NEON; - } - } -#endif - - // Add plane - for (y = 0; y < height; ++y) { - ARGBAddRow(src_argb0, src_argb1, dst_argb, width); - src_argb0 += src_stride_argb0; - src_argb1 += src_stride_argb1; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Subtract 2 ARGB images and store to destination. -LIBYUV_API -int ARGBSubtract(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBSubtractRow)(const uint8* src0, const uint8* src1, uint8* dst, - int width) = ARGBSubtractRow_C; - if (!src_argb0 || !src_argb1 || !dst_argb || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - // Coalesce rows. - if (src_stride_argb0 == width * 4 && - src_stride_argb1 == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb0 = src_stride_argb1 = dst_stride_argb = 0; - } -#if defined(HAS_ARGBSUBTRACTROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBSubtractRow = ARGBSubtractRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBSubtractRow = ARGBSubtractRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBSUBTRACTROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBSubtractRow = ARGBSubtractRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBSubtractRow = ARGBSubtractRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBSUBTRACTROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBSubtractRow = ARGBSubtractRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBSubtractRow = ARGBSubtractRow_NEON; - } - } -#endif - - // Subtract plane - for (y = 0; y < height; ++y) { - ARGBSubtractRow(src_argb0, src_argb1, dst_argb, width); - src_argb0 += src_stride_argb0; - src_argb1 += src_stride_argb1; - dst_argb += dst_stride_argb; - } - return 0; -} -// Convert I422 to RGBA with matrix -static int I422ToRGBAMatrix(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - const struct YuvConstants* yuvconstants, - int width, int height) { - int y; - void (*I422ToRGBARow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = I422ToRGBARow_C; - if (!src_y || !src_u || !src_v || !dst_rgba || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_rgba = dst_rgba + (height - 1) * dst_stride_rgba; - dst_stride_rgba = -dst_stride_rgba; - } -#if defined(HAS_I422TORGBAROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToRGBARow = I422ToRGBARow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - I422ToRGBARow = I422ToRGBARow_SSSE3; - } - } -#endif -#if defined(HAS_I422TORGBAROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToRGBARow = I422ToRGBARow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - I422ToRGBARow = I422ToRGBARow_AVX2; - } - } -#endif -#if defined(HAS_I422TORGBAROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToRGBARow = I422ToRGBARow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - I422ToRGBARow = I422ToRGBARow_NEON; - } - } -#endif -#if defined(HAS_I422TORGBAROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && - IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && - IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && - IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && - IS_ALIGNED(dst_rgba, 4) && IS_ALIGNED(dst_stride_rgba, 4)) { - I422ToRGBARow = I422ToRGBARow_DSPR2; - } -#endif - - for (y = 0; y < height; ++y) { - I422ToRGBARow(src_y, src_u, src_v, dst_rgba, yuvconstants, width); - dst_rgba += dst_stride_rgba; - src_y += src_stride_y; - src_u += src_stride_u; - src_v += src_stride_v; - } - return 0; -} - -// Convert I422 to RGBA. -LIBYUV_API -int I422ToRGBA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height) { - return I422ToRGBAMatrix(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_rgba, dst_stride_rgba, - &kYuvI601Constants, - width, height); -} - -// Convert I422 to BGRA. -LIBYUV_API -int I422ToBGRA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_bgra, int dst_stride_bgra, - int width, int height) { - return I422ToRGBAMatrix(src_y, src_stride_y, - src_v, src_stride_v, // Swap U and V - src_u, src_stride_u, - dst_bgra, dst_stride_bgra, - &kYvuI601Constants, // Use Yvu matrix - width, height); -} - -// Convert NV12 to RGB565. -LIBYUV_API -int NV12ToRGB565(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height) { - int y; - void (*NV12ToRGB565Row)(const uint8* y_buf, - const uint8* uv_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) = NV12ToRGB565Row_C; - if (!src_y || !src_uv || !dst_rgb565 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst_rgb565 = dst_rgb565 + (height - 1) * dst_stride_rgb565; - dst_stride_rgb565 = -dst_stride_rgb565; - } -#if defined(HAS_NV12TORGB565ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - NV12ToRGB565Row = NV12ToRGB565Row_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - NV12ToRGB565Row = NV12ToRGB565Row_SSSE3; - } - } -#endif -#if defined(HAS_NV12TORGB565ROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - NV12ToRGB565Row = NV12ToRGB565Row_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - NV12ToRGB565Row = NV12ToRGB565Row_AVX2; - } - } -#endif -#if defined(HAS_NV12TORGB565ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - NV12ToRGB565Row = NV12ToRGB565Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - NV12ToRGB565Row = NV12ToRGB565Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - NV12ToRGB565Row(src_y, src_uv, dst_rgb565, &kYuvI601Constants, width); - dst_rgb565 += dst_stride_rgb565; - src_y += src_stride_y; - if (y & 1) { - src_uv += src_stride_uv; - } - } - return 0; -} - -// Convert RAW to RGB24. -LIBYUV_API -int RAWToRGB24(const uint8* src_raw, int src_stride_raw, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height) { - int y; - void (*RAWToRGB24Row)(const uint8* src_rgb, uint8* dst_rgb24, int width) = - RAWToRGB24Row_C; - if (!src_raw || !dst_rgb24 || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_raw = src_raw + (height - 1) * src_stride_raw; - src_stride_raw = -src_stride_raw; - } - // Coalesce rows. - if (src_stride_raw == width * 3 && - dst_stride_rgb24 == width * 3) { - width *= height; - height = 1; - src_stride_raw = dst_stride_rgb24 = 0; - } -#if defined(HAS_RAWTORGB24ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - RAWToRGB24Row = RAWToRGB24Row_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - RAWToRGB24Row = RAWToRGB24Row_SSSE3; - } - } -#endif -#if defined(HAS_RAWTORGB24ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - RAWToRGB24Row = RAWToRGB24Row_Any_NEON; - if (IS_ALIGNED(width, 8)) { - RAWToRGB24Row = RAWToRGB24Row_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - RAWToRGB24Row(src_raw, dst_rgb24, width); - src_raw += src_stride_raw; - dst_rgb24 += dst_stride_rgb24; - } - return 0; -} - -LIBYUV_API -void SetPlane(uint8* dst_y, int dst_stride_y, - int width, int height, - uint32 value) { - int y; - void (*SetRow)(uint8* dst, uint8 value, int width) = SetRow_C; - if (height < 0) { - height = -height; - dst_y = dst_y + (height - 1) * dst_stride_y; - dst_stride_y = -dst_stride_y; - } - // Coalesce rows. - if (dst_stride_y == width) { - width *= height; - height = 1; - dst_stride_y = 0; - } -#if defined(HAS_SETROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SetRow = SetRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - SetRow = SetRow_NEON; - } - } -#endif -#if defined(HAS_SETROW_X86) - if (TestCpuFlag(kCpuHasX86)) { - SetRow = SetRow_Any_X86; - if (IS_ALIGNED(width, 4)) { - SetRow = SetRow_X86; - } - } -#endif -#if defined(HAS_SETROW_ERMS) - if (TestCpuFlag(kCpuHasERMS)) { - SetRow = SetRow_ERMS; - } -#endif - - // Set plane - for (y = 0; y < height; ++y) { - SetRow(dst_y, value, width); - dst_y += dst_stride_y; - } -} - -// Draw a rectangle into I420 -LIBYUV_API -int I420Rect(uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int x, int y, - int width, int height, - int value_y, int value_u, int value_v) { - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - uint8* start_y = dst_y + y * dst_stride_y + x; - uint8* start_u = dst_u + (y / 2) * dst_stride_u + (x / 2); - uint8* start_v = dst_v + (y / 2) * dst_stride_v + (x / 2); - if (!dst_y || !dst_u || !dst_v || - width <= 0 || height == 0 || - x < 0 || y < 0 || - value_y < 0 || value_y > 255 || - value_u < 0 || value_u > 255 || - value_v < 0 || value_v > 255) { - return -1; - } - - SetPlane(start_y, dst_stride_y, width, height, value_y); - SetPlane(start_u, dst_stride_u, halfwidth, halfheight, value_u); - SetPlane(start_v, dst_stride_v, halfwidth, halfheight, value_v); - return 0; -} - -// Draw a rectangle into ARGB -LIBYUV_API -int ARGBRect(uint8* dst_argb, int dst_stride_argb, - int dst_x, int dst_y, - int width, int height, - uint32 value) { - int y; - void (*ARGBSetRow)(uint8* dst_argb, uint32 value, int width) = ARGBSetRow_C; - if (!dst_argb || - width <= 0 || height == 0 || - dst_x < 0 || dst_y < 0) { - return -1; - } - if (height < 0) { - height = -height; - dst_argb = dst_argb + (height - 1) * dst_stride_argb; - dst_stride_argb = -dst_stride_argb; - } - dst_argb += dst_y * dst_stride_argb + dst_x * 4; - // Coalesce rows. - if (dst_stride_argb == width * 4) { - width *= height; - height = 1; - dst_stride_argb = 0; - } - -#if defined(HAS_ARGBSETROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBSetRow = ARGBSetRow_Any_NEON; - if (IS_ALIGNED(width, 4)) { - ARGBSetRow = ARGBSetRow_NEON; - } - } -#endif -#if defined(HAS_ARGBSETROW_X86) - if (TestCpuFlag(kCpuHasX86)) { - ARGBSetRow = ARGBSetRow_X86; - } -#endif - - // Set plane - for (y = 0; y < height; ++y) { - ARGBSetRow(dst_argb, value, width); - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert unattentuated ARGB to preattenuated ARGB. -// An unattenutated ARGB alpha blend uses the formula -// p = a * f + (1 - a) * b -// where -// p is output pixel -// f is foreground pixel -// b is background pixel -// a is alpha value from foreground pixel -// An preattenutated ARGB alpha blend uses the formula -// p = f + (1 - a) * b -// where -// f is foreground pixel premultiplied by alpha - -LIBYUV_API -int ARGBAttenuate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBAttenuateRow)(const uint8* src_argb, uint8* dst_argb, - int width) = ARGBAttenuateRow_C; - if (!src_argb || !dst_argb || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb = 0; - } -#if defined(HAS_ARGBATTENUATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBAttenuateRow = ARGBAttenuateRow_Any_SSSE3; - if (IS_ALIGNED(width, 4)) { - ARGBAttenuateRow = ARGBAttenuateRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBATTENUATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBAttenuateRow = ARGBAttenuateRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBAttenuateRow = ARGBAttenuateRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBATTENUATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBAttenuateRow = ARGBAttenuateRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBAttenuateRow = ARGBAttenuateRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBAttenuateRow(src_argb, dst_argb, width); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert preattentuated ARGB to unattenuated ARGB. -LIBYUV_API -int ARGBUnattenuate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBUnattenuateRow)(const uint8* src_argb, uint8* dst_argb, - int width) = ARGBUnattenuateRow_C; - if (!src_argb || !dst_argb || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb = 0; - } -#if defined(HAS_ARGBUNATTENUATEROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBUnattenuateRow = ARGBUnattenuateRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBUnattenuateRow = ARGBUnattenuateRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBUNATTENUATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBUnattenuateRow = ARGBUnattenuateRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBUnattenuateRow = ARGBUnattenuateRow_AVX2; - } - } -#endif -// TODO(fbarchard): Neon version. - - for (y = 0; y < height; ++y) { - ARGBUnattenuateRow(src_argb, dst_argb, width); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Convert ARGB to Grayed ARGB. -LIBYUV_API -int ARGBGrayTo(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBGrayRow)(const uint8* src_argb, uint8* dst_argb, - int width) = ARGBGrayRow_C; - if (!src_argb || !dst_argb || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb = 0; - } -#if defined(HAS_ARGBGRAYROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && IS_ALIGNED(width, 8)) { - ARGBGrayRow = ARGBGrayRow_SSSE3; - } -#endif -#if defined(HAS_ARGBGRAYROW_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 8)) { - ARGBGrayRow = ARGBGrayRow_NEON; - } -#endif - - for (y = 0; y < height; ++y) { - ARGBGrayRow(src_argb, dst_argb, width); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Make a rectangle of ARGB gray scale. -LIBYUV_API -int ARGBGray(uint8* dst_argb, int dst_stride_argb, - int dst_x, int dst_y, - int width, int height) { - int y; - void (*ARGBGrayRow)(const uint8* src_argb, uint8* dst_argb, - int width) = ARGBGrayRow_C; - uint8* dst = dst_argb + dst_y * dst_stride_argb + dst_x * 4; - if (!dst_argb || width <= 0 || height <= 0 || dst_x < 0 || dst_y < 0) { - return -1; - } - // Coalesce rows. - if (dst_stride_argb == width * 4) { - width *= height; - height = 1; - dst_stride_argb = 0; - } -#if defined(HAS_ARGBGRAYROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && IS_ALIGNED(width, 8)) { - ARGBGrayRow = ARGBGrayRow_SSSE3; - } -#endif -#if defined(HAS_ARGBGRAYROW_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 8)) { - ARGBGrayRow = ARGBGrayRow_NEON; - } -#endif - for (y = 0; y < height; ++y) { - ARGBGrayRow(dst, dst, width); - dst += dst_stride_argb; - } - return 0; -} - -// Make a rectangle of ARGB Sepia tone. -LIBYUV_API -int ARGBSepia(uint8* dst_argb, int dst_stride_argb, - int dst_x, int dst_y, int width, int height) { - int y; - void (*ARGBSepiaRow)(uint8* dst_argb, int width) = ARGBSepiaRow_C; - uint8* dst = dst_argb + dst_y * dst_stride_argb + dst_x * 4; - if (!dst_argb || width <= 0 || height <= 0 || dst_x < 0 || dst_y < 0) { - return -1; - } - // Coalesce rows. - if (dst_stride_argb == width * 4) { - width *= height; - height = 1; - dst_stride_argb = 0; - } -#if defined(HAS_ARGBSEPIAROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && IS_ALIGNED(width, 8)) { - ARGBSepiaRow = ARGBSepiaRow_SSSE3; - } -#endif -#if defined(HAS_ARGBSEPIAROW_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 8)) { - ARGBSepiaRow = ARGBSepiaRow_NEON; - } -#endif - for (y = 0; y < height; ++y) { - ARGBSepiaRow(dst, width); - dst += dst_stride_argb; - } - return 0; -} - -// Apply a 4x4 matrix to each ARGB pixel. -// Note: Normally for shading, but can be used to swizzle or invert. -LIBYUV_API -int ARGBColorMatrix(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const int8* matrix_argb, - int width, int height) { - int y; - void (*ARGBColorMatrixRow)(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width) = ARGBColorMatrixRow_C; - if (!src_argb || !dst_argb || !matrix_argb || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb = 0; - } -#if defined(HAS_ARGBCOLORMATRIXROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && IS_ALIGNED(width, 8)) { - ARGBColorMatrixRow = ARGBColorMatrixRow_SSSE3; - } -#endif -#if defined(HAS_ARGBCOLORMATRIXROW_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 8)) { - ARGBColorMatrixRow = ARGBColorMatrixRow_NEON; - } -#endif - for (y = 0; y < height; ++y) { - ARGBColorMatrixRow(src_argb, dst_argb, matrix_argb, width); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Apply a 4x3 matrix to each ARGB pixel. -// Deprecated. -LIBYUV_API -int RGBColorMatrix(uint8* dst_argb, int dst_stride_argb, - const int8* matrix_rgb, - int dst_x, int dst_y, int width, int height) { - SIMD_ALIGNED(int8 matrix_argb[16]); - uint8* dst = dst_argb + dst_y * dst_stride_argb + dst_x * 4; - if (!dst_argb || !matrix_rgb || width <= 0 || height <= 0 || - dst_x < 0 || dst_y < 0) { - return -1; - } - - // Convert 4x3 7 bit matrix to 4x4 6 bit matrix. - matrix_argb[0] = matrix_rgb[0] / 2; - matrix_argb[1] = matrix_rgb[1] / 2; - matrix_argb[2] = matrix_rgb[2] / 2; - matrix_argb[3] = matrix_rgb[3] / 2; - matrix_argb[4] = matrix_rgb[4] / 2; - matrix_argb[5] = matrix_rgb[5] / 2; - matrix_argb[6] = matrix_rgb[6] / 2; - matrix_argb[7] = matrix_rgb[7] / 2; - matrix_argb[8] = matrix_rgb[8] / 2; - matrix_argb[9] = matrix_rgb[9] / 2; - matrix_argb[10] = matrix_rgb[10] / 2; - matrix_argb[11] = matrix_rgb[11] / 2; - matrix_argb[14] = matrix_argb[13] = matrix_argb[12] = 0; - matrix_argb[15] = 64; // 1.0 - - return ARGBColorMatrix((const uint8*)(dst), dst_stride_argb, - dst, dst_stride_argb, - &matrix_argb[0], width, height); -} - -// Apply a color table each ARGB pixel. -// Table contains 256 ARGB values. -LIBYUV_API -int ARGBColorTable(uint8* dst_argb, int dst_stride_argb, - const uint8* table_argb, - int dst_x, int dst_y, int width, int height) { - int y; - void (*ARGBColorTableRow)(uint8* dst_argb, const uint8* table_argb, - int width) = ARGBColorTableRow_C; - uint8* dst = dst_argb + dst_y * dst_stride_argb + dst_x * 4; - if (!dst_argb || !table_argb || width <= 0 || height <= 0 || - dst_x < 0 || dst_y < 0) { - return -1; - } - // Coalesce rows. - if (dst_stride_argb == width * 4) { - width *= height; - height = 1; - dst_stride_argb = 0; - } -#if defined(HAS_ARGBCOLORTABLEROW_X86) - if (TestCpuFlag(kCpuHasX86)) { - ARGBColorTableRow = ARGBColorTableRow_X86; - } -#endif - for (y = 0; y < height; ++y) { - ARGBColorTableRow(dst, table_argb, width); - dst += dst_stride_argb; - } - return 0; -} - -// Apply a color table each ARGB pixel but preserve destination alpha. -// Table contains 256 ARGB values. -LIBYUV_API -int RGBColorTable(uint8* dst_argb, int dst_stride_argb, - const uint8* table_argb, - int dst_x, int dst_y, int width, int height) { - int y; - void (*RGBColorTableRow)(uint8* dst_argb, const uint8* table_argb, - int width) = RGBColorTableRow_C; - uint8* dst = dst_argb + dst_y * dst_stride_argb + dst_x * 4; - if (!dst_argb || !table_argb || width <= 0 || height <= 0 || - dst_x < 0 || dst_y < 0) { - return -1; - } - // Coalesce rows. - if (dst_stride_argb == width * 4) { - width *= height; - height = 1; - dst_stride_argb = 0; - } -#if defined(HAS_RGBCOLORTABLEROW_X86) - if (TestCpuFlag(kCpuHasX86)) { - RGBColorTableRow = RGBColorTableRow_X86; - } -#endif - for (y = 0; y < height; ++y) { - RGBColorTableRow(dst, table_argb, width); - dst += dst_stride_argb; - } - return 0; -} - -// ARGBQuantize is used to posterize art. -// e.g. rgb / qvalue * qvalue + qvalue / 2 -// But the low levels implement efficiently with 3 parameters, and could be -// used for other high level operations. -// dst_argb[0] = (b * scale >> 16) * interval_size + interval_offset; -// where scale is 1 / interval_size as a fixed point value. -// The divide is replaces with a multiply by reciprocal fixed point multiply. -// Caveat - although SSE2 saturates, the C function does not and should be used -// with care if doing anything but quantization. -LIBYUV_API -int ARGBQuantize(uint8* dst_argb, int dst_stride_argb, - int scale, int interval_size, int interval_offset, - int dst_x, int dst_y, int width, int height) { - int y; - void (*ARGBQuantizeRow)(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width) = ARGBQuantizeRow_C; - uint8* dst = dst_argb + dst_y * dst_stride_argb + dst_x * 4; - if (!dst_argb || width <= 0 || height <= 0 || dst_x < 0 || dst_y < 0 || - interval_size < 1 || interval_size > 255) { - return -1; - } - // Coalesce rows. - if (dst_stride_argb == width * 4) { - width *= height; - height = 1; - dst_stride_argb = 0; - } -#if defined(HAS_ARGBQUANTIZEROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(width, 4)) { - ARGBQuantizeRow = ARGBQuantizeRow_SSE2; - } -#endif -#if defined(HAS_ARGBQUANTIZEROW_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 8)) { - ARGBQuantizeRow = ARGBQuantizeRow_NEON; - } -#endif - for (y = 0; y < height; ++y) { - ARGBQuantizeRow(dst, scale, interval_size, interval_offset, width); - dst += dst_stride_argb; - } - return 0; -} - -// Computes table of cumulative sum for image where the value is the sum -// of all values above and to the left of the entry. Used by ARGBBlur. -LIBYUV_API -int ARGBComputeCumulativeSum(const uint8* src_argb, int src_stride_argb, - int32* dst_cumsum, int dst_stride32_cumsum, - int width, int height) { - int y; - void (*ComputeCumulativeSumRow)(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width) = ComputeCumulativeSumRow_C; - int32* previous_cumsum = dst_cumsum; - if (!dst_cumsum || !src_argb || width <= 0 || height <= 0) { - return -1; - } -#if defined(HAS_CUMULATIVESUMTOAVERAGEROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ComputeCumulativeSumRow = ComputeCumulativeSumRow_SSE2; - } -#endif - memset(dst_cumsum, 0, width * sizeof(dst_cumsum[0]) * 4); // 4 int per pixel. - for (y = 0; y < height; ++y) { - ComputeCumulativeSumRow(src_argb, dst_cumsum, previous_cumsum, width); - previous_cumsum = dst_cumsum; - dst_cumsum += dst_stride32_cumsum; - src_argb += src_stride_argb; - } - return 0; -} - -// Blur ARGB image. -// Caller should allocate CumulativeSum table of width * height * 16 bytes -// aligned to 16 byte boundary. height can be radius * 2 + 2 to save memory -// as the buffer is treated as circular. -LIBYUV_API -int ARGBBlur(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int32* dst_cumsum, int dst_stride32_cumsum, - int width, int height, int radius) { - int y; - void (*ComputeCumulativeSumRow)(const uint8 *row, int32 *cumsum, - const int32* previous_cumsum, int width) = ComputeCumulativeSumRow_C; - void (*CumulativeSumToAverageRow)(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, int count) = CumulativeSumToAverageRow_C; - int32* cumsum_bot_row; - int32* max_cumsum_bot_row; - int32* cumsum_top_row; - - if (!src_argb || !dst_argb || width <= 0 || height == 0) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - if (radius > height) { - radius = height; - } - if (radius > (width / 2 - 1)) { - radius = width / 2 - 1; - } - if (radius <= 0) { - return -1; - } -#if defined(HAS_CUMULATIVESUMTOAVERAGEROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ComputeCumulativeSumRow = ComputeCumulativeSumRow_SSE2; - CumulativeSumToAverageRow = CumulativeSumToAverageRow_SSE2; - } -#endif - // Compute enough CumulativeSum for first row to be blurred. After this - // one row of CumulativeSum is updated at a time. - ARGBComputeCumulativeSum(src_argb, src_stride_argb, - dst_cumsum, dst_stride32_cumsum, - width, radius); - - src_argb = src_argb + radius * src_stride_argb; - cumsum_bot_row = &dst_cumsum[(radius - 1) * dst_stride32_cumsum]; - - max_cumsum_bot_row = &dst_cumsum[(radius * 2 + 2) * dst_stride32_cumsum]; - cumsum_top_row = &dst_cumsum[0]; - - for (y = 0; y < height; ++y) { - int top_y = ((y - radius - 1) >= 0) ? (y - radius - 1) : 0; - int bot_y = ((y + radius) < height) ? (y + radius) : (height - 1); - int area = radius * (bot_y - top_y); - int boxwidth = radius * 4; - int x; - int n; - - // Increment cumsum_top_row pointer with circular buffer wrap around. - if (top_y) { - cumsum_top_row += dst_stride32_cumsum; - if (cumsum_top_row >= max_cumsum_bot_row) { - cumsum_top_row = dst_cumsum; - } - } - // Increment cumsum_bot_row pointer with circular buffer wrap around and - // then fill in a row of CumulativeSum. - if ((y + radius) < height) { - const int32* prev_cumsum_bot_row = cumsum_bot_row; - cumsum_bot_row += dst_stride32_cumsum; - if (cumsum_bot_row >= max_cumsum_bot_row) { - cumsum_bot_row = dst_cumsum; - } - ComputeCumulativeSumRow(src_argb, cumsum_bot_row, prev_cumsum_bot_row, - width); - src_argb += src_stride_argb; - } - - // Left clipped. - for (x = 0; x < radius + 1; ++x) { - CumulativeSumToAverageRow(cumsum_top_row, cumsum_bot_row, - boxwidth, area, &dst_argb[x * 4], 1); - area += (bot_y - top_y); - boxwidth += 4; - } - - // Middle unclipped. - n = (width - 1) - radius - x + 1; - CumulativeSumToAverageRow(cumsum_top_row, cumsum_bot_row, - boxwidth, area, &dst_argb[x * 4], n); - - // Right clipped. - for (x += n; x <= width - 1; ++x) { - area -= (bot_y - top_y); - boxwidth -= 4; - CumulativeSumToAverageRow(cumsum_top_row + (x - radius - 1) * 4, - cumsum_bot_row + (x - radius - 1) * 4, - boxwidth, area, &dst_argb[x * 4], 1); - } - dst_argb += dst_stride_argb; - } - return 0; -} - -// Multiply ARGB image by a specified ARGB value. -LIBYUV_API -int ARGBShade(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height, uint32 value) { - int y; - void (*ARGBShadeRow)(const uint8* src_argb, uint8* dst_argb, - int width, uint32 value) = ARGBShadeRow_C; - if (!src_argb || !dst_argb || width <= 0 || height == 0 || value == 0u) { - return -1; - } - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb = 0; - } -#if defined(HAS_ARGBSHADEROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(width, 4)) { - ARGBShadeRow = ARGBShadeRow_SSE2; - } -#endif -#if defined(HAS_ARGBSHADEROW_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 8)) { - ARGBShadeRow = ARGBShadeRow_NEON; - } -#endif - - for (y = 0; y < height; ++y) { - ARGBShadeRow(src_argb, dst_argb, width, value); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Interpolate 2 planes by specified amount (0 to 255). -LIBYUV_API -int InterpolatePlane(const uint8* src0, int src_stride0, - const uint8* src1, int src_stride1, - uint8* dst, int dst_stride, - int width, int height, int interpolation) { - int y; - void (*InterpolateRow)(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, - int source_y_fraction) = InterpolateRow_C; - if (!src0 || !src1 || !dst || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - dst = dst + (height - 1) * dst_stride; - dst_stride = -dst_stride; - } - // Coalesce rows. - if (src_stride0 == width && - src_stride1 == width && - dst_stride == width) { - width *= height; - height = 1; - src_stride0 = src_stride1 = dst_stride = 0; - } -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(src0, 4) && IS_ALIGNED(src_stride0, 4) && - IS_ALIGNED(src1, 4) && IS_ALIGNED(src_stride1, 4) && - IS_ALIGNED(dst, 4) && IS_ALIGNED(dst_stride, 4) && - IS_ALIGNED(width, 4)) { - InterpolateRow = InterpolateRow_DSPR2; - } -#endif - - for (y = 0; y < height; ++y) { - InterpolateRow(dst, src0, src1 - src0, width, interpolation); - src0 += src_stride0; - src1 += src_stride1; - dst += dst_stride; - } - return 0; -} - -// Interpolate 2 ARGB images by specified amount (0 to 255). -LIBYUV_API -int ARGBInterpolate(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height, int interpolation) { - return InterpolatePlane(src_argb0, src_stride_argb0, - src_argb1, src_stride_argb1, - dst_argb, dst_stride_argb, - width * 4, height, interpolation); -} - -// Interpolate 2 YUV images by specified amount (0 to 255). -LIBYUV_API -int I420Interpolate(const uint8* src0_y, int src0_stride_y, - const uint8* src0_u, int src0_stride_u, - const uint8* src0_v, int src0_stride_v, - const uint8* src1_y, int src1_stride_y, - const uint8* src1_u, int src1_stride_u, - const uint8* src1_v, int src1_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height, int interpolation) { - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - if (!src0_y || !src0_u || !src0_v || - !src1_y || !src1_u || !src1_v || - !dst_y || !dst_u || !dst_v || - width <= 0 || height == 0) { - return -1; - } - InterpolatePlane(src0_y, src0_stride_y, - src1_y, src1_stride_y, - dst_y, dst_stride_y, - width, height, interpolation); - InterpolatePlane(src0_u, src0_stride_u, - src1_u, src1_stride_u, - dst_u, dst_stride_u, - halfwidth, halfheight, interpolation); - InterpolatePlane(src0_v, src0_stride_v, - src1_v, src1_stride_v, - dst_v, dst_stride_v, - halfwidth, halfheight, interpolation); - return 0; -} - -// Shuffle ARGB channel order. e.g. BGRA to ARGB. -LIBYUV_API -int ARGBShuffle(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_argb, int dst_stride_argb, - const uint8* shuffler, int width, int height) { - int y; - void (*ARGBShuffleRow)(const uint8* src_bgra, uint8* dst_argb, - const uint8* shuffler, int width) = ARGBShuffleRow_C; - if (!src_bgra || !dst_argb || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_bgra = src_bgra + (height - 1) * src_stride_bgra; - src_stride_bgra = -src_stride_bgra; - } - // Coalesce rows. - if (src_stride_bgra == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_bgra = dst_stride_argb = 0; - } -#if defined(HAS_ARGBSHUFFLEROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBShuffleRow = ARGBShuffleRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBShuffleRow = ARGBShuffleRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBSHUFFLEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBShuffleRow = ARGBShuffleRow_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - ARGBShuffleRow = ARGBShuffleRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBSHUFFLEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBShuffleRow = ARGBShuffleRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - ARGBShuffleRow = ARGBShuffleRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBSHUFFLEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBShuffleRow = ARGBShuffleRow_Any_NEON; - if (IS_ALIGNED(width, 4)) { - ARGBShuffleRow = ARGBShuffleRow_NEON; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBShuffleRow(src_bgra, dst_argb, shuffler, width); - src_bgra += src_stride_bgra; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Sobel ARGB effect. -static int ARGBSobelize(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height, - void (*SobelRow)(const uint8* src_sobelx, - const uint8* src_sobely, - uint8* dst, int width)) { - int y; - void (*ARGBToYJRow)(const uint8* src_argb, uint8* dst_g, int width) = - ARGBToYJRow_C; - void (*SobelYRow)(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width) = SobelYRow_C; - void (*SobelXRow)(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobely, int width) = - SobelXRow_C; - const int kEdge = 16; // Extra pixels at start of row for extrude/align. - if (!src_argb || !dst_argb || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - -#if defined(HAS_ARGBTOYJROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToYJRow = ARGBToYJRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToYJRow = ARGBToYJRow_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOYJROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBToYJRow = ARGBToYJRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - ARGBToYJRow = ARGBToYJRow_AVX2; - } - } -#endif -#if defined(HAS_ARGBTOYJROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToYJRow = ARGBToYJRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - ARGBToYJRow = ARGBToYJRow_NEON; - } - } -#endif - -#if defined(HAS_SOBELYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - SobelYRow = SobelYRow_SSE2; - } -#endif -#if defined(HAS_SOBELYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SobelYRow = SobelYRow_NEON; - } -#endif -#if defined(HAS_SOBELXROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - SobelXRow = SobelXRow_SSE2; - } -#endif -#if defined(HAS_SOBELXROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SobelXRow = SobelXRow_NEON; - } -#endif - { - // 3 rows with edges before/after. - const int kRowSize = (width + kEdge + 31) & ~31; - align_buffer_64(rows, kRowSize * 2 + (kEdge + kRowSize * 3 + kEdge)); - uint8* row_sobelx = rows; - uint8* row_sobely = rows + kRowSize; - uint8* row_y = rows + kRowSize * 2; - - // Convert first row. - uint8* row_y0 = row_y + kEdge; - uint8* row_y1 = row_y0 + kRowSize; - uint8* row_y2 = row_y1 + kRowSize; - ARGBToYJRow(src_argb, row_y0, width); - row_y0[-1] = row_y0[0]; - memset(row_y0 + width, row_y0[width - 1], 16); // Extrude 16 for valgrind. - ARGBToYJRow(src_argb, row_y1, width); - row_y1[-1] = row_y1[0]; - memset(row_y1 + width, row_y1[width - 1], 16); - memset(row_y2 + width, 0, 16); - - for (y = 0; y < height; ++y) { - // Convert next row of ARGB to G. - if (y < (height - 1)) { - src_argb += src_stride_argb; - } - ARGBToYJRow(src_argb, row_y2, width); - row_y2[-1] = row_y2[0]; - row_y2[width] = row_y2[width - 1]; - - SobelXRow(row_y0 - 1, row_y1 - 1, row_y2 - 1, row_sobelx, width); - SobelYRow(row_y0 - 1, row_y2 - 1, row_sobely, width); - SobelRow(row_sobelx, row_sobely, dst_argb, width); - - // Cycle thru circular queue of 3 row_y buffers. - { - uint8* row_yt = row_y0; - row_y0 = row_y1; - row_y1 = row_y2; - row_y2 = row_yt; - } - - dst_argb += dst_stride_argb; - } - free_aligned_buffer_64(rows); - } - return 0; -} - -// Sobel ARGB effect. -LIBYUV_API -int ARGBSobel(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - void (*SobelRow)(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) = SobelRow_C; -#if defined(HAS_SOBELROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - SobelRow = SobelRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - SobelRow = SobelRow_SSE2; - } - } -#endif -#if defined(HAS_SOBELROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SobelRow = SobelRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - SobelRow = SobelRow_NEON; - } - } -#endif - return ARGBSobelize(src_argb, src_stride_argb, dst_argb, dst_stride_argb, - width, height, SobelRow); -} - -// Sobel ARGB effect with planar output. -LIBYUV_API -int ARGBSobelToPlane(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - int width, int height) { - void (*SobelToPlaneRow)(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_, int width) = SobelToPlaneRow_C; -#if defined(HAS_SOBELTOPLANEROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - SobelToPlaneRow = SobelToPlaneRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - SobelToPlaneRow = SobelToPlaneRow_SSE2; - } - } -#endif -#if defined(HAS_SOBELTOPLANEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SobelToPlaneRow = SobelToPlaneRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - SobelToPlaneRow = SobelToPlaneRow_NEON; - } - } -#endif - return ARGBSobelize(src_argb, src_stride_argb, dst_y, dst_stride_y, - width, height, SobelToPlaneRow); -} - -// SobelXY ARGB effect. -// Similar to Sobel, but also stores Sobel X in R and Sobel Y in B. G = Sobel. -LIBYUV_API -int ARGBSobelXY(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - void (*SobelXYRow)(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) = SobelXYRow_C; -#if defined(HAS_SOBELXYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - SobelXYRow = SobelXYRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - SobelXYRow = SobelXYRow_SSE2; - } - } -#endif -#if defined(HAS_SOBELXYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SobelXYRow = SobelXYRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - SobelXYRow = SobelXYRow_NEON; - } - } -#endif - return ARGBSobelize(src_argb, src_stride_argb, dst_argb, dst_stride_argb, - width, height, SobelXYRow); -} - -// Apply a 4x4 polynomial to each ARGB pixel. -LIBYUV_API -int ARGBPolynomial(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const float* poly, - int width, int height) { - int y; - void (*ARGBPolynomialRow)(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width) = ARGBPolynomialRow_C; - if (!src_argb || !dst_argb || !poly || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb = 0; - } -#if defined(HAS_ARGBPOLYNOMIALROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(width, 2)) { - ARGBPolynomialRow = ARGBPolynomialRow_SSE2; - } -#endif -#if defined(HAS_ARGBPOLYNOMIALROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2) && TestCpuFlag(kCpuHasFMA3) && - IS_ALIGNED(width, 2)) { - ARGBPolynomialRow = ARGBPolynomialRow_AVX2; - } -#endif - - for (y = 0; y < height; ++y) { - ARGBPolynomialRow(src_argb, dst_argb, poly, width); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Apply a lumacolortable to each ARGB pixel. -LIBYUV_API -int ARGBLumaColorTable(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const uint8* luma, - int width, int height) { - int y; - void (*ARGBLumaColorTableRow)(const uint8* src_argb, uint8* dst_argb, - int width, const uint8* luma, const uint32 lumacoeff) = - ARGBLumaColorTableRow_C; - if (!src_argb || !dst_argb || !luma || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb = 0; - } -#if defined(HAS_ARGBLUMACOLORTABLEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && IS_ALIGNED(width, 4)) { - ARGBLumaColorTableRow = ARGBLumaColorTableRow_SSSE3; - } -#endif - - for (y = 0; y < height; ++y) { - ARGBLumaColorTableRow(src_argb, dst_argb, width, luma, 0x00264b0f); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Copy Alpha from one ARGB image to another. -LIBYUV_API -int ARGBCopyAlpha(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBCopyAlphaRow)(const uint8* src_argb, uint8* dst_argb, int width) = - ARGBCopyAlphaRow_C; - if (!src_argb || !dst_argb || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - // Coalesce rows. - if (src_stride_argb == width * 4 && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_argb = dst_stride_argb = 0; - } -#if defined(HAS_ARGBCOPYALPHAROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBCopyAlphaRow = ARGBCopyAlphaRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - ARGBCopyAlphaRow = ARGBCopyAlphaRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBCOPYALPHAROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBCopyAlphaRow = ARGBCopyAlphaRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - ARGBCopyAlphaRow = ARGBCopyAlphaRow_AVX2; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBCopyAlphaRow(src_argb, dst_argb, width); - src_argb += src_stride_argb; - dst_argb += dst_stride_argb; - } - return 0; -} - -// Extract just the alpha channel from ARGB. -LIBYUV_API -int ARGBExtractAlpha(const uint8* src_argb, int src_stride, - uint8* dst_a, int dst_stride, - int width, int height) { - if (!src_argb || !dst_a || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb += (height - 1) * src_stride; - src_stride = -src_stride; - } - // Coalesce rows. - if (src_stride == width * 4 && dst_stride == width) { - width *= height; - height = 1; - src_stride = dst_stride = 0; - } - void (*ARGBExtractAlphaRow)(const uint8 *src_argb, uint8 *dst_a, int width) = - ARGBExtractAlphaRow_C; -#if defined(HAS_ARGBEXTRACTALPHAROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBExtractAlphaRow = IS_ALIGNED(width, 8) ? ARGBExtractAlphaRow_SSE2 - : ARGBExtractAlphaRow_Any_SSE2; - } -#endif -#if defined(HAS_ARGBEXTRACTALPHAROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBExtractAlphaRow = IS_ALIGNED(width, 16) ? ARGBExtractAlphaRow_NEON - : ARGBExtractAlphaRow_Any_NEON; - } -#endif - - for (int y = 0; y < height; ++y) { - ARGBExtractAlphaRow(src_argb, dst_a, width); - src_argb += src_stride; - dst_a += dst_stride; - } - return 0; -} - -// Copy a planar Y channel to the alpha channel of a destination ARGB image. -LIBYUV_API -int ARGBCopyYToAlpha(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height) { - int y; - void (*ARGBCopyYToAlphaRow)(const uint8* src_y, uint8* dst_argb, int width) = - ARGBCopyYToAlphaRow_C; - if (!src_y || !dst_argb || width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_y = src_y + (height - 1) * src_stride_y; - src_stride_y = -src_stride_y; - } - // Coalesce rows. - if (src_stride_y == width && - dst_stride_argb == width * 4) { - width *= height; - height = 1; - src_stride_y = dst_stride_argb = 0; - } -#if defined(HAS_ARGBCOPYYTOALPHAROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBCopyYToAlphaRow = ARGBCopyYToAlphaRow_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - ARGBCopyYToAlphaRow = ARGBCopyYToAlphaRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBCOPYYTOALPHAROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBCopyYToAlphaRow = ARGBCopyYToAlphaRow_Any_AVX2; - if (IS_ALIGNED(width, 16)) { - ARGBCopyYToAlphaRow = ARGBCopyYToAlphaRow_AVX2; - } - } -#endif - - for (y = 0; y < height; ++y) { - ARGBCopyYToAlphaRow(src_y, dst_argb, width); - src_y += src_stride_y; - dst_argb += dst_stride_argb; - } - return 0; -} - -// TODO(fbarchard): Consider if width is even Y channel can be split -// directly. A SplitUVRow_Odd function could copy the remaining chroma. - -LIBYUV_API -int YUY2ToNV12(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height) { - int y; - int halfwidth = (width + 1) >> 1; - void (*SplitUVRow)(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) = SplitUVRow_C; - void (*InterpolateRow)(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, - int source_y_fraction) = InterpolateRow_C; - if (!src_yuy2 || - !dst_y || !dst_uv || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_yuy2 = src_yuy2 + (height - 1) * src_stride_yuy2; - src_stride_yuy2 = -src_stride_yuy2; - } -#if defined(HAS_SPLITUVROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - SplitUVRow = SplitUVRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - SplitUVRow = SplitUVRow_SSE2; - } - } -#endif -#if defined(HAS_SPLITUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - SplitUVRow = SplitUVRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - SplitUVRow = SplitUVRow_AVX2; - } - } -#endif -#if defined(HAS_SPLITUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SplitUVRow = SplitUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - SplitUVRow = SplitUVRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif - - { - int awidth = halfwidth * 2; - // row of y and 2 rows of uv - align_buffer_64(rows, awidth * 3); - - for (y = 0; y < height - 1; y += 2) { - // Split Y from UV. - SplitUVRow(src_yuy2, rows, rows + awidth, awidth); - memcpy(dst_y, rows, width); - SplitUVRow(src_yuy2 + src_stride_yuy2, rows, rows + awidth * 2, awidth); - memcpy(dst_y + dst_stride_y, rows, width); - InterpolateRow(dst_uv, rows + awidth, awidth, awidth, 128); - src_yuy2 += src_stride_yuy2 * 2; - dst_y += dst_stride_y * 2; - dst_uv += dst_stride_uv; - } - if (height & 1) { - // Split Y from UV. - SplitUVRow(src_yuy2, rows, dst_uv, awidth); - memcpy(dst_y, rows, width); - } - free_aligned_buffer_64(rows); - } - return 0; -} - -LIBYUV_API -int UYVYToNV12(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height) { - int y; - int halfwidth = (width + 1) >> 1; - void (*SplitUVRow)(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) = SplitUVRow_C; - void (*InterpolateRow)(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, - int source_y_fraction) = InterpolateRow_C; - if (!src_uyvy || - !dst_y || !dst_uv || - width <= 0 || height == 0) { - return -1; - } - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_uyvy = src_uyvy + (height - 1) * src_stride_uyvy; - src_stride_uyvy = -src_stride_uyvy; - } -#if defined(HAS_SPLITUVROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - SplitUVRow = SplitUVRow_Any_SSE2; - if (IS_ALIGNED(width, 16)) { - SplitUVRow = SplitUVRow_SSE2; - } - } -#endif -#if defined(HAS_SPLITUVROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - SplitUVRow = SplitUVRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - SplitUVRow = SplitUVRow_AVX2; - } - } -#endif -#if defined(HAS_SPLITUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - SplitUVRow = SplitUVRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - SplitUVRow = SplitUVRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif - - { - int awidth = halfwidth * 2; - // row of y and 2 rows of uv - align_buffer_64(rows, awidth * 3); - - for (y = 0; y < height - 1; y += 2) { - // Split Y from UV. - SplitUVRow(src_uyvy, rows + awidth, rows, awidth); - memcpy(dst_y, rows, width); - SplitUVRow(src_uyvy + src_stride_uyvy, rows + awidth * 2, rows, awidth); - memcpy(dst_y + dst_stride_y, rows, width); - InterpolateRow(dst_uv, rows + awidth, awidth, awidth, 128); - src_uyvy += src_stride_uyvy * 2; - dst_y += dst_stride_y * 2; - dst_uv += dst_stride_uv; - } - if (height & 1) { - // Split Y from UV. - SplitUVRow(src_uyvy, dst_uv, rows, awidth); - memcpy(dst_y, rows, width); - } - free_aligned_buffer_64(rows); - } - return 0; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate.cc deleted file mode 100644 index 01ea5c4..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate.cc +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/rotate.h" - -#include "libyuv/cpu_id.h" -#include "libyuv/convert.h" -#include "libyuv/planar_functions.h" -#include "libyuv/rotate_row.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -LIBYUV_API -void TransposePlane(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height) { - int i = height; - void (*TransposeWx8)(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width) = TransposeWx8_C; -#if defined(HAS_TRANSPOSEWX8_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - TransposeWx8 = TransposeWx8_NEON; - } -#endif -#if defined(HAS_TRANSPOSEWX8_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - TransposeWx8 = TransposeWx8_Any_SSSE3; - if (IS_ALIGNED(width, 8)) { - TransposeWx8 = TransposeWx8_SSSE3; - } - } -#endif -#if defined(HAS_TRANSPOSEWX8_FAST_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - TransposeWx8 = TransposeWx8_Fast_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - TransposeWx8 = TransposeWx8_Fast_SSSE3; - } - } -#endif -#if defined(HAS_TRANSPOSEWX8_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2)) { - if (IS_ALIGNED(width, 4) && - IS_ALIGNED(src, 4) && IS_ALIGNED(src_stride, 4)) { - TransposeWx8 = TransposeWx8_Fast_DSPR2; - } else { - TransposeWx8 = TransposeWx8_DSPR2; - } - } -#endif - - // Work across the source in 8x8 tiles - while (i >= 8) { - TransposeWx8(src, src_stride, dst, dst_stride, width); - src += 8 * src_stride; // Go down 8 rows. - dst += 8; // Move over 8 columns. - i -= 8; - } - - if (i > 0) { - TransposeWxH_C(src, src_stride, dst, dst_stride, width, i); - } -} - -LIBYUV_API -void RotatePlane90(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height) { - // Rotate by 90 is a transpose with the source read - // from bottom to top. So set the source pointer to the end - // of the buffer and flip the sign of the source stride. - src += src_stride * (height - 1); - src_stride = -src_stride; - TransposePlane(src, src_stride, dst, dst_stride, width, height); -} - -LIBYUV_API -void RotatePlane270(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height) { - // Rotate by 270 is a transpose with the destination written - // from bottom to top. So set the destination pointer to the end - // of the buffer and flip the sign of the destination stride. - dst += dst_stride * (width - 1); - dst_stride = -dst_stride; - TransposePlane(src, src_stride, dst, dst_stride, width, height); -} - -LIBYUV_API -void RotatePlane180(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height) { - // Swap first and last row and mirror the content. Uses a temporary row. - align_buffer_64(row, width); - const uint8* src_bot = src + src_stride * (height - 1); - uint8* dst_bot = dst + dst_stride * (height - 1); - int half_height = (height + 1) >> 1; - int y; - void (*MirrorRow)(const uint8* src, uint8* dst, int width) = MirrorRow_C; - void (*CopyRow)(const uint8* src, uint8* dst, int width) = CopyRow_C; -#if defined(HAS_MIRRORROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - MirrorRow = MirrorRow_Any_NEON; - if (IS_ALIGNED(width, 16)) { - MirrorRow = MirrorRow_NEON; - } - } -#endif -#if defined(HAS_MIRRORROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - MirrorRow = MirrorRow_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - MirrorRow = MirrorRow_SSSE3; - } - } -#endif -#if defined(HAS_MIRRORROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - MirrorRow = MirrorRow_Any_AVX2; - if (IS_ALIGNED(width, 32)) { - MirrorRow = MirrorRow_AVX2; - } - } -#endif -// TODO(fbarchard): Mirror on mips handle unaligned memory. -#if defined(HAS_MIRRORROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(src, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst, 4) && IS_ALIGNED(dst_stride, 4)) { - MirrorRow = MirrorRow_DSPR2; - } -#endif -#if defined(HAS_COPYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - CopyRow = IS_ALIGNED(width, 32) ? CopyRow_SSE2 : CopyRow_Any_SSE2; - } -#endif -#if defined(HAS_COPYROW_AVX) - if (TestCpuFlag(kCpuHasAVX)) { - CopyRow = IS_ALIGNED(width, 64) ? CopyRow_AVX : CopyRow_Any_AVX; - } -#endif -#if defined(HAS_COPYROW_ERMS) - if (TestCpuFlag(kCpuHasERMS)) { - CopyRow = CopyRow_ERMS; - } -#endif -#if defined(HAS_COPYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - CopyRow = IS_ALIGNED(width, 32) ? CopyRow_NEON : CopyRow_Any_NEON; - } -#endif -#if defined(HAS_COPYROW_MIPS) - if (TestCpuFlag(kCpuHasMIPS)) { - CopyRow = CopyRow_MIPS; - } -#endif - - // Odd height will harmlessly mirror the middle row twice. - for (y = 0; y < half_height; ++y) { - MirrorRow(src, row, width); // Mirror first row into a buffer - src += src_stride; - MirrorRow(src_bot, dst, width); // Mirror last row into first row - dst += dst_stride; - CopyRow(row, dst_bot, width); // Copy first mirrored row into last - src_bot -= src_stride; - dst_bot -= dst_stride; - } - free_aligned_buffer_64(row); -} - -LIBYUV_API -void TransposeUV(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height) { - int i = height; - void (*TransposeUVWx8)(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width) = TransposeUVWx8_C; -#if defined(HAS_TRANSPOSEUVWX8_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - TransposeUVWx8 = TransposeUVWx8_NEON; - } -#endif -#if defined(HAS_TRANSPOSEUVWX8_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - TransposeUVWx8 = TransposeUVWx8_Any_SSE2; - if (IS_ALIGNED(width, 8)) { - TransposeUVWx8 = TransposeUVWx8_SSE2; - } - } -#endif -#if defined(HAS_TRANSPOSEUVWX8_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 2) && - IS_ALIGNED(src, 4) && IS_ALIGNED(src_stride, 4)) { - TransposeUVWx8 = TransposeUVWx8_DSPR2; - } -#endif - - // Work through the source in 8x8 tiles. - while (i >= 8) { - TransposeUVWx8(src, src_stride, - dst_a, dst_stride_a, - dst_b, dst_stride_b, - width); - src += 8 * src_stride; // Go down 8 rows. - dst_a += 8; // Move over 8 columns. - dst_b += 8; // Move over 8 columns. - i -= 8; - } - - if (i > 0) { - TransposeUVWxH_C(src, src_stride, - dst_a, dst_stride_a, - dst_b, dst_stride_b, - width, i); - } -} - -LIBYUV_API -void RotateUV90(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height) { - src += src_stride * (height - 1); - src_stride = -src_stride; - - TransposeUV(src, src_stride, - dst_a, dst_stride_a, - dst_b, dst_stride_b, - width, height); -} - -LIBYUV_API -void RotateUV270(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height) { - dst_a += dst_stride_a * (width - 1); - dst_b += dst_stride_b * (width - 1); - dst_stride_a = -dst_stride_a; - dst_stride_b = -dst_stride_b; - - TransposeUV(src, src_stride, - dst_a, dst_stride_a, - dst_b, dst_stride_b, - width, height); -} - -// Rotate 180 is a horizontal and vertical flip. -LIBYUV_API -void RotateUV180(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height) { - int i; - void (*MirrorUVRow)(const uint8* src, uint8* dst_u, uint8* dst_v, int width) = - MirrorUVRow_C; -#if defined(HAS_MIRRORUVROW_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 8)) { - MirrorUVRow = MirrorUVRow_NEON; - } -#endif -#if defined(HAS_MIRRORUVROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && IS_ALIGNED(width, 16)) { - MirrorUVRow = MirrorUVRow_SSSE3; - } -#endif -#if defined(HAS_MIRRORUVROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(src, 4) && IS_ALIGNED(src_stride, 4)) { - MirrorUVRow = MirrorUVRow_DSPR2; - } -#endif - - dst_a += dst_stride_a * (height - 1); - dst_b += dst_stride_b * (height - 1); - - for (i = 0; i < height; ++i) { - MirrorUVRow(src, dst_a, dst_b, width); - src += src_stride; - dst_a -= dst_stride_a; - dst_b -= dst_stride_b; - } -} - -LIBYUV_API -int RotatePlane(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height, - enum RotationMode mode) { - if (!src || width <= 0 || height == 0 || !dst) { - return -1; - } - - // Negative height means invert the image. - if (height < 0) { - height = -height; - src = src + (height - 1) * src_stride; - src_stride = -src_stride; - } - - switch (mode) { - case kRotate0: - // copy frame - CopyPlane(src, src_stride, - dst, dst_stride, - width, height); - return 0; - case kRotate90: - RotatePlane90(src, src_stride, - dst, dst_stride, - width, height); - return 0; - case kRotate270: - RotatePlane270(src, src_stride, - dst, dst_stride, - width, height); - return 0; - case kRotate180: - RotatePlane180(src, src_stride, - dst, dst_stride, - width, height); - return 0; - default: - break; - } - return -1; -} - -LIBYUV_API -int I420Rotate(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height, - enum RotationMode mode) { - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - if (!src_y || !src_u || !src_v || width <= 0 || height == 0 || - !dst_y || !dst_u || !dst_v) { - return -1; - } - - // Negative height means invert the image. - if (height < 0) { - height = -height; - halfheight = (height + 1) >> 1; - src_y = src_y + (height - 1) * src_stride_y; - src_u = src_u + (halfheight - 1) * src_stride_u; - src_v = src_v + (halfheight - 1) * src_stride_v; - src_stride_y = -src_stride_y; - src_stride_u = -src_stride_u; - src_stride_v = -src_stride_v; - } - - switch (mode) { - case kRotate0: - // copy frame - return I420Copy(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height); - case kRotate90: - RotatePlane90(src_y, src_stride_y, - dst_y, dst_stride_y, - width, height); - RotatePlane90(src_u, src_stride_u, - dst_u, dst_stride_u, - halfwidth, halfheight); - RotatePlane90(src_v, src_stride_v, - dst_v, dst_stride_v, - halfwidth, halfheight); - return 0; - case kRotate270: - RotatePlane270(src_y, src_stride_y, - dst_y, dst_stride_y, - width, height); - RotatePlane270(src_u, src_stride_u, - dst_u, dst_stride_u, - halfwidth, halfheight); - RotatePlane270(src_v, src_stride_v, - dst_v, dst_stride_v, - halfwidth, halfheight); - return 0; - case kRotate180: - RotatePlane180(src_y, src_stride_y, - dst_y, dst_stride_y, - width, height); - RotatePlane180(src_u, src_stride_u, - dst_u, dst_stride_u, - halfwidth, halfheight); - RotatePlane180(src_v, src_stride_v, - dst_v, dst_stride_v, - halfwidth, halfheight); - return 0; - default: - break; - } - return -1; -} - -LIBYUV_API -int NV12ToI420Rotate(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height, - enum RotationMode mode) { - int halfwidth = (width + 1) >> 1; - int halfheight = (height + 1) >> 1; - if (!src_y || !src_uv || width <= 0 || height == 0 || - !dst_y || !dst_u || !dst_v) { - return -1; - } - - // Negative height means invert the image. - if (height < 0) { - height = -height; - halfheight = (height + 1) >> 1; - src_y = src_y + (height - 1) * src_stride_y; - src_uv = src_uv + (halfheight - 1) * src_stride_uv; - src_stride_y = -src_stride_y; - src_stride_uv = -src_stride_uv; - } - - switch (mode) { - case kRotate0: - // copy frame - return NV12ToI420(src_y, src_stride_y, - src_uv, src_stride_uv, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - width, height); - case kRotate90: - RotatePlane90(src_y, src_stride_y, - dst_y, dst_stride_y, - width, height); - RotateUV90(src_uv, src_stride_uv, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - halfwidth, halfheight); - return 0; - case kRotate270: - RotatePlane270(src_y, src_stride_y, - dst_y, dst_stride_y, - width, height); - RotateUV270(src_uv, src_stride_uv, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - halfwidth, halfheight); - return 0; - case kRotate180: - RotatePlane180(src_y, src_stride_y, - dst_y, dst_stride_y, - width, height); - RotateUV180(src_uv, src_stride_uv, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - halfwidth, halfheight); - return 0; - default: - break; - } - return -1; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate_any.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate_any.cc deleted file mode 100644 index 31a74c3..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate_any.cc +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/rotate.h" -#include "libyuv/rotate_row.h" - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#define TANY(NAMEANY, TPOS_SIMD, MASK) \ - void NAMEANY(const uint8* src, int src_stride, \ - uint8* dst, int dst_stride, int width) { \ - int r = width & MASK; \ - int n = width - r; \ - if (n > 0) { \ - TPOS_SIMD(src, src_stride, dst, dst_stride, n); \ - } \ - TransposeWx8_C(src + n, src_stride, dst + n * dst_stride, dst_stride, r);\ - } - -#ifdef HAS_TRANSPOSEWX8_NEON -TANY(TransposeWx8_Any_NEON, TransposeWx8_NEON, 7) -#endif -#ifdef HAS_TRANSPOSEWX8_SSSE3 -TANY(TransposeWx8_Any_SSSE3, TransposeWx8_SSSE3, 7) -#endif -#ifdef HAS_TRANSPOSEWX8_FAST_SSSE3 -TANY(TransposeWx8_Fast_Any_SSSE3, TransposeWx8_Fast_SSSE3, 15) -#endif -#ifdef HAS_TRANSPOSEWX8_DSPR2 -TANY(TransposeWx8_Any_DSPR2, TransposeWx8_DSPR2, 7) -#endif -#undef TANY - -#define TUVANY(NAMEANY, TPOS_SIMD, MASK) \ - void NAMEANY(const uint8* src, int src_stride, \ - uint8* dst_a, int dst_stride_a, \ - uint8* dst_b, int dst_stride_b, int width) { \ - int r = width & MASK; \ - int n = width - r; \ - if (n > 0) { \ - TPOS_SIMD(src, src_stride, dst_a, dst_stride_a, dst_b, dst_stride_b, \ - n); \ - } \ - TransposeUVWx8_C(src + n * 2, src_stride, \ - dst_a + n * dst_stride_a, dst_stride_a, \ - dst_b + n * dst_stride_b, dst_stride_b, r); \ - } - -#ifdef HAS_TRANSPOSEUVWX8_NEON -TUVANY(TransposeUVWx8_Any_NEON, TransposeUVWx8_NEON, 7) -#endif -#ifdef HAS_TRANSPOSEUVWX8_SSE2 -TUVANY(TransposeUVWx8_Any_SSE2, TransposeUVWx8_SSE2, 7) -#endif -#ifdef HAS_TRANSPOSEUVWX8_DSPR2 -TUVANY(TransposeUVWx8_Any_DSPR2, TransposeUVWx8_DSPR2, 7) -#endif -#undef TUVANY - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - - - - - diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate_argb.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate_argb.cc deleted file mode 100644 index 787c0ad..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate_argb.cc +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/rotate.h" - -#include "libyuv/cpu_id.h" -#include "libyuv/convert.h" -#include "libyuv/planar_functions.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// ARGBScale has a function to copy pixels to a row, striding each source -// pixel by a constant. -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || \ - (defined(__x86_64__) && !defined(__native_client__)) || defined(__i386__)) -#define HAS_SCALEARGBROWDOWNEVEN_SSE2 -void ScaleARGBRowDownEven_SSE2(const uint8* src_ptr, int src_stride, - int src_stepx, uint8* dst_ptr, int dst_width); -#endif -#if !defined(LIBYUV_DISABLE_NEON) && !defined(__native_client__) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_SCALEARGBROWDOWNEVEN_NEON -void ScaleARGBRowDownEven_NEON(const uint8* src_ptr, int src_stride, - int src_stepx, uint8* dst_ptr, int dst_width); -#endif - -void ScaleARGBRowDownEven_C(const uint8* src_ptr, int, - int src_stepx, uint8* dst_ptr, int dst_width); - -static void ARGBTranspose(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width, int height) { - int i; - int src_pixel_step = src_stride >> 2; - void (*ScaleARGBRowDownEven)(const uint8* src_ptr, int src_stride, - int src_step, uint8* dst_ptr, int dst_width) = ScaleARGBRowDownEven_C; -#if defined(HAS_SCALEARGBROWDOWNEVEN_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(height, 4)) { // Width of dest. - ScaleARGBRowDownEven = ScaleARGBRowDownEven_SSE2; - } -#endif -#if defined(HAS_SCALEARGBROWDOWNEVEN_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(height, 4)) { // Width of dest. - ScaleARGBRowDownEven = ScaleARGBRowDownEven_NEON; - } -#endif - - for (i = 0; i < width; ++i) { // column of source to row of dest. - ScaleARGBRowDownEven(src, 0, src_pixel_step, dst, height); - dst += dst_stride; - src += 4; - } -} - -void ARGBRotate90(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width, int height) { - // Rotate by 90 is a ARGBTranspose with the source read - // from bottom to top. So set the source pointer to the end - // of the buffer and flip the sign of the source stride. - src += src_stride * (height - 1); - src_stride = -src_stride; - ARGBTranspose(src, src_stride, dst, dst_stride, width, height); -} - -void ARGBRotate270(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width, int height) { - // Rotate by 270 is a ARGBTranspose with the destination written - // from bottom to top. So set the destination pointer to the end - // of the buffer and flip the sign of the destination stride. - dst += dst_stride * (width - 1); - dst_stride = -dst_stride; - ARGBTranspose(src, src_stride, dst, dst_stride, width, height); -} - -void ARGBRotate180(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width, int height) { - // Swap first and last row and mirror the content. Uses a temporary row. - align_buffer_64(row, width * 4); - const uint8* src_bot = src + src_stride * (height - 1); - uint8* dst_bot = dst + dst_stride * (height - 1); - int half_height = (height + 1) >> 1; - int y; - void (*ARGBMirrorRow)(const uint8* src, uint8* dst, int width) = - ARGBMirrorRow_C; - void (*CopyRow)(const uint8* src, uint8* dst, int width) = CopyRow_C; -#if defined(HAS_ARGBMIRRORROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBMirrorRow = ARGBMirrorRow_Any_NEON; - if (IS_ALIGNED(width, 4)) { - ARGBMirrorRow = ARGBMirrorRow_NEON; - } - } -#endif -#if defined(HAS_ARGBMIRRORROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ARGBMirrorRow = ARGBMirrorRow_Any_SSE2; - if (IS_ALIGNED(width, 4)) { - ARGBMirrorRow = ARGBMirrorRow_SSE2; - } - } -#endif -#if defined(HAS_ARGBMIRRORROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ARGBMirrorRow = ARGBMirrorRow_Any_AVX2; - if (IS_ALIGNED(width, 8)) { - ARGBMirrorRow = ARGBMirrorRow_AVX2; - } - } -#endif -#if defined(HAS_COPYROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - CopyRow = IS_ALIGNED(width * 4, 32) ? CopyRow_SSE2 : CopyRow_Any_SSE2; - } -#endif -#if defined(HAS_COPYROW_AVX) - if (TestCpuFlag(kCpuHasAVX)) { - CopyRow = IS_ALIGNED(width * 4, 64) ? CopyRow_AVX : CopyRow_Any_AVX; - } -#endif -#if defined(HAS_COPYROW_ERMS) - if (TestCpuFlag(kCpuHasERMS)) { - CopyRow = CopyRow_ERMS; - } -#endif -#if defined(HAS_COPYROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - CopyRow = IS_ALIGNED(width * 4, 32) ? CopyRow_NEON : CopyRow_Any_NEON; - } -#endif -#if defined(HAS_COPYROW_MIPS) - if (TestCpuFlag(kCpuHasMIPS)) { - CopyRow = CopyRow_MIPS; - } -#endif - - // Odd height will harmlessly mirror the middle row twice. - for (y = 0; y < half_height; ++y) { - ARGBMirrorRow(src, row, width); // Mirror first row into a buffer - ARGBMirrorRow(src_bot, dst, width); // Mirror last row into first row - CopyRow(row, dst_bot, width * 4); // Copy first mirrored row into last - src += src_stride; - dst += dst_stride; - src_bot -= src_stride; - dst_bot -= dst_stride; - } - free_aligned_buffer_64(row); -} - -LIBYUV_API -int ARGBRotate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, int width, int height, - enum RotationMode mode) { - if (!src_argb || width <= 0 || height == 0 || !dst_argb) { - return -1; - } - - // Negative height means invert the image. - if (height < 0) { - height = -height; - src_argb = src_argb + (height - 1) * src_stride_argb; - src_stride_argb = -src_stride_argb; - } - - switch (mode) { - case kRotate0: - // copy frame - return ARGBCopy(src_argb, src_stride_argb, - dst_argb, dst_stride_argb, - width, height); - case kRotate90: - ARGBRotate90(src_argb, src_stride_argb, - dst_argb, dst_stride_argb, - width, height); - return 0; - case kRotate270: - ARGBRotate270(src_argb, src_stride_argb, - dst_argb, dst_stride_argb, - width, height); - return 0; - case kRotate180: - ARGBRotate180(src_argb, src_stride_argb, - dst_argb, dst_stride_argb, - width, height); - return 0; - default: - break; - } - return -1; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate_common.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate_common.cc deleted file mode 100644 index b33a9a0..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate_common.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" -#include "libyuv/rotate_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -void TransposeWx8_C(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width) { - int i; - for (i = 0; i < width; ++i) { - dst[0] = src[0 * src_stride]; - dst[1] = src[1 * src_stride]; - dst[2] = src[2 * src_stride]; - dst[3] = src[3 * src_stride]; - dst[4] = src[4 * src_stride]; - dst[5] = src[5 * src_stride]; - dst[6] = src[6 * src_stride]; - dst[7] = src[7 * src_stride]; - ++src; - dst += dst_stride; - } -} - -void TransposeUVWx8_C(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width) { - int i; - for (i = 0; i < width; ++i) { - dst_a[0] = src[0 * src_stride + 0]; - dst_b[0] = src[0 * src_stride + 1]; - dst_a[1] = src[1 * src_stride + 0]; - dst_b[1] = src[1 * src_stride + 1]; - dst_a[2] = src[2 * src_stride + 0]; - dst_b[2] = src[2 * src_stride + 1]; - dst_a[3] = src[3 * src_stride + 0]; - dst_b[3] = src[3 * src_stride + 1]; - dst_a[4] = src[4 * src_stride + 0]; - dst_b[4] = src[4 * src_stride + 1]; - dst_a[5] = src[5 * src_stride + 0]; - dst_b[5] = src[5 * src_stride + 1]; - dst_a[6] = src[6 * src_stride + 0]; - dst_b[6] = src[6 * src_stride + 1]; - dst_a[7] = src[7 * src_stride + 0]; - dst_b[7] = src[7 * src_stride + 1]; - src += 2; - dst_a += dst_stride_a; - dst_b += dst_stride_b; - } -} - -void TransposeWxH_C(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height) { - int i; - for (i = 0; i < width; ++i) { - int j; - for (j = 0; j < height; ++j) { - dst[i * dst_stride + j] = src[j * src_stride + i]; - } - } -} - -void TransposeUVWxH_C(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height) { - int i; - for (i = 0; i < width * 2; i += 2) { - int j; - for (j = 0; j < height; ++j) { - dst_a[j + ((i >> 1) * dst_stride_a)] = src[i + (j * src_stride)]; - dst_b[j + ((i >> 1) * dst_stride_b)] = src[i + (j * src_stride) + 1]; - } - } -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate_gcc.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate_gcc.cc deleted file mode 100644 index cbe870c..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate_gcc.cc +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright 2015 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" -#include "libyuv/rotate_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC x86 and x64. -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) && !defined(_MSC_VER))) - -// Transpose 8x8. 32 or 64 bit, but not NaCL for 64 bit. -#if defined(HAS_TRANSPOSEWX8_SSSE3) -void TransposeWx8_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width) { - asm volatile ( - // Read in the data from the source pointer. - // First round of bit swap. - LABELALIGN - "1: \n" - "movq (%0),%%xmm0 \n" - "movq (%0,%3),%%xmm1 \n" - "lea (%0,%3,2),%0 \n" - "punpcklbw %%xmm1,%%xmm0 \n" - "movq (%0),%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "palignr $0x8,%%xmm1,%%xmm1 \n" - "movq (%0,%3),%%xmm3 \n" - "lea (%0,%3,2),%0 \n" - "punpcklbw %%xmm3,%%xmm2 \n" - "movdqa %%xmm2,%%xmm3 \n" - "movq (%0),%%xmm4 \n" - "palignr $0x8,%%xmm3,%%xmm3 \n" - "movq (%0,%3),%%xmm5 \n" - "lea (%0,%3,2),%0 \n" - "punpcklbw %%xmm5,%%xmm4 \n" - "movdqa %%xmm4,%%xmm5 \n" - "movq (%0),%%xmm6 \n" - "palignr $0x8,%%xmm5,%%xmm5 \n" - "movq (%0,%3),%%xmm7 \n" - "lea (%0,%3,2),%0 \n" - "punpcklbw %%xmm7,%%xmm6 \n" - "neg %3 \n" - "movdqa %%xmm6,%%xmm7 \n" - "lea 0x8(%0,%3,8),%0 \n" - "palignr $0x8,%%xmm7,%%xmm7 \n" - "neg %3 \n" - // Second round of bit swap. - "punpcklwd %%xmm2,%%xmm0 \n" - "punpcklwd %%xmm3,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "movdqa %%xmm1,%%xmm3 \n" - "palignr $0x8,%%xmm2,%%xmm2 \n" - "palignr $0x8,%%xmm3,%%xmm3 \n" - "punpcklwd %%xmm6,%%xmm4 \n" - "punpcklwd %%xmm7,%%xmm5 \n" - "movdqa %%xmm4,%%xmm6 \n" - "movdqa %%xmm5,%%xmm7 \n" - "palignr $0x8,%%xmm6,%%xmm6 \n" - "palignr $0x8,%%xmm7,%%xmm7 \n" - // Third round of bit swap. - // Write to the destination pointer. - "punpckldq %%xmm4,%%xmm0 \n" - "movq %%xmm0,(%1) \n" - "movdqa %%xmm0,%%xmm4 \n" - "palignr $0x8,%%xmm4,%%xmm4 \n" - "movq %%xmm4,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "punpckldq %%xmm6,%%xmm2 \n" - "movdqa %%xmm2,%%xmm6 \n" - "movq %%xmm2,(%1) \n" - "palignr $0x8,%%xmm6,%%xmm6 \n" - "punpckldq %%xmm5,%%xmm1 \n" - "movq %%xmm6,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "movdqa %%xmm1,%%xmm5 \n" - "movq %%xmm1,(%1) \n" - "palignr $0x8,%%xmm5,%%xmm5 \n" - "movq %%xmm5,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "punpckldq %%xmm7,%%xmm3 \n" - "movq %%xmm3,(%1) \n" - "movdqa %%xmm3,%%xmm7 \n" - "palignr $0x8,%%xmm7,%%xmm7 \n" - "sub $0x8,%2 \n" - "movq %%xmm7,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : "r"((intptr_t)(src_stride)), // %3 - "r"((intptr_t)(dst_stride)) // %4 - : "memory", "cc", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // defined(HAS_TRANSPOSEWX8_SSSE3) - -// Transpose 16x8. 64 bit -#if defined(HAS_TRANSPOSEWX8_FAST_SSSE3) -void TransposeWx8_Fast_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width) { - asm volatile ( - // Read in the data from the source pointer. - // First round of bit swap. - LABELALIGN - "1: \n" - "movdqu (%0),%%xmm0 \n" - "movdqu (%0,%3),%%xmm1 \n" - "lea (%0,%3,2),%0 \n" - "movdqa %%xmm0,%%xmm8 \n" - "punpcklbw %%xmm1,%%xmm0 \n" - "punpckhbw %%xmm1,%%xmm8 \n" - "movdqu (%0),%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm8,%%xmm9 \n" - "palignr $0x8,%%xmm1,%%xmm1 \n" - "palignr $0x8,%%xmm9,%%xmm9 \n" - "movdqu (%0,%3),%%xmm3 \n" - "lea (%0,%3,2),%0 \n" - "movdqa %%xmm2,%%xmm10 \n" - "punpcklbw %%xmm3,%%xmm2 \n" - "punpckhbw %%xmm3,%%xmm10 \n" - "movdqa %%xmm2,%%xmm3 \n" - "movdqa %%xmm10,%%xmm11 \n" - "movdqu (%0),%%xmm4 \n" - "palignr $0x8,%%xmm3,%%xmm3 \n" - "palignr $0x8,%%xmm11,%%xmm11 \n" - "movdqu (%0,%3),%%xmm5 \n" - "lea (%0,%3,2),%0 \n" - "movdqa %%xmm4,%%xmm12 \n" - "punpcklbw %%xmm5,%%xmm4 \n" - "punpckhbw %%xmm5,%%xmm12 \n" - "movdqa %%xmm4,%%xmm5 \n" - "movdqa %%xmm12,%%xmm13 \n" - "movdqu (%0),%%xmm6 \n" - "palignr $0x8,%%xmm5,%%xmm5 \n" - "palignr $0x8,%%xmm13,%%xmm13 \n" - "movdqu (%0,%3),%%xmm7 \n" - "lea (%0,%3,2),%0 \n" - "movdqa %%xmm6,%%xmm14 \n" - "punpcklbw %%xmm7,%%xmm6 \n" - "punpckhbw %%xmm7,%%xmm14 \n" - "neg %3 \n" - "movdqa %%xmm6,%%xmm7 \n" - "movdqa %%xmm14,%%xmm15 \n" - "lea 0x10(%0,%3,8),%0 \n" - "palignr $0x8,%%xmm7,%%xmm7 \n" - "palignr $0x8,%%xmm15,%%xmm15 \n" - "neg %3 \n" - // Second round of bit swap. - "punpcklwd %%xmm2,%%xmm0 \n" - "punpcklwd %%xmm3,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "movdqa %%xmm1,%%xmm3 \n" - "palignr $0x8,%%xmm2,%%xmm2 \n" - "palignr $0x8,%%xmm3,%%xmm3 \n" - "punpcklwd %%xmm6,%%xmm4 \n" - "punpcklwd %%xmm7,%%xmm5 \n" - "movdqa %%xmm4,%%xmm6 \n" - "movdqa %%xmm5,%%xmm7 \n" - "palignr $0x8,%%xmm6,%%xmm6 \n" - "palignr $0x8,%%xmm7,%%xmm7 \n" - "punpcklwd %%xmm10,%%xmm8 \n" - "punpcklwd %%xmm11,%%xmm9 \n" - "movdqa %%xmm8,%%xmm10 \n" - "movdqa %%xmm9,%%xmm11 \n" - "palignr $0x8,%%xmm10,%%xmm10 \n" - "palignr $0x8,%%xmm11,%%xmm11 \n" - "punpcklwd %%xmm14,%%xmm12 \n" - "punpcklwd %%xmm15,%%xmm13 \n" - "movdqa %%xmm12,%%xmm14 \n" - "movdqa %%xmm13,%%xmm15 \n" - "palignr $0x8,%%xmm14,%%xmm14 \n" - "palignr $0x8,%%xmm15,%%xmm15 \n" - // Third round of bit swap. - // Write to the destination pointer. - "punpckldq %%xmm4,%%xmm0 \n" - "movq %%xmm0,(%1) \n" - "movdqa %%xmm0,%%xmm4 \n" - "palignr $0x8,%%xmm4,%%xmm4 \n" - "movq %%xmm4,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "punpckldq %%xmm6,%%xmm2 \n" - "movdqa %%xmm2,%%xmm6 \n" - "movq %%xmm2,(%1) \n" - "palignr $0x8,%%xmm6,%%xmm6 \n" - "punpckldq %%xmm5,%%xmm1 \n" - "movq %%xmm6,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "movdqa %%xmm1,%%xmm5 \n" - "movq %%xmm1,(%1) \n" - "palignr $0x8,%%xmm5,%%xmm5 \n" - "movq %%xmm5,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "punpckldq %%xmm7,%%xmm3 \n" - "movq %%xmm3,(%1) \n" - "movdqa %%xmm3,%%xmm7 \n" - "palignr $0x8,%%xmm7,%%xmm7 \n" - "movq %%xmm7,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "punpckldq %%xmm12,%%xmm8 \n" - "movq %%xmm8,(%1) \n" - "movdqa %%xmm8,%%xmm12 \n" - "palignr $0x8,%%xmm12,%%xmm12 \n" - "movq %%xmm12,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "punpckldq %%xmm14,%%xmm10 \n" - "movdqa %%xmm10,%%xmm14 \n" - "movq %%xmm10,(%1) \n" - "palignr $0x8,%%xmm14,%%xmm14 \n" - "punpckldq %%xmm13,%%xmm9 \n" - "movq %%xmm14,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "movdqa %%xmm9,%%xmm13 \n" - "movq %%xmm9,(%1) \n" - "palignr $0x8,%%xmm13,%%xmm13 \n" - "movq %%xmm13,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "punpckldq %%xmm15,%%xmm11 \n" - "movq %%xmm11,(%1) \n" - "movdqa %%xmm11,%%xmm15 \n" - "palignr $0x8,%%xmm15,%%xmm15 \n" - "sub $0x10,%2 \n" - "movq %%xmm15,(%1,%4) \n" - "lea (%1,%4,2),%1 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : "r"((intptr_t)(src_stride)), // %3 - "r"((intptr_t)(dst_stride)) // %4 - : "memory", "cc", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", - "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15" - ); -} -#endif // defined(HAS_TRANSPOSEWX8_FAST_SSSE3) - -// Transpose UV 8x8. 64 bit. -#if defined(HAS_TRANSPOSEUVWX8_SSE2) -void TransposeUVWx8_SSE2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width) { - asm volatile ( - // Read in the data from the source pointer. - // First round of bit swap. - LABELALIGN - "1: \n" - "movdqu (%0),%%xmm0 \n" - "movdqu (%0,%4),%%xmm1 \n" - "lea (%0,%4,2),%0 \n" - "movdqa %%xmm0,%%xmm8 \n" - "punpcklbw %%xmm1,%%xmm0 \n" - "punpckhbw %%xmm1,%%xmm8 \n" - "movdqa %%xmm8,%%xmm1 \n" - "movdqu (%0),%%xmm2 \n" - "movdqu (%0,%4),%%xmm3 \n" - "lea (%0,%4,2),%0 \n" - "movdqa %%xmm2,%%xmm8 \n" - "punpcklbw %%xmm3,%%xmm2 \n" - "punpckhbw %%xmm3,%%xmm8 \n" - "movdqa %%xmm8,%%xmm3 \n" - "movdqu (%0),%%xmm4 \n" - "movdqu (%0,%4),%%xmm5 \n" - "lea (%0,%4,2),%0 \n" - "movdqa %%xmm4,%%xmm8 \n" - "punpcklbw %%xmm5,%%xmm4 \n" - "punpckhbw %%xmm5,%%xmm8 \n" - "movdqa %%xmm8,%%xmm5 \n" - "movdqu (%0),%%xmm6 \n" - "movdqu (%0,%4),%%xmm7 \n" - "lea (%0,%4,2),%0 \n" - "movdqa %%xmm6,%%xmm8 \n" - "punpcklbw %%xmm7,%%xmm6 \n" - "neg %4 \n" - "lea 0x10(%0,%4,8),%0 \n" - "punpckhbw %%xmm7,%%xmm8 \n" - "movdqa %%xmm8,%%xmm7 \n" - "neg %4 \n" - // Second round of bit swap. - "movdqa %%xmm0,%%xmm8 \n" - "movdqa %%xmm1,%%xmm9 \n" - "punpckhwd %%xmm2,%%xmm8 \n" - "punpckhwd %%xmm3,%%xmm9 \n" - "punpcklwd %%xmm2,%%xmm0 \n" - "punpcklwd %%xmm3,%%xmm1 \n" - "movdqa %%xmm8,%%xmm2 \n" - "movdqa %%xmm9,%%xmm3 \n" - "movdqa %%xmm4,%%xmm8 \n" - "movdqa %%xmm5,%%xmm9 \n" - "punpckhwd %%xmm6,%%xmm8 \n" - "punpckhwd %%xmm7,%%xmm9 \n" - "punpcklwd %%xmm6,%%xmm4 \n" - "punpcklwd %%xmm7,%%xmm5 \n" - "movdqa %%xmm8,%%xmm6 \n" - "movdqa %%xmm9,%%xmm7 \n" - // Third round of bit swap. - // Write to the destination pointer. - "movdqa %%xmm0,%%xmm8 \n" - "punpckldq %%xmm4,%%xmm0 \n" - "movlpd %%xmm0,(%1) \n" // Write back U channel - "movhpd %%xmm0,(%2) \n" // Write back V channel - "punpckhdq %%xmm4,%%xmm8 \n" - "movlpd %%xmm8,(%1,%5) \n" - "lea (%1,%5,2),%1 \n" - "movhpd %%xmm8,(%2,%6) \n" - "lea (%2,%6,2),%2 \n" - "movdqa %%xmm2,%%xmm8 \n" - "punpckldq %%xmm6,%%xmm2 \n" - "movlpd %%xmm2,(%1) \n" - "movhpd %%xmm2,(%2) \n" - "punpckhdq %%xmm6,%%xmm8 \n" - "movlpd %%xmm8,(%1,%5) \n" - "lea (%1,%5,2),%1 \n" - "movhpd %%xmm8,(%2,%6) \n" - "lea (%2,%6,2),%2 \n" - "movdqa %%xmm1,%%xmm8 \n" - "punpckldq %%xmm5,%%xmm1 \n" - "movlpd %%xmm1,(%1) \n" - "movhpd %%xmm1,(%2) \n" - "punpckhdq %%xmm5,%%xmm8 \n" - "movlpd %%xmm8,(%1,%5) \n" - "lea (%1,%5,2),%1 \n" - "movhpd %%xmm8,(%2,%6) \n" - "lea (%2,%6,2),%2 \n" - "movdqa %%xmm3,%%xmm8 \n" - "punpckldq %%xmm7,%%xmm3 \n" - "movlpd %%xmm3,(%1) \n" - "movhpd %%xmm3,(%2) \n" - "punpckhdq %%xmm7,%%xmm8 \n" - "sub $0x8,%3 \n" - "movlpd %%xmm8,(%1,%5) \n" - "lea (%1,%5,2),%1 \n" - "movhpd %%xmm8,(%2,%6) \n" - "lea (%2,%6,2),%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst_a), // %1 - "+r"(dst_b), // %2 - "+r"(width) // %3 - : "r"((intptr_t)(src_stride)), // %4 - "r"((intptr_t)(dst_stride_a)), // %5 - "r"((intptr_t)(dst_stride_b)) // %6 - : "memory", "cc", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", - "xmm8", "xmm9" - ); -} -#endif // defined(HAS_TRANSPOSEUVWX8_SSE2) -#endif // defined(__x86_64__) || defined(__i386__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate_mips.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate_mips.cc deleted file mode 100644 index 1e8ce25..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate_mips.cc +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" -#include "libyuv/rotate_row.h" - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if !defined(LIBYUV_DISABLE_MIPS) && \ - defined(__mips_dsp) && (__mips_dsp_rev >= 2) && \ - (_MIPS_SIM == _MIPS_SIM_ABI32) - -void TransposeWx8_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - "sll $t2, %[src_stride], 0x1 \n" // src_stride x 2 - "sll $t4, %[src_stride], 0x2 \n" // src_stride x 4 - "sll $t9, %[src_stride], 0x3 \n" // src_stride x 8 - "addu $t3, $t2, %[src_stride] \n" - "addu $t5, $t4, %[src_stride] \n" - "addu $t6, $t2, $t4 \n" - "andi $t0, %[dst], 0x3 \n" - "andi $t1, %[dst_stride], 0x3 \n" - "or $t0, $t0, $t1 \n" - "bnez $t0, 11f \n" - " subu $t7, $t9, %[src_stride] \n" -//dst + dst_stride word aligned - "1: \n" - "lbu $t0, 0(%[src]) \n" - "lbux $t1, %[src_stride](%[src]) \n" - "lbux $t8, $t2(%[src]) \n" - "lbux $t9, $t3(%[src]) \n" - "sll $t1, $t1, 16 \n" - "sll $t9, $t9, 16 \n" - "or $t0, $t0, $t1 \n" - "or $t8, $t8, $t9 \n" - "precr.qb.ph $s0, $t8, $t0 \n" - "lbux $t0, $t4(%[src]) \n" - "lbux $t1, $t5(%[src]) \n" - "lbux $t8, $t6(%[src]) \n" - "lbux $t9, $t7(%[src]) \n" - "sll $t1, $t1, 16 \n" - "sll $t9, $t9, 16 \n" - "or $t0, $t0, $t1 \n" - "or $t8, $t8, $t9 \n" - "precr.qb.ph $s1, $t8, $t0 \n" - "sw $s0, 0(%[dst]) \n" - "addiu %[width], -1 \n" - "addiu %[src], 1 \n" - "sw $s1, 4(%[dst]) \n" - "bnez %[width], 1b \n" - " addu %[dst], %[dst], %[dst_stride] \n" - "b 2f \n" -//dst + dst_stride unaligned - "11: \n" - "lbu $t0, 0(%[src]) \n" - "lbux $t1, %[src_stride](%[src]) \n" - "lbux $t8, $t2(%[src]) \n" - "lbux $t9, $t3(%[src]) \n" - "sll $t1, $t1, 16 \n" - "sll $t9, $t9, 16 \n" - "or $t0, $t0, $t1 \n" - "or $t8, $t8, $t9 \n" - "precr.qb.ph $s0, $t8, $t0 \n" - "lbux $t0, $t4(%[src]) \n" - "lbux $t1, $t5(%[src]) \n" - "lbux $t8, $t6(%[src]) \n" - "lbux $t9, $t7(%[src]) \n" - "sll $t1, $t1, 16 \n" - "sll $t9, $t9, 16 \n" - "or $t0, $t0, $t1 \n" - "or $t8, $t8, $t9 \n" - "precr.qb.ph $s1, $t8, $t0 \n" - "swr $s0, 0(%[dst]) \n" - "swl $s0, 3(%[dst]) \n" - "addiu %[width], -1 \n" - "addiu %[src], 1 \n" - "swr $s1, 4(%[dst]) \n" - "swl $s1, 7(%[dst]) \n" - "bnez %[width], 11b \n" - "addu %[dst], %[dst], %[dst_stride] \n" - "2: \n" - ".set pop \n" - :[src] "+r" (src), - [dst] "+r" (dst), - [width] "+r" (width) - :[src_stride] "r" (src_stride), - [dst_stride] "r" (dst_stride) - : "t0", "t1", "t2", "t3", "t4", "t5", - "t6", "t7", "t8", "t9", - "s0", "s1" - ); -} - -void TransposeWx8_Fast_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width) { - __asm__ __volatile__ ( - ".set noat \n" - ".set push \n" - ".set noreorder \n" - "beqz %[width], 2f \n" - " sll $t2, %[src_stride], 0x1 \n" // src_stride x 2 - "sll $t4, %[src_stride], 0x2 \n" // src_stride x 4 - "sll $t9, %[src_stride], 0x3 \n" // src_stride x 8 - "addu $t3, $t2, %[src_stride] \n" - "addu $t5, $t4, %[src_stride] \n" - "addu $t6, $t2, $t4 \n" - - "srl $AT, %[width], 0x2 \n" - "andi $t0, %[dst], 0x3 \n" - "andi $t1, %[dst_stride], 0x3 \n" - "or $t0, $t0, $t1 \n" - "bnez $t0, 11f \n" - " subu $t7, $t9, %[src_stride] \n" -//dst + dst_stride word aligned - "1: \n" - "lw $t0, 0(%[src]) \n" - "lwx $t1, %[src_stride](%[src]) \n" - "lwx $t8, $t2(%[src]) \n" - "lwx $t9, $t3(%[src]) \n" - -// t0 = | 30 | 20 | 10 | 00 | -// t1 = | 31 | 21 | 11 | 01 | -// t8 = | 32 | 22 | 12 | 02 | -// t9 = | 33 | 23 | 13 | 03 | - - "precr.qb.ph $s0, $t1, $t0 \n" - "precr.qb.ph $s1, $t9, $t8 \n" - "precrq.qb.ph $s2, $t1, $t0 \n" - "precrq.qb.ph $s3, $t9, $t8 \n" - - // s0 = | 21 | 01 | 20 | 00 | - // s1 = | 23 | 03 | 22 | 02 | - // s2 = | 31 | 11 | 30 | 10 | - // s3 = | 33 | 13 | 32 | 12 | - - "precr.qb.ph $s4, $s1, $s0 \n" - "precrq.qb.ph $s5, $s1, $s0 \n" - "precr.qb.ph $s6, $s3, $s2 \n" - "precrq.qb.ph $s7, $s3, $s2 \n" - - // s4 = | 03 | 02 | 01 | 00 | - // s5 = | 23 | 22 | 21 | 20 | - // s6 = | 13 | 12 | 11 | 10 | - // s7 = | 33 | 32 | 31 | 30 | - - "lwx $t0, $t4(%[src]) \n" - "lwx $t1, $t5(%[src]) \n" - "lwx $t8, $t6(%[src]) \n" - "lwx $t9, $t7(%[src]) \n" - -// t0 = | 34 | 24 | 14 | 04 | -// t1 = | 35 | 25 | 15 | 05 | -// t8 = | 36 | 26 | 16 | 06 | -// t9 = | 37 | 27 | 17 | 07 | - - "precr.qb.ph $s0, $t1, $t0 \n" - "precr.qb.ph $s1, $t9, $t8 \n" - "precrq.qb.ph $s2, $t1, $t0 \n" - "precrq.qb.ph $s3, $t9, $t8 \n" - - // s0 = | 25 | 05 | 24 | 04 | - // s1 = | 27 | 07 | 26 | 06 | - // s2 = | 35 | 15 | 34 | 14 | - // s3 = | 37 | 17 | 36 | 16 | - - "precr.qb.ph $t0, $s1, $s0 \n" - "precrq.qb.ph $t1, $s1, $s0 \n" - "precr.qb.ph $t8, $s3, $s2 \n" - "precrq.qb.ph $t9, $s3, $s2 \n" - - // t0 = | 07 | 06 | 05 | 04 | - // t1 = | 27 | 26 | 25 | 24 | - // t8 = | 17 | 16 | 15 | 14 | - // t9 = | 37 | 36 | 35 | 34 | - - "addu $s0, %[dst], %[dst_stride] \n" - "addu $s1, $s0, %[dst_stride] \n" - "addu $s2, $s1, %[dst_stride] \n" - - "sw $s4, 0(%[dst]) \n" - "sw $t0, 4(%[dst]) \n" - "sw $s6, 0($s0) \n" - "sw $t8, 4($s0) \n" - "sw $s5, 0($s1) \n" - "sw $t1, 4($s1) \n" - "sw $s7, 0($s2) \n" - "sw $t9, 4($s2) \n" - - "addiu $AT, -1 \n" - "addiu %[src], 4 \n" - - "bnez $AT, 1b \n" - " addu %[dst], $s2, %[dst_stride] \n" - "b 2f \n" -//dst + dst_stride unaligned - "11: \n" - "lw $t0, 0(%[src]) \n" - "lwx $t1, %[src_stride](%[src]) \n" - "lwx $t8, $t2(%[src]) \n" - "lwx $t9, $t3(%[src]) \n" - -// t0 = | 30 | 20 | 10 | 00 | -// t1 = | 31 | 21 | 11 | 01 | -// t8 = | 32 | 22 | 12 | 02 | -// t9 = | 33 | 23 | 13 | 03 | - - "precr.qb.ph $s0, $t1, $t0 \n" - "precr.qb.ph $s1, $t9, $t8 \n" - "precrq.qb.ph $s2, $t1, $t0 \n" - "precrq.qb.ph $s3, $t9, $t8 \n" - - // s0 = | 21 | 01 | 20 | 00 | - // s1 = | 23 | 03 | 22 | 02 | - // s2 = | 31 | 11 | 30 | 10 | - // s3 = | 33 | 13 | 32 | 12 | - - "precr.qb.ph $s4, $s1, $s0 \n" - "precrq.qb.ph $s5, $s1, $s0 \n" - "precr.qb.ph $s6, $s3, $s2 \n" - "precrq.qb.ph $s7, $s3, $s2 \n" - - // s4 = | 03 | 02 | 01 | 00 | - // s5 = | 23 | 22 | 21 | 20 | - // s6 = | 13 | 12 | 11 | 10 | - // s7 = | 33 | 32 | 31 | 30 | - - "lwx $t0, $t4(%[src]) \n" - "lwx $t1, $t5(%[src]) \n" - "lwx $t8, $t6(%[src]) \n" - "lwx $t9, $t7(%[src]) \n" - -// t0 = | 34 | 24 | 14 | 04 | -// t1 = | 35 | 25 | 15 | 05 | -// t8 = | 36 | 26 | 16 | 06 | -// t9 = | 37 | 27 | 17 | 07 | - - "precr.qb.ph $s0, $t1, $t0 \n" - "precr.qb.ph $s1, $t9, $t8 \n" - "precrq.qb.ph $s2, $t1, $t0 \n" - "precrq.qb.ph $s3, $t9, $t8 \n" - - // s0 = | 25 | 05 | 24 | 04 | - // s1 = | 27 | 07 | 26 | 06 | - // s2 = | 35 | 15 | 34 | 14 | - // s3 = | 37 | 17 | 36 | 16 | - - "precr.qb.ph $t0, $s1, $s0 \n" - "precrq.qb.ph $t1, $s1, $s0 \n" - "precr.qb.ph $t8, $s3, $s2 \n" - "precrq.qb.ph $t9, $s3, $s2 \n" - - // t0 = | 07 | 06 | 05 | 04 | - // t1 = | 27 | 26 | 25 | 24 | - // t8 = | 17 | 16 | 15 | 14 | - // t9 = | 37 | 36 | 35 | 34 | - - "addu $s0, %[dst], %[dst_stride] \n" - "addu $s1, $s0, %[dst_stride] \n" - "addu $s2, $s1, %[dst_stride] \n" - - "swr $s4, 0(%[dst]) \n" - "swl $s4, 3(%[dst]) \n" - "swr $t0, 4(%[dst]) \n" - "swl $t0, 7(%[dst]) \n" - "swr $s6, 0($s0) \n" - "swl $s6, 3($s0) \n" - "swr $t8, 4($s0) \n" - "swl $t8, 7($s0) \n" - "swr $s5, 0($s1) \n" - "swl $s5, 3($s1) \n" - "swr $t1, 4($s1) \n" - "swl $t1, 7($s1) \n" - "swr $s7, 0($s2) \n" - "swl $s7, 3($s2) \n" - "swr $t9, 4($s2) \n" - "swl $t9, 7($s2) \n" - - "addiu $AT, -1 \n" - "addiu %[src], 4 \n" - - "bnez $AT, 11b \n" - " addu %[dst], $s2, %[dst_stride] \n" - "2: \n" - ".set pop \n" - ".set at \n" - :[src] "+r" (src), - [dst] "+r" (dst), - [width] "+r" (width) - :[src_stride] "r" (src_stride), - [dst_stride] "r" (dst_stride) - : "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", - "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7" - ); -} - -void TransposeUVWx8_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - "beqz %[width], 2f \n" - " sll $t2, %[src_stride], 0x1 \n" // src_stride x 2 - "sll $t4, %[src_stride], 0x2 \n" // src_stride x 4 - "sll $t9, %[src_stride], 0x3 \n" // src_stride x 8 - "addu $t3, $t2, %[src_stride] \n" - "addu $t5, $t4, %[src_stride] \n" - "addu $t6, $t2, $t4 \n" - "subu $t7, $t9, %[src_stride] \n" - "srl $t1, %[width], 1 \n" - -// check word aligment for dst_a, dst_b, dst_stride_a and dst_stride_b - "andi $t0, %[dst_a], 0x3 \n" - "andi $t8, %[dst_b], 0x3 \n" - "or $t0, $t0, $t8 \n" - "andi $t8, %[dst_stride_a], 0x3 \n" - "andi $s5, %[dst_stride_b], 0x3 \n" - "or $t8, $t8, $s5 \n" - "or $t0, $t0, $t8 \n" - "bnez $t0, 11f \n" - " nop \n" -// dst + dst_stride word aligned (both, a & b dst addresses) - "1: \n" - "lw $t0, 0(%[src]) \n" // |B0|A0|b0|a0| - "lwx $t8, %[src_stride](%[src]) \n" // |B1|A1|b1|a1| - "addu $s5, %[dst_a], %[dst_stride_a] \n" - "lwx $t9, $t2(%[src]) \n" // |B2|A2|b2|a2| - "lwx $s0, $t3(%[src]) \n" // |B3|A3|b3|a3| - "addu $s6, %[dst_b], %[dst_stride_b] \n" - - "precrq.ph.w $s1, $t8, $t0 \n" // |B1|A1|B0|A0| - "precrq.ph.w $s2, $s0, $t9 \n" // |B3|A3|B2|A2| - "precr.qb.ph $s3, $s2, $s1 \n" // |A3|A2|A1|A0| - "precrq.qb.ph $s4, $s2, $s1 \n" // |B3|B2|B1|B0| - - "sll $t0, $t0, 16 \n" - "packrl.ph $s1, $t8, $t0 \n" // |b1|a1|b0|a0| - "sll $t9, $t9, 16 \n" - "packrl.ph $s2, $s0, $t9 \n" // |b3|a3|b2|a2| - - "sw $s3, 0($s5) \n" - "sw $s4, 0($s6) \n" - - "precr.qb.ph $s3, $s2, $s1 \n" // |a3|a2|a1|a0| - "precrq.qb.ph $s4, $s2, $s1 \n" // |b3|b2|b1|b0| - - "lwx $t0, $t4(%[src]) \n" // |B4|A4|b4|a4| - "lwx $t8, $t5(%[src]) \n" // |B5|A5|b5|a5| - "lwx $t9, $t6(%[src]) \n" // |B6|A6|b6|a6| - "lwx $s0, $t7(%[src]) \n" // |B7|A7|b7|a7| - "sw $s3, 0(%[dst_a]) \n" - "sw $s4, 0(%[dst_b]) \n" - - "precrq.ph.w $s1, $t8, $t0 \n" // |B5|A5|B4|A4| - "precrq.ph.w $s2, $s0, $t9 \n" // |B6|A6|B7|A7| - "precr.qb.ph $s3, $s2, $s1 \n" // |A7|A6|A5|A4| - "precrq.qb.ph $s4, $s2, $s1 \n" // |B7|B6|B5|B4| - - "sll $t0, $t0, 16 \n" - "packrl.ph $s1, $t8, $t0 \n" // |b5|a5|b4|a4| - "sll $t9, $t9, 16 \n" - "packrl.ph $s2, $s0, $t9 \n" // |b7|a7|b6|a6| - "sw $s3, 4($s5) \n" - "sw $s4, 4($s6) \n" - - "precr.qb.ph $s3, $s2, $s1 \n" // |a7|a6|a5|a4| - "precrq.qb.ph $s4, $s2, $s1 \n" // |b7|b6|b5|b4| - - "addiu %[src], 4 \n" - "addiu $t1, -1 \n" - "sll $t0, %[dst_stride_a], 1 \n" - "sll $t8, %[dst_stride_b], 1 \n" - "sw $s3, 4(%[dst_a]) \n" - "sw $s4, 4(%[dst_b]) \n" - "addu %[dst_a], %[dst_a], $t0 \n" - "bnez $t1, 1b \n" - " addu %[dst_b], %[dst_b], $t8 \n" - "b 2f \n" - " nop \n" - -// dst_a or dst_b or dst_stride_a or dst_stride_b not word aligned - "11: \n" - "lw $t0, 0(%[src]) \n" // |B0|A0|b0|a0| - "lwx $t8, %[src_stride](%[src]) \n" // |B1|A1|b1|a1| - "addu $s5, %[dst_a], %[dst_stride_a] \n" - "lwx $t9, $t2(%[src]) \n" // |B2|A2|b2|a2| - "lwx $s0, $t3(%[src]) \n" // |B3|A3|b3|a3| - "addu $s6, %[dst_b], %[dst_stride_b] \n" - - "precrq.ph.w $s1, $t8, $t0 \n" // |B1|A1|B0|A0| - "precrq.ph.w $s2, $s0, $t9 \n" // |B3|A3|B2|A2| - "precr.qb.ph $s3, $s2, $s1 \n" // |A3|A2|A1|A0| - "precrq.qb.ph $s4, $s2, $s1 \n" // |B3|B2|B1|B0| - - "sll $t0, $t0, 16 \n" - "packrl.ph $s1, $t8, $t0 \n" // |b1|a1|b0|a0| - "sll $t9, $t9, 16 \n" - "packrl.ph $s2, $s0, $t9 \n" // |b3|a3|b2|a2| - - "swr $s3, 0($s5) \n" - "swl $s3, 3($s5) \n" - "swr $s4, 0($s6) \n" - "swl $s4, 3($s6) \n" - - "precr.qb.ph $s3, $s2, $s1 \n" // |a3|a2|a1|a0| - "precrq.qb.ph $s4, $s2, $s1 \n" // |b3|b2|b1|b0| - - "lwx $t0, $t4(%[src]) \n" // |B4|A4|b4|a4| - "lwx $t8, $t5(%[src]) \n" // |B5|A5|b5|a5| - "lwx $t9, $t6(%[src]) \n" // |B6|A6|b6|a6| - "lwx $s0, $t7(%[src]) \n" // |B7|A7|b7|a7| - "swr $s3, 0(%[dst_a]) \n" - "swl $s3, 3(%[dst_a]) \n" - "swr $s4, 0(%[dst_b]) \n" - "swl $s4, 3(%[dst_b]) \n" - - "precrq.ph.w $s1, $t8, $t0 \n" // |B5|A5|B4|A4| - "precrq.ph.w $s2, $s0, $t9 \n" // |B6|A6|B7|A7| - "precr.qb.ph $s3, $s2, $s1 \n" // |A7|A6|A5|A4| - "precrq.qb.ph $s4, $s2, $s1 \n" // |B7|B6|B5|B4| - - "sll $t0, $t0, 16 \n" - "packrl.ph $s1, $t8, $t0 \n" // |b5|a5|b4|a4| - "sll $t9, $t9, 16 \n" - "packrl.ph $s2, $s0, $t9 \n" // |b7|a7|b6|a6| - - "swr $s3, 4($s5) \n" - "swl $s3, 7($s5) \n" - "swr $s4, 4($s6) \n" - "swl $s4, 7($s6) \n" - - "precr.qb.ph $s3, $s2, $s1 \n" // |a7|a6|a5|a4| - "precrq.qb.ph $s4, $s2, $s1 \n" // |b7|b6|b5|b4| - - "addiu %[src], 4 \n" - "addiu $t1, -1 \n" - "sll $t0, %[dst_stride_a], 1 \n" - "sll $t8, %[dst_stride_b], 1 \n" - "swr $s3, 4(%[dst_a]) \n" - "swl $s3, 7(%[dst_a]) \n" - "swr $s4, 4(%[dst_b]) \n" - "swl $s4, 7(%[dst_b]) \n" - "addu %[dst_a], %[dst_a], $t0 \n" - "bnez $t1, 11b \n" - " addu %[dst_b], %[dst_b], $t8 \n" - - "2: \n" - ".set pop \n" - : [src] "+r" (src), - [dst_a] "+r" (dst_a), - [dst_b] "+r" (dst_b), - [width] "+r" (width), - [src_stride] "+r" (src_stride) - : [dst_stride_a] "r" (dst_stride_a), - [dst_stride_b] "r" (dst_stride_b) - : "t0", "t1", "t2", "t3", "t4", "t5", - "t6", "t7", "t8", "t9", - "s0", "s1", "s2", "s3", - "s4", "s5", "s6" - ); -} - -#endif // defined(__mips_dsp) && (__mips_dsp_rev >= 2) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate_neon.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate_neon.cc deleted file mode 100644 index 1c22b47..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate_neon.cc +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" -#include "libyuv/rotate_row.h" - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if !defined(LIBYUV_DISABLE_NEON) && defined(__ARM_NEON__) && \ - !defined(__aarch64__) - -static uvec8 kVTbl4x4Transpose = - { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; - -void TransposeWx8_NEON(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width) { - const uint8* src_temp; - asm volatile ( - // loops are on blocks of 8. loop will stop when - // counter gets to or below 0. starting the counter - // at w-8 allow for this - "sub %5, #8 \n" - - // handle 8x8 blocks. this should be the majority of the plane - "1: \n" - "mov %0, %1 \n" - - MEMACCESS(0) - "vld1.8 {d0}, [%0], %2 \n" - MEMACCESS(0) - "vld1.8 {d1}, [%0], %2 \n" - MEMACCESS(0) - "vld1.8 {d2}, [%0], %2 \n" - MEMACCESS(0) - "vld1.8 {d3}, [%0], %2 \n" - MEMACCESS(0) - "vld1.8 {d4}, [%0], %2 \n" - MEMACCESS(0) - "vld1.8 {d5}, [%0], %2 \n" - MEMACCESS(0) - "vld1.8 {d6}, [%0], %2 \n" - MEMACCESS(0) - "vld1.8 {d7}, [%0] \n" - - "vtrn.8 d1, d0 \n" - "vtrn.8 d3, d2 \n" - "vtrn.8 d5, d4 \n" - "vtrn.8 d7, d6 \n" - - "vtrn.16 d1, d3 \n" - "vtrn.16 d0, d2 \n" - "vtrn.16 d5, d7 \n" - "vtrn.16 d4, d6 \n" - - "vtrn.32 d1, d5 \n" - "vtrn.32 d0, d4 \n" - "vtrn.32 d3, d7 \n" - "vtrn.32 d2, d6 \n" - - "vrev16.8 q0, q0 \n" - "vrev16.8 q1, q1 \n" - "vrev16.8 q2, q2 \n" - "vrev16.8 q3, q3 \n" - - "mov %0, %3 \n" - - MEMACCESS(0) - "vst1.8 {d1}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d0}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d3}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d2}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d5}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d4}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d7}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d6}, [%0] \n" - - "add %1, #8 \n" // src += 8 - "add %3, %3, %4, lsl #3 \n" // dst += 8 * dst_stride - "subs %5, #8 \n" // w -= 8 - "bge 1b \n" - - // add 8 back to counter. if the result is 0 there are - // no residuals. - "adds %5, #8 \n" - "beq 4f \n" - - // some residual, so between 1 and 7 lines left to transpose - "cmp %5, #2 \n" - "blt 3f \n" - - "cmp %5, #4 \n" - "blt 2f \n" - - // 4x8 block - "mov %0, %1 \n" - MEMACCESS(0) - "vld1.32 {d0[0]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.32 {d0[1]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.32 {d1[0]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.32 {d1[1]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.32 {d2[0]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.32 {d2[1]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.32 {d3[0]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.32 {d3[1]}, [%0] \n" - - "mov %0, %3 \n" - - MEMACCESS(6) - "vld1.8 {q3}, [%6] \n" - - "vtbl.8 d4, {d0, d1}, d6 \n" - "vtbl.8 d5, {d0, d1}, d7 \n" - "vtbl.8 d0, {d2, d3}, d6 \n" - "vtbl.8 d1, {d2, d3}, d7 \n" - - // TODO(frkoenig): Rework shuffle above to - // write out with 4 instead of 8 writes. - MEMACCESS(0) - "vst1.32 {d4[0]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d4[1]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d5[0]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d5[1]}, [%0] \n" - - "add %0, %3, #4 \n" - MEMACCESS(0) - "vst1.32 {d0[0]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d0[1]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d1[0]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d1[1]}, [%0] \n" - - "add %1, #4 \n" // src += 4 - "add %3, %3, %4, lsl #2 \n" // dst += 4 * dst_stride - "subs %5, #4 \n" // w -= 4 - "beq 4f \n" - - // some residual, check to see if it includes a 2x8 block, - // or less - "cmp %5, #2 \n" - "blt 3f \n" - - // 2x8 block - "2: \n" - "mov %0, %1 \n" - MEMACCESS(0) - "vld1.16 {d0[0]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.16 {d1[0]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.16 {d0[1]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.16 {d1[1]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.16 {d0[2]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.16 {d1[2]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.16 {d0[3]}, [%0], %2 \n" - MEMACCESS(0) - "vld1.16 {d1[3]}, [%0] \n" - - "vtrn.8 d0, d1 \n" - - "mov %0, %3 \n" - - MEMACCESS(0) - "vst1.64 {d0}, [%0], %4 \n" - MEMACCESS(0) - "vst1.64 {d1}, [%0] \n" - - "add %1, #2 \n" // src += 2 - "add %3, %3, %4, lsl #1 \n" // dst += 2 * dst_stride - "subs %5, #2 \n" // w -= 2 - "beq 4f \n" - - // 1x8 block - "3: \n" - MEMACCESS(1) - "vld1.8 {d0[0]}, [%1], %2 \n" - MEMACCESS(1) - "vld1.8 {d0[1]}, [%1], %2 \n" - MEMACCESS(1) - "vld1.8 {d0[2]}, [%1], %2 \n" - MEMACCESS(1) - "vld1.8 {d0[3]}, [%1], %2 \n" - MEMACCESS(1) - "vld1.8 {d0[4]}, [%1], %2 \n" - MEMACCESS(1) - "vld1.8 {d0[5]}, [%1], %2 \n" - MEMACCESS(1) - "vld1.8 {d0[6]}, [%1], %2 \n" - MEMACCESS(1) - "vld1.8 {d0[7]}, [%1] \n" - - MEMACCESS(3) - "vst1.64 {d0}, [%3] \n" - - "4: \n" - - : "=&r"(src_temp), // %0 - "+r"(src), // %1 - "+r"(src_stride), // %2 - "+r"(dst), // %3 - "+r"(dst_stride), // %4 - "+r"(width) // %5 - : "r"(&kVTbl4x4Transpose) // %6 - : "memory", "cc", "q0", "q1", "q2", "q3" - ); -} - -static uvec8 kVTbl4x4TransposeDi = - { 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; - -void TransposeUVWx8_NEON(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width) { - const uint8* src_temp; - asm volatile ( - // loops are on blocks of 8. loop will stop when - // counter gets to or below 0. starting the counter - // at w-8 allow for this - "sub %7, #8 \n" - - // handle 8x8 blocks. this should be the majority of the plane - "1: \n" - "mov %0, %1 \n" - - MEMACCESS(0) - "vld2.8 {d0, d1}, [%0], %2 \n" - MEMACCESS(0) - "vld2.8 {d2, d3}, [%0], %2 \n" - MEMACCESS(0) - "vld2.8 {d4, d5}, [%0], %2 \n" - MEMACCESS(0) - "vld2.8 {d6, d7}, [%0], %2 \n" - MEMACCESS(0) - "vld2.8 {d16, d17}, [%0], %2 \n" - MEMACCESS(0) - "vld2.8 {d18, d19}, [%0], %2 \n" - MEMACCESS(0) - "vld2.8 {d20, d21}, [%0], %2 \n" - MEMACCESS(0) - "vld2.8 {d22, d23}, [%0] \n" - - "vtrn.8 q1, q0 \n" - "vtrn.8 q3, q2 \n" - "vtrn.8 q9, q8 \n" - "vtrn.8 q11, q10 \n" - - "vtrn.16 q1, q3 \n" - "vtrn.16 q0, q2 \n" - "vtrn.16 q9, q11 \n" - "vtrn.16 q8, q10 \n" - - "vtrn.32 q1, q9 \n" - "vtrn.32 q0, q8 \n" - "vtrn.32 q3, q11 \n" - "vtrn.32 q2, q10 \n" - - "vrev16.8 q0, q0 \n" - "vrev16.8 q1, q1 \n" - "vrev16.8 q2, q2 \n" - "vrev16.8 q3, q3 \n" - "vrev16.8 q8, q8 \n" - "vrev16.8 q9, q9 \n" - "vrev16.8 q10, q10 \n" - "vrev16.8 q11, q11 \n" - - "mov %0, %3 \n" - - MEMACCESS(0) - "vst1.8 {d2}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d0}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d6}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d4}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d18}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d16}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d22}, [%0], %4 \n" - MEMACCESS(0) - "vst1.8 {d20}, [%0] \n" - - "mov %0, %5 \n" - - MEMACCESS(0) - "vst1.8 {d3}, [%0], %6 \n" - MEMACCESS(0) - "vst1.8 {d1}, [%0], %6 \n" - MEMACCESS(0) - "vst1.8 {d7}, [%0], %6 \n" - MEMACCESS(0) - "vst1.8 {d5}, [%0], %6 \n" - MEMACCESS(0) - "vst1.8 {d19}, [%0], %6 \n" - MEMACCESS(0) - "vst1.8 {d17}, [%0], %6 \n" - MEMACCESS(0) - "vst1.8 {d23}, [%0], %6 \n" - MEMACCESS(0) - "vst1.8 {d21}, [%0] \n" - - "add %1, #8*2 \n" // src += 8*2 - "add %3, %3, %4, lsl #3 \n" // dst_a += 8 * dst_stride_a - "add %5, %5, %6, lsl #3 \n" // dst_b += 8 * dst_stride_b - "subs %7, #8 \n" // w -= 8 - "bge 1b \n" - - // add 8 back to counter. if the result is 0 there are - // no residuals. - "adds %7, #8 \n" - "beq 4f \n" - - // some residual, so between 1 and 7 lines left to transpose - "cmp %7, #2 \n" - "blt 3f \n" - - "cmp %7, #4 \n" - "blt 2f \n" - - // TODO(frkoenig): Clean this up - // 4x8 block - "mov %0, %1 \n" - MEMACCESS(0) - "vld1.64 {d0}, [%0], %2 \n" - MEMACCESS(0) - "vld1.64 {d1}, [%0], %2 \n" - MEMACCESS(0) - "vld1.64 {d2}, [%0], %2 \n" - MEMACCESS(0) - "vld1.64 {d3}, [%0], %2 \n" - MEMACCESS(0) - "vld1.64 {d4}, [%0], %2 \n" - MEMACCESS(0) - "vld1.64 {d5}, [%0], %2 \n" - MEMACCESS(0) - "vld1.64 {d6}, [%0], %2 \n" - MEMACCESS(0) - "vld1.64 {d7}, [%0] \n" - - MEMACCESS(8) - "vld1.8 {q15}, [%8] \n" - - "vtrn.8 q0, q1 \n" - "vtrn.8 q2, q3 \n" - - "vtbl.8 d16, {d0, d1}, d30 \n" - "vtbl.8 d17, {d0, d1}, d31 \n" - "vtbl.8 d18, {d2, d3}, d30 \n" - "vtbl.8 d19, {d2, d3}, d31 \n" - "vtbl.8 d20, {d4, d5}, d30 \n" - "vtbl.8 d21, {d4, d5}, d31 \n" - "vtbl.8 d22, {d6, d7}, d30 \n" - "vtbl.8 d23, {d6, d7}, d31 \n" - - "mov %0, %3 \n" - - MEMACCESS(0) - "vst1.32 {d16[0]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d16[1]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d17[0]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d17[1]}, [%0], %4 \n" - - "add %0, %3, #4 \n" - MEMACCESS(0) - "vst1.32 {d20[0]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d20[1]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d21[0]}, [%0], %4 \n" - MEMACCESS(0) - "vst1.32 {d21[1]}, [%0] \n" - - "mov %0, %5 \n" - - MEMACCESS(0) - "vst1.32 {d18[0]}, [%0], %6 \n" - MEMACCESS(0) - "vst1.32 {d18[1]}, [%0], %6 \n" - MEMACCESS(0) - "vst1.32 {d19[0]}, [%0], %6 \n" - MEMACCESS(0) - "vst1.32 {d19[1]}, [%0], %6 \n" - - "add %0, %5, #4 \n" - MEMACCESS(0) - "vst1.32 {d22[0]}, [%0], %6 \n" - MEMACCESS(0) - "vst1.32 {d22[1]}, [%0], %6 \n" - MEMACCESS(0) - "vst1.32 {d23[0]}, [%0], %6 \n" - MEMACCESS(0) - "vst1.32 {d23[1]}, [%0] \n" - - "add %1, #4*2 \n" // src += 4 * 2 - "add %3, %3, %4, lsl #2 \n" // dst_a += 4 * dst_stride_a - "add %5, %5, %6, lsl #2 \n" // dst_b += 4 * dst_stride_b - "subs %7, #4 \n" // w -= 4 - "beq 4f \n" - - // some residual, check to see if it includes a 2x8 block, - // or less - "cmp %7, #2 \n" - "blt 3f \n" - - // 2x8 block - "2: \n" - "mov %0, %1 \n" - MEMACCESS(0) - "vld2.16 {d0[0], d2[0]}, [%0], %2 \n" - MEMACCESS(0) - "vld2.16 {d1[0], d3[0]}, [%0], %2 \n" - MEMACCESS(0) - "vld2.16 {d0[1], d2[1]}, [%0], %2 \n" - MEMACCESS(0) - "vld2.16 {d1[1], d3[1]}, [%0], %2 \n" - MEMACCESS(0) - "vld2.16 {d0[2], d2[2]}, [%0], %2 \n" - MEMACCESS(0) - "vld2.16 {d1[2], d3[2]}, [%0], %2 \n" - MEMACCESS(0) - "vld2.16 {d0[3], d2[3]}, [%0], %2 \n" - MEMACCESS(0) - "vld2.16 {d1[3], d3[3]}, [%0] \n" - - "vtrn.8 d0, d1 \n" - "vtrn.8 d2, d3 \n" - - "mov %0, %3 \n" - - MEMACCESS(0) - "vst1.64 {d0}, [%0], %4 \n" - MEMACCESS(0) - "vst1.64 {d2}, [%0] \n" - - "mov %0, %5 \n" - - MEMACCESS(0) - "vst1.64 {d1}, [%0], %6 \n" - MEMACCESS(0) - "vst1.64 {d3}, [%0] \n" - - "add %1, #2*2 \n" // src += 2 * 2 - "add %3, %3, %4, lsl #1 \n" // dst_a += 2 * dst_stride_a - "add %5, %5, %6, lsl #1 \n" // dst_b += 2 * dst_stride_b - "subs %7, #2 \n" // w -= 2 - "beq 4f \n" - - // 1x8 block - "3: \n" - MEMACCESS(1) - "vld2.8 {d0[0], d1[0]}, [%1], %2 \n" - MEMACCESS(1) - "vld2.8 {d0[1], d1[1]}, [%1], %2 \n" - MEMACCESS(1) - "vld2.8 {d0[2], d1[2]}, [%1], %2 \n" - MEMACCESS(1) - "vld2.8 {d0[3], d1[3]}, [%1], %2 \n" - MEMACCESS(1) - "vld2.8 {d0[4], d1[4]}, [%1], %2 \n" - MEMACCESS(1) - "vld2.8 {d0[5], d1[5]}, [%1], %2 \n" - MEMACCESS(1) - "vld2.8 {d0[6], d1[6]}, [%1], %2 \n" - MEMACCESS(1) - "vld2.8 {d0[7], d1[7]}, [%1] \n" - - MEMACCESS(3) - "vst1.64 {d0}, [%3] \n" - MEMACCESS(5) - "vst1.64 {d1}, [%5] \n" - - "4: \n" - - : "=&r"(src_temp), // %0 - "+r"(src), // %1 - "+r"(src_stride), // %2 - "+r"(dst_a), // %3 - "+r"(dst_stride_a), // %4 - "+r"(dst_b), // %5 - "+r"(dst_stride_b), // %6 - "+r"(width) // %7 - : "r"(&kVTbl4x4TransposeDi) // %8 - : "memory", "cc", - "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11" - ); -} -#endif // defined(__ARM_NEON__) && !defined(__aarch64__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate_neon64.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate_neon64.cc deleted file mode 100644 index 1ab448f..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate_neon64.cc +++ /dev/null @@ -1,543 +0,0 @@ -/* - * Copyright 2014 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" -#include "libyuv/rotate_row.h" - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC Neon armv8 64 bit. -#if !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) - -static uvec8 kVTbl4x4Transpose = - { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; - -void TransposeWx8_NEON(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width) { - const uint8* src_temp; - int64 width64 = (int64) width; // Work around clang 3.4 warning. - asm volatile ( - // loops are on blocks of 8. loop will stop when - // counter gets to or below 0. starting the counter - // at w-8 allow for this - "sub %3, %3, #8 \n" - - // handle 8x8 blocks. this should be the majority of the plane - "1: \n" - "mov %0, %1 \n" - - MEMACCESS(0) - "ld1 {v0.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v2.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v3.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v4.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v5.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v6.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v7.8b}, [%0] \n" - - "trn2 v16.8b, v0.8b, v1.8b \n" - "trn1 v17.8b, v0.8b, v1.8b \n" - "trn2 v18.8b, v2.8b, v3.8b \n" - "trn1 v19.8b, v2.8b, v3.8b \n" - "trn2 v20.8b, v4.8b, v5.8b \n" - "trn1 v21.8b, v4.8b, v5.8b \n" - "trn2 v22.8b, v6.8b, v7.8b \n" - "trn1 v23.8b, v6.8b, v7.8b \n" - - "trn2 v3.4h, v17.4h, v19.4h \n" - "trn1 v1.4h, v17.4h, v19.4h \n" - "trn2 v2.4h, v16.4h, v18.4h \n" - "trn1 v0.4h, v16.4h, v18.4h \n" - "trn2 v7.4h, v21.4h, v23.4h \n" - "trn1 v5.4h, v21.4h, v23.4h \n" - "trn2 v6.4h, v20.4h, v22.4h \n" - "trn1 v4.4h, v20.4h, v22.4h \n" - - "trn2 v21.2s, v1.2s, v5.2s \n" - "trn1 v17.2s, v1.2s, v5.2s \n" - "trn2 v20.2s, v0.2s, v4.2s \n" - "trn1 v16.2s, v0.2s, v4.2s \n" - "trn2 v23.2s, v3.2s, v7.2s \n" - "trn1 v19.2s, v3.2s, v7.2s \n" - "trn2 v22.2s, v2.2s, v6.2s \n" - "trn1 v18.2s, v2.2s, v6.2s \n" - - "mov %0, %2 \n" - - MEMACCESS(0) - "st1 {v17.8b}, [%0], %6 \n" - MEMACCESS(0) - "st1 {v16.8b}, [%0], %6 \n" - MEMACCESS(0) - "st1 {v19.8b}, [%0], %6 \n" - MEMACCESS(0) - "st1 {v18.8b}, [%0], %6 \n" - MEMACCESS(0) - "st1 {v21.8b}, [%0], %6 \n" - MEMACCESS(0) - "st1 {v20.8b}, [%0], %6 \n" - MEMACCESS(0) - "st1 {v23.8b}, [%0], %6 \n" - MEMACCESS(0) - "st1 {v22.8b}, [%0] \n" - - "add %1, %1, #8 \n" // src += 8 - "add %2, %2, %6, lsl #3 \n" // dst += 8 * dst_stride - "subs %3, %3, #8 \n" // w -= 8 - "b.ge 1b \n" - - // add 8 back to counter. if the result is 0 there are - // no residuals. - "adds %3, %3, #8 \n" - "b.eq 4f \n" - - // some residual, so between 1 and 7 lines left to transpose - "cmp %3, #2 \n" - "b.lt 3f \n" - - "cmp %3, #4 \n" - "b.lt 2f \n" - - // 4x8 block - "mov %0, %1 \n" - MEMACCESS(0) - "ld1 {v0.s}[0], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v0.s}[1], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v0.s}[2], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v0.s}[3], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.s}[0], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.s}[1], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.s}[2], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.s}[3], [%0] \n" - - "mov %0, %2 \n" - - MEMACCESS(4) - "ld1 {v2.16b}, [%4] \n" - - "tbl v3.16b, {v0.16b}, v2.16b \n" - "tbl v0.16b, {v1.16b}, v2.16b \n" - - // TODO(frkoenig): Rework shuffle above to - // write out with 4 instead of 8 writes. - MEMACCESS(0) - "st1 {v3.s}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v3.s}[1], [%0], %6 \n" - MEMACCESS(0) - "st1 {v3.s}[2], [%0], %6 \n" - MEMACCESS(0) - "st1 {v3.s}[3], [%0] \n" - - "add %0, %2, #4 \n" - MEMACCESS(0) - "st1 {v0.s}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v0.s}[1], [%0], %6 \n" - MEMACCESS(0) - "st1 {v0.s}[2], [%0], %6 \n" - MEMACCESS(0) - "st1 {v0.s}[3], [%0] \n" - - "add %1, %1, #4 \n" // src += 4 - "add %2, %2, %6, lsl #2 \n" // dst += 4 * dst_stride - "subs %3, %3, #4 \n" // w -= 4 - "b.eq 4f \n" - - // some residual, check to see if it includes a 2x8 block, - // or less - "cmp %3, #2 \n" - "b.lt 3f \n" - - // 2x8 block - "2: \n" - "mov %0, %1 \n" - MEMACCESS(0) - "ld1 {v0.h}[0], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.h}[0], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v0.h}[1], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.h}[1], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v0.h}[2], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.h}[2], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v0.h}[3], [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.h}[3], [%0] \n" - - "trn2 v2.8b, v0.8b, v1.8b \n" - "trn1 v3.8b, v0.8b, v1.8b \n" - - "mov %0, %2 \n" - - MEMACCESS(0) - "st1 {v3.8b}, [%0], %6 \n" - MEMACCESS(0) - "st1 {v2.8b}, [%0] \n" - - "add %1, %1, #2 \n" // src += 2 - "add %2, %2, %6, lsl #1 \n" // dst += 2 * dst_stride - "subs %3, %3, #2 \n" // w -= 2 - "b.eq 4f \n" - - // 1x8 block - "3: \n" - MEMACCESS(1) - "ld1 {v0.b}[0], [%1], %5 \n" - MEMACCESS(1) - "ld1 {v0.b}[1], [%1], %5 \n" - MEMACCESS(1) - "ld1 {v0.b}[2], [%1], %5 \n" - MEMACCESS(1) - "ld1 {v0.b}[3], [%1], %5 \n" - MEMACCESS(1) - "ld1 {v0.b}[4], [%1], %5 \n" - MEMACCESS(1) - "ld1 {v0.b}[5], [%1], %5 \n" - MEMACCESS(1) - "ld1 {v0.b}[6], [%1], %5 \n" - MEMACCESS(1) - "ld1 {v0.b}[7], [%1] \n" - - MEMACCESS(2) - "st1 {v0.8b}, [%2] \n" - - "4: \n" - - : "=&r"(src_temp), // %0 - "+r"(src), // %1 - "+r"(dst), // %2 - "+r"(width64) // %3 - : "r"(&kVTbl4x4Transpose), // %4 - "r"(static_cast(src_stride)), // %5 - "r"(static_cast(dst_stride)) // %6 - : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", - "v17", "v18", "v19", "v20", "v21", "v22", "v23" - ); -} - -static uint8 kVTbl4x4TransposeDi[32] = - { 0, 16, 32, 48, 2, 18, 34, 50, 4, 20, 36, 52, 6, 22, 38, 54, - 1, 17, 33, 49, 3, 19, 35, 51, 5, 21, 37, 53, 7, 23, 39, 55}; - -void TransposeUVWx8_NEON(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width) { - const uint8* src_temp; - int64 width64 = (int64) width; // Work around clang 3.4 warning. - asm volatile ( - // loops are on blocks of 8. loop will stop when - // counter gets to or below 0. starting the counter - // at w-8 allow for this - "sub %4, %4, #8 \n" - - // handle 8x8 blocks. this should be the majority of the plane - "1: \n" - "mov %0, %1 \n" - - MEMACCESS(0) - "ld1 {v0.16b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.16b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v2.16b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v3.16b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v4.16b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v5.16b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v6.16b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v7.16b}, [%0] \n" - - "trn1 v16.16b, v0.16b, v1.16b \n" - "trn2 v17.16b, v0.16b, v1.16b \n" - "trn1 v18.16b, v2.16b, v3.16b \n" - "trn2 v19.16b, v2.16b, v3.16b \n" - "trn1 v20.16b, v4.16b, v5.16b \n" - "trn2 v21.16b, v4.16b, v5.16b \n" - "trn1 v22.16b, v6.16b, v7.16b \n" - "trn2 v23.16b, v6.16b, v7.16b \n" - - "trn1 v0.8h, v16.8h, v18.8h \n" - "trn2 v1.8h, v16.8h, v18.8h \n" - "trn1 v2.8h, v20.8h, v22.8h \n" - "trn2 v3.8h, v20.8h, v22.8h \n" - "trn1 v4.8h, v17.8h, v19.8h \n" - "trn2 v5.8h, v17.8h, v19.8h \n" - "trn1 v6.8h, v21.8h, v23.8h \n" - "trn2 v7.8h, v21.8h, v23.8h \n" - - "trn1 v16.4s, v0.4s, v2.4s \n" - "trn2 v17.4s, v0.4s, v2.4s \n" - "trn1 v18.4s, v1.4s, v3.4s \n" - "trn2 v19.4s, v1.4s, v3.4s \n" - "trn1 v20.4s, v4.4s, v6.4s \n" - "trn2 v21.4s, v4.4s, v6.4s \n" - "trn1 v22.4s, v5.4s, v7.4s \n" - "trn2 v23.4s, v5.4s, v7.4s \n" - - "mov %0, %2 \n" - - MEMACCESS(0) - "st1 {v16.d}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v18.d}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v17.d}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v19.d}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v16.d}[1], [%0], %6 \n" - MEMACCESS(0) - "st1 {v18.d}[1], [%0], %6 \n" - MEMACCESS(0) - "st1 {v17.d}[1], [%0], %6 \n" - MEMACCESS(0) - "st1 {v19.d}[1], [%0] \n" - - "mov %0, %3 \n" - - MEMACCESS(0) - "st1 {v20.d}[0], [%0], %7 \n" - MEMACCESS(0) - "st1 {v22.d}[0], [%0], %7 \n" - MEMACCESS(0) - "st1 {v21.d}[0], [%0], %7 \n" - MEMACCESS(0) - "st1 {v23.d}[0], [%0], %7 \n" - MEMACCESS(0) - "st1 {v20.d}[1], [%0], %7 \n" - MEMACCESS(0) - "st1 {v22.d}[1], [%0], %7 \n" - MEMACCESS(0) - "st1 {v21.d}[1], [%0], %7 \n" - MEMACCESS(0) - "st1 {v23.d}[1], [%0] \n" - - "add %1, %1, #16 \n" // src += 8*2 - "add %2, %2, %6, lsl #3 \n" // dst_a += 8 * dst_stride_a - "add %3, %3, %7, lsl #3 \n" // dst_b += 8 * dst_stride_b - "subs %4, %4, #8 \n" // w -= 8 - "b.ge 1b \n" - - // add 8 back to counter. if the result is 0 there are - // no residuals. - "adds %4, %4, #8 \n" - "b.eq 4f \n" - - // some residual, so between 1 and 7 lines left to transpose - "cmp %4, #2 \n" - "b.lt 3f \n" - - "cmp %4, #4 \n" - "b.lt 2f \n" - - // TODO(frkoenig): Clean this up - // 4x8 block - "mov %0, %1 \n" - MEMACCESS(0) - "ld1 {v0.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v1.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v2.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v3.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v4.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v5.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v6.8b}, [%0], %5 \n" - MEMACCESS(0) - "ld1 {v7.8b}, [%0] \n" - - MEMACCESS(8) - "ld1 {v30.16b}, [%8], #16 \n" - "ld1 {v31.16b}, [%8] \n" - - "tbl v16.16b, {v0.16b, v1.16b, v2.16b, v3.16b}, v30.16b \n" - "tbl v17.16b, {v0.16b, v1.16b, v2.16b, v3.16b}, v31.16b \n" - "tbl v18.16b, {v4.16b, v5.16b, v6.16b, v7.16b}, v30.16b \n" - "tbl v19.16b, {v4.16b, v5.16b, v6.16b, v7.16b}, v31.16b \n" - - "mov %0, %2 \n" - - MEMACCESS(0) - "st1 {v16.s}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v16.s}[1], [%0], %6 \n" - MEMACCESS(0) - "st1 {v16.s}[2], [%0], %6 \n" - MEMACCESS(0) - "st1 {v16.s}[3], [%0], %6 \n" - - "add %0, %2, #4 \n" - MEMACCESS(0) - "st1 {v18.s}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v18.s}[1], [%0], %6 \n" - MEMACCESS(0) - "st1 {v18.s}[2], [%0], %6 \n" - MEMACCESS(0) - "st1 {v18.s}[3], [%0] \n" - - "mov %0, %3 \n" - - MEMACCESS(0) - "st1 {v17.s}[0], [%0], %7 \n" - MEMACCESS(0) - "st1 {v17.s}[1], [%0], %7 \n" - MEMACCESS(0) - "st1 {v17.s}[2], [%0], %7 \n" - MEMACCESS(0) - "st1 {v17.s}[3], [%0], %7 \n" - - "add %0, %3, #4 \n" - MEMACCESS(0) - "st1 {v19.s}[0], [%0], %7 \n" - MEMACCESS(0) - "st1 {v19.s}[1], [%0], %7 \n" - MEMACCESS(0) - "st1 {v19.s}[2], [%0], %7 \n" - MEMACCESS(0) - "st1 {v19.s}[3], [%0] \n" - - "add %1, %1, #8 \n" // src += 4 * 2 - "add %2, %2, %6, lsl #2 \n" // dst_a += 4 * dst_stride_a - "add %3, %3, %7, lsl #2 \n" // dst_b += 4 * dst_stride_b - "subs %4, %4, #4 \n" // w -= 4 - "b.eq 4f \n" - - // some residual, check to see if it includes a 2x8 block, - // or less - "cmp %4, #2 \n" - "b.lt 3f \n" - - // 2x8 block - "2: \n" - "mov %0, %1 \n" - MEMACCESS(0) - "ld2 {v0.h, v1.h}[0], [%0], %5 \n" - MEMACCESS(0) - "ld2 {v2.h, v3.h}[0], [%0], %5 \n" - MEMACCESS(0) - "ld2 {v0.h, v1.h}[1], [%0], %5 \n" - MEMACCESS(0) - "ld2 {v2.h, v3.h}[1], [%0], %5 \n" - MEMACCESS(0) - "ld2 {v0.h, v1.h}[2], [%0], %5 \n" - MEMACCESS(0) - "ld2 {v2.h, v3.h}[2], [%0], %5 \n" - MEMACCESS(0) - "ld2 {v0.h, v1.h}[3], [%0], %5 \n" - MEMACCESS(0) - "ld2 {v2.h, v3.h}[3], [%0] \n" - - "trn1 v4.8b, v0.8b, v2.8b \n" - "trn2 v5.8b, v0.8b, v2.8b \n" - "trn1 v6.8b, v1.8b, v3.8b \n" - "trn2 v7.8b, v1.8b, v3.8b \n" - - "mov %0, %2 \n" - - MEMACCESS(0) - "st1 {v4.d}[0], [%0], %6 \n" - MEMACCESS(0) - "st1 {v6.d}[0], [%0] \n" - - "mov %0, %3 \n" - - MEMACCESS(0) - "st1 {v5.d}[0], [%0], %7 \n" - MEMACCESS(0) - "st1 {v7.d}[0], [%0] \n" - - "add %1, %1, #4 \n" // src += 2 * 2 - "add %2, %2, %6, lsl #1 \n" // dst_a += 2 * dst_stride_a - "add %3, %3, %7, lsl #1 \n" // dst_b += 2 * dst_stride_b - "subs %4, %4, #2 \n" // w -= 2 - "b.eq 4f \n" - - // 1x8 block - "3: \n" - MEMACCESS(1) - "ld2 {v0.b, v1.b}[0], [%1], %5 \n" - MEMACCESS(1) - "ld2 {v0.b, v1.b}[1], [%1], %5 \n" - MEMACCESS(1) - "ld2 {v0.b, v1.b}[2], [%1], %5 \n" - MEMACCESS(1) - "ld2 {v0.b, v1.b}[3], [%1], %5 \n" - MEMACCESS(1) - "ld2 {v0.b, v1.b}[4], [%1], %5 \n" - MEMACCESS(1) - "ld2 {v0.b, v1.b}[5], [%1], %5 \n" - MEMACCESS(1) - "ld2 {v0.b, v1.b}[6], [%1], %5 \n" - MEMACCESS(1) - "ld2 {v0.b, v1.b}[7], [%1] \n" - - MEMACCESS(2) - "st1 {v0.d}[0], [%2] \n" - MEMACCESS(3) - "st1 {v1.d}[0], [%3] \n" - - "4: \n" - - : "=&r"(src_temp), // %0 - "+r"(src), // %1 - "+r"(dst_a), // %2 - "+r"(dst_b), // %3 - "+r"(width64) // %4 - : "r"(static_cast(src_stride)), // %5 - "r"(static_cast(dst_stride_a)), // %6 - "r"(static_cast(dst_stride_b)), // %7 - "r"(&kVTbl4x4TransposeDi) // %8 - : "memory", "cc", - "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", - "v30", "v31" - ); -} -#endif // !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/rotate_win.cc b/telegramgallery/src/main/cpp/libyuv/source/rotate_win.cc deleted file mode 100644 index 1300fc0..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/rotate_win.cc +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" -#include "libyuv/rotate_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for 32 bit Visual C x86 and clangcl -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) - -__declspec(naked) -void TransposeWx8_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width) { - __asm { - push edi - push esi - push ebp - mov eax, [esp + 12 + 4] // src - mov edi, [esp + 12 + 8] // src_stride - mov edx, [esp + 12 + 12] // dst - mov esi, [esp + 12 + 16] // dst_stride - mov ecx, [esp + 12 + 20] // width - - // Read in the data from the source pointer. - // First round of bit swap. - align 4 - convertloop: - movq xmm0, qword ptr [eax] - lea ebp, [eax + 8] - movq xmm1, qword ptr [eax + edi] - lea eax, [eax + 2 * edi] - punpcklbw xmm0, xmm1 - movq xmm2, qword ptr [eax] - movdqa xmm1, xmm0 - palignr xmm1, xmm1, 8 - movq xmm3, qword ptr [eax + edi] - lea eax, [eax + 2 * edi] - punpcklbw xmm2, xmm3 - movdqa xmm3, xmm2 - movq xmm4, qword ptr [eax] - palignr xmm3, xmm3, 8 - movq xmm5, qword ptr [eax + edi] - punpcklbw xmm4, xmm5 - lea eax, [eax + 2 * edi] - movdqa xmm5, xmm4 - movq xmm6, qword ptr [eax] - palignr xmm5, xmm5, 8 - movq xmm7, qword ptr [eax + edi] - punpcklbw xmm6, xmm7 - mov eax, ebp - movdqa xmm7, xmm6 - palignr xmm7, xmm7, 8 - // Second round of bit swap. - punpcklwd xmm0, xmm2 - punpcklwd xmm1, xmm3 - movdqa xmm2, xmm0 - movdqa xmm3, xmm1 - palignr xmm2, xmm2, 8 - palignr xmm3, xmm3, 8 - punpcklwd xmm4, xmm6 - punpcklwd xmm5, xmm7 - movdqa xmm6, xmm4 - movdqa xmm7, xmm5 - palignr xmm6, xmm6, 8 - palignr xmm7, xmm7, 8 - // Third round of bit swap. - // Write to the destination pointer. - punpckldq xmm0, xmm4 - movq qword ptr [edx], xmm0 - movdqa xmm4, xmm0 - palignr xmm4, xmm4, 8 - movq qword ptr [edx + esi], xmm4 - lea edx, [edx + 2 * esi] - punpckldq xmm2, xmm6 - movdqa xmm6, xmm2 - palignr xmm6, xmm6, 8 - movq qword ptr [edx], xmm2 - punpckldq xmm1, xmm5 - movq qword ptr [edx + esi], xmm6 - lea edx, [edx + 2 * esi] - movdqa xmm5, xmm1 - movq qword ptr [edx], xmm1 - palignr xmm5, xmm5, 8 - punpckldq xmm3, xmm7 - movq qword ptr [edx + esi], xmm5 - lea edx, [edx + 2 * esi] - movq qword ptr [edx], xmm3 - movdqa xmm7, xmm3 - palignr xmm7, xmm7, 8 - sub ecx, 8 - movq qword ptr [edx + esi], xmm7 - lea edx, [edx + 2 * esi] - jg convertloop - - pop ebp - pop esi - pop edi - ret - } -} - -__declspec(naked) -void TransposeUVWx8_SSE2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int w) { - __asm { - push ebx - push esi - push edi - push ebp - mov eax, [esp + 16 + 4] // src - mov edi, [esp + 16 + 8] // src_stride - mov edx, [esp + 16 + 12] // dst_a - mov esi, [esp + 16 + 16] // dst_stride_a - mov ebx, [esp + 16 + 20] // dst_b - mov ebp, [esp + 16 + 24] // dst_stride_b - mov ecx, esp - sub esp, 4 + 16 - and esp, ~15 - mov [esp + 16], ecx - mov ecx, [ecx + 16 + 28] // w - - align 4 - convertloop: - // Read in the data from the source pointer. - // First round of bit swap. - movdqu xmm0, [eax] - movdqu xmm1, [eax + edi] - lea eax, [eax + 2 * edi] - movdqa xmm7, xmm0 // use xmm7 as temp register. - punpcklbw xmm0, xmm1 - punpckhbw xmm7, xmm1 - movdqa xmm1, xmm7 - movdqu xmm2, [eax] - movdqu xmm3, [eax + edi] - lea eax, [eax + 2 * edi] - movdqa xmm7, xmm2 - punpcklbw xmm2, xmm3 - punpckhbw xmm7, xmm3 - movdqa xmm3, xmm7 - movdqu xmm4, [eax] - movdqu xmm5, [eax + edi] - lea eax, [eax + 2 * edi] - movdqa xmm7, xmm4 - punpcklbw xmm4, xmm5 - punpckhbw xmm7, xmm5 - movdqa xmm5, xmm7 - movdqu xmm6, [eax] - movdqu xmm7, [eax + edi] - lea eax, [eax + 2 * edi] - movdqu [esp], xmm5 // backup xmm5 - neg edi - movdqa xmm5, xmm6 // use xmm5 as temp register. - punpcklbw xmm6, xmm7 - punpckhbw xmm5, xmm7 - movdqa xmm7, xmm5 - lea eax, [eax + 8 * edi + 16] - neg edi - // Second round of bit swap. - movdqa xmm5, xmm0 - punpcklwd xmm0, xmm2 - punpckhwd xmm5, xmm2 - movdqa xmm2, xmm5 - movdqa xmm5, xmm1 - punpcklwd xmm1, xmm3 - punpckhwd xmm5, xmm3 - movdqa xmm3, xmm5 - movdqa xmm5, xmm4 - punpcklwd xmm4, xmm6 - punpckhwd xmm5, xmm6 - movdqa xmm6, xmm5 - movdqu xmm5, [esp] // restore xmm5 - movdqu [esp], xmm6 // backup xmm6 - movdqa xmm6, xmm5 // use xmm6 as temp register. - punpcklwd xmm5, xmm7 - punpckhwd xmm6, xmm7 - movdqa xmm7, xmm6 - // Third round of bit swap. - // Write to the destination pointer. - movdqa xmm6, xmm0 - punpckldq xmm0, xmm4 - punpckhdq xmm6, xmm4 - movdqa xmm4, xmm6 - movdqu xmm6, [esp] // restore xmm6 - movlpd qword ptr [edx], xmm0 - movhpd qword ptr [ebx], xmm0 - movlpd qword ptr [edx + esi], xmm4 - lea edx, [edx + 2 * esi] - movhpd qword ptr [ebx + ebp], xmm4 - lea ebx, [ebx + 2 * ebp] - movdqa xmm0, xmm2 // use xmm0 as the temp register. - punpckldq xmm2, xmm6 - movlpd qword ptr [edx], xmm2 - movhpd qword ptr [ebx], xmm2 - punpckhdq xmm0, xmm6 - movlpd qword ptr [edx + esi], xmm0 - lea edx, [edx + 2 * esi] - movhpd qword ptr [ebx + ebp], xmm0 - lea ebx, [ebx + 2 * ebp] - movdqa xmm0, xmm1 // use xmm0 as the temp register. - punpckldq xmm1, xmm5 - movlpd qword ptr [edx], xmm1 - movhpd qword ptr [ebx], xmm1 - punpckhdq xmm0, xmm5 - movlpd qword ptr [edx + esi], xmm0 - lea edx, [edx + 2 * esi] - movhpd qword ptr [ebx + ebp], xmm0 - lea ebx, [ebx + 2 * ebp] - movdqa xmm0, xmm3 // use xmm0 as the temp register. - punpckldq xmm3, xmm7 - movlpd qword ptr [edx], xmm3 - movhpd qword ptr [ebx], xmm3 - punpckhdq xmm0, xmm7 - sub ecx, 8 - movlpd qword ptr [edx + esi], xmm0 - lea edx, [edx + 2 * esi] - movhpd qword ptr [ebx + ebp], xmm0 - lea ebx, [ebx + 2 * ebp] - jg convertloop - - mov esp, [esp + 16] - pop ebp - pop edi - pop esi - pop ebx - ret - } -} - -#endif // !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/row_any.cc b/telegramgallery/src/main/cpp/libyuv/source/row_any.cc deleted file mode 100644 index 494164f..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/row_any.cc +++ /dev/null @@ -1,824 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" - -#include // For memset. - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Subsampled source needs to be increase by 1 of not even. -#define SS(width, shift) (((width) + (1 << (shift)) - 1) >> (shift)) - -// Any 4 planes to 1 with yuvconstants -#define ANY41C(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, BPP, MASK) \ - void NAMEANY(const uint8* y_buf, const uint8* u_buf, const uint8* v_buf, \ - const uint8* a_buf, uint8* dst_ptr, \ - const struct YuvConstants* yuvconstants, int width) { \ - SIMD_ALIGNED(uint8 temp[64 * 5]); \ - memset(temp, 0, 64 * 4); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(y_buf, u_buf, v_buf, a_buf, dst_ptr, yuvconstants, n); \ - } \ - memcpy(temp, y_buf + n, r); \ - memcpy(temp + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 192, a_buf + n, r); \ - ANY_SIMD(temp, temp + 64, temp + 128, temp + 192, temp + 256, \ - yuvconstants, MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, temp + 256, \ - SS(r, DUVSHIFT) * BPP); \ - } - -#ifdef HAS_I422ALPHATOARGBROW_SSSE3 -ANY41C(I422AlphaToARGBRow_Any_SSSE3, I422AlphaToARGBRow_SSSE3, 1, 0, 4, 7) -#endif -#ifdef HAS_I422ALPHATOARGBROW_AVX2 -ANY41C(I422AlphaToARGBRow_Any_AVX2, I422AlphaToARGBRow_AVX2, 1, 0, 4, 15) -#endif -#ifdef HAS_I422ALPHATOARGBROW_NEON -ANY41C(I422AlphaToARGBRow_Any_NEON, I422AlphaToARGBRow_NEON, 1, 0, 4, 7) -#endif -#undef ANY41C - -// Any 3 planes to 1. -#define ANY31(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, BPP, MASK) \ - void NAMEANY(const uint8* y_buf, const uint8* u_buf, const uint8* v_buf, \ - uint8* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8 temp[64 * 4]); \ - memset(temp, 0, 64 * 3); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(y_buf, u_buf, v_buf, dst_ptr, n); \ - } \ - memcpy(temp, y_buf + n, r); \ - memcpy(temp + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - ANY_SIMD(temp, temp + 64, temp + 128, temp + 192, MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, temp + 192, \ - SS(r, DUVSHIFT) * BPP); \ - } -#ifdef HAS_I422TOYUY2ROW_SSE2 -ANY31(I422ToYUY2Row_Any_SSE2, I422ToYUY2Row_SSE2, 1, 1, 4, 15) -ANY31(I422ToUYVYRow_Any_SSE2, I422ToUYVYRow_SSE2, 1, 1, 4, 15) -#endif -#ifdef HAS_I422TOYUY2ROW_NEON -ANY31(I422ToYUY2Row_Any_NEON, I422ToYUY2Row_NEON, 1, 1, 4, 15) -#endif -#ifdef HAS_I422TOUYVYROW_NEON -ANY31(I422ToUYVYRow_Any_NEON, I422ToUYVYRow_NEON, 1, 1, 4, 15) -#endif -#ifdef HAS_BLENDPLANEROW_AVX2 -ANY31(BlendPlaneRow_Any_AVX2, BlendPlaneRow_AVX2, 0, 0, 1, 31) -#endif -#ifdef HAS_BLENDPLANEROW_SSSE3 -ANY31(BlendPlaneRow_Any_SSSE3, BlendPlaneRow_SSSE3, 0, 0, 1, 7) -#endif -#undef ANY31 - -// Note that odd width replication includes 444 due to implementation -// on arm that subsamples 444 to 422 internally. -// Any 3 planes to 1 with yuvconstants -#define ANY31C(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, BPP, MASK) \ - void NAMEANY(const uint8* y_buf, const uint8* u_buf, const uint8* v_buf, \ - uint8* dst_ptr, const struct YuvConstants* yuvconstants, \ - int width) { \ - SIMD_ALIGNED(uint8 temp[64 * 4]); \ - memset(temp, 0, 64 * 3); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(y_buf, u_buf, v_buf, dst_ptr, yuvconstants, n); \ - } \ - memcpy(temp, y_buf + n, r); \ - memcpy(temp + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - if (width & 1) { \ - temp[64 + SS(r, UVSHIFT)] = temp[64 + SS(r, UVSHIFT) - 1]; \ - temp[128 + SS(r, UVSHIFT)] = temp[128 + SS(r, UVSHIFT) - 1]; \ - } \ - ANY_SIMD(temp, temp + 64, temp + 128, temp + 192, \ - yuvconstants, MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, temp + 192, \ - SS(r, DUVSHIFT) * BPP); \ - } - -#ifdef HAS_I422TOARGBROW_SSSE3 -ANY31C(I422ToARGBRow_Any_SSSE3, I422ToARGBRow_SSSE3, 1, 0, 4, 7) -#endif -#ifdef HAS_I411TOARGBROW_SSSE3 -ANY31C(I411ToARGBRow_Any_SSSE3, I411ToARGBRow_SSSE3, 2, 0, 4, 7) -#endif -#ifdef HAS_I444TOARGBROW_SSSE3 -ANY31C(I444ToARGBRow_Any_SSSE3, I444ToARGBRow_SSSE3, 0, 0, 4, 7) -ANY31C(I422ToRGBARow_Any_SSSE3, I422ToRGBARow_SSSE3, 1, 0, 4, 7) -ANY31C(I422ToARGB4444Row_Any_SSSE3, I422ToARGB4444Row_SSSE3, 1, 0, 2, 7) -ANY31C(I422ToARGB1555Row_Any_SSSE3, I422ToARGB1555Row_SSSE3, 1, 0, 2, 7) -ANY31C(I422ToRGB565Row_Any_SSSE3, I422ToRGB565Row_SSSE3, 1, 0, 2, 7) -ANY31C(I422ToRGB24Row_Any_SSSE3, I422ToRGB24Row_SSSE3, 1, 0, 3, 7) -#endif // HAS_I444TOARGBROW_SSSE3 -#ifdef HAS_I422TORGB24ROW_AVX2 -ANY31C(I422ToRGB24Row_Any_AVX2, I422ToRGB24Row_AVX2, 1, 0, 3, 15) -#endif -#ifdef HAS_I422TOARGBROW_AVX2 -ANY31C(I422ToARGBRow_Any_AVX2, I422ToARGBRow_AVX2, 1, 0, 4, 15) -#endif -#ifdef HAS_I422TORGBAROW_AVX2 -ANY31C(I422ToRGBARow_Any_AVX2, I422ToRGBARow_AVX2, 1, 0, 4, 15) -#endif -#ifdef HAS_I444TOARGBROW_AVX2 -ANY31C(I444ToARGBRow_Any_AVX2, I444ToARGBRow_AVX2, 0, 0, 4, 15) -#endif -#ifdef HAS_I411TOARGBROW_AVX2 -ANY31C(I411ToARGBRow_Any_AVX2, I411ToARGBRow_AVX2, 2, 0, 4, 15) -#endif -#ifdef HAS_I422TOARGB4444ROW_AVX2 -ANY31C(I422ToARGB4444Row_Any_AVX2, I422ToARGB4444Row_AVX2, 1, 0, 2, 7) -#endif -#ifdef HAS_I422TOARGB1555ROW_AVX2 -ANY31C(I422ToARGB1555Row_Any_AVX2, I422ToARGB1555Row_AVX2, 1, 0, 2, 7) -#endif -#ifdef HAS_I422TORGB565ROW_AVX2 -ANY31C(I422ToRGB565Row_Any_AVX2, I422ToRGB565Row_AVX2, 1, 0, 2, 7) -#endif -#ifdef HAS_I422TOARGBROW_NEON -ANY31C(I444ToARGBRow_Any_NEON, I444ToARGBRow_NEON, 0, 0, 4, 7) -ANY31C(I422ToARGBRow_Any_NEON, I422ToARGBRow_NEON, 1, 0, 4, 7) -ANY31C(I411ToARGBRow_Any_NEON, I411ToARGBRow_NEON, 2, 0, 4, 7) -ANY31C(I422ToRGBARow_Any_NEON, I422ToRGBARow_NEON, 1, 0, 4, 7) -ANY31C(I422ToRGB24Row_Any_NEON, I422ToRGB24Row_NEON, 1, 0, 3, 7) -ANY31C(I422ToARGB4444Row_Any_NEON, I422ToARGB4444Row_NEON, 1, 0, 2, 7) -ANY31C(I422ToARGB1555Row_Any_NEON, I422ToARGB1555Row_NEON, 1, 0, 2, 7) -ANY31C(I422ToRGB565Row_Any_NEON, I422ToRGB565Row_NEON, 1, 0, 2, 7) -#endif -#undef ANY31C - -// Any 2 planes to 1. -#define ANY21(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, SBPP2, BPP, MASK) \ - void NAMEANY(const uint8* y_buf, const uint8* uv_buf, \ - uint8* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8 temp[64 * 3]); \ - memset(temp, 0, 64 * 2); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(y_buf, uv_buf, dst_ptr, n); \ - } \ - memcpy(temp, y_buf + n * SBPP, r * SBPP); \ - memcpy(temp + 64, uv_buf + (n >> UVSHIFT) * SBPP2, \ - SS(r, UVSHIFT) * SBPP2); \ - ANY_SIMD(temp, temp + 64, temp + 128, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \ - } - -// Merge functions. -#ifdef HAS_MERGEUVROW_SSE2 -ANY21(MergeUVRow_Any_SSE2, MergeUVRow_SSE2, 0, 1, 1, 2, 15) -#endif -#ifdef HAS_MERGEUVROW_AVX2 -ANY21(MergeUVRow_Any_AVX2, MergeUVRow_AVX2, 0, 1, 1, 2, 31) -#endif -#ifdef HAS_MERGEUVROW_NEON -ANY21(MergeUVRow_Any_NEON, MergeUVRow_NEON, 0, 1, 1, 2, 15) -#endif - -// Math functions. -#ifdef HAS_ARGBMULTIPLYROW_SSE2 -ANY21(ARGBMultiplyRow_Any_SSE2, ARGBMultiplyRow_SSE2, 0, 4, 4, 4, 3) -#endif -#ifdef HAS_ARGBADDROW_SSE2 -ANY21(ARGBAddRow_Any_SSE2, ARGBAddRow_SSE2, 0, 4, 4, 4, 3) -#endif -#ifdef HAS_ARGBSUBTRACTROW_SSE2 -ANY21(ARGBSubtractRow_Any_SSE2, ARGBSubtractRow_SSE2, 0, 4, 4, 4, 3) -#endif -#ifdef HAS_ARGBMULTIPLYROW_AVX2 -ANY21(ARGBMultiplyRow_Any_AVX2, ARGBMultiplyRow_AVX2, 0, 4, 4, 4, 7) -#endif -#ifdef HAS_ARGBADDROW_AVX2 -ANY21(ARGBAddRow_Any_AVX2, ARGBAddRow_AVX2, 0, 4, 4, 4, 7) -#endif -#ifdef HAS_ARGBSUBTRACTROW_AVX2 -ANY21(ARGBSubtractRow_Any_AVX2, ARGBSubtractRow_AVX2, 0, 4, 4, 4, 7) -#endif -#ifdef HAS_ARGBMULTIPLYROW_NEON -ANY21(ARGBMultiplyRow_Any_NEON, ARGBMultiplyRow_NEON, 0, 4, 4, 4, 7) -#endif -#ifdef HAS_ARGBADDROW_NEON -ANY21(ARGBAddRow_Any_NEON, ARGBAddRow_NEON, 0, 4, 4, 4, 7) -#endif -#ifdef HAS_ARGBSUBTRACTROW_NEON -ANY21(ARGBSubtractRow_Any_NEON, ARGBSubtractRow_NEON, 0, 4, 4, 4, 7) -#endif -#ifdef HAS_SOBELROW_SSE2 -ANY21(SobelRow_Any_SSE2, SobelRow_SSE2, 0, 1, 1, 4, 15) -#endif -#ifdef HAS_SOBELROW_NEON -ANY21(SobelRow_Any_NEON, SobelRow_NEON, 0, 1, 1, 4, 7) -#endif -#ifdef HAS_SOBELTOPLANEROW_SSE2 -ANY21(SobelToPlaneRow_Any_SSE2, SobelToPlaneRow_SSE2, 0, 1, 1, 1, 15) -#endif -#ifdef HAS_SOBELTOPLANEROW_NEON -ANY21(SobelToPlaneRow_Any_NEON, SobelToPlaneRow_NEON, 0, 1, 1, 1, 15) -#endif -#ifdef HAS_SOBELXYROW_SSE2 -ANY21(SobelXYRow_Any_SSE2, SobelXYRow_SSE2, 0, 1, 1, 4, 15) -#endif -#ifdef HAS_SOBELXYROW_NEON -ANY21(SobelXYRow_Any_NEON, SobelXYRow_NEON, 0, 1, 1, 4, 7) -#endif -#undef ANY21 - -// Any 2 planes to 1 with yuvconstants -#define ANY21C(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, SBPP2, BPP, MASK) \ - void NAMEANY(const uint8* y_buf, const uint8* uv_buf, \ - uint8* dst_ptr, const struct YuvConstants* yuvconstants, \ - int width) { \ - SIMD_ALIGNED(uint8 temp[64 * 3]); \ - memset(temp, 0, 64 * 2); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(y_buf, uv_buf, dst_ptr, yuvconstants, n); \ - } \ - memcpy(temp, y_buf + n * SBPP, r * SBPP); \ - memcpy(temp + 64, uv_buf + (n >> UVSHIFT) * SBPP2, \ - SS(r, UVSHIFT) * SBPP2); \ - ANY_SIMD(temp, temp + 64, temp + 128, yuvconstants, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \ - } - -// Biplanar to RGB. -#ifdef HAS_NV12TOARGBROW_SSSE3 -ANY21C(NV12ToARGBRow_Any_SSSE3, NV12ToARGBRow_SSSE3, 1, 1, 2, 4, 7) -#endif -#ifdef HAS_NV12TOARGBROW_AVX2 -ANY21C(NV12ToARGBRow_Any_AVX2, NV12ToARGBRow_AVX2, 1, 1, 2, 4, 15) -#endif -#ifdef HAS_NV12TOARGBROW_NEON -ANY21C(NV12ToARGBRow_Any_NEON, NV12ToARGBRow_NEON, 1, 1, 2, 4, 7) -#endif -#ifdef HAS_NV21TOARGBROW_SSSE3 -ANY21C(NV21ToARGBRow_Any_SSSE3, NV21ToARGBRow_SSSE3, 1, 1, 2, 4, 7) -#endif -#ifdef HAS_NV21TOARGBROW_AVX2 -ANY21C(NV21ToARGBRow_Any_AVX2, NV21ToARGBRow_AVX2, 1, 1, 2, 4, 15) -#endif -#ifdef HAS_NV21TOARGBROW_NEON -ANY21C(NV21ToARGBRow_Any_NEON, NV21ToARGBRow_NEON, 1, 1, 2, 4, 7) -#endif -#ifdef HAS_NV12TORGB565ROW_SSSE3 -ANY21C(NV12ToRGB565Row_Any_SSSE3, NV12ToRGB565Row_SSSE3, 1, 1, 2, 2, 7) -#endif -#ifdef HAS_NV12TORGB565ROW_AVX2 -ANY21C(NV12ToRGB565Row_Any_AVX2, NV12ToRGB565Row_AVX2, 1, 1, 2, 2, 15) -#endif -#ifdef HAS_NV12TORGB565ROW_NEON -ANY21C(NV12ToRGB565Row_Any_NEON, NV12ToRGB565Row_NEON, 1, 1, 2, 2, 7) -#endif -#undef ANY21C - -// Any 1 to 1. -#define ANY11(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, uint8* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8 temp[128 * 2]); \ - memset(temp, 0, 128); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_ptr, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ - ANY_SIMD(temp, temp + 128, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \ - } - -#ifdef HAS_COPYROW_AVX -ANY11(CopyRow_Any_AVX, CopyRow_AVX, 0, 1, 1, 63) -#endif -#ifdef HAS_COPYROW_SSE2 -ANY11(CopyRow_Any_SSE2, CopyRow_SSE2, 0, 1, 1, 31) -#endif -#ifdef HAS_COPYROW_NEON -ANY11(CopyRow_Any_NEON, CopyRow_NEON, 0, 1, 1, 31) -#endif -#if defined(HAS_ARGBTORGB24ROW_SSSE3) -ANY11(ARGBToRGB24Row_Any_SSSE3, ARGBToRGB24Row_SSSE3, 0, 4, 3, 15) -ANY11(ARGBToRAWRow_Any_SSSE3, ARGBToRAWRow_SSSE3, 0, 4, 3, 15) -ANY11(ARGBToRGB565Row_Any_SSE2, ARGBToRGB565Row_SSE2, 0, 4, 2, 3) -ANY11(ARGBToARGB1555Row_Any_SSE2, ARGBToARGB1555Row_SSE2, 0, 4, 2, 3) -ANY11(ARGBToARGB4444Row_Any_SSE2, ARGBToARGB4444Row_SSE2, 0, 4, 2, 3) -#endif -#if defined(HAS_ARGBTORGB565ROW_AVX2) -ANY11(ARGBToRGB565Row_Any_AVX2, ARGBToRGB565Row_AVX2, 0, 4, 2, 7) -#endif -#if defined(HAS_ARGBTOARGB4444ROW_AVX2) -ANY11(ARGBToARGB1555Row_Any_AVX2, ARGBToARGB1555Row_AVX2, 0, 4, 2, 7) -ANY11(ARGBToARGB4444Row_Any_AVX2, ARGBToARGB4444Row_AVX2, 0, 4, 2, 7) -#endif -#if defined(HAS_J400TOARGBROW_SSE2) -ANY11(J400ToARGBRow_Any_SSE2, J400ToARGBRow_SSE2, 0, 1, 4, 7) -#endif -#if defined(HAS_J400TOARGBROW_AVX2) -ANY11(J400ToARGBRow_Any_AVX2, J400ToARGBRow_AVX2, 0, 1, 4, 15) -#endif -#if defined(HAS_I400TOARGBROW_SSE2) -ANY11(I400ToARGBRow_Any_SSE2, I400ToARGBRow_SSE2, 0, 1, 4, 7) -#endif -#if defined(HAS_I400TOARGBROW_AVX2) -ANY11(I400ToARGBRow_Any_AVX2, I400ToARGBRow_AVX2, 0, 1, 4, 15) -#endif -#if defined(HAS_RGB24TOARGBROW_SSSE3) -ANY11(RGB24ToARGBRow_Any_SSSE3, RGB24ToARGBRow_SSSE3, 0, 3, 4, 15) -ANY11(RAWToARGBRow_Any_SSSE3, RAWToARGBRow_SSSE3, 0, 3, 4, 15) -ANY11(RGB565ToARGBRow_Any_SSE2, RGB565ToARGBRow_SSE2, 0, 2, 4, 7) -ANY11(ARGB1555ToARGBRow_Any_SSE2, ARGB1555ToARGBRow_SSE2, 0, 2, 4, 7) -ANY11(ARGB4444ToARGBRow_Any_SSE2, ARGB4444ToARGBRow_SSE2, 0, 2, 4, 7) -#endif -#if defined(HAS_RAWTORGB24ROW_SSSE3) -ANY11(RAWToRGB24Row_Any_SSSE3, RAWToRGB24Row_SSSE3, 0, 3, 3, 7) -#endif -#if defined(HAS_RGB565TOARGBROW_AVX2) -ANY11(RGB565ToARGBRow_Any_AVX2, RGB565ToARGBRow_AVX2, 0, 2, 4, 15) -#endif -#if defined(HAS_ARGB1555TOARGBROW_AVX2) -ANY11(ARGB1555ToARGBRow_Any_AVX2, ARGB1555ToARGBRow_AVX2, 0, 2, 4, 15) -#endif -#if defined(HAS_ARGB4444TOARGBROW_AVX2) -ANY11(ARGB4444ToARGBRow_Any_AVX2, ARGB4444ToARGBRow_AVX2, 0, 2, 4, 15) -#endif -#if defined(HAS_ARGBTORGB24ROW_NEON) -ANY11(ARGBToRGB24Row_Any_NEON, ARGBToRGB24Row_NEON, 0, 4, 3, 7) -ANY11(ARGBToRAWRow_Any_NEON, ARGBToRAWRow_NEON, 0, 4, 3, 7) -ANY11(ARGBToRGB565Row_Any_NEON, ARGBToRGB565Row_NEON, 0, 4, 2, 7) -ANY11(ARGBToARGB1555Row_Any_NEON, ARGBToARGB1555Row_NEON, 0, 4, 2, 7) -ANY11(ARGBToARGB4444Row_Any_NEON, ARGBToARGB4444Row_NEON, 0, 4, 2, 7) -ANY11(J400ToARGBRow_Any_NEON, J400ToARGBRow_NEON, 0, 1, 4, 7) -ANY11(I400ToARGBRow_Any_NEON, I400ToARGBRow_NEON, 0, 1, 4, 7) -#endif -#if defined(HAS_RAWTORGB24ROW_NEON) -ANY11(RAWToRGB24Row_Any_NEON, RAWToRGB24Row_NEON, 0, 3, 3, 7) -#endif -#ifdef HAS_ARGBTOYROW_AVX2 -ANY11(ARGBToYRow_Any_AVX2, ARGBToYRow_AVX2, 0, 4, 1, 31) -#endif -#ifdef HAS_ARGBTOYJROW_AVX2 -ANY11(ARGBToYJRow_Any_AVX2, ARGBToYJRow_AVX2, 0, 4, 1, 31) -#endif -#ifdef HAS_UYVYTOYROW_AVX2 -ANY11(UYVYToYRow_Any_AVX2, UYVYToYRow_AVX2, 0, 2, 1, 31) -#endif -#ifdef HAS_YUY2TOYROW_AVX2 -ANY11(YUY2ToYRow_Any_AVX2, YUY2ToYRow_AVX2, 1, 4, 1, 31) -#endif -#ifdef HAS_ARGBTOYROW_SSSE3 -ANY11(ARGBToYRow_Any_SSSE3, ARGBToYRow_SSSE3, 0, 4, 1, 15) -#endif -#ifdef HAS_BGRATOYROW_SSSE3 -ANY11(BGRAToYRow_Any_SSSE3, BGRAToYRow_SSSE3, 0, 4, 1, 15) -ANY11(ABGRToYRow_Any_SSSE3, ABGRToYRow_SSSE3, 0, 4, 1, 15) -ANY11(RGBAToYRow_Any_SSSE3, RGBAToYRow_SSSE3, 0, 4, 1, 15) -ANY11(YUY2ToYRow_Any_SSE2, YUY2ToYRow_SSE2, 1, 4, 1, 15) -ANY11(UYVYToYRow_Any_SSE2, UYVYToYRow_SSE2, 1, 4, 1, 15) -#endif -#ifdef HAS_ARGBTOYJROW_SSSE3 -ANY11(ARGBToYJRow_Any_SSSE3, ARGBToYJRow_SSSE3, 0, 4, 1, 15) -#endif -#ifdef HAS_ARGBTOYROW_NEON -ANY11(ARGBToYRow_Any_NEON, ARGBToYRow_NEON, 0, 4, 1, 7) -#endif -#ifdef HAS_ARGBTOYJROW_NEON -ANY11(ARGBToYJRow_Any_NEON, ARGBToYJRow_NEON, 0, 4, 1, 7) -#endif -#ifdef HAS_BGRATOYROW_NEON -ANY11(BGRAToYRow_Any_NEON, BGRAToYRow_NEON, 0, 4, 1, 7) -#endif -#ifdef HAS_ABGRTOYROW_NEON -ANY11(ABGRToYRow_Any_NEON, ABGRToYRow_NEON, 0, 4, 1, 7) -#endif -#ifdef HAS_RGBATOYROW_NEON -ANY11(RGBAToYRow_Any_NEON, RGBAToYRow_NEON, 0, 4, 1, 7) -#endif -#ifdef HAS_RGB24TOYROW_NEON -ANY11(RGB24ToYRow_Any_NEON, RGB24ToYRow_NEON, 0, 3, 1, 7) -#endif -#ifdef HAS_RAWTOYROW_NEON -ANY11(RAWToYRow_Any_NEON, RAWToYRow_NEON, 0, 3, 1, 7) -#endif -#ifdef HAS_RGB565TOYROW_NEON -ANY11(RGB565ToYRow_Any_NEON, RGB565ToYRow_NEON, 0, 2, 1, 7) -#endif -#ifdef HAS_ARGB1555TOYROW_NEON -ANY11(ARGB1555ToYRow_Any_NEON, ARGB1555ToYRow_NEON, 0, 2, 1, 7) -#endif -#ifdef HAS_ARGB4444TOYROW_NEON -ANY11(ARGB4444ToYRow_Any_NEON, ARGB4444ToYRow_NEON, 0, 2, 1, 7) -#endif -#ifdef HAS_YUY2TOYROW_NEON -ANY11(YUY2ToYRow_Any_NEON, YUY2ToYRow_NEON, 1, 4, 1, 15) -#endif -#ifdef HAS_UYVYTOYROW_NEON -ANY11(UYVYToYRow_Any_NEON, UYVYToYRow_NEON, 0, 2, 1, 15) -#endif -#ifdef HAS_RGB24TOARGBROW_NEON -ANY11(RGB24ToARGBRow_Any_NEON, RGB24ToARGBRow_NEON, 0, 3, 4, 7) -#endif -#ifdef HAS_RAWTOARGBROW_NEON -ANY11(RAWToARGBRow_Any_NEON, RAWToARGBRow_NEON, 0, 3, 4, 7) -#endif -#ifdef HAS_RGB565TOARGBROW_NEON -ANY11(RGB565ToARGBRow_Any_NEON, RGB565ToARGBRow_NEON, 0, 2, 4, 7) -#endif -#ifdef HAS_ARGB1555TOARGBROW_NEON -ANY11(ARGB1555ToARGBRow_Any_NEON, ARGB1555ToARGBRow_NEON, 0, 2, 4, 7) -#endif -#ifdef HAS_ARGB4444TOARGBROW_NEON -ANY11(ARGB4444ToARGBRow_Any_NEON, ARGB4444ToARGBRow_NEON, 0, 2, 4, 7) -#endif -#ifdef HAS_ARGBATTENUATEROW_SSSE3 -ANY11(ARGBAttenuateRow_Any_SSSE3, ARGBAttenuateRow_SSSE3, 0, 4, 4, 3) -#endif -#ifdef HAS_ARGBUNATTENUATEROW_SSE2 -ANY11(ARGBUnattenuateRow_Any_SSE2, ARGBUnattenuateRow_SSE2, 0, 4, 4, 3) -#endif -#ifdef HAS_ARGBATTENUATEROW_AVX2 -ANY11(ARGBAttenuateRow_Any_AVX2, ARGBAttenuateRow_AVX2, 0, 4, 4, 7) -#endif -#ifdef HAS_ARGBUNATTENUATEROW_AVX2 -ANY11(ARGBUnattenuateRow_Any_AVX2, ARGBUnattenuateRow_AVX2, 0, 4, 4, 7) -#endif -#ifdef HAS_ARGBATTENUATEROW_NEON -ANY11(ARGBAttenuateRow_Any_NEON, ARGBAttenuateRow_NEON, 0, 4, 4, 7) -#endif -#ifdef HAS_ARGBEXTRACTALPHAROW_SSE2 -ANY11(ARGBExtractAlphaRow_Any_SSE2, ARGBExtractAlphaRow_SSE2, 0, 4, 1, 7) -#endif -#ifdef HAS_ARGBEXTRACTALPHAROW_NEON -ANY11(ARGBExtractAlphaRow_Any_NEON, ARGBExtractAlphaRow_NEON, 0, 4, 1, 15) -#endif -#undef ANY11 - -// Any 1 to 1 blended. Destination is read, modify, write. -#define ANY11B(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, uint8* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8 temp[128 * 2]); \ - memset(temp, 0, 128 * 2); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_ptr, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ - memcpy(temp + 128, dst_ptr + n * BPP, r * BPP); \ - ANY_SIMD(temp, temp + 128, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \ - } - -#ifdef HAS_ARGBCOPYALPHAROW_AVX2 -ANY11B(ARGBCopyAlphaRow_Any_AVX2, ARGBCopyAlphaRow_AVX2, 0, 4, 4, 15) -#endif -#ifdef HAS_ARGBCOPYALPHAROW_SSE2 -ANY11B(ARGBCopyAlphaRow_Any_SSE2, ARGBCopyAlphaRow_SSE2, 0, 4, 4, 7) -#endif -#ifdef HAS_ARGBCOPYYTOALPHAROW_AVX2 -ANY11B(ARGBCopyYToAlphaRow_Any_AVX2, ARGBCopyYToAlphaRow_AVX2, 0, 1, 4, 15) -#endif -#ifdef HAS_ARGBCOPYYTOALPHAROW_SSE2 -ANY11B(ARGBCopyYToAlphaRow_Any_SSE2, ARGBCopyYToAlphaRow_SSE2, 0, 1, 4, 7) -#endif -#undef ANY11B - -// Any 1 to 1 with parameter. -#define ANY11P(NAMEANY, ANY_SIMD, T, SBPP, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, uint8* dst_ptr, \ - T shuffler, int width) { \ - SIMD_ALIGNED(uint8 temp[64 * 2]); \ - memset(temp, 0, 64); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_ptr, shuffler, n); \ - } \ - memcpy(temp, src_ptr + n * SBPP, r * SBPP); \ - ANY_SIMD(temp, temp + 64, shuffler, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 64, r * BPP); \ - } - -#if defined(HAS_ARGBTORGB565DITHERROW_SSE2) -ANY11P(ARGBToRGB565DitherRow_Any_SSE2, ARGBToRGB565DitherRow_SSE2, - const uint32, 4, 2, 3) -#endif -#if defined(HAS_ARGBTORGB565DITHERROW_AVX2) -ANY11P(ARGBToRGB565DitherRow_Any_AVX2, ARGBToRGB565DitherRow_AVX2, - const uint32, 4, 2, 7) -#endif -#if defined(HAS_ARGBTORGB565DITHERROW_NEON) -ANY11P(ARGBToRGB565DitherRow_Any_NEON, ARGBToRGB565DitherRow_NEON, - const uint32, 4, 2, 7) -#endif -#ifdef HAS_ARGBSHUFFLEROW_SSE2 -ANY11P(ARGBShuffleRow_Any_SSE2, ARGBShuffleRow_SSE2, const uint8*, 4, 4, 3) -#endif -#ifdef HAS_ARGBSHUFFLEROW_SSSE3 -ANY11P(ARGBShuffleRow_Any_SSSE3, ARGBShuffleRow_SSSE3, const uint8*, 4, 4, 7) -#endif -#ifdef HAS_ARGBSHUFFLEROW_AVX2 -ANY11P(ARGBShuffleRow_Any_AVX2, ARGBShuffleRow_AVX2, const uint8*, 4, 4, 15) -#endif -#ifdef HAS_ARGBSHUFFLEROW_NEON -ANY11P(ARGBShuffleRow_Any_NEON, ARGBShuffleRow_NEON, const uint8*, 4, 4, 3) -#endif -#undef ANY11P - -// Any 1 to 1 with yuvconstants -#define ANY11C(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, uint8* dst_ptr, \ - const struct YuvConstants* yuvconstants, int width) { \ - SIMD_ALIGNED(uint8 temp[128 * 2]); \ - memset(temp, 0, 128); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_ptr, yuvconstants, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ - ANY_SIMD(temp, temp + 128, yuvconstants, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \ - } -#if defined(HAS_YUY2TOARGBROW_SSSE3) -ANY11C(YUY2ToARGBRow_Any_SSSE3, YUY2ToARGBRow_SSSE3, 1, 4, 4, 15) -ANY11C(UYVYToARGBRow_Any_SSSE3, UYVYToARGBRow_SSSE3, 1, 4, 4, 15) -#endif -#if defined(HAS_YUY2TOARGBROW_AVX2) -ANY11C(YUY2ToARGBRow_Any_AVX2, YUY2ToARGBRow_AVX2, 1, 4, 4, 31) -ANY11C(UYVYToARGBRow_Any_AVX2, UYVYToARGBRow_AVX2, 1, 4, 4, 31) -#endif -#if defined(HAS_YUY2TOARGBROW_NEON) -ANY11C(YUY2ToARGBRow_Any_NEON, YUY2ToARGBRow_NEON, 1, 4, 4, 7) -ANY11C(UYVYToARGBRow_Any_NEON, UYVYToARGBRow_NEON, 1, 4, 4, 7) -#endif -#undef ANY11C - -// Any 1 to 1 interpolate. Takes 2 rows of source via stride. -#define ANY11T(NAMEANY, ANY_SIMD, SBPP, BPP, MASK) \ - void NAMEANY(uint8* dst_ptr, const uint8* src_ptr, \ - ptrdiff_t src_stride_ptr, int width, \ - int source_y_fraction) { \ - SIMD_ALIGNED(uint8 temp[64 * 3]); \ - memset(temp, 0, 64 * 2); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(dst_ptr, src_ptr, src_stride_ptr, n, source_y_fraction); \ - } \ - memcpy(temp, src_ptr + n * SBPP, r * SBPP); \ - memcpy(temp + 64, src_ptr + src_stride_ptr + n * SBPP, r * SBPP); \ - ANY_SIMD(temp + 128, temp, 64, MASK + 1, source_y_fraction); \ - memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \ - } - -#ifdef HAS_INTERPOLATEROW_AVX2 -ANY11T(InterpolateRow_Any_AVX2, InterpolateRow_AVX2, 1, 1, 31) -#endif -#ifdef HAS_INTERPOLATEROW_SSSE3 -ANY11T(InterpolateRow_Any_SSSE3, InterpolateRow_SSSE3, 1, 1, 15) -#endif -#ifdef HAS_INTERPOLATEROW_NEON -ANY11T(InterpolateRow_Any_NEON, InterpolateRow_NEON, 1, 1, 15) -#endif -#ifdef HAS_INTERPOLATEROW_DSPR2 -ANY11T(InterpolateRow_Any_DSPR2, InterpolateRow_DSPR2, 1, 1, 3) -#endif -#undef ANY11T - -// Any 1 to 1 mirror. -#define ANY11M(NAMEANY, ANY_SIMD, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, uint8* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8 temp[64 * 2]); \ - memset(temp, 0, 64); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr + r * BPP, dst_ptr, n); \ - } \ - memcpy(temp, src_ptr, r * BPP); \ - ANY_SIMD(temp, temp + 64, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 64 + (MASK + 1 - r) * BPP, r * BPP); \ - } - -#ifdef HAS_MIRRORROW_AVX2 -ANY11M(MirrorRow_Any_AVX2, MirrorRow_AVX2, 1, 31) -#endif -#ifdef HAS_MIRRORROW_SSSE3 -ANY11M(MirrorRow_Any_SSSE3, MirrorRow_SSSE3, 1, 15) -#endif -#ifdef HAS_MIRRORROW_NEON -ANY11M(MirrorRow_Any_NEON, MirrorRow_NEON, 1, 15) -#endif -#ifdef HAS_ARGBMIRRORROW_AVX2 -ANY11M(ARGBMirrorRow_Any_AVX2, ARGBMirrorRow_AVX2, 4, 7) -#endif -#ifdef HAS_ARGBMIRRORROW_SSE2 -ANY11M(ARGBMirrorRow_Any_SSE2, ARGBMirrorRow_SSE2, 4, 3) -#endif -#ifdef HAS_ARGBMIRRORROW_NEON -ANY11M(ARGBMirrorRow_Any_NEON, ARGBMirrorRow_NEON, 4, 3) -#endif -#undef ANY11M - -// Any 1 plane. (memset) -#define ANY1(NAMEANY, ANY_SIMD, T, BPP, MASK) \ - void NAMEANY(uint8* dst_ptr, T v32, int width) { \ - SIMD_ALIGNED(uint8 temp[64]); \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(dst_ptr, v32, n); \ - } \ - ANY_SIMD(temp, v32, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp, r * BPP); \ - } - -#ifdef HAS_SETROW_X86 -ANY1(SetRow_Any_X86, SetRow_X86, uint8, 1, 3) -#endif -#ifdef HAS_SETROW_NEON -ANY1(SetRow_Any_NEON, SetRow_NEON, uint8, 1, 15) -#endif -#ifdef HAS_ARGBSETROW_NEON -ANY1(ARGBSetRow_Any_NEON, ARGBSetRow_NEON, uint32, 4, 3) -#endif -#undef ANY1 - -// Any 1 to 2. Outputs UV planes. -#define ANY12(NAMEANY, ANY_SIMD, UVSHIFT, BPP, DUVSHIFT, MASK) \ - void NAMEANY(const uint8* src_ptr, uint8* dst_u, uint8* dst_v, int width) {\ - SIMD_ALIGNED(uint8 temp[128 * 3]); \ - memset(temp, 0, 128); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_u, dst_v, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * BPP, SS(r, UVSHIFT) * BPP); \ - /* repeat last 4 bytes for 422 subsampler */ \ - if ((width & 1) && BPP == 4 && DUVSHIFT == 1) { \ - memcpy(temp + SS(r, UVSHIFT) * BPP, \ - temp + SS(r, UVSHIFT) * BPP - BPP, BPP); \ - } \ - /* repeat last 4 - 12 bytes for 411 subsampler */ \ - if (((width & 3) == 1) && BPP == 4 && DUVSHIFT == 2) { \ - memcpy(temp + SS(r, UVSHIFT) * BPP, \ - temp + SS(r, UVSHIFT) * BPP - BPP, BPP); \ - memcpy(temp + SS(r, UVSHIFT) * BPP + BPP, \ - temp + SS(r, UVSHIFT) * BPP - BPP, BPP * 2); \ - } \ - if (((width & 3) == 2) && BPP == 4 && DUVSHIFT == 2) { \ - memcpy(temp + SS(r, UVSHIFT) * BPP, \ - temp + SS(r, UVSHIFT) * BPP - BPP * 2, BPP * 2); \ - } \ - if (((width & 3) == 3) && BPP == 4 && DUVSHIFT == 2) { \ - memcpy(temp + SS(r, UVSHIFT) * BPP, \ - temp + SS(r, UVSHIFT) * BPP - BPP, BPP); \ - } \ - ANY_SIMD(temp, temp + 128, temp + 256, MASK + 1); \ - memcpy(dst_u + (n >> DUVSHIFT), temp + 128, SS(r, DUVSHIFT)); \ - memcpy(dst_v + (n >> DUVSHIFT), temp + 256, SS(r, DUVSHIFT)); \ - } - -#ifdef HAS_SPLITUVROW_SSE2 -ANY12(SplitUVRow_Any_SSE2, SplitUVRow_SSE2, 0, 2, 0, 15) -#endif -#ifdef HAS_SPLITUVROW_AVX2 -ANY12(SplitUVRow_Any_AVX2, SplitUVRow_AVX2, 0, 2, 0, 31) -#endif -#ifdef HAS_SPLITUVROW_NEON -ANY12(SplitUVRow_Any_NEON, SplitUVRow_NEON, 0, 2, 0, 15) -#endif -#ifdef HAS_SPLITUVROW_DSPR2 -ANY12(SplitUVRow_Any_DSPR2, SplitUVRow_DSPR2, 0, 2, 0, 15) -#endif -#ifdef HAS_ARGBTOUV444ROW_SSSE3 -ANY12(ARGBToUV444Row_Any_SSSE3, ARGBToUV444Row_SSSE3, 0, 4, 0, 15) -#endif -#ifdef HAS_YUY2TOUV422ROW_AVX2 -ANY12(YUY2ToUV422Row_Any_AVX2, YUY2ToUV422Row_AVX2, 1, 4, 1, 31) -ANY12(UYVYToUV422Row_Any_AVX2, UYVYToUV422Row_AVX2, 1, 4, 1, 31) -#endif -#ifdef HAS_YUY2TOUV422ROW_SSE2 -ANY12(YUY2ToUV422Row_Any_SSE2, YUY2ToUV422Row_SSE2, 1, 4, 1, 15) -ANY12(UYVYToUV422Row_Any_SSE2, UYVYToUV422Row_SSE2, 1, 4, 1, 15) -#endif -#ifdef HAS_YUY2TOUV422ROW_NEON -ANY12(ARGBToUV444Row_Any_NEON, ARGBToUV444Row_NEON, 0, 4, 0, 7) -ANY12(ARGBToUV411Row_Any_NEON, ARGBToUV411Row_NEON, 0, 4, 2, 31) -ANY12(YUY2ToUV422Row_Any_NEON, YUY2ToUV422Row_NEON, 1, 4, 1, 15) -ANY12(UYVYToUV422Row_Any_NEON, UYVYToUV422Row_NEON, 1, 4, 1, 15) -#endif -#undef ANY12 - -// Any 1 to 2 with source stride (2 rows of source). Outputs UV planes. -// 128 byte row allows for 32 avx ARGB pixels. -#define ANY12S(NAMEANY, ANY_SIMD, UVSHIFT, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, int src_stride_ptr, \ - uint8* dst_u, uint8* dst_v, int width) { \ - SIMD_ALIGNED(uint8 temp[128 * 4]); \ - memset(temp, 0, 128 * 2); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, src_stride_ptr, dst_u, dst_v, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * BPP, SS(r, UVSHIFT) * BPP); \ - memcpy(temp + 128, src_ptr + src_stride_ptr + (n >> UVSHIFT) * BPP, \ - SS(r, UVSHIFT) * BPP); \ - if ((width & 1) && UVSHIFT == 0) { /* repeat last pixel for subsample */\ - memcpy(temp + SS(r, UVSHIFT) * BPP, \ - temp + SS(r, UVSHIFT) * BPP - BPP, BPP); \ - memcpy(temp + 128 + SS(r, UVSHIFT) * BPP, \ - temp + 128 + SS(r, UVSHIFT) * BPP - BPP, BPP); \ - } \ - ANY_SIMD(temp, 128, temp + 256, temp + 384, MASK + 1); \ - memcpy(dst_u + (n >> 1), temp + 256, SS(r, 1)); \ - memcpy(dst_v + (n >> 1), temp + 384, SS(r, 1)); \ - } - -#ifdef HAS_ARGBTOUVROW_AVX2 -ANY12S(ARGBToUVRow_Any_AVX2, ARGBToUVRow_AVX2, 0, 4, 31) -#endif -#ifdef HAS_ARGBTOUVJROW_AVX2 -ANY12S(ARGBToUVJRow_Any_AVX2, ARGBToUVJRow_AVX2, 0, 4, 31) -#endif -#ifdef HAS_ARGBTOUVROW_SSSE3 -ANY12S(ARGBToUVRow_Any_SSSE3, ARGBToUVRow_SSSE3, 0, 4, 15) -ANY12S(ARGBToUVJRow_Any_SSSE3, ARGBToUVJRow_SSSE3, 0, 4, 15) -ANY12S(BGRAToUVRow_Any_SSSE3, BGRAToUVRow_SSSE3, 0, 4, 15) -ANY12S(ABGRToUVRow_Any_SSSE3, ABGRToUVRow_SSSE3, 0, 4, 15) -ANY12S(RGBAToUVRow_Any_SSSE3, RGBAToUVRow_SSSE3, 0, 4, 15) -#endif -#ifdef HAS_YUY2TOUVROW_AVX2 -ANY12S(YUY2ToUVRow_Any_AVX2, YUY2ToUVRow_AVX2, 1, 4, 31) -ANY12S(UYVYToUVRow_Any_AVX2, UYVYToUVRow_AVX2, 1, 4, 31) -#endif -#ifdef HAS_YUY2TOUVROW_SSE2 -ANY12S(YUY2ToUVRow_Any_SSE2, YUY2ToUVRow_SSE2, 1, 4, 15) -ANY12S(UYVYToUVRow_Any_SSE2, UYVYToUVRow_SSE2, 1, 4, 15) -#endif -#ifdef HAS_ARGBTOUVROW_NEON -ANY12S(ARGBToUVRow_Any_NEON, ARGBToUVRow_NEON, 0, 4, 15) -#endif -#ifdef HAS_ARGBTOUVJROW_NEON -ANY12S(ARGBToUVJRow_Any_NEON, ARGBToUVJRow_NEON, 0, 4, 15) -#endif -#ifdef HAS_BGRATOUVROW_NEON -ANY12S(BGRAToUVRow_Any_NEON, BGRAToUVRow_NEON, 0, 4, 15) -#endif -#ifdef HAS_ABGRTOUVROW_NEON -ANY12S(ABGRToUVRow_Any_NEON, ABGRToUVRow_NEON, 0, 4, 15) -#endif -#ifdef HAS_RGBATOUVROW_NEON -ANY12S(RGBAToUVRow_Any_NEON, RGBAToUVRow_NEON, 0, 4, 15) -#endif -#ifdef HAS_RGB24TOUVROW_NEON -ANY12S(RGB24ToUVRow_Any_NEON, RGB24ToUVRow_NEON, 0, 3, 15) -#endif -#ifdef HAS_RAWTOUVROW_NEON -ANY12S(RAWToUVRow_Any_NEON, RAWToUVRow_NEON, 0, 3, 15) -#endif -#ifdef HAS_RGB565TOUVROW_NEON -ANY12S(RGB565ToUVRow_Any_NEON, RGB565ToUVRow_NEON, 0, 2, 15) -#endif -#ifdef HAS_ARGB1555TOUVROW_NEON -ANY12S(ARGB1555ToUVRow_Any_NEON, ARGB1555ToUVRow_NEON, 0, 2, 15) -#endif -#ifdef HAS_ARGB4444TOUVROW_NEON -ANY12S(ARGB4444ToUVRow_Any_NEON, ARGB4444ToUVRow_NEON, 0, 2, 15) -#endif -#ifdef HAS_YUY2TOUVROW_NEON -ANY12S(YUY2ToUVRow_Any_NEON, YUY2ToUVRow_NEON, 1, 4, 15) -#endif -#ifdef HAS_UYVYTOUVROW_NEON -ANY12S(UYVYToUVRow_Any_NEON, UYVYToUVRow_NEON, 1, 4, 15) -#endif -#undef ANY12S - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/row_common.cc b/telegramgallery/src/main/cpp/libyuv/source/row_common.cc deleted file mode 100644 index 32d2f68..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/row_common.cc +++ /dev/null @@ -1,2627 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" - -#include // For memcpy and memset. - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// llvm x86 is poor at ternary operator, so use branchless min/max. - -#define USE_BRANCHLESS 1 -#if USE_BRANCHLESS -static __inline int32 clamp0(int32 v) { - return ((-(v) >> 31) & (v)); -} - -static __inline int32 clamp255(int32 v) { - return (((255 - (v)) >> 31) | (v)) & 255; -} - -static __inline uint32 Clamp(int32 val) { - int v = clamp0(val); - return (uint32)(clamp255(v)); -} - -static __inline uint32 Abs(int32 v) { - int m = v >> 31; - return (v + m) ^ m; -} -#else // USE_BRANCHLESS -static __inline int32 clamp0(int32 v) { - return (v < 0) ? 0 : v; -} - -static __inline int32 clamp255(int32 v) { - return (v > 255) ? 255 : v; -} - -static __inline uint32 Clamp(int32 val) { - int v = clamp0(val); - return (uint32)(clamp255(v)); -} - -static __inline uint32 Abs(int32 v) { - return (v < 0) ? -v : v; -} -#endif // USE_BRANCHLESS - -#ifdef LIBYUV_LITTLE_ENDIAN -#define WRITEWORD(p, v) *(uint32*)(p) = v -#else -static inline void WRITEWORD(uint8* p, uint32 v) { - p[0] = (uint8)(v & 255); - p[1] = (uint8)((v >> 8) & 255); - p[2] = (uint8)((v >> 16) & 255); - p[3] = (uint8)((v >> 24) & 255); -} -#endif - -void RGB24ToARGBRow_C(const uint8* src_rgb24, uint8* dst_argb, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_rgb24[0]; - uint8 g = src_rgb24[1]; - uint8 r = src_rgb24[2]; - dst_argb[0] = b; - dst_argb[1] = g; - dst_argb[2] = r; - dst_argb[3] = 255u; - dst_argb += 4; - src_rgb24 += 3; - } -} - -void RAWToARGBRow_C(const uint8* src_raw, uint8* dst_argb, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 r = src_raw[0]; - uint8 g = src_raw[1]; - uint8 b = src_raw[2]; - dst_argb[0] = b; - dst_argb[1] = g; - dst_argb[2] = r; - dst_argb[3] = 255u; - dst_argb += 4; - src_raw += 3; - } -} - -void RAWToRGB24Row_C(const uint8* src_raw, uint8* dst_rgb24, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 r = src_raw[0]; - uint8 g = src_raw[1]; - uint8 b = src_raw[2]; - dst_rgb24[0] = b; - dst_rgb24[1] = g; - dst_rgb24[2] = r; - dst_rgb24 += 3; - src_raw += 3; - } -} - -void RGB565ToARGBRow_C(const uint8* src_rgb565, uint8* dst_argb, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_rgb565[0] & 0x1f; - uint8 g = (src_rgb565[0] >> 5) | ((src_rgb565[1] & 0x07) << 3); - uint8 r = src_rgb565[1] >> 3; - dst_argb[0] = (b << 3) | (b >> 2); - dst_argb[1] = (g << 2) | (g >> 4); - dst_argb[2] = (r << 3) | (r >> 2); - dst_argb[3] = 255u; - dst_argb += 4; - src_rgb565 += 2; - } -} - -void ARGB1555ToARGBRow_C(const uint8* src_argb1555, uint8* dst_argb, - int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_argb1555[0] & 0x1f; - uint8 g = (src_argb1555[0] >> 5) | ((src_argb1555[1] & 0x03) << 3); - uint8 r = (src_argb1555[1] & 0x7c) >> 2; - uint8 a = src_argb1555[1] >> 7; - dst_argb[0] = (b << 3) | (b >> 2); - dst_argb[1] = (g << 3) | (g >> 2); - dst_argb[2] = (r << 3) | (r >> 2); - dst_argb[3] = -a; - dst_argb += 4; - src_argb1555 += 2; - } -} - -void ARGB4444ToARGBRow_C(const uint8* src_argb4444, uint8* dst_argb, - int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_argb4444[0] & 0x0f; - uint8 g = src_argb4444[0] >> 4; - uint8 r = src_argb4444[1] & 0x0f; - uint8 a = src_argb4444[1] >> 4; - dst_argb[0] = (b << 4) | b; - dst_argb[1] = (g << 4) | g; - dst_argb[2] = (r << 4) | r; - dst_argb[3] = (a << 4) | a; - dst_argb += 4; - src_argb4444 += 2; - } -} - -void ARGBToRGB24Row_C(const uint8* src_argb, uint8* dst_rgb, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_argb[0]; - uint8 g = src_argb[1]; - uint8 r = src_argb[2]; - dst_rgb[0] = b; - dst_rgb[1] = g; - dst_rgb[2] = r; - dst_rgb += 3; - src_argb += 4; - } -} - -void ARGBToRAWRow_C(const uint8* src_argb, uint8* dst_rgb, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_argb[0]; - uint8 g = src_argb[1]; - uint8 r = src_argb[2]; - dst_rgb[0] = r; - dst_rgb[1] = g; - dst_rgb[2] = b; - dst_rgb += 3; - src_argb += 4; - } -} - -void ARGBToRGB565Row_C(const uint8* src_argb, uint8* dst_rgb, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 b0 = src_argb[0] >> 3; - uint8 g0 = src_argb[1] >> 2; - uint8 r0 = src_argb[2] >> 3; - uint8 b1 = src_argb[4] >> 3; - uint8 g1 = src_argb[5] >> 2; - uint8 r1 = src_argb[6] >> 3; - WRITEWORD(dst_rgb, b0 | (g0 << 5) | (r0 << 11) | - (b1 << 16) | (g1 << 21) | (r1 << 27)); - dst_rgb += 4; - src_argb += 8; - } - if (width & 1) { - uint8 b0 = src_argb[0] >> 3; - uint8 g0 = src_argb[1] >> 2; - uint8 r0 = src_argb[2] >> 3; - *(uint16*)(dst_rgb) = b0 | (g0 << 5) | (r0 << 11); - } -} - -// dither4 is a row of 4 values from 4x4 dither matrix. -// The 4x4 matrix contains values to increase RGB. When converting to -// fewer bits (565) this provides an ordered dither. -// The order in the 4x4 matrix in first byte is upper left. -// The 4 values are passed as an int, then referenced as an array, so -// endian will not affect order of the original matrix. But the dither4 -// will containing the first pixel in the lower byte for little endian -// or the upper byte for big endian. -void ARGBToRGB565DitherRow_C(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - int dither0 = ((const unsigned char*)(&dither4))[x & 3]; - int dither1 = ((const unsigned char*)(&dither4))[(x + 1) & 3]; - uint8 b0 = clamp255(src_argb[0] + dither0) >> 3; - uint8 g0 = clamp255(src_argb[1] + dither0) >> 2; - uint8 r0 = clamp255(src_argb[2] + dither0) >> 3; - uint8 b1 = clamp255(src_argb[4] + dither1) >> 3; - uint8 g1 = clamp255(src_argb[5] + dither1) >> 2; - uint8 r1 = clamp255(src_argb[6] + dither1) >> 3; - WRITEWORD(dst_rgb, b0 | (g0 << 5) | (r0 << 11) | - (b1 << 16) | (g1 << 21) | (r1 << 27)); - dst_rgb += 4; - src_argb += 8; - } - if (width & 1) { - int dither0 = ((const unsigned char*)(&dither4))[(width - 1) & 3]; - uint8 b0 = clamp255(src_argb[0] + dither0) >> 3; - uint8 g0 = clamp255(src_argb[1] + dither0) >> 2; - uint8 r0 = clamp255(src_argb[2] + dither0) >> 3; - *(uint16*)(dst_rgb) = b0 | (g0 << 5) | (r0 << 11); - } -} - -void ARGBToARGB1555Row_C(const uint8* src_argb, uint8* dst_rgb, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 b0 = src_argb[0] >> 3; - uint8 g0 = src_argb[1] >> 3; - uint8 r0 = src_argb[2] >> 3; - uint8 a0 = src_argb[3] >> 7; - uint8 b1 = src_argb[4] >> 3; - uint8 g1 = src_argb[5] >> 3; - uint8 r1 = src_argb[6] >> 3; - uint8 a1 = src_argb[7] >> 7; - *(uint32*)(dst_rgb) = - b0 | (g0 << 5) | (r0 << 10) | (a0 << 15) | - (b1 << 16) | (g1 << 21) | (r1 << 26) | (a1 << 31); - dst_rgb += 4; - src_argb += 8; - } - if (width & 1) { - uint8 b0 = src_argb[0] >> 3; - uint8 g0 = src_argb[1] >> 3; - uint8 r0 = src_argb[2] >> 3; - uint8 a0 = src_argb[3] >> 7; - *(uint16*)(dst_rgb) = - b0 | (g0 << 5) | (r0 << 10) | (a0 << 15); - } -} - -void ARGBToARGB4444Row_C(const uint8* src_argb, uint8* dst_rgb, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 b0 = src_argb[0] >> 4; - uint8 g0 = src_argb[1] >> 4; - uint8 r0 = src_argb[2] >> 4; - uint8 a0 = src_argb[3] >> 4; - uint8 b1 = src_argb[4] >> 4; - uint8 g1 = src_argb[5] >> 4; - uint8 r1 = src_argb[6] >> 4; - uint8 a1 = src_argb[7] >> 4; - *(uint32*)(dst_rgb) = - b0 | (g0 << 4) | (r0 << 8) | (a0 << 12) | - (b1 << 16) | (g1 << 20) | (r1 << 24) | (a1 << 28); - dst_rgb += 4; - src_argb += 8; - } - if (width & 1) { - uint8 b0 = src_argb[0] >> 4; - uint8 g0 = src_argb[1] >> 4; - uint8 r0 = src_argb[2] >> 4; - uint8 a0 = src_argb[3] >> 4; - *(uint16*)(dst_rgb) = - b0 | (g0 << 4) | (r0 << 8) | (a0 << 12); - } -} - -static __inline int RGBToY(uint8 r, uint8 g, uint8 b) { - return (66 * r + 129 * g + 25 * b + 0x1080) >> 8; -} - -static __inline int RGBToU(uint8 r, uint8 g, uint8 b) { - return (112 * b - 74 * g - 38 * r + 0x8080) >> 8; -} -static __inline int RGBToV(uint8 r, uint8 g, uint8 b) { - return (112 * r - 94 * g - 18 * b + 0x8080) >> 8; -} - -#define MAKEROWY(NAME, R, G, B, BPP) \ -void NAME ## ToYRow_C(const uint8* src_argb0, uint8* dst_y, int width) { \ - int x; \ - for (x = 0; x < width; ++x) { \ - dst_y[0] = RGBToY(src_argb0[R], src_argb0[G], src_argb0[B]); \ - src_argb0 += BPP; \ - dst_y += 1; \ - } \ -} \ -void NAME ## ToUVRow_C(const uint8* src_rgb0, int src_stride_rgb, \ - uint8* dst_u, uint8* dst_v, int width) { \ - const uint8* src_rgb1 = src_rgb0 + src_stride_rgb; \ - int x; \ - for (x = 0; x < width - 1; x += 2) { \ - uint8 ab = (src_rgb0[B] + src_rgb0[B + BPP] + \ - src_rgb1[B] + src_rgb1[B + BPP]) >> 2; \ - uint8 ag = (src_rgb0[G] + src_rgb0[G + BPP] + \ - src_rgb1[G] + src_rgb1[G + BPP]) >> 2; \ - uint8 ar = (src_rgb0[R] + src_rgb0[R + BPP] + \ - src_rgb1[R] + src_rgb1[R + BPP]) >> 2; \ - dst_u[0] = RGBToU(ar, ag, ab); \ - dst_v[0] = RGBToV(ar, ag, ab); \ - src_rgb0 += BPP * 2; \ - src_rgb1 += BPP * 2; \ - dst_u += 1; \ - dst_v += 1; \ - } \ - if (width & 1) { \ - uint8 ab = (src_rgb0[B] + src_rgb1[B]) >> 1; \ - uint8 ag = (src_rgb0[G] + src_rgb1[G]) >> 1; \ - uint8 ar = (src_rgb0[R] + src_rgb1[R]) >> 1; \ - dst_u[0] = RGBToU(ar, ag, ab); \ - dst_v[0] = RGBToV(ar, ag, ab); \ - } \ -} - -MAKEROWY(ARGB, 2, 1, 0, 4) -MAKEROWY(BGRA, 1, 2, 3, 4) -MAKEROWY(ABGR, 0, 1, 2, 4) -MAKEROWY(RGBA, 3, 2, 1, 4) -MAKEROWY(RGB24, 2, 1, 0, 3) -MAKEROWY(RAW, 0, 1, 2, 3) -#undef MAKEROWY - -// JPeg uses a variation on BT.601-1 full range -// y = 0.29900 * r + 0.58700 * g + 0.11400 * b -// u = -0.16874 * r - 0.33126 * g + 0.50000 * b + center -// v = 0.50000 * r - 0.41869 * g - 0.08131 * b + center -// BT.601 Mpeg range uses: -// b 0.1016 * 255 = 25.908 = 25 -// g 0.5078 * 255 = 129.489 = 129 -// r 0.2578 * 255 = 65.739 = 66 -// JPeg 8 bit Y (not used): -// b 0.11400 * 256 = 29.184 = 29 -// g 0.58700 * 256 = 150.272 = 150 -// r 0.29900 * 256 = 76.544 = 77 -// JPeg 7 bit Y: -// b 0.11400 * 128 = 14.592 = 15 -// g 0.58700 * 128 = 75.136 = 75 -// r 0.29900 * 128 = 38.272 = 38 -// JPeg 8 bit U: -// b 0.50000 * 255 = 127.5 = 127 -// g -0.33126 * 255 = -84.4713 = -84 -// r -0.16874 * 255 = -43.0287 = -43 -// JPeg 8 bit V: -// b -0.08131 * 255 = -20.73405 = -20 -// g -0.41869 * 255 = -106.76595 = -107 -// r 0.50000 * 255 = 127.5 = 127 - -static __inline int RGBToYJ(uint8 r, uint8 g, uint8 b) { - return (38 * r + 75 * g + 15 * b + 64) >> 7; -} - -static __inline int RGBToUJ(uint8 r, uint8 g, uint8 b) { - return (127 * b - 84 * g - 43 * r + 0x8080) >> 8; -} -static __inline int RGBToVJ(uint8 r, uint8 g, uint8 b) { - return (127 * r - 107 * g - 20 * b + 0x8080) >> 8; -} - -#define AVGB(a, b) (((a) + (b) + 1) >> 1) - -#define MAKEROWYJ(NAME, R, G, B, BPP) \ -void NAME ## ToYJRow_C(const uint8* src_argb0, uint8* dst_y, int width) { \ - int x; \ - for (x = 0; x < width; ++x) { \ - dst_y[0] = RGBToYJ(src_argb0[R], src_argb0[G], src_argb0[B]); \ - src_argb0 += BPP; \ - dst_y += 1; \ - } \ -} \ -void NAME ## ToUVJRow_C(const uint8* src_rgb0, int src_stride_rgb, \ - uint8* dst_u, uint8* dst_v, int width) { \ - const uint8* src_rgb1 = src_rgb0 + src_stride_rgb; \ - int x; \ - for (x = 0; x < width - 1; x += 2) { \ - uint8 ab = AVGB(AVGB(src_rgb0[B], src_rgb1[B]), \ - AVGB(src_rgb0[B + BPP], src_rgb1[B + BPP])); \ - uint8 ag = AVGB(AVGB(src_rgb0[G], src_rgb1[G]), \ - AVGB(src_rgb0[G + BPP], src_rgb1[G + BPP])); \ - uint8 ar = AVGB(AVGB(src_rgb0[R], src_rgb1[R]), \ - AVGB(src_rgb0[R + BPP], src_rgb1[R + BPP])); \ - dst_u[0] = RGBToUJ(ar, ag, ab); \ - dst_v[0] = RGBToVJ(ar, ag, ab); \ - src_rgb0 += BPP * 2; \ - src_rgb1 += BPP * 2; \ - dst_u += 1; \ - dst_v += 1; \ - } \ - if (width & 1) { \ - uint8 ab = AVGB(src_rgb0[B], src_rgb1[B]); \ - uint8 ag = AVGB(src_rgb0[G], src_rgb1[G]); \ - uint8 ar = AVGB(src_rgb0[R], src_rgb1[R]); \ - dst_u[0] = RGBToUJ(ar, ag, ab); \ - dst_v[0] = RGBToVJ(ar, ag, ab); \ - } \ -} - -MAKEROWYJ(ARGB, 2, 1, 0, 4) -#undef MAKEROWYJ - -void RGB565ToYRow_C(const uint8* src_rgb565, uint8* dst_y, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_rgb565[0] & 0x1f; - uint8 g = (src_rgb565[0] >> 5) | ((src_rgb565[1] & 0x07) << 3); - uint8 r = src_rgb565[1] >> 3; - b = (b << 3) | (b >> 2); - g = (g << 2) | (g >> 4); - r = (r << 3) | (r >> 2); - dst_y[0] = RGBToY(r, g, b); - src_rgb565 += 2; - dst_y += 1; - } -} - -void ARGB1555ToYRow_C(const uint8* src_argb1555, uint8* dst_y, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_argb1555[0] & 0x1f; - uint8 g = (src_argb1555[0] >> 5) | ((src_argb1555[1] & 0x03) << 3); - uint8 r = (src_argb1555[1] & 0x7c) >> 2; - b = (b << 3) | (b >> 2); - g = (g << 3) | (g >> 2); - r = (r << 3) | (r >> 2); - dst_y[0] = RGBToY(r, g, b); - src_argb1555 += 2; - dst_y += 1; - } -} - -void ARGB4444ToYRow_C(const uint8* src_argb4444, uint8* dst_y, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 b = src_argb4444[0] & 0x0f; - uint8 g = src_argb4444[0] >> 4; - uint8 r = src_argb4444[1] & 0x0f; - b = (b << 4) | b; - g = (g << 4) | g; - r = (r << 4) | r; - dst_y[0] = RGBToY(r, g, b); - src_argb4444 += 2; - dst_y += 1; - } -} - -void RGB565ToUVRow_C(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* next_rgb565 = src_rgb565 + src_stride_rgb565; - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 b0 = src_rgb565[0] & 0x1f; - uint8 g0 = (src_rgb565[0] >> 5) | ((src_rgb565[1] & 0x07) << 3); - uint8 r0 = src_rgb565[1] >> 3; - uint8 b1 = src_rgb565[2] & 0x1f; - uint8 g1 = (src_rgb565[2] >> 5) | ((src_rgb565[3] & 0x07) << 3); - uint8 r1 = src_rgb565[3] >> 3; - uint8 b2 = next_rgb565[0] & 0x1f; - uint8 g2 = (next_rgb565[0] >> 5) | ((next_rgb565[1] & 0x07) << 3); - uint8 r2 = next_rgb565[1] >> 3; - uint8 b3 = next_rgb565[2] & 0x1f; - uint8 g3 = (next_rgb565[2] >> 5) | ((next_rgb565[3] & 0x07) << 3); - uint8 r3 = next_rgb565[3] >> 3; - uint8 b = (b0 + b1 + b2 + b3); // 565 * 4 = 787. - uint8 g = (g0 + g1 + g2 + g3); - uint8 r = (r0 + r1 + r2 + r3); - b = (b << 1) | (b >> 6); // 787 -> 888. - r = (r << 1) | (r >> 6); - dst_u[0] = RGBToU(r, g, b); - dst_v[0] = RGBToV(r, g, b); - src_rgb565 += 4; - next_rgb565 += 4; - dst_u += 1; - dst_v += 1; - } - if (width & 1) { - uint8 b0 = src_rgb565[0] & 0x1f; - uint8 g0 = (src_rgb565[0] >> 5) | ((src_rgb565[1] & 0x07) << 3); - uint8 r0 = src_rgb565[1] >> 3; - uint8 b2 = next_rgb565[0] & 0x1f; - uint8 g2 = (next_rgb565[0] >> 5) | ((next_rgb565[1] & 0x07) << 3); - uint8 r2 = next_rgb565[1] >> 3; - uint8 b = (b0 + b2); // 565 * 2 = 676. - uint8 g = (g0 + g2); - uint8 r = (r0 + r2); - b = (b << 2) | (b >> 4); // 676 -> 888 - g = (g << 1) | (g >> 6); - r = (r << 2) | (r >> 4); - dst_u[0] = RGBToU(r, g, b); - dst_v[0] = RGBToV(r, g, b); - } -} - -void ARGB1555ToUVRow_C(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* next_argb1555 = src_argb1555 + src_stride_argb1555; - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 b0 = src_argb1555[0] & 0x1f; - uint8 g0 = (src_argb1555[0] >> 5) | ((src_argb1555[1] & 0x03) << 3); - uint8 r0 = (src_argb1555[1] & 0x7c) >> 2; - uint8 b1 = src_argb1555[2] & 0x1f; - uint8 g1 = (src_argb1555[2] >> 5) | ((src_argb1555[3] & 0x03) << 3); - uint8 r1 = (src_argb1555[3] & 0x7c) >> 2; - uint8 b2 = next_argb1555[0] & 0x1f; - uint8 g2 = (next_argb1555[0] >> 5) | ((next_argb1555[1] & 0x03) << 3); - uint8 r2 = (next_argb1555[1] & 0x7c) >> 2; - uint8 b3 = next_argb1555[2] & 0x1f; - uint8 g3 = (next_argb1555[2] >> 5) | ((next_argb1555[3] & 0x03) << 3); - uint8 r3 = (next_argb1555[3] & 0x7c) >> 2; - uint8 b = (b0 + b1 + b2 + b3); // 555 * 4 = 777. - uint8 g = (g0 + g1 + g2 + g3); - uint8 r = (r0 + r1 + r2 + r3); - b = (b << 1) | (b >> 6); // 777 -> 888. - g = (g << 1) | (g >> 6); - r = (r << 1) | (r >> 6); - dst_u[0] = RGBToU(r, g, b); - dst_v[0] = RGBToV(r, g, b); - src_argb1555 += 4; - next_argb1555 += 4; - dst_u += 1; - dst_v += 1; - } - if (width & 1) { - uint8 b0 = src_argb1555[0] & 0x1f; - uint8 g0 = (src_argb1555[0] >> 5) | ((src_argb1555[1] & 0x03) << 3); - uint8 r0 = (src_argb1555[1] & 0x7c) >> 2; - uint8 b2 = next_argb1555[0] & 0x1f; - uint8 g2 = (next_argb1555[0] >> 5) | ((next_argb1555[1] & 0x03) << 3); - uint8 r2 = next_argb1555[1] >> 3; - uint8 b = (b0 + b2); // 555 * 2 = 666. - uint8 g = (g0 + g2); - uint8 r = (r0 + r2); - b = (b << 2) | (b >> 4); // 666 -> 888. - g = (g << 2) | (g >> 4); - r = (r << 2) | (r >> 4); - dst_u[0] = RGBToU(r, g, b); - dst_v[0] = RGBToV(r, g, b); - } -} - -void ARGB4444ToUVRow_C(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* next_argb4444 = src_argb4444 + src_stride_argb4444; - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 b0 = src_argb4444[0] & 0x0f; - uint8 g0 = src_argb4444[0] >> 4; - uint8 r0 = src_argb4444[1] & 0x0f; - uint8 b1 = src_argb4444[2] & 0x0f; - uint8 g1 = src_argb4444[2] >> 4; - uint8 r1 = src_argb4444[3] & 0x0f; - uint8 b2 = next_argb4444[0] & 0x0f; - uint8 g2 = next_argb4444[0] >> 4; - uint8 r2 = next_argb4444[1] & 0x0f; - uint8 b3 = next_argb4444[2] & 0x0f; - uint8 g3 = next_argb4444[2] >> 4; - uint8 r3 = next_argb4444[3] & 0x0f; - uint8 b = (b0 + b1 + b2 + b3); // 444 * 4 = 666. - uint8 g = (g0 + g1 + g2 + g3); - uint8 r = (r0 + r1 + r2 + r3); - b = (b << 2) | (b >> 4); // 666 -> 888. - g = (g << 2) | (g >> 4); - r = (r << 2) | (r >> 4); - dst_u[0] = RGBToU(r, g, b); - dst_v[0] = RGBToV(r, g, b); - src_argb4444 += 4; - next_argb4444 += 4; - dst_u += 1; - dst_v += 1; - } - if (width & 1) { - uint8 b0 = src_argb4444[0] & 0x0f; - uint8 g0 = src_argb4444[0] >> 4; - uint8 r0 = src_argb4444[1] & 0x0f; - uint8 b2 = next_argb4444[0] & 0x0f; - uint8 g2 = next_argb4444[0] >> 4; - uint8 r2 = next_argb4444[1] & 0x0f; - uint8 b = (b0 + b2); // 444 * 2 = 555. - uint8 g = (g0 + g2); - uint8 r = (r0 + r2); - b = (b << 3) | (b >> 2); // 555 -> 888. - g = (g << 3) | (g >> 2); - r = (r << 3) | (r >> 2); - dst_u[0] = RGBToU(r, g, b); - dst_v[0] = RGBToV(r, g, b); - } -} - -void ARGBToUV444Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 ab = src_argb[0]; - uint8 ag = src_argb[1]; - uint8 ar = src_argb[2]; - dst_u[0] = RGBToU(ar, ag, ab); - dst_v[0] = RGBToV(ar, ag, ab); - src_argb += 4; - dst_u += 1; - dst_v += 1; - } -} - -void ARGBToUV411Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width) { - int x; - for (x = 0; x < width - 3; x += 4) { - uint8 ab = (src_argb[0] + src_argb[4] + src_argb[8] + src_argb[12]) >> 2; - uint8 ag = (src_argb[1] + src_argb[5] + src_argb[9] + src_argb[13]) >> 2; - uint8 ar = (src_argb[2] + src_argb[6] + src_argb[10] + src_argb[14]) >> 2; - dst_u[0] = RGBToU(ar, ag, ab); - dst_v[0] = RGBToV(ar, ag, ab); - src_argb += 16; - dst_u += 1; - dst_v += 1; - } - // Odd width handling mimics 'any' function which replicates last pixel. - if ((width & 3) == 3) { - uint8 ab = (src_argb[0] + src_argb[4] + src_argb[8] + src_argb[8]) >> 2; - uint8 ag = (src_argb[1] + src_argb[5] + src_argb[9] + src_argb[9]) >> 2; - uint8 ar = (src_argb[2] + src_argb[6] + src_argb[10] + src_argb[10]) >> 2; - dst_u[0] = RGBToU(ar, ag, ab); - dst_v[0] = RGBToV(ar, ag, ab); - } else if ((width & 3) == 2) { - uint8 ab = (src_argb[0] + src_argb[4]) >> 1; - uint8 ag = (src_argb[1] + src_argb[5]) >> 1; - uint8 ar = (src_argb[2] + src_argb[6]) >> 1; - dst_u[0] = RGBToU(ar, ag, ab); - dst_v[0] = RGBToV(ar, ag, ab); - } else if ((width & 3) == 1) { - uint8 ab = src_argb[0]; - uint8 ag = src_argb[1]; - uint8 ar = src_argb[2]; - dst_u[0] = RGBToU(ar, ag, ab); - dst_v[0] = RGBToV(ar, ag, ab); - } -} - -void ARGBGrayRow_C(const uint8* src_argb, uint8* dst_argb, int width) { - int x; - for (x = 0; x < width; ++x) { - uint8 y = RGBToYJ(src_argb[2], src_argb[1], src_argb[0]); - dst_argb[2] = dst_argb[1] = dst_argb[0] = y; - dst_argb[3] = src_argb[3]; - dst_argb += 4; - src_argb += 4; - } -} - -// Convert a row of image to Sepia tone. -void ARGBSepiaRow_C(uint8* dst_argb, int width) { - int x; - for (x = 0; x < width; ++x) { - int b = dst_argb[0]; - int g = dst_argb[1]; - int r = dst_argb[2]; - int sb = (b * 17 + g * 68 + r * 35) >> 7; - int sg = (b * 22 + g * 88 + r * 45) >> 7; - int sr = (b * 24 + g * 98 + r * 50) >> 7; - // b does not over flow. a is preserved from original. - dst_argb[0] = sb; - dst_argb[1] = clamp255(sg); - dst_argb[2] = clamp255(sr); - dst_argb += 4; - } -} - -// Apply color matrix to a row of image. Matrix is signed. -// TODO(fbarchard): Consider adding rounding (+32). -void ARGBColorMatrixRow_C(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width) { - int x; - for (x = 0; x < width; ++x) { - int b = src_argb[0]; - int g = src_argb[1]; - int r = src_argb[2]; - int a = src_argb[3]; - int sb = (b * matrix_argb[0] + g * matrix_argb[1] + - r * matrix_argb[2] + a * matrix_argb[3]) >> 6; - int sg = (b * matrix_argb[4] + g * matrix_argb[5] + - r * matrix_argb[6] + a * matrix_argb[7]) >> 6; - int sr = (b * matrix_argb[8] + g * matrix_argb[9] + - r * matrix_argb[10] + a * matrix_argb[11]) >> 6; - int sa = (b * matrix_argb[12] + g * matrix_argb[13] + - r * matrix_argb[14] + a * matrix_argb[15]) >> 6; - dst_argb[0] = Clamp(sb); - dst_argb[1] = Clamp(sg); - dst_argb[2] = Clamp(sr); - dst_argb[3] = Clamp(sa); - src_argb += 4; - dst_argb += 4; - } -} - -// Apply color table to a row of image. -void ARGBColorTableRow_C(uint8* dst_argb, const uint8* table_argb, int width) { - int x; - for (x = 0; x < width; ++x) { - int b = dst_argb[0]; - int g = dst_argb[1]; - int r = dst_argb[2]; - int a = dst_argb[3]; - dst_argb[0] = table_argb[b * 4 + 0]; - dst_argb[1] = table_argb[g * 4 + 1]; - dst_argb[2] = table_argb[r * 4 + 2]; - dst_argb[3] = table_argb[a * 4 + 3]; - dst_argb += 4; - } -} - -// Apply color table to a row of image. -void RGBColorTableRow_C(uint8* dst_argb, const uint8* table_argb, int width) { - int x; - for (x = 0; x < width; ++x) { - int b = dst_argb[0]; - int g = dst_argb[1]; - int r = dst_argb[2]; - dst_argb[0] = table_argb[b * 4 + 0]; - dst_argb[1] = table_argb[g * 4 + 1]; - dst_argb[2] = table_argb[r * 4 + 2]; - dst_argb += 4; - } -} - -void ARGBQuantizeRow_C(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width) { - int x; - for (x = 0; x < width; ++x) { - int b = dst_argb[0]; - int g = dst_argb[1]; - int r = dst_argb[2]; - dst_argb[0] = (b * scale >> 16) * interval_size + interval_offset; - dst_argb[1] = (g * scale >> 16) * interval_size + interval_offset; - dst_argb[2] = (r * scale >> 16) * interval_size + interval_offset; - dst_argb += 4; - } -} - -#define REPEAT8(v) (v) | ((v) << 8) -#define SHADE(f, v) v * f >> 24 - -void ARGBShadeRow_C(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value) { - const uint32 b_scale = REPEAT8(value & 0xff); - const uint32 g_scale = REPEAT8((value >> 8) & 0xff); - const uint32 r_scale = REPEAT8((value >> 16) & 0xff); - const uint32 a_scale = REPEAT8(value >> 24); - - int i; - for (i = 0; i < width; ++i) { - const uint32 b = REPEAT8(src_argb[0]); - const uint32 g = REPEAT8(src_argb[1]); - const uint32 r = REPEAT8(src_argb[2]); - const uint32 a = REPEAT8(src_argb[3]); - dst_argb[0] = SHADE(b, b_scale); - dst_argb[1] = SHADE(g, g_scale); - dst_argb[2] = SHADE(r, r_scale); - dst_argb[3] = SHADE(a, a_scale); - src_argb += 4; - dst_argb += 4; - } -} -#undef REPEAT8 -#undef SHADE - -#define REPEAT8(v) (v) | ((v) << 8) -#define SHADE(f, v) v * f >> 16 - -void ARGBMultiplyRow_C(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - int i; - for (i = 0; i < width; ++i) { - const uint32 b = REPEAT8(src_argb0[0]); - const uint32 g = REPEAT8(src_argb0[1]); - const uint32 r = REPEAT8(src_argb0[2]); - const uint32 a = REPEAT8(src_argb0[3]); - const uint32 b_scale = src_argb1[0]; - const uint32 g_scale = src_argb1[1]; - const uint32 r_scale = src_argb1[2]; - const uint32 a_scale = src_argb1[3]; - dst_argb[0] = SHADE(b, b_scale); - dst_argb[1] = SHADE(g, g_scale); - dst_argb[2] = SHADE(r, r_scale); - dst_argb[3] = SHADE(a, a_scale); - src_argb0 += 4; - src_argb1 += 4; - dst_argb += 4; - } -} -#undef REPEAT8 -#undef SHADE - -#define SHADE(f, v) clamp255(v + f) - -void ARGBAddRow_C(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - int i; - for (i = 0; i < width; ++i) { - const int b = src_argb0[0]; - const int g = src_argb0[1]; - const int r = src_argb0[2]; - const int a = src_argb0[3]; - const int b_add = src_argb1[0]; - const int g_add = src_argb1[1]; - const int r_add = src_argb1[2]; - const int a_add = src_argb1[3]; - dst_argb[0] = SHADE(b, b_add); - dst_argb[1] = SHADE(g, g_add); - dst_argb[2] = SHADE(r, r_add); - dst_argb[3] = SHADE(a, a_add); - src_argb0 += 4; - src_argb1 += 4; - dst_argb += 4; - } -} -#undef SHADE - -#define SHADE(f, v) clamp0(f - v) - -void ARGBSubtractRow_C(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - int i; - for (i = 0; i < width; ++i) { - const int b = src_argb0[0]; - const int g = src_argb0[1]; - const int r = src_argb0[2]; - const int a = src_argb0[3]; - const int b_sub = src_argb1[0]; - const int g_sub = src_argb1[1]; - const int r_sub = src_argb1[2]; - const int a_sub = src_argb1[3]; - dst_argb[0] = SHADE(b, b_sub); - dst_argb[1] = SHADE(g, g_sub); - dst_argb[2] = SHADE(r, r_sub); - dst_argb[3] = SHADE(a, a_sub); - src_argb0 += 4; - src_argb1 += 4; - dst_argb += 4; - } -} -#undef SHADE - -// Sobel functions which mimics SSSE3. -void SobelXRow_C(const uint8* src_y0, const uint8* src_y1, const uint8* src_y2, - uint8* dst_sobelx, int width) { - int i; - for (i = 0; i < width; ++i) { - int a = src_y0[i]; - int b = src_y1[i]; - int c = src_y2[i]; - int a_sub = src_y0[i + 2]; - int b_sub = src_y1[i + 2]; - int c_sub = src_y2[i + 2]; - int a_diff = a - a_sub; - int b_diff = b - b_sub; - int c_diff = c - c_sub; - int sobel = Abs(a_diff + b_diff * 2 + c_diff); - dst_sobelx[i] = (uint8)(clamp255(sobel)); - } -} - -void SobelYRow_C(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width) { - int i; - for (i = 0; i < width; ++i) { - int a = src_y0[i + 0]; - int b = src_y0[i + 1]; - int c = src_y0[i + 2]; - int a_sub = src_y1[i + 0]; - int b_sub = src_y1[i + 1]; - int c_sub = src_y1[i + 2]; - int a_diff = a - a_sub; - int b_diff = b - b_sub; - int c_diff = c - c_sub; - int sobel = Abs(a_diff + b_diff * 2 + c_diff); - dst_sobely[i] = (uint8)(clamp255(sobel)); - } -} - -void SobelRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - int i; - for (i = 0; i < width; ++i) { - int r = src_sobelx[i]; - int b = src_sobely[i]; - int s = clamp255(r + b); - dst_argb[0] = (uint8)(s); - dst_argb[1] = (uint8)(s); - dst_argb[2] = (uint8)(s); - dst_argb[3] = (uint8)(255u); - dst_argb += 4; - } -} - -void SobelToPlaneRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width) { - int i; - for (i = 0; i < width; ++i) { - int r = src_sobelx[i]; - int b = src_sobely[i]; - int s = clamp255(r + b); - dst_y[i] = (uint8)(s); - } -} - -void SobelXYRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - int i; - for (i = 0; i < width; ++i) { - int r = src_sobelx[i]; - int b = src_sobely[i]; - int g = clamp255(r + b); - dst_argb[0] = (uint8)(b); - dst_argb[1] = (uint8)(g); - dst_argb[2] = (uint8)(r); - dst_argb[3] = (uint8)(255u); - dst_argb += 4; - } -} - -void J400ToARGBRow_C(const uint8* src_y, uint8* dst_argb, int width) { - // Copy a Y to RGB. - int x; - for (x = 0; x < width; ++x) { - uint8 y = src_y[0]; - dst_argb[2] = dst_argb[1] = dst_argb[0] = y; - dst_argb[3] = 255u; - dst_argb += 4; - ++src_y; - } -} - -// TODO(fbarchard): Unify these structures to be platform independent. -// TODO(fbarchard): Generate SIMD structures from float matrix. - -// BT.601 YUV to RGB reference -// R = (Y - 16) * 1.164 - V * -1.596 -// G = (Y - 16) * 1.164 - U * 0.391 - V * 0.813 -// B = (Y - 16) * 1.164 - U * -2.018 - -// Y contribution to R,G,B. Scale and bias. -#define YG 18997 /* round(1.164 * 64 * 256 * 256 / 257) */ -#define YGB -1160 /* 1.164 * 64 * -16 + 64 / 2 */ - -// U and V contributions to R,G,B. -#define UB -128 /* max(-128, round(-2.018 * 64)) */ -#define UG 25 /* round(0.391 * 64) */ -#define VG 52 /* round(0.813 * 64) */ -#define VR -102 /* round(-1.596 * 64) */ - -// Bias values to subtract 16 from Y and 128 from U and V. -#define BB (UB * 128 + YGB) -#define BG (UG * 128 + VG * 128 + YGB) -#define BR (VR * 128 + YGB) - -#if defined(__aarch64__) -const YuvConstants SIMD_ALIGNED(kYuvI601Constants) = { - { -UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR }, - { -UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR }, - { UG, VG, UG, VG, UG, VG, UG, VG }, - { UG, VG, UG, VG, UG, VG, UG, VG }, - { BB, BG, BR, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -const YuvConstants SIMD_ALIGNED(kYvuI601Constants) = { - { -VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB }, - { -VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB }, - { VG, UG, VG, UG, VG, UG, VG, UG }, - { VG, UG, VG, UG, VG, UG, VG, UG }, - { BR, BG, BB, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -#elif defined(__arm__) -const YuvConstants SIMD_ALIGNED(kYuvI601Constants) = { - { -UB, -UB, -UB, -UB, -VR, -VR, -VR, -VR, 0, 0, 0, 0, 0, 0, 0, 0 }, - { UG, UG, UG, UG, VG, VG, VG, VG, 0, 0, 0, 0, 0, 0, 0, 0 }, - { BB, BG, BR, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -const YuvConstants SIMD_ALIGNED(kYvuI601Constants) = { - { -VR, -VR, -VR, -VR, -UB, -UB, -UB, -UB, 0, 0, 0, 0, 0, 0, 0, 0 }, - { VG, VG, VG, VG, UG, UG, UG, UG, 0, 0, 0, 0, 0, 0, 0, 0 }, - { BR, BG, BB, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -#else -const YuvConstants SIMD_ALIGNED(kYuvI601Constants) = { - { UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, - UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0 }, - { UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, - UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG }, - { 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, - 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR }, - { BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB }, - { BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG }, - { BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR }, - { YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG } -}; -const YuvConstants SIMD_ALIGNED(kYvuI601Constants) = { - { VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, - VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0 }, - { VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, - VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG }, - { 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, - 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB }, - { BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR }, - { BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG }, - { BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB }, - { YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG } -}; -#endif - -#undef BB -#undef BG -#undef BR -#undef YGB -#undef UB -#undef UG -#undef VG -#undef VR -#undef YG - -// JPEG YUV to RGB reference -// * R = Y - V * -1.40200 -// * G = Y - U * 0.34414 - V * 0.71414 -// * B = Y - U * -1.77200 - -// Y contribution to R,G,B. Scale and bias. -#define YG 16320 /* round(1.000 * 64 * 256 * 256 / 257) */ -#define YGB 32 /* 64 / 2 */ - -// U and V contributions to R,G,B. -#define UB -113 /* round(-1.77200 * 64) */ -#define UG 22 /* round(0.34414 * 64) */ -#define VG 46 /* round(0.71414 * 64) */ -#define VR -90 /* round(-1.40200 * 64) */ - -// Bias values to round, and subtract 128 from U and V. -#define BB (UB * 128 + YGB) -#define BG (UG * 128 + VG * 128 + YGB) -#define BR (VR * 128 + YGB) - -#if defined(__aarch64__) -const YuvConstants SIMD_ALIGNED(kYuvJPEGConstants) = { - { -UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR }, - { -UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR }, - { UG, VG, UG, VG, UG, VG, UG, VG }, - { UG, VG, UG, VG, UG, VG, UG, VG }, - { BB, BG, BR, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -const YuvConstants SIMD_ALIGNED(kYvuJPEGConstants) = { - { -VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB }, - { -VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB }, - { VG, UG, VG, UG, VG, UG, VG, UG }, - { VG, UG, VG, UG, VG, UG, VG, UG }, - { BR, BG, BB, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -#elif defined(__arm__) -const YuvConstants SIMD_ALIGNED(kYuvJPEGConstants) = { - { -UB, -UB, -UB, -UB, -VR, -VR, -VR, -VR, 0, 0, 0, 0, 0, 0, 0, 0 }, - { UG, UG, UG, UG, VG, VG, VG, VG, 0, 0, 0, 0, 0, 0, 0, 0 }, - { BB, BG, BR, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -const YuvConstants SIMD_ALIGNED(kYvuJPEGConstants) = { - { -VR, -VR, -VR, -VR, -UB, -UB, -UB, -UB, 0, 0, 0, 0, 0, 0, 0, 0 }, - { VG, VG, VG, VG, UG, UG, UG, UG, 0, 0, 0, 0, 0, 0, 0, 0 }, - { BR, BG, BB, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -#else -const YuvConstants SIMD_ALIGNED(kYuvJPEGConstants) = { - { UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, - UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0 }, - { UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, - UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG }, - { 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, - 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR }, - { BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB }, - { BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG }, - { BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR }, - { YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG } -}; -const YuvConstants SIMD_ALIGNED(kYvuJPEGConstants) = { - { VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, - VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0 }, - { VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, - VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG }, - { 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, - 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB }, - { BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR }, - { BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG }, - { BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB }, - { YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG } -}; -#endif - -#undef BB -#undef BG -#undef BR -#undef YGB -#undef UB -#undef UG -#undef VG -#undef VR -#undef YG - -// BT.709 YUV to RGB reference -// * R = Y - V * -1.28033 -// * G = Y - U * 0.21482 - V * 0.38059 -// * B = Y - U * -2.12798 - -// Y contribution to R,G,B. Scale and bias. -#define YG 16320 /* round(1.000 * 64 * 256 * 256 / 257) */ -#define YGB 32 /* 64 / 2 */ - -// TODO(fbarchard): Find way to express 2.12 instead of 2.0. -// U and V contributions to R,G,B. -#define UB -128 /* max(-128, round(-2.12798 * 64)) */ -#define UG 14 /* round(0.21482 * 64) */ -#define VG 24 /* round(0.38059 * 64) */ -#define VR -82 /* round(-1.28033 * 64) */ - -// Bias values to round, and subtract 128 from U and V. -#define BB (UB * 128 + YGB) -#define BG (UG * 128 + VG * 128 + YGB) -#define BR (VR * 128 + YGB) - -#if defined(__aarch64__) -const YuvConstants SIMD_ALIGNED(kYuvH709Constants) = { - { -UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR }, - { -UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR }, - { UG, VG, UG, VG, UG, VG, UG, VG }, - { UG, VG, UG, VG, UG, VG, UG, VG }, - { BB, BG, BR, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -const YuvConstants SIMD_ALIGNED(kYvuH709Constants) = { - { -VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB }, - { -VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB }, - { VG, UG, VG, UG, VG, UG, VG, UG }, - { VG, UG, VG, UG, VG, UG, VG, UG }, - { BR, BG, BB, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -#elif defined(__arm__) -const YuvConstants SIMD_ALIGNED(kYuvH709Constants) = { - { -UB, -UB, -UB, -UB, -VR, -VR, -VR, -VR, 0, 0, 0, 0, 0, 0, 0, 0 }, - { UG, UG, UG, UG, VG, VG, VG, VG, 0, 0, 0, 0, 0, 0, 0, 0 }, - { BB, BG, BR, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -const YuvConstants SIMD_ALIGNED(kYvuH709Constants) = { - { -VR, -VR, -VR, -VR, -UB, -UB, -UB, -UB, 0, 0, 0, 0, 0, 0, 0, 0 }, - { VG, VG, VG, VG, UG, UG, UG, UG, 0, 0, 0, 0, 0, 0, 0, 0 }, - { BR, BG, BB, 0, 0, 0, 0, 0 }, - { 0x0101 * YG, 0, 0, 0 } -}; -#else -const YuvConstants SIMD_ALIGNED(kYuvH709Constants) = { - { UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, - UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0 }, - { UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, - UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG }, - { 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, - 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR }, - { BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB }, - { BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG }, - { BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR }, - { YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG } -}; -const YuvConstants SIMD_ALIGNED(kYvuH709Constants) = { - { VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, - VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0 }, - { VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, - VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG }, - { 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, - 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB }, - { BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR }, - { BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG }, - { BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB }, - { YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG } -}; -#endif - -#undef BB -#undef BG -#undef BR -#undef YGB -#undef UB -#undef UG -#undef VG -#undef VR -#undef YG - -// C reference code that mimics the YUV assembly. -static __inline void YuvPixel(uint8 y, uint8 u, uint8 v, - uint8* b, uint8* g, uint8* r, - const struct YuvConstants* yuvconstants) { -#if defined(__aarch64__) - int ub = -yuvconstants->kUVToRB[0]; - int ug = yuvconstants->kUVToG[0]; - int vg = yuvconstants->kUVToG[1]; - int vr = -yuvconstants->kUVToRB[1]; - int bb = yuvconstants->kUVBiasBGR[0]; - int bg = yuvconstants->kUVBiasBGR[1]; - int br = yuvconstants->kUVBiasBGR[2]; - int yg = yuvconstants->kYToRgb[0] / 0x0101; -#elif defined(__arm__) - int ub = -yuvconstants->kUVToRB[0]; - int ug = yuvconstants->kUVToG[0]; - int vg = yuvconstants->kUVToG[4]; - int vr = -yuvconstants->kUVToRB[4]; - int bb = yuvconstants->kUVBiasBGR[0]; - int bg = yuvconstants->kUVBiasBGR[1]; - int br = yuvconstants->kUVBiasBGR[2]; - int yg = yuvconstants->kYToRgb[0] / 0x0101; -#else - int ub = yuvconstants->kUVToB[0]; - int ug = yuvconstants->kUVToG[0]; - int vg = yuvconstants->kUVToG[1]; - int vr = yuvconstants->kUVToR[1]; - int bb = yuvconstants->kUVBiasB[0]; - int bg = yuvconstants->kUVBiasG[0]; - int br = yuvconstants->kUVBiasR[0]; - int yg = yuvconstants->kYToRgb[0]; -#endif - - uint32 y1 = (uint32)(y * 0x0101 * yg) >> 16; - *b = Clamp((int32)(-(u * ub ) + y1 + bb) >> 6); - *g = Clamp((int32)(-(u * ug + v * vg) + y1 + bg) >> 6); - *r = Clamp((int32)(-( v * vr) + y1 + br) >> 6); -} - -// Y contribution to R,G,B. Scale and bias. -#define YG 18997 /* round(1.164 * 64 * 256 * 256 / 257) */ -#define YGB -1160 /* 1.164 * 64 * -16 + 64 / 2 */ - -// C reference code that mimics the YUV assembly. -static __inline void YPixel(uint8 y, uint8* b, uint8* g, uint8* r) { - uint32 y1 = (uint32)(y * 0x0101 * YG) >> 16; - *b = Clamp((int32)(y1 + YGB) >> 6); - *g = Clamp((int32)(y1 + YGB) >> 6); - *r = Clamp((int32)(y1 + YGB) >> 6); -} - -#undef YG -#undef YGB - -#if !defined(LIBYUV_DISABLE_NEON) && \ - (defined(__ARM_NEON__) || defined(__aarch64__) || defined(LIBYUV_NEON)) -// C mimic assembly. -// TODO(fbarchard): Remove subsampling from Neon. -void I444ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 u = (src_u[0] + src_u[1] + 1) >> 1; - uint8 v = (src_v[0] + src_v[1] + 1) >> 1; - YuvPixel(src_y[0], u, v, rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, - yuvconstants); - rgb_buf[3] = 255; - YuvPixel(src_y[1], u, v, rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, - yuvconstants); - rgb_buf[7] = 255; - src_y += 2; - src_u += 2; - src_v += 2; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - } -} -#else -void I444ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width; ++x) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - src_y += 1; - src_u += 1; - src_v += 1; - rgb_buf += 4; // Advance 1 pixel. - } -} -#endif - -// Also used for 420 -void I422ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - YuvPixel(src_y[1], src_u[0], src_v[0], - rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, yuvconstants); - rgb_buf[7] = 255; - src_y += 2; - src_u += 1; - src_v += 1; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - } -} - -void I422AlphaToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - const uint8* src_a, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = src_a[0]; - YuvPixel(src_y[1], src_u[0], src_v[0], - rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, yuvconstants); - rgb_buf[7] = src_a[1]; - src_y += 2; - src_u += 1; - src_v += 1; - src_a += 2; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = src_a[0]; - } -} - -void I422ToRGB24Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - YuvPixel(src_y[1], src_u[0], src_v[0], - rgb_buf + 3, rgb_buf + 4, rgb_buf + 5, yuvconstants); - src_y += 2; - src_u += 1; - src_v += 1; - rgb_buf += 6; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - } -} - -void I422ToARGB4444Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width) { - uint8 b0; - uint8 g0; - uint8 r0; - uint8 b1; - uint8 g1; - uint8 r1; - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_u[0], src_v[0], &b0, &g0, &r0, yuvconstants); - YuvPixel(src_y[1], src_u[0], src_v[0], &b1, &g1, &r1, yuvconstants); - b0 = b0 >> 4; - g0 = g0 >> 4; - r0 = r0 >> 4; - b1 = b1 >> 4; - g1 = g1 >> 4; - r1 = r1 >> 4; - *(uint32*)(dst_argb4444) = b0 | (g0 << 4) | (r0 << 8) | - (b1 << 16) | (g1 << 20) | (r1 << 24) | 0xf000f000; - src_y += 2; - src_u += 1; - src_v += 1; - dst_argb4444 += 4; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], &b0, &g0, &r0, yuvconstants); - b0 = b0 >> 4; - g0 = g0 >> 4; - r0 = r0 >> 4; - *(uint16*)(dst_argb4444) = b0 | (g0 << 4) | (r0 << 8) | - 0xf000; - } -} - -void I422ToARGB1555Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb1555, - const struct YuvConstants* yuvconstants, - int width) { - uint8 b0; - uint8 g0; - uint8 r0; - uint8 b1; - uint8 g1; - uint8 r1; - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_u[0], src_v[0], &b0, &g0, &r0, yuvconstants); - YuvPixel(src_y[1], src_u[0], src_v[0], &b1, &g1, &r1, yuvconstants); - b0 = b0 >> 3; - g0 = g0 >> 3; - r0 = r0 >> 3; - b1 = b1 >> 3; - g1 = g1 >> 3; - r1 = r1 >> 3; - *(uint32*)(dst_argb1555) = b0 | (g0 << 5) | (r0 << 10) | - (b1 << 16) | (g1 << 21) | (r1 << 26) | 0x80008000; - src_y += 2; - src_u += 1; - src_v += 1; - dst_argb1555 += 4; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], &b0, &g0, &r0, yuvconstants); - b0 = b0 >> 3; - g0 = g0 >> 3; - r0 = r0 >> 3; - *(uint16*)(dst_argb1555) = b0 | (g0 << 5) | (r0 << 10) | - 0x8000; - } -} - -void I422ToRGB565Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - uint8 b0; - uint8 g0; - uint8 r0; - uint8 b1; - uint8 g1; - uint8 r1; - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_u[0], src_v[0], &b0, &g0, &r0, yuvconstants); - YuvPixel(src_y[1], src_u[0], src_v[0], &b1, &g1, &r1, yuvconstants); - b0 = b0 >> 3; - g0 = g0 >> 2; - r0 = r0 >> 3; - b1 = b1 >> 3; - g1 = g1 >> 2; - r1 = r1 >> 3; - *(uint32*)(dst_rgb565) = b0 | (g0 << 5) | (r0 << 11) | - (b1 << 16) | (g1 << 21) | (r1 << 27); - src_y += 2; - src_u += 1; - src_v += 1; - dst_rgb565 += 4; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], &b0, &g0, &r0, yuvconstants); - b0 = b0 >> 3; - g0 = g0 >> 2; - r0 = r0 >> 3; - *(uint16*)(dst_rgb565) = b0 | (g0 << 5) | (r0 << 11); - } -} - -void I411ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 3; x += 4) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - YuvPixel(src_y[1], src_u[0], src_v[0], - rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, yuvconstants); - rgb_buf[7] = 255; - YuvPixel(src_y[2], src_u[0], src_v[0], - rgb_buf + 8, rgb_buf + 9, rgb_buf + 10, yuvconstants); - rgb_buf[11] = 255; - YuvPixel(src_y[3], src_u[0], src_v[0], - rgb_buf + 12, rgb_buf + 13, rgb_buf + 14, yuvconstants); - rgb_buf[15] = 255; - src_y += 4; - src_u += 1; - src_v += 1; - rgb_buf += 16; // Advance 4 pixels. - } - if (width & 2) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - YuvPixel(src_y[1], src_u[0], src_v[0], - rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, yuvconstants); - rgb_buf[7] = 255; - src_y += 2; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - } -} - -void NV12ToARGBRow_C(const uint8* src_y, - const uint8* src_uv, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_uv[0], src_uv[1], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - YuvPixel(src_y[1], src_uv[0], src_uv[1], - rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, yuvconstants); - rgb_buf[7] = 255; - src_y += 2; - src_uv += 2; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_uv[0], src_uv[1], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - } -} - -void NV21ToARGBRow_C(const uint8* src_y, - const uint8* src_vu, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_vu[1], src_vu[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - YuvPixel(src_y[1], src_vu[1], src_vu[0], - rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, yuvconstants); - rgb_buf[7] = 255; - src_y += 2; - src_vu += 2; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_vu[1], src_vu[0], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - } -} - -void NV12ToRGB565Row_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - uint8 b0; - uint8 g0; - uint8 r0; - uint8 b1; - uint8 g1; - uint8 r1; - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_uv[0], src_uv[1], &b0, &g0, &r0, yuvconstants); - YuvPixel(src_y[1], src_uv[0], src_uv[1], &b1, &g1, &r1, yuvconstants); - b0 = b0 >> 3; - g0 = g0 >> 2; - r0 = r0 >> 3; - b1 = b1 >> 3; - g1 = g1 >> 2; - r1 = r1 >> 3; - *(uint32*)(dst_rgb565) = b0 | (g0 << 5) | (r0 << 11) | - (b1 << 16) | (g1 << 21) | (r1 << 27); - src_y += 2; - src_uv += 2; - dst_rgb565 += 4; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_uv[0], src_uv[1], &b0, &g0, &r0, yuvconstants); - b0 = b0 >> 3; - g0 = g0 >> 2; - r0 = r0 >> 3; - *(uint16*)(dst_rgb565) = b0 | (g0 << 5) | (r0 << 11); - } -} - -void YUY2ToARGBRow_C(const uint8* src_yuy2, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_yuy2[0], src_yuy2[1], src_yuy2[3], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - YuvPixel(src_yuy2[2], src_yuy2[1], src_yuy2[3], - rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, yuvconstants); - rgb_buf[7] = 255; - src_yuy2 += 4; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_yuy2[0], src_yuy2[1], src_yuy2[3], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - } -} - -void UYVYToARGBRow_C(const uint8* src_uyvy, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_uyvy[1], src_uyvy[0], src_uyvy[2], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - YuvPixel(src_uyvy[3], src_uyvy[0], src_uyvy[2], - rgb_buf + 4, rgb_buf + 5, rgb_buf + 6, yuvconstants); - rgb_buf[7] = 255; - src_uyvy += 4; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_uyvy[1], src_uyvy[0], src_uyvy[2], - rgb_buf + 0, rgb_buf + 1, rgb_buf + 2, yuvconstants); - rgb_buf[3] = 255; - } -} - -void I422ToRGBARow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 1, rgb_buf + 2, rgb_buf + 3, yuvconstants); - rgb_buf[0] = 255; - YuvPixel(src_y[1], src_u[0], src_v[0], - rgb_buf + 5, rgb_buf + 6, rgb_buf + 7, yuvconstants); - rgb_buf[4] = 255; - src_y += 2; - src_u += 1; - src_v += 1; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YuvPixel(src_y[0], src_u[0], src_v[0], - rgb_buf + 1, rgb_buf + 2, rgb_buf + 3, yuvconstants); - rgb_buf[0] = 255; - } -} - -void I400ToARGBRow_C(const uint8* src_y, uint8* rgb_buf, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - YPixel(src_y[0], rgb_buf + 0, rgb_buf + 1, rgb_buf + 2); - rgb_buf[3] = 255; - YPixel(src_y[1], rgb_buf + 4, rgb_buf + 5, rgb_buf + 6); - rgb_buf[7] = 255; - src_y += 2; - rgb_buf += 8; // Advance 2 pixels. - } - if (width & 1) { - YPixel(src_y[0], rgb_buf + 0, rgb_buf + 1, rgb_buf + 2); - rgb_buf[3] = 255; - } -} - -void MirrorRow_C(const uint8* src, uint8* dst, int width) { - int x; - src += width - 1; - for (x = 0; x < width - 1; x += 2) { - dst[x] = src[0]; - dst[x + 1] = src[-1]; - src -= 2; - } - if (width & 1) { - dst[width - 1] = src[0]; - } -} - -void MirrorUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width) { - int x; - src_uv += (width - 1) << 1; - for (x = 0; x < width - 1; x += 2) { - dst_u[x] = src_uv[0]; - dst_u[x + 1] = src_uv[-2]; - dst_v[x] = src_uv[1]; - dst_v[x + 1] = src_uv[-2 + 1]; - src_uv -= 4; - } - if (width & 1) { - dst_u[width - 1] = src_uv[0]; - dst_v[width - 1] = src_uv[1]; - } -} - -void ARGBMirrorRow_C(const uint8* src, uint8* dst, int width) { - int x; - const uint32* src32 = (const uint32*)(src); - uint32* dst32 = (uint32*)(dst); - src32 += width - 1; - for (x = 0; x < width - 1; x += 2) { - dst32[x] = src32[0]; - dst32[x + 1] = src32[-1]; - src32 -= 2; - } - if (width & 1) { - dst32[width - 1] = src32[0]; - } -} - -void SplitUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - dst_u[x] = src_uv[0]; - dst_u[x + 1] = src_uv[2]; - dst_v[x] = src_uv[1]; - dst_v[x + 1] = src_uv[3]; - src_uv += 4; - } - if (width & 1) { - dst_u[width - 1] = src_uv[0]; - dst_v[width - 1] = src_uv[1]; - } -} - -void MergeUVRow_C(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - dst_uv[0] = src_u[x]; - dst_uv[1] = src_v[x]; - dst_uv[2] = src_u[x + 1]; - dst_uv[3] = src_v[x + 1]; - dst_uv += 4; - } - if (width & 1) { - dst_uv[0] = src_u[width - 1]; - dst_uv[1] = src_v[width - 1]; - } -} - -void CopyRow_C(const uint8* src, uint8* dst, int count) { - memcpy(dst, src, count); -} - -void CopyRow_16_C(const uint16* src, uint16* dst, int count) { - memcpy(dst, src, count * 2); -} - -void SetRow_C(uint8* dst, uint8 v8, int width) { - memset(dst, v8, width); -} - -void ARGBSetRow_C(uint8* dst_argb, uint32 v32, int width) { - uint32* d = (uint32*)(dst_argb); - int x; - for (x = 0; x < width; ++x) { - d[x] = v32; - } -} - -// Filter 2 rows of YUY2 UV's (422) into U and V (420). -void YUY2ToUVRow_C(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - // Output a row of UV values, filtering 2 rows of YUY2. - int x; - for (x = 0; x < width; x += 2) { - dst_u[0] = (src_yuy2[1] + src_yuy2[src_stride_yuy2 + 1] + 1) >> 1; - dst_v[0] = (src_yuy2[3] + src_yuy2[src_stride_yuy2 + 3] + 1) >> 1; - src_yuy2 += 4; - dst_u += 1; - dst_v += 1; - } -} - -// Copy row of YUY2 UV's (422) into U and V (422). -void YUY2ToUV422Row_C(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - // Output a row of UV values. - int x; - for (x = 0; x < width; x += 2) { - dst_u[0] = src_yuy2[1]; - dst_v[0] = src_yuy2[3]; - src_yuy2 += 4; - dst_u += 1; - dst_v += 1; - } -} - -// Copy row of YUY2 Y's (422) into Y (420/422). -void YUY2ToYRow_C(const uint8* src_yuy2, uint8* dst_y, int width) { - // Output a row of Y values. - int x; - for (x = 0; x < width - 1; x += 2) { - dst_y[x] = src_yuy2[0]; - dst_y[x + 1] = src_yuy2[2]; - src_yuy2 += 4; - } - if (width & 1) { - dst_y[width - 1] = src_yuy2[0]; - } -} - -// Filter 2 rows of UYVY UV's (422) into U and V (420). -void UYVYToUVRow_C(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - // Output a row of UV values. - int x; - for (x = 0; x < width; x += 2) { - dst_u[0] = (src_uyvy[0] + src_uyvy[src_stride_uyvy + 0] + 1) >> 1; - dst_v[0] = (src_uyvy[2] + src_uyvy[src_stride_uyvy + 2] + 1) >> 1; - src_uyvy += 4; - dst_u += 1; - dst_v += 1; - } -} - -// Copy row of UYVY UV's (422) into U and V (422). -void UYVYToUV422Row_C(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - // Output a row of UV values. - int x; - for (x = 0; x < width; x += 2) { - dst_u[0] = src_uyvy[0]; - dst_v[0] = src_uyvy[2]; - src_uyvy += 4; - dst_u += 1; - dst_v += 1; - } -} - -// Copy row of UYVY Y's (422) into Y (420/422). -void UYVYToYRow_C(const uint8* src_uyvy, uint8* dst_y, int width) { - // Output a row of Y values. - int x; - for (x = 0; x < width - 1; x += 2) { - dst_y[x] = src_uyvy[1]; - dst_y[x + 1] = src_uyvy[3]; - src_uyvy += 4; - } - if (width & 1) { - dst_y[width - 1] = src_uyvy[1]; - } -} - -#define BLEND(f, b, a) (((256 - a) * b) >> 8) + f - -// Blend src_argb0 over src_argb1 and store to dst_argb. -// dst_argb may be src_argb0 or src_argb1. -// This code mimics the SSSE3 version for better testability. -void ARGBBlendRow_C(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - uint32 fb = src_argb0[0]; - uint32 fg = src_argb0[1]; - uint32 fr = src_argb0[2]; - uint32 a = src_argb0[3]; - uint32 bb = src_argb1[0]; - uint32 bg = src_argb1[1]; - uint32 br = src_argb1[2]; - dst_argb[0] = BLEND(fb, bb, a); - dst_argb[1] = BLEND(fg, bg, a); - dst_argb[2] = BLEND(fr, br, a); - dst_argb[3] = 255u; - - fb = src_argb0[4 + 0]; - fg = src_argb0[4 + 1]; - fr = src_argb0[4 + 2]; - a = src_argb0[4 + 3]; - bb = src_argb1[4 + 0]; - bg = src_argb1[4 + 1]; - br = src_argb1[4 + 2]; - dst_argb[4 + 0] = BLEND(fb, bb, a); - dst_argb[4 + 1] = BLEND(fg, bg, a); - dst_argb[4 + 2] = BLEND(fr, br, a); - dst_argb[4 + 3] = 255u; - src_argb0 += 8; - src_argb1 += 8; - dst_argb += 8; - } - - if (width & 1) { - uint32 fb = src_argb0[0]; - uint32 fg = src_argb0[1]; - uint32 fr = src_argb0[2]; - uint32 a = src_argb0[3]; - uint32 bb = src_argb1[0]; - uint32 bg = src_argb1[1]; - uint32 br = src_argb1[2]; - dst_argb[0] = BLEND(fb, bb, a); - dst_argb[1] = BLEND(fg, bg, a); - dst_argb[2] = BLEND(fr, br, a); - dst_argb[3] = 255u; - } -} -#undef BLEND - -#define UBLEND(f, b, a) (((a) * f) + ((255 - a) * b) + 255) >> 8 -void BlendPlaneRow_C(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - dst[0] = UBLEND(src0[0], src1[0], alpha[0]); - dst[1] = UBLEND(src0[1], src1[1], alpha[1]); - src0 += 2; - src1 += 2; - alpha += 2; - dst += 2; - } - if (width & 1) { - dst[0] = UBLEND(src0[0], src1[0], alpha[0]); - } -} -#undef UBLEND - -#define ATTENUATE(f, a) (a | (a << 8)) * (f | (f << 8)) >> 24 - -// Multiply source RGB by alpha and store to destination. -// This code mimics the SSSE3 version for better testability. -void ARGBAttenuateRow_C(const uint8* src_argb, uint8* dst_argb, int width) { - int i; - for (i = 0; i < width - 1; i += 2) { - uint32 b = src_argb[0]; - uint32 g = src_argb[1]; - uint32 r = src_argb[2]; - uint32 a = src_argb[3]; - dst_argb[0] = ATTENUATE(b, a); - dst_argb[1] = ATTENUATE(g, a); - dst_argb[2] = ATTENUATE(r, a); - dst_argb[3] = a; - b = src_argb[4]; - g = src_argb[5]; - r = src_argb[6]; - a = src_argb[7]; - dst_argb[4] = ATTENUATE(b, a); - dst_argb[5] = ATTENUATE(g, a); - dst_argb[6] = ATTENUATE(r, a); - dst_argb[7] = a; - src_argb += 8; - dst_argb += 8; - } - - if (width & 1) { - const uint32 b = src_argb[0]; - const uint32 g = src_argb[1]; - const uint32 r = src_argb[2]; - const uint32 a = src_argb[3]; - dst_argb[0] = ATTENUATE(b, a); - dst_argb[1] = ATTENUATE(g, a); - dst_argb[2] = ATTENUATE(r, a); - dst_argb[3] = a; - } -} -#undef ATTENUATE - -// Divide source RGB by alpha and store to destination. -// b = (b * 255 + (a / 2)) / a; -// g = (g * 255 + (a / 2)) / a; -// r = (r * 255 + (a / 2)) / a; -// Reciprocal method is off by 1 on some values. ie 125 -// 8.8 fixed point inverse table with 1.0 in upper short and 1 / a in lower. -#define T(a) 0x01000000 + (0x10000 / a) -const uint32 fixed_invtbl8[256] = { - 0x01000000, 0x0100ffff, T(0x02), T(0x03), T(0x04), T(0x05), T(0x06), T(0x07), - T(0x08), T(0x09), T(0x0a), T(0x0b), T(0x0c), T(0x0d), T(0x0e), T(0x0f), - T(0x10), T(0x11), T(0x12), T(0x13), T(0x14), T(0x15), T(0x16), T(0x17), - T(0x18), T(0x19), T(0x1a), T(0x1b), T(0x1c), T(0x1d), T(0x1e), T(0x1f), - T(0x20), T(0x21), T(0x22), T(0x23), T(0x24), T(0x25), T(0x26), T(0x27), - T(0x28), T(0x29), T(0x2a), T(0x2b), T(0x2c), T(0x2d), T(0x2e), T(0x2f), - T(0x30), T(0x31), T(0x32), T(0x33), T(0x34), T(0x35), T(0x36), T(0x37), - T(0x38), T(0x39), T(0x3a), T(0x3b), T(0x3c), T(0x3d), T(0x3e), T(0x3f), - T(0x40), T(0x41), T(0x42), T(0x43), T(0x44), T(0x45), T(0x46), T(0x47), - T(0x48), T(0x49), T(0x4a), T(0x4b), T(0x4c), T(0x4d), T(0x4e), T(0x4f), - T(0x50), T(0x51), T(0x52), T(0x53), T(0x54), T(0x55), T(0x56), T(0x57), - T(0x58), T(0x59), T(0x5a), T(0x5b), T(0x5c), T(0x5d), T(0x5e), T(0x5f), - T(0x60), T(0x61), T(0x62), T(0x63), T(0x64), T(0x65), T(0x66), T(0x67), - T(0x68), T(0x69), T(0x6a), T(0x6b), T(0x6c), T(0x6d), T(0x6e), T(0x6f), - T(0x70), T(0x71), T(0x72), T(0x73), T(0x74), T(0x75), T(0x76), T(0x77), - T(0x78), T(0x79), T(0x7a), T(0x7b), T(0x7c), T(0x7d), T(0x7e), T(0x7f), - T(0x80), T(0x81), T(0x82), T(0x83), T(0x84), T(0x85), T(0x86), T(0x87), - T(0x88), T(0x89), T(0x8a), T(0x8b), T(0x8c), T(0x8d), T(0x8e), T(0x8f), - T(0x90), T(0x91), T(0x92), T(0x93), T(0x94), T(0x95), T(0x96), T(0x97), - T(0x98), T(0x99), T(0x9a), T(0x9b), T(0x9c), T(0x9d), T(0x9e), T(0x9f), - T(0xa0), T(0xa1), T(0xa2), T(0xa3), T(0xa4), T(0xa5), T(0xa6), T(0xa7), - T(0xa8), T(0xa9), T(0xaa), T(0xab), T(0xac), T(0xad), T(0xae), T(0xaf), - T(0xb0), T(0xb1), T(0xb2), T(0xb3), T(0xb4), T(0xb5), T(0xb6), T(0xb7), - T(0xb8), T(0xb9), T(0xba), T(0xbb), T(0xbc), T(0xbd), T(0xbe), T(0xbf), - T(0xc0), T(0xc1), T(0xc2), T(0xc3), T(0xc4), T(0xc5), T(0xc6), T(0xc7), - T(0xc8), T(0xc9), T(0xca), T(0xcb), T(0xcc), T(0xcd), T(0xce), T(0xcf), - T(0xd0), T(0xd1), T(0xd2), T(0xd3), T(0xd4), T(0xd5), T(0xd6), T(0xd7), - T(0xd8), T(0xd9), T(0xda), T(0xdb), T(0xdc), T(0xdd), T(0xde), T(0xdf), - T(0xe0), T(0xe1), T(0xe2), T(0xe3), T(0xe4), T(0xe5), T(0xe6), T(0xe7), - T(0xe8), T(0xe9), T(0xea), T(0xeb), T(0xec), T(0xed), T(0xee), T(0xef), - T(0xf0), T(0xf1), T(0xf2), T(0xf3), T(0xf4), T(0xf5), T(0xf6), T(0xf7), - T(0xf8), T(0xf9), T(0xfa), T(0xfb), T(0xfc), T(0xfd), T(0xfe), 0x01000100 }; -#undef T - -void ARGBUnattenuateRow_C(const uint8* src_argb, uint8* dst_argb, int width) { - int i; - for (i = 0; i < width; ++i) { - uint32 b = src_argb[0]; - uint32 g = src_argb[1]; - uint32 r = src_argb[2]; - const uint32 a = src_argb[3]; - const uint32 ia = fixed_invtbl8[a] & 0xffff; // 8.8 fixed point - b = (b * ia) >> 8; - g = (g * ia) >> 8; - r = (r * ia) >> 8; - // Clamping should not be necessary but is free in assembly. - dst_argb[0] = clamp255(b); - dst_argb[1] = clamp255(g); - dst_argb[2] = clamp255(r); - dst_argb[3] = a; - src_argb += 4; - dst_argb += 4; - } -} - -void ComputeCumulativeSumRow_C(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width) { - int32 row_sum[4] = {0, 0, 0, 0}; - int x; - for (x = 0; x < width; ++x) { - row_sum[0] += row[x * 4 + 0]; - row_sum[1] += row[x * 4 + 1]; - row_sum[2] += row[x * 4 + 2]; - row_sum[3] += row[x * 4 + 3]; - cumsum[x * 4 + 0] = row_sum[0] + previous_cumsum[x * 4 + 0]; - cumsum[x * 4 + 1] = row_sum[1] + previous_cumsum[x * 4 + 1]; - cumsum[x * 4 + 2] = row_sum[2] + previous_cumsum[x * 4 + 2]; - cumsum[x * 4 + 3] = row_sum[3] + previous_cumsum[x * 4 + 3]; - } -} - -void CumulativeSumToAverageRow_C(const int32* tl, const int32* bl, - int w, int area, uint8* dst, int count) { - float ooa = 1.0f / area; - int i; - for (i = 0; i < count; ++i) { - dst[0] = (uint8)((bl[w + 0] + tl[0] - bl[0] - tl[w + 0]) * ooa); - dst[1] = (uint8)((bl[w + 1] + tl[1] - bl[1] - tl[w + 1]) * ooa); - dst[2] = (uint8)((bl[w + 2] + tl[2] - bl[2] - tl[w + 2]) * ooa); - dst[3] = (uint8)((bl[w + 3] + tl[3] - bl[3] - tl[w + 3]) * ooa); - dst += 4; - tl += 4; - bl += 4; - } -} - -// Copy pixels from rotated source to destination row with a slope. -LIBYUV_API -void ARGBAffineRow_C(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width) { - int i; - // Render a row of pixels from source into a buffer. - float uv[2]; - uv[0] = uv_dudv[0]; - uv[1] = uv_dudv[1]; - for (i = 0; i < width; ++i) { - int x = (int)(uv[0]); - int y = (int)(uv[1]); - *(uint32*)(dst_argb) = - *(const uint32*)(src_argb + y * src_argb_stride + - x * 4); - dst_argb += 4; - uv[0] += uv_dudv[2]; - uv[1] += uv_dudv[3]; - } -} - -// Blend 2 rows into 1. -static void HalfRow_C(const uint8* src_uv, ptrdiff_t src_uv_stride, - uint8* dst_uv, int width) { - int x; - for (x = 0; x < width; ++x) { - dst_uv[x] = (src_uv[x] + src_uv[src_uv_stride + x] + 1) >> 1; - } -} - -static void HalfRow_16_C(const uint16* src_uv, ptrdiff_t src_uv_stride, - uint16* dst_uv, int width) { - int x; - for (x = 0; x < width; ++x) { - dst_uv[x] = (src_uv[x] + src_uv[src_uv_stride + x] + 1) >> 1; - } -} - -// C version 2x2 -> 2x1. -void InterpolateRow_C(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, - int width, int source_y_fraction) { - int y1_fraction = source_y_fraction ; - int y0_fraction = 256 - y1_fraction; - const uint8* src_ptr1 = src_ptr + src_stride; - int x; - if (y1_fraction == 0) { - memcpy(dst_ptr, src_ptr, width); - return; - } - if (y1_fraction == 128) { - HalfRow_C(src_ptr, src_stride, dst_ptr, width); - return; - } - for (x = 0; x < width - 1; x += 2) { - dst_ptr[0] = - (src_ptr[0] * y0_fraction + src_ptr1[0] * y1_fraction + 128) >> 8; - dst_ptr[1] = - (src_ptr[1] * y0_fraction + src_ptr1[1] * y1_fraction + 128) >> 8; - src_ptr += 2; - src_ptr1 += 2; - dst_ptr += 2; - } - if (width & 1) { - dst_ptr[0] = - (src_ptr[0] * y0_fraction + src_ptr1[0] * y1_fraction + 128) >> 8; - } -} - -void InterpolateRow_16_C(uint16* dst_ptr, const uint16* src_ptr, - ptrdiff_t src_stride, - int width, int source_y_fraction) { - int y1_fraction = source_y_fraction; - int y0_fraction = 256 - y1_fraction; - const uint16* src_ptr1 = src_ptr + src_stride; - int x; - if (source_y_fraction == 0) { - memcpy(dst_ptr, src_ptr, width * 2); - return; - } - if (source_y_fraction == 128) { - HalfRow_16_C(src_ptr, src_stride, dst_ptr, width); - return; - } - for (x = 0; x < width - 1; x += 2) { - dst_ptr[0] = (src_ptr[0] * y0_fraction + src_ptr1[0] * y1_fraction) >> 8; - dst_ptr[1] = (src_ptr[1] * y0_fraction + src_ptr1[1] * y1_fraction) >> 8; - src_ptr += 2; - src_ptr1 += 2; - dst_ptr += 2; - } - if (width & 1) { - dst_ptr[0] = (src_ptr[0] * y0_fraction + src_ptr1[0] * y1_fraction) >> 8; - } -} - -// Use first 4 shuffler values to reorder ARGB channels. -void ARGBShuffleRow_C(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - int index0 = shuffler[0]; - int index1 = shuffler[1]; - int index2 = shuffler[2]; - int index3 = shuffler[3]; - // Shuffle a row of ARGB. - int x; - for (x = 0; x < width; ++x) { - // To support in-place conversion. - uint8 b = src_argb[index0]; - uint8 g = src_argb[index1]; - uint8 r = src_argb[index2]; - uint8 a = src_argb[index3]; - dst_argb[0] = b; - dst_argb[1] = g; - dst_argb[2] = r; - dst_argb[3] = a; - src_argb += 4; - dst_argb += 4; - } -} - -void I422ToYUY2Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_frame, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - dst_frame[0] = src_y[0]; - dst_frame[1] = src_u[0]; - dst_frame[2] = src_y[1]; - dst_frame[3] = src_v[0]; - dst_frame += 4; - src_y += 2; - src_u += 1; - src_v += 1; - } - if (width & 1) { - dst_frame[0] = src_y[0]; - dst_frame[1] = src_u[0]; - dst_frame[2] = 0; - dst_frame[3] = src_v[0]; - } -} - -void I422ToUYVYRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_frame, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - dst_frame[0] = src_u[0]; - dst_frame[1] = src_y[0]; - dst_frame[2] = src_v[0]; - dst_frame[3] = src_y[1]; - dst_frame += 4; - src_y += 2; - src_u += 1; - src_v += 1; - } - if (width & 1) { - dst_frame[0] = src_u[0]; - dst_frame[1] = src_y[0]; - dst_frame[2] = src_v[0]; - dst_frame[3] = 0; - } -} - - -void ARGBPolynomialRow_C(const uint8* src_argb, - uint8* dst_argb, - const float* poly, - int width) { - int i; - for (i = 0; i < width; ++i) { - float b = (float)(src_argb[0]); - float g = (float)(src_argb[1]); - float r = (float)(src_argb[2]); - float a = (float)(src_argb[3]); - float b2 = b * b; - float g2 = g * g; - float r2 = r * r; - float a2 = a * a; - float db = poly[0] + poly[4] * b; - float dg = poly[1] + poly[5] * g; - float dr = poly[2] + poly[6] * r; - float da = poly[3] + poly[7] * a; - float b3 = b2 * b; - float g3 = g2 * g; - float r3 = r2 * r; - float a3 = a2 * a; - db += poly[8] * b2; - dg += poly[9] * g2; - dr += poly[10] * r2; - da += poly[11] * a2; - db += poly[12] * b3; - dg += poly[13] * g3; - dr += poly[14] * r3; - da += poly[15] * a3; - - dst_argb[0] = Clamp((int32)(db)); - dst_argb[1] = Clamp((int32)(dg)); - dst_argb[2] = Clamp((int32)(dr)); - dst_argb[3] = Clamp((int32)(da)); - src_argb += 4; - dst_argb += 4; - } -} - -void ARGBLumaColorTableRow_C(const uint8* src_argb, uint8* dst_argb, int width, - const uint8* luma, uint32 lumacoeff) { - uint32 bc = lumacoeff & 0xff; - uint32 gc = (lumacoeff >> 8) & 0xff; - uint32 rc = (lumacoeff >> 16) & 0xff; - - int i; - for (i = 0; i < width - 1; i += 2) { - // Luminance in rows, color values in columns. - const uint8* luma0 = ((src_argb[0] * bc + src_argb[1] * gc + - src_argb[2] * rc) & 0x7F00u) + luma; - const uint8* luma1; - dst_argb[0] = luma0[src_argb[0]]; - dst_argb[1] = luma0[src_argb[1]]; - dst_argb[2] = luma0[src_argb[2]]; - dst_argb[3] = src_argb[3]; - luma1 = ((src_argb[4] * bc + src_argb[5] * gc + - src_argb[6] * rc) & 0x7F00u) + luma; - dst_argb[4] = luma1[src_argb[4]]; - dst_argb[5] = luma1[src_argb[5]]; - dst_argb[6] = luma1[src_argb[6]]; - dst_argb[7] = src_argb[7]; - src_argb += 8; - dst_argb += 8; - } - if (width & 1) { - // Luminance in rows, color values in columns. - const uint8* luma0 = ((src_argb[0] * bc + src_argb[1] * gc + - src_argb[2] * rc) & 0x7F00u) + luma; - dst_argb[0] = luma0[src_argb[0]]; - dst_argb[1] = luma0[src_argb[1]]; - dst_argb[2] = luma0[src_argb[2]]; - dst_argb[3] = src_argb[3]; - } -} - -void ARGBCopyAlphaRow_C(const uint8* src, uint8* dst, int width) { - int i; - for (i = 0; i < width - 1; i += 2) { - dst[3] = src[3]; - dst[7] = src[7]; - dst += 8; - src += 8; - } - if (width & 1) { - dst[3] = src[3]; - } -} - -void ARGBExtractAlphaRow_C(const uint8* src_argb, uint8* dst_a, int width) { - int i; - for (i = 0; i < width - 1; i += 2) { - dst_a[0] = src_argb[3]; - dst_a[1] = src_argb[7]; - dst_a += 2; - src_argb += 8; - } - if (width & 1) { - dst_a[0] = src_argb[3]; - } -} - -void ARGBCopyYToAlphaRow_C(const uint8* src, uint8* dst, int width) { - int i; - for (i = 0; i < width - 1; i += 2) { - dst[3] = src[0]; - dst[7] = src[1]; - dst += 8; - src += 2; - } - if (width & 1) { - dst[3] = src[0]; - } -} - -// Maximum temporary width for wrappers to process at a time, in pixels. -#define MAXTWIDTH 2048 - -#if !(defined(_MSC_VER) && defined(_M_IX86)) && \ - defined(HAS_I422TORGB565ROW_SSSE3) -// row_win.cc has asm version, but GCC uses 2 step wrapper. -void I422ToRGB565Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - SIMD_ALIGNED(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - I422ToARGBRow_SSSE3(src_y, src_u, src_v, row, yuvconstants, twidth); - ARGBToRGB565Row_SSE2(row, dst_rgb565, twidth); - src_y += twidth; - src_u += twidth / 2; - src_v += twidth / 2; - dst_rgb565 += twidth * 2; - width -= twidth; - } -} -#endif - -#if defined(HAS_I422TOARGB1555ROW_SSSE3) -void I422ToARGB1555Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb1555, - const struct YuvConstants* yuvconstants, - int width) { - // Row buffer for intermediate ARGB pixels. - SIMD_ALIGNED(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - I422ToARGBRow_SSSE3(src_y, src_u, src_v, row, yuvconstants, twidth); - ARGBToARGB1555Row_SSE2(row, dst_argb1555, twidth); - src_y += twidth; - src_u += twidth / 2; - src_v += twidth / 2; - dst_argb1555 += twidth * 2; - width -= twidth; - } -} -#endif - -#if defined(HAS_I422TOARGB4444ROW_SSSE3) -void I422ToARGB4444Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width) { - // Row buffer for intermediate ARGB pixels. - SIMD_ALIGNED(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - I422ToARGBRow_SSSE3(src_y, src_u, src_v, row, yuvconstants, twidth); - ARGBToARGB4444Row_SSE2(row, dst_argb4444, twidth); - src_y += twidth; - src_u += twidth / 2; - src_v += twidth / 2; - dst_argb4444 += twidth * 2; - width -= twidth; - } -} -#endif - -#if defined(HAS_NV12TORGB565ROW_SSSE3) -void NV12ToRGB565Row_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - // Row buffer for intermediate ARGB pixels. - SIMD_ALIGNED(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - NV12ToARGBRow_SSSE3(src_y, src_uv, row, yuvconstants, twidth); - ARGBToRGB565Row_SSE2(row, dst_rgb565, twidth); - src_y += twidth; - src_uv += twidth; - dst_rgb565 += twidth * 2; - width -= twidth; - } -} -#endif - -#if defined(HAS_I422TORGB565ROW_AVX2) -void I422ToRGB565Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - SIMD_ALIGNED32(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - I422ToARGBRow_AVX2(src_y, src_u, src_v, row, yuvconstants, twidth); -#if defined(HAS_ARGBTORGB565ROW_AVX2) - ARGBToRGB565Row_AVX2(row, dst_rgb565, twidth); -#else - ARGBToRGB565Row_SSE2(row, dst_rgb565, twidth); -#endif - src_y += twidth; - src_u += twidth / 2; - src_v += twidth / 2; - dst_rgb565 += twidth * 2; - width -= twidth; - } -} -#endif - -#if defined(HAS_I422TOARGB1555ROW_AVX2) -void I422ToARGB1555Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb1555, - const struct YuvConstants* yuvconstants, - int width) { - // Row buffer for intermediate ARGB pixels. - SIMD_ALIGNED32(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - I422ToARGBRow_AVX2(src_y, src_u, src_v, row, yuvconstants, twidth); -#if defined(HAS_ARGBTOARGB1555ROW_AVX2) - ARGBToARGB1555Row_AVX2(row, dst_argb1555, twidth); -#else - ARGBToARGB1555Row_SSE2(row, dst_argb1555, twidth); -#endif - src_y += twidth; - src_u += twidth / 2; - src_v += twidth / 2; - dst_argb1555 += twidth * 2; - width -= twidth; - } -} -#endif - -#if defined(HAS_I422TOARGB4444ROW_AVX2) -void I422ToARGB4444Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width) { - // Row buffer for intermediate ARGB pixels. - SIMD_ALIGNED32(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - I422ToARGBRow_AVX2(src_y, src_u, src_v, row, yuvconstants, twidth); -#if defined(HAS_ARGBTOARGB4444ROW_AVX2) - ARGBToARGB4444Row_AVX2(row, dst_argb4444, twidth); -#else - ARGBToARGB4444Row_SSE2(row, dst_argb4444, twidth); -#endif - src_y += twidth; - src_u += twidth / 2; - src_v += twidth / 2; - dst_argb4444 += twidth * 2; - width -= twidth; - } -} -#endif - -#if defined(HAS_I422TORGB24ROW_AVX2) -void I422ToRGB24Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width) { - // Row buffer for intermediate ARGB pixels. - SIMD_ALIGNED32(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - I422ToARGBRow_AVX2(src_y, src_u, src_v, row, yuvconstants, twidth); - // TODO(fbarchard): ARGBToRGB24Row_AVX2 - ARGBToRGB24Row_SSSE3(row, dst_rgb24, twidth); - src_y += twidth; - src_u += twidth / 2; - src_v += twidth / 2; - dst_rgb24 += twidth * 3; - width -= twidth; - } -} -#endif - -#if defined(HAS_NV12TORGB565ROW_AVX2) -void NV12ToRGB565Row_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - // Row buffer for intermediate ARGB pixels. - SIMD_ALIGNED32(uint8 row[MAXTWIDTH * 4]); - while (width > 0) { - int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; - NV12ToARGBRow_AVX2(src_y, src_uv, row, yuvconstants, twidth); -#if defined(HAS_ARGBTORGB565ROW_AVX2) - ARGBToRGB565Row_AVX2(row, dst_rgb565, twidth); -#else - ARGBToRGB565Row_SSE2(row, dst_rgb565, twidth); -#endif - src_y += twidth; - src_uv += twidth; - dst_rgb565 += twidth * 2; - width -= twidth; - } -} -#endif - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/row_gcc.cc b/telegramgallery/src/main/cpp/libyuv/source/row_gcc.cc deleted file mode 100644 index 1ac7ef1..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/row_gcc.cc +++ /dev/null @@ -1,5534 +0,0 @@ -// VERSION 2 -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC x86 and x64. -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) && !defined(_MSC_VER))) - -#if defined(HAS_ARGBTOYROW_SSSE3) || defined(HAS_ARGBGRAYROW_SSSE3) - -// Constants for ARGB -static vec8 kARGBToY = { - 13, 65, 33, 0, 13, 65, 33, 0, 13, 65, 33, 0, 13, 65, 33, 0 -}; - -// JPeg full range. -static vec8 kARGBToYJ = { - 15, 75, 38, 0, 15, 75, 38, 0, 15, 75, 38, 0, 15, 75, 38, 0 -}; -#endif // defined(HAS_ARGBTOYROW_SSSE3) || defined(HAS_ARGBGRAYROW_SSSE3) - -#if defined(HAS_ARGBTOYROW_SSSE3) || defined(HAS_I422TOARGBROW_SSSE3) - -static vec8 kARGBToU = { - 112, -74, -38, 0, 112, -74, -38, 0, 112, -74, -38, 0, 112, -74, -38, 0 -}; - -static vec8 kARGBToUJ = { - 127, -84, -43, 0, 127, -84, -43, 0, 127, -84, -43, 0, 127, -84, -43, 0 -}; - -static vec8 kARGBToV = { - -18, -94, 112, 0, -18, -94, 112, 0, -18, -94, 112, 0, -18, -94, 112, 0, -}; - -static vec8 kARGBToVJ = { - -20, -107, 127, 0, -20, -107, 127, 0, -20, -107, 127, 0, -20, -107, 127, 0 -}; - -// Constants for BGRA -static vec8 kBGRAToY = { - 0, 33, 65, 13, 0, 33, 65, 13, 0, 33, 65, 13, 0, 33, 65, 13 -}; - -static vec8 kBGRAToU = { - 0, -38, -74, 112, 0, -38, -74, 112, 0, -38, -74, 112, 0, -38, -74, 112 -}; - -static vec8 kBGRAToV = { - 0, 112, -94, -18, 0, 112, -94, -18, 0, 112, -94, -18, 0, 112, -94, -18 -}; - -// Constants for ABGR -static vec8 kABGRToY = { - 33, 65, 13, 0, 33, 65, 13, 0, 33, 65, 13, 0, 33, 65, 13, 0 -}; - -static vec8 kABGRToU = { - -38, -74, 112, 0, -38, -74, 112, 0, -38, -74, 112, 0, -38, -74, 112, 0 -}; - -static vec8 kABGRToV = { - 112, -94, -18, 0, 112, -94, -18, 0, 112, -94, -18, 0, 112, -94, -18, 0 -}; - -// Constants for RGBA. -static vec8 kRGBAToY = { - 0, 13, 65, 33, 0, 13, 65, 33, 0, 13, 65, 33, 0, 13, 65, 33 -}; - -static vec8 kRGBAToU = { - 0, 112, -74, -38, 0, 112, -74, -38, 0, 112, -74, -38, 0, 112, -74, -38 -}; - -static vec8 kRGBAToV = { - 0, -18, -94, 112, 0, -18, -94, 112, 0, -18, -94, 112, 0, -18, -94, 112 -}; - -static uvec8 kAddY16 = { - 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u -}; - -// 7 bit fixed point 0.5. -static vec16 kAddYJ64 = { - 64, 64, 64, 64, 64, 64, 64, 64 -}; - -static uvec8 kAddUV128 = { - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u, - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u -}; - -static uvec16 kAddUVJ128 = { - 0x8080u, 0x8080u, 0x8080u, 0x8080u, 0x8080u, 0x8080u, 0x8080u, 0x8080u -}; -#endif // defined(HAS_ARGBTOYROW_SSSE3) || defined(HAS_I422TOARGBROW_SSSE3) - -#ifdef HAS_RGB24TOARGBROW_SSSE3 - -// Shuffle table for converting RGB24 to ARGB. -static uvec8 kShuffleMaskRGB24ToARGB = { - 0u, 1u, 2u, 12u, 3u, 4u, 5u, 13u, 6u, 7u, 8u, 14u, 9u, 10u, 11u, 15u -}; - -// Shuffle table for converting RAW to ARGB. -static uvec8 kShuffleMaskRAWToARGB = { - 2u, 1u, 0u, 12u, 5u, 4u, 3u, 13u, 8u, 7u, 6u, 14u, 11u, 10u, 9u, 15u -}; - -// Shuffle table for converting RAW to RGB24. First 8. -static const uvec8 kShuffleMaskRAWToRGB24_0 = { - 2u, 1u, 0u, 5u, 4u, 3u, 8u, 7u, - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting RAW to RGB24. Middle 8. -static const uvec8 kShuffleMaskRAWToRGB24_1 = { - 2u, 7u, 6u, 5u, 10u, 9u, 8u, 13u, - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting RAW to RGB24. Last 8. -static const uvec8 kShuffleMaskRAWToRGB24_2 = { - 8u, 7u, 12u, 11u, 10u, 15u, 14u, 13u, - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting ARGB to RGB24. -static uvec8 kShuffleMaskARGBToRGB24 = { - 0u, 1u, 2u, 4u, 5u, 6u, 8u, 9u, 10u, 12u, 13u, 14u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting ARGB to RAW. -static uvec8 kShuffleMaskARGBToRAW = { - 2u, 1u, 0u, 6u, 5u, 4u, 10u, 9u, 8u, 14u, 13u, 12u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting ARGBToRGB24 for I422ToRGB24. First 8 + next 4 -static uvec8 kShuffleMaskARGBToRGB24_0 = { - 0u, 1u, 2u, 4u, 5u, 6u, 8u, 9u, 128u, 128u, 128u, 128u, 10u, 12u, 13u, 14u -}; - -// YUY2 shuf 16 Y to 32 Y. -static const lvec8 kShuffleYUY2Y = { - 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, - 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14 -}; - -// YUY2 shuf 8 UV to 16 UV. -static const lvec8 kShuffleYUY2UV = { - 1, 3, 1, 3, 5, 7, 5, 7, 9, 11, 9, 11, 13, 15, 13, 15, - 1, 3, 1, 3, 5, 7, 5, 7, 9, 11, 9, 11, 13, 15, 13, 15 -}; - -// UYVY shuf 16 Y to 32 Y. -static const lvec8 kShuffleUYVYY = { - 1, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11, 13, 13, 15, 15, - 1, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11, 13, 13, 15, 15 -}; - -// UYVY shuf 8 UV to 16 UV. -static const lvec8 kShuffleUYVYUV = { - 0, 2, 0, 2, 4, 6, 4, 6, 8, 10, 8, 10, 12, 14, 12, 14, - 0, 2, 0, 2, 4, 6, 4, 6, 8, 10, 8, 10, 12, 14, 12, 14 -}; - -// NV21 shuf 8 VU to 16 UV. -static const lvec8 kShuffleNV21 = { - 1, 0, 1, 0, 3, 2, 3, 2, 5, 4, 5, 4, 7, 6, 7, 6, - 1, 0, 1, 0, 3, 2, 3, 2, 5, 4, 5, 4, 7, 6, 7, 6, -}; -#endif // HAS_RGB24TOARGBROW_SSSE3 - -#ifdef HAS_J400TOARGBROW_SSE2 -void J400ToARGBRow_SSE2(const uint8* src_y, uint8* dst_argb, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "pslld $0x18,%%xmm5 \n" - LABELALIGN - "1: \n" - "movq " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x8,0) ",%0 \n" - "punpcklbw %%xmm0,%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklwd %%xmm0,%%xmm0 \n" - "punpckhwd %%xmm1,%%xmm1 \n" - "por %%xmm5,%%xmm0 \n" - "por %%xmm5,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src_y), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - :: "memory", "cc", "xmm0", "xmm1", "xmm5" - ); -} -#endif // HAS_J400TOARGBROW_SSE2 - -#ifdef HAS_RGB24TOARGBROW_SSSE3 -void RGB24ToARGBRow_SSSE3(const uint8* src_rgb24, uint8* dst_argb, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" // generate mask 0xff000000 - "pslld $0x18,%%xmm5 \n" - "movdqa %3,%%xmm4 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm3 \n" - "lea " MEMLEA(0x30,0) ",%0 \n" - "movdqa %%xmm3,%%xmm2 \n" - "palignr $0x8,%%xmm1,%%xmm2 \n" - "pshufb %%xmm4,%%xmm2 \n" - "por %%xmm5,%%xmm2 \n" - "palignr $0xc,%%xmm0,%%xmm1 \n" - "pshufb %%xmm4,%%xmm0 \n" - "movdqu %%xmm2," MEMACCESS2(0x20,1) " \n" - "por %%xmm5,%%xmm0 \n" - "pshufb %%xmm4,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "por %%xmm5,%%xmm1 \n" - "palignr $0x4,%%xmm3,%%xmm3 \n" - "pshufb %%xmm4,%%xmm3 \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "por %%xmm5,%%xmm3 \n" - "movdqu %%xmm3," MEMACCESS2(0x30,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_rgb24), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "m"(kShuffleMaskRGB24ToARGB) // %3 - : "memory", "cc" , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void RAWToARGBRow_SSSE3(const uint8* src_raw, uint8* dst_argb, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" // generate mask 0xff000000 - "pslld $0x18,%%xmm5 \n" - "movdqa %3,%%xmm4 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm3 \n" - "lea " MEMLEA(0x30,0) ",%0 \n" - "movdqa %%xmm3,%%xmm2 \n" - "palignr $0x8,%%xmm1,%%xmm2 \n" - "pshufb %%xmm4,%%xmm2 \n" - "por %%xmm5,%%xmm2 \n" - "palignr $0xc,%%xmm0,%%xmm1 \n" - "pshufb %%xmm4,%%xmm0 \n" - "movdqu %%xmm2," MEMACCESS2(0x20,1) " \n" - "por %%xmm5,%%xmm0 \n" - "pshufb %%xmm4,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "por %%xmm5,%%xmm1 \n" - "palignr $0x4,%%xmm3,%%xmm3 \n" - "pshufb %%xmm4,%%xmm3 \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "por %%xmm5,%%xmm3 \n" - "movdqu %%xmm3," MEMACCESS2(0x30,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_raw), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "m"(kShuffleMaskRAWToARGB) // %3 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void RAWToRGB24Row_SSSE3(const uint8* src_raw, uint8* dst_rgb24, int width) { - asm volatile ( - "movdqa %3,%%xmm3 \n" - "movdqa %4,%%xmm4 \n" - "movdqa %5,%%xmm5 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x4,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x8,0) ",%%xmm2 \n" - "lea " MEMLEA(0x18,0) ",%0 \n" - "pshufb %%xmm3,%%xmm0 \n" - "pshufb %%xmm4,%%xmm1 \n" - "pshufb %%xmm5,%%xmm2 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "movq %%xmm1," MEMACCESS2(0x8,1) " \n" - "movq %%xmm2," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x18,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src_raw), // %0 - "+r"(dst_rgb24), // %1 - "+r"(width) // %2 - : "m"(kShuffleMaskRAWToRGB24_0), // %3 - "m"(kShuffleMaskRAWToRGB24_1), // %4 - "m"(kShuffleMaskRAWToRGB24_2) // %5 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void RGB565ToARGBRow_SSE2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "mov $0x1080108,%%eax \n" - "movd %%eax,%%xmm5 \n" - "pshufd $0x0,%%xmm5,%%xmm5 \n" - "mov $0x20802080,%%eax \n" - "movd %%eax,%%xmm6 \n" - "pshufd $0x0,%%xmm6,%%xmm6 \n" - "pcmpeqb %%xmm3,%%xmm3 \n" - "psllw $0xb,%%xmm3 \n" - "pcmpeqb %%xmm4,%%xmm4 \n" - "psllw $0xa,%%xmm4 \n" - "psrlw $0x5,%%xmm4 \n" - "pcmpeqb %%xmm7,%%xmm7 \n" - "psllw $0x8,%%xmm7 \n" - "sub %0,%1 \n" - "sub %0,%1 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "pand %%xmm3,%%xmm1 \n" - "psllw $0xb,%%xmm2 \n" - "pmulhuw %%xmm5,%%xmm1 \n" - "pmulhuw %%xmm5,%%xmm2 \n" - "psllw $0x8,%%xmm1 \n" - "por %%xmm2,%%xmm1 \n" - "pand %%xmm4,%%xmm0 \n" - "pmulhuw %%xmm6,%%xmm0 \n" - "por %%xmm7,%%xmm0 \n" - "movdqa %%xmm1,%%xmm2 \n" - "punpcklbw %%xmm0,%%xmm1 \n" - "punpckhbw %%xmm0,%%xmm2 \n" - MEMOPMEM(movdqu,xmm1,0x00,1,0,2) // movdqu %%xmm1,(%1,%0,2) - MEMOPMEM(movdqu,xmm2,0x10,1,0,2) // movdqu %%xmm2,0x10(%1,%0,2) - "lea " MEMLEA(0x10,0) ",%0 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "memory", "cc", "eax", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} - -void ARGB1555ToARGBRow_SSE2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "mov $0x1080108,%%eax \n" - "movd %%eax,%%xmm5 \n" - "pshufd $0x0,%%xmm5,%%xmm5 \n" - "mov $0x42004200,%%eax \n" - "movd %%eax,%%xmm6 \n" - "pshufd $0x0,%%xmm6,%%xmm6 \n" - "pcmpeqb %%xmm3,%%xmm3 \n" - "psllw $0xb,%%xmm3 \n" - "movdqa %%xmm3,%%xmm4 \n" - "psrlw $0x6,%%xmm4 \n" - "pcmpeqb %%xmm7,%%xmm7 \n" - "psllw $0x8,%%xmm7 \n" - "sub %0,%1 \n" - "sub %0,%1 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "psllw $0x1,%%xmm1 \n" - "psllw $0xb,%%xmm2 \n" - "pand %%xmm3,%%xmm1 \n" - "pmulhuw %%xmm5,%%xmm2 \n" - "pmulhuw %%xmm5,%%xmm1 \n" - "psllw $0x8,%%xmm1 \n" - "por %%xmm2,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "pand %%xmm4,%%xmm0 \n" - "psraw $0x8,%%xmm2 \n" - "pmulhuw %%xmm6,%%xmm0 \n" - "pand %%xmm7,%%xmm2 \n" - "por %%xmm2,%%xmm0 \n" - "movdqa %%xmm1,%%xmm2 \n" - "punpcklbw %%xmm0,%%xmm1 \n" - "punpckhbw %%xmm0,%%xmm2 \n" - MEMOPMEM(movdqu,xmm1,0x00,1,0,2) // movdqu %%xmm1,(%1,%0,2) - MEMOPMEM(movdqu,xmm2,0x10,1,0,2) // movdqu %%xmm2,0x10(%1,%0,2) - "lea " MEMLEA(0x10,0) ",%0 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "memory", "cc", "eax", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} - -void ARGB4444ToARGBRow_SSE2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "mov $0xf0f0f0f,%%eax \n" - "movd %%eax,%%xmm4 \n" - "pshufd $0x0,%%xmm4,%%xmm4 \n" - "movdqa %%xmm4,%%xmm5 \n" - "pslld $0x4,%%xmm5 \n" - "sub %0,%1 \n" - "sub %0,%1 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqa %%xmm0,%%xmm2 \n" - "pand %%xmm4,%%xmm0 \n" - "pand %%xmm5,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm2,%%xmm3 \n" - "psllw $0x4,%%xmm1 \n" - "psrlw $0x4,%%xmm3 \n" - "por %%xmm1,%%xmm0 \n" - "por %%xmm3,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm2,%%xmm0 \n" - "punpckhbw %%xmm2,%%xmm1 \n" - MEMOPMEM(movdqu,xmm0,0x00,1,0,2) // movdqu %%xmm0,(%1,%0,2) - MEMOPMEM(movdqu,xmm1,0x10,1,0,2) // movdqu %%xmm1,0x10(%1,%0,2) - "lea " MEMLEA(0x10,0) ",%0 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "memory", "cc", "eax", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void ARGBToRGB24Row_SSSE3(const uint8* src, uint8* dst, int width) { - asm volatile ( - "movdqa %3,%%xmm6 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "pshufb %%xmm6,%%xmm0 \n" - "pshufb %%xmm6,%%xmm1 \n" - "pshufb %%xmm6,%%xmm2 \n" - "pshufb %%xmm6,%%xmm3 \n" - "movdqa %%xmm1,%%xmm4 \n" - "psrldq $0x4,%%xmm1 \n" - "pslldq $0xc,%%xmm4 \n" - "movdqa %%xmm2,%%xmm5 \n" - "por %%xmm4,%%xmm0 \n" - "pslldq $0x8,%%xmm5 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "por %%xmm5,%%xmm1 \n" - "psrldq $0x8,%%xmm2 \n" - "pslldq $0x4,%%xmm3 \n" - "por %%xmm3,%%xmm2 \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "movdqu %%xmm2," MEMACCESS2(0x20,1) " \n" - "lea " MEMLEA(0x30,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : "m"(kShuffleMaskARGBToRGB24) // %3 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} - -void ARGBToRAWRow_SSSE3(const uint8* src, uint8* dst, int width) { - asm volatile ( - "movdqa %3,%%xmm6 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "pshufb %%xmm6,%%xmm0 \n" - "pshufb %%xmm6,%%xmm1 \n" - "pshufb %%xmm6,%%xmm2 \n" - "pshufb %%xmm6,%%xmm3 \n" - "movdqa %%xmm1,%%xmm4 \n" - "psrldq $0x4,%%xmm1 \n" - "pslldq $0xc,%%xmm4 \n" - "movdqa %%xmm2,%%xmm5 \n" - "por %%xmm4,%%xmm0 \n" - "pslldq $0x8,%%xmm5 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "por %%xmm5,%%xmm1 \n" - "psrldq $0x8,%%xmm2 \n" - "pslldq $0x4,%%xmm3 \n" - "por %%xmm3,%%xmm2 \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "movdqu %%xmm2," MEMACCESS2(0x20,1) " \n" - "lea " MEMLEA(0x30,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : "m"(kShuffleMaskARGBToRAW) // %3 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} - -void ARGBToRGB565Row_SSE2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "pcmpeqb %%xmm3,%%xmm3 \n" - "psrld $0x1b,%%xmm3 \n" - "pcmpeqb %%xmm4,%%xmm4 \n" - "psrld $0x1a,%%xmm4 \n" - "pslld $0x5,%%xmm4 \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - "pslld $0xb,%%xmm5 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "pslld $0x8,%%xmm0 \n" - "psrld $0x3,%%xmm1 \n" - "psrld $0x5,%%xmm2 \n" - "psrad $0x10,%%xmm0 \n" - "pand %%xmm3,%%xmm1 \n" - "pand %%xmm4,%%xmm2 \n" - "pand %%xmm5,%%xmm0 \n" - "por %%xmm2,%%xmm1 \n" - "por %%xmm1,%%xmm0 \n" - "packssdw %%xmm0,%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - :: "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void ARGBToRGB565DitherRow_SSE2(const uint8* src, uint8* dst, - const uint32 dither4, int width) { - asm volatile ( - "movd %3,%%xmm6 \n" - "punpcklbw %%xmm6,%%xmm6 \n" - "movdqa %%xmm6,%%xmm7 \n" - "punpcklwd %%xmm6,%%xmm6 \n" - "punpckhwd %%xmm7,%%xmm7 \n" - "pcmpeqb %%xmm3,%%xmm3 \n" - "psrld $0x1b,%%xmm3 \n" - "pcmpeqb %%xmm4,%%xmm4 \n" - "psrld $0x1a,%%xmm4 \n" - "pslld $0x5,%%xmm4 \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - "pslld $0xb,%%xmm5 \n" - - LABELALIGN - "1: \n" - "movdqu (%0),%%xmm0 \n" - "paddusb %%xmm6,%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "pslld $0x8,%%xmm0 \n" - "psrld $0x3,%%xmm1 \n" - "psrld $0x5,%%xmm2 \n" - "psrad $0x10,%%xmm0 \n" - "pand %%xmm3,%%xmm1 \n" - "pand %%xmm4,%%xmm2 \n" - "pand %%xmm5,%%xmm0 \n" - "por %%xmm2,%%xmm1 \n" - "por %%xmm1,%%xmm0 \n" - "packssdw %%xmm0,%%xmm0 \n" - "lea 0x10(%0),%0 \n" - "movq %%xmm0,(%1) \n" - "lea 0x8(%1),%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : "m"(dither4) // %3 - : "memory", "cc", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} - -#ifdef HAS_ARGBTORGB565DITHERROW_AVX2 -void ARGBToRGB565DitherRow_AVX2(const uint8* src, uint8* dst, - const uint32 dither4, int width) { - asm volatile ( - "vbroadcastss %3,%%xmm6 \n" - "vpunpcklbw %%xmm6,%%xmm6,%%xmm6 \n" - "vpermq $0xd8,%%ymm6,%%ymm6 \n" - "vpunpcklwd %%ymm6,%%ymm6,%%ymm6 \n" - "vpcmpeqb %%ymm3,%%ymm3,%%ymm3 \n" - "vpsrld $0x1b,%%ymm3,%%ymm3 \n" - "vpcmpeqb %%ymm4,%%ymm4,%%ymm4 \n" - "vpsrld $0x1a,%%ymm4,%%ymm4 \n" - "vpslld $0x5,%%ymm4,%%ymm4 \n" - "vpslld $0xb,%%ymm3,%%ymm5 \n" - - LABELALIGN - "1: \n" - "vmovdqu (%0),%%ymm0 \n" - "vpaddusb %%ymm6,%%ymm0,%%ymm0 \n" - "vpsrld $0x5,%%ymm0,%%ymm2 \n" - "vpsrld $0x3,%%ymm0,%%ymm1 \n" - "vpsrld $0x8,%%ymm0,%%ymm0 \n" - "vpand %%ymm4,%%ymm2,%%ymm2 \n" - "vpand %%ymm3,%%ymm1,%%ymm1 \n" - "vpand %%ymm5,%%ymm0,%%ymm0 \n" - "vpor %%ymm2,%%ymm1,%%ymm1 \n" - "vpor %%ymm1,%%ymm0,%%ymm0 \n" - "vpackusdw %%ymm0,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "lea 0x20(%0),%0 \n" - "vmovdqu %%xmm0,(%1) \n" - "lea 0x10(%1),%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : "m"(dither4) // %3 - : "memory", "cc", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBTORGB565DITHERROW_AVX2 - - -void ARGBToARGB1555Row_SSE2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "pcmpeqb %%xmm4,%%xmm4 \n" - "psrld $0x1b,%%xmm4 \n" - "movdqa %%xmm4,%%xmm5 \n" - "pslld $0x5,%%xmm5 \n" - "movdqa %%xmm4,%%xmm6 \n" - "pslld $0xa,%%xmm6 \n" - "pcmpeqb %%xmm7,%%xmm7 \n" - "pslld $0xf,%%xmm7 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "movdqa %%xmm0,%%xmm3 \n" - "psrad $0x10,%%xmm0 \n" - "psrld $0x3,%%xmm1 \n" - "psrld $0x6,%%xmm2 \n" - "psrld $0x9,%%xmm3 \n" - "pand %%xmm7,%%xmm0 \n" - "pand %%xmm4,%%xmm1 \n" - "pand %%xmm5,%%xmm2 \n" - "pand %%xmm6,%%xmm3 \n" - "por %%xmm1,%%xmm0 \n" - "por %%xmm3,%%xmm2 \n" - "por %%xmm2,%%xmm0 \n" - "packssdw %%xmm0,%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - :: "memory", "cc", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} - -void ARGBToARGB4444Row_SSE2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "pcmpeqb %%xmm4,%%xmm4 \n" - "psllw $0xc,%%xmm4 \n" - "movdqa %%xmm4,%%xmm3 \n" - "psrlw $0x8,%%xmm3 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "pand %%xmm3,%%xmm0 \n" - "pand %%xmm4,%%xmm1 \n" - "psrlq $0x4,%%xmm0 \n" - "psrlq $0x8,%%xmm1 \n" - "por %%xmm1,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - :: "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4" - ); -} -#endif // HAS_RGB24TOARGBROW_SSSE3 - -#ifdef HAS_ARGBTOYROW_SSSE3 -// Convert 16 ARGB pixels (64 bytes) to 16 Y values. -void ARGBToYRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width) { - asm volatile ( - "movdqa %3,%%xmm4 \n" - "movdqa %4,%%xmm5 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "phaddw %%xmm1,%%xmm0 \n" - "phaddw %%xmm3,%%xmm2 \n" - "psrlw $0x7,%%xmm0 \n" - "psrlw $0x7,%%xmm2 \n" - "packuswb %%xmm2,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : "m"(kARGBToY), // %3 - "m"(kAddY16) // %4 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_ARGBTOYROW_SSSE3 - -#ifdef HAS_ARGBTOYJROW_SSSE3 -// Convert 16 ARGB pixels (64 bytes) to 16 YJ values. -// Same as ARGBToYRow but different coefficients, no add 16, but do rounding. -void ARGBToYJRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width) { - asm volatile ( - "movdqa %3,%%xmm4 \n" - "movdqa %4,%%xmm5 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "phaddw %%xmm1,%%xmm0 \n" - "phaddw %%xmm3,%%xmm2 \n" - "paddw %%xmm5,%%xmm0 \n" - "paddw %%xmm5,%%xmm2 \n" - "psrlw $0x7,%%xmm0 \n" - "psrlw $0x7,%%xmm2 \n" - "packuswb %%xmm2,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : "m"(kARGBToYJ), // %3 - "m"(kAddYJ64) // %4 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_ARGBTOYJROW_SSSE3 - -#ifdef HAS_ARGBTOYROW_AVX2 -// vpermd for vphaddw + vpackuswb vpermd. -static const lvec32 kPermdARGBToY_AVX = { - 0, 4, 1, 5, 2, 6, 3, 7 -}; - -// Convert 32 ARGB pixels (128 bytes) to 32 Y values. -void ARGBToYRow_AVX2(const uint8* src_argb, uint8* dst_y, int width) { - asm volatile ( - "vbroadcastf128 %3,%%ymm4 \n" - "vbroadcastf128 %4,%%ymm5 \n" - "vmovdqu %5,%%ymm6 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "vmovdqu " MEMACCESS2(0x40,0) ",%%ymm2 \n" - "vmovdqu " MEMACCESS2(0x60,0) ",%%ymm3 \n" - "vpmaddubsw %%ymm4,%%ymm0,%%ymm0 \n" - "vpmaddubsw %%ymm4,%%ymm1,%%ymm1 \n" - "vpmaddubsw %%ymm4,%%ymm2,%%ymm2 \n" - "vpmaddubsw %%ymm4,%%ymm3,%%ymm3 \n" - "lea " MEMLEA(0x80,0) ",%0 \n" - "vphaddw %%ymm1,%%ymm0,%%ymm0 \n" // mutates. - "vphaddw %%ymm3,%%ymm2,%%ymm2 \n" - "vpsrlw $0x7,%%ymm0,%%ymm0 \n" - "vpsrlw $0x7,%%ymm2,%%ymm2 \n" - "vpackuswb %%ymm2,%%ymm0,%%ymm0 \n" // mutates. - "vpermd %%ymm0,%%ymm6,%%ymm0 \n" // unmutate. - "vpaddb %%ymm5,%%ymm0,%%ymm0 \n" // add 16 for Y - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : "m"(kARGBToY), // %3 - "m"(kAddY16), // %4 - "m"(kPermdARGBToY_AVX) // %5 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} -#endif // HAS_ARGBTOYROW_AVX2 - -#ifdef HAS_ARGBTOYJROW_AVX2 -// Convert 32 ARGB pixels (128 bytes) to 32 Y values. -void ARGBToYJRow_AVX2(const uint8* src_argb, uint8* dst_y, int width) { - asm volatile ( - "vbroadcastf128 %3,%%ymm4 \n" - "vbroadcastf128 %4,%%ymm5 \n" - "vmovdqu %5,%%ymm6 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "vmovdqu " MEMACCESS2(0x40,0) ",%%ymm2 \n" - "vmovdqu " MEMACCESS2(0x60,0) ",%%ymm3 \n" - "vpmaddubsw %%ymm4,%%ymm0,%%ymm0 \n" - "vpmaddubsw %%ymm4,%%ymm1,%%ymm1 \n" - "vpmaddubsw %%ymm4,%%ymm2,%%ymm2 \n" - "vpmaddubsw %%ymm4,%%ymm3,%%ymm3 \n" - "lea " MEMLEA(0x80,0) ",%0 \n" - "vphaddw %%ymm1,%%ymm0,%%ymm0 \n" // mutates. - "vphaddw %%ymm3,%%ymm2,%%ymm2 \n" - "vpaddw %%ymm5,%%ymm0,%%ymm0 \n" // Add .5 for rounding. - "vpaddw %%ymm5,%%ymm2,%%ymm2 \n" - "vpsrlw $0x7,%%ymm0,%%ymm0 \n" - "vpsrlw $0x7,%%ymm2,%%ymm2 \n" - "vpackuswb %%ymm2,%%ymm0,%%ymm0 \n" // mutates. - "vpermd %%ymm0,%%ymm6,%%ymm0 \n" // unmutate. - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : "m"(kARGBToYJ), // %3 - "m"(kAddYJ64), // %4 - "m"(kPermdARGBToY_AVX) // %5 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} -#endif // HAS_ARGBTOYJROW_AVX2 - -#ifdef HAS_ARGBTOUVROW_SSSE3 -void ARGBToUVRow_SSSE3(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "movdqa %5,%%xmm3 \n" - "movdqa %6,%%xmm4 \n" - "movdqa %7,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,4,1,xmm7) // movdqu (%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x10,0,4,1,xmm7) // movdqu 0x10(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - MEMOPREG(movdqu,0x20,0,4,1,xmm7) // movdqu 0x20(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x30,0,4,1,xmm7) // movdqu 0x30(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm6 \n" - - "lea " MEMLEA(0x40,0) ",%0 \n" - "movdqa %%xmm0,%%xmm7 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm7 \n" - "pavgb %%xmm7,%%xmm0 \n" - "movdqa %%xmm2,%%xmm7 \n" - "shufps $0x88,%%xmm6,%%xmm2 \n" - "shufps $0xdd,%%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm2,%%xmm6 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm6 \n" - "phaddw %%xmm2,%%xmm0 \n" - "phaddw %%xmm6,%%xmm1 \n" - "psraw $0x8,%%xmm0 \n" - "psraw $0x8,%%xmm1 \n" - "packsswb %%xmm1,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movlps %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movhps,xmm0,0x00,1,2,1) // movhps %%xmm0,(%1,%2,1) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_argb0), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "r"((intptr_t)(src_stride_argb)), // %4 - "m"(kARGBToV), // %5 - "m"(kARGBToU), // %6 - "m"(kAddUV128) // %7 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBTOUVROW_SSSE3 - -#ifdef HAS_ARGBTOUVROW_AVX2 -// vpshufb for vphaddw + vpackuswb packed to shorts. -static const lvec8 kShufARGBToUV_AVX = { - 0, 1, 8, 9, 2, 3, 10, 11, 4, 5, 12, 13, 6, 7, 14, 15, - 0, 1, 8, 9, 2, 3, 10, 11, 4, 5, 12, 13, 6, 7, 14, 15 -}; -void ARGBToUVRow_AVX2(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "vbroadcastf128 %5,%%ymm5 \n" - "vbroadcastf128 %6,%%ymm6 \n" - "vbroadcastf128 %7,%%ymm7 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "vmovdqu " MEMACCESS2(0x40,0) ",%%ymm2 \n" - "vmovdqu " MEMACCESS2(0x60,0) ",%%ymm3 \n" - VMEMOPREG(vpavgb,0x00,0,4,1,ymm0,ymm0) // vpavgb (%0,%4,1),%%ymm0,%%ymm0 - VMEMOPREG(vpavgb,0x20,0,4,1,ymm1,ymm1) - VMEMOPREG(vpavgb,0x40,0,4,1,ymm2,ymm2) - VMEMOPREG(vpavgb,0x60,0,4,1,ymm3,ymm3) - "lea " MEMLEA(0x80,0) ",%0 \n" - "vshufps $0x88,%%ymm1,%%ymm0,%%ymm4 \n" - "vshufps $0xdd,%%ymm1,%%ymm0,%%ymm0 \n" - "vpavgb %%ymm4,%%ymm0,%%ymm0 \n" - "vshufps $0x88,%%ymm3,%%ymm2,%%ymm4 \n" - "vshufps $0xdd,%%ymm3,%%ymm2,%%ymm2 \n" - "vpavgb %%ymm4,%%ymm2,%%ymm2 \n" - - "vpmaddubsw %%ymm7,%%ymm0,%%ymm1 \n" - "vpmaddubsw %%ymm7,%%ymm2,%%ymm3 \n" - "vpmaddubsw %%ymm6,%%ymm0,%%ymm0 \n" - "vpmaddubsw %%ymm6,%%ymm2,%%ymm2 \n" - "vphaddw %%ymm3,%%ymm1,%%ymm1 \n" - "vphaddw %%ymm2,%%ymm0,%%ymm0 \n" - "vpsraw $0x8,%%ymm1,%%ymm1 \n" - "vpsraw $0x8,%%ymm0,%%ymm0 \n" - "vpacksswb %%ymm0,%%ymm1,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpshufb %8,%%ymm0,%%ymm0 \n" - "vpaddb %%ymm5,%%ymm0,%%ymm0 \n" - - "vextractf128 $0x0,%%ymm0," MEMACCESS(1) " \n" - VEXTOPMEM(vextractf128,1,ymm0,0x0,1,2,1) // vextractf128 $1,%%ymm0,(%1,%2,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x20,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb0), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "r"((intptr_t)(src_stride_argb)), // %4 - "m"(kAddUV128), // %5 - "m"(kARGBToV), // %6 - "m"(kARGBToU), // %7 - "m"(kShufARGBToUV_AVX) // %8 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBTOUVROW_AVX2 - -#ifdef HAS_ARGBTOUVJROW_AVX2 -void ARGBToUVJRow_AVX2(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "vbroadcastf128 %5,%%ymm5 \n" - "vbroadcastf128 %6,%%ymm6 \n" - "vbroadcastf128 %7,%%ymm7 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "vmovdqu " MEMACCESS2(0x40,0) ",%%ymm2 \n" - "vmovdqu " MEMACCESS2(0x60,0) ",%%ymm3 \n" - VMEMOPREG(vpavgb,0x00,0,4,1,ymm0,ymm0) // vpavgb (%0,%4,1),%%ymm0,%%ymm0 - VMEMOPREG(vpavgb,0x20,0,4,1,ymm1,ymm1) - VMEMOPREG(vpavgb,0x40,0,4,1,ymm2,ymm2) - VMEMOPREG(vpavgb,0x60,0,4,1,ymm3,ymm3) - "lea " MEMLEA(0x80,0) ",%0 \n" - "vshufps $0x88,%%ymm1,%%ymm0,%%ymm4 \n" - "vshufps $0xdd,%%ymm1,%%ymm0,%%ymm0 \n" - "vpavgb %%ymm4,%%ymm0,%%ymm0 \n" - "vshufps $0x88,%%ymm3,%%ymm2,%%ymm4 \n" - "vshufps $0xdd,%%ymm3,%%ymm2,%%ymm2 \n" - "vpavgb %%ymm4,%%ymm2,%%ymm2 \n" - - "vpmaddubsw %%ymm7,%%ymm0,%%ymm1 \n" - "vpmaddubsw %%ymm7,%%ymm2,%%ymm3 \n" - "vpmaddubsw %%ymm6,%%ymm0,%%ymm0 \n" - "vpmaddubsw %%ymm6,%%ymm2,%%ymm2 \n" - "vphaddw %%ymm3,%%ymm1,%%ymm1 \n" - "vphaddw %%ymm2,%%ymm0,%%ymm0 \n" - "vpaddw %%ymm5,%%ymm0,%%ymm0 \n" - "vpaddw %%ymm5,%%ymm1,%%ymm1 \n" - "vpsraw $0x8,%%ymm1,%%ymm1 \n" - "vpsraw $0x8,%%ymm0,%%ymm0 \n" - "vpacksswb %%ymm0,%%ymm1,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpshufb %8,%%ymm0,%%ymm0 \n" - - "vextractf128 $0x0,%%ymm0," MEMACCESS(1) " \n" - VEXTOPMEM(vextractf128,1,ymm0,0x0,1,2,1) // vextractf128 $1,%%ymm0,(%1,%2,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x20,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb0), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "r"((intptr_t)(src_stride_argb)), // %4 - "m"(kAddUVJ128), // %5 - "m"(kARGBToVJ), // %6 - "m"(kARGBToUJ), // %7 - "m"(kShufARGBToUV_AVX) // %8 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBTOUVJROW_AVX2 - -#ifdef HAS_ARGBTOUVJROW_SSSE3 -void ARGBToUVJRow_SSSE3(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "movdqa %5,%%xmm3 \n" - "movdqa %6,%%xmm4 \n" - "movdqa %7,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,4,1,xmm7) // movdqu (%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x10,0,4,1,xmm7) // movdqu 0x10(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - MEMOPREG(movdqu,0x20,0,4,1,xmm7) // movdqu 0x20(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x30,0,4,1,xmm7) // movdqu 0x30(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm6 \n" - - "lea " MEMLEA(0x40,0) ",%0 \n" - "movdqa %%xmm0,%%xmm7 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm7 \n" - "pavgb %%xmm7,%%xmm0 \n" - "movdqa %%xmm2,%%xmm7 \n" - "shufps $0x88,%%xmm6,%%xmm2 \n" - "shufps $0xdd,%%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm2,%%xmm6 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm6 \n" - "phaddw %%xmm2,%%xmm0 \n" - "phaddw %%xmm6,%%xmm1 \n" - "paddw %%xmm5,%%xmm0 \n" - "paddw %%xmm5,%%xmm1 \n" - "psraw $0x8,%%xmm0 \n" - "psraw $0x8,%%xmm1 \n" - "packsswb %%xmm1,%%xmm0 \n" - "movlps %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movhps,xmm0,0x00,1,2,1) // movhps %%xmm0,(%1,%2,1) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_argb0), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "r"((intptr_t)(src_stride_argb)), // %4 - "m"(kARGBToVJ), // %5 - "m"(kARGBToUJ), // %6 - "m"(kAddUVJ128) // %7 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBTOUVJROW_SSSE3 - -#ifdef HAS_ARGBTOUV444ROW_SSSE3 -void ARGBToUV444Row_SSSE3(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "movdqa %4,%%xmm3 \n" - "movdqa %5,%%xmm4 \n" - "movdqa %6,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm6 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm6 \n" - "phaddw %%xmm1,%%xmm0 \n" - "phaddw %%xmm6,%%xmm2 \n" - "psraw $0x8,%%xmm0 \n" - "psraw $0x8,%%xmm2 \n" - "packsswb %%xmm2,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm6 \n" - "pmaddubsw %%xmm3,%%xmm0 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm2 \n" - "pmaddubsw %%xmm3,%%xmm6 \n" - "phaddw %%xmm1,%%xmm0 \n" - "phaddw %%xmm6,%%xmm2 \n" - "psraw $0x8,%%xmm0 \n" - "psraw $0x8,%%xmm2 \n" - "packsswb %%xmm2,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - MEMOPMEM(movdqu,xmm0,0x00,1,2,1) // movdqu %%xmm0,(%1,%2,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "m"(kARGBToV), // %4 - "m"(kARGBToU), // %5 - "m"(kAddUV128) // %6 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm6" - ); -} -#endif // HAS_ARGBTOUV444ROW_SSSE3 - -void BGRAToYRow_SSSE3(const uint8* src_bgra, uint8* dst_y, int width) { - asm volatile ( - "movdqa %4,%%xmm5 \n" - "movdqa %3,%%xmm4 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "phaddw %%xmm1,%%xmm0 \n" - "phaddw %%xmm3,%%xmm2 \n" - "psrlw $0x7,%%xmm0 \n" - "psrlw $0x7,%%xmm2 \n" - "packuswb %%xmm2,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_bgra), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : "m"(kBGRAToY), // %3 - "m"(kAddY16) // %4 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void BGRAToUVRow_SSSE3(const uint8* src_bgra0, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "movdqa %5,%%xmm3 \n" - "movdqa %6,%%xmm4 \n" - "movdqa %7,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,4,1,xmm7) // movdqu (%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x10,0,4,1,xmm7) // movdqu 0x10(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - MEMOPREG(movdqu,0x20,0,4,1,xmm7) // movdqu 0x20(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x30,0,4,1,xmm7) // movdqu 0x30(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm6 \n" - - "lea " MEMLEA(0x40,0) ",%0 \n" - "movdqa %%xmm0,%%xmm7 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm7 \n" - "pavgb %%xmm7,%%xmm0 \n" - "movdqa %%xmm2,%%xmm7 \n" - "shufps $0x88,%%xmm6,%%xmm2 \n" - "shufps $0xdd,%%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm2,%%xmm6 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm6 \n" - "phaddw %%xmm2,%%xmm0 \n" - "phaddw %%xmm6,%%xmm1 \n" - "psraw $0x8,%%xmm0 \n" - "psraw $0x8,%%xmm1 \n" - "packsswb %%xmm1,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movlps %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movhps,xmm0,0x00,1,2,1) // movhps %%xmm0,(%1,%2,1) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_bgra0), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "r"((intptr_t)(src_stride_bgra)), // %4 - "m"(kBGRAToV), // %5 - "m"(kBGRAToU), // %6 - "m"(kAddUV128) // %7 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm6", "xmm7" - ); -} - -void ABGRToYRow_SSSE3(const uint8* src_abgr, uint8* dst_y, int width) { - asm volatile ( - "movdqa %4,%%xmm5 \n" - "movdqa %3,%%xmm4 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "phaddw %%xmm1,%%xmm0 \n" - "phaddw %%xmm3,%%xmm2 \n" - "psrlw $0x7,%%xmm0 \n" - "psrlw $0x7,%%xmm2 \n" - "packuswb %%xmm2,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_abgr), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : "m"(kABGRToY), // %3 - "m"(kAddY16) // %4 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void RGBAToYRow_SSSE3(const uint8* src_rgba, uint8* dst_y, int width) { - asm volatile ( - "movdqa %4,%%xmm5 \n" - "movdqa %3,%%xmm4 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "phaddw %%xmm1,%%xmm0 \n" - "phaddw %%xmm3,%%xmm2 \n" - "psrlw $0x7,%%xmm0 \n" - "psrlw $0x7,%%xmm2 \n" - "packuswb %%xmm2,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_rgba), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : "m"(kRGBAToY), // %3 - "m"(kAddY16) // %4 - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void ABGRToUVRow_SSSE3(const uint8* src_abgr0, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "movdqa %5,%%xmm3 \n" - "movdqa %6,%%xmm4 \n" - "movdqa %7,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,4,1,xmm7) // movdqu (%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x10,0,4,1,xmm7) // movdqu 0x10(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - MEMOPREG(movdqu,0x20,0,4,1,xmm7) // movdqu 0x20(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x30,0,4,1,xmm7) // movdqu 0x30(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm6 \n" - - "lea " MEMLEA(0x40,0) ",%0 \n" - "movdqa %%xmm0,%%xmm7 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm7 \n" - "pavgb %%xmm7,%%xmm0 \n" - "movdqa %%xmm2,%%xmm7 \n" - "shufps $0x88,%%xmm6,%%xmm2 \n" - "shufps $0xdd,%%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm2,%%xmm6 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm6 \n" - "phaddw %%xmm2,%%xmm0 \n" - "phaddw %%xmm6,%%xmm1 \n" - "psraw $0x8,%%xmm0 \n" - "psraw $0x8,%%xmm1 \n" - "packsswb %%xmm1,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movlps %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movhps,xmm0,0x00,1,2,1) // movhps %%xmm0,(%1,%2,1) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_abgr0), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "r"((intptr_t)(src_stride_abgr)), // %4 - "m"(kABGRToV), // %5 - "m"(kABGRToU), // %6 - "m"(kAddUV128) // %7 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm6", "xmm7" - ); -} - -void RGBAToUVRow_SSSE3(const uint8* src_rgba0, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "movdqa %5,%%xmm3 \n" - "movdqa %6,%%xmm4 \n" - "movdqa %7,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,4,1,xmm7) // movdqu (%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x10,0,4,1,xmm7) // movdqu 0x10(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - MEMOPREG(movdqu,0x20,0,4,1,xmm7) // movdqu 0x20(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x30,0,4,1,xmm7) // movdqu 0x30(%0,%4,1),%%xmm7 - "pavgb %%xmm7,%%xmm6 \n" - - "lea " MEMLEA(0x40,0) ",%0 \n" - "movdqa %%xmm0,%%xmm7 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm7 \n" - "pavgb %%xmm7,%%xmm0 \n" - "movdqa %%xmm2,%%xmm7 \n" - "shufps $0x88,%%xmm6,%%xmm2 \n" - "shufps $0xdd,%%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm2,%%xmm6 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm6 \n" - "phaddw %%xmm2,%%xmm0 \n" - "phaddw %%xmm6,%%xmm1 \n" - "psraw $0x8,%%xmm0 \n" - "psraw $0x8,%%xmm1 \n" - "packsswb %%xmm1,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movlps %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movhps,xmm0,0x00,1,2,1) // movhps %%xmm0,(%1,%2,1) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_rgba0), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "r"((intptr_t)(src_stride_rgba)), // %4 - "m"(kRGBAToV), // %5 - "m"(kRGBAToU), // %6 - "m"(kAddUV128) // %7 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm6", "xmm7" - ); -} - -#if defined(HAS_I422TOARGBROW_SSSE3) || defined(HAS_I422TOARGBROW_AVX2) - -// Read 8 UV from 444 -#define READYUV444 \ - "movq " MEMACCESS([u_buf]) ",%%xmm0 \n" \ - MEMOPREG(movq, 0x00, [u_buf], [v_buf], 1, xmm1) \ - "lea " MEMLEA(0x8, [u_buf]) ",%[u_buf] \n" \ - "punpcklbw %%xmm1,%%xmm0 \n" \ - "movq " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "punpcklbw %%xmm4,%%xmm4 \n" \ - "lea " MEMLEA(0x8, [y_buf]) ",%[y_buf] \n" - -// Read 4 UV from 422, upsample to 8 UV -#define READYUV422 \ - "movd " MEMACCESS([u_buf]) ",%%xmm0 \n" \ - MEMOPREG(movd, 0x00, [u_buf], [v_buf], 1, xmm1) \ - "lea " MEMLEA(0x4, [u_buf]) ",%[u_buf] \n" \ - "punpcklbw %%xmm1,%%xmm0 \n" \ - "punpcklwd %%xmm0,%%xmm0 \n" \ - "movq " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "punpcklbw %%xmm4,%%xmm4 \n" \ - "lea " MEMLEA(0x8, [y_buf]) ",%[y_buf] \n" - -// Read 4 UV from 422, upsample to 8 UV. With 8 Alpha. -#define READYUVA422 \ - "movd " MEMACCESS([u_buf]) ",%%xmm0 \n" \ - MEMOPREG(movd, 0x00, [u_buf], [v_buf], 1, xmm1) \ - "lea " MEMLEA(0x4, [u_buf]) ",%[u_buf] \n" \ - "punpcklbw %%xmm1,%%xmm0 \n" \ - "punpcklwd %%xmm0,%%xmm0 \n" \ - "movq " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "punpcklbw %%xmm4,%%xmm4 \n" \ - "lea " MEMLEA(0x8, [y_buf]) ",%[y_buf] \n" \ - "movq " MEMACCESS([a_buf]) ",%%xmm5 \n" \ - "lea " MEMLEA(0x8, [a_buf]) ",%[a_buf] \n" - -// Read 2 UV from 411, upsample to 8 UV. -// reading 4 bytes is an msan violation. -// "movd " MEMACCESS([u_buf]) ",%%xmm0 \n" -// MEMOPREG(movd, 0x00, [u_buf], [v_buf], 1, xmm1) -// pinsrw fails with drmemory -// __asm pinsrw xmm0, [esi], 0 /* U */ -// __asm pinsrw xmm1, [esi + edi], 0 /* V */ -#define READYUV411_TEMP \ - "movzwl " MEMACCESS([u_buf]) ",%[temp] \n" \ - "movd %[temp],%%xmm0 \n" \ - MEMOPARG(movzwl, 0x00, [u_buf], [v_buf], 1, [temp]) " \n" \ - "movd %[temp],%%xmm1 \n" \ - "lea " MEMLEA(0x2, [u_buf]) ",%[u_buf] \n" \ - "punpcklbw %%xmm1,%%xmm0 \n" \ - "punpcklwd %%xmm0,%%xmm0 \n" \ - "punpckldq %%xmm0,%%xmm0 \n" \ - "movq " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "punpcklbw %%xmm4,%%xmm4 \n" \ - "lea " MEMLEA(0x8, [y_buf]) ",%[y_buf] \n" - -// Read 4 UV from NV12, upsample to 8 UV -#define READNV12 \ - "movq " MEMACCESS([uv_buf]) ",%%xmm0 \n" \ - "lea " MEMLEA(0x8, [uv_buf]) ",%[uv_buf] \n" \ - "punpcklwd %%xmm0,%%xmm0 \n" \ - "movq " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "punpcklbw %%xmm4,%%xmm4 \n" \ - "lea " MEMLEA(0x8, [y_buf]) ",%[y_buf] \n" - -// Read 4 VU from NV21, upsample to 8 UV -#define READNV21 \ - "movq " MEMACCESS([vu_buf]) ",%%xmm0 \n" \ - "lea " MEMLEA(0x8, [vu_buf]) ",%[vu_buf] \n" \ - "pshufb %[kShuffleNV21], %%xmm0 \n" \ - "movq " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "punpcklbw %%xmm4,%%xmm4 \n" \ - "lea " MEMLEA(0x8, [y_buf]) ",%[y_buf] \n" - -// Read 4 YUY2 with 8 Y and update 4 UV to 8 UV. -#define READYUY2 \ - "movdqu " MEMACCESS([yuy2_buf]) ",%%xmm4 \n" \ - "pshufb %[kShuffleYUY2Y], %%xmm4 \n" \ - "movdqu " MEMACCESS([yuy2_buf]) ",%%xmm0 \n" \ - "pshufb %[kShuffleYUY2UV], %%xmm0 \n" \ - "lea " MEMLEA(0x10, [yuy2_buf]) ",%[yuy2_buf] \n" - -// Read 4 UYVY with 8 Y and update 4 UV to 8 UV. -#define READUYVY \ - "movdqu " MEMACCESS([uyvy_buf]) ",%%xmm4 \n" \ - "pshufb %[kShuffleUYVYY], %%xmm4 \n" \ - "movdqu " MEMACCESS([uyvy_buf]) ",%%xmm0 \n" \ - "pshufb %[kShuffleUYVYUV], %%xmm0 \n" \ - "lea " MEMLEA(0x10, [uyvy_buf]) ",%[uyvy_buf] \n" - -#if defined(__x86_64__) -#define YUVTORGB_SETUP(yuvconstants) \ - "movdqa " MEMACCESS([yuvconstants]) ",%%xmm8 \n" \ - "movdqa " MEMACCESS2(32, [yuvconstants]) ",%%xmm9 \n" \ - "movdqa " MEMACCESS2(64, [yuvconstants]) ",%%xmm10 \n" \ - "movdqa " MEMACCESS2(96, [yuvconstants]) ",%%xmm11 \n" \ - "movdqa " MEMACCESS2(128, [yuvconstants]) ",%%xmm12 \n" \ - "movdqa " MEMACCESS2(160, [yuvconstants]) ",%%xmm13 \n" \ - "movdqa " MEMACCESS2(192, [yuvconstants]) ",%%xmm14 \n" -// Convert 8 pixels: 8 UV and 8 Y -#define YUVTORGB(yuvconstants) \ - "movdqa %%xmm0,%%xmm1 \n" \ - "movdqa %%xmm0,%%xmm2 \n" \ - "movdqa %%xmm0,%%xmm3 \n" \ - "movdqa %%xmm11,%%xmm0 \n" \ - "pmaddubsw %%xmm8,%%xmm1 \n" \ - "psubw %%xmm1,%%xmm0 \n" \ - "movdqa %%xmm12,%%xmm1 \n" \ - "pmaddubsw %%xmm9,%%xmm2 \n" \ - "psubw %%xmm2,%%xmm1 \n" \ - "movdqa %%xmm13,%%xmm2 \n" \ - "pmaddubsw %%xmm10,%%xmm3 \n" \ - "psubw %%xmm3,%%xmm2 \n" \ - "pmulhuw %%xmm14,%%xmm4 \n" \ - "paddsw %%xmm4,%%xmm0 \n" \ - "paddsw %%xmm4,%%xmm1 \n" \ - "paddsw %%xmm4,%%xmm2 \n" \ - "psraw $0x6,%%xmm0 \n" \ - "psraw $0x6,%%xmm1 \n" \ - "psraw $0x6,%%xmm2 \n" \ - "packuswb %%xmm0,%%xmm0 \n" \ - "packuswb %%xmm1,%%xmm1 \n" \ - "packuswb %%xmm2,%%xmm2 \n" -#define YUVTORGB_REGS \ - "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", - -#else -#define YUVTORGB_SETUP(yuvconstants) -// Convert 8 pixels: 8 UV and 8 Y -#define YUVTORGB(yuvconstants) \ - "movdqa %%xmm0,%%xmm1 \n" \ - "movdqa %%xmm0,%%xmm2 \n" \ - "movdqa %%xmm0,%%xmm3 \n" \ - "movdqa " MEMACCESS2(96, [yuvconstants]) ",%%xmm0 \n" \ - "pmaddubsw " MEMACCESS([yuvconstants]) ",%%xmm1 \n" \ - "psubw %%xmm1,%%xmm0 \n" \ - "movdqa " MEMACCESS2(128, [yuvconstants]) ",%%xmm1 \n" \ - "pmaddubsw " MEMACCESS2(32, [yuvconstants]) ",%%xmm2 \n" \ - "psubw %%xmm2,%%xmm1 \n" \ - "movdqa " MEMACCESS2(160, [yuvconstants]) ",%%xmm2 \n" \ - "pmaddubsw " MEMACCESS2(64, [yuvconstants]) ",%%xmm3 \n" \ - "psubw %%xmm3,%%xmm2 \n" \ - "pmulhuw " MEMACCESS2(192, [yuvconstants]) ",%%xmm4 \n" \ - "paddsw %%xmm4,%%xmm0 \n" \ - "paddsw %%xmm4,%%xmm1 \n" \ - "paddsw %%xmm4,%%xmm2 \n" \ - "psraw $0x6,%%xmm0 \n" \ - "psraw $0x6,%%xmm1 \n" \ - "psraw $0x6,%%xmm2 \n" \ - "packuswb %%xmm0,%%xmm0 \n" \ - "packuswb %%xmm1,%%xmm1 \n" \ - "packuswb %%xmm2,%%xmm2 \n" -#define YUVTORGB_REGS -#endif - -// Store 8 ARGB values. -#define STOREARGB \ - "punpcklbw %%xmm1,%%xmm0 \n" \ - "punpcklbw %%xmm5,%%xmm2 \n" \ - "movdqa %%xmm0,%%xmm1 \n" \ - "punpcklwd %%xmm2,%%xmm0 \n" \ - "punpckhwd %%xmm2,%%xmm1 \n" \ - "movdqu %%xmm0," MEMACCESS([dst_argb]) " \n" \ - "movdqu %%xmm1," MEMACCESS2(0x10, [dst_argb]) " \n" \ - "lea " MEMLEA(0x20, [dst_argb]) ", %[dst_argb] \n" - -// Store 8 RGBA values. -#define STORERGBA \ - "pcmpeqb %%xmm5,%%xmm5 \n" \ - "punpcklbw %%xmm2,%%xmm1 \n" \ - "punpcklbw %%xmm0,%%xmm5 \n" \ - "movdqa %%xmm5,%%xmm0 \n" \ - "punpcklwd %%xmm1,%%xmm5 \n" \ - "punpckhwd %%xmm1,%%xmm0 \n" \ - "movdqu %%xmm5," MEMACCESS([dst_rgba]) " \n" \ - "movdqu %%xmm0," MEMACCESS2(0x10, [dst_rgba]) " \n" \ - "lea " MEMLEA(0x20, [dst_rgba]) ",%[dst_rgba] \n" - -void OMITFP I444ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - READYUV444 - YUVTORGB(yuvconstants) - STOREARGB - "sub $0x8,%[width] \n" - "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void OMITFP I422ToRGB24Row_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "movdqa %[kShuffleMaskARGBToRGB24_0],%%xmm5 \n" - "movdqa %[kShuffleMaskARGBToRGB24],%%xmm6 \n" - "sub %[u_buf],%[v_buf] \n" - LABELALIGN - "1: \n" - READYUV422 - YUVTORGB(yuvconstants) - "punpcklbw %%xmm1,%%xmm0 \n" - "punpcklbw %%xmm2,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklwd %%xmm2,%%xmm0 \n" - "punpckhwd %%xmm2,%%xmm1 \n" - "pshufb %%xmm5,%%xmm0 \n" - "pshufb %%xmm6,%%xmm1 \n" - "palignr $0xc,%%xmm0,%%xmm1 \n" - "movq %%xmm0," MEMACCESS([dst_rgb24]) "\n" - "movdqu %%xmm1," MEMACCESS2(0x8,[dst_rgb24]) "\n" - "lea " MEMLEA(0x18,[dst_rgb24]) ",%[dst_rgb24] \n" - "subl $0x8,%[width] \n" - "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_rgb24]"+r"(dst_rgb24), // %[dst_rgb24] -#if defined(__i386__) && defined(__pic__) - [width]"+m"(width) // %[width] -#else - [width]"+rm"(width) // %[width] -#endif - : [yuvconstants]"r"(yuvconstants), // %[yuvconstants] - [kShuffleMaskARGBToRGB24_0]"m"(kShuffleMaskARGBToRGB24_0), - [kShuffleMaskARGBToRGB24]"m"(kShuffleMaskARGBToRGB24) - : "memory", "cc", NACL_R14 YUVTORGB_REGS - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} - -void OMITFP I422ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - READYUV422 - YUVTORGB(yuvconstants) - STOREARGB - "sub $0x8,%[width] \n" - "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -#ifdef HAS_I422ALPHATOARGBROW_SSSE3 -void OMITFP I422AlphaToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - LABELALIGN - "1: \n" - READYUVA422 - YUVTORGB(yuvconstants) - STOREARGB - "subl $0x8,%[width] \n" - "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [a_buf]"+r"(a_buf), // %[a_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] -#if defined(__i386__) && defined(__pic__) - [width]"+m"(width) // %[width] -#else - [width]"+rm"(width) // %[width] -#endif - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_I422ALPHATOARGBROW_SSSE3 - -#ifdef HAS_I411TOARGBROW_SSSE3 -void OMITFP I411ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - int temp; - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - READYUV411_TEMP - YUVTORGB(yuvconstants) - STOREARGB - "subl $0x8,%[width] \n" - "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [temp]"=&r"(temp), // %[temp] -#if defined(__i386__) && defined(__pic__) - [width]"+m"(width) // %[width] -#else - [width]"+rm"(width) // %[width] -#endif - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif - -void OMITFP NV12ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* uv_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "pcmpeqb %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - READNV12 - YUVTORGB(yuvconstants) - STOREARGB - "sub $0x8,%[width] \n" - "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [uv_buf]"+r"(uv_buf), // %[uv_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", YUVTORGB_REGS // Does not use r14. - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void OMITFP NV21ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* vu_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "pcmpeqb %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - READNV21 - YUVTORGB(yuvconstants) - STOREARGB - "sub $0x8,%[width] \n" - "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [vu_buf]"+r"(vu_buf), // %[vu_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants), // %[yuvconstants] - [kShuffleNV21]"m"(kShuffleNV21) - : "memory", "cc", YUVTORGB_REGS // Does not use r14. - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void OMITFP YUY2ToARGBRow_SSSE3(const uint8* yuy2_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "pcmpeqb %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - READYUY2 - YUVTORGB(yuvconstants) - STOREARGB - "sub $0x8,%[width] \n" - "jg 1b \n" - : [yuy2_buf]"+r"(yuy2_buf), // %[yuy2_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants), // %[yuvconstants] - [kShuffleYUY2Y]"m"(kShuffleYUY2Y), - [kShuffleYUY2UV]"m"(kShuffleYUY2UV) - : "memory", "cc", YUVTORGB_REGS // Does not use r14. - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void OMITFP UYVYToARGBRow_SSSE3(const uint8* uyvy_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "pcmpeqb %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - READUYVY - YUVTORGB(yuvconstants) - STOREARGB - "sub $0x8,%[width] \n" - "jg 1b \n" - : [uyvy_buf]"+r"(uyvy_buf), // %[uyvy_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants), // %[yuvconstants] - [kShuffleUYVYY]"m"(kShuffleUYVYY), - [kShuffleUYVYUV]"m"(kShuffleUYVYUV) - : "memory", "cc", YUVTORGB_REGS // Does not use r14. - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void OMITFP I422ToRGBARow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - LABELALIGN - "1: \n" - READYUV422 - YUVTORGB(yuvconstants) - STORERGBA - "sub $0x8,%[width] \n" - "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_rgba]"+r"(dst_rgba), // %[dst_rgba] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -#endif // HAS_I422TOARGBROW_SSSE3 - -// Read 16 UV from 444 -#define READYUV444_AVX2 \ - "vmovdqu " MEMACCESS([u_buf]) ",%%xmm0 \n" \ - MEMOPREG(vmovdqu, 0x00, [u_buf], [v_buf], 1, xmm1) \ - "lea " MEMLEA(0x10, [u_buf]) ",%[u_buf] \n" \ - "vpermq $0xd8,%%ymm0,%%ymm0 \n" \ - "vpermq $0xd8,%%ymm1,%%ymm1 \n" \ - "vpunpcklbw %%ymm1,%%ymm0,%%ymm0 \n" \ - "vmovdqu " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "vpermq $0xd8,%%ymm4,%%ymm4 \n" \ - "vpunpcklbw %%ymm4,%%ymm4,%%ymm4 \n" \ - "lea " MEMLEA(0x10, [y_buf]) ",%[y_buf] \n" - -// Read 8 UV from 422, upsample to 16 UV. -#define READYUV422_AVX2 \ - "vmovq " MEMACCESS([u_buf]) ",%%xmm0 \n" \ - MEMOPREG(vmovq, 0x00, [u_buf], [v_buf], 1, xmm1) \ - "lea " MEMLEA(0x8, [u_buf]) ",%[u_buf] \n" \ - "vpunpcklbw %%ymm1,%%ymm0,%%ymm0 \n" \ - "vpermq $0xd8,%%ymm0,%%ymm0 \n" \ - "vpunpcklwd %%ymm0,%%ymm0,%%ymm0 \n" \ - "vmovdqu " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "vpermq $0xd8,%%ymm4,%%ymm4 \n" \ - "vpunpcklbw %%ymm4,%%ymm4,%%ymm4 \n" \ - "lea " MEMLEA(0x10, [y_buf]) ",%[y_buf] \n" - -// Read 8 UV from 422, upsample to 16 UV. With 16 Alpha. -#define READYUVA422_AVX2 \ - "vmovq " MEMACCESS([u_buf]) ",%%xmm0 \n" \ - MEMOPREG(vmovq, 0x00, [u_buf], [v_buf], 1, xmm1) \ - "lea " MEMLEA(0x8, [u_buf]) ",%[u_buf] \n" \ - "vpunpcklbw %%ymm1,%%ymm0,%%ymm0 \n" \ - "vpermq $0xd8,%%ymm0,%%ymm0 \n" \ - "vpunpcklwd %%ymm0,%%ymm0,%%ymm0 \n" \ - "vmovdqu " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "vpermq $0xd8,%%ymm4,%%ymm4 \n" \ - "vpunpcklbw %%ymm4,%%ymm4,%%ymm4 \n" \ - "lea " MEMLEA(0x10, [y_buf]) ",%[y_buf] \n" \ - "vmovdqu " MEMACCESS([a_buf]) ",%%xmm5 \n" \ - "vpermq $0xd8,%%ymm5,%%ymm5 \n" \ - "lea " MEMLEA(0x10, [a_buf]) ",%[a_buf] \n" - -// Read 4 UV from 411, upsample to 16 UV. -#define READYUV411_AVX2 \ - "vmovd " MEMACCESS([u_buf]) ",%%xmm0 \n" \ - MEMOPREG(vmovd, 0x00, [u_buf], [v_buf], 1, xmm1) \ - "lea " MEMLEA(0x4, [u_buf]) ",%[u_buf] \n" \ - "vpunpcklbw %%ymm1,%%ymm0,%%ymm0 \n" \ - "vpunpcklwd %%ymm0,%%ymm0,%%ymm0 \n" \ - "vpermq $0xd8,%%ymm0,%%ymm0 \n" \ - "vpunpckldq %%ymm0,%%ymm0,%%ymm0 \n" \ - "vmovdqu " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "vpermq $0xd8,%%ymm4,%%ymm4 \n" \ - "vpunpcklbw %%ymm4,%%ymm4,%%ymm4 \n" \ - "lea " MEMLEA(0x10, [y_buf]) ",%[y_buf] \n" - -// Read 8 UV from NV12, upsample to 16 UV. -#define READNV12_AVX2 \ - "vmovdqu " MEMACCESS([uv_buf]) ",%%xmm0 \n" \ - "lea " MEMLEA(0x10, [uv_buf]) ",%[uv_buf] \n" \ - "vpermq $0xd8,%%ymm0,%%ymm0 \n" \ - "vpunpcklwd %%ymm0,%%ymm0,%%ymm0 \n" \ - "vmovdqu " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "vpermq $0xd8,%%ymm4,%%ymm4 \n" \ - "vpunpcklbw %%ymm4,%%ymm4,%%ymm4 \n" \ - "lea " MEMLEA(0x10, [y_buf]) ",%[y_buf] \n" - -// Read 8 VU from NV21, upsample to 16 UV. -#define READNV21_AVX2 \ - "vmovdqu " MEMACCESS([vu_buf]) ",%%xmm0 \n" \ - "lea " MEMLEA(0x10, [vu_buf]) ",%[vu_buf] \n" \ - "vpermq $0xd8,%%ymm0,%%ymm0 \n" \ - "vpshufb %[kShuffleNV21], %%ymm0, %%ymm0 \n" \ - "vmovdqu " MEMACCESS([y_buf]) ",%%xmm4 \n" \ - "vpermq $0xd8,%%ymm4,%%ymm4 \n" \ - "vpunpcklbw %%ymm4,%%ymm4,%%ymm4 \n" \ - "lea " MEMLEA(0x10, [y_buf]) ",%[y_buf] \n" - -// Read 8 YUY2 with 16 Y and upsample 8 UV to 16 UV. -#define READYUY2_AVX2 \ - "vmovdqu " MEMACCESS([yuy2_buf]) ",%%ymm4 \n" \ - "vpshufb %[kShuffleYUY2Y], %%ymm4, %%ymm4 \n" \ - "vmovdqu " MEMACCESS([yuy2_buf]) ",%%ymm0 \n" \ - "vpshufb %[kShuffleYUY2UV], %%ymm0, %%ymm0 \n" \ - "lea " MEMLEA(0x20, [yuy2_buf]) ",%[yuy2_buf] \n" - -// Read 8 UYVY with 16 Y and upsample 8 UV to 16 UV. -#define READUYVY_AVX2 \ - "vmovdqu " MEMACCESS([uyvy_buf]) ",%%ymm4 \n" \ - "vpshufb %[kShuffleUYVYY], %%ymm4, %%ymm4 \n" \ - "vmovdqu " MEMACCESS([uyvy_buf]) ",%%ymm0 \n" \ - "vpshufb %[kShuffleUYVYUV], %%ymm0, %%ymm0 \n" \ - "lea " MEMLEA(0x20, [uyvy_buf]) ",%[uyvy_buf] \n" - -#if defined(__x86_64__) -#define YUVTORGB_SETUP_AVX2(yuvconstants) \ - "vmovdqa " MEMACCESS([yuvconstants]) ",%%ymm8 \n" \ - "vmovdqa " MEMACCESS2(32, [yuvconstants]) ",%%ymm9 \n" \ - "vmovdqa " MEMACCESS2(64, [yuvconstants]) ",%%ymm10 \n" \ - "vmovdqa " MEMACCESS2(96, [yuvconstants]) ",%%ymm11 \n" \ - "vmovdqa " MEMACCESS2(128, [yuvconstants]) ",%%ymm12 \n" \ - "vmovdqa " MEMACCESS2(160, [yuvconstants]) ",%%ymm13 \n" \ - "vmovdqa " MEMACCESS2(192, [yuvconstants]) ",%%ymm14 \n" -#define YUVTORGB_AVX2(yuvconstants) \ - "vpmaddubsw %%ymm10,%%ymm0,%%ymm2 \n" \ - "vpmaddubsw %%ymm9,%%ymm0,%%ymm1 \n" \ - "vpmaddubsw %%ymm8,%%ymm0,%%ymm0 \n" \ - "vpsubw %%ymm2,%%ymm13,%%ymm2 \n" \ - "vpsubw %%ymm1,%%ymm12,%%ymm1 \n" \ - "vpsubw %%ymm0,%%ymm11,%%ymm0 \n" \ - "vpmulhuw %%ymm14,%%ymm4,%%ymm4 \n" \ - "vpaddsw %%ymm4,%%ymm0,%%ymm0 \n" \ - "vpaddsw %%ymm4,%%ymm1,%%ymm1 \n" \ - "vpaddsw %%ymm4,%%ymm2,%%ymm2 \n" \ - "vpsraw $0x6,%%ymm0,%%ymm0 \n" \ - "vpsraw $0x6,%%ymm1,%%ymm1 \n" \ - "vpsraw $0x6,%%ymm2,%%ymm2 \n" \ - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" \ - "vpackuswb %%ymm1,%%ymm1,%%ymm1 \n" \ - "vpackuswb %%ymm2,%%ymm2,%%ymm2 \n" -#define YUVTORGB_REGS_AVX2 \ - "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", -#else // Convert 16 pixels: 16 UV and 16 Y. -#define YUVTORGB_SETUP_AVX2(yuvconstants) -#define YUVTORGB_AVX2(yuvconstants) \ - "vpmaddubsw " MEMACCESS2(64, [yuvconstants]) ",%%ymm0,%%ymm2 \n" \ - "vpmaddubsw " MEMACCESS2(32, [yuvconstants]) ",%%ymm0,%%ymm1 \n" \ - "vpmaddubsw " MEMACCESS([yuvconstants]) ",%%ymm0,%%ymm0 \n" \ - "vmovdqu " MEMACCESS2(160, [yuvconstants]) ",%%ymm3 \n" \ - "vpsubw %%ymm2,%%ymm3,%%ymm2 \n" \ - "vmovdqu " MEMACCESS2(128, [yuvconstants]) ",%%ymm3 \n" \ - "vpsubw %%ymm1,%%ymm3,%%ymm1 \n" \ - "vmovdqu " MEMACCESS2(96, [yuvconstants]) ",%%ymm3 \n" \ - "vpsubw %%ymm0,%%ymm3,%%ymm0 \n" \ - "vpmulhuw " MEMACCESS2(192, [yuvconstants]) ",%%ymm4,%%ymm4 \n" \ - "vpaddsw %%ymm4,%%ymm0,%%ymm0 \n" \ - "vpaddsw %%ymm4,%%ymm1,%%ymm1 \n" \ - "vpaddsw %%ymm4,%%ymm2,%%ymm2 \n" \ - "vpsraw $0x6,%%ymm0,%%ymm0 \n" \ - "vpsraw $0x6,%%ymm1,%%ymm1 \n" \ - "vpsraw $0x6,%%ymm2,%%ymm2 \n" \ - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" \ - "vpackuswb %%ymm1,%%ymm1,%%ymm1 \n" \ - "vpackuswb %%ymm2,%%ymm2,%%ymm2 \n" -#define YUVTORGB_REGS_AVX2 -#endif - -// Store 16 ARGB values. -#define STOREARGB_AVX2 \ - "vpunpcklbw %%ymm1,%%ymm0,%%ymm0 \n" \ - "vpermq $0xd8,%%ymm0,%%ymm0 \n" \ - "vpunpcklbw %%ymm5,%%ymm2,%%ymm2 \n" \ - "vpermq $0xd8,%%ymm2,%%ymm2 \n" \ - "vpunpcklwd %%ymm2,%%ymm0,%%ymm1 \n" \ - "vpunpckhwd %%ymm2,%%ymm0,%%ymm0 \n" \ - "vmovdqu %%ymm1," MEMACCESS([dst_argb]) " \n" \ - "vmovdqu %%ymm0," MEMACCESS2(0x20, [dst_argb]) " \n" \ - "lea " MEMLEA(0x40, [dst_argb]) ", %[dst_argb] \n" - -#ifdef HAS_I444TOARGBROW_AVX2 -// 16 pixels -// 16 UV values with 16 Y producing 16 ARGB (64 bytes). -void OMITFP I444ToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - READYUV444_AVX2 - YUVTORGB_AVX2(yuvconstants) - STOREARGB_AVX2 - "sub $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS_AVX2 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_I444TOARGBROW_AVX2 - -#ifdef HAS_I411TOARGBROW_AVX2 -// 16 pixels -// 4 UV values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). -void OMITFP I411ToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - READYUV411_AVX2 - YUVTORGB_AVX2(yuvconstants) - STOREARGB_AVX2 - "sub $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS_AVX2 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_I411TOARGBROW_AVX2 - -#if defined(HAS_I422TOARGBROW_AVX2) -// 16 pixels -// 8 UV values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). -void OMITFP I422ToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - READYUV422_AVX2 - YUVTORGB_AVX2(yuvconstants) - STOREARGB_AVX2 - "sub $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS_AVX2 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_I422TOARGBROW_AVX2 - -#if defined(HAS_I422ALPHATOARGBROW_AVX2) -// 16 pixels -// 8 UV values upsampled to 16 UV, mixed with 16 Y and 16 A producing 16 ARGB. -void OMITFP I422AlphaToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - LABELALIGN - "1: \n" - READYUVA422_AVX2 - YUVTORGB_AVX2(yuvconstants) - STOREARGB_AVX2 - "subl $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [a_buf]"+r"(a_buf), // %[a_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] -#if defined(__i386__) && defined(__pic__) - [width]"+m"(width) // %[width] -#else - [width]"+rm"(width) // %[width] -#endif - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS_AVX2 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_I422ALPHATOARGBROW_AVX2 - -#if defined(HAS_I422TORGBAROW_AVX2) -// 16 pixels -// 8 UV values upsampled to 16 UV, mixed with 16 Y producing 16 RGBA (64 bytes). -void OMITFP I422ToRGBARow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "sub %[u_buf],%[v_buf] \n" - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - READYUV422_AVX2 - YUVTORGB_AVX2(yuvconstants) - - // Step 3: Weave into RGBA - "vpunpcklbw %%ymm2,%%ymm1,%%ymm1 \n" - "vpermq $0xd8,%%ymm1,%%ymm1 \n" - "vpunpcklbw %%ymm0,%%ymm5,%%ymm2 \n" - "vpermq $0xd8,%%ymm2,%%ymm2 \n" - "vpunpcklwd %%ymm1,%%ymm2,%%ymm0 \n" - "vpunpckhwd %%ymm1,%%ymm2,%%ymm1 \n" - "vmovdqu %%ymm0," MEMACCESS([dst_argb]) "\n" - "vmovdqu %%ymm1," MEMACCESS2(0x20,[dst_argb]) "\n" - "lea " MEMLEA(0x40,[dst_argb]) ",%[dst_argb] \n" - "sub $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", NACL_R14 YUVTORGB_REGS_AVX2 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_I422TORGBAROW_AVX2 - -#if defined(HAS_NV12TOARGBROW_AVX2) -// 16 pixels. -// 8 UV values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). -void OMITFP NV12ToARGBRow_AVX2(const uint8* y_buf, - const uint8* uv_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - READNV12_AVX2 - YUVTORGB_AVX2(yuvconstants) - STOREARGB_AVX2 - "sub $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [uv_buf]"+r"(uv_buf), // %[uv_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] - : "memory", "cc", YUVTORGB_REGS_AVX2 // Does not use r14. - "xmm0", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_NV12TOARGBROW_AVX2 - -#if defined(HAS_NV21TOARGBROW_AVX2) -// 16 pixels. -// 8 VU values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). -void OMITFP NV21ToARGBRow_AVX2(const uint8* y_buf, - const uint8* vu_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - READNV21_AVX2 - YUVTORGB_AVX2(yuvconstants) - STOREARGB_AVX2 - "sub $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [vu_buf]"+r"(vu_buf), // %[vu_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants), // %[yuvconstants] - [kShuffleNV21]"m"(kShuffleNV21) - : "memory", "cc", YUVTORGB_REGS_AVX2 // Does not use r14. - "xmm0", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_NV21TOARGBROW_AVX2 - -#if defined(HAS_YUY2TOARGBROW_AVX2) -// 16 pixels. -// 8 YUY2 values with 16 Y and 8 UV producing 16 ARGB (64 bytes). -void OMITFP YUY2ToARGBRow_AVX2(const uint8* yuy2_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - READYUY2_AVX2 - YUVTORGB_AVX2(yuvconstants) - STOREARGB_AVX2 - "sub $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [yuy2_buf]"+r"(yuy2_buf), // %[yuy2_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants), // %[yuvconstants] - [kShuffleYUY2Y]"m"(kShuffleYUY2Y), - [kShuffleYUY2UV]"m"(kShuffleYUY2UV) - : "memory", "cc", YUVTORGB_REGS_AVX2 // Does not use r14. - "xmm0", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_YUY2TOARGBROW_AVX2 - -#if defined(HAS_UYVYTOARGBROW_AVX2) -// 16 pixels. -// 8 UYVY values with 16 Y and 8 UV producing 16 ARGB (64 bytes). -void OMITFP UYVYToARGBRow_AVX2(const uint8* uyvy_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP_AVX2(yuvconstants) - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - READUYVY_AVX2 - YUVTORGB_AVX2(yuvconstants) - STOREARGB_AVX2 - "sub $0x10,%[width] \n" - "jg 1b \n" - "vzeroupper \n" - : [uyvy_buf]"+r"(uyvy_buf), // %[uyvy_buf] - [dst_argb]"+r"(dst_argb), // %[dst_argb] - [width]"+rm"(width) // %[width] - : [yuvconstants]"r"(yuvconstants), // %[yuvconstants] - [kShuffleUYVYY]"m"(kShuffleUYVYY), - [kShuffleUYVYUV]"m"(kShuffleUYVYUV) - : "memory", "cc", YUVTORGB_REGS_AVX2 // Does not use r14. - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_UYVYTOARGBROW_AVX2 - -#ifdef HAS_I400TOARGBROW_SSE2 -void I400ToARGBRow_SSE2(const uint8* y_buf, uint8* dst_argb, int width) { - asm volatile ( - "mov $0x4a354a35,%%eax \n" // 4a35 = 18997 = 1.164 - "movd %%eax,%%xmm2 \n" - "pshufd $0x0,%%xmm2,%%xmm2 \n" - "mov $0x04880488,%%eax \n" // 0488 = 1160 = 1.164 * 16 - "movd %%eax,%%xmm3 \n" - "pshufd $0x0,%%xmm3,%%xmm3 \n" - "pcmpeqb %%xmm4,%%xmm4 \n" - "pslld $0x18,%%xmm4 \n" - LABELALIGN - "1: \n" - // Step 1: Scale Y contribution to 8 G values. G = (y - 16) * 1.164 - "movq " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x8,0) ",%0 \n" - "punpcklbw %%xmm0,%%xmm0 \n" - "pmulhuw %%xmm2,%%xmm0 \n" - "psubusw %%xmm3,%%xmm0 \n" - "psrlw $6, %%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - - // Step 2: Weave into ARGB - "punpcklbw %%xmm0,%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklwd %%xmm0,%%xmm0 \n" - "punpckhwd %%xmm1,%%xmm1 \n" - "por %%xmm4,%%xmm0 \n" - "por %%xmm4,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(y_buf), // %0 - "+r"(dst_argb), // %1 - "+rm"(width) // %2 - : - : "memory", "cc", "eax" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4" - ); -} -#endif // HAS_I400TOARGBROW_SSE2 - -#ifdef HAS_I400TOARGBROW_AVX2 -// 16 pixels of Y converted to 16 pixels of ARGB (64 bytes). -// note: vpunpcklbw mutates and vpackuswb unmutates. -void I400ToARGBRow_AVX2(const uint8* y_buf, uint8* dst_argb, int width) { - asm volatile ( - "mov $0x4a354a35,%%eax \n" // 0488 = 1160 = 1.164 * 16 - "vmovd %%eax,%%xmm2 \n" - "vbroadcastss %%xmm2,%%ymm2 \n" - "mov $0x4880488,%%eax \n" // 4a35 = 18997 = 1.164 - "vmovd %%eax,%%xmm3 \n" - "vbroadcastss %%xmm3,%%ymm3 \n" - "vpcmpeqb %%ymm4,%%ymm4,%%ymm4 \n" - "vpslld $0x18,%%ymm4,%%ymm4 \n" - - LABELALIGN - "1: \n" - // Step 1: Scale Y contribution to 16 G values. G = (y - 16) * 1.164 - "vmovdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpunpcklbw %%ymm0,%%ymm0,%%ymm0 \n" - "vpmulhuw %%ymm2,%%ymm0,%%ymm0 \n" - "vpsubusw %%ymm3,%%ymm0,%%ymm0 \n" - "vpsrlw $0x6,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" - "vpunpcklbw %%ymm0,%%ymm0,%%ymm1 \n" - "vpermq $0xd8,%%ymm1,%%ymm1 \n" - "vpunpcklwd %%ymm1,%%ymm1,%%ymm0 \n" - "vpunpckhwd %%ymm1,%%ymm1,%%ymm1 \n" - "vpor %%ymm4,%%ymm0,%%ymm0 \n" - "vpor %%ymm4,%%ymm1,%%ymm1 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "vmovdqu %%ymm1," MEMACCESS2(0x20,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(y_buf), // %0 - "+r"(dst_argb), // %1 - "+rm"(width) // %2 - : - : "memory", "cc", "eax" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4" - ); -} -#endif // HAS_I400TOARGBROW_AVX2 - -#ifdef HAS_MIRRORROW_SSSE3 -// Shuffle table for reversing the bytes. -static uvec8 kShuffleMirror = { - 15u, 14u, 13u, 12u, 11u, 10u, 9u, 8u, 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u -}; - -void MirrorRow_SSSE3(const uint8* src, uint8* dst, int width) { - intptr_t temp_width = (intptr_t)(width); - asm volatile ( - "movdqa %3,%%xmm5 \n" - LABELALIGN - "1: \n" - MEMOPREG(movdqu,-0x10,0,2,1,xmm0) // movdqu -0x10(%0,%2),%%xmm0 - "pshufb %%xmm5,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(temp_width) // %2 - : "m"(kShuffleMirror) // %3 - : "memory", "cc", NACL_R14 - "xmm0", "xmm5" - ); -} -#endif // HAS_MIRRORROW_SSSE3 - -#ifdef HAS_MIRRORROW_AVX2 -void MirrorRow_AVX2(const uint8* src, uint8* dst, int width) { - intptr_t temp_width = (intptr_t)(width); - asm volatile ( - "vbroadcastf128 %3,%%ymm5 \n" - LABELALIGN - "1: \n" - MEMOPREG(vmovdqu,-0x20,0,2,1,ymm0) // vmovdqu -0x20(%0,%2),%%ymm0 - "vpshufb %%ymm5,%%ymm0,%%ymm0 \n" - "vpermq $0x4e,%%ymm0,%%ymm0 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(temp_width) // %2 - : "m"(kShuffleMirror) // %3 - : "memory", "cc", NACL_R14 - "xmm0", "xmm5" - ); -} -#endif // HAS_MIRRORROW_AVX2 - -#ifdef HAS_MIRRORUVROW_SSSE3 -// Shuffle table for reversing the bytes of UV channels. -static uvec8 kShuffleMirrorUV = { - 14u, 12u, 10u, 8u, 6u, 4u, 2u, 0u, 15u, 13u, 11u, 9u, 7u, 5u, 3u, 1u -}; -void MirrorUVRow_SSSE3(const uint8* src, uint8* dst_u, uint8* dst_v, - int width) { - intptr_t temp_width = (intptr_t)(width); - asm volatile ( - "movdqa %4,%%xmm1 \n" - "lea " MEMLEA4(-0x10,0,3,2) ",%0 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(-0x10,0) ",%0 \n" - "pshufb %%xmm1,%%xmm0 \n" - "movlpd %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movhpd,xmm0,0x00,1,2,1) // movhpd %%xmm0,(%1,%2) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $8,%3 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(temp_width) // %3 - : "m"(kShuffleMirrorUV) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1" - ); -} -#endif // HAS_MIRRORUVROW_SSSE3 - -#ifdef HAS_ARGBMIRRORROW_SSE2 - -void ARGBMirrorRow_SSE2(const uint8* src, uint8* dst, int width) { - intptr_t temp_width = (intptr_t)(width); - asm volatile ( - "lea " MEMLEA4(-0x10,0,2,4) ",%0 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "pshufd $0x1b,%%xmm0,%%xmm0 \n" - "lea " MEMLEA(-0x10,0) ",%0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(temp_width) // %2 - : - : "memory", "cc" - , "xmm0" - ); -} -#endif // HAS_ARGBMIRRORROW_SSE2 - -#ifdef HAS_ARGBMIRRORROW_AVX2 -// Shuffle table for reversing the bytes. -static const ulvec32 kARGBShuffleMirror_AVX2 = { - 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u -}; -void ARGBMirrorRow_AVX2(const uint8* src, uint8* dst, int width) { - intptr_t temp_width = (intptr_t)(width); - asm volatile ( - "vmovdqu %3,%%ymm5 \n" - LABELALIGN - "1: \n" - VMEMOPREG(vpermd,-0x20,0,2,4,ymm5,ymm0) // vpermd -0x20(%0,%2,4),ymm5,ymm0 - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(temp_width) // %2 - : "m"(kARGBShuffleMirror_AVX2) // %3 - : "memory", "cc", NACL_R14 - "xmm0", "xmm5" - ); -} -#endif // HAS_ARGBMIRRORROW_AVX2 - -#ifdef HAS_SPLITUVROW_AVX2 -void SplitUVRow_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpsrlw $0x8,%%ymm5,%%ymm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpsrlw $0x8,%%ymm0,%%ymm2 \n" - "vpsrlw $0x8,%%ymm1,%%ymm3 \n" - "vpand %%ymm5,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm3,%%ymm2,%%ymm2 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm2,%%ymm2 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - MEMOPMEM(vmovdqu,ymm2,0x00,1,2,1) // vmovdqu %%ymm2,(%1,%2) - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_uv), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} -#endif // HAS_SPLITUVROW_AVX2 - -#ifdef HAS_SPLITUVROW_SSE2 -void SplitUVRow_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "psrlw $0x8,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "movdqa %%xmm0,%%xmm2 \n" - "movdqa %%xmm1,%%xmm3 \n" - "pand %%xmm5,%%xmm0 \n" - "pand %%xmm5,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "psrlw $0x8,%%xmm2 \n" - "psrlw $0x8,%%xmm3 \n" - "packuswb %%xmm3,%%xmm2 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movdqu,xmm2,0x00,1,2,1) // movdqu %%xmm2,(%1,%2) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_uv), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} -#endif // HAS_SPLITUVROW_SSE2 - -#ifdef HAS_MERGEUVROW_AVX2 -void MergeUVRow_AVX2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) { - asm volatile ( - "sub %0,%1 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - MEMOPREG(vmovdqu,0x00,0,1,1,ymm1) // vmovdqu (%0,%1,1),%%ymm1 - "lea " MEMLEA(0x20,0) ",%0 \n" - "vpunpcklbw %%ymm1,%%ymm0,%%ymm2 \n" - "vpunpckhbw %%ymm1,%%ymm0,%%ymm0 \n" - "vextractf128 $0x0,%%ymm2," MEMACCESS(2) " \n" - "vextractf128 $0x0,%%ymm0," MEMACCESS2(0x10,2) "\n" - "vextractf128 $0x1,%%ymm2," MEMACCESS2(0x20,2) "\n" - "vextractf128 $0x1,%%ymm0," MEMACCESS2(0x30,2) "\n" - "lea " MEMLEA(0x40,2) ",%2 \n" - "sub $0x20,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_u), // %0 - "+r"(src_v), // %1 - "+r"(dst_uv), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2" - ); -} -#endif // HAS_MERGEUVROW_AVX2 - -#ifdef HAS_MERGEUVROW_SSE2 -void MergeUVRow_SSE2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) { - asm volatile ( - "sub %0,%1 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,1,1,xmm1) // movdqu (%0,%1,1),%%xmm1 - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm0,%%xmm2 \n" - "punpcklbw %%xmm1,%%xmm0 \n" - "punpckhbw %%xmm1,%%xmm2 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "movdqu %%xmm2," MEMACCESS2(0x10,2) " \n" - "lea " MEMLEA(0x20,2) ",%2 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_u), // %0 - "+r"(src_v), // %1 - "+r"(dst_uv), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2" - ); -} -#endif // HAS_MERGEUVROW_SSE2 - -#ifdef HAS_COPYROW_SSE2 -void CopyRow_SSE2(const uint8* src, uint8* dst, int count) { - asm volatile ( - "test $0xf,%0 \n" - "jne 2f \n" - "test $0xf,%1 \n" - "jne 2f \n" - LABELALIGN - "1: \n" - "movdqa " MEMACCESS(0) ",%%xmm0 \n" - "movdqa " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "movdqa %%xmm0," MEMACCESS(1) " \n" - "movdqa %%xmm1," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "jmp 9f \n" - LABELALIGN - "2: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 2b \n" - "9: \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(count) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1" - ); -} -#endif // HAS_COPYROW_SSE2 - -#ifdef HAS_COPYROW_AVX -void CopyRow_AVX(const uint8* src, uint8* dst, int count) { - asm volatile ( - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "vmovdqu %%ymm1," MEMACCESS2(0x20,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x40,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(count) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1" - ); -} -#endif // HAS_COPYROW_AVX - -#ifdef HAS_COPYROW_ERMS -// Multiple of 1. -void CopyRow_ERMS(const uint8* src, uint8* dst, int width) { - size_t width_tmp = (size_t)(width); - asm volatile ( - "rep movsb " MEMMOVESTRING(0,1) " \n" - : "+S"(src), // %0 - "+D"(dst), // %1 - "+c"(width_tmp) // %2 - : - : "memory", "cc" - ); -} -#endif // HAS_COPYROW_ERMS - -#ifdef HAS_ARGBCOPYALPHAROW_SSE2 -// width in pixels -void ARGBCopyAlphaRow_SSE2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "pcmpeqb %%xmm0,%%xmm0 \n" - "pslld $0x18,%%xmm0 \n" - "pcmpeqb %%xmm1,%%xmm1 \n" - "psrld $0x8,%%xmm1 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm3 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "movdqu " MEMACCESS(1) ",%%xmm4 \n" - "movdqu " MEMACCESS2(0x10,1) ",%%xmm5 \n" - "pand %%xmm0,%%xmm2 \n" - "pand %%xmm0,%%xmm3 \n" - "pand %%xmm1,%%xmm4 \n" - "pand %%xmm1,%%xmm5 \n" - "por %%xmm4,%%xmm2 \n" - "por %%xmm5,%%xmm3 \n" - "movdqu %%xmm2," MEMACCESS(1) " \n" - "movdqu %%xmm3," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_ARGBCOPYALPHAROW_SSE2 - -#ifdef HAS_ARGBCOPYALPHAROW_AVX2 -// width in pixels -void ARGBCopyAlphaRow_AVX2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "vpcmpeqb %%ymm0,%%ymm0,%%ymm0 \n" - "vpsrld $0x8,%%ymm0,%%ymm0 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm1 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm2 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpblendvb %%ymm0," MEMACCESS(1) ",%%ymm1,%%ymm1 \n" - "vpblendvb %%ymm0," MEMACCESS2(0x20,1) ",%%ymm2,%%ymm2 \n" - "vmovdqu %%ymm1," MEMACCESS(1) " \n" - "vmovdqu %%ymm2," MEMACCESS2(0x20,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm2" - ); -} -#endif // HAS_ARGBCOPYALPHAROW_AVX2 - -#ifdef HAS_ARGBEXTRACTALPHAROW_SSE2 -// width in pixels -void ARGBExtractAlphaRow_SSE2(const uint8* src_argb, uint8* dst_a, int width) { - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ", %%xmm0 \n" - "movdqu " MEMACCESS2(0x10, 0) ", %%xmm1 \n" - "lea " MEMLEA(0x20, 0) ", %0 \n" - "psrld $0x18, %%xmm0 \n" - "psrld $0x18, %%xmm1 \n" - "packssdw %%xmm1, %%xmm0 \n" - "packuswb %%xmm0, %%xmm0 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x8, 1) ", %1 \n" - "sub $0x8, %2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_a), // %1 - "+rm"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1" - ); -} -#endif // HAS_ARGBEXTRACTALPHAROW_SSE2 - -#ifdef HAS_ARGBCOPYYTOALPHAROW_SSE2 -// width in pixels -void ARGBCopyYToAlphaRow_SSE2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "pcmpeqb %%xmm0,%%xmm0 \n" - "pslld $0x18,%%xmm0 \n" - "pcmpeqb %%xmm1,%%xmm1 \n" - "psrld $0x8,%%xmm1 \n" - LABELALIGN - "1: \n" - "movq " MEMACCESS(0) ",%%xmm2 \n" - "lea " MEMLEA(0x8,0) ",%0 \n" - "punpcklbw %%xmm2,%%xmm2 \n" - "punpckhwd %%xmm2,%%xmm3 \n" - "punpcklwd %%xmm2,%%xmm2 \n" - "movdqu " MEMACCESS(1) ",%%xmm4 \n" - "movdqu " MEMACCESS2(0x10,1) ",%%xmm5 \n" - "pand %%xmm0,%%xmm2 \n" - "pand %%xmm0,%%xmm3 \n" - "pand %%xmm1,%%xmm4 \n" - "pand %%xmm1,%%xmm5 \n" - "por %%xmm4,%%xmm2 \n" - "por %%xmm5,%%xmm3 \n" - "movdqu %%xmm2," MEMACCESS(1) " \n" - "movdqu %%xmm3," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_ARGBCOPYYTOALPHAROW_SSE2 - -#ifdef HAS_ARGBCOPYYTOALPHAROW_AVX2 -// width in pixels -void ARGBCopyYToAlphaRow_AVX2(const uint8* src, uint8* dst, int width) { - asm volatile ( - "vpcmpeqb %%ymm0,%%ymm0,%%ymm0 \n" - "vpsrld $0x8,%%ymm0,%%ymm0 \n" - LABELALIGN - "1: \n" - "vpmovzxbd " MEMACCESS(0) ",%%ymm1 \n" - "vpmovzxbd " MEMACCESS2(0x8,0) ",%%ymm2 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "vpslld $0x18,%%ymm1,%%ymm1 \n" - "vpslld $0x18,%%ymm2,%%ymm2 \n" - "vpblendvb %%ymm0," MEMACCESS(1) ",%%ymm1,%%ymm1 \n" - "vpblendvb %%ymm0," MEMACCESS2(0x20,1) ",%%ymm2,%%ymm2 \n" - "vmovdqu %%ymm1," MEMACCESS(1) " \n" - "vmovdqu %%ymm2," MEMACCESS2(0x20,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm2" - ); -} -#endif // HAS_ARGBCOPYYTOALPHAROW_AVX2 - -#ifdef HAS_SETROW_X86 -void SetRow_X86(uint8* dst, uint8 v8, int width) { - size_t width_tmp = (size_t)(width >> 2); - const uint32 v32 = v8 * 0x01010101u; // Duplicate byte to all bytes. - asm volatile ( - "rep stosl " MEMSTORESTRING(eax,0) " \n" - : "+D"(dst), // %0 - "+c"(width_tmp) // %1 - : "a"(v32) // %2 - : "memory", "cc"); -} - -void SetRow_ERMS(uint8* dst, uint8 v8, int width) { - size_t width_tmp = (size_t)(width); - asm volatile ( - "rep stosb " MEMSTORESTRING(al,0) " \n" - : "+D"(dst), // %0 - "+c"(width_tmp) // %1 - : "a"(v8) // %2 - : "memory", "cc"); -} - -void ARGBSetRow_X86(uint8* dst_argb, uint32 v32, int width) { - size_t width_tmp = (size_t)(width); - asm volatile ( - "rep stosl " MEMSTORESTRING(eax,0) " \n" - : "+D"(dst_argb), // %0 - "+c"(width_tmp) // %1 - : "a"(v32) // %2 - : "memory", "cc"); -} -#endif // HAS_SETROW_X86 - -#ifdef HAS_YUY2TOYROW_SSE2 -void YUY2ToYRow_SSE2(const uint8* src_yuy2, uint8* dst_y, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "psrlw $0x8,%%xmm5 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "pand %%xmm5,%%xmm0 \n" - "pand %%xmm5,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm5" - ); -} - -void YUY2ToUVRow_SSE2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "psrlw $0x8,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x00,0,4,1,xmm2) // movdqu (%0,%4,1),%%xmm2 - MEMOPREG(movdqu,0x10,0,4,1,xmm3) // movdqu 0x10(%0,%4,1),%%xmm3 - "lea " MEMLEA(0x20,0) ",%0 \n" - "pavgb %%xmm2,%%xmm0 \n" - "pavgb %%xmm3,%%xmm1 \n" - "psrlw $0x8,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "pand %%xmm5,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm1 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movq,xmm1,0x00,1,2,1) // movq %%xmm1,(%1,%2) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : "r"((intptr_t)(stride_yuy2)) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} - -void YUY2ToUV422Row_SSE2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "psrlw $0x8,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "psrlw $0x8,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "pand %%xmm5,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm1 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movq,xmm1,0x00,1,2,1) // movq %%xmm1,(%1,%2) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm5" - ); -} - -void UYVYToYRow_SSE2(const uint8* src_uyvy, uint8* dst_y, int width) { - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "psrlw $0x8,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1" - ); -} - -void UYVYToUVRow_SSE2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "psrlw $0x8,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x00,0,4,1,xmm2) // movdqu (%0,%4,1),%%xmm2 - MEMOPREG(movdqu,0x10,0,4,1,xmm3) // movdqu 0x10(%0,%4,1),%%xmm3 - "lea " MEMLEA(0x20,0) ",%0 \n" - "pavgb %%xmm2,%%xmm0 \n" - "pavgb %%xmm3,%%xmm1 \n" - "pand %%xmm5,%%xmm0 \n" - "pand %%xmm5,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "pand %%xmm5,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm1 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movq,xmm1,0x00,1,2,1) // movq %%xmm1,(%1,%2) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : "r"((intptr_t)(stride_uyvy)) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} - -void UYVYToUV422Row_SSE2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "psrlw $0x8,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "pand %%xmm5,%%xmm0 \n" - "pand %%xmm5,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "pand %%xmm5,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm1 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movq,xmm1,0x00,1,2,1) // movq %%xmm1,(%1,%2) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm5" - ); -} -#endif // HAS_YUY2TOYROW_SSE2 - -#ifdef HAS_YUY2TOYROW_AVX2 -void YUY2ToYRow_AVX2(const uint8* src_yuy2, uint8* dst_y, int width) { - asm volatile ( - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpsrlw $0x8,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpand %%ymm5,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm5" - ); -} - -void YUY2ToUVRow_AVX2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpsrlw $0x8,%%ymm5,%%ymm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - VMEMOPREG(vpavgb,0x00,0,4,1,ymm0,ymm0) // vpavgb (%0,%4,1),%%ymm0,%%ymm0 - VMEMOPREG(vpavgb,0x20,0,4,1,ymm1,ymm1) - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpsrlw $0x8,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm0,%%ymm1 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm1,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm1,%%ymm1 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vextractf128 $0x0,%%ymm1," MEMACCESS(1) " \n" - VEXTOPMEM(vextractf128,0,ymm0,0x00,1,2,1) // vextractf128 $0x0,%%ymm0,(%1,%2,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x20,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : "r"((intptr_t)(stride_yuy2)) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm5" - ); -} - -void YUY2ToUV422Row_AVX2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpsrlw $0x8,%%ymm5,%%ymm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpsrlw $0x8,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm0,%%ymm1 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm1,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm1,%%ymm1 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vextractf128 $0x0,%%ymm1," MEMACCESS(1) " \n" - VEXTOPMEM(vextractf128,0,ymm0,0x00,1,2,1) // vextractf128 $0x0,%%ymm0,(%1,%2,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x20,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm5" - ); -} - -void UYVYToYRow_AVX2(const uint8* src_uyvy, uint8* dst_y, int width) { - asm volatile ( - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpsrlw $0x8,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm5" - ); -} -void UYVYToUVRow_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpsrlw $0x8,%%ymm5,%%ymm5 \n" - "sub %1,%2 \n" - - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - VMEMOPREG(vpavgb,0x00,0,4,1,ymm0,ymm0) // vpavgb (%0,%4,1),%%ymm0,%%ymm0 - VMEMOPREG(vpavgb,0x20,0,4,1,ymm1,ymm1) - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpand %%ymm5,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm0,%%ymm1 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm1,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm1,%%ymm1 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vextractf128 $0x0,%%ymm1," MEMACCESS(1) " \n" - VEXTOPMEM(vextractf128,0,ymm0,0x00,1,2,1) // vextractf128 $0x0,%%ymm0,(%1,%2,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x20,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : "r"((intptr_t)(stride_uyvy)) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm5" - ); -} - -void UYVYToUV422Row_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpsrlw $0x8,%%ymm5,%%ymm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpand %%ymm5,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm0,%%ymm1 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm1,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm1,%%ymm1 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vextractf128 $0x0,%%ymm1," MEMACCESS(1) " \n" - VEXTOPMEM(vextractf128,0,ymm0,0x00,1,2,1) // vextractf128 $0x0,%%ymm0,(%1,%2,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x20,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm5" - ); -} -#endif // HAS_YUY2TOYROW_AVX2 - -#ifdef HAS_ARGBBLENDROW_SSSE3 -// Shuffle table for isolating alpha. -static uvec8 kShuffleAlpha = { - 3u, 0x80, 3u, 0x80, 7u, 0x80, 7u, 0x80, - 11u, 0x80, 11u, 0x80, 15u, 0x80, 15u, 0x80 -}; - -// Blend 8 pixels at a time -void ARGBBlendRow_SSSE3(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - "pcmpeqb %%xmm7,%%xmm7 \n" - "psrlw $0xf,%%xmm7 \n" - "pcmpeqb %%xmm6,%%xmm6 \n" - "psrlw $0x8,%%xmm6 \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - "psllw $0x8,%%xmm5 \n" - "pcmpeqb %%xmm4,%%xmm4 \n" - "pslld $0x18,%%xmm4 \n" - "sub $0x4,%3 \n" - "jl 49f \n" - - // 4 pixel loop. - LABELALIGN - "40: \n" - "movdqu " MEMACCESS(0) ",%%xmm3 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm3,%%xmm0 \n" - "pxor %%xmm4,%%xmm3 \n" - "movdqu " MEMACCESS(1) ",%%xmm2 \n" - "pshufb %4,%%xmm3 \n" - "pand %%xmm6,%%xmm2 \n" - "paddw %%xmm7,%%xmm3 \n" - "pmullw %%xmm3,%%xmm2 \n" - "movdqu " MEMACCESS(1) ",%%xmm1 \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "psrlw $0x8,%%xmm1 \n" - "por %%xmm4,%%xmm0 \n" - "pmullw %%xmm3,%%xmm1 \n" - "psrlw $0x8,%%xmm2 \n" - "paddusb %%xmm2,%%xmm0 \n" - "pand %%xmm5,%%xmm1 \n" - "paddusb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%3 \n" - "jge 40b \n" - - "49: \n" - "add $0x3,%3 \n" - "jl 99f \n" - - // 1 pixel loop. - "91: \n" - "movd " MEMACCESS(0) ",%%xmm3 \n" - "lea " MEMLEA(0x4,0) ",%0 \n" - "movdqa %%xmm3,%%xmm0 \n" - "pxor %%xmm4,%%xmm3 \n" - "movd " MEMACCESS(1) ",%%xmm2 \n" - "pshufb %4,%%xmm3 \n" - "pand %%xmm6,%%xmm2 \n" - "paddw %%xmm7,%%xmm3 \n" - "pmullw %%xmm3,%%xmm2 \n" - "movd " MEMACCESS(1) ",%%xmm1 \n" - "lea " MEMLEA(0x4,1) ",%1 \n" - "psrlw $0x8,%%xmm1 \n" - "por %%xmm4,%%xmm0 \n" - "pmullw %%xmm3,%%xmm1 \n" - "psrlw $0x8,%%xmm2 \n" - "paddusb %%xmm2,%%xmm0 \n" - "pand %%xmm5,%%xmm1 \n" - "paddusb %%xmm1,%%xmm0 \n" - "movd %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x4,2) ",%2 \n" - "sub $0x1,%3 \n" - "jge 91b \n" - "99: \n" - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : "m"(kShuffleAlpha) // %4 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBBLENDROW_SSSE3 - -#ifdef HAS_BLENDPLANEROW_SSSE3 -// Blend 8 pixels at a time. -// unsigned version of math -// =((A2*C2)+(B2*(255-C2))+255)/256 -// signed version of math -// =(((A2-128)*C2)+((B2-128)*(255-C2))+32768+127)/256 -void BlendPlaneRow_SSSE3(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "psllw $0x8,%%xmm5 \n" - "mov $0x80808080,%%eax \n" - "movd %%eax,%%xmm6 \n" - "pshufd $0x0,%%xmm6,%%xmm6 \n" - "mov $0x807f807f,%%eax \n" - "movd %%eax,%%xmm7 \n" - "pshufd $0x0,%%xmm7,%%xmm7 \n" - "sub %2,%0 \n" - "sub %2,%1 \n" - "sub %2,%3 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movq (%2),%%xmm0 \n" - "punpcklbw %%xmm0,%%xmm0 \n" - "pxor %%xmm5,%%xmm0 \n" - "movq (%0,%2,1),%%xmm1 \n" - "movq (%1,%2,1),%%xmm2 \n" - "punpcklbw %%xmm2,%%xmm1 \n" - "psubb %%xmm6,%%xmm1 \n" - "pmaddubsw %%xmm1,%%xmm0 \n" - "paddw %%xmm7,%%xmm0 \n" - "psrlw $0x8,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movq %%xmm0,(%3,%2,1) \n" - "lea 0x8(%2),%2 \n" - "sub $0x8,%4 \n" - "jg 1b \n" - : "+r"(src0), // %0 - "+r"(src1), // %1 - "+r"(alpha), // %2 - "+r"(dst), // %3 - "+rm"(width) // %4 - :: "memory", "cc", "eax", "xmm0", "xmm1", "xmm2", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_BLENDPLANEROW_SSSE3 - -#ifdef HAS_BLENDPLANEROW_AVX2 -// Blend 32 pixels at a time. -// unsigned version of math -// =((A2*C2)+(B2*(255-C2))+255)/256 -// signed version of math -// =(((A2-128)*C2)+((B2-128)*(255-C2))+32768+127)/256 -void BlendPlaneRow_AVX2(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width) { - asm volatile ( - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpsllw $0x8,%%ymm5,%%ymm5 \n" - "mov $0x80808080,%%eax \n" - "vmovd %%eax,%%xmm6 \n" - "vbroadcastss %%xmm6,%%ymm6 \n" - "mov $0x807f807f,%%eax \n" - "vmovd %%eax,%%xmm7 \n" - "vbroadcastss %%xmm7,%%ymm7 \n" - "sub %2,%0 \n" - "sub %2,%1 \n" - "sub %2,%3 \n" - - // 32 pixel loop. - LABELALIGN - "1: \n" - "vmovdqu (%2),%%ymm0 \n" - "vpunpckhbw %%ymm0,%%ymm0,%%ymm3 \n" - "vpunpcklbw %%ymm0,%%ymm0,%%ymm0 \n" - "vpxor %%ymm5,%%ymm3,%%ymm3 \n" - "vpxor %%ymm5,%%ymm0,%%ymm0 \n" - "vmovdqu (%0,%2,1),%%ymm1 \n" - "vmovdqu (%1,%2,1),%%ymm2 \n" - "vpunpckhbw %%ymm2,%%ymm1,%%ymm4 \n" - "vpunpcklbw %%ymm2,%%ymm1,%%ymm1 \n" - "vpsubb %%ymm6,%%ymm4,%%ymm4 \n" - "vpsubb %%ymm6,%%ymm1,%%ymm1 \n" - "vpmaddubsw %%ymm4,%%ymm3,%%ymm3 \n" - "vpmaddubsw %%ymm1,%%ymm0,%%ymm0 \n" - "vpaddw %%ymm7,%%ymm3,%%ymm3 \n" - "vpaddw %%ymm7,%%ymm0,%%ymm0 \n" - "vpsrlw $0x8,%%ymm3,%%ymm3 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm3,%%ymm0,%%ymm0 \n" - "vmovdqu %%ymm0,(%3,%2,1) \n" - "lea 0x20(%2),%2 \n" - "sub $0x20,%4 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src0), // %0 - "+r"(src1), // %1 - "+r"(alpha), // %2 - "+r"(dst), // %3 - "+rm"(width) // %4 - :: "memory", "cc", "eax", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_BLENDPLANEROW_AVX2 - -#ifdef HAS_ARGBATTENUATEROW_SSSE3 -// Shuffle table duplicating alpha -static uvec8 kShuffleAlpha0 = { - 3u, 3u, 3u, 3u, 3u, 3u, 128u, 128u, 7u, 7u, 7u, 7u, 7u, 7u, 128u, 128u -}; -static uvec8 kShuffleAlpha1 = { - 11u, 11u, 11u, 11u, 11u, 11u, 128u, 128u, - 15u, 15u, 15u, 15u, 15u, 15u, 128u, 128u -}; -// Attenuate 4 pixels at a time. -void ARGBAttenuateRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width) { - asm volatile ( - "pcmpeqb %%xmm3,%%xmm3 \n" - "pslld $0x18,%%xmm3 \n" - "movdqa %3,%%xmm4 \n" - "movdqa %4,%%xmm5 \n" - - // 4 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "pshufb %%xmm4,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm1 \n" - "punpcklbw %%xmm1,%%xmm1 \n" - "pmulhuw %%xmm1,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm1 \n" - "pshufb %%xmm5,%%xmm1 \n" - "movdqu " MEMACCESS(0) ",%%xmm2 \n" - "punpckhbw %%xmm2,%%xmm2 \n" - "pmulhuw %%xmm2,%%xmm1 \n" - "movdqu " MEMACCESS(0) ",%%xmm2 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "pand %%xmm3,%%xmm2 \n" - "psrlw $0x8,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "por %%xmm2,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "m"(kShuffleAlpha0), // %3 - "m"(kShuffleAlpha1) // %4 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_ARGBATTENUATEROW_SSSE3 - -#ifdef HAS_ARGBATTENUATEROW_AVX2 -// Shuffle table duplicating alpha. -static const uvec8 kShuffleAlpha_AVX2 = { - 6u, 7u, 6u, 7u, 6u, 7u, 128u, 128u, 14u, 15u, 14u, 15u, 14u, 15u, 128u, 128u -}; -// Attenuate 8 pixels at a time. -void ARGBAttenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width) { - asm volatile ( - "vbroadcastf128 %3,%%ymm4 \n" - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpslld $0x18,%%ymm5,%%ymm5 \n" - "sub %0,%1 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm6 \n" - "vpunpcklbw %%ymm6,%%ymm6,%%ymm0 \n" - "vpunpckhbw %%ymm6,%%ymm6,%%ymm1 \n" - "vpshufb %%ymm4,%%ymm0,%%ymm2 \n" - "vpshufb %%ymm4,%%ymm1,%%ymm3 \n" - "vpmulhuw %%ymm2,%%ymm0,%%ymm0 \n" - "vpmulhuw %%ymm3,%%ymm1,%%ymm1 \n" - "vpand %%ymm5,%%ymm6,%%ymm6 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpsrlw $0x8,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpor %%ymm6,%%ymm0,%%ymm0 \n" - MEMOPMEM(vmovdqu,ymm0,0x00,0,1,1) // vmovdqu %%ymm0,(%0,%1) - "lea " MEMLEA(0x20,0) ",%0 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "m"(kShuffleAlpha_AVX2) // %3 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} -#endif // HAS_ARGBATTENUATEROW_AVX2 - -#ifdef HAS_ARGBUNATTENUATEROW_SSE2 -// Unattenuate 4 pixels at a time. -void ARGBUnattenuateRow_SSE2(const uint8* src_argb, uint8* dst_argb, - int width) { - uintptr_t alpha; - asm volatile ( - // 4 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movzb " MEMACCESS2(0x03,0) ",%3 \n" - "punpcklbw %%xmm0,%%xmm0 \n" - MEMOPREG(movd,0x00,4,3,4,xmm2) // movd 0x0(%4,%3,4),%%xmm2 - "movzb " MEMACCESS2(0x07,0) ",%3 \n" - MEMOPREG(movd,0x00,4,3,4,xmm3) // movd 0x0(%4,%3,4),%%xmm3 - "pshuflw $0x40,%%xmm2,%%xmm2 \n" - "pshuflw $0x40,%%xmm3,%%xmm3 \n" - "movlhps %%xmm3,%%xmm2 \n" - "pmulhuw %%xmm2,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm1 \n" - "movzb " MEMACCESS2(0x0b,0) ",%3 \n" - "punpckhbw %%xmm1,%%xmm1 \n" - MEMOPREG(movd,0x00,4,3,4,xmm2) // movd 0x0(%4,%3,4),%%xmm2 - "movzb " MEMACCESS2(0x0f,0) ",%3 \n" - MEMOPREG(movd,0x00,4,3,4,xmm3) // movd 0x0(%4,%3,4),%%xmm3 - "pshuflw $0x40,%%xmm2,%%xmm2 \n" - "pshuflw $0x40,%%xmm3,%%xmm3 \n" - "movlhps %%xmm3,%%xmm2 \n" - "pmulhuw %%xmm2,%%xmm1 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width), // %2 - "=&r"(alpha) // %3 - : "r"(fixed_invtbl8) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_ARGBUNATTENUATEROW_SSE2 - -#ifdef HAS_ARGBUNATTENUATEROW_AVX2 -// Shuffle table duplicating alpha. -static const uvec8 kUnattenShuffleAlpha_AVX2 = { - 0u, 1u, 0u, 1u, 0u, 1u, 6u, 7u, 8u, 9u, 8u, 9u, 8u, 9u, 14u, 15u -}; -// Unattenuate 8 pixels at a time. -void ARGBUnattenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, - int width) { - uintptr_t alpha; - asm volatile ( - "sub %0,%1 \n" - "vbroadcastf128 %5,%%ymm5 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - // replace VPGATHER - "movzb " MEMACCESS2(0x03,0) ",%3 \n" - MEMOPREG(vmovd,0x00,4,3,4,xmm0) // vmovd 0x0(%4,%3,4),%%xmm0 - "movzb " MEMACCESS2(0x07,0) ",%3 \n" - MEMOPREG(vmovd,0x00,4,3,4,xmm1) // vmovd 0x0(%4,%3,4),%%xmm1 - "movzb " MEMACCESS2(0x0b,0) ",%3 \n" - "vpunpckldq %%xmm1,%%xmm0,%%xmm6 \n" - MEMOPREG(vmovd,0x00,4,3,4,xmm2) // vmovd 0x0(%4,%3,4),%%xmm2 - "movzb " MEMACCESS2(0x0f,0) ",%3 \n" - MEMOPREG(vmovd,0x00,4,3,4,xmm3) // vmovd 0x0(%4,%3,4),%%xmm3 - "movzb " MEMACCESS2(0x13,0) ",%3 \n" - "vpunpckldq %%xmm3,%%xmm2,%%xmm7 \n" - MEMOPREG(vmovd,0x00,4,3,4,xmm0) // vmovd 0x0(%4,%3,4),%%xmm0 - "movzb " MEMACCESS2(0x17,0) ",%3 \n" - MEMOPREG(vmovd,0x00,4,3,4,xmm1) // vmovd 0x0(%4,%3,4),%%xmm1 - "movzb " MEMACCESS2(0x1b,0) ",%3 \n" - "vpunpckldq %%xmm1,%%xmm0,%%xmm0 \n" - MEMOPREG(vmovd,0x00,4,3,4,xmm2) // vmovd 0x0(%4,%3,4),%%xmm2 - "movzb " MEMACCESS2(0x1f,0) ",%3 \n" - MEMOPREG(vmovd,0x00,4,3,4,xmm3) // vmovd 0x0(%4,%3,4),%%xmm3 - "vpunpckldq %%xmm3,%%xmm2,%%xmm2 \n" - "vpunpcklqdq %%xmm7,%%xmm6,%%xmm3 \n" - "vpunpcklqdq %%xmm2,%%xmm0,%%xmm0 \n" - "vinserti128 $0x1,%%xmm0,%%ymm3,%%ymm3 \n" - // end of VPGATHER - - "vmovdqu " MEMACCESS(0) ",%%ymm6 \n" - "vpunpcklbw %%ymm6,%%ymm6,%%ymm0 \n" - "vpunpckhbw %%ymm6,%%ymm6,%%ymm1 \n" - "vpunpcklwd %%ymm3,%%ymm3,%%ymm2 \n" - "vpunpckhwd %%ymm3,%%ymm3,%%ymm3 \n" - "vpshufb %%ymm5,%%ymm2,%%ymm2 \n" - "vpshufb %%ymm5,%%ymm3,%%ymm3 \n" - "vpmulhuw %%ymm2,%%ymm0,%%ymm0 \n" - "vpmulhuw %%ymm3,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - MEMOPMEM(vmovdqu,ymm0,0x00,0,1,1) // vmovdqu %%ymm0,(%0,%1) - "lea " MEMLEA(0x20,0) ",%0 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width), // %2 - "=&r"(alpha) // %3 - : "r"(fixed_invtbl8), // %4 - "m"(kUnattenShuffleAlpha_AVX2) // %5 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBUNATTENUATEROW_AVX2 - -#ifdef HAS_ARGBGRAYROW_SSSE3 -// Convert 8 ARGB pixels (64 bytes) to 8 Gray ARGB pixels -void ARGBGrayRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width) { - asm volatile ( - "movdqa %3,%%xmm4 \n" - "movdqa %4,%%xmm5 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "phaddw %%xmm1,%%xmm0 \n" - "paddw %%xmm5,%%xmm0 \n" - "psrlw $0x7,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm3 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "psrld $0x18,%%xmm2 \n" - "psrld $0x18,%%xmm3 \n" - "packuswb %%xmm3,%%xmm2 \n" - "packuswb %%xmm2,%%xmm2 \n" - "movdqa %%xmm0,%%xmm3 \n" - "punpcklbw %%xmm0,%%xmm0 \n" - "punpcklbw %%xmm2,%%xmm3 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklwd %%xmm3,%%xmm0 \n" - "punpckhwd %%xmm3,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "m"(kARGBToYJ), // %3 - "m"(kAddYJ64) // %4 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_ARGBGRAYROW_SSSE3 - -#ifdef HAS_ARGBSEPIAROW_SSSE3 -// b = (r * 35 + g * 68 + b * 17) >> 7 -// g = (r * 45 + g * 88 + b * 22) >> 7 -// r = (r * 50 + g * 98 + b * 24) >> 7 -// Constant for ARGB color to sepia tone -static vec8 kARGBToSepiaB = { - 17, 68, 35, 0, 17, 68, 35, 0, 17, 68, 35, 0, 17, 68, 35, 0 -}; - -static vec8 kARGBToSepiaG = { - 22, 88, 45, 0, 22, 88, 45, 0, 22, 88, 45, 0, 22, 88, 45, 0 -}; - -static vec8 kARGBToSepiaR = { - 24, 98, 50, 0, 24, 98, 50, 0, 24, 98, 50, 0, 24, 98, 50, 0 -}; - -// Convert 8 ARGB pixels (32 bytes) to 8 Sepia ARGB pixels. -void ARGBSepiaRow_SSSE3(uint8* dst_argb, int width) { - asm volatile ( - "movdqa %2,%%xmm2 \n" - "movdqa %3,%%xmm3 \n" - "movdqa %4,%%xmm4 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm6 \n" - "pmaddubsw %%xmm2,%%xmm0 \n" - "pmaddubsw %%xmm2,%%xmm6 \n" - "phaddw %%xmm6,%%xmm0 \n" - "psrlw $0x7,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm5 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm5 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "phaddw %%xmm1,%%xmm5 \n" - "psrlw $0x7,%%xmm5 \n" - "packuswb %%xmm5,%%xmm5 \n" - "punpcklbw %%xmm5,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm5 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm5 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "phaddw %%xmm1,%%xmm5 \n" - "psrlw $0x7,%%xmm5 \n" - "packuswb %%xmm5,%%xmm5 \n" - "movdqu " MEMACCESS(0) ",%%xmm6 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "psrld $0x18,%%xmm6 \n" - "psrld $0x18,%%xmm1 \n" - "packuswb %%xmm1,%%xmm6 \n" - "packuswb %%xmm6,%%xmm6 \n" - "punpcklbw %%xmm6,%%xmm5 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklwd %%xmm5,%%xmm0 \n" - "punpckhwd %%xmm5,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(0) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,0) " \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "sub $0x8,%1 \n" - "jg 1b \n" - : "+r"(dst_argb), // %0 - "+r"(width) // %1 - : "m"(kARGBToSepiaB), // %2 - "m"(kARGBToSepiaG), // %3 - "m"(kARGBToSepiaR) // %4 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} -#endif // HAS_ARGBSEPIAROW_SSSE3 - -#ifdef HAS_ARGBCOLORMATRIXROW_SSSE3 -// Tranform 8 ARGB pixels (32 bytes) with color matrix. -// Same as Sepia except matrix is provided. -void ARGBColorMatrixRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width) { - asm volatile ( - "movdqu " MEMACCESS(3) ",%%xmm5 \n" - "pshufd $0x00,%%xmm5,%%xmm2 \n" - "pshufd $0x55,%%xmm5,%%xmm3 \n" - "pshufd $0xaa,%%xmm5,%%xmm4 \n" - "pshufd $0xff,%%xmm5,%%xmm5 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm7 \n" - "pmaddubsw %%xmm2,%%xmm0 \n" - "pmaddubsw %%xmm2,%%xmm7 \n" - "movdqu " MEMACCESS(0) ",%%xmm6 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm6 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "phaddsw %%xmm7,%%xmm0 \n" - "phaddsw %%xmm1,%%xmm6 \n" - "psraw $0x6,%%xmm0 \n" - "psraw $0x6,%%xmm6 \n" - "packuswb %%xmm0,%%xmm0 \n" - "packuswb %%xmm6,%%xmm6 \n" - "punpcklbw %%xmm6,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm7 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm7 \n" - "phaddsw %%xmm7,%%xmm1 \n" - "movdqu " MEMACCESS(0) ",%%xmm6 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm7 \n" - "pmaddubsw %%xmm5,%%xmm6 \n" - "pmaddubsw %%xmm5,%%xmm7 \n" - "phaddsw %%xmm7,%%xmm6 \n" - "psraw $0x6,%%xmm1 \n" - "psraw $0x6,%%xmm6 \n" - "packuswb %%xmm1,%%xmm1 \n" - "packuswb %%xmm6,%%xmm6 \n" - "punpcklbw %%xmm6,%%xmm1 \n" - "movdqa %%xmm0,%%xmm6 \n" - "punpcklwd %%xmm1,%%xmm0 \n" - "punpckhwd %%xmm1,%%xmm6 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "movdqu %%xmm6," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(matrix_argb) // %3 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBCOLORMATRIXROW_SSSE3 - -#ifdef HAS_ARGBQUANTIZEROW_SSE2 -// Quantize 4 ARGB pixels (16 bytes). -void ARGBQuantizeRow_SSE2(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width) { - asm volatile ( - "movd %2,%%xmm2 \n" - "movd %3,%%xmm3 \n" - "movd %4,%%xmm4 \n" - "pshuflw $0x40,%%xmm2,%%xmm2 \n" - "pshufd $0x44,%%xmm2,%%xmm2 \n" - "pshuflw $0x40,%%xmm3,%%xmm3 \n" - "pshufd $0x44,%%xmm3,%%xmm3 \n" - "pshuflw $0x40,%%xmm4,%%xmm4 \n" - "pshufd $0x44,%%xmm4,%%xmm4 \n" - "pxor %%xmm5,%%xmm5 \n" - "pcmpeqb %%xmm6,%%xmm6 \n" - "pslld $0x18,%%xmm6 \n" - - // 4 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "punpcklbw %%xmm5,%%xmm0 \n" - "pmulhuw %%xmm2,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm1 \n" - "punpckhbw %%xmm5,%%xmm1 \n" - "pmulhuw %%xmm2,%%xmm1 \n" - "pmullw %%xmm3,%%xmm0 \n" - "movdqu " MEMACCESS(0) ",%%xmm7 \n" - "pmullw %%xmm3,%%xmm1 \n" - "pand %%xmm6,%%xmm7 \n" - "paddw %%xmm4,%%xmm0 \n" - "paddw %%xmm4,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "por %%xmm7,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(0) " \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "sub $0x4,%1 \n" - "jg 1b \n" - : "+r"(dst_argb), // %0 - "+r"(width) // %1 - : "r"(scale), // %2 - "r"(interval_size), // %3 - "r"(interval_offset) // %4 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBQUANTIZEROW_SSE2 - -#ifdef HAS_ARGBSHADEROW_SSE2 -// Shade 4 pixels at a time by specified value. -void ARGBShadeRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value) { - asm volatile ( - "movd %3,%%xmm2 \n" - "punpcklbw %%xmm2,%%xmm2 \n" - "punpcklqdq %%xmm2,%%xmm2 \n" - - // 4 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm0,%%xmm0 \n" - "punpckhbw %%xmm1,%%xmm1 \n" - "pmulhuw %%xmm2,%%xmm0 \n" - "pmulhuw %%xmm2,%%xmm1 \n" - "psrlw $0x8,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(value) // %3 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2" - ); -} -#endif // HAS_ARGBSHADEROW_SSE2 - -#ifdef HAS_ARGBMULTIPLYROW_SSE2 -// Multiply 2 rows of ARGB pixels together, 4 pixels at a time. -void ARGBMultiplyRow_SSE2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - "pxor %%xmm5,%%xmm5 \n" - - // 4 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqu " MEMACCESS(1) ",%%xmm2 \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "movdqu %%xmm0,%%xmm1 \n" - "movdqu %%xmm2,%%xmm3 \n" - "punpcklbw %%xmm0,%%xmm0 \n" - "punpckhbw %%xmm1,%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm2 \n" - "punpckhbw %%xmm5,%%xmm3 \n" - "pmulhuw %%xmm2,%%xmm0 \n" - "pmulhuw %%xmm3,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%3 \n" - "jg 1b \n" - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} -#endif // HAS_ARGBMULTIPLYROW_SSE2 - -#ifdef HAS_ARGBMULTIPLYROW_AVX2 -// Multiply 2 rows of ARGB pixels together, 8 pixels at a time. -void ARGBMultiplyRow_AVX2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - "vpxor %%ymm5,%%ymm5,%%ymm5 \n" - - // 4 pixel loop. - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "vmovdqu " MEMACCESS(1) ",%%ymm3 \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "vpunpcklbw %%ymm1,%%ymm1,%%ymm0 \n" - "vpunpckhbw %%ymm1,%%ymm1,%%ymm1 \n" - "vpunpcklbw %%ymm5,%%ymm3,%%ymm2 \n" - "vpunpckhbw %%ymm5,%%ymm3,%%ymm3 \n" - "vpmulhuw %%ymm2,%%ymm0,%%ymm0 \n" - "vpmulhuw %%ymm3,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vmovdqu %%ymm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x20,2) ",%2 \n" - "sub $0x8,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "memory", "cc" -#if defined(__AVX2__) - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" -#endif - ); -} -#endif // HAS_ARGBMULTIPLYROW_AVX2 - -#ifdef HAS_ARGBADDROW_SSE2 -// Add 2 rows of ARGB pixels together, 4 pixels at a time. -void ARGBAddRow_SSE2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 4 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqu " MEMACCESS(1) ",%%xmm1 \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "paddusb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%3 \n" - "jg 1b \n" - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "memory", "cc" - , "xmm0", "xmm1" - ); -} -#endif // HAS_ARGBADDROW_SSE2 - -#ifdef HAS_ARGBADDROW_AVX2 -// Add 2 rows of ARGB pixels together, 4 pixels at a time. -void ARGBAddRow_AVX2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 4 pixel loop. - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "vpaddusb " MEMACCESS(1) ",%%ymm0,%%ymm0 \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "vmovdqu %%ymm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x20,2) ",%2 \n" - "sub $0x8,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "memory", "cc" - , "xmm0" - ); -} -#endif // HAS_ARGBADDROW_AVX2 - -#ifdef HAS_ARGBSUBTRACTROW_SSE2 -// Subtract 2 rows of ARGB pixels, 4 pixels at a time. -void ARGBSubtractRow_SSE2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 4 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqu " MEMACCESS(1) ",%%xmm1 \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "psubusb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%3 \n" - "jg 1b \n" - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "memory", "cc" - , "xmm0", "xmm1" - ); -} -#endif // HAS_ARGBSUBTRACTROW_SSE2 - -#ifdef HAS_ARGBSUBTRACTROW_AVX2 -// Subtract 2 rows of ARGB pixels, 8 pixels at a time. -void ARGBSubtractRow_AVX2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 4 pixel loop. - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "vpsubusb " MEMACCESS(1) ",%%ymm0,%%ymm0 \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "vmovdqu %%ymm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x20,2) ",%2 \n" - "sub $0x8,%3 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "memory", "cc" - , "xmm0" - ); -} -#endif // HAS_ARGBSUBTRACTROW_AVX2 - -#ifdef HAS_SOBELXROW_SSE2 -// SobelX as a matrix is -// -1 0 1 -// -2 0 2 -// -1 0 1 -void SobelXRow_SSE2(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width) { - asm volatile ( - "sub %0,%1 \n" - "sub %0,%2 \n" - "sub %0,%3 \n" - "pxor %%xmm5,%%xmm5 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movq " MEMACCESS(0) ",%%xmm0 \n" - "movq " MEMACCESS2(0x2,0) ",%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm0 \n" - "punpcklbw %%xmm5,%%xmm1 \n" - "psubw %%xmm1,%%xmm0 \n" - MEMOPREG(movq,0x00,0,1,1,xmm1) // movq (%0,%1,1),%%xmm1 - MEMOPREG(movq,0x02,0,1,1,xmm2) // movq 0x2(%0,%1,1),%%xmm2 - "punpcklbw %%xmm5,%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm2 \n" - "psubw %%xmm2,%%xmm1 \n" - MEMOPREG(movq,0x00,0,2,1,xmm2) // movq (%0,%2,1),%%xmm2 - MEMOPREG(movq,0x02,0,2,1,xmm3) // movq 0x2(%0,%2,1),%%xmm3 - "punpcklbw %%xmm5,%%xmm2 \n" - "punpcklbw %%xmm5,%%xmm3 \n" - "psubw %%xmm3,%%xmm2 \n" - "paddw %%xmm2,%%xmm0 \n" - "paddw %%xmm1,%%xmm0 \n" - "paddw %%xmm1,%%xmm0 \n" - "pxor %%xmm1,%%xmm1 \n" - "psubw %%xmm0,%%xmm1 \n" - "pmaxsw %%xmm1,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - MEMOPMEM(movq,xmm0,0x00,0,3,1) // movq %%xmm0,(%0,%3,1) - "lea " MEMLEA(0x8,0) ",%0 \n" - "sub $0x8,%4 \n" - "jg 1b \n" - : "+r"(src_y0), // %0 - "+r"(src_y1), // %1 - "+r"(src_y2), // %2 - "+r"(dst_sobelx), // %3 - "+r"(width) // %4 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} -#endif // HAS_SOBELXROW_SSE2 - -#ifdef HAS_SOBELYROW_SSE2 -// SobelY as a matrix is -// -1 -2 -1 -// 0 0 0 -// 1 2 1 -void SobelYRow_SSE2(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width) { - asm volatile ( - "sub %0,%1 \n" - "sub %0,%2 \n" - "pxor %%xmm5,%%xmm5 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movq " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movq,0x00,0,1,1,xmm1) // movq (%0,%1,1),%%xmm1 - "punpcklbw %%xmm5,%%xmm0 \n" - "punpcklbw %%xmm5,%%xmm1 \n" - "psubw %%xmm1,%%xmm0 \n" - "movq " MEMACCESS2(0x1,0) ",%%xmm1 \n" - MEMOPREG(movq,0x01,0,1,1,xmm2) // movq 0x1(%0,%1,1),%%xmm2 - "punpcklbw %%xmm5,%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm2 \n" - "psubw %%xmm2,%%xmm1 \n" - "movq " MEMACCESS2(0x2,0) ",%%xmm2 \n" - MEMOPREG(movq,0x02,0,1,1,xmm3) // movq 0x2(%0,%1,1),%%xmm3 - "punpcklbw %%xmm5,%%xmm2 \n" - "punpcklbw %%xmm5,%%xmm3 \n" - "psubw %%xmm3,%%xmm2 \n" - "paddw %%xmm2,%%xmm0 \n" - "paddw %%xmm1,%%xmm0 \n" - "paddw %%xmm1,%%xmm0 \n" - "pxor %%xmm1,%%xmm1 \n" - "psubw %%xmm0,%%xmm1 \n" - "pmaxsw %%xmm1,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - MEMOPMEM(movq,xmm0,0x00,0,2,1) // movq %%xmm0,(%0,%2,1) - "lea " MEMLEA(0x8,0) ",%0 \n" - "sub $0x8,%3 \n" - "jg 1b \n" - : "+r"(src_y0), // %0 - "+r"(src_y1), // %1 - "+r"(dst_sobely), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} -#endif // HAS_SOBELYROW_SSE2 - -#ifdef HAS_SOBELROW_SSE2 -// Adds Sobel X and Sobel Y and stores Sobel into ARGB. -// A = 255 -// R = Sobel -// G = Sobel -// B = Sobel -void SobelRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - asm volatile ( - "sub %0,%1 \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - "pslld $0x18,%%xmm5 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,1,1,xmm1) // movdqu (%0,%1,1),%%xmm1 - "lea " MEMLEA(0x10,0) ",%0 \n" - "paddusb %%xmm1,%%xmm0 \n" - "movdqa %%xmm0,%%xmm2 \n" - "punpcklbw %%xmm0,%%xmm2 \n" - "punpckhbw %%xmm0,%%xmm0 \n" - "movdqa %%xmm2,%%xmm1 \n" - "punpcklwd %%xmm2,%%xmm1 \n" - "punpckhwd %%xmm2,%%xmm2 \n" - "por %%xmm5,%%xmm1 \n" - "por %%xmm5,%%xmm2 \n" - "movdqa %%xmm0,%%xmm3 \n" - "punpcklwd %%xmm0,%%xmm3 \n" - "punpckhwd %%xmm0,%%xmm0 \n" - "por %%xmm5,%%xmm3 \n" - "por %%xmm5,%%xmm0 \n" - "movdqu %%xmm1," MEMACCESS(2) " \n" - "movdqu %%xmm2," MEMACCESS2(0x10,2) " \n" - "movdqu %%xmm3," MEMACCESS2(0x20,2) " \n" - "movdqu %%xmm0," MEMACCESS2(0x30,2) " \n" - "lea " MEMLEA(0x40,2) ",%2 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} -#endif // HAS_SOBELROW_SSE2 - -#ifdef HAS_SOBELTOPLANEROW_SSE2 -// Adds Sobel X and Sobel Y and stores Sobel into a plane. -void SobelToPlaneRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width) { - asm volatile ( - "sub %0,%1 \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - "pslld $0x18,%%xmm5 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,1,1,xmm1) // movdqu (%0,%1,1),%%xmm1 - "lea " MEMLEA(0x10,0) ",%0 \n" - "paddusb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_y), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1" - ); -} -#endif // HAS_SOBELTOPLANEROW_SSE2 - -#ifdef HAS_SOBELXYROW_SSE2 -// Mixes Sobel X, Sobel Y and Sobel into ARGB. -// A = 255 -// R = Sobel X -// G = Sobel -// B = Sobel Y -void SobelXYRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - asm volatile ( - "sub %0,%1 \n" - "pcmpeqb %%xmm5,%%xmm5 \n" - - // 8 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,1,1,xmm1) // movdqu (%0,%1,1),%%xmm1 - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm0,%%xmm2 \n" - "paddusb %%xmm1,%%xmm2 \n" - "movdqa %%xmm0,%%xmm3 \n" - "punpcklbw %%xmm5,%%xmm3 \n" - "punpckhbw %%xmm5,%%xmm0 \n" - "movdqa %%xmm1,%%xmm4 \n" - "punpcklbw %%xmm2,%%xmm4 \n" - "punpckhbw %%xmm2,%%xmm1 \n" - "movdqa %%xmm4,%%xmm6 \n" - "punpcklwd %%xmm3,%%xmm6 \n" - "punpckhwd %%xmm3,%%xmm4 \n" - "movdqa %%xmm1,%%xmm7 \n" - "punpcklwd %%xmm0,%%xmm7 \n" - "punpckhwd %%xmm0,%%xmm1 \n" - "movdqu %%xmm6," MEMACCESS(2) " \n" - "movdqu %%xmm4," MEMACCESS2(0x10,2) " \n" - "movdqu %%xmm7," MEMACCESS2(0x20,2) " \n" - "movdqu %%xmm1," MEMACCESS2(0x30,2) " \n" - "lea " MEMLEA(0x40,2) ",%2 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_SOBELXYROW_SSE2 - -#ifdef HAS_COMPUTECUMULATIVESUMROW_SSE2 -// Creates a table of cumulative sums where each value is a sum of all values -// above and to the left of the value, inclusive of the value. -void ComputeCumulativeSumRow_SSE2(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width) { - asm volatile ( - "pxor %%xmm0,%%xmm0 \n" - "pxor %%xmm1,%%xmm1 \n" - "sub $0x4,%3 \n" - "jl 49f \n" - "test $0xf,%1 \n" - "jne 49f \n" - - // 4 pixel loop \n" - LABELALIGN - "40: \n" - "movdqu " MEMACCESS(0) ",%%xmm2 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm2,%%xmm4 \n" - "punpcklbw %%xmm1,%%xmm2 \n" - "movdqa %%xmm2,%%xmm3 \n" - "punpcklwd %%xmm1,%%xmm2 \n" - "punpckhwd %%xmm1,%%xmm3 \n" - "punpckhbw %%xmm1,%%xmm4 \n" - "movdqa %%xmm4,%%xmm5 \n" - "punpcklwd %%xmm1,%%xmm4 \n" - "punpckhwd %%xmm1,%%xmm5 \n" - "paddd %%xmm2,%%xmm0 \n" - "movdqu " MEMACCESS(2) ",%%xmm2 \n" - "paddd %%xmm0,%%xmm2 \n" - "paddd %%xmm3,%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,2) ",%%xmm3 \n" - "paddd %%xmm0,%%xmm3 \n" - "paddd %%xmm4,%%xmm0 \n" - "movdqu " MEMACCESS2(0x20,2) ",%%xmm4 \n" - "paddd %%xmm0,%%xmm4 \n" - "paddd %%xmm5,%%xmm0 \n" - "movdqu " MEMACCESS2(0x30,2) ",%%xmm5 \n" - "lea " MEMLEA(0x40,2) ",%2 \n" - "paddd %%xmm0,%%xmm5 \n" - "movdqu %%xmm2," MEMACCESS(1) " \n" - "movdqu %%xmm3," MEMACCESS2(0x10,1) " \n" - "movdqu %%xmm4," MEMACCESS2(0x20,1) " \n" - "movdqu %%xmm5," MEMACCESS2(0x30,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x4,%3 \n" - "jge 40b \n" - - "49: \n" - "add $0x3,%3 \n" - "jl 19f \n" - - // 1 pixel loop \n" - LABELALIGN - "10: \n" - "movd " MEMACCESS(0) ",%%xmm2 \n" - "lea " MEMLEA(0x4,0) ",%0 \n" - "punpcklbw %%xmm1,%%xmm2 \n" - "punpcklwd %%xmm1,%%xmm2 \n" - "paddd %%xmm2,%%xmm0 \n" - "movdqu " MEMACCESS(2) ",%%xmm2 \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "paddd %%xmm0,%%xmm2 \n" - "movdqu %%xmm2," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x1,%3 \n" - "jge 10b \n" - - "19: \n" - : "+r"(row), // %0 - "+r"(cumsum), // %1 - "+r"(previous_cumsum), // %2 - "+r"(width) // %3 - : - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_COMPUTECUMULATIVESUMROW_SSE2 - -#ifdef HAS_CUMULATIVESUMTOAVERAGEROW_SSE2 -void CumulativeSumToAverageRow_SSE2(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, - int count) { - asm volatile ( - "movd %5,%%xmm5 \n" - "cvtdq2ps %%xmm5,%%xmm5 \n" - "rcpss %%xmm5,%%xmm4 \n" - "pshufd $0x0,%%xmm4,%%xmm4 \n" - "sub $0x4,%3 \n" - "jl 49f \n" - "cmpl $0x80,%5 \n" - "ja 40f \n" - - "pshufd $0x0,%%xmm5,%%xmm5 \n" - "pcmpeqb %%xmm6,%%xmm6 \n" - "psrld $0x10,%%xmm6 \n" - "cvtdq2ps %%xmm6,%%xmm6 \n" - "addps %%xmm6,%%xmm5 \n" - "mulps %%xmm4,%%xmm5 \n" - "cvtps2dq %%xmm5,%%xmm5 \n" - "packssdw %%xmm5,%%xmm5 \n" - - // 4 pixel small loop \n" - LABELALIGN - "4: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - MEMOPREG(psubd,0x00,0,4,4,xmm0) // psubd 0x00(%0,%4,4),%%xmm0 - MEMOPREG(psubd,0x10,0,4,4,xmm1) // psubd 0x10(%0,%4,4),%%xmm1 - MEMOPREG(psubd,0x20,0,4,4,xmm2) // psubd 0x20(%0,%4,4),%%xmm2 - MEMOPREG(psubd,0x30,0,4,4,xmm3) // psubd 0x30(%0,%4,4),%%xmm3 - "lea " MEMLEA(0x40,0) ",%0 \n" - "psubd " MEMACCESS(1) ",%%xmm0 \n" - "psubd " MEMACCESS2(0x10,1) ",%%xmm1 \n" - "psubd " MEMACCESS2(0x20,1) ",%%xmm2 \n" - "psubd " MEMACCESS2(0x30,1) ",%%xmm3 \n" - MEMOPREG(paddd,0x00,1,4,4,xmm0) // paddd 0x00(%1,%4,4),%%xmm0 - MEMOPREG(paddd,0x10,1,4,4,xmm1) // paddd 0x10(%1,%4,4),%%xmm1 - MEMOPREG(paddd,0x20,1,4,4,xmm2) // paddd 0x20(%1,%4,4),%%xmm2 - MEMOPREG(paddd,0x30,1,4,4,xmm3) // paddd 0x30(%1,%4,4),%%xmm3 - "lea " MEMLEA(0x40,1) ",%1 \n" - "packssdw %%xmm1,%%xmm0 \n" - "packssdw %%xmm3,%%xmm2 \n" - "pmulhuw %%xmm5,%%xmm0 \n" - "pmulhuw %%xmm5,%%xmm2 \n" - "packuswb %%xmm2,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%3 \n" - "jge 4b \n" - "jmp 49f \n" - - // 4 pixel loop \n" - LABELALIGN - "40: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm3 \n" - MEMOPREG(psubd,0x00,0,4,4,xmm0) // psubd 0x00(%0,%4,4),%%xmm0 - MEMOPREG(psubd,0x10,0,4,4,xmm1) // psubd 0x10(%0,%4,4),%%xmm1 - MEMOPREG(psubd,0x20,0,4,4,xmm2) // psubd 0x20(%0,%4,4),%%xmm2 - MEMOPREG(psubd,0x30,0,4,4,xmm3) // psubd 0x30(%0,%4,4),%%xmm3 - "lea " MEMLEA(0x40,0) ",%0 \n" - "psubd " MEMACCESS(1) ",%%xmm0 \n" - "psubd " MEMACCESS2(0x10,1) ",%%xmm1 \n" - "psubd " MEMACCESS2(0x20,1) ",%%xmm2 \n" - "psubd " MEMACCESS2(0x30,1) ",%%xmm3 \n" - MEMOPREG(paddd,0x00,1,4,4,xmm0) // paddd 0x00(%1,%4,4),%%xmm0 - MEMOPREG(paddd,0x10,1,4,4,xmm1) // paddd 0x10(%1,%4,4),%%xmm1 - MEMOPREG(paddd,0x20,1,4,4,xmm2) // paddd 0x20(%1,%4,4),%%xmm2 - MEMOPREG(paddd,0x30,1,4,4,xmm3) // paddd 0x30(%1,%4,4),%%xmm3 - "lea " MEMLEA(0x40,1) ",%1 \n" - "cvtdq2ps %%xmm0,%%xmm0 \n" - "cvtdq2ps %%xmm1,%%xmm1 \n" - "mulps %%xmm4,%%xmm0 \n" - "mulps %%xmm4,%%xmm1 \n" - "cvtdq2ps %%xmm2,%%xmm2 \n" - "cvtdq2ps %%xmm3,%%xmm3 \n" - "mulps %%xmm4,%%xmm2 \n" - "mulps %%xmm4,%%xmm3 \n" - "cvtps2dq %%xmm0,%%xmm0 \n" - "cvtps2dq %%xmm1,%%xmm1 \n" - "cvtps2dq %%xmm2,%%xmm2 \n" - "cvtps2dq %%xmm3,%%xmm3 \n" - "packssdw %%xmm1,%%xmm0 \n" - "packssdw %%xmm3,%%xmm2 \n" - "packuswb %%xmm2,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%3 \n" - "jge 40b \n" - - "49: \n" - "add $0x3,%3 \n" - "jl 19f \n" - - // 1 pixel loop \n" - LABELALIGN - "10: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(psubd,0x00,0,4,4,xmm0) // psubd 0x00(%0,%4,4),%%xmm0 - "lea " MEMLEA(0x10,0) ",%0 \n" - "psubd " MEMACCESS(1) ",%%xmm0 \n" - MEMOPREG(paddd,0x00,1,4,4,xmm0) // paddd 0x00(%1,%4,4),%%xmm0 - "lea " MEMLEA(0x10,1) ",%1 \n" - "cvtdq2ps %%xmm0,%%xmm0 \n" - "mulps %%xmm4,%%xmm0 \n" - "cvtps2dq %%xmm0,%%xmm0 \n" - "packssdw %%xmm0,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movd %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x4,2) ",%2 \n" - "sub $0x1,%3 \n" - "jge 10b \n" - "19: \n" - : "+r"(topleft), // %0 - "+r"(botleft), // %1 - "+r"(dst), // %2 - "+rm"(count) // %3 - : "r"((intptr_t)(width)), // %4 - "rm"(area) // %5 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} -#endif // HAS_CUMULATIVESUMTOAVERAGEROW_SSE2 - -#ifdef HAS_ARGBAFFINEROW_SSE2 -// Copy ARGB pixels from source image with slope to a row of destination. -LIBYUV_API -void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* src_dudv, int width) { - intptr_t src_argb_stride_temp = src_argb_stride; - intptr_t temp; - asm volatile ( - "movq " MEMACCESS(3) ",%%xmm2 \n" - "movq " MEMACCESS2(0x08,3) ",%%xmm7 \n" - "shl $0x10,%1 \n" - "add $0x4,%1 \n" - "movd %1,%%xmm5 \n" - "sub $0x4,%4 \n" - "jl 49f \n" - - "pshufd $0x44,%%xmm7,%%xmm7 \n" - "pshufd $0x0,%%xmm5,%%xmm5 \n" - "movdqa %%xmm2,%%xmm0 \n" - "addps %%xmm7,%%xmm0 \n" - "movlhps %%xmm0,%%xmm2 \n" - "movdqa %%xmm7,%%xmm4 \n" - "addps %%xmm4,%%xmm4 \n" - "movdqa %%xmm2,%%xmm3 \n" - "addps %%xmm4,%%xmm3 \n" - "addps %%xmm4,%%xmm4 \n" - - // 4 pixel loop \n" - LABELALIGN - "40: \n" - "cvttps2dq %%xmm2,%%xmm0 \n" // x, y float to int first 2 - "cvttps2dq %%xmm3,%%xmm1 \n" // x, y float to int next 2 - "packssdw %%xmm1,%%xmm0 \n" // x, y as 8 shorts - "pmaddwd %%xmm5,%%xmm0 \n" // off = x * 4 + y * stride - "movd %%xmm0,%k1 \n" - "pshufd $0x39,%%xmm0,%%xmm0 \n" - "movd %%xmm0,%k5 \n" - "pshufd $0x39,%%xmm0,%%xmm0 \n" - MEMOPREG(movd,0x00,0,1,1,xmm1) // movd (%0,%1,1),%%xmm1 - MEMOPREG(movd,0x00,0,5,1,xmm6) // movd (%0,%5,1),%%xmm6 - "punpckldq %%xmm6,%%xmm1 \n" - "addps %%xmm4,%%xmm2 \n" - "movq %%xmm1," MEMACCESS(2) " \n" - "movd %%xmm0,%k1 \n" - "pshufd $0x39,%%xmm0,%%xmm0 \n" - "movd %%xmm0,%k5 \n" - MEMOPREG(movd,0x00,0,1,1,xmm0) // movd (%0,%1,1),%%xmm0 - MEMOPREG(movd,0x00,0,5,1,xmm6) // movd (%0,%5,1),%%xmm6 - "punpckldq %%xmm6,%%xmm0 \n" - "addps %%xmm4,%%xmm3 \n" - "movq %%xmm0," MEMACCESS2(0x08,2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%4 \n" - "jge 40b \n" - - "49: \n" - "add $0x3,%4 \n" - "jl 19f \n" - - // 1 pixel loop \n" - LABELALIGN - "10: \n" - "cvttps2dq %%xmm2,%%xmm0 \n" - "packssdw %%xmm0,%%xmm0 \n" - "pmaddwd %%xmm5,%%xmm0 \n" - "addps %%xmm7,%%xmm2 \n" - "movd %%xmm0,%k1 \n" - MEMOPREG(movd,0x00,0,1,1,xmm0) // movd (%0,%1,1),%%xmm0 - "movd %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x04,2) ",%2 \n" - "sub $0x1,%4 \n" - "jge 10b \n" - "19: \n" - : "+r"(src_argb), // %0 - "+r"(src_argb_stride_temp), // %1 - "+r"(dst_argb), // %2 - "+r"(src_dudv), // %3 - "+rm"(width), // %4 - "=&r"(temp) // %5 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBAFFINEROW_SSE2 - -#ifdef HAS_INTERPOLATEROW_SSSE3 -// Bilinear filter 16x2 -> 16x1 -void InterpolateRow_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, - int source_y_fraction) { - asm volatile ( - "sub %1,%0 \n" - "cmp $0x0,%3 \n" - "je 100f \n" - "cmp $0x80,%3 \n" - "je 50f \n" - - "movd %3,%%xmm0 \n" - "neg %3 \n" - "add $0x100,%3 \n" - "movd %3,%%xmm5 \n" - "punpcklbw %%xmm0,%%xmm5 \n" - "punpcklwd %%xmm5,%%xmm5 \n" - "pshufd $0x0,%%xmm5,%%xmm5 \n" - "mov $0x80808080,%%eax \n" - "movd %%eax,%%xmm4 \n" - "pshufd $0x0,%%xmm4,%%xmm4 \n" - - // General purpose row blend. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(1) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,1,4,1,xmm2) - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm2,%%xmm0 \n" - "punpckhbw %%xmm2,%%xmm1 \n" - "psubb %%xmm4,%%xmm0 \n" - "psubb %%xmm4,%%xmm1 \n" - "movdqa %%xmm5,%%xmm2 \n" - "movdqa %%xmm5,%%xmm3 \n" - "pmaddubsw %%xmm0,%%xmm2 \n" - "pmaddubsw %%xmm1,%%xmm3 \n" - "paddw %%xmm4,%%xmm2 \n" - "paddw %%xmm4,%%xmm3 \n" - "psrlw $0x8,%%xmm2 \n" - "psrlw $0x8,%%xmm3 \n" - "packuswb %%xmm3,%%xmm2 \n" - MEMOPMEM(movdqu,xmm2,0x00,1,0,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - "jmp 99f \n" - - // Blend 50 / 50. - LABELALIGN - "50: \n" - "movdqu " MEMACCESS(1) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,1,4,1,xmm1) - "pavgb %%xmm1,%%xmm0 \n" - MEMOPMEM(movdqu,xmm0,0x00,1,0,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 50b \n" - "jmp 99f \n" - - // Blend 100 / 0 - Copy row unchanged. - LABELALIGN - "100: \n" - "movdqu " MEMACCESS(1) ",%%xmm0 \n" - MEMOPMEM(movdqu,xmm0,0x00,1,0,1) - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 100b \n" - - "99: \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+rm"(dst_width), // %2 - "+r"(source_y_fraction) // %3 - : "r"((intptr_t)(src_stride)) // %4 - : "memory", "cc", "eax", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_INTERPOLATEROW_SSSE3 - -#ifdef HAS_INTERPOLATEROW_AVX2 -// Bilinear filter 32x2 -> 32x1 -void InterpolateRow_AVX2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, - int source_y_fraction) { - asm volatile ( - "cmp $0x0,%3 \n" - "je 100f \n" - "sub %1,%0 \n" - "cmp $0x80,%3 \n" - "je 50f \n" - - "vmovd %3,%%xmm0 \n" - "neg %3 \n" - "add $0x100,%3 \n" - "vmovd %3,%%xmm5 \n" - "vpunpcklbw %%xmm0,%%xmm5,%%xmm5 \n" - "vpunpcklwd %%xmm5,%%xmm5,%%xmm5 \n" - "vbroadcastss %%xmm5,%%ymm5 \n" - "mov $0x80808080,%%eax \n" - "vmovd %%eax,%%xmm4 \n" - "vbroadcastss %%xmm4,%%ymm4 \n" - - // General purpose row blend. - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(1) ",%%ymm0 \n" - MEMOPREG(vmovdqu,0x00,1,4,1,ymm2) - "vpunpckhbw %%ymm2,%%ymm0,%%ymm1 \n" - "vpunpcklbw %%ymm2,%%ymm0,%%ymm0 \n" - "vpsubb %%ymm4,%%ymm1,%%ymm1 \n" - "vpsubb %%ymm4,%%ymm0,%%ymm0 \n" - "vpmaddubsw %%ymm1,%%ymm5,%%ymm1 \n" - "vpmaddubsw %%ymm0,%%ymm5,%%ymm0 \n" - "vpaddw %%ymm4,%%ymm1,%%ymm1 \n" - "vpaddw %%ymm4,%%ymm0,%%ymm0 \n" - "vpsrlw $0x8,%%ymm1,%%ymm1 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - MEMOPMEM(vmovdqu,ymm0,0x00,1,0,1) - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "jmp 99f \n" - - // Blend 50 / 50. - LABELALIGN - "50: \n" - "vmovdqu " MEMACCESS(1) ",%%ymm0 \n" - VMEMOPREG(vpavgb,0x00,1,4,1,ymm0,ymm0) // vpavgb (%1,%4,1),%%ymm0,%%ymm0 - MEMOPMEM(vmovdqu,ymm0,0x00,1,0,1) - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 50b \n" - "jmp 99f \n" - - // Blend 100 / 0 - Copy row unchanged. - LABELALIGN - "100: \n" - "rep movsb " MEMMOVESTRING(1,0) " \n" - "jmp 999f \n" - - "99: \n" - "vzeroupper \n" - "999: \n" - : "+D"(dst_ptr), // %0 - "+S"(src_ptr), // %1 - "+cm"(dst_width), // %2 - "+r"(source_y_fraction) // %3 - : "r"((intptr_t)(src_stride)) // %4 - : "memory", "cc", "eax", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm4", "xmm5" - ); -} -#endif // HAS_INTERPOLATEROW_AVX2 - -#ifdef HAS_ARGBSHUFFLEROW_SSSE3 -// For BGRAToARGB, ABGRToARGB, RGBAToARGB, and ARGBToRGBA. -void ARGBShuffleRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - asm volatile ( - "movdqu " MEMACCESS(3) ",%%xmm5 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "pshufb %%xmm5,%%xmm0 \n" - "pshufb %%xmm5,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(shuffler) // %3 - : "memory", "cc" - , "xmm0", "xmm1", "xmm5" - ); -} -#endif // HAS_ARGBSHUFFLEROW_SSSE3 - -#ifdef HAS_ARGBSHUFFLEROW_AVX2 -// For BGRAToARGB, ABGRToARGB, RGBAToARGB, and ARGBToRGBA. -void ARGBShuffleRow_AVX2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - asm volatile ( - "vbroadcastf128 " MEMACCESS(3) ",%%ymm5 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpshufb %%ymm5,%%ymm0,%%ymm0 \n" - "vpshufb %%ymm5,%%ymm1,%%ymm1 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "vmovdqu %%ymm1," MEMACCESS2(0x20,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(shuffler) // %3 - : "memory", "cc" - , "xmm0", "xmm1", "xmm5" - ); -} -#endif // HAS_ARGBSHUFFLEROW_AVX2 - -#ifdef HAS_ARGBSHUFFLEROW_SSE2 -// For BGRAToARGB, ABGRToARGB, RGBAToARGB, and ARGBToRGBA. -void ARGBShuffleRow_SSE2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - uintptr_t pixel_temp; - asm volatile ( - "pxor %%xmm5,%%xmm5 \n" - "mov " MEMACCESS(4) ",%k2 \n" - "cmp $0x3000102,%k2 \n" - "je 3012f \n" - "cmp $0x10203,%k2 \n" - "je 123f \n" - "cmp $0x30201,%k2 \n" - "je 321f \n" - "cmp $0x2010003,%k2 \n" - "je 2103f \n" - - LABELALIGN - "1: \n" - "movzb " MEMACCESS(4) ",%2 \n" - MEMOPARG(movzb,0x00,0,2,1,2) " \n" // movzb (%0,%2,1),%2 - "mov %b2," MEMACCESS(1) " \n" - "movzb " MEMACCESS2(0x1,4) ",%2 \n" - MEMOPARG(movzb,0x00,0,2,1,2) " \n" // movzb (%0,%2,1),%2 - "mov %b2," MEMACCESS2(0x1,1) " \n" - "movzb " MEMACCESS2(0x2,4) ",%2 \n" - MEMOPARG(movzb,0x00,0,2,1,2) " \n" // movzb (%0,%2,1),%2 - "mov %b2," MEMACCESS2(0x2,1) " \n" - "movzb " MEMACCESS2(0x3,4) ",%2 \n" - MEMOPARG(movzb,0x00,0,2,1,2) " \n" // movzb (%0,%2,1),%2 - "mov %b2," MEMACCESS2(0x3,1) " \n" - "lea " MEMLEA(0x4,0) ",%0 \n" - "lea " MEMLEA(0x4,1) ",%1 \n" - "sub $0x1,%3 \n" - "jg 1b \n" - "jmp 99f \n" - - LABELALIGN - "123: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm0 \n" - "punpckhbw %%xmm5,%%xmm1 \n" - "pshufhw $0x1b,%%xmm0,%%xmm0 \n" - "pshuflw $0x1b,%%xmm0,%%xmm0 \n" - "pshufhw $0x1b,%%xmm1,%%xmm1 \n" - "pshuflw $0x1b,%%xmm1,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%3 \n" - "jg 123b \n" - "jmp 99f \n" - - LABELALIGN - "321: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm0 \n" - "punpckhbw %%xmm5,%%xmm1 \n" - "pshufhw $0x39,%%xmm0,%%xmm0 \n" - "pshuflw $0x39,%%xmm0,%%xmm0 \n" - "pshufhw $0x39,%%xmm1,%%xmm1 \n" - "pshuflw $0x39,%%xmm1,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%3 \n" - "jg 321b \n" - "jmp 99f \n" - - LABELALIGN - "2103: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm0 \n" - "punpckhbw %%xmm5,%%xmm1 \n" - "pshufhw $0x93,%%xmm0,%%xmm0 \n" - "pshuflw $0x93,%%xmm0,%%xmm0 \n" - "pshufhw $0x93,%%xmm1,%%xmm1 \n" - "pshuflw $0x93,%%xmm1,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%3 \n" - "jg 2103b \n" - "jmp 99f \n" - - LABELALIGN - "3012: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm0 \n" - "punpckhbw %%xmm5,%%xmm1 \n" - "pshufhw $0xc6,%%xmm0,%%xmm0 \n" - "pshuflw $0xc6,%%xmm0,%%xmm0 \n" - "pshufhw $0xc6,%%xmm1,%%xmm1 \n" - "pshuflw $0xc6,%%xmm1,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%3 \n" - "jg 3012b \n" - - "99: \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "=&d"(pixel_temp), // %2 - "+r"(width) // %3 - : "r"(shuffler) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm5" - ); -} -#endif // HAS_ARGBSHUFFLEROW_SSE2 - -#ifdef HAS_I422TOYUY2ROW_SSE2 -void I422ToYUY2Row_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_frame, int width) { - asm volatile ( - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movq " MEMACCESS(1) ",%%xmm2 \n" - MEMOPREG(movq,0x00,1,2,1,xmm3) // movq (%1,%2,1),%%xmm3 - "lea " MEMLEA(0x8,1) ",%1 \n" - "punpcklbw %%xmm3,%%xmm2 \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm2,%%xmm0 \n" - "punpckhbw %%xmm2,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(3) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,3) " \n" - "lea " MEMLEA(0x20,3) ",%3 \n" - "sub $0x10,%4 \n" - "jg 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_frame), // %3 - "+rm"(width) // %4 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3" - ); -} -#endif // HAS_I422TOYUY2ROW_SSE2 - -#ifdef HAS_I422TOUYVYROW_SSE2 -void I422ToUYVYRow_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_frame, int width) { - asm volatile ( - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movq " MEMACCESS(1) ",%%xmm2 \n" - MEMOPREG(movq,0x00,1,2,1,xmm3) // movq (%1,%2,1),%%xmm3 - "lea " MEMLEA(0x8,1) ",%1 \n" - "punpcklbw %%xmm3,%%xmm2 \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqa %%xmm2,%%xmm1 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" - "punpcklbw %%xmm0,%%xmm1 \n" - "punpckhbw %%xmm0,%%xmm2 \n" - "movdqu %%xmm1," MEMACCESS(3) " \n" - "movdqu %%xmm2," MEMACCESS2(0x10,3) " \n" - "lea " MEMLEA(0x20,3) ",%3 \n" - "sub $0x10,%4 \n" - "jg 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_frame), // %3 - "+rm"(width) // %4 - : - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3" - ); -} -#endif // HAS_I422TOUYVYROW_SSE2 - -#ifdef HAS_ARGBPOLYNOMIALROW_SSE2 -void ARGBPolynomialRow_SSE2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width) { - asm volatile ( - "pxor %%xmm3,%%xmm3 \n" - - // 2 pixel loop. - LABELALIGN - "1: \n" - "movq " MEMACCESS(0) ",%%xmm0 \n" - "lea " MEMLEA(0x8,0) ",%0 \n" - "punpcklbw %%xmm3,%%xmm0 \n" - "movdqa %%xmm0,%%xmm4 \n" - "punpcklwd %%xmm3,%%xmm0 \n" - "punpckhwd %%xmm3,%%xmm4 \n" - "cvtdq2ps %%xmm0,%%xmm0 \n" - "cvtdq2ps %%xmm4,%%xmm4 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm4,%%xmm5 \n" - "mulps " MEMACCESS2(0x10,3) ",%%xmm0 \n" - "mulps " MEMACCESS2(0x10,3) ",%%xmm4 \n" - "addps " MEMACCESS(3) ",%%xmm0 \n" - "addps " MEMACCESS(3) ",%%xmm4 \n" - "movdqa %%xmm1,%%xmm2 \n" - "movdqa %%xmm5,%%xmm6 \n" - "mulps %%xmm1,%%xmm2 \n" - "mulps %%xmm5,%%xmm6 \n" - "mulps %%xmm2,%%xmm1 \n" - "mulps %%xmm6,%%xmm5 \n" - "mulps " MEMACCESS2(0x20,3) ",%%xmm2 \n" - "mulps " MEMACCESS2(0x20,3) ",%%xmm6 \n" - "mulps " MEMACCESS2(0x30,3) ",%%xmm1 \n" - "mulps " MEMACCESS2(0x30,3) ",%%xmm5 \n" - "addps %%xmm2,%%xmm0 \n" - "addps %%xmm6,%%xmm4 \n" - "addps %%xmm1,%%xmm0 \n" - "addps %%xmm5,%%xmm4 \n" - "cvttps2dq %%xmm0,%%xmm0 \n" - "cvttps2dq %%xmm4,%%xmm4 \n" - "packuswb %%xmm4,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x2,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(poly) // %3 - : "memory", "cc" - , "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} -#endif // HAS_ARGBPOLYNOMIALROW_SSE2 - -#ifdef HAS_ARGBPOLYNOMIALROW_AVX2 -void ARGBPolynomialRow_AVX2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width) { - asm volatile ( - "vbroadcastf128 " MEMACCESS(3) ",%%ymm4 \n" - "vbroadcastf128 " MEMACCESS2(0x10,3) ",%%ymm5 \n" - "vbroadcastf128 " MEMACCESS2(0x20,3) ",%%ymm6 \n" - "vbroadcastf128 " MEMACCESS2(0x30,3) ",%%ymm7 \n" - - // 2 pixel loop. - LABELALIGN - "1: \n" - "vpmovzxbd " MEMACCESS(0) ",%%ymm0 \n" // 2 ARGB pixels - "lea " MEMLEA(0x8,0) ",%0 \n" - "vcvtdq2ps %%ymm0,%%ymm0 \n" // X 8 floats - "vmulps %%ymm0,%%ymm0,%%ymm2 \n" // X * X - "vmulps %%ymm7,%%ymm0,%%ymm3 \n" // C3 * X - "vfmadd132ps %%ymm5,%%ymm4,%%ymm0 \n" // result = C0 + C1 * X - "vfmadd231ps %%ymm6,%%ymm2,%%ymm0 \n" // result += C2 * X * X - "vfmadd231ps %%ymm3,%%ymm2,%%ymm0 \n" // result += C3 * X * X * X - "vcvttps2dq %%ymm0,%%ymm0 \n" - "vpackusdw %%ymm0,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpackuswb %%xmm0,%%xmm0,%%xmm0 \n" - "vmovq %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x2,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(poly) // %3 - : "memory", "cc", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBPOLYNOMIALROW_AVX2 - -#ifdef HAS_ARGBCOLORTABLEROW_X86 -// Tranform ARGB pixels with color table. -void ARGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, - int width) { - uintptr_t pixel_temp; - asm volatile ( - // 1 pixel loop. - LABELALIGN - "1: \n" - "movzb " MEMACCESS(0) ",%1 \n" - "lea " MEMLEA(0x4,0) ",%0 \n" - MEMOPARG(movzb,0x00,3,1,4,1) " \n" // movzb (%3,%1,4),%1 - "mov %b1," MEMACCESS2(-0x4,0) " \n" - "movzb " MEMACCESS2(-0x3,0) ",%1 \n" - MEMOPARG(movzb,0x01,3,1,4,1) " \n" // movzb 0x1(%3,%1,4),%1 - "mov %b1," MEMACCESS2(-0x3,0) " \n" - "movzb " MEMACCESS2(-0x2,0) ",%1 \n" - MEMOPARG(movzb,0x02,3,1,4,1) " \n" // movzb 0x2(%3,%1,4),%1 - "mov %b1," MEMACCESS2(-0x2,0) " \n" - "movzb " MEMACCESS2(-0x1,0) ",%1 \n" - MEMOPARG(movzb,0x03,3,1,4,1) " \n" // movzb 0x3(%3,%1,4),%1 - "mov %b1," MEMACCESS2(-0x1,0) " \n" - "dec %2 \n" - "jg 1b \n" - : "+r"(dst_argb), // %0 - "=&d"(pixel_temp), // %1 - "+r"(width) // %2 - : "r"(table_argb) // %3 - : "memory", "cc"); -} -#endif // HAS_ARGBCOLORTABLEROW_X86 - -#ifdef HAS_RGBCOLORTABLEROW_X86 -// Tranform RGB pixels with color table. -void RGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width) { - uintptr_t pixel_temp; - asm volatile ( - // 1 pixel loop. - LABELALIGN - "1: \n" - "movzb " MEMACCESS(0) ",%1 \n" - "lea " MEMLEA(0x4,0) ",%0 \n" - MEMOPARG(movzb,0x00,3,1,4,1) " \n" // movzb (%3,%1,4),%1 - "mov %b1," MEMACCESS2(-0x4,0) " \n" - "movzb " MEMACCESS2(-0x3,0) ",%1 \n" - MEMOPARG(movzb,0x01,3,1,4,1) " \n" // movzb 0x1(%3,%1,4),%1 - "mov %b1," MEMACCESS2(-0x3,0) " \n" - "movzb " MEMACCESS2(-0x2,0) ",%1 \n" - MEMOPARG(movzb,0x02,3,1,4,1) " \n" // movzb 0x2(%3,%1,4),%1 - "mov %b1," MEMACCESS2(-0x2,0) " \n" - "dec %2 \n" - "jg 1b \n" - : "+r"(dst_argb), // %0 - "=&d"(pixel_temp), // %1 - "+r"(width) // %2 - : "r"(table_argb) // %3 - : "memory", "cc"); -} -#endif // HAS_RGBCOLORTABLEROW_X86 - -#ifdef HAS_ARGBLUMACOLORTABLEROW_SSSE3 -// Tranform RGB pixels with luma table. -void ARGBLumaColorTableRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - int width, - const uint8* luma, uint32 lumacoeff) { - uintptr_t pixel_temp; - uintptr_t table_temp; - asm volatile ( - "movd %6,%%xmm3 \n" - "pshufd $0x0,%%xmm3,%%xmm3 \n" - "pcmpeqb %%xmm4,%%xmm4 \n" - "psllw $0x8,%%xmm4 \n" - "pxor %%xmm5,%%xmm5 \n" - - // 4 pixel loop. - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(2) ",%%xmm0 \n" - "pmaddubsw %%xmm3,%%xmm0 \n" - "phaddw %%xmm0,%%xmm0 \n" - "pand %%xmm4,%%xmm0 \n" - "punpcklwd %%xmm5,%%xmm0 \n" - "movd %%xmm0,%k1 \n" // 32 bit offset - "add %5,%1 \n" - "pshufd $0x39,%%xmm0,%%xmm0 \n" - - "movzb " MEMACCESS(2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS(3) " \n" - "movzb " MEMACCESS2(0x1,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0x1,3) " \n" - "movzb " MEMACCESS2(0x2,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0x2,3) " \n" - "movzb " MEMACCESS2(0x3,2) ",%0 \n" - "mov %b0," MEMACCESS2(0x3,3) " \n" - - "movd %%xmm0,%k1 \n" // 32 bit offset - "add %5,%1 \n" - "pshufd $0x39,%%xmm0,%%xmm0 \n" - - "movzb " MEMACCESS2(0x4,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0x4,3) " \n" - "movzb " MEMACCESS2(0x5,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0x5,3) " \n" - "movzb " MEMACCESS2(0x6,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0x6,3) " \n" - "movzb " MEMACCESS2(0x7,2) ",%0 \n" - "mov %b0," MEMACCESS2(0x7,3) " \n" - - "movd %%xmm0,%k1 \n" // 32 bit offset - "add %5,%1 \n" - "pshufd $0x39,%%xmm0,%%xmm0 \n" - - "movzb " MEMACCESS2(0x8,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0x8,3) " \n" - "movzb " MEMACCESS2(0x9,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0x9,3) " \n" - "movzb " MEMACCESS2(0xa,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0xa,3) " \n" - "movzb " MEMACCESS2(0xb,2) ",%0 \n" - "mov %b0," MEMACCESS2(0xb,3) " \n" - - "movd %%xmm0,%k1 \n" // 32 bit offset - "add %5,%1 \n" - - "movzb " MEMACCESS2(0xc,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0xc,3) " \n" - "movzb " MEMACCESS2(0xd,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0xd,3) " \n" - "movzb " MEMACCESS2(0xe,2) ",%0 \n" - MEMOPARG(movzb,0x00,1,0,1,0) " \n" // movzb (%1,%0,1),%0 - "mov %b0," MEMACCESS2(0xe,3) " \n" - "movzb " MEMACCESS2(0xf,2) ",%0 \n" - "mov %b0," MEMACCESS2(0xf,3) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "lea " MEMLEA(0x10,3) ",%3 \n" - "sub $0x4,%4 \n" - "jg 1b \n" - : "=&d"(pixel_temp), // %0 - "=&a"(table_temp), // %1 - "+r"(src_argb), // %2 - "+r"(dst_argb), // %3 - "+rm"(width) // %4 - : "r"(luma), // %5 - "rm"(lumacoeff) // %6 - : "memory", "cc", "xmm0", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_ARGBLUMACOLORTABLEROW_SSSE3 - -#endif // defined(__x86_64__) || defined(__i386__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/row_mips.cc b/telegramgallery/src/main/cpp/libyuv/source/row_mips.cc deleted file mode 100644 index 285f0b5..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/row_mips.cc +++ /dev/null @@ -1,782 +0,0 @@ -/* - * Copyright (c) 2012 The LibYuv project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// The following are available on Mips platforms: -#if !defined(LIBYUV_DISABLE_MIPS) && defined(__mips__) && \ - (_MIPS_SIM == _MIPS_SIM_ABI32) - -#ifdef HAS_COPYROW_MIPS -void CopyRow_MIPS(const uint8* src, uint8* dst, int count) { - __asm__ __volatile__ ( - ".set noreorder \n" - ".set noat \n" - "slti $at, %[count], 8 \n" - "bne $at ,$zero, $last8 \n" - "xor $t8, %[src], %[dst] \n" - "andi $t8, $t8, 0x3 \n" - - "bne $t8, $zero, unaligned \n" - "negu $a3, %[dst] \n" - // make dst/src aligned - "andi $a3, $a3, 0x3 \n" - "beq $a3, $zero, $chk16w \n" - // word-aligned now count is the remining bytes count - "subu %[count], %[count], $a3 \n" - - "lwr $t8, 0(%[src]) \n" - "addu %[src], %[src], $a3 \n" - "swr $t8, 0(%[dst]) \n" - "addu %[dst], %[dst], $a3 \n" - - // Now the dst/src are mutually word-aligned with word-aligned addresses - "$chk16w: \n" - "andi $t8, %[count], 0x3f \n" // whole 64-B chunks? - // t8 is the byte count after 64-byte chunks - "beq %[count], $t8, chk8w \n" - // There will be at most 1 32-byte chunk after it - "subu $a3, %[count], $t8 \n" // the reminder - // Here a3 counts bytes in 16w chunks - "addu $a3, %[dst], $a3 \n" - // Now a3 is the final dst after 64-byte chunks - "addu $t0, %[dst], %[count] \n" - // t0 is the "past the end" address - - // When in the loop we exercise "pref 30,x(a1)", the a1+x should not be past - // the "t0-32" address - // This means: for x=128 the last "safe" a1 address is "t0-160" - // Alternatively, for x=64 the last "safe" a1 address is "t0-96" - // we will use "pref 30,128(a1)", so "t0-160" is the limit - "subu $t9, $t0, 160 \n" - // t9 is the "last safe pref 30,128(a1)" address - "pref 0, 0(%[src]) \n" // first line of src - "pref 0, 32(%[src]) \n" // second line of src - "pref 0, 64(%[src]) \n" - "pref 30, 32(%[dst]) \n" - // In case the a1 > t9 don't use "pref 30" at all - "sgtu $v1, %[dst], $t9 \n" - "bgtz $v1, $loop16w \n" - "nop \n" - // otherwise, start with using pref30 - "pref 30, 64(%[dst]) \n" - "$loop16w: \n" - "pref 0, 96(%[src]) \n" - "lw $t0, 0(%[src]) \n" - "bgtz $v1, $skip_pref30_96 \n" // skip - "lw $t1, 4(%[src]) \n" - "pref 30, 96(%[dst]) \n" // continue - "$skip_pref30_96: \n" - "lw $t2, 8(%[src]) \n" - "lw $t3, 12(%[src]) \n" - "lw $t4, 16(%[src]) \n" - "lw $t5, 20(%[src]) \n" - "lw $t6, 24(%[src]) \n" - "lw $t7, 28(%[src]) \n" - "pref 0, 128(%[src]) \n" - // bring the next lines of src, addr 128 - "sw $t0, 0(%[dst]) \n" - "sw $t1, 4(%[dst]) \n" - "sw $t2, 8(%[dst]) \n" - "sw $t3, 12(%[dst]) \n" - "sw $t4, 16(%[dst]) \n" - "sw $t5, 20(%[dst]) \n" - "sw $t6, 24(%[dst]) \n" - "sw $t7, 28(%[dst]) \n" - "lw $t0, 32(%[src]) \n" - "bgtz $v1, $skip_pref30_128 \n" // skip pref 30,128(a1) - "lw $t1, 36(%[src]) \n" - "pref 30, 128(%[dst]) \n" // set dest, addr 128 - "$skip_pref30_128: \n" - "lw $t2, 40(%[src]) \n" - "lw $t3, 44(%[src]) \n" - "lw $t4, 48(%[src]) \n" - "lw $t5, 52(%[src]) \n" - "lw $t6, 56(%[src]) \n" - "lw $t7, 60(%[src]) \n" - "pref 0, 160(%[src]) \n" - // bring the next lines of src, addr 160 - "sw $t0, 32(%[dst]) \n" - "sw $t1, 36(%[dst]) \n" - "sw $t2, 40(%[dst]) \n" - "sw $t3, 44(%[dst]) \n" - "sw $t4, 48(%[dst]) \n" - "sw $t5, 52(%[dst]) \n" - "sw $t6, 56(%[dst]) \n" - "sw $t7, 60(%[dst]) \n" - - "addiu %[dst], %[dst], 64 \n" // adding 64 to dest - "sgtu $v1, %[dst], $t9 \n" - "bne %[dst], $a3, $loop16w \n" - " addiu %[src], %[src], 64 \n" // adding 64 to src - "move %[count], $t8 \n" - - // Here we have src and dest word-aligned but less than 64-bytes to go - - "chk8w: \n" - "pref 0, 0x0(%[src]) \n" - "andi $t8, %[count], 0x1f \n" // 32-byte chunk? - // the t8 is the reminder count past 32-bytes - "beq %[count], $t8, chk1w \n" - // count=t8,no 32-byte chunk - " nop \n" - - "lw $t0, 0(%[src]) \n" - "lw $t1, 4(%[src]) \n" - "lw $t2, 8(%[src]) \n" - "lw $t3, 12(%[src]) \n" - "lw $t4, 16(%[src]) \n" - "lw $t5, 20(%[src]) \n" - "lw $t6, 24(%[src]) \n" - "lw $t7, 28(%[src]) \n" - "addiu %[src], %[src], 32 \n" - - "sw $t0, 0(%[dst]) \n" - "sw $t1, 4(%[dst]) \n" - "sw $t2, 8(%[dst]) \n" - "sw $t3, 12(%[dst]) \n" - "sw $t4, 16(%[dst]) \n" - "sw $t5, 20(%[dst]) \n" - "sw $t6, 24(%[dst]) \n" - "sw $t7, 28(%[dst]) \n" - "addiu %[dst], %[dst], 32 \n" - - "chk1w: \n" - "andi %[count], $t8, 0x3 \n" - // now count is the reminder past 1w chunks - "beq %[count], $t8, $last8 \n" - " subu $a3, $t8, %[count] \n" - // a3 is count of bytes in 1w chunks - "addu $a3, %[dst], $a3 \n" - // now a3 is the dst address past the 1w chunks - // copying in words (4-byte chunks) - "$wordCopy_loop: \n" - "lw $t3, 0(%[src]) \n" - // the first t3 may be equal t0 ... optimize? - "addiu %[src], %[src],4 \n" - "addiu %[dst], %[dst],4 \n" - "bne %[dst], $a3,$wordCopy_loop \n" - " sw $t3, -4(%[dst]) \n" - - // For the last (<8) bytes - "$last8: \n" - "blez %[count], leave \n" - " addu $a3, %[dst], %[count] \n" // a3 -last dst address - "$last8loop: \n" - "lb $v1, 0(%[src]) \n" - "addiu %[src], %[src], 1 \n" - "addiu %[dst], %[dst], 1 \n" - "bne %[dst], $a3, $last8loop \n" - " sb $v1, -1(%[dst]) \n" - - "leave: \n" - " j $ra \n" - " nop \n" - - // - // UNALIGNED case - // - - "unaligned: \n" - // got here with a3="negu a1" - "andi $a3, $a3, 0x3 \n" // a1 is word aligned? - "beqz $a3, $ua_chk16w \n" - " subu %[count], %[count], $a3 \n" - // bytes left after initial a3 bytes - "lwr $v1, 0(%[src]) \n" - "lwl $v1, 3(%[src]) \n" - "addu %[src], %[src], $a3 \n" // a3 may be 1, 2 or 3 - "swr $v1, 0(%[dst]) \n" - "addu %[dst], %[dst], $a3 \n" - // below the dst will be word aligned (NOTE1) - "$ua_chk16w: \n" - "andi $t8, %[count], 0x3f \n" // whole 64-B chunks? - // t8 is the byte count after 64-byte chunks - "beq %[count], $t8, ua_chk8w \n" - // if a2==t8, no 64-byte chunks - // There will be at most 1 32-byte chunk after it - "subu $a3, %[count], $t8 \n" // the reminder - // Here a3 counts bytes in 16w chunks - "addu $a3, %[dst], $a3 \n" - // Now a3 is the final dst after 64-byte chunks - "addu $t0, %[dst], %[count] \n" // t0 "past the end" - "subu $t9, $t0, 160 \n" - // t9 is the "last safe pref 30,128(a1)" address - "pref 0, 0(%[src]) \n" // first line of src - "pref 0, 32(%[src]) \n" // second line addr 32 - "pref 0, 64(%[src]) \n" - "pref 30, 32(%[dst]) \n" - // safe, as we have at least 64 bytes ahead - // In case the a1 > t9 don't use "pref 30" at all - "sgtu $v1, %[dst], $t9 \n" - "bgtz $v1, $ua_loop16w \n" - // skip "pref 30,64(a1)" for too short arrays - " nop \n" - // otherwise, start with using pref30 - "pref 30, 64(%[dst]) \n" - "$ua_loop16w: \n" - "pref 0, 96(%[src]) \n" - "lwr $t0, 0(%[src]) \n" - "lwl $t0, 3(%[src]) \n" - "lwr $t1, 4(%[src]) \n" - "bgtz $v1, $ua_skip_pref30_96 \n" - " lwl $t1, 7(%[src]) \n" - "pref 30, 96(%[dst]) \n" - // continue setting up the dest, addr 96 - "$ua_skip_pref30_96: \n" - "lwr $t2, 8(%[src]) \n" - "lwl $t2, 11(%[src]) \n" - "lwr $t3, 12(%[src]) \n" - "lwl $t3, 15(%[src]) \n" - "lwr $t4, 16(%[src]) \n" - "lwl $t4, 19(%[src]) \n" - "lwr $t5, 20(%[src]) \n" - "lwl $t5, 23(%[src]) \n" - "lwr $t6, 24(%[src]) \n" - "lwl $t6, 27(%[src]) \n" - "lwr $t7, 28(%[src]) \n" - "lwl $t7, 31(%[src]) \n" - "pref 0, 128(%[src]) \n" - // bring the next lines of src, addr 128 - "sw $t0, 0(%[dst]) \n" - "sw $t1, 4(%[dst]) \n" - "sw $t2, 8(%[dst]) \n" - "sw $t3, 12(%[dst]) \n" - "sw $t4, 16(%[dst]) \n" - "sw $t5, 20(%[dst]) \n" - "sw $t6, 24(%[dst]) \n" - "sw $t7, 28(%[dst]) \n" - "lwr $t0, 32(%[src]) \n" - "lwl $t0, 35(%[src]) \n" - "lwr $t1, 36(%[src]) \n" - "bgtz $v1, ua_skip_pref30_128 \n" - " lwl $t1, 39(%[src]) \n" - "pref 30, 128(%[dst]) \n" - // continue setting up the dest, addr 128 - "ua_skip_pref30_128: \n" - - "lwr $t2, 40(%[src]) \n" - "lwl $t2, 43(%[src]) \n" - "lwr $t3, 44(%[src]) \n" - "lwl $t3, 47(%[src]) \n" - "lwr $t4, 48(%[src]) \n" - "lwl $t4, 51(%[src]) \n" - "lwr $t5, 52(%[src]) \n" - "lwl $t5, 55(%[src]) \n" - "lwr $t6, 56(%[src]) \n" - "lwl $t6, 59(%[src]) \n" - "lwr $t7, 60(%[src]) \n" - "lwl $t7, 63(%[src]) \n" - "pref 0, 160(%[src]) \n" - // bring the next lines of src, addr 160 - "sw $t0, 32(%[dst]) \n" - "sw $t1, 36(%[dst]) \n" - "sw $t2, 40(%[dst]) \n" - "sw $t3, 44(%[dst]) \n" - "sw $t4, 48(%[dst]) \n" - "sw $t5, 52(%[dst]) \n" - "sw $t6, 56(%[dst]) \n" - "sw $t7, 60(%[dst]) \n" - - "addiu %[dst],%[dst],64 \n" // adding 64 to dest - "sgtu $v1,%[dst],$t9 \n" - "bne %[dst],$a3,$ua_loop16w \n" - " addiu %[src],%[src],64 \n" // adding 64 to src - "move %[count],$t8 \n" - - // Here we have src and dest word-aligned but less than 64-bytes to go - - "ua_chk8w: \n" - "pref 0, 0x0(%[src]) \n" - "andi $t8, %[count], 0x1f \n" // 32-byte chunk? - // the t8 is the reminder count - "beq %[count], $t8, $ua_chk1w \n" - // when count==t8, no 32-byte chunk - - "lwr $t0, 0(%[src]) \n" - "lwl $t0, 3(%[src]) \n" - "lwr $t1, 4(%[src]) \n" - "lwl $t1, 7(%[src]) \n" - "lwr $t2, 8(%[src]) \n" - "lwl $t2, 11(%[src]) \n" - "lwr $t3, 12(%[src]) \n" - "lwl $t3, 15(%[src]) \n" - "lwr $t4, 16(%[src]) \n" - "lwl $t4, 19(%[src]) \n" - "lwr $t5, 20(%[src]) \n" - "lwl $t5, 23(%[src]) \n" - "lwr $t6, 24(%[src]) \n" - "lwl $t6, 27(%[src]) \n" - "lwr $t7, 28(%[src]) \n" - "lwl $t7, 31(%[src]) \n" - "addiu %[src], %[src], 32 \n" - - "sw $t0, 0(%[dst]) \n" - "sw $t1, 4(%[dst]) \n" - "sw $t2, 8(%[dst]) \n" - "sw $t3, 12(%[dst]) \n" - "sw $t4, 16(%[dst]) \n" - "sw $t5, 20(%[dst]) \n" - "sw $t6, 24(%[dst]) \n" - "sw $t7, 28(%[dst]) \n" - "addiu %[dst], %[dst], 32 \n" - - "$ua_chk1w: \n" - "andi %[count], $t8, 0x3 \n" - // now count is the reminder past 1w chunks - "beq %[count], $t8, ua_smallCopy \n" - "subu $a3, $t8, %[count] \n" - // a3 is count of bytes in 1w chunks - "addu $a3, %[dst], $a3 \n" - // now a3 is the dst address past the 1w chunks - - // copying in words (4-byte chunks) - "$ua_wordCopy_loop: \n" - "lwr $v1, 0(%[src]) \n" - "lwl $v1, 3(%[src]) \n" - "addiu %[src], %[src], 4 \n" - "addiu %[dst], %[dst], 4 \n" - // note: dst=a1 is word aligned here, see NOTE1 - "bne %[dst], $a3, $ua_wordCopy_loop \n" - " sw $v1,-4(%[dst]) \n" - - // Now less than 4 bytes (value in count) left to copy - "ua_smallCopy: \n" - "beqz %[count], leave \n" - " addu $a3, %[dst], %[count] \n" // a3 = last dst address - "$ua_smallCopy_loop: \n" - "lb $v1, 0(%[src]) \n" - "addiu %[src], %[src], 1 \n" - "addiu %[dst], %[dst], 1 \n" - "bne %[dst],$a3,$ua_smallCopy_loop \n" - " sb $v1, -1(%[dst]) \n" - - "j $ra \n" - " nop \n" - ".set at \n" - ".set reorder \n" - : [dst] "+r" (dst), [src] "+r" (src) - : [count] "r" (count) - : "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", - "t8", "t9", "a3", "v1", "at" - ); -} -#endif // HAS_COPYROW_MIPS - -// DSPR2 functions -#if !defined(LIBYUV_DISABLE_MIPS) && defined(__mips_dsp) && \ - (__mips_dsp_rev >= 2) && \ - (_MIPS_SIM == _MIPS_SIM_ABI32) && (__mips_isa_rev < 6) - -void SplitUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - "srl $t4, %[width], 4 \n" // multiplies of 16 - "blez $t4, 2f \n" - " andi %[width], %[width], 0xf \n" // residual - - "1: \n" - "addiu $t4, $t4, -1 \n" - "lw $t0, 0(%[src_uv]) \n" // V1 | U1 | V0 | U0 - "lw $t1, 4(%[src_uv]) \n" // V3 | U3 | V2 | U2 - "lw $t2, 8(%[src_uv]) \n" // V5 | U5 | V4 | U4 - "lw $t3, 12(%[src_uv]) \n" // V7 | U7 | V6 | U6 - "lw $t5, 16(%[src_uv]) \n" // V9 | U9 | V8 | U8 - "lw $t6, 20(%[src_uv]) \n" // V11 | U11 | V10 | U10 - "lw $t7, 24(%[src_uv]) \n" // V13 | U13 | V12 | U12 - "lw $t8, 28(%[src_uv]) \n" // V15 | U15 | V14 | U14 - "addiu %[src_uv], %[src_uv], 32 \n" - "precrq.qb.ph $t9, $t1, $t0 \n" // V3 | V2 | V1 | V0 - "precr.qb.ph $t0, $t1, $t0 \n" // U3 | U2 | U1 | U0 - "precrq.qb.ph $t1, $t3, $t2 \n" // V7 | V6 | V5 | V4 - "precr.qb.ph $t2, $t3, $t2 \n" // U7 | U6 | U5 | U4 - "precrq.qb.ph $t3, $t6, $t5 \n" // V11 | V10 | V9 | V8 - "precr.qb.ph $t5, $t6, $t5 \n" // U11 | U10 | U9 | U8 - "precrq.qb.ph $t6, $t8, $t7 \n" // V15 | V14 | V13 | V12 - "precr.qb.ph $t7, $t8, $t7 \n" // U15 | U14 | U13 | U12 - "sw $t9, 0(%[dst_v]) \n" - "sw $t0, 0(%[dst_u]) \n" - "sw $t1, 4(%[dst_v]) \n" - "sw $t2, 4(%[dst_u]) \n" - "sw $t3, 8(%[dst_v]) \n" - "sw $t5, 8(%[dst_u]) \n" - "sw $t6, 12(%[dst_v]) \n" - "sw $t7, 12(%[dst_u]) \n" - "addiu %[dst_v], %[dst_v], 16 \n" - "bgtz $t4, 1b \n" - " addiu %[dst_u], %[dst_u], 16 \n" - - "beqz %[width], 3f \n" - " nop \n" - - "2: \n" - "lbu $t0, 0(%[src_uv]) \n" - "lbu $t1, 1(%[src_uv]) \n" - "addiu %[src_uv], %[src_uv], 2 \n" - "addiu %[width], %[width], -1 \n" - "sb $t0, 0(%[dst_u]) \n" - "sb $t1, 0(%[dst_v]) \n" - "addiu %[dst_u], %[dst_u], 1 \n" - "bgtz %[width], 2b \n" - " addiu %[dst_v], %[dst_v], 1 \n" - - "3: \n" - ".set pop \n" - : [src_uv] "+r" (src_uv), - [width] "+r" (width), - [dst_u] "+r" (dst_u), - [dst_v] "+r" (dst_v) - : - : "t0", "t1", "t2", "t3", - "t4", "t5", "t6", "t7", "t8", "t9" - ); -} - -void MirrorRow_DSPR2(const uint8* src, uint8* dst, int width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "srl $t4, %[width], 4 \n" // multiplies of 16 - "andi $t5, %[width], 0xf \n" - "blez $t4, 2f \n" - " addu %[src], %[src], %[width] \n" // src += width - - "1: \n" - "lw $t0, -16(%[src]) \n" // |3|2|1|0| - "lw $t1, -12(%[src]) \n" // |7|6|5|4| - "lw $t2, -8(%[src]) \n" // |11|10|9|8| - "lw $t3, -4(%[src]) \n" // |15|14|13|12| - "wsbh $t0, $t0 \n" // |2|3|0|1| - "wsbh $t1, $t1 \n" // |6|7|4|5| - "wsbh $t2, $t2 \n" // |10|11|8|9| - "wsbh $t3, $t3 \n" // |14|15|12|13| - "rotr $t0, $t0, 16 \n" // |0|1|2|3| - "rotr $t1, $t1, 16 \n" // |4|5|6|7| - "rotr $t2, $t2, 16 \n" // |8|9|10|11| - "rotr $t3, $t3, 16 \n" // |12|13|14|15| - "addiu %[src], %[src], -16 \n" - "addiu $t4, $t4, -1 \n" - "sw $t3, 0(%[dst]) \n" // |15|14|13|12| - "sw $t2, 4(%[dst]) \n" // |11|10|9|8| - "sw $t1, 8(%[dst]) \n" // |7|6|5|4| - "sw $t0, 12(%[dst]) \n" // |3|2|1|0| - "bgtz $t4, 1b \n" - " addiu %[dst], %[dst], 16 \n" - "beqz $t5, 3f \n" - " nop \n" - - "2: \n" - "lbu $t0, -1(%[src]) \n" - "addiu $t5, $t5, -1 \n" - "addiu %[src], %[src], -1 \n" - "sb $t0, 0(%[dst]) \n" - "bgez $t5, 2b \n" - " addiu %[dst], %[dst], 1 \n" - - "3: \n" - ".set pop \n" - : [src] "+r" (src), [dst] "+r" (dst) - : [width] "r" (width) - : "t0", "t1", "t2", "t3", "t4", "t5" - ); -} - -void MirrorUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - int x; - int y; - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "addu $t4, %[width], %[width] \n" - "srl %[x], %[width], 4 \n" - "andi %[y], %[width], 0xf \n" - "blez %[x], 2f \n" - " addu %[src_uv], %[src_uv], $t4 \n" - - "1: \n" - "lw $t0, -32(%[src_uv]) \n" // |3|2|1|0| - "lw $t1, -28(%[src_uv]) \n" // |7|6|5|4| - "lw $t2, -24(%[src_uv]) \n" // |11|10|9|8| - "lw $t3, -20(%[src_uv]) \n" // |15|14|13|12| - "lw $t4, -16(%[src_uv]) \n" // |19|18|17|16| - "lw $t6, -12(%[src_uv]) \n" // |23|22|21|20| - "lw $t7, -8(%[src_uv]) \n" // |27|26|25|24| - "lw $t8, -4(%[src_uv]) \n" // |31|30|29|28| - - "rotr $t0, $t0, 16 \n" // |1|0|3|2| - "rotr $t1, $t1, 16 \n" // |5|4|7|6| - "rotr $t2, $t2, 16 \n" // |9|8|11|10| - "rotr $t3, $t3, 16 \n" // |13|12|15|14| - "rotr $t4, $t4, 16 \n" // |17|16|19|18| - "rotr $t6, $t6, 16 \n" // |21|20|23|22| - "rotr $t7, $t7, 16 \n" // |25|24|27|26| - "rotr $t8, $t8, 16 \n" // |29|28|31|30| - "precr.qb.ph $t9, $t0, $t1 \n" // |0|2|4|6| - "precrq.qb.ph $t5, $t0, $t1 \n" // |1|3|5|7| - "precr.qb.ph $t0, $t2, $t3 \n" // |8|10|12|14| - "precrq.qb.ph $t1, $t2, $t3 \n" // |9|11|13|15| - "precr.qb.ph $t2, $t4, $t6 \n" // |16|18|20|22| - "precrq.qb.ph $t3, $t4, $t6 \n" // |17|19|21|23| - "precr.qb.ph $t4, $t7, $t8 \n" // |24|26|28|30| - "precrq.qb.ph $t6, $t7, $t8 \n" // |25|27|29|31| - "addiu %[src_uv], %[src_uv], -32 \n" - "addiu %[x], %[x], -1 \n" - "swr $t4, 0(%[dst_u]) \n" - "swl $t4, 3(%[dst_u]) \n" // |30|28|26|24| - "swr $t6, 0(%[dst_v]) \n" - "swl $t6, 3(%[dst_v]) \n" // |31|29|27|25| - "swr $t2, 4(%[dst_u]) \n" - "swl $t2, 7(%[dst_u]) \n" // |22|20|18|16| - "swr $t3, 4(%[dst_v]) \n" - "swl $t3, 7(%[dst_v]) \n" // |23|21|19|17| - "swr $t0, 8(%[dst_u]) \n" - "swl $t0, 11(%[dst_u]) \n" // |14|12|10|8| - "swr $t1, 8(%[dst_v]) \n" - "swl $t1, 11(%[dst_v]) \n" // |15|13|11|9| - "swr $t9, 12(%[dst_u]) \n" - "swl $t9, 15(%[dst_u]) \n" // |6|4|2|0| - "swr $t5, 12(%[dst_v]) \n" - "swl $t5, 15(%[dst_v]) \n" // |7|5|3|1| - "addiu %[dst_v], %[dst_v], 16 \n" - "bgtz %[x], 1b \n" - " addiu %[dst_u], %[dst_u], 16 \n" - "beqz %[y], 3f \n" - " nop \n" - "b 2f \n" - " nop \n" - - "2: \n" - "lbu $t0, -2(%[src_uv]) \n" - "lbu $t1, -1(%[src_uv]) \n" - "addiu %[src_uv], %[src_uv], -2 \n" - "addiu %[y], %[y], -1 \n" - "sb $t0, 0(%[dst_u]) \n" - "sb $t1, 0(%[dst_v]) \n" - "addiu %[dst_u], %[dst_u], 1 \n" - "bgtz %[y], 2b \n" - " addiu %[dst_v], %[dst_v], 1 \n" - - "3: \n" - ".set pop \n" - : [src_uv] "+r" (src_uv), - [dst_u] "+r" (dst_u), - [dst_v] "+r" (dst_v), - [x] "=&r" (x), - [y] "=&r" (y) - : [width] "r" (width) - : "t0", "t1", "t2", "t3", "t4", - "t5", "t7", "t8", "t9" - ); -} - -// Convert (4 Y and 2 VU) I422 and arrange RGB values into -// t5 = | 0 | B0 | 0 | b0 | -// t4 = | 0 | B1 | 0 | b1 | -// t9 = | 0 | G0 | 0 | g0 | -// t8 = | 0 | G1 | 0 | g1 | -// t2 = | 0 | R0 | 0 | r0 | -// t1 = | 0 | R1 | 0 | r1 | -#define YUVTORGB \ - "lw $t0, 0(%[y_buf]) \n" \ - "lhu $t1, 0(%[u_buf]) \n" \ - "lhu $t2, 0(%[v_buf]) \n" \ - "preceu.ph.qbr $t1, $t1 \n" \ - "preceu.ph.qbr $t2, $t2 \n" \ - "preceu.ph.qbra $t3, $t0 \n" \ - "preceu.ph.qbla $t0, $t0 \n" \ - "subu.ph $t1, $t1, $s5 \n" \ - "subu.ph $t2, $t2, $s5 \n" \ - "subu.ph $t3, $t3, $s4 \n" \ - "subu.ph $t0, $t0, $s4 \n" \ - "mul.ph $t3, $t3, $s0 \n" \ - "mul.ph $t0, $t0, $s0 \n" \ - "shll.ph $t4, $t1, 0x7 \n" \ - "subu.ph $t4, $t4, $t1 \n" \ - "mul.ph $t6, $t1, $s1 \n" \ - "mul.ph $t1, $t2, $s2 \n" \ - "addq_s.ph $t5, $t4, $t3 \n" \ - "addq_s.ph $t4, $t4, $t0 \n" \ - "shra.ph $t5, $t5, 6 \n" \ - "shra.ph $t4, $t4, 6 \n" \ - "addiu %[u_buf], 2 \n" \ - "addiu %[v_buf], 2 \n" \ - "addu.ph $t6, $t6, $t1 \n" \ - "mul.ph $t1, $t2, $s3 \n" \ - "addu.ph $t9, $t6, $t3 \n" \ - "addu.ph $t8, $t6, $t0 \n" \ - "shra.ph $t9, $t9, 6 \n" \ - "shra.ph $t8, $t8, 6 \n" \ - "addu.ph $t2, $t1, $t3 \n" \ - "addu.ph $t1, $t1, $t0 \n" \ - "shra.ph $t2, $t2, 6 \n" \ - "shra.ph $t1, $t1, 6 \n" \ - "subu.ph $t5, $t5, $s5 \n" \ - "subu.ph $t4, $t4, $s5 \n" \ - "subu.ph $t9, $t9, $s5 \n" \ - "subu.ph $t8, $t8, $s5 \n" \ - "subu.ph $t2, $t2, $s5 \n" \ - "subu.ph $t1, $t1, $s5 \n" \ - "shll_s.ph $t5, $t5, 8 \n" \ - "shll_s.ph $t4, $t4, 8 \n" \ - "shll_s.ph $t9, $t9, 8 \n" \ - "shll_s.ph $t8, $t8, 8 \n" \ - "shll_s.ph $t2, $t2, 8 \n" \ - "shll_s.ph $t1, $t1, 8 \n" \ - "shra.ph $t5, $t5, 8 \n" \ - "shra.ph $t4, $t4, 8 \n" \ - "shra.ph $t9, $t9, 8 \n" \ - "shra.ph $t8, $t8, 8 \n" \ - "shra.ph $t2, $t2, 8 \n" \ - "shra.ph $t1, $t1, 8 \n" \ - "addu.ph $t5, $t5, $s5 \n" \ - "addu.ph $t4, $t4, $s5 \n" \ - "addu.ph $t9, $t9, $s5 \n" \ - "addu.ph $t8, $t8, $s5 \n" \ - "addu.ph $t2, $t2, $s5 \n" \ - "addu.ph $t1, $t1, $s5 \n" - -// TODO(fbarchard): accept yuv conversion constants. -void I422ToARGBRow_DSPR2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - const struct YuvConstants* yuvconstants, - int width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - "beqz %[width], 2f \n" - " repl.ph $s0, 74 \n" // |YG|YG| = |74|74| - "repl.ph $s1, -25 \n" // |UG|UG| = |-25|-25| - "repl.ph $s2, -52 \n" // |VG|VG| = |-52|-52| - "repl.ph $s3, 102 \n" // |VR|VR| = |102|102| - "repl.ph $s4, 16 \n" // |0|16|0|16| - "repl.ph $s5, 128 \n" // |128|128| // clipping - "lui $s6, 0xff00 \n" - "ori $s6, 0xff00 \n" // |ff|00|ff|00|ff| - - "1: \n" - YUVTORGB -// Arranging into argb format - "precr.qb.ph $t4, $t8, $t4 \n" // |G1|g1|B1|b1| - "precr.qb.ph $t5, $t9, $t5 \n" // |G0|g0|B0|b0| - "addiu %[width], -4 \n" - "precrq.qb.ph $t8, $t4, $t5 \n" // |G1|B1|G0|B0| - "precr.qb.ph $t9, $t4, $t5 \n" // |g1|b1|g0|b0| - "precr.qb.ph $t2, $t1, $t2 \n" // |R1|r1|R0|r0| - - "addiu %[y_buf], 4 \n" - "preceu.ph.qbla $t1, $t2 \n" // |0 |R1|0 |R0| - "preceu.ph.qbra $t2, $t2 \n" // |0 |r1|0 |r0| - "or $t1, $t1, $s6 \n" // |ff|R1|ff|R0| - "or $t2, $t2, $s6 \n" // |ff|r1|ff|r0| - "precrq.ph.w $t0, $t2, $t9 \n" // |ff|r1|g1|b1| - "precrq.ph.w $t3, $t1, $t8 \n" // |ff|R1|G1|B1| - "sll $t9, $t9, 16 \n" - "sll $t8, $t8, 16 \n" - "packrl.ph $t2, $t2, $t9 \n" // |ff|r0|g0|b0| - "packrl.ph $t1, $t1, $t8 \n" // |ff|R0|G0|B0| -// Store results. - "sw $t2, 0(%[rgb_buf]) \n" - "sw $t0, 4(%[rgb_buf]) \n" - "sw $t1, 8(%[rgb_buf]) \n" - "sw $t3, 12(%[rgb_buf]) \n" - "bnez %[width], 1b \n" - " addiu %[rgb_buf], 16 \n" - "2: \n" - ".set pop \n" - :[y_buf] "+r" (y_buf), - [u_buf] "+r" (u_buf), - [v_buf] "+r" (v_buf), - [width] "+r" (width), - [rgb_buf] "+r" (rgb_buf) - : - : "t0", "t1", "t2", "t3", "t4", "t5", - "t6", "t7", "t8", "t9", - "s0", "s1", "s2", "s3", - "s4", "s5", "s6" - ); -} - -// Bilinear filter 8x2 -> 8x1 -void InterpolateRow_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, - int source_y_fraction) { - int y0_fraction = 256 - source_y_fraction; - const uint8* src_ptr1 = src_ptr + src_stride; - - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "replv.ph $t0, %[y0_fraction] \n" - "replv.ph $t1, %[source_y_fraction] \n" - - "1: \n" - "lw $t2, 0(%[src_ptr]) \n" - "lw $t3, 0(%[src_ptr1]) \n" - "lw $t4, 4(%[src_ptr]) \n" - "lw $t5, 4(%[src_ptr1]) \n" - "muleu_s.ph.qbl $t6, $t2, $t0 \n" - "muleu_s.ph.qbr $t7, $t2, $t0 \n" - "muleu_s.ph.qbl $t8, $t3, $t1 \n" - "muleu_s.ph.qbr $t9, $t3, $t1 \n" - "muleu_s.ph.qbl $t2, $t4, $t0 \n" - "muleu_s.ph.qbr $t3, $t4, $t0 \n" - "muleu_s.ph.qbl $t4, $t5, $t1 \n" - "muleu_s.ph.qbr $t5, $t5, $t1 \n" - "addq.ph $t6, $t6, $t8 \n" - "addq.ph $t7, $t7, $t9 \n" - "addq.ph $t2, $t2, $t4 \n" - "addq.ph $t3, $t3, $t5 \n" - "shra.ph $t6, $t6, 8 \n" - "shra.ph $t7, $t7, 8 \n" - "shra.ph $t2, $t2, 8 \n" - "shra.ph $t3, $t3, 8 \n" - "precr.qb.ph $t6, $t6, $t7 \n" - "precr.qb.ph $t2, $t2, $t3 \n" - "addiu %[src_ptr], %[src_ptr], 8 \n" - "addiu %[src_ptr1], %[src_ptr1], 8 \n" - "addiu %[dst_width], %[dst_width], -8 \n" - "sw $t6, 0(%[dst_ptr]) \n" - "sw $t2, 4(%[dst_ptr]) \n" - "bgtz %[dst_width], 1b \n" - " addiu %[dst_ptr], %[dst_ptr], 8 \n" - - ".set pop \n" - : [dst_ptr] "+r" (dst_ptr), - [src_ptr1] "+r" (src_ptr1), - [src_ptr] "+r" (src_ptr), - [dst_width] "+r" (dst_width) - : [source_y_fraction] "r" (source_y_fraction), - [y0_fraction] "r" (y0_fraction), - [src_stride] "r" (src_stride) - : "t0", "t1", "t2", "t3", "t4", "t5", - "t6", "t7", "t8", "t9" - ); -} -#endif // __mips_dsp_rev >= 2 - -#endif // defined(__mips__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/row_neon.cc b/telegramgallery/src/main/cpp/libyuv/source/row_neon.cc deleted file mode 100644 index 909df06..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/row_neon.cc +++ /dev/null @@ -1,2843 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC Neon -#if !defined(LIBYUV_DISABLE_NEON) && defined(__ARM_NEON__) && \ - !defined(__aarch64__) - -// Read 8 Y, 4 U and 4 V from 422 -#define READYUV422 \ - MEMACCESS(0) \ - "vld1.8 {d0}, [%0]! \n" \ - MEMACCESS(1) \ - "vld1.32 {d2[0]}, [%1]! \n" \ - MEMACCESS(2) \ - "vld1.32 {d2[1]}, [%2]! \n" - -// Read 8 Y, 2 U and 2 V from 422 -#define READYUV411 \ - MEMACCESS(0) \ - "vld1.8 {d0}, [%0]! \n" \ - MEMACCESS(1) \ - "vld1.16 {d2[0]}, [%1]! \n" \ - MEMACCESS(2) \ - "vld1.16 {d2[1]}, [%2]! \n" \ - "vmov.u8 d3, d2 \n" \ - "vzip.u8 d2, d3 \n" - -// Read 8 Y, 8 U and 8 V from 444 -#define READYUV444 \ - MEMACCESS(0) \ - "vld1.8 {d0}, [%0]! \n" \ - MEMACCESS(1) \ - "vld1.8 {d2}, [%1]! \n" \ - MEMACCESS(2) \ - "vld1.8 {d3}, [%2]! \n" \ - "vpaddl.u8 q1, q1 \n" \ - "vrshrn.u16 d2, q1, #1 \n" - -// Read 8 Y, and set 4 U and 4 V to 128 -#define READYUV400 \ - MEMACCESS(0) \ - "vld1.8 {d0}, [%0]! \n" \ - "vmov.u8 d2, #128 \n" - -// Read 8 Y and 4 UV from NV12 -#define READNV12 \ - MEMACCESS(0) \ - "vld1.8 {d0}, [%0]! \n" \ - MEMACCESS(1) \ - "vld1.8 {d2}, [%1]! \n" \ - "vmov.u8 d3, d2 \n"/* split odd/even uv apart */\ - "vuzp.u8 d2, d3 \n" \ - "vtrn.u32 d2, d3 \n" - -// Read 8 Y and 4 VU from NV21 -#define READNV21 \ - MEMACCESS(0) \ - "vld1.8 {d0}, [%0]! \n" \ - MEMACCESS(1) \ - "vld1.8 {d2}, [%1]! \n" \ - "vmov.u8 d3, d2 \n"/* split odd/even uv apart */\ - "vuzp.u8 d3, d2 \n" \ - "vtrn.u32 d2, d3 \n" - -// Read 8 YUY2 -#define READYUY2 \ - MEMACCESS(0) \ - "vld2.8 {d0, d2}, [%0]! \n" \ - "vmov.u8 d3, d2 \n" \ - "vuzp.u8 d2, d3 \n" \ - "vtrn.u32 d2, d3 \n" - -// Read 8 UYVY -#define READUYVY \ - MEMACCESS(0) \ - "vld2.8 {d2, d3}, [%0]! \n" \ - "vmov.u8 d0, d3 \n" \ - "vmov.u8 d3, d2 \n" \ - "vuzp.u8 d2, d3 \n" \ - "vtrn.u32 d2, d3 \n" - -#define YUVTORGB_SETUP \ - MEMACCESS([kUVToRB]) \ - "vld1.8 {d24}, [%[kUVToRB]] \n" \ - MEMACCESS([kUVToG]) \ - "vld1.8 {d25}, [%[kUVToG]] \n" \ - MEMACCESS([kUVBiasBGR]) \ - "vld1.16 {d26[], d27[]}, [%[kUVBiasBGR]]! \n" \ - MEMACCESS([kUVBiasBGR]) \ - "vld1.16 {d8[], d9[]}, [%[kUVBiasBGR]]! \n" \ - MEMACCESS([kUVBiasBGR]) \ - "vld1.16 {d28[], d29[]}, [%[kUVBiasBGR]] \n" \ - MEMACCESS([kYToRgb]) \ - "vld1.32 {d30[], d31[]}, [%[kYToRgb]] \n" - -#define YUVTORGB \ - "vmull.u8 q8, d2, d24 \n" /* u/v B/R component */\ - "vmull.u8 q9, d2, d25 \n" /* u/v G component */\ - "vmovl.u8 q0, d0 \n" /* Y */\ - "vmovl.s16 q10, d1 \n" \ - "vmovl.s16 q0, d0 \n" \ - "vmul.s32 q10, q10, q15 \n" \ - "vmul.s32 q0, q0, q15 \n" \ - "vqshrun.s32 d0, q0, #16 \n" \ - "vqshrun.s32 d1, q10, #16 \n" /* Y */\ - "vadd.s16 d18, d19 \n" \ - "vshll.u16 q1, d16, #16 \n" /* Replicate u * UB */\ - "vshll.u16 q10, d17, #16 \n" /* Replicate v * VR */\ - "vshll.u16 q3, d18, #16 \n" /* Replicate (v*VG + u*UG)*/\ - "vaddw.u16 q1, q1, d16 \n" \ - "vaddw.u16 q10, q10, d17 \n" \ - "vaddw.u16 q3, q3, d18 \n" \ - "vqadd.s16 q8, q0, q13 \n" /* B */ \ - "vqadd.s16 q9, q0, q14 \n" /* R */ \ - "vqadd.s16 q0, q0, q4 \n" /* G */ \ - "vqadd.s16 q8, q8, q1 \n" /* B */ \ - "vqadd.s16 q9, q9, q10 \n" /* R */ \ - "vqsub.s16 q0, q0, q3 \n" /* G */ \ - "vqshrun.s16 d20, q8, #6 \n" /* B */ \ - "vqshrun.s16 d22, q9, #6 \n" /* R */ \ - "vqshrun.s16 d21, q0, #6 \n" /* G */ - -void I444ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d23, #255 \n" - "1: \n" - READYUV444 - YUVTORGB - "subs %4, %4, #8 \n" - MEMACCESS(3) - "vst4.8 {d20, d21, d22, d23}, [%3]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void I422ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d23, #255 \n" - "1: \n" - READYUV422 - YUVTORGB - "subs %4, %4, #8 \n" - MEMACCESS(3) - "vst4.8 {d20, d21, d22, d23}, [%3]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void I422AlphaToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - const uint8* src_a, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READYUV422 - YUVTORGB - "subs %5, %5, #8 \n" - MEMACCESS(3) - "vld1.8 {d23}, [%3]! \n" - MEMACCESS(4) - "vst4.8 {d20, d21, d22, d23}, [%4]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(src_a), // %3 - "+r"(dst_argb), // %4 - "+r"(width) // %5 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void I411ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d23, #255 \n" - "1: \n" - READYUV411 - YUVTORGB - "subs %4, %4, #8 \n" - MEMACCESS(3) - "vst4.8 {d20, d21, d22, d23}, [%3]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void I422ToRGBARow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READYUV422 - YUVTORGB - "subs %4, %4, #8 \n" - "vmov.u8 d19, #255 \n" // d19 modified by YUVTORGB - MEMACCESS(3) - "vst4.8 {d19, d20, d21, d22}, [%3]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_rgba), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void I422ToRGB24Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READYUV422 - YUVTORGB - "subs %4, %4, #8 \n" - MEMACCESS(3) - "vst3.8 {d20, d21, d22}, [%3]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_rgb24), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -#define ARGBTORGB565 \ - "vshll.u8 q0, d22, #8 \n" /* R */ \ - "vshll.u8 q8, d21, #8 \n" /* G */ \ - "vshll.u8 q9, d20, #8 \n" /* B */ \ - "vsri.16 q0, q8, #5 \n" /* RG */ \ - "vsri.16 q0, q9, #11 \n" /* RGB */ - -void I422ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READYUV422 - YUVTORGB - "subs %4, %4, #8 \n" - ARGBTORGB565 - MEMACCESS(3) - "vst1.8 {q0}, [%3]! \n" // store 8 pixels RGB565. - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_rgb565), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -#define ARGBTOARGB1555 \ - "vshll.u8 q0, d23, #8 \n" /* A */ \ - "vshll.u8 q8, d22, #8 \n" /* R */ \ - "vshll.u8 q9, d21, #8 \n" /* G */ \ - "vshll.u8 q10, d20, #8 \n" /* B */ \ - "vsri.16 q0, q8, #1 \n" /* AR */ \ - "vsri.16 q0, q9, #6 \n" /* ARG */ \ - "vsri.16 q0, q10, #11 \n" /* ARGB */ - -void I422ToARGB1555Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb1555, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READYUV422 - YUVTORGB - "subs %4, %4, #8 \n" - "vmov.u8 d23, #255 \n" - ARGBTOARGB1555 - MEMACCESS(3) - "vst1.8 {q0}, [%3]! \n" // store 8 pixels ARGB1555. - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb1555), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -#define ARGBTOARGB4444 \ - "vshr.u8 d20, d20, #4 \n" /* B */ \ - "vbic.32 d21, d21, d4 \n" /* G */ \ - "vshr.u8 d22, d22, #4 \n" /* R */ \ - "vbic.32 d23, d23, d4 \n" /* A */ \ - "vorr d0, d20, d21 \n" /* BG */ \ - "vorr d1, d22, d23 \n" /* RA */ \ - "vzip.u8 d0, d1 \n" /* BGRA */ - -void I422ToARGB4444Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d4, #0x0f \n" // bits to clear with vbic. - "1: \n" - READYUV422 - YUVTORGB - "subs %4, %4, #8 \n" - "vmov.u8 d23, #255 \n" - ARGBTOARGB4444 - MEMACCESS(3) - "vst1.8 {q0}, [%3]! \n" // store 8 pixels ARGB4444. - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb4444), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void I400ToARGBRow_NEON(const uint8* src_y, - uint8* dst_argb, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d23, #255 \n" - "1: \n" - READYUV400 - YUVTORGB - "subs %2, %2, #8 \n" - MEMACCESS(1) - "vst4.8 {d20, d21, d22, d23}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : [kUVToRB]"r"(&kYuvI601Constants.kUVToRB), - [kUVToG]"r"(&kYuvI601Constants.kUVToG), - [kUVBiasBGR]"r"(&kYuvI601Constants.kUVBiasBGR), - [kYToRgb]"r"(&kYuvI601Constants.kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void J400ToARGBRow_NEON(const uint8* src_y, - uint8* dst_argb, - int width) { - asm volatile ( - "vmov.u8 d23, #255 \n" - "1: \n" - MEMACCESS(0) - "vld1.8 {d20}, [%0]! \n" - "vmov d21, d20 \n" - "vmov d22, d20 \n" - "subs %2, %2, #8 \n" - MEMACCESS(1) - "vst4.8 {d20, d21, d22, d23}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d20", "d21", "d22", "d23" - ); -} - -void NV12ToARGBRow_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d23, #255 \n" - "1: \n" - READNV12 - YUVTORGB - "subs %3, %3, #8 \n" - MEMACCESS(2) - "vst4.8 {d20, d21, d22, d23}, [%2]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_uv), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void NV21ToARGBRow_NEON(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d23, #255 \n" - "1: \n" - READNV21 - YUVTORGB - "subs %3, %3, #8 \n" - MEMACCESS(2) - "vst4.8 {d20, d21, d22, d23}, [%2]! \n" - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_vu), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void NV12ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READNV12 - YUVTORGB - "subs %3, %3, #8 \n" - ARGBTORGB565 - MEMACCESS(2) - "vst1.8 {q0}, [%2]! \n" // store 8 pixels RGB565. - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_uv), // %1 - "+r"(dst_rgb565), // %2 - "+r"(width) // %3 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void YUY2ToARGBRow_NEON(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d23, #255 \n" - "1: \n" - READYUY2 - YUVTORGB - "subs %2, %2, #8 \n" - MEMACCESS(1) - "vst4.8 {d20, d21, d22, d23}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void UYVYToARGBRow_NEON(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "vmov.u8 d23, #255 \n" - "1: \n" - READUYVY - YUVTORGB - "subs %2, %2, #8 \n" - MEMACCESS(1) - "vst4.8 {d20, d21, d22, d23}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -// Reads 16 pairs of UV and write even values to dst_u and odd to dst_v. -void SplitUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld2.8 {q0, q1}, [%0]! \n" // load 16 pairs of UV - "subs %3, %3, #16 \n" // 16 processed per loop - MEMACCESS(1) - "vst1.8 {q0}, [%1]! \n" // store U - MEMACCESS(2) - "vst1.8 {q1}, [%2]! \n" // store V - "bgt 1b \n" - : "+r"(src_uv), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 // Output registers - : // Input registers - : "cc", "memory", "q0", "q1" // Clobber List - ); -} - -// Reads 16 U's and V's and writes out 16 pairs of UV. -void MergeUVRow_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load U - MEMACCESS(1) - "vld1.8 {q1}, [%1]! \n" // load V - "subs %3, %3, #16 \n" // 16 processed per loop - MEMACCESS(2) - "vst2.u8 {q0, q1}, [%2]! \n" // store 16 pairs of UV - "bgt 1b \n" - : - "+r"(src_u), // %0 - "+r"(src_v), // %1 - "+r"(dst_uv), // %2 - "+r"(width) // %3 // Output registers - : // Input registers - : "cc", "memory", "q0", "q1" // Clobber List - ); -} - -// Copy multiple of 32. vld4.8 allow unaligned and is fastest on a15. -void CopyRow_NEON(const uint8* src, uint8* dst, int count) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld1.8 {d0, d1, d2, d3}, [%0]! \n" // load 32 - "subs %2, %2, #32 \n" // 32 processed per loop - MEMACCESS(1) - "vst1.8 {d0, d1, d2, d3}, [%1]! \n" // store 32 - "bgt 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(count) // %2 // Output registers - : // Input registers - : "cc", "memory", "q0", "q1" // Clobber List - ); -} - -// SetRow writes 'count' bytes using an 8 bit value repeated. -void SetRow_NEON(uint8* dst, uint8 v8, int count) { - asm volatile ( - "vdup.8 q0, %2 \n" // duplicate 16 bytes - "1: \n" - "subs %1, %1, #16 \n" // 16 bytes per loop - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" // store - "bgt 1b \n" - : "+r"(dst), // %0 - "+r"(count) // %1 - : "r"(v8) // %2 - : "cc", "memory", "q0" - ); -} - -// ARGBSetRow writes 'count' pixels using an 32 bit value repeated. -void ARGBSetRow_NEON(uint8* dst, uint32 v32, int count) { - asm volatile ( - "vdup.u32 q0, %2 \n" // duplicate 4 ints - "1: \n" - "subs %1, %1, #4 \n" // 4 pixels per loop - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" // store - "bgt 1b \n" - : "+r"(dst), // %0 - "+r"(count) // %1 - : "r"(v32) // %2 - : "cc", "memory", "q0" - ); -} - -void MirrorRow_NEON(const uint8* src, uint8* dst, int width) { - asm volatile ( - // Start at end of source row. - "mov r3, #-16 \n" - "add %0, %0, %2 \n" - "sub %0, #16 \n" - - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0], r3 \n" // src -= 16 - "subs %2, #16 \n" // 16 pixels per loop. - "vrev64.8 q0, q0 \n" - MEMACCESS(1) - "vst1.8 {d1}, [%1]! \n" // dst += 16 - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" - "bgt 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "r3", "q0" - ); -} - -void MirrorUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - // Start at end of source row. - "mov r12, #-16 \n" - "add %0, %0, %3, lsl #1 \n" - "sub %0, #16 \n" - - "1: \n" - MEMACCESS(0) - "vld2.8 {d0, d1}, [%0], r12 \n" // src -= 16 - "subs %3, #8 \n" // 8 pixels per loop. - "vrev64.8 q0, q0 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // dst += 8 - MEMACCESS(2) - "vst1.8 {d1}, [%2]! \n" - "bgt 1b \n" - : "+r"(src_uv), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "r12", "q0" - ); -} - -void ARGBMirrorRow_NEON(const uint8* src, uint8* dst, int width) { - asm volatile ( - // Start at end of source row. - "mov r3, #-16 \n" - "add %0, %0, %2, lsl #2 \n" - "sub %0, #16 \n" - - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0], r3 \n" // src -= 16 - "subs %2, #4 \n" // 4 pixels per loop. - "vrev64.32 q0, q0 \n" - MEMACCESS(1) - "vst1.8 {d1}, [%1]! \n" // dst += 16 - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" - "bgt 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "r3", "q0" - ); -} - -void RGB24ToARGBRow_NEON(const uint8* src_rgb24, uint8* dst_argb, int width) { - asm volatile ( - "vmov.u8 d4, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "vld3.8 {d1, d2, d3}, [%0]! \n" // load 8 pixels of RGB24. - "subs %2, %2, #8 \n" // 8 processed per loop. - MEMACCESS(1) - "vst4.8 {d1, d2, d3, d4}, [%1]! \n" // store 8 pixels of ARGB. - "bgt 1b \n" - : "+r"(src_rgb24), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d1", "d2", "d3", "d4" // Clobber List - ); -} - -void RAWToARGBRow_NEON(const uint8* src_raw, uint8* dst_argb, int width) { - asm volatile ( - "vmov.u8 d4, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "vld3.8 {d1, d2, d3}, [%0]! \n" // load 8 pixels of RAW. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vswp.u8 d1, d3 \n" // swap R, B - MEMACCESS(1) - "vst4.8 {d1, d2, d3, d4}, [%1]! \n" // store 8 pixels of ARGB. - "bgt 1b \n" - : "+r"(src_raw), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d1", "d2", "d3", "d4" // Clobber List - ); -} - -void RAWToRGB24Row_NEON(const uint8* src_raw, uint8* dst_rgb24, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld3.8 {d1, d2, d3}, [%0]! \n" // load 8 pixels of RAW. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vswp.u8 d1, d3 \n" // swap R, B - MEMACCESS(1) - "vst3.8 {d1, d2, d3}, [%1]! \n" // store 8 pixels of RGB24. - "bgt 1b \n" - : "+r"(src_raw), // %0 - "+r"(dst_rgb24), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d1", "d2", "d3" // Clobber List - ); -} - -#define RGB565TOARGB \ - "vshrn.u16 d6, q0, #5 \n" /* G xxGGGGGG */ \ - "vuzp.u8 d0, d1 \n" /* d0 xxxBBBBB RRRRRxxx */ \ - "vshl.u8 d6, d6, #2 \n" /* G GGGGGG00 upper 6 */ \ - "vshr.u8 d1, d1, #3 \n" /* R 000RRRRR lower 5 */ \ - "vshl.u8 q0, q0, #3 \n" /* B,R BBBBB000 upper 5 */ \ - "vshr.u8 q2, q0, #5 \n" /* B,R 00000BBB lower 3 */ \ - "vorr.u8 d0, d0, d4 \n" /* B */ \ - "vshr.u8 d4, d6, #6 \n" /* G 000000GG lower 2 */ \ - "vorr.u8 d2, d1, d5 \n" /* R */ \ - "vorr.u8 d1, d4, d6 \n" /* G */ - -void RGB565ToARGBRow_NEON(const uint8* src_rgb565, uint8* dst_argb, int width) { - asm volatile ( - "vmov.u8 d3, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 RGB565 pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - RGB565TOARGB - MEMACCESS(1) - "vst4.8 {d0, d1, d2, d3}, [%1]! \n" // store 8 pixels of ARGB. - "bgt 1b \n" - : "+r"(src_rgb565), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q3" // Clobber List - ); -} - -#define ARGB1555TOARGB \ - "vshrn.u16 d7, q0, #8 \n" /* A Arrrrrxx */ \ - "vshr.u8 d6, d7, #2 \n" /* R xxxRRRRR */ \ - "vshrn.u16 d5, q0, #5 \n" /* G xxxGGGGG */ \ - "vmovn.u16 d4, q0 \n" /* B xxxBBBBB */ \ - "vshr.u8 d7, d7, #7 \n" /* A 0000000A */ \ - "vneg.s8 d7, d7 \n" /* A AAAAAAAA upper 8 */ \ - "vshl.u8 d6, d6, #3 \n" /* R RRRRR000 upper 5 */ \ - "vshr.u8 q1, q3, #5 \n" /* R,A 00000RRR lower 3 */ \ - "vshl.u8 q0, q2, #3 \n" /* B,G BBBBB000 upper 5 */ \ - "vshr.u8 q2, q0, #5 \n" /* B,G 00000BBB lower 3 */ \ - "vorr.u8 q1, q1, q3 \n" /* R,A */ \ - "vorr.u8 q0, q0, q2 \n" /* B,G */ \ - -// RGB555TOARGB is same as ARGB1555TOARGB but ignores alpha. -#define RGB555TOARGB \ - "vshrn.u16 d6, q0, #5 \n" /* G xxxGGGGG */ \ - "vuzp.u8 d0, d1 \n" /* d0 xxxBBBBB xRRRRRxx */ \ - "vshl.u8 d6, d6, #3 \n" /* G GGGGG000 upper 5 */ \ - "vshr.u8 d1, d1, #2 \n" /* R 00xRRRRR lower 5 */ \ - "vshl.u8 q0, q0, #3 \n" /* B,R BBBBB000 upper 5 */ \ - "vshr.u8 q2, q0, #5 \n" /* B,R 00000BBB lower 3 */ \ - "vorr.u8 d0, d0, d4 \n" /* B */ \ - "vshr.u8 d4, d6, #5 \n" /* G 00000GGG lower 3 */ \ - "vorr.u8 d2, d1, d5 \n" /* R */ \ - "vorr.u8 d1, d4, d6 \n" /* G */ - -void ARGB1555ToARGBRow_NEON(const uint8* src_argb1555, uint8* dst_argb, - int width) { - asm volatile ( - "vmov.u8 d3, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 ARGB1555 pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - ARGB1555TOARGB - MEMACCESS(1) - "vst4.8 {d0, d1, d2, d3}, [%1]! \n" // store 8 pixels of ARGB. - "bgt 1b \n" - : "+r"(src_argb1555), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q3" // Clobber List - ); -} - -#define ARGB4444TOARGB \ - "vuzp.u8 d0, d1 \n" /* d0 BG, d1 RA */ \ - "vshl.u8 q2, q0, #4 \n" /* B,R BBBB0000 */ \ - "vshr.u8 q1, q0, #4 \n" /* G,A 0000GGGG */ \ - "vshr.u8 q0, q2, #4 \n" /* B,R 0000BBBB */ \ - "vorr.u8 q0, q0, q2 \n" /* B,R BBBBBBBB */ \ - "vshl.u8 q2, q1, #4 \n" /* G,A GGGG0000 */ \ - "vorr.u8 q1, q1, q2 \n" /* G,A GGGGGGGG */ \ - "vswp.u8 d1, d2 \n" /* B,R,G,A -> B,G,R,A */ - -void ARGB4444ToARGBRow_NEON(const uint8* src_argb4444, uint8* dst_argb, - int width) { - asm volatile ( - "vmov.u8 d3, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 ARGB4444 pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - ARGB4444TOARGB - MEMACCESS(1) - "vst4.8 {d0, d1, d2, d3}, [%1]! \n" // store 8 pixels of ARGB. - "bgt 1b \n" - : "+r"(src_argb4444), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2" // Clobber List - ); -} - -void ARGBToRGB24Row_NEON(const uint8* src_argb, uint8* dst_rgb24, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d1, d2, d3, d4}, [%0]! \n" // load 8 pixels of ARGB. - "subs %2, %2, #8 \n" // 8 processed per loop. - MEMACCESS(1) - "vst3.8 {d1, d2, d3}, [%1]! \n" // store 8 pixels of RGB24. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_rgb24), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d1", "d2", "d3", "d4" // Clobber List - ); -} - -void ARGBToRAWRow_NEON(const uint8* src_argb, uint8* dst_raw, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d1, d2, d3, d4}, [%0]! \n" // load 8 pixels of ARGB. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vswp.u8 d1, d3 \n" // swap R, B - MEMACCESS(1) - "vst3.8 {d1, d2, d3}, [%1]! \n" // store 8 pixels of RAW. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_raw), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d1", "d2", "d3", "d4" // Clobber List - ); -} - -void YUY2ToYRow_NEON(const uint8* src_yuy2, uint8* dst_y, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld2.8 {q0, q1}, [%0]! \n" // load 16 pixels of YUY2. - "subs %2, %2, #16 \n" // 16 processed per loop. - MEMACCESS(1) - "vst1.8 {q0}, [%1]! \n" // store 16 pixels of Y. - "bgt 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1" // Clobber List - ); -} - -void UYVYToYRow_NEON(const uint8* src_uyvy, uint8* dst_y, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld2.8 {q0, q1}, [%0]! \n" // load 16 pixels of UYVY. - "subs %2, %2, #16 \n" // 16 processed per loop. - MEMACCESS(1) - "vst1.8 {q1}, [%1]! \n" // store 16 pixels of Y. - "bgt 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1" // Clobber List - ); -} - -void YUY2ToUV422Row_NEON(const uint8* src_yuy2, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 16 pixels of YUY2. - "subs %3, %3, #16 \n" // 16 pixels = 8 UVs. - MEMACCESS(1) - "vst1.8 {d1}, [%1]! \n" // store 8 U. - MEMACCESS(2) - "vst1.8 {d3}, [%2]! \n" // store 8 V. - "bgt 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "d0", "d1", "d2", "d3" // Clobber List - ); -} - -void UYVYToUV422Row_NEON(const uint8* src_uyvy, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 16 pixels of UYVY. - "subs %3, %3, #16 \n" // 16 pixels = 8 UVs. - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 U. - MEMACCESS(2) - "vst1.8 {d2}, [%2]! \n" // store 8 V. - "bgt 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "d0", "d1", "d2", "d3" // Clobber List - ); -} - -void YUY2ToUVRow_NEON(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // stride + src_yuy2 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 16 pixels of YUY2. - "subs %4, %4, #16 \n" // 16 pixels = 8 UVs. - MEMACCESS(1) - "vld4.8 {d4, d5, d6, d7}, [%1]! \n" // load next row YUY2. - "vrhadd.u8 d1, d1, d5 \n" // average rows of U - "vrhadd.u8 d3, d3, d7 \n" // average rows of V - MEMACCESS(2) - "vst1.8 {d1}, [%2]! \n" // store 8 U. - MEMACCESS(3) - "vst1.8 {d3}, [%3]! \n" // store 8 V. - "bgt 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(stride_yuy2), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7" // Clobber List - ); -} - -void UYVYToUVRow_NEON(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // stride + src_uyvy - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 16 pixels of UYVY. - "subs %4, %4, #16 \n" // 16 pixels = 8 UVs. - MEMACCESS(1) - "vld4.8 {d4, d5, d6, d7}, [%1]! \n" // load next row UYVY. - "vrhadd.u8 d0, d0, d4 \n" // average rows of U - "vrhadd.u8 d2, d2, d6 \n" // average rows of V - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 U. - MEMACCESS(3) - "vst1.8 {d2}, [%3]! \n" // store 8 V. - "bgt 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(stride_uyvy), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7" // Clobber List - ); -} - -// For BGRAToARGB, ABGRToARGB, RGBAToARGB, and ARGBToRGBA. -void ARGBShuffleRow_NEON(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - asm volatile ( - MEMACCESS(3) - "vld1.8 {q2}, [%3] \n" // shuffler - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 4 pixels. - "subs %2, %2, #4 \n" // 4 processed per loop - "vtbl.8 d2, {d0, d1}, d4 \n" // look up 2 first pixels - "vtbl.8 d3, {d0, d1}, d5 \n" // look up 2 next pixels - MEMACCESS(1) - "vst1.8 {q1}, [%1]! \n" // store 4. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(shuffler) // %3 - : "cc", "memory", "q0", "q1", "q2" // Clobber List - ); -} - -void I422ToYUY2Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld2.8 {d0, d2}, [%0]! \n" // load 16 Ys - MEMACCESS(1) - "vld1.8 {d1}, [%1]! \n" // load 8 Us - MEMACCESS(2) - "vld1.8 {d3}, [%2]! \n" // load 8 Vs - "subs %4, %4, #16 \n" // 16 pixels - MEMACCESS(3) - "vst4.8 {d0, d1, d2, d3}, [%3]! \n" // Store 8 YUY2/16 pixels. - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_yuy2), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "d0", "d1", "d2", "d3" - ); -} - -void I422ToUYVYRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld2.8 {d1, d3}, [%0]! \n" // load 16 Ys - MEMACCESS(1) - "vld1.8 {d0}, [%1]! \n" // load 8 Us - MEMACCESS(2) - "vld1.8 {d2}, [%2]! \n" // load 8 Vs - "subs %4, %4, #16 \n" // 16 pixels - MEMACCESS(3) - "vst4.8 {d0, d1, d2, d3}, [%3]! \n" // Store 8 UYVY/16 pixels. - "bgt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_uyvy), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "d0", "d1", "d2", "d3" - ); -} - -void ARGBToRGB565Row_NEON(const uint8* src_argb, uint8* dst_rgb565, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d20, d21, d22, d23}, [%0]! \n" // load 8 pixels of ARGB. - "subs %2, %2, #8 \n" // 8 processed per loop. - ARGBTORGB565 - MEMACCESS(1) - "vst1.8 {q0}, [%1]! \n" // store 8 pixels RGB565. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_rgb565), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q8", "q9", "q10", "q11" - ); -} - -void ARGBToRGB565DitherRow_NEON(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width) { - asm volatile ( - "vdup.32 d2, %2 \n" // dither4 - "1: \n" - MEMACCESS(1) - "vld4.8 {d20, d21, d22, d23}, [%1]! \n" // load 8 pixels of ARGB. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vqadd.u8 d20, d20, d2 \n" - "vqadd.u8 d21, d21, d2 \n" - "vqadd.u8 d22, d22, d2 \n" - ARGBTORGB565 - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" // store 8 pixels RGB565. - "bgt 1b \n" - : "+r"(dst_rgb) // %0 - : "r"(src_argb), // %1 - "r"(dither4), // %2 - "r"(width) // %3 - : "cc", "memory", "q0", "q1", "q8", "q9", "q10", "q11" - ); -} - -void ARGBToARGB1555Row_NEON(const uint8* src_argb, uint8* dst_argb1555, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d20, d21, d22, d23}, [%0]! \n" // load 8 pixels of ARGB. - "subs %2, %2, #8 \n" // 8 processed per loop. - ARGBTOARGB1555 - MEMACCESS(1) - "vst1.8 {q0}, [%1]! \n" // store 8 pixels ARGB1555. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb1555), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q8", "q9", "q10", "q11" - ); -} - -void ARGBToARGB4444Row_NEON(const uint8* src_argb, uint8* dst_argb4444, - int width) { - asm volatile ( - "vmov.u8 d4, #0x0f \n" // bits to clear with vbic. - "1: \n" - MEMACCESS(0) - "vld4.8 {d20, d21, d22, d23}, [%0]! \n" // load 8 pixels of ARGB. - "subs %2, %2, #8 \n" // 8 processed per loop. - ARGBTOARGB4444 - MEMACCESS(1) - "vst1.8 {q0}, [%1]! \n" // store 8 pixels ARGB4444. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb4444), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q8", "q9", "q10", "q11" - ); -} - -void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d24, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d25, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d26, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d27, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 ARGB pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q2, d0, d24 \n" // B - "vmlal.u8 q2, d1, d25 \n" // G - "vmlal.u8 q2, d2, d26 \n" // R - "vqrshrun.s16 d0, q2, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d27 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q12", "q13" - ); -} - -void ARGBExtractAlphaRow_NEON(const uint8* src_argb, uint8* dst_a, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ARGB pixels - "subs %2, %2, #16 \n" // 16 processed per loop - MEMACCESS(1) - "vst1.8 {q3}, [%1]! \n" // store 16 A's. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_a), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q3" // Clobber List - ); -} - -void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d24, #15 \n" // B * 0.11400 coefficient - "vmov.u8 d25, #75 \n" // G * 0.58700 coefficient - "vmov.u8 d26, #38 \n" // R * 0.29900 coefficient - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 ARGB pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q2, d0, d24 \n" // B - "vmlal.u8 q2, d1, d25 \n" // G - "vmlal.u8 q2, d2, d26 \n" // R - "vqrshrun.s16 d0, q2, #7 \n" // 15 bit to 8 bit Y - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q12", "q13" - ); -} - -// 8x1 pixels. -void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "vmov.u8 d24, #112 \n" // UB / VR 0.875 coefficient - "vmov.u8 d25, #74 \n" // UG -0.5781 coefficient - "vmov.u8 d26, #38 \n" // UR -0.2969 coefficient - "vmov.u8 d27, #18 \n" // VB -0.1406 coefficient - "vmov.u8 d28, #94 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 ARGB pixels. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vmull.u8 q2, d0, d24 \n" // B - "vmlsl.u8 q2, d1, d25 \n" // G - "vmlsl.u8 q2, d2, d26 \n" // R - "vadd.u16 q2, q2, q15 \n" // +128 -> unsigned - - "vmull.u8 q3, d2, d24 \n" // R - "vmlsl.u8 q3, d1, d28 \n" // G - "vmlsl.u8 q3, d0, d27 \n" // B - "vadd.u16 q3, q3, q15 \n" // +128 -> unsigned - - "vqshrn.u16 d0, q2, #8 \n" // 16 bit to 8 bit U - "vqshrn.u16 d1, q3, #8 \n" // 16 bit to 8 bit V - - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels U. - MEMACCESS(2) - "vst1.8 {d1}, [%2]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q12", "q13", "q14", "q15" - ); -} - -// 32x1 pixels -> 8x1. width is number of argb pixels. e.g. 32. -void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ARGB pixels. - "vpaddl.u8 q0, q0 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q2 \n" // R 16 bytes -> 8 shorts. - MEMACCESS(0) - "vld4.8 {d8, d10, d12, d14}, [%0]! \n" // load 8 more ARGB pixels. - MEMACCESS(0) - "vld4.8 {d9, d11, d13, d15}, [%0]! \n" // load last 8 ARGB pixels. - "vpaddl.u8 q4, q4 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q5, q5 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q6, q6 \n" // R 16 bytes -> 8 shorts. - - "vpadd.u16 d0, d0, d1 \n" // B 16 shorts -> 8 shorts. - "vpadd.u16 d1, d8, d9 \n" // B - "vpadd.u16 d2, d2, d3 \n" // G 16 shorts -> 8 shorts. - "vpadd.u16 d3, d10, d11 \n" // G - "vpadd.u16 d4, d4, d5 \n" // R 16 shorts -> 8 shorts. - "vpadd.u16 d5, d12, d13 \n" // R - - "vrshr.u16 q0, q0, #1 \n" // 2x average - "vrshr.u16 q1, q1, #1 \n" - "vrshr.u16 q2, q2, #1 \n" - - "subs %3, %3, #32 \n" // 32 processed per loop. - "vmul.s16 q8, q0, q10 \n" // B - "vmls.s16 q8, q1, q11 \n" // G - "vmls.s16 q8, q2, q12 \n" // R - "vadd.u16 q8, q8, q15 \n" // +128 -> unsigned - "vmul.s16 q9, q2, q10 \n" // R - "vmls.s16 q9, q1, q14 \n" // G - "vmls.s16 q9, q0, q13 \n" // B - "vadd.u16 q9, q9, q15 \n" // +128 -> unsigned - "vqshrn.u16 d0, q8, #8 \n" // 16 bit to 8 bit U - "vqshrn.u16 d1, q9, #8 \n" // 16 bit to 8 bit V - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels U. - MEMACCESS(2) - "vst1.8 {d1}, [%2]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16. -#define RGBTOUV(QB, QG, QR) \ - "vmul.s16 q8, " #QB ", q10 \n" /* B */ \ - "vmls.s16 q8, " #QG ", q11 \n" /* G */ \ - "vmls.s16 q8, " #QR ", q12 \n" /* R */ \ - "vadd.u16 q8, q8, q15 \n" /* +128 -> unsigned */ \ - "vmul.s16 q9, " #QR ", q10 \n" /* R */ \ - "vmls.s16 q9, " #QG ", q14 \n" /* G */ \ - "vmls.s16 q9, " #QB ", q13 \n" /* B */ \ - "vadd.u16 q9, q9, q15 \n" /* +128 -> unsigned */ \ - "vqshrn.u16 d0, q8, #8 \n" /* 16 bit to 8 bit U */ \ - "vqshrn.u16 d1, q9, #8 \n" /* 16 bit to 8 bit V */ - -// TODO(fbarchard): Consider vhadd vertical, then vpaddl horizontal, avoid shr. -void ARGBToUVRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_argb - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ARGB pixels. - "vpaddl.u8 q0, q0 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q2 \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "vld4.8 {d8, d10, d12, d14}, [%1]! \n" // load 8 more ARGB pixels. - MEMACCESS(1) - "vld4.8 {d9, d11, d13, d15}, [%1]! \n" // load last 8 ARGB pixels. - "vpadal.u8 q0, q4 \n" // B 16 bytes -> 8 shorts. - "vpadal.u8 q1, q5 \n" // G 16 bytes -> 8 shorts. - "vpadal.u8 q2, q6 \n" // R 16 bytes -> 8 shorts. - - "vrshr.u16 q0, q0, #1 \n" // 2x average - "vrshr.u16 q1, q1, #1 \n" - "vrshr.u16 q2, q2, #1 \n" - - "subs %4, %4, #16 \n" // 32 processed per loop. - RGBTOUV(q0, q1, q2) - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_stride_argb), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -// TODO(fbarchard): Subsample match C code. -void ARGBToUVJRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_argb - "vmov.s16 q10, #127 / 2 \n" // UB / VR 0.500 coefficient - "vmov.s16 q11, #84 / 2 \n" // UG -0.33126 coefficient - "vmov.s16 q12, #43 / 2 \n" // UR -0.16874 coefficient - "vmov.s16 q13, #20 / 2 \n" // VB -0.08131 coefficient - "vmov.s16 q14, #107 / 2 \n" // VG -0.41869 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ARGB pixels. - "vpaddl.u8 q0, q0 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q2 \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "vld4.8 {d8, d10, d12, d14}, [%1]! \n" // load 8 more ARGB pixels. - MEMACCESS(1) - "vld4.8 {d9, d11, d13, d15}, [%1]! \n" // load last 8 ARGB pixels. - "vpadal.u8 q0, q4 \n" // B 16 bytes -> 8 shorts. - "vpadal.u8 q1, q5 \n" // G 16 bytes -> 8 shorts. - "vpadal.u8 q2, q6 \n" // R 16 bytes -> 8 shorts. - - "vrshr.u16 q0, q0, #1 \n" // 2x average - "vrshr.u16 q1, q1, #1 \n" - "vrshr.u16 q2, q2, #1 \n" - - "subs %4, %4, #16 \n" // 32 processed per loop. - RGBTOUV(q0, q1, q2) - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_stride_argb), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void BGRAToUVRow_NEON(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_bgra - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 BGRA pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 BGRA pixels. - "vpaddl.u8 q3, q3 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q2 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "vld4.8 {d8, d10, d12, d14}, [%1]! \n" // load 8 more BGRA pixels. - MEMACCESS(1) - "vld4.8 {d9, d11, d13, d15}, [%1]! \n" // load last 8 BGRA pixels. - "vpadal.u8 q3, q7 \n" // B 16 bytes -> 8 shorts. - "vpadal.u8 q2, q6 \n" // G 16 bytes -> 8 shorts. - "vpadal.u8 q1, q5 \n" // R 16 bytes -> 8 shorts. - - "vrshr.u16 q1, q1, #1 \n" // 2x average - "vrshr.u16 q2, q2, #1 \n" - "vrshr.u16 q3, q3, #1 \n" - - "subs %4, %4, #16 \n" // 32 processed per loop. - RGBTOUV(q3, q2, q1) - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_bgra), // %0 - "+r"(src_stride_bgra), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void ABGRToUVRow_NEON(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_abgr - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ABGR pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ABGR pixels. - "vpaddl.u8 q2, q2 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q0, q0 \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "vld4.8 {d8, d10, d12, d14}, [%1]! \n" // load 8 more ABGR pixels. - MEMACCESS(1) - "vld4.8 {d9, d11, d13, d15}, [%1]! \n" // load last 8 ABGR pixels. - "vpadal.u8 q2, q6 \n" // B 16 bytes -> 8 shorts. - "vpadal.u8 q1, q5 \n" // G 16 bytes -> 8 shorts. - "vpadal.u8 q0, q4 \n" // R 16 bytes -> 8 shorts. - - "vrshr.u16 q0, q0, #1 \n" // 2x average - "vrshr.u16 q1, q1, #1 \n" - "vrshr.u16 q2, q2, #1 \n" - - "subs %4, %4, #16 \n" // 32 processed per loop. - RGBTOUV(q2, q1, q0) - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_abgr), // %0 - "+r"(src_stride_abgr), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void RGBAToUVRow_NEON(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_rgba - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 RGBA pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 RGBA pixels. - "vpaddl.u8 q0, q1 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q2 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q3 \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "vld4.8 {d8, d10, d12, d14}, [%1]! \n" // load 8 more RGBA pixels. - MEMACCESS(1) - "vld4.8 {d9, d11, d13, d15}, [%1]! \n" // load last 8 RGBA pixels. - "vpadal.u8 q0, q5 \n" // B 16 bytes -> 8 shorts. - "vpadal.u8 q1, q6 \n" // G 16 bytes -> 8 shorts. - "vpadal.u8 q2, q7 \n" // R 16 bytes -> 8 shorts. - - "vrshr.u16 q0, q0, #1 \n" // 2x average - "vrshr.u16 q1, q1, #1 \n" - "vrshr.u16 q2, q2, #1 \n" - - "subs %4, %4, #16 \n" // 32 processed per loop. - RGBTOUV(q0, q1, q2) - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_rgba), // %0 - "+r"(src_stride_rgba), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void RGB24ToUVRow_NEON(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_rgb24 - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld3.8 {d0, d2, d4}, [%0]! \n" // load 8 RGB24 pixels. - MEMACCESS(0) - "vld3.8 {d1, d3, d5}, [%0]! \n" // load next 8 RGB24 pixels. - "vpaddl.u8 q0, q0 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q2 \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "vld3.8 {d8, d10, d12}, [%1]! \n" // load 8 more RGB24 pixels. - MEMACCESS(1) - "vld3.8 {d9, d11, d13}, [%1]! \n" // load last 8 RGB24 pixels. - "vpadal.u8 q0, q4 \n" // B 16 bytes -> 8 shorts. - "vpadal.u8 q1, q5 \n" // G 16 bytes -> 8 shorts. - "vpadal.u8 q2, q6 \n" // R 16 bytes -> 8 shorts. - - "vrshr.u16 q0, q0, #1 \n" // 2x average - "vrshr.u16 q1, q1, #1 \n" - "vrshr.u16 q2, q2, #1 \n" - - "subs %4, %4, #16 \n" // 32 processed per loop. - RGBTOUV(q0, q1, q2) - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_rgb24), // %0 - "+r"(src_stride_rgb24), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void RAWToUVRow_NEON(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_raw - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld3.8 {d0, d2, d4}, [%0]! \n" // load 8 RAW pixels. - MEMACCESS(0) - "vld3.8 {d1, d3, d5}, [%0]! \n" // load next 8 RAW pixels. - "vpaddl.u8 q2, q2 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q0, q0 \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "vld3.8 {d8, d10, d12}, [%1]! \n" // load 8 more RAW pixels. - MEMACCESS(1) - "vld3.8 {d9, d11, d13}, [%1]! \n" // load last 8 RAW pixels. - "vpadal.u8 q2, q6 \n" // B 16 bytes -> 8 shorts. - "vpadal.u8 q1, q5 \n" // G 16 bytes -> 8 shorts. - "vpadal.u8 q0, q4 \n" // R 16 bytes -> 8 shorts. - - "vrshr.u16 q0, q0, #1 \n" // 2x average - "vrshr.u16 q1, q1, #1 \n" - "vrshr.u16 q2, q2, #1 \n" - - "subs %4, %4, #16 \n" // 32 processed per loop. - RGBTOUV(q2, q1, q0) - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_raw), // %0 - "+r"(src_stride_raw), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16. -void RGB565ToUVRow_NEON(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_argb - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 RGB565 pixels. - RGB565TOARGB - "vpaddl.u8 d8, d0 \n" // B 8 bytes -> 4 shorts. - "vpaddl.u8 d10, d1 \n" // G 8 bytes -> 4 shorts. - "vpaddl.u8 d12, d2 \n" // R 8 bytes -> 4 shorts. - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // next 8 RGB565 pixels. - RGB565TOARGB - "vpaddl.u8 d9, d0 \n" // B 8 bytes -> 4 shorts. - "vpaddl.u8 d11, d1 \n" // G 8 bytes -> 4 shorts. - "vpaddl.u8 d13, d2 \n" // R 8 bytes -> 4 shorts. - - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" // load 8 RGB565 pixels. - RGB565TOARGB - "vpadal.u8 d8, d0 \n" // B 8 bytes -> 4 shorts. - "vpadal.u8 d10, d1 \n" // G 8 bytes -> 4 shorts. - "vpadal.u8 d12, d2 \n" // R 8 bytes -> 4 shorts. - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" // next 8 RGB565 pixels. - RGB565TOARGB - "vpadal.u8 d9, d0 \n" // B 8 bytes -> 4 shorts. - "vpadal.u8 d11, d1 \n" // G 8 bytes -> 4 shorts. - "vpadal.u8 d13, d2 \n" // R 8 bytes -> 4 shorts. - - "vrshr.u16 q4, q4, #1 \n" // 2x average - "vrshr.u16 q5, q5, #1 \n" - "vrshr.u16 q6, q6, #1 \n" - - "subs %4, %4, #16 \n" // 16 processed per loop. - "vmul.s16 q8, q4, q10 \n" // B - "vmls.s16 q8, q5, q11 \n" // G - "vmls.s16 q8, q6, q12 \n" // R - "vadd.u16 q8, q8, q15 \n" // +128 -> unsigned - "vmul.s16 q9, q6, q10 \n" // R - "vmls.s16 q9, q5, q14 \n" // G - "vmls.s16 q9, q4, q13 \n" // B - "vadd.u16 q9, q9, q15 \n" // +128 -> unsigned - "vqshrn.u16 d0, q8, #8 \n" // 16 bit to 8 bit U - "vqshrn.u16 d1, q9, #8 \n" // 16 bit to 8 bit V - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_rgb565), // %0 - "+r"(src_stride_rgb565), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16. -void ARGB1555ToUVRow_NEON(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_argb - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 ARGB1555 pixels. - RGB555TOARGB - "vpaddl.u8 d8, d0 \n" // B 8 bytes -> 4 shorts. - "vpaddl.u8 d10, d1 \n" // G 8 bytes -> 4 shorts. - "vpaddl.u8 d12, d2 \n" // R 8 bytes -> 4 shorts. - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // next 8 ARGB1555 pixels. - RGB555TOARGB - "vpaddl.u8 d9, d0 \n" // B 8 bytes -> 4 shorts. - "vpaddl.u8 d11, d1 \n" // G 8 bytes -> 4 shorts. - "vpaddl.u8 d13, d2 \n" // R 8 bytes -> 4 shorts. - - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" // load 8 ARGB1555 pixels. - RGB555TOARGB - "vpadal.u8 d8, d0 \n" // B 8 bytes -> 4 shorts. - "vpadal.u8 d10, d1 \n" // G 8 bytes -> 4 shorts. - "vpadal.u8 d12, d2 \n" // R 8 bytes -> 4 shorts. - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" // next 8 ARGB1555 pixels. - RGB555TOARGB - "vpadal.u8 d9, d0 \n" // B 8 bytes -> 4 shorts. - "vpadal.u8 d11, d1 \n" // G 8 bytes -> 4 shorts. - "vpadal.u8 d13, d2 \n" // R 8 bytes -> 4 shorts. - - "vrshr.u16 q4, q4, #1 \n" // 2x average - "vrshr.u16 q5, q5, #1 \n" - "vrshr.u16 q6, q6, #1 \n" - - "subs %4, %4, #16 \n" // 16 processed per loop. - "vmul.s16 q8, q4, q10 \n" // B - "vmls.s16 q8, q5, q11 \n" // G - "vmls.s16 q8, q6, q12 \n" // R - "vadd.u16 q8, q8, q15 \n" // +128 -> unsigned - "vmul.s16 q9, q6, q10 \n" // R - "vmls.s16 q9, q5, q14 \n" // G - "vmls.s16 q9, q4, q13 \n" // B - "vadd.u16 q9, q9, q15 \n" // +128 -> unsigned - "vqshrn.u16 d0, q8, #8 \n" // 16 bit to 8 bit U - "vqshrn.u16 d1, q9, #8 \n" // 16 bit to 8 bit V - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_argb1555), // %0 - "+r"(src_stride_argb1555), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16. -void ARGB4444ToUVRow_NEON(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "add %1, %0, %1 \n" // src_stride + src_argb - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 ARGB4444 pixels. - ARGB4444TOARGB - "vpaddl.u8 d8, d0 \n" // B 8 bytes -> 4 shorts. - "vpaddl.u8 d10, d1 \n" // G 8 bytes -> 4 shorts. - "vpaddl.u8 d12, d2 \n" // R 8 bytes -> 4 shorts. - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // next 8 ARGB4444 pixels. - ARGB4444TOARGB - "vpaddl.u8 d9, d0 \n" // B 8 bytes -> 4 shorts. - "vpaddl.u8 d11, d1 \n" // G 8 bytes -> 4 shorts. - "vpaddl.u8 d13, d2 \n" // R 8 bytes -> 4 shorts. - - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" // load 8 ARGB4444 pixels. - ARGB4444TOARGB - "vpadal.u8 d8, d0 \n" // B 8 bytes -> 4 shorts. - "vpadal.u8 d10, d1 \n" // G 8 bytes -> 4 shorts. - "vpadal.u8 d12, d2 \n" // R 8 bytes -> 4 shorts. - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" // next 8 ARGB4444 pixels. - ARGB4444TOARGB - "vpadal.u8 d9, d0 \n" // B 8 bytes -> 4 shorts. - "vpadal.u8 d11, d1 \n" // G 8 bytes -> 4 shorts. - "vpadal.u8 d13, d2 \n" // R 8 bytes -> 4 shorts. - - "vrshr.u16 q4, q4, #1 \n" // 2x average - "vrshr.u16 q5, q5, #1 \n" - "vrshr.u16 q6, q6, #1 \n" - - "subs %4, %4, #16 \n" // 16 processed per loop. - "vmul.s16 q8, q4, q10 \n" // B - "vmls.s16 q8, q5, q11 \n" // G - "vmls.s16 q8, q6, q12 \n" // R - "vadd.u16 q8, q8, q15 \n" // +128 -> unsigned - "vmul.s16 q9, q6, q10 \n" // R - "vmls.s16 q9, q5, q14 \n" // G - "vmls.s16 q9, q4, q13 \n" // B - "vadd.u16 q9, q9, q15 \n" // +128 -> unsigned - "vqshrn.u16 d0, q8, #8 \n" // 16 bit to 8 bit U - "vqshrn.u16 d1, q9, #8 \n" // 16 bit to 8 bit V - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 pixels U. - MEMACCESS(3) - "vst1.8 {d1}, [%3]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_argb4444), // %0 - "+r"(src_stride_argb4444), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -void RGB565ToYRow_NEON(const uint8* src_rgb565, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d24, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d25, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d26, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d27, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 RGB565 pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - RGB565TOARGB - "vmull.u8 q2, d0, d24 \n" // B - "vmlal.u8 q2, d1, d25 \n" // G - "vmlal.u8 q2, d2, d26 \n" // R - "vqrshrun.s16 d0, q2, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d27 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_rgb565), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q12", "q13" - ); -} - -void ARGB1555ToYRow_NEON(const uint8* src_argb1555, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d24, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d25, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d26, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d27, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 ARGB1555 pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - ARGB1555TOARGB - "vmull.u8 q2, d0, d24 \n" // B - "vmlal.u8 q2, d1, d25 \n" // G - "vmlal.u8 q2, d2, d26 \n" // R - "vqrshrun.s16 d0, q2, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d27 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_argb1555), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q12", "q13" - ); -} - -void ARGB4444ToYRow_NEON(const uint8* src_argb4444, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d24, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d25, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d26, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d27, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 8 ARGB4444 pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - ARGB4444TOARGB - "vmull.u8 q2, d0, d24 \n" // B - "vmlal.u8 q2, d1, d25 \n" // G - "vmlal.u8 q2, d2, d26 \n" // R - "vqrshrun.s16 d0, q2, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d27 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_argb4444), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q12", "q13" - ); -} - -void BGRAToYRow_NEON(const uint8* src_bgra, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d4, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d5, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d6, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d7, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 pixels of BGRA. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q8, d1, d4 \n" // R - "vmlal.u8 q8, d2, d5 \n" // G - "vmlal.u8 q8, d3, d6 \n" // B - "vqrshrun.s16 d0, q8, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d7 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_bgra), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "q8" - ); -} - -void ABGRToYRow_NEON(const uint8* src_abgr, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d4, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d5, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d6, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d7, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 pixels of ABGR. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q8, d0, d4 \n" // R - "vmlal.u8 q8, d1, d5 \n" // G - "vmlal.u8 q8, d2, d6 \n" // B - "vqrshrun.s16 d0, q8, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d7 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_abgr), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "q8" - ); -} - -void RGBAToYRow_NEON(const uint8* src_rgba, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d4, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d5, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d6, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d7, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 pixels of RGBA. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q8, d1, d4 \n" // B - "vmlal.u8 q8, d2, d5 \n" // G - "vmlal.u8 q8, d3, d6 \n" // R - "vqrshrun.s16 d0, q8, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d7 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_rgba), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "q8" - ); -} - -void RGB24ToYRow_NEON(const uint8* src_rgb24, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d4, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d5, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d6, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d7, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld3.8 {d0, d1, d2}, [%0]! \n" // load 8 pixels of RGB24. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q8, d0, d4 \n" // B - "vmlal.u8 q8, d1, d5 \n" // G - "vmlal.u8 q8, d2, d6 \n" // R - "vqrshrun.s16 d0, q8, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d7 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_rgb24), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "q8" - ); -} - -void RAWToYRow_NEON(const uint8* src_raw, uint8* dst_y, int width) { - asm volatile ( - "vmov.u8 d4, #33 \n" // R * 0.2578 coefficient - "vmov.u8 d5, #65 \n" // G * 0.5078 coefficient - "vmov.u8 d6, #13 \n" // B * 0.1016 coefficient - "vmov.u8 d7, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "vld3.8 {d0, d1, d2}, [%0]! \n" // load 8 pixels of RAW. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q8, d0, d4 \n" // B - "vmlal.u8 q8, d1, d5 \n" // G - "vmlal.u8 q8, d2, d6 \n" // R - "vqrshrun.s16 d0, q8, #7 \n" // 16 bit to 8 bit Y - "vqadd.u8 d0, d7 \n" - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels Y. - "bgt 1b \n" - : "+r"(src_raw), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "q8" - ); -} - -// Bilinear filter 16x2 -> 16x1 -void InterpolateRow_NEON(uint8* dst_ptr, - const uint8* src_ptr, ptrdiff_t src_stride, - int dst_width, int source_y_fraction) { - int y1_fraction = source_y_fraction; - asm volatile ( - "cmp %4, #0 \n" - "beq 100f \n" - "add %2, %1 \n" - "cmp %4, #128 \n" - "beq 50f \n" - - "vdup.8 d5, %4 \n" - "rsb %4, #256 \n" - "vdup.8 d4, %4 \n" - // General purpose row blend. - "1: \n" - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" - MEMACCESS(2) - "vld1.8 {q1}, [%2]! \n" - "subs %3, %3, #16 \n" - "vmull.u8 q13, d0, d4 \n" - "vmull.u8 q14, d1, d4 \n" - "vmlal.u8 q13, d2, d5 \n" - "vmlal.u8 q14, d3, d5 \n" - "vrshrn.u16 d0, q13, #8 \n" - "vrshrn.u16 d1, q14, #8 \n" - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" - "bgt 1b \n" - "b 99f \n" - - // Blend 50 / 50. - "50: \n" - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" - MEMACCESS(2) - "vld1.8 {q1}, [%2]! \n" - "subs %3, %3, #16 \n" - "vrhadd.u8 q0, q1 \n" - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" - "bgt 50b \n" - "b 99f \n" - - // Blend 100 / 0 - Copy row unchanged. - "100: \n" - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" - "subs %3, %3, #16 \n" - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" - "bgt 100b \n" - - "99: \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(src_stride), // %2 - "+r"(dst_width), // %3 - "+r"(y1_fraction) // %4 - : - : "cc", "memory", "q0", "q1", "d4", "d5", "q13", "q14" - ); -} - -// dr * (256 - sa) / 256 + sr = dr - dr * sa / 256 + sr -void ARGBBlendRow_NEON(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - "subs %3, #8 \n" - "blt 89f \n" - // Blend 8 pixels. - "8: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 pixels of ARGB0. - MEMACCESS(1) - "vld4.8 {d4, d5, d6, d7}, [%1]! \n" // load 8 pixels of ARGB1. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vmull.u8 q10, d4, d3 \n" // db * a - "vmull.u8 q11, d5, d3 \n" // dg * a - "vmull.u8 q12, d6, d3 \n" // dr * a - "vqrshrn.u16 d20, q10, #8 \n" // db >>= 8 - "vqrshrn.u16 d21, q11, #8 \n" // dg >>= 8 - "vqrshrn.u16 d22, q12, #8 \n" // dr >>= 8 - "vqsub.u8 q2, q2, q10 \n" // dbg - dbg * a / 256 - "vqsub.u8 d6, d6, d22 \n" // dr - dr * a / 256 - "vqadd.u8 q0, q0, q2 \n" // + sbg - "vqadd.u8 d2, d2, d6 \n" // + sr - "vmov.u8 d3, #255 \n" // a = 255 - MEMACCESS(2) - "vst4.8 {d0, d1, d2, d3}, [%2]! \n" // store 8 pixels of ARGB. - "bge 8b \n" - - "89: \n" - "adds %3, #8-1 \n" - "blt 99f \n" - - // Blend 1 pixels. - "1: \n" - MEMACCESS(0) - "vld4.8 {d0[0],d1[0],d2[0],d3[0]}, [%0]! \n" // load 1 pixel ARGB0. - MEMACCESS(1) - "vld4.8 {d4[0],d5[0],d6[0],d7[0]}, [%1]! \n" // load 1 pixel ARGB1. - "subs %3, %3, #1 \n" // 1 processed per loop. - "vmull.u8 q10, d4, d3 \n" // db * a - "vmull.u8 q11, d5, d3 \n" // dg * a - "vmull.u8 q12, d6, d3 \n" // dr * a - "vqrshrn.u16 d20, q10, #8 \n" // db >>= 8 - "vqrshrn.u16 d21, q11, #8 \n" // dg >>= 8 - "vqrshrn.u16 d22, q12, #8 \n" // dr >>= 8 - "vqsub.u8 q2, q2, q10 \n" // dbg - dbg * a / 256 - "vqsub.u8 d6, d6, d22 \n" // dr - dr * a / 256 - "vqadd.u8 q0, q0, q2 \n" // + sbg - "vqadd.u8 d2, d2, d6 \n" // + sr - "vmov.u8 d3, #255 \n" // a = 255 - MEMACCESS(2) - "vst4.8 {d0[0],d1[0],d2[0],d3[0]}, [%2]! \n" // store 1 pixel. - "bge 1b \n" - - "99: \n" - - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1", "q2", "q3", "q10", "q11", "q12" - ); -} - -// Attenuate 8 pixels at a time. -void ARGBAttenuateRow_NEON(const uint8* src_argb, uint8* dst_argb, int width) { - asm volatile ( - // Attenuate 8 pixels. - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 pixels of ARGB. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q10, d0, d3 \n" // b * a - "vmull.u8 q11, d1, d3 \n" // g * a - "vmull.u8 q12, d2, d3 \n" // r * a - "vqrshrn.u16 d0, q10, #8 \n" // b >>= 8 - "vqrshrn.u16 d1, q11, #8 \n" // g >>= 8 - "vqrshrn.u16 d2, q12, #8 \n" // r >>= 8 - MEMACCESS(1) - "vst4.8 {d0, d1, d2, d3}, [%1]! \n" // store 8 pixels of ARGB. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q10", "q11", "q12" - ); -} - -// Quantize 8 ARGB pixels (32 bytes). -// dst = (dst * scale >> 16) * interval_size + interval_offset; -void ARGBQuantizeRow_NEON(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width) { - asm volatile ( - "vdup.u16 q8, %2 \n" - "vshr.u16 q8, q8, #1 \n" // scale >>= 1 - "vdup.u16 q9, %3 \n" // interval multiply. - "vdup.u16 q10, %4 \n" // interval add - - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0] \n" // load 8 pixels of ARGB. - "subs %1, %1, #8 \n" // 8 processed per loop. - "vmovl.u8 q0, d0 \n" // b (0 .. 255) - "vmovl.u8 q1, d2 \n" - "vmovl.u8 q2, d4 \n" - "vqdmulh.s16 q0, q0, q8 \n" // b * scale - "vqdmulh.s16 q1, q1, q8 \n" // g - "vqdmulh.s16 q2, q2, q8 \n" // r - "vmul.u16 q0, q0, q9 \n" // b * interval_size - "vmul.u16 q1, q1, q9 \n" // g - "vmul.u16 q2, q2, q9 \n" // r - "vadd.u16 q0, q0, q10 \n" // b + interval_offset - "vadd.u16 q1, q1, q10 \n" // g - "vadd.u16 q2, q2, q10 \n" // r - "vqmovn.u16 d0, q0 \n" - "vqmovn.u16 d2, q1 \n" - "vqmovn.u16 d4, q2 \n" - MEMACCESS(0) - "vst4.8 {d0, d2, d4, d6}, [%0]! \n" // store 8 pixels of ARGB. - "bgt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(width) // %1 - : "r"(scale), // %2 - "r"(interval_size), // %3 - "r"(interval_offset) // %4 - : "cc", "memory", "q0", "q1", "q2", "q3", "q8", "q9", "q10" - ); -} - -// Shade 8 pixels at a time by specified value. -// NOTE vqrdmulh.s16 q10, q10, d0[0] must use a scaler register from 0 to 8. -// Rounding in vqrdmulh does +1 to high if high bit of low s16 is set. -void ARGBShadeRow_NEON(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value) { - asm volatile ( - "vdup.u32 q0, %3 \n" // duplicate scale value. - "vzip.u8 d0, d1 \n" // d0 aarrggbb. - "vshr.u16 q0, q0, #1 \n" // scale / 2. - - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "vld4.8 {d20, d22, d24, d26}, [%0]! \n" // load 8 pixels of ARGB. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmovl.u8 q10, d20 \n" // b (0 .. 255) - "vmovl.u8 q11, d22 \n" - "vmovl.u8 q12, d24 \n" - "vmovl.u8 q13, d26 \n" - "vqrdmulh.s16 q10, q10, d0[0] \n" // b * scale * 2 - "vqrdmulh.s16 q11, q11, d0[1] \n" // g - "vqrdmulh.s16 q12, q12, d0[2] \n" // r - "vqrdmulh.s16 q13, q13, d0[3] \n" // a - "vqmovn.u16 d20, q10 \n" - "vqmovn.u16 d22, q11 \n" - "vqmovn.u16 d24, q12 \n" - "vqmovn.u16 d26, q13 \n" - MEMACCESS(1) - "vst4.8 {d20, d22, d24, d26}, [%1]! \n" // store 8 pixels of ARGB. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(value) // %3 - : "cc", "memory", "q0", "q10", "q11", "q12", "q13" - ); -} - -// Convert 8 ARGB pixels (64 bytes) to 8 Gray ARGB pixels -// Similar to ARGBToYJ but stores ARGB. -// C code is (15 * b + 75 * g + 38 * r + 64) >> 7; -void ARGBGrayRow_NEON(const uint8* src_argb, uint8* dst_argb, int width) { - asm volatile ( - "vmov.u8 d24, #15 \n" // B * 0.11400 coefficient - "vmov.u8 d25, #75 \n" // G * 0.58700 coefficient - "vmov.u8 d26, #38 \n" // R * 0.29900 coefficient - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 ARGB pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmull.u8 q2, d0, d24 \n" // B - "vmlal.u8 q2, d1, d25 \n" // G - "vmlal.u8 q2, d2, d26 \n" // R - "vqrshrun.s16 d0, q2, #7 \n" // 15 bit to 8 bit B - "vmov d1, d0 \n" // G - "vmov d2, d0 \n" // R - MEMACCESS(1) - "vst4.8 {d0, d1, d2, d3}, [%1]! \n" // store 8 ARGB pixels. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "q0", "q1", "q2", "q12", "q13" - ); -} - -// Convert 8 ARGB pixels (32 bytes) to 8 Sepia ARGB pixels. -// b = (r * 35 + g * 68 + b * 17) >> 7 -// g = (r * 45 + g * 88 + b * 22) >> 7 -// r = (r * 50 + g * 98 + b * 24) >> 7 -void ARGBSepiaRow_NEON(uint8* dst_argb, int width) { - asm volatile ( - "vmov.u8 d20, #17 \n" // BB coefficient - "vmov.u8 d21, #68 \n" // BG coefficient - "vmov.u8 d22, #35 \n" // BR coefficient - "vmov.u8 d24, #22 \n" // GB coefficient - "vmov.u8 d25, #88 \n" // GG coefficient - "vmov.u8 d26, #45 \n" // GR coefficient - "vmov.u8 d28, #24 \n" // BB coefficient - "vmov.u8 d29, #98 \n" // BG coefficient - "vmov.u8 d30, #50 \n" // BR coefficient - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0] \n" // load 8 ARGB pixels. - "subs %1, %1, #8 \n" // 8 processed per loop. - "vmull.u8 q2, d0, d20 \n" // B to Sepia B - "vmlal.u8 q2, d1, d21 \n" // G - "vmlal.u8 q2, d2, d22 \n" // R - "vmull.u8 q3, d0, d24 \n" // B to Sepia G - "vmlal.u8 q3, d1, d25 \n" // G - "vmlal.u8 q3, d2, d26 \n" // R - "vmull.u8 q8, d0, d28 \n" // B to Sepia R - "vmlal.u8 q8, d1, d29 \n" // G - "vmlal.u8 q8, d2, d30 \n" // R - "vqshrn.u16 d0, q2, #7 \n" // 16 bit to 8 bit B - "vqshrn.u16 d1, q3, #7 \n" // 16 bit to 8 bit G - "vqshrn.u16 d2, q8, #7 \n" // 16 bit to 8 bit R - MEMACCESS(0) - "vst4.8 {d0, d1, d2, d3}, [%0]! \n" // store 8 ARGB pixels. - "bgt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(width) // %1 - : - : "cc", "memory", "q0", "q1", "q2", "q3", - "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -// Tranform 8 ARGB pixels (32 bytes) with color matrix. -// TODO(fbarchard): Was same as Sepia except matrix is provided. This function -// needs to saturate. Consider doing a non-saturating version. -void ARGBColorMatrixRow_NEON(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width) { - asm volatile ( - MEMACCESS(3) - "vld1.8 {q2}, [%3] \n" // load 3 ARGB vectors. - "vmovl.s8 q0, d4 \n" // B,G coefficients s16. - "vmovl.s8 q1, d5 \n" // R,A coefficients s16. - - "1: \n" - MEMACCESS(0) - "vld4.8 {d16, d18, d20, d22}, [%0]! \n" // load 8 ARGB pixels. - "subs %2, %2, #8 \n" // 8 processed per loop. - "vmovl.u8 q8, d16 \n" // b (0 .. 255) 16 bit - "vmovl.u8 q9, d18 \n" // g - "vmovl.u8 q10, d20 \n" // r - "vmovl.u8 q11, d22 \n" // a - "vmul.s16 q12, q8, d0[0] \n" // B = B * Matrix B - "vmul.s16 q13, q8, d1[0] \n" // G = B * Matrix G - "vmul.s16 q14, q8, d2[0] \n" // R = B * Matrix R - "vmul.s16 q15, q8, d3[0] \n" // A = B * Matrix A - "vmul.s16 q4, q9, d0[1] \n" // B += G * Matrix B - "vmul.s16 q5, q9, d1[1] \n" // G += G * Matrix G - "vmul.s16 q6, q9, d2[1] \n" // R += G * Matrix R - "vmul.s16 q7, q9, d3[1] \n" // A += G * Matrix A - "vqadd.s16 q12, q12, q4 \n" // Accumulate B - "vqadd.s16 q13, q13, q5 \n" // Accumulate G - "vqadd.s16 q14, q14, q6 \n" // Accumulate R - "vqadd.s16 q15, q15, q7 \n" // Accumulate A - "vmul.s16 q4, q10, d0[2] \n" // B += R * Matrix B - "vmul.s16 q5, q10, d1[2] \n" // G += R * Matrix G - "vmul.s16 q6, q10, d2[2] \n" // R += R * Matrix R - "vmul.s16 q7, q10, d3[2] \n" // A += R * Matrix A - "vqadd.s16 q12, q12, q4 \n" // Accumulate B - "vqadd.s16 q13, q13, q5 \n" // Accumulate G - "vqadd.s16 q14, q14, q6 \n" // Accumulate R - "vqadd.s16 q15, q15, q7 \n" // Accumulate A - "vmul.s16 q4, q11, d0[3] \n" // B += A * Matrix B - "vmul.s16 q5, q11, d1[3] \n" // G += A * Matrix G - "vmul.s16 q6, q11, d2[3] \n" // R += A * Matrix R - "vmul.s16 q7, q11, d3[3] \n" // A += A * Matrix A - "vqadd.s16 q12, q12, q4 \n" // Accumulate B - "vqadd.s16 q13, q13, q5 \n" // Accumulate G - "vqadd.s16 q14, q14, q6 \n" // Accumulate R - "vqadd.s16 q15, q15, q7 \n" // Accumulate A - "vqshrun.s16 d16, q12, #6 \n" // 16 bit to 8 bit B - "vqshrun.s16 d18, q13, #6 \n" // 16 bit to 8 bit G - "vqshrun.s16 d20, q14, #6 \n" // 16 bit to 8 bit R - "vqshrun.s16 d22, q15, #6 \n" // 16 bit to 8 bit A - MEMACCESS(1) - "vst4.8 {d16, d18, d20, d22}, [%1]! \n" // store 8 ARGB pixels. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(matrix_argb) // %3 - : "cc", "memory", "q0", "q1", "q2", "q4", "q5", "q6", "q7", "q8", "q9", - "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -// Multiply 2 rows of ARGB pixels together, 8 pixels at a time. -void ARGBMultiplyRow_NEON(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(1) - "vld4.8 {d1, d3, d5, d7}, [%1]! \n" // load 8 more ARGB pixels. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vmull.u8 q0, d0, d1 \n" // multiply B - "vmull.u8 q1, d2, d3 \n" // multiply G - "vmull.u8 q2, d4, d5 \n" // multiply R - "vmull.u8 q3, d6, d7 \n" // multiply A - "vrshrn.u16 d0, q0, #8 \n" // 16 bit to 8 bit B - "vrshrn.u16 d1, q1, #8 \n" // 16 bit to 8 bit G - "vrshrn.u16 d2, q2, #8 \n" // 16 bit to 8 bit R - "vrshrn.u16 d3, q3, #8 \n" // 16 bit to 8 bit A - MEMACCESS(2) - "vst4.8 {d0, d1, d2, d3}, [%2]! \n" // store 8 ARGB pixels. - "bgt 1b \n" - - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1", "q2", "q3" - ); -} - -// Add 2 rows of ARGB pixels together, 8 pixels at a time. -void ARGBAddRow_NEON(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(1) - "vld4.8 {d4, d5, d6, d7}, [%1]! \n" // load 8 more ARGB pixels. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vqadd.u8 q0, q0, q2 \n" // add B, G - "vqadd.u8 q1, q1, q3 \n" // add R, A - MEMACCESS(2) - "vst4.8 {d0, d1, d2, d3}, [%2]! \n" // store 8 ARGB pixels. - "bgt 1b \n" - - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1", "q2", "q3" - ); -} - -// Subtract 2 rows of ARGB pixels, 8 pixels at a time. -void ARGBSubtractRow_NEON(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(1) - "vld4.8 {d4, d5, d6, d7}, [%1]! \n" // load 8 more ARGB pixels. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vqsub.u8 q0, q0, q2 \n" // subtract B, G - "vqsub.u8 q1, q1, q3 \n" // subtract R, A - MEMACCESS(2) - "vst4.8 {d0, d1, d2, d3}, [%2]! \n" // store 8 ARGB pixels. - "bgt 1b \n" - - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1", "q2", "q3" - ); -} - -// Adds Sobel X and Sobel Y and stores Sobel into ARGB. -// A = 255 -// R = Sobel -// G = Sobel -// B = Sobel -void SobelRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - asm volatile ( - "vmov.u8 d3, #255 \n" // alpha - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "vld1.8 {d0}, [%0]! \n" // load 8 sobelx. - MEMACCESS(1) - "vld1.8 {d1}, [%1]! \n" // load 8 sobely. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vqadd.u8 d0, d0, d1 \n" // add - "vmov.u8 d1, d0 \n" - "vmov.u8 d2, d0 \n" - MEMACCESS(2) - "vst4.8 {d0, d1, d2, d3}, [%2]! \n" // store 8 ARGB pixels. - "bgt 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1" - ); -} - -// Adds Sobel X and Sobel Y and stores Sobel into plane. -void SobelToPlaneRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width) { - asm volatile ( - // 16 pixel loop. - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load 16 sobelx. - MEMACCESS(1) - "vld1.8 {q1}, [%1]! \n" // load 16 sobely. - "subs %3, %3, #16 \n" // 16 processed per loop. - "vqadd.u8 q0, q0, q1 \n" // add - MEMACCESS(2) - "vst1.8 {q0}, [%2]! \n" // store 16 pixels. - "bgt 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_y), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1" - ); -} - -// Mixes Sobel X, Sobel Y and Sobel into ARGB. -// A = 255 -// R = Sobel X -// G = Sobel -// B = Sobel Y -void SobelXYRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - asm volatile ( - "vmov.u8 d3, #255 \n" // alpha - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "vld1.8 {d2}, [%0]! \n" // load 8 sobelx. - MEMACCESS(1) - "vld1.8 {d0}, [%1]! \n" // load 8 sobely. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vqadd.u8 d1, d0, d2 \n" // add - MEMACCESS(2) - "vst4.8 {d0, d1, d2, d3}, [%2]! \n" // store 8 ARGB pixels. - "bgt 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1" - ); -} - -// SobelX as a matrix is -// -1 0 1 -// -2 0 2 -// -1 0 1 -void SobelXRow_NEON(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld1.8 {d0}, [%0],%5 \n" // top - MEMACCESS(0) - "vld1.8 {d1}, [%0],%6 \n" - "vsubl.u8 q0, d0, d1 \n" - MEMACCESS(1) - "vld1.8 {d2}, [%1],%5 \n" // center * 2 - MEMACCESS(1) - "vld1.8 {d3}, [%1],%6 \n" - "vsubl.u8 q1, d2, d3 \n" - "vadd.s16 q0, q0, q1 \n" - "vadd.s16 q0, q0, q1 \n" - MEMACCESS(2) - "vld1.8 {d2}, [%2],%5 \n" // bottom - MEMACCESS(2) - "vld1.8 {d3}, [%2],%6 \n" - "subs %4, %4, #8 \n" // 8 pixels - "vsubl.u8 q1, d2, d3 \n" - "vadd.s16 q0, q0, q1 \n" - "vabs.s16 q0, q0 \n" - "vqmovn.u16 d0, q0 \n" - MEMACCESS(3) - "vst1.8 {d0}, [%3]! \n" // store 8 sobelx - "bgt 1b \n" - : "+r"(src_y0), // %0 - "+r"(src_y1), // %1 - "+r"(src_y2), // %2 - "+r"(dst_sobelx), // %3 - "+r"(width) // %4 - : "r"(2), // %5 - "r"(6) // %6 - : "cc", "memory", "q0", "q1" // Clobber List - ); -} - -// SobelY as a matrix is -// -1 -2 -1 -// 0 0 0 -// 1 2 1 -void SobelYRow_NEON(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld1.8 {d0}, [%0],%4 \n" // left - MEMACCESS(1) - "vld1.8 {d1}, [%1],%4 \n" - "vsubl.u8 q0, d0, d1 \n" - MEMACCESS(0) - "vld1.8 {d2}, [%0],%4 \n" // center * 2 - MEMACCESS(1) - "vld1.8 {d3}, [%1],%4 \n" - "vsubl.u8 q1, d2, d3 \n" - "vadd.s16 q0, q0, q1 \n" - "vadd.s16 q0, q0, q1 \n" - MEMACCESS(0) - "vld1.8 {d2}, [%0],%5 \n" // right - MEMACCESS(1) - "vld1.8 {d3}, [%1],%5 \n" - "subs %3, %3, #8 \n" // 8 pixels - "vsubl.u8 q1, d2, d3 \n" - "vadd.s16 q0, q0, q1 \n" - "vabs.s16 q0, q0 \n" - "vqmovn.u16 d0, q0 \n" - MEMACCESS(2) - "vst1.8 {d0}, [%2]! \n" // store 8 sobely - "bgt 1b \n" - : "+r"(src_y0), // %0 - "+r"(src_y1), // %1 - "+r"(dst_sobely), // %2 - "+r"(width) // %3 - : "r"(1), // %4 - "r"(6) // %5 - : "cc", "memory", "q0", "q1" // Clobber List - ); -} -#endif // defined(__ARM_NEON__) && !defined(__aarch64__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/row_neon64.cc b/telegramgallery/src/main/cpp/libyuv/source/row_neon64.cc deleted file mode 100644 index 6375d4f..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/row_neon64.cc +++ /dev/null @@ -1,2809 +0,0 @@ -/* - * Copyright 2014 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC Neon armv8 64 bit. -#if !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) - -// Read 8 Y, 4 U and 4 V from 422 -#define READYUV422 \ - MEMACCESS(0) \ - "ld1 {v0.8b}, [%0], #8 \n" \ - MEMACCESS(1) \ - "ld1 {v1.s}[0], [%1], #4 \n" \ - MEMACCESS(2) \ - "ld1 {v1.s}[1], [%2], #4 \n" - -// Read 8 Y, 2 U and 2 V from 422 -#define READYUV411 \ - MEMACCESS(0) \ - "ld1 {v0.8b}, [%0], #8 \n" \ - MEMACCESS(1) \ - "ld1 {v2.h}[0], [%1], #2 \n" \ - MEMACCESS(2) \ - "ld1 {v2.h}[1], [%2], #2 \n" \ - "zip1 v1.8b, v2.8b, v2.8b \n" - -// Read 8 Y, 8 U and 8 V from 444 -#define READYUV444 \ - MEMACCESS(0) \ - "ld1 {v0.8b}, [%0], #8 \n" \ - MEMACCESS(1) \ - "ld1 {v1.d}[0], [%1], #8 \n" \ - MEMACCESS(2) \ - "ld1 {v1.d}[1], [%2], #8 \n" \ - "uaddlp v1.8h, v1.16b \n" \ - "rshrn v1.8b, v1.8h, #1 \n" - -// Read 8 Y, and set 4 U and 4 V to 128 -#define READYUV400 \ - MEMACCESS(0) \ - "ld1 {v0.8b}, [%0], #8 \n" \ - "movi v1.8b , #128 \n" - -// Read 8 Y and 4 UV from NV12 -#define READNV12 \ - MEMACCESS(0) \ - "ld1 {v0.8b}, [%0], #8 \n" \ - MEMACCESS(1) \ - "ld1 {v2.8b}, [%1], #8 \n" \ - "uzp1 v1.8b, v2.8b, v2.8b \n" \ - "uzp2 v3.8b, v2.8b, v2.8b \n" \ - "ins v1.s[1], v3.s[0] \n" - -// Read 8 Y and 4 VU from NV21 -#define READNV21 \ - MEMACCESS(0) \ - "ld1 {v0.8b}, [%0], #8 \n" \ - MEMACCESS(1) \ - "ld1 {v2.8b}, [%1], #8 \n" \ - "uzp1 v3.8b, v2.8b, v2.8b \n" \ - "uzp2 v1.8b, v2.8b, v2.8b \n" \ - "ins v1.s[1], v3.s[0] \n" - -// Read 8 YUY2 -#define READYUY2 \ - MEMACCESS(0) \ - "ld2 {v0.8b, v1.8b}, [%0], #16 \n" \ - "uzp2 v3.8b, v1.8b, v1.8b \n" \ - "uzp1 v1.8b, v1.8b, v1.8b \n" \ - "ins v1.s[1], v3.s[0] \n" - -// Read 8 UYVY -#define READUYVY \ - MEMACCESS(0) \ - "ld2 {v2.8b, v3.8b}, [%0], #16 \n" \ - "orr v0.8b, v3.8b, v3.8b \n" \ - "uzp1 v1.8b, v2.8b, v2.8b \n" \ - "uzp2 v3.8b, v2.8b, v2.8b \n" \ - "ins v1.s[1], v3.s[0] \n" - -#define YUVTORGB_SETUP \ - "ld1r {v24.8h}, [%[kUVBiasBGR]], #2 \n" \ - "ld1r {v25.8h}, [%[kUVBiasBGR]], #2 \n" \ - "ld1r {v26.8h}, [%[kUVBiasBGR]] \n" \ - "ld1r {v31.4s}, [%[kYToRgb]] \n" \ - "ld2 {v27.8h, v28.8h}, [%[kUVToRB]] \n" \ - "ld2 {v29.8h, v30.8h}, [%[kUVToG]] \n" - -#define YUVTORGB(vR, vG, vB) \ - "uxtl v0.8h, v0.8b \n" /* Extract Y */ \ - "shll v2.8h, v1.8b, #8 \n" /* Replicate UV */ \ - "ushll2 v3.4s, v0.8h, #0 \n" /* Y */ \ - "ushll v0.4s, v0.4h, #0 \n" \ - "mul v3.4s, v3.4s, v31.4s \n" \ - "mul v0.4s, v0.4s, v31.4s \n" \ - "sqshrun v0.4h, v0.4s, #16 \n" \ - "sqshrun2 v0.8h, v3.4s, #16 \n" /* Y */ \ - "uaddw v1.8h, v2.8h, v1.8b \n" /* Replicate UV */ \ - "mov v2.d[0], v1.d[1] \n" /* Extract V */ \ - "uxtl v2.8h, v2.8b \n" \ - "uxtl v1.8h, v1.8b \n" /* Extract U */ \ - "mul v3.8h, v1.8h, v27.8h \n" \ - "mul v5.8h, v1.8h, v29.8h \n" \ - "mul v6.8h, v2.8h, v30.8h \n" \ - "mul v7.8h, v2.8h, v28.8h \n" \ - "sqadd v6.8h, v6.8h, v5.8h \n" \ - "sqadd " #vB ".8h, v24.8h, v0.8h \n" /* B */ \ - "sqadd " #vG ".8h, v25.8h, v0.8h \n" /* G */ \ - "sqadd " #vR ".8h, v26.8h, v0.8h \n" /* R */ \ - "sqadd " #vB ".8h, " #vB ".8h, v3.8h \n" /* B */ \ - "sqsub " #vG ".8h, " #vG ".8h, v6.8h \n" /* G */ \ - "sqadd " #vR ".8h, " #vR ".8h, v7.8h \n" /* R */ \ - "sqshrun " #vB ".8b, " #vB ".8h, #6 \n" /* B */ \ - "sqshrun " #vG ".8b, " #vG ".8h, #6 \n" /* G */ \ - "sqshrun " #vR ".8b, " #vR ".8h, #6 \n" /* R */ \ - -void I444ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" /* A */ - "1: \n" - READYUV444 - YUVTORGB(v22, v21, v20) - "subs %w4, %w4, #8 \n" - MEMACCESS(3) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%3], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void I422ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" /* A */ - "1: \n" - READYUV422 - YUVTORGB(v22, v21, v20) - "subs %w4, %w4, #8 \n" - MEMACCESS(3) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%3], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void I422AlphaToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - const uint8* src_a, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READYUV422 - YUVTORGB(v22, v21, v20) - MEMACCESS(3) - "ld1 {v23.8b}, [%3], #8 \n" - "subs %w5, %w5, #8 \n" - MEMACCESS(4) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%4], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(src_a), // %3 - "+r"(dst_argb), // %4 - "+r"(width) // %5 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void I411ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" /* A */ - "1: \n" - READYUV411 - YUVTORGB(v22, v21, v20) - "subs %w4, %w4, #8 \n" - MEMACCESS(3) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%3], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void I422ToRGBARow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v20.8b, #255 \n" /* A */ - "1: \n" - READYUV422 - YUVTORGB(v23, v22, v21) - "subs %w4, %w4, #8 \n" - MEMACCESS(3) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%3], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_rgba), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void I422ToRGB24Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READYUV422 - YUVTORGB(v22, v21, v20) - "subs %w4, %w4, #8 \n" - MEMACCESS(3) - "st3 {v20.8b,v21.8b,v22.8b}, [%3], #24 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_rgb24), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -#define ARGBTORGB565 \ - "shll v0.8h, v22.8b, #8 \n" /* R */ \ - "shll v21.8h, v21.8b, #8 \n" /* G */ \ - "shll v20.8h, v20.8b, #8 \n" /* B */ \ - "sri v0.8h, v21.8h, #5 \n" /* RG */ \ - "sri v0.8h, v20.8h, #11 \n" /* RGB */ - -void I422ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READYUV422 - YUVTORGB(v22, v21, v20) - "subs %w4, %w4, #8 \n" - ARGBTORGB565 - MEMACCESS(3) - "st1 {v0.8h}, [%3], #16 \n" // store 8 pixels RGB565. - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_rgb565), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -#define ARGBTOARGB1555 \ - "shll v0.8h, v23.8b, #8 \n" /* A */ \ - "shll v22.8h, v22.8b, #8 \n" /* R */ \ - "shll v21.8h, v21.8b, #8 \n" /* G */ \ - "shll v20.8h, v20.8b, #8 \n" /* B */ \ - "sri v0.8h, v22.8h, #1 \n" /* AR */ \ - "sri v0.8h, v21.8h, #6 \n" /* ARG */ \ - "sri v0.8h, v20.8h, #11 \n" /* ARGB */ - -void I422ToARGB1555Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb1555, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" - "1: \n" - READYUV422 - YUVTORGB(v22, v21, v20) - "subs %w4, %w4, #8 \n" - ARGBTOARGB1555 - MEMACCESS(3) - "st1 {v0.8h}, [%3], #16 \n" // store 8 pixels RGB565. - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb1555), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -#define ARGBTOARGB4444 \ - /* Input v20.8b<=B, v21.8b<=G, v22.8b<=R, v23.8b<=A, v4.8b<=0x0f */ \ - "ushr v20.8b, v20.8b, #4 \n" /* B */ \ - "bic v21.8b, v21.8b, v4.8b \n" /* G */ \ - "ushr v22.8b, v22.8b, #4 \n" /* R */ \ - "bic v23.8b, v23.8b, v4.8b \n" /* A */ \ - "orr v0.8b, v20.8b, v21.8b \n" /* BG */ \ - "orr v1.8b, v22.8b, v23.8b \n" /* RA */ \ - "zip1 v0.16b, v0.16b, v1.16b \n" /* BGRA */ - -void I422ToARGB4444Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v4.16b, #0x0f \n" // bits to clear with vbic. - "1: \n" - READYUV422 - YUVTORGB(v22, v21, v20) - "subs %w4, %w4, #8 \n" - "movi v23.8b, #255 \n" - ARGBTOARGB4444 - MEMACCESS(3) - "st1 {v0.8h}, [%3], #16 \n" // store 8 pixels ARGB4444. - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_argb4444), // %3 - "+r"(width) // %4 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void I400ToARGBRow_NEON(const uint8* src_y, - uint8* dst_argb, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" - "1: \n" - READYUV400 - YUVTORGB(v22, v21, v20) - "subs %w2, %w2, #8 \n" - MEMACCESS(1) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%1], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : [kUVToRB]"r"(&kYuvI601Constants.kUVToRB), - [kUVToG]"r"(&kYuvI601Constants.kUVToG), - [kUVBiasBGR]"r"(&kYuvI601Constants.kUVBiasBGR), - [kYToRgb]"r"(&kYuvI601Constants.kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void J400ToARGBRow_NEON(const uint8* src_y, - uint8* dst_argb, - int width) { - asm volatile ( - "movi v23.8b, #255 \n" - "1: \n" - MEMACCESS(0) - "ld1 {v20.8b}, [%0], #8 \n" - "orr v21.8b, v20.8b, v20.8b \n" - "orr v22.8b, v20.8b, v20.8b \n" - "subs %w2, %w2, #8 \n" - MEMACCESS(1) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%1], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v20", "v21", "v22", "v23" - ); -} - -void NV12ToARGBRow_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" - "1: \n" - READNV12 - YUVTORGB(v22, v21, v20) - "subs %w3, %w3, #8 \n" - MEMACCESS(2) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%2], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_uv), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void NV21ToARGBRow_NEON(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" - "1: \n" - READNV21 - YUVTORGB(v22, v21, v20) - "subs %w3, %w3, #8 \n" - MEMACCESS(2) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%2], #32 \n" - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_vu), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void NV12ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "1: \n" - READNV12 - YUVTORGB(v22, v21, v20) - "subs %w3, %w3, #8 \n" - ARGBTORGB565 - MEMACCESS(2) - "st1 {v0.8h}, [%2], 16 \n" // store 8 pixels RGB565. - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_uv), // %1 - "+r"(dst_rgb565), // %2 - "+r"(width) // %3 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void YUY2ToARGBRow_NEON(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" - "1: \n" - READYUY2 - YUVTORGB(v22, v21, v20) - "subs %w2, %w2, #8 \n" - MEMACCESS(1) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%1], #32 \n" - "b.gt 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -void UYVYToARGBRow_NEON(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - asm volatile ( - YUVTORGB_SETUP - "movi v23.8b, #255 \n" - "1: \n" - READUYVY - YUVTORGB(v22, v21, v20) - "subs %w2, %w2, #8 \n" - MEMACCESS(1) - "st4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%1], 32 \n" - "b.gt 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : [kUVToRB]"r"(&yuvconstants->kUVToRB), - [kUVToG]"r"(&yuvconstants->kUVToG), - [kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR), - [kYToRgb]"r"(&yuvconstants->kYToRgb) - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", - "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30" - ); -} - -// Reads 16 pairs of UV and write even values to dst_u and odd to dst_v. -void SplitUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld2 {v0.16b,v1.16b}, [%0], #32 \n" // load 16 pairs of UV - "subs %w3, %w3, #16 \n" // 16 processed per loop - MEMACCESS(1) - "st1 {v0.16b}, [%1], #16 \n" // store U - MEMACCESS(2) - "st1 {v1.16b}, [%2], #16 \n" // store V - "b.gt 1b \n" - : "+r"(src_uv), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 // Output registers - : // Input registers - : "cc", "memory", "v0", "v1" // Clobber List - ); -} - -// Reads 16 U's and V's and writes out 16 pairs of UV. -void MergeUVRow_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load U - MEMACCESS(1) - "ld1 {v1.16b}, [%1], #16 \n" // load V - "subs %w3, %w3, #16 \n" // 16 processed per loop - MEMACCESS(2) - "st2 {v0.16b,v1.16b}, [%2], #32 \n" // store 16 pairs of UV - "b.gt 1b \n" - : - "+r"(src_u), // %0 - "+r"(src_v), // %1 - "+r"(dst_uv), // %2 - "+r"(width) // %3 // Output registers - : // Input registers - : "cc", "memory", "v0", "v1" // Clobber List - ); -} - -// Copy multiple of 32. vld4.8 allow unaligned and is fastest on a15. -void CopyRow_NEON(const uint8* src, uint8* dst, int count) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld1 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 32 - "subs %w2, %w2, #32 \n" // 32 processed per loop - MEMACCESS(1) - "st1 {v0.8b,v1.8b,v2.8b,v3.8b}, [%1], #32 \n" // store 32 - "b.gt 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(count) // %2 // Output registers - : // Input registers - : "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -// SetRow writes 'count' bytes using an 8 bit value repeated. -void SetRow_NEON(uint8* dst, uint8 v8, int count) { - asm volatile ( - "dup v0.16b, %w2 \n" // duplicate 16 bytes - "1: \n" - "subs %w1, %w1, #16 \n" // 16 bytes per loop - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" // store - "b.gt 1b \n" - : "+r"(dst), // %0 - "+r"(count) // %1 - : "r"(v8) // %2 - : "cc", "memory", "v0" - ); -} - -void ARGBSetRow_NEON(uint8* dst, uint32 v32, int count) { - asm volatile ( - "dup v0.4s, %w2 \n" // duplicate 4 ints - "1: \n" - "subs %w1, %w1, #4 \n" // 4 ints per loop - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" // store - "b.gt 1b \n" - : "+r"(dst), // %0 - "+r"(count) // %1 - : "r"(v32) // %2 - : "cc", "memory", "v0" - ); -} - -void MirrorRow_NEON(const uint8* src, uint8* dst, int width) { - asm volatile ( - // Start at end of source row. - "add %0, %0, %w2, sxtw \n" - "sub %0, %0, #16 \n" - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], %3 \n" // src -= 16 - "subs %w2, %w2, #16 \n" // 16 pixels per loop. - "rev64 v0.16b, v0.16b \n" - MEMACCESS(1) - "st1 {v0.D}[1], [%1], #8 \n" // dst += 16 - MEMACCESS(1) - "st1 {v0.D}[0], [%1], #8 \n" - "b.gt 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : "r"((ptrdiff_t)-16) // %3 - : "cc", "memory", "v0" - ); -} - -void MirrorUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - // Start at end of source row. - "add %0, %0, %w3, sxtw #1 \n" - "sub %0, %0, #16 \n" - "1: \n" - MEMACCESS(0) - "ld2 {v0.8b, v1.8b}, [%0], %4 \n" // src -= 16 - "subs %w3, %w3, #8 \n" // 8 pixels per loop. - "rev64 v0.8b, v0.8b \n" - "rev64 v1.8b, v1.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // dst += 8 - MEMACCESS(2) - "st1 {v1.8b}, [%2], #8 \n" - "b.gt 1b \n" - : "+r"(src_uv), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : "r"((ptrdiff_t)-16) // %4 - : "cc", "memory", "v0", "v1" - ); -} - -void ARGBMirrorRow_NEON(const uint8* src, uint8* dst, int width) { - asm volatile ( - // Start at end of source row. - "add %0, %0, %w2, sxtw #2 \n" - "sub %0, %0, #16 \n" - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], %3 \n" // src -= 16 - "subs %w2, %w2, #4 \n" // 4 pixels per loop. - "rev64 v0.4s, v0.4s \n" - MEMACCESS(1) - "st1 {v0.D}[1], [%1], #8 \n" // dst += 16 - MEMACCESS(1) - "st1 {v0.D}[0], [%1], #8 \n" - "b.gt 1b \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width) // %2 - : "r"((ptrdiff_t)-16) // %3 - : "cc", "memory", "v0" - ); -} - -void RGB24ToARGBRow_NEON(const uint8* src_rgb24, uint8* dst_argb, int width) { - asm volatile ( - "movi v4.8b, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "ld3 {v1.8b,v2.8b,v3.8b}, [%0], #24 \n" // load 8 pixels of RGB24. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - MEMACCESS(1) - "st4 {v1.8b,v2.8b,v3.8b,v4.8b}, [%1], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(src_rgb24), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v1", "v2", "v3", "v4" // Clobber List - ); -} - -void RAWToARGBRow_NEON(const uint8* src_raw, uint8* dst_argb, int width) { - asm volatile ( - "movi v5.8b, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "ld3 {v0.8b,v1.8b,v2.8b}, [%0], #24 \n" // read r g b - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "orr v3.8b, v1.8b, v1.8b \n" // move g - "orr v4.8b, v0.8b, v0.8b \n" // move r - MEMACCESS(1) - "st4 {v2.8b,v3.8b,v4.8b,v5.8b}, [%1], #32 \n" // store b g r a - "b.gt 1b \n" - : "+r"(src_raw), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5" // Clobber List - ); -} - -void RAWToRGB24Row_NEON(const uint8* src_raw, uint8* dst_rgb24, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld3 {v0.8b,v1.8b,v2.8b}, [%0], #24 \n" // read r g b - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "orr v3.8b, v1.8b, v1.8b \n" // move g - "orr v4.8b, v0.8b, v0.8b \n" // move r - MEMACCESS(1) - "st3 {v2.8b,v3.8b,v4.8b}, [%1], #24 \n" // store b g r - "b.gt 1b \n" - : "+r"(src_raw), // %0 - "+r"(dst_rgb24), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4" // Clobber List - ); -} - -#define RGB565TOARGB \ - "shrn v6.8b, v0.8h, #5 \n" /* G xxGGGGGG */ \ - "shl v6.8b, v6.8b, #2 \n" /* G GGGGGG00 upper 6 */ \ - "ushr v4.8b, v6.8b, #6 \n" /* G 000000GG lower 2 */ \ - "orr v1.8b, v4.8b, v6.8b \n" /* G */ \ - "xtn v2.8b, v0.8h \n" /* B xxxBBBBB */ \ - "ushr v0.8h, v0.8h, #11 \n" /* R 000RRRRR */ \ - "xtn2 v2.16b,v0.8h \n" /* R in upper part */ \ - "shl v2.16b, v2.16b, #3 \n" /* R,B BBBBB000 upper 5 */ \ - "ushr v0.16b, v2.16b, #5 \n" /* R,B 00000BBB lower 3 */ \ - "orr v0.16b, v0.16b, v2.16b \n" /* R,B */ \ - "dup v2.2D, v0.D[1] \n" /* R */ - -void RGB565ToARGBRow_NEON(const uint8* src_rgb565, uint8* dst_argb, int width) { - asm volatile ( - "movi v3.8b, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 RGB565 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - RGB565TOARGB - MEMACCESS(1) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%1], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(src_rgb565), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v6" // Clobber List - ); -} - -#define ARGB1555TOARGB \ - "ushr v2.8h, v0.8h, #10 \n" /* R xxxRRRRR */ \ - "shl v2.8h, v2.8h, #3 \n" /* R RRRRR000 upper 5 */ \ - "xtn v3.8b, v2.8h \n" /* RRRRR000 AAAAAAAA */ \ - \ - "sshr v2.8h, v0.8h, #15 \n" /* A AAAAAAAA */ \ - "xtn2 v3.16b, v2.8h \n" \ - \ - "xtn v2.8b, v0.8h \n" /* B xxxBBBBB */ \ - "shrn2 v2.16b,v0.8h, #5 \n" /* G xxxGGGGG */ \ - \ - "ushr v1.16b, v3.16b, #5 \n" /* R,A 00000RRR lower 3 */ \ - "shl v0.16b, v2.16b, #3 \n" /* B,G BBBBB000 upper 5 */ \ - "ushr v2.16b, v0.16b, #5 \n" /* B,G 00000BBB lower 3 */ \ - \ - "orr v0.16b, v0.16b, v2.16b \n" /* B,G */ \ - "orr v2.16b, v1.16b, v3.16b \n" /* R,A */ \ - "dup v1.2D, v0.D[1] \n" \ - "dup v3.2D, v2.D[1] \n" - -// RGB555TOARGB is same as ARGB1555TOARGB but ignores alpha. -#define RGB555TOARGB \ - "ushr v2.8h, v0.8h, #10 \n" /* R xxxRRRRR */ \ - "shl v2.8h, v2.8h, #3 \n" /* R RRRRR000 upper 5 */ \ - "xtn v3.8b, v2.8h \n" /* RRRRR000 */ \ - \ - "xtn v2.8b, v0.8h \n" /* B xxxBBBBB */ \ - "shrn2 v2.16b,v0.8h, #5 \n" /* G xxxGGGGG */ \ - \ - "ushr v1.16b, v3.16b, #5 \n" /* R 00000RRR lower 3 */ \ - "shl v0.16b, v2.16b, #3 \n" /* B,G BBBBB000 upper 5 */ \ - "ushr v2.16b, v0.16b, #5 \n" /* B,G 00000BBB lower 3 */ \ - \ - "orr v0.16b, v0.16b, v2.16b \n" /* B,G */ \ - "orr v2.16b, v1.16b, v3.16b \n" /* R */ \ - "dup v1.2D, v0.D[1] \n" /* G */ \ - -void ARGB1555ToARGBRow_NEON(const uint8* src_argb1555, uint8* dst_argb, - int width) { - asm volatile ( - "movi v3.8b, #255 \n" // Alpha - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 ARGB1555 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - ARGB1555TOARGB - MEMACCESS(1) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%1], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(src_argb1555), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -#define ARGB4444TOARGB \ - "shrn v1.8b, v0.8h, #8 \n" /* v1(l) AR */ \ - "xtn2 v1.16b, v0.8h \n" /* v1(h) GB */ \ - "shl v2.16b, v1.16b, #4 \n" /* B,R BBBB0000 */ \ - "ushr v3.16b, v1.16b, #4 \n" /* G,A 0000GGGG */ \ - "ushr v0.16b, v2.16b, #4 \n" /* B,R 0000BBBB */ \ - "shl v1.16b, v3.16b, #4 \n" /* G,A GGGG0000 */ \ - "orr v2.16b, v0.16b, v2.16b \n" /* B,R BBBBBBBB */ \ - "orr v3.16b, v1.16b, v3.16b \n" /* G,A GGGGGGGG */ \ - "dup v0.2D, v2.D[1] \n" \ - "dup v1.2D, v3.D[1] \n" - -void ARGB4444ToARGBRow_NEON(const uint8* src_argb4444, uint8* dst_argb, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 ARGB4444 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - ARGB4444TOARGB - MEMACCESS(1) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%1], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(src_argb4444), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4" // Clobber List - ); -} - -void ARGBToRGB24Row_NEON(const uint8* src_argb, uint8* dst_rgb24, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v1.8b,v2.8b,v3.8b,v4.8b}, [%0], #32 \n" // load 8 ARGB pixels - "subs %w2, %w2, #8 \n" // 8 processed per loop. - MEMACCESS(1) - "st3 {v1.8b,v2.8b,v3.8b}, [%1], #24 \n" // store 8 pixels of RGB24. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_rgb24), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v1", "v2", "v3", "v4" // Clobber List - ); -} - -void ARGBToRAWRow_NEON(const uint8* src_argb, uint8* dst_raw, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v1.8b,v2.8b,v3.8b,v4.8b}, [%0], #32 \n" // load b g r a - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "orr v4.8b, v2.8b, v2.8b \n" // mov g - "orr v5.8b, v1.8b, v1.8b \n" // mov b - MEMACCESS(1) - "st3 {v3.8b,v4.8b,v5.8b}, [%1], #24 \n" // store r g b - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_raw), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v1", "v2", "v3", "v4", "v5" // Clobber List - ); -} - -void YUY2ToYRow_NEON(const uint8* src_yuy2, uint8* dst_y, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld2 {v0.16b,v1.16b}, [%0], #32 \n" // load 16 pixels of YUY2. - "subs %w2, %w2, #16 \n" // 16 processed per loop. - MEMACCESS(1) - "st1 {v0.16b}, [%1], #16 \n" // store 16 pixels of Y. - "b.gt 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1" // Clobber List - ); -} - -void UYVYToYRow_NEON(const uint8* src_uyvy, uint8* dst_y, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld2 {v0.16b,v1.16b}, [%0], #32 \n" // load 16 pixels of UYVY. - "subs %w2, %w2, #16 \n" // 16 processed per loop. - MEMACCESS(1) - "st1 {v1.16b}, [%1], #16 \n" // store 16 pixels of Y. - "b.gt 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1" // Clobber List - ); -} - -void YUY2ToUV422Row_NEON(const uint8* src_yuy2, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 16 YUY2 pixels - "subs %w3, %w3, #16 \n" // 16 pixels = 8 UVs. - MEMACCESS(1) - "st1 {v1.8b}, [%1], #8 \n" // store 8 U. - MEMACCESS(2) - "st1 {v3.8b}, [%2], #8 \n" // store 8 V. - "b.gt 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -void UYVYToUV422Row_NEON(const uint8* src_uyvy, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 16 UYVY pixels - "subs %w3, %w3, #16 \n" // 16 pixels = 8 UVs. - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 U. - MEMACCESS(2) - "st1 {v2.8b}, [%2], #8 \n" // store 8 V. - "b.gt 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -void YUY2ToUVRow_NEON(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_yuy2b = src_yuy2 + stride_yuy2; - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 16 pixels - "subs %w4, %w4, #16 \n" // 16 pixels = 8 UVs. - MEMACCESS(1) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%1], #32 \n" // load next row - "urhadd v1.8b, v1.8b, v5.8b \n" // average rows of U - "urhadd v3.8b, v3.8b, v7.8b \n" // average rows of V - MEMACCESS(2) - "st1 {v1.8b}, [%2], #8 \n" // store 8 U. - MEMACCESS(3) - "st1 {v3.8b}, [%3], #8 \n" // store 8 V. - "b.gt 1b \n" - : "+r"(src_yuy2), // %0 - "+r"(src_yuy2b), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", - "v5", "v6", "v7" // Clobber List - ); -} - -void UYVYToUVRow_NEON(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_uyvyb = src_uyvy + stride_uyvy; - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 16 pixels - "subs %w4, %w4, #16 \n" // 16 pixels = 8 UVs. - MEMACCESS(1) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%1], #32 \n" // load next row - "urhadd v0.8b, v0.8b, v4.8b \n" // average rows of U - "urhadd v2.8b, v2.8b, v6.8b \n" // average rows of V - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 U. - MEMACCESS(3) - "st1 {v2.8b}, [%3], #8 \n" // store 8 V. - "b.gt 1b \n" - : "+r"(src_uyvy), // %0 - "+r"(src_uyvyb), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", - "v5", "v6", "v7" // Clobber List - ); -} - -// For BGRAToARGB, ABGRToARGB, RGBAToARGB, and ARGBToRGBA. -void ARGBShuffleRow_NEON(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - asm volatile ( - MEMACCESS(3) - "ld1 {v2.16b}, [%3] \n" // shuffler - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 4 pixels. - "subs %w2, %w2, #4 \n" // 4 processed per loop - "tbl v1.16b, {v0.16b}, v2.16b \n" // look up 4 pixels - MEMACCESS(1) - "st1 {v1.16b}, [%1], #16 \n" // store 4. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(shuffler) // %3 - : "cc", "memory", "v0", "v1", "v2" // Clobber List - ); -} - -void I422ToYUY2Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld2 {v0.8b, v1.8b}, [%0], #16 \n" // load 16 Ys - "orr v2.8b, v1.8b, v1.8b \n" - MEMACCESS(1) - "ld1 {v1.8b}, [%1], #8 \n" // load 8 Us - MEMACCESS(2) - "ld1 {v3.8b}, [%2], #8 \n" // load 8 Vs - "subs %w4, %w4, #16 \n" // 16 pixels - MEMACCESS(3) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%3], #32 \n" // Store 16 pixels. - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_yuy2), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3" - ); -} - -void I422ToUYVYRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld2 {v1.8b,v2.8b}, [%0], #16 \n" // load 16 Ys - "orr v3.8b, v2.8b, v2.8b \n" - MEMACCESS(1) - "ld1 {v0.8b}, [%1], #8 \n" // load 8 Us - MEMACCESS(2) - "ld1 {v2.8b}, [%2], #8 \n" // load 8 Vs - "subs %w4, %w4, #16 \n" // 16 pixels - MEMACCESS(3) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%3], #32 \n" // Store 16 pixels. - "b.gt 1b \n" - : "+r"(src_y), // %0 - "+r"(src_u), // %1 - "+r"(src_v), // %2 - "+r"(dst_uyvy), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3" - ); -} - -void ARGBToRGB565Row_NEON(const uint8* src_argb, uint8* dst_rgb565, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%0], #32 \n" // load 8 pixels - "subs %w2, %w2, #8 \n" // 8 processed per loop. - ARGBTORGB565 - MEMACCESS(1) - "st1 {v0.16b}, [%1], #16 \n" // store 8 pixels RGB565. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_rgb565), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v20", "v21", "v22", "v23" - ); -} - -void ARGBToRGB565DitherRow_NEON(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width) { - asm volatile ( - "dup v1.4s, %w2 \n" // dither4 - "1: \n" - MEMACCESS(1) - "ld4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%1], #32 \n" // load 8 pixels - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "uqadd v20.8b, v20.8b, v1.8b \n" - "uqadd v21.8b, v21.8b, v1.8b \n" - "uqadd v22.8b, v22.8b, v1.8b \n" - ARGBTORGB565 - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" // store 8 pixels RGB565. - "b.gt 1b \n" - : "+r"(dst_rgb) // %0 - : "r"(src_argb), // %1 - "r"(dither4), // %2 - "r"(width) // %3 - : "cc", "memory", "v0", "v1", "v20", "v21", "v22", "v23" - ); -} - -void ARGBToARGB1555Row_NEON(const uint8* src_argb, uint8* dst_argb1555, - int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%0], #32 \n" // load 8 pixels - "subs %w2, %w2, #8 \n" // 8 processed per loop. - ARGBTOARGB1555 - MEMACCESS(1) - "st1 {v0.16b}, [%1], #16 \n" // store 8 pixels ARGB1555. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb1555), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v20", "v21", "v22", "v23" - ); -} - -void ARGBToARGB4444Row_NEON(const uint8* src_argb, uint8* dst_argb4444, - int width) { - asm volatile ( - "movi v4.16b, #0x0f \n" // bits to clear with vbic. - "1: \n" - MEMACCESS(0) - "ld4 {v20.8b,v21.8b,v22.8b,v23.8b}, [%0], #32 \n" // load 8 pixels - "subs %w2, %w2, #8 \n" // 8 processed per loop. - ARGBTOARGB4444 - MEMACCESS(1) - "st1 {v0.16b}, [%1], #16 \n" // store 8 pixels ARGB4444. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb4444), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v4", "v20", "v21", "v22", "v23" - ); -} - -void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width) { - asm volatile ( - "movi v4.8b, #13 \n" // B * 0.1016 coefficient - "movi v5.8b, #65 \n" // G * 0.5078 coefficient - "movi v6.8b, #33 \n" // R * 0.2578 coefficient - "movi v7.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v3.8h, v0.8b, v4.8b \n" // B - "umlal v3.8h, v1.8b, v5.8b \n" // G - "umlal v3.8h, v2.8b, v6.8b \n" // R - "sqrshrun v0.8b, v3.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v7.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7" - ); -} - -void ARGBExtractAlphaRow_NEON(const uint8* src_argb, uint8* dst_a, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load row 16 pixels - "subs %w2, %w2, #16 \n" // 16 processed per loop - MEMACCESS(1) - "st1 {v3.16b}, [%1], #16 \n" // store 16 A's. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_a), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width) { - asm volatile ( - "movi v4.8b, #15 \n" // B * 0.11400 coefficient - "movi v5.8b, #75 \n" // G * 0.58700 coefficient - "movi v6.8b, #38 \n" // R * 0.29900 coefficient - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v3.8h, v0.8b, v4.8b \n" // B - "umlal v3.8h, v1.8b, v5.8b \n" // G - "umlal v3.8h, v2.8b, v6.8b \n" // R - "sqrshrun v0.8b, v3.8h, #7 \n" // 15 bit to 8 bit Y - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6" - ); -} - -// 8x1 pixels. -void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "movi v24.8b, #112 \n" // UB / VR 0.875 coefficient - "movi v25.8b, #74 \n" // UG -0.5781 coefficient - "movi v26.8b, #38 \n" // UR -0.2969 coefficient - "movi v27.8b, #18 \n" // VB -0.1406 coefficient - "movi v28.8b, #94 \n" // VG -0.7344 coefficient - "movi v29.16b,#0x80 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB pixels. - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "umull v4.8h, v0.8b, v24.8b \n" // B - "umlsl v4.8h, v1.8b, v25.8b \n" // G - "umlsl v4.8h, v2.8b, v26.8b \n" // R - "add v4.8h, v4.8h, v29.8h \n" // +128 -> unsigned - - "umull v3.8h, v2.8b, v24.8b \n" // R - "umlsl v3.8h, v1.8b, v28.8b \n" // G - "umlsl v3.8h, v0.8b, v27.8b \n" // B - "add v3.8h, v3.8h, v29.8h \n" // +128 -> unsigned - - "uqshrn v0.8b, v4.8h, #8 \n" // 16 bit to 8 bit U - "uqshrn v1.8b, v3.8h, #8 \n" // 16 bit to 8 bit V - - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels U. - MEMACCESS(2) - "st1 {v1.8b}, [%2], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", - "v24", "v25", "v26", "v27", "v28", "v29" - ); -} - -#define RGBTOUV_SETUP_REG \ - "movi v20.8h, #56, lsl #0 \n" /* UB/VR coefficient (0.875) / 2 */ \ - "movi v21.8h, #37, lsl #0 \n" /* UG coefficient (-0.5781) / 2 */ \ - "movi v22.8h, #19, lsl #0 \n" /* UR coefficient (-0.2969) / 2 */ \ - "movi v23.8h, #9, lsl #0 \n" /* VB coefficient (-0.1406) / 2 */ \ - "movi v24.8h, #47, lsl #0 \n" /* VG coefficient (-0.7344) / 2 */ \ - "movi v25.16b, #0x80 \n" /* 128.5 (0x8080 in 16-bit) */ - -// 32x1 pixels -> 8x1. width is number of argb pixels. e.g. 32. -void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load 16 pixels. - "uaddlp v0.8h, v0.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v2.16b \n" // R 16 bytes -> 8 shorts. - MEMACCESS(0) - "ld4 {v4.16b,v5.16b,v6.16b,v7.16b}, [%0], #64 \n" // load next 16. - "uaddlp v4.8h, v4.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v5.8h, v5.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v6.8h, v6.16b \n" // R 16 bytes -> 8 shorts. - - "addp v0.8h, v0.8h, v4.8h \n" // B 16 shorts -> 8 shorts. - "addp v1.8h, v1.8h, v5.8h \n" // G 16 shorts -> 8 shorts. - "addp v2.8h, v2.8h, v6.8h \n" // R 16 shorts -> 8 shorts. - - "urshr v0.8h, v0.8h, #1 \n" // 2x average - "urshr v1.8h, v1.8h, #1 \n" - "urshr v2.8h, v2.8h, #1 \n" - - "subs %w3, %w3, #32 \n" // 32 processed per loop. - "mul v3.8h, v0.8h, v20.8h \n" // B - "mls v3.8h, v1.8h, v21.8h \n" // G - "mls v3.8h, v2.8h, v22.8h \n" // R - "add v3.8h, v3.8h, v25.8h \n" // +128 -> unsigned - "mul v4.8h, v2.8h, v20.8h \n" // R - "mls v4.8h, v1.8h, v24.8h \n" // G - "mls v4.8h, v0.8h, v23.8h \n" // B - "add v4.8h, v4.8h, v25.8h \n" // +128 -> unsigned - "uqshrn v0.8b, v3.8h, #8 \n" // 16 bit to 8 bit U - "uqshrn v1.8b, v4.8h, #8 \n" // 16 bit to 8 bit V - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels U. - MEMACCESS(2) - "st1 {v1.8b}, [%2], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} - -// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16. -#define RGBTOUV(QB, QG, QR) \ - "mul v3.8h, " #QB ",v20.8h \n" /* B */ \ - "mul v4.8h, " #QR ",v20.8h \n" /* R */ \ - "mls v3.8h, " #QG ",v21.8h \n" /* G */ \ - "mls v4.8h, " #QG ",v24.8h \n" /* G */ \ - "mls v3.8h, " #QR ",v22.8h \n" /* R */ \ - "mls v4.8h, " #QB ",v23.8h \n" /* B */ \ - "add v3.8h, v3.8h, v25.8h \n" /* +128 -> unsigned */ \ - "add v4.8h, v4.8h, v25.8h \n" /* +128 -> unsigned */ \ - "uqshrn v0.8b, v3.8h, #8 \n" /* 16 bit to 8 bit U */ \ - "uqshrn v1.8b, v4.8h, #8 \n" /* 16 bit to 8 bit V */ - -// TODO(fbarchard): Consider vhadd vertical, then vpaddl horizontal, avoid shr. -// TODO(fbarchard): consider ptrdiff_t for all strides. - -void ARGBToUVRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_argb_1 = src_argb + src_stride_argb; - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load 16 pixels. - "uaddlp v0.8h, v0.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v2.16b \n" // R 16 bytes -> 8 shorts. - - MEMACCESS(1) - "ld4 {v4.16b,v5.16b,v6.16b,v7.16b}, [%1], #64 \n" // load next 16 - "uadalp v0.8h, v4.16b \n" // B 16 bytes -> 8 shorts. - "uadalp v1.8h, v5.16b \n" // G 16 bytes -> 8 shorts. - "uadalp v2.8h, v6.16b \n" // R 16 bytes -> 8 shorts. - - "urshr v0.8h, v0.8h, #1 \n" // 2x average - "urshr v1.8h, v1.8h, #1 \n" - "urshr v2.8h, v2.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 32 processed per loop. - RGBTOUV(v0.8h, v1.8h, v2.8h) - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_argb_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} - -// TODO(fbarchard): Subsample match C code. -void ARGBToUVJRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_argb_1 = src_argb + src_stride_argb; - asm volatile ( - "movi v20.8h, #63, lsl #0 \n" // UB/VR coeff (0.500) / 2 - "movi v21.8h, #42, lsl #0 \n" // UG coeff (-0.33126) / 2 - "movi v22.8h, #21, lsl #0 \n" // UR coeff (-0.16874) / 2 - "movi v23.8h, #10, lsl #0 \n" // VB coeff (-0.08131) / 2 - "movi v24.8h, #53, lsl #0 \n" // VG coeff (-0.41869) / 2 - "movi v25.16b, #0x80 \n" // 128.5 (0x8080 in 16-bit) - "1: \n" - MEMACCESS(0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load 16 pixels. - "uaddlp v0.8h, v0.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v2.16b \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "ld4 {v4.16b,v5.16b,v6.16b,v7.16b}, [%1], #64 \n" // load next 16 - "uadalp v0.8h, v4.16b \n" // B 16 bytes -> 8 shorts. - "uadalp v1.8h, v5.16b \n" // G 16 bytes -> 8 shorts. - "uadalp v2.8h, v6.16b \n" // R 16 bytes -> 8 shorts. - - "urshr v0.8h, v0.8h, #1 \n" // 2x average - "urshr v1.8h, v1.8h, #1 \n" - "urshr v2.8h, v2.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 32 processed per loop. - RGBTOUV(v0.8h, v1.8h, v2.8h) - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_argb_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} - -void BGRAToUVRow_NEON(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_bgra_1 = src_bgra + src_stride_bgra; - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load 16 pixels. - "uaddlp v0.8h, v3.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v3.8h, v2.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v1.16b \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "ld4 {v4.16b,v5.16b,v6.16b,v7.16b}, [%1], #64 \n" // load 16 more - "uadalp v0.8h, v7.16b \n" // B 16 bytes -> 8 shorts. - "uadalp v3.8h, v6.16b \n" // G 16 bytes -> 8 shorts. - "uadalp v2.8h, v5.16b \n" // R 16 bytes -> 8 shorts. - - "urshr v0.8h, v0.8h, #1 \n" // 2x average - "urshr v1.8h, v3.8h, #1 \n" - "urshr v2.8h, v2.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 32 processed per loop. - RGBTOUV(v0.8h, v1.8h, v2.8h) - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_bgra), // %0 - "+r"(src_bgra_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} - -void ABGRToUVRow_NEON(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_abgr_1 = src_abgr + src_stride_abgr; - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load 16 pixels. - "uaddlp v3.8h, v2.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v2.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v1.8h, v0.16b \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "ld4 {v4.16b,v5.16b,v6.16b,v7.16b}, [%1], #64 \n" // load 16 more. - "uadalp v3.8h, v6.16b \n" // B 16 bytes -> 8 shorts. - "uadalp v2.8h, v5.16b \n" // G 16 bytes -> 8 shorts. - "uadalp v1.8h, v4.16b \n" // R 16 bytes -> 8 shorts. - - "urshr v0.8h, v3.8h, #1 \n" // 2x average - "urshr v2.8h, v2.8h, #1 \n" - "urshr v1.8h, v1.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 32 processed per loop. - RGBTOUV(v0.8h, v2.8h, v1.8h) - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_abgr), // %0 - "+r"(src_abgr_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} - -void RGBAToUVRow_NEON(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_rgba_1 = src_rgba + src_stride_rgba; - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load 16 pixels. - "uaddlp v0.8h, v1.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v2.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v3.16b \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "ld4 {v4.16b,v5.16b,v6.16b,v7.16b}, [%1], #64 \n" // load 16 more. - "uadalp v0.8h, v5.16b \n" // B 16 bytes -> 8 shorts. - "uadalp v1.8h, v6.16b \n" // G 16 bytes -> 8 shorts. - "uadalp v2.8h, v7.16b \n" // R 16 bytes -> 8 shorts. - - "urshr v0.8h, v0.8h, #1 \n" // 2x average - "urshr v1.8h, v1.8h, #1 \n" - "urshr v2.8h, v2.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 32 processed per loop. - RGBTOUV(v0.8h, v1.8h, v2.8h) - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_rgba), // %0 - "+r"(src_rgba_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} - -void RGB24ToUVRow_NEON(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_rgb24_1 = src_rgb24 + src_stride_rgb24; - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld3 {v0.16b,v1.16b,v2.16b}, [%0], #48 \n" // load 16 pixels. - "uaddlp v0.8h, v0.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v2.16b \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "ld3 {v4.16b,v5.16b,v6.16b}, [%1], #48 \n" // load 16 more. - "uadalp v0.8h, v4.16b \n" // B 16 bytes -> 8 shorts. - "uadalp v1.8h, v5.16b \n" // G 16 bytes -> 8 shorts. - "uadalp v2.8h, v6.16b \n" // R 16 bytes -> 8 shorts. - - "urshr v0.8h, v0.8h, #1 \n" // 2x average - "urshr v1.8h, v1.8h, #1 \n" - "urshr v2.8h, v2.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 32 processed per loop. - RGBTOUV(v0.8h, v1.8h, v2.8h) - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_rgb24), // %0 - "+r"(src_rgb24_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} - -void RAWToUVRow_NEON(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_raw_1 = src_raw + src_stride_raw; - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld3 {v0.16b,v1.16b,v2.16b}, [%0], #48 \n" // load 8 RAW pixels. - "uaddlp v2.8h, v2.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v0.8h, v0.16b \n" // R 16 bytes -> 8 shorts. - MEMACCESS(1) - "ld3 {v4.16b,v5.16b,v6.16b}, [%1], #48 \n" // load 8 more RAW pixels - "uadalp v2.8h, v6.16b \n" // B 16 bytes -> 8 shorts. - "uadalp v1.8h, v5.16b \n" // G 16 bytes -> 8 shorts. - "uadalp v0.8h, v4.16b \n" // R 16 bytes -> 8 shorts. - - "urshr v2.8h, v2.8h, #1 \n" // 2x average - "urshr v1.8h, v1.8h, #1 \n" - "urshr v0.8h, v0.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 32 processed per loop. - RGBTOUV(v2.8h, v1.8h, v0.8h) - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_raw), // %0 - "+r"(src_raw_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} - -// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16. -void RGB565ToUVRow_NEON(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_rgb565_1 = src_rgb565 + src_stride_rgb565; - asm volatile ( - "movi v22.8h, #56, lsl #0 \n" // UB / VR coeff (0.875) / 2 - "movi v23.8h, #37, lsl #0 \n" // UG coeff (-0.5781) / 2 - "movi v24.8h, #19, lsl #0 \n" // UR coeff (-0.2969) / 2 - "movi v25.8h, #9 , lsl #0 \n" // VB coeff (-0.1406) / 2 - "movi v26.8h, #47, lsl #0 \n" // VG coeff (-0.7344) / 2 - "movi v27.16b, #0x80 \n" // 128.5 (0x8080 in 16-bit) - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 RGB565 pixels. - RGB565TOARGB - "uaddlp v16.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uaddlp v18.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uaddlp v20.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // next 8 RGB565 pixels. - RGB565TOARGB - "uaddlp v17.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uaddlp v19.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uaddlp v21.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" // load 8 RGB565 pixels. - RGB565TOARGB - "uadalp v16.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uadalp v18.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uadalp v20.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" // next 8 RGB565 pixels. - RGB565TOARGB - "uadalp v17.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uadalp v19.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uadalp v21.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - - "ins v16.D[1], v17.D[0] \n" - "ins v18.D[1], v19.D[0] \n" - "ins v20.D[1], v21.D[0] \n" - - "urshr v4.8h, v16.8h, #1 \n" // 2x average - "urshr v5.8h, v18.8h, #1 \n" - "urshr v6.8h, v20.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 16 processed per loop. - "mul v16.8h, v4.8h, v22.8h \n" // B - "mls v16.8h, v5.8h, v23.8h \n" // G - "mls v16.8h, v6.8h, v24.8h \n" // R - "add v16.8h, v16.8h, v27.8h \n" // +128 -> unsigned - "mul v17.8h, v6.8h, v22.8h \n" // R - "mls v17.8h, v5.8h, v26.8h \n" // G - "mls v17.8h, v4.8h, v25.8h \n" // B - "add v17.8h, v17.8h, v27.8h \n" // +128 -> unsigned - "uqshrn v0.8b, v16.8h, #8 \n" // 16 bit to 8 bit U - "uqshrn v1.8b, v17.8h, #8 \n" // 16 bit to 8 bit V - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_rgb565), // %0 - "+r"(src_rgb565_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", "v24", - "v25", "v26", "v27" - ); -} - -// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16. -void ARGB1555ToUVRow_NEON(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_argb1555_1 = src_argb1555 + src_stride_argb1555; - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 ARGB1555 pixels. - RGB555TOARGB - "uaddlp v16.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uaddlp v17.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uaddlp v18.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // next 8 ARGB1555 pixels. - RGB555TOARGB - "uaddlp v26.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uaddlp v27.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uaddlp v28.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" // load 8 ARGB1555 pixels. - RGB555TOARGB - "uadalp v16.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uadalp v17.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uadalp v18.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" // next 8 ARGB1555 pixels. - RGB555TOARGB - "uadalp v26.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uadalp v27.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uadalp v28.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - - "ins v16.D[1], v26.D[0] \n" - "ins v17.D[1], v27.D[0] \n" - "ins v18.D[1], v28.D[0] \n" - - "urshr v4.8h, v16.8h, #1 \n" // 2x average - "urshr v5.8h, v17.8h, #1 \n" - "urshr v6.8h, v18.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 16 processed per loop. - "mul v2.8h, v4.8h, v20.8h \n" // B - "mls v2.8h, v5.8h, v21.8h \n" // G - "mls v2.8h, v6.8h, v22.8h \n" // R - "add v2.8h, v2.8h, v25.8h \n" // +128 -> unsigned - "mul v3.8h, v6.8h, v20.8h \n" // R - "mls v3.8h, v5.8h, v24.8h \n" // G - "mls v3.8h, v4.8h, v23.8h \n" // B - "add v3.8h, v3.8h, v25.8h \n" // +128 -> unsigned - "uqshrn v0.8b, v2.8h, #8 \n" // 16 bit to 8 bit U - "uqshrn v1.8b, v3.8h, #8 \n" // 16 bit to 8 bit V - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_argb1555), // %0 - "+r"(src_argb1555_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", - "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", - "v26", "v27", "v28" - ); -} - -// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16. -void ARGB4444ToUVRow_NEON(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width) { - const uint8* src_argb4444_1 = src_argb4444 + src_stride_argb4444; - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 ARGB4444 pixels. - ARGB4444TOARGB - "uaddlp v16.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uaddlp v17.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uaddlp v18.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // next 8 ARGB4444 pixels. - ARGB4444TOARGB - "uaddlp v26.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uaddlp v27.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uaddlp v28.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" // load 8 ARGB4444 pixels. - ARGB4444TOARGB - "uadalp v16.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uadalp v17.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uadalp v18.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" // next 8 ARGB4444 pixels. - ARGB4444TOARGB - "uadalp v26.4h, v0.8b \n" // B 8 bytes -> 4 shorts. - "uadalp v27.4h, v1.8b \n" // G 8 bytes -> 4 shorts. - "uadalp v28.4h, v2.8b \n" // R 8 bytes -> 4 shorts. - - "ins v16.D[1], v26.D[0] \n" - "ins v17.D[1], v27.D[0] \n" - "ins v18.D[1], v28.D[0] \n" - - "urshr v4.8h, v16.8h, #1 \n" // 2x average - "urshr v5.8h, v17.8h, #1 \n" - "urshr v6.8h, v18.8h, #1 \n" - - "subs %w4, %w4, #16 \n" // 16 processed per loop. - "mul v2.8h, v4.8h, v20.8h \n" // B - "mls v2.8h, v5.8h, v21.8h \n" // G - "mls v2.8h, v6.8h, v22.8h \n" // R - "add v2.8h, v2.8h, v25.8h \n" // +128 -> unsigned - "mul v3.8h, v6.8h, v20.8h \n" // R - "mls v3.8h, v5.8h, v24.8h \n" // G - "mls v3.8h, v4.8h, v23.8h \n" // B - "add v3.8h, v3.8h, v25.8h \n" // +128 -> unsigned - "uqshrn v0.8b, v2.8h, #8 \n" // 16 bit to 8 bit U - "uqshrn v1.8b, v3.8h, #8 \n" // 16 bit to 8 bit V - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 pixels U. - MEMACCESS(3) - "st1 {v1.8b}, [%3], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_argb4444), // %0 - "+r"(src_argb4444_1), // %1 - "+r"(dst_u), // %2 - "+r"(dst_v), // %3 - "+r"(width) // %4 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", - "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", - "v26", "v27", "v28" - - ); -} - -void RGB565ToYRow_NEON(const uint8* src_rgb565, uint8* dst_y, int width) { - asm volatile ( - "movi v24.8b, #13 \n" // B * 0.1016 coefficient - "movi v25.8b, #65 \n" // G * 0.5078 coefficient - "movi v26.8b, #33 \n" // R * 0.2578 coefficient - "movi v27.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 RGB565 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - RGB565TOARGB - "umull v3.8h, v0.8b, v24.8b \n" // B - "umlal v3.8h, v1.8b, v25.8b \n" // G - "umlal v3.8h, v2.8b, v26.8b \n" // R - "sqrshrun v0.8b, v3.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v27.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_rgb565), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v6", - "v24", "v25", "v26", "v27" - ); -} - -void ARGB1555ToYRow_NEON(const uint8* src_argb1555, uint8* dst_y, int width) { - asm volatile ( - "movi v4.8b, #13 \n" // B * 0.1016 coefficient - "movi v5.8b, #65 \n" // G * 0.5078 coefficient - "movi v6.8b, #33 \n" // R * 0.2578 coefficient - "movi v7.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 ARGB1555 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - ARGB1555TOARGB - "umull v3.8h, v0.8b, v4.8b \n" // B - "umlal v3.8h, v1.8b, v5.8b \n" // G - "umlal v3.8h, v2.8b, v6.8b \n" // R - "sqrshrun v0.8b, v3.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v7.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_argb1555), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7" - ); -} - -void ARGB4444ToYRow_NEON(const uint8* src_argb4444, uint8* dst_y, int width) { - asm volatile ( - "movi v24.8b, #13 \n" // B * 0.1016 coefficient - "movi v25.8b, #65 \n" // G * 0.5078 coefficient - "movi v26.8b, #33 \n" // R * 0.2578 coefficient - "movi v27.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 8 ARGB4444 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - ARGB4444TOARGB - "umull v3.8h, v0.8b, v24.8b \n" // B - "umlal v3.8h, v1.8b, v25.8b \n" // G - "umlal v3.8h, v2.8b, v26.8b \n" // R - "sqrshrun v0.8b, v3.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v27.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_argb4444), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v24", "v25", "v26", "v27" - ); -} - -void BGRAToYRow_NEON(const uint8* src_bgra, uint8* dst_y, int width) { - asm volatile ( - "movi v4.8b, #33 \n" // R * 0.2578 coefficient - "movi v5.8b, #65 \n" // G * 0.5078 coefficient - "movi v6.8b, #13 \n" // B * 0.1016 coefficient - "movi v7.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v16.8h, v1.8b, v4.8b \n" // R - "umlal v16.8h, v2.8b, v5.8b \n" // G - "umlal v16.8h, v3.8b, v6.8b \n" // B - "sqrshrun v0.8b, v16.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v7.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_bgra), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16" - ); -} - -void ABGRToYRow_NEON(const uint8* src_abgr, uint8* dst_y, int width) { - asm volatile ( - "movi v4.8b, #33 \n" // R * 0.2578 coefficient - "movi v5.8b, #65 \n" // G * 0.5078 coefficient - "movi v6.8b, #13 \n" // B * 0.1016 coefficient - "movi v7.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v16.8h, v0.8b, v4.8b \n" // R - "umlal v16.8h, v1.8b, v5.8b \n" // G - "umlal v16.8h, v2.8b, v6.8b \n" // B - "sqrshrun v0.8b, v16.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v7.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_abgr), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16" - ); -} - -void RGBAToYRow_NEON(const uint8* src_rgba, uint8* dst_y, int width) { - asm volatile ( - "movi v4.8b, #13 \n" // B * 0.1016 coefficient - "movi v5.8b, #65 \n" // G * 0.5078 coefficient - "movi v6.8b, #33 \n" // R * 0.2578 coefficient - "movi v7.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v16.8h, v1.8b, v4.8b \n" // B - "umlal v16.8h, v2.8b, v5.8b \n" // G - "umlal v16.8h, v3.8b, v6.8b \n" // R - "sqrshrun v0.8b, v16.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v7.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_rgba), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16" - ); -} - -void RGB24ToYRow_NEON(const uint8* src_rgb24, uint8* dst_y, int width) { - asm volatile ( - "movi v4.8b, #13 \n" // B * 0.1016 coefficient - "movi v5.8b, #65 \n" // G * 0.5078 coefficient - "movi v6.8b, #33 \n" // R * 0.2578 coefficient - "movi v7.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld3 {v0.8b,v1.8b,v2.8b}, [%0], #24 \n" // load 8 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v16.8h, v0.8b, v4.8b \n" // B - "umlal v16.8h, v1.8b, v5.8b \n" // G - "umlal v16.8h, v2.8b, v6.8b \n" // R - "sqrshrun v0.8b, v16.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v7.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_rgb24), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16" - ); -} - -void RAWToYRow_NEON(const uint8* src_raw, uint8* dst_y, int width) { - asm volatile ( - "movi v4.8b, #33 \n" // R * 0.2578 coefficient - "movi v5.8b, #65 \n" // G * 0.5078 coefficient - "movi v6.8b, #13 \n" // B * 0.1016 coefficient - "movi v7.8b, #16 \n" // Add 16 constant - "1: \n" - MEMACCESS(0) - "ld3 {v0.8b,v1.8b,v2.8b}, [%0], #24 \n" // load 8 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v16.8h, v0.8b, v4.8b \n" // B - "umlal v16.8h, v1.8b, v5.8b \n" // G - "umlal v16.8h, v2.8b, v6.8b \n" // R - "sqrshrun v0.8b, v16.8h, #7 \n" // 16 bit to 8 bit Y - "uqadd v0.8b, v0.8b, v7.8b \n" - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels Y. - "b.gt 1b \n" - : "+r"(src_raw), // %0 - "+r"(dst_y), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16" - ); -} - -// Bilinear filter 16x2 -> 16x1 -void InterpolateRow_NEON(uint8* dst_ptr, - const uint8* src_ptr, ptrdiff_t src_stride, - int dst_width, int source_y_fraction) { - int y1_fraction = source_y_fraction; - int y0_fraction = 256 - y1_fraction; - const uint8* src_ptr1 = src_ptr + src_stride; - asm volatile ( - "cmp %w4, #0 \n" - "b.eq 100f \n" - "cmp %w4, #128 \n" - "b.eq 50f \n" - - "dup v5.16b, %w4 \n" - "dup v4.16b, %w5 \n" - // General purpose row blend. - "1: \n" - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" - MEMACCESS(2) - "ld1 {v1.16b}, [%2], #16 \n" - "subs %w3, %w3, #16 \n" - "umull v2.8h, v0.8b, v4.8b \n" - "umull2 v3.8h, v0.16b, v4.16b \n" - "umlal v2.8h, v1.8b, v5.8b \n" - "umlal2 v3.8h, v1.16b, v5.16b \n" - "rshrn v0.8b, v2.8h, #8 \n" - "rshrn2 v0.16b, v3.8h, #8 \n" - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" - "b.gt 1b \n" - "b 99f \n" - - // Blend 50 / 50. - "50: \n" - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" - MEMACCESS(2) - "ld1 {v1.16b}, [%2], #16 \n" - "subs %w3, %w3, #16 \n" - "urhadd v0.16b, v0.16b, v1.16b \n" - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" - "b.gt 50b \n" - "b 99f \n" - - // Blend 100 / 0 - Copy row unchanged. - "100: \n" - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" - "subs %w3, %w3, #16 \n" - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" - "b.gt 100b \n" - - "99: \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(src_ptr1), // %2 - "+r"(dst_width), // %3 - "+r"(y1_fraction), // %4 - "+r"(y0_fraction) // %5 - : - : "cc", "memory", "v0", "v1", "v3", "v4", "v5" - ); -} - -// dr * (256 - sa) / 256 + sr = dr - dr * sa / 256 + sr -void ARGBBlendRow_NEON(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - "subs %w3, %w3, #8 \n" - "b.lt 89f \n" - // Blend 8 pixels. - "8: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB0 pixels - MEMACCESS(1) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%1], #32 \n" // load 8 ARGB1 pixels - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "umull v16.8h, v4.8b, v3.8b \n" // db * a - "umull v17.8h, v5.8b, v3.8b \n" // dg * a - "umull v18.8h, v6.8b, v3.8b \n" // dr * a - "uqrshrn v16.8b, v16.8h, #8 \n" // db >>= 8 - "uqrshrn v17.8b, v17.8h, #8 \n" // dg >>= 8 - "uqrshrn v18.8b, v18.8h, #8 \n" // dr >>= 8 - "uqsub v4.8b, v4.8b, v16.8b \n" // db - (db * a / 256) - "uqsub v5.8b, v5.8b, v17.8b \n" // dg - (dg * a / 256) - "uqsub v6.8b, v6.8b, v18.8b \n" // dr - (dr * a / 256) - "uqadd v0.8b, v0.8b, v4.8b \n" // + sb - "uqadd v1.8b, v1.8b, v5.8b \n" // + sg - "uqadd v2.8b, v2.8b, v6.8b \n" // + sr - "movi v3.8b, #255 \n" // a = 255 - MEMACCESS(2) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%2], #32 \n" // store 8 ARGB pixels - "b.ge 8b \n" - - "89: \n" - "adds %w3, %w3, #8-1 \n" - "b.lt 99f \n" - - // Blend 1 pixels. - "1: \n" - MEMACCESS(0) - "ld4 {v0.b,v1.b,v2.b,v3.b}[0], [%0], #4 \n" // load 1 pixel ARGB0. - MEMACCESS(1) - "ld4 {v4.b,v5.b,v6.b,v7.b}[0], [%1], #4 \n" // load 1 pixel ARGB1. - "subs %w3, %w3, #1 \n" // 1 processed per loop. - "umull v16.8h, v4.8b, v3.8b \n" // db * a - "umull v17.8h, v5.8b, v3.8b \n" // dg * a - "umull v18.8h, v6.8b, v3.8b \n" // dr * a - "uqrshrn v16.8b, v16.8h, #8 \n" // db >>= 8 - "uqrshrn v17.8b, v17.8h, #8 \n" // dg >>= 8 - "uqrshrn v18.8b, v18.8h, #8 \n" // dr >>= 8 - "uqsub v4.8b, v4.8b, v16.8b \n" // db - (db * a / 256) - "uqsub v5.8b, v5.8b, v17.8b \n" // dg - (dg * a / 256) - "uqsub v6.8b, v6.8b, v18.8b \n" // dr - (dr * a / 256) - "uqadd v0.8b, v0.8b, v4.8b \n" // + sb - "uqadd v1.8b, v1.8b, v5.8b \n" // + sg - "uqadd v2.8b, v2.8b, v6.8b \n" // + sr - "movi v3.8b, #255 \n" // a = 255 - MEMACCESS(2) - "st4 {v0.b,v1.b,v2.b,v3.b}[0], [%2], #4 \n" // store 1 pixel. - "b.ge 1b \n" - - "99: \n" - - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v16", "v17", "v18" - ); -} - -// Attenuate 8 pixels at a time. -void ARGBAttenuateRow_NEON(const uint8* src_argb, uint8* dst_argb, int width) { - asm volatile ( - // Attenuate 8 pixels. - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB pixels - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v4.8h, v0.8b, v3.8b \n" // b * a - "umull v5.8h, v1.8b, v3.8b \n" // g * a - "umull v6.8h, v2.8b, v3.8b \n" // r * a - "uqrshrn v0.8b, v4.8h, #8 \n" // b >>= 8 - "uqrshrn v1.8b, v5.8h, #8 \n" // g >>= 8 - "uqrshrn v2.8b, v6.8h, #8 \n" // r >>= 8 - MEMACCESS(1) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%1], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6" - ); -} - -// Quantize 8 ARGB pixels (32 bytes). -// dst = (dst * scale >> 16) * interval_size + interval_offset; -void ARGBQuantizeRow_NEON(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width) { - asm volatile ( - "dup v4.8h, %w2 \n" - "ushr v4.8h, v4.8h, #1 \n" // scale >>= 1 - "dup v5.8h, %w3 \n" // interval multiply. - "dup v6.8h, %w4 \n" // interval add - - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0] \n" // load 8 pixels of ARGB. - "subs %w1, %w1, #8 \n" // 8 processed per loop. - "uxtl v0.8h, v0.8b \n" // b (0 .. 255) - "uxtl v1.8h, v1.8b \n" - "uxtl v2.8h, v2.8b \n" - "sqdmulh v0.8h, v0.8h, v4.8h \n" // b * scale - "sqdmulh v1.8h, v1.8h, v4.8h \n" // g - "sqdmulh v2.8h, v2.8h, v4.8h \n" // r - "mul v0.8h, v0.8h, v5.8h \n" // b * interval_size - "mul v1.8h, v1.8h, v5.8h \n" // g - "mul v2.8h, v2.8h, v5.8h \n" // r - "add v0.8h, v0.8h, v6.8h \n" // b + interval_offset - "add v1.8h, v1.8h, v6.8h \n" // g - "add v2.8h, v2.8h, v6.8h \n" // r - "uqxtn v0.8b, v0.8h \n" - "uqxtn v1.8b, v1.8h \n" - "uqxtn v2.8b, v2.8h \n" - MEMACCESS(0) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(width) // %1 - : "r"(scale), // %2 - "r"(interval_size), // %3 - "r"(interval_offset) // %4 - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6" - ); -} - -// Shade 8 pixels at a time by specified value. -// NOTE vqrdmulh.s16 q10, q10, d0[0] must use a scaler register from 0 to 8. -// Rounding in vqrdmulh does +1 to high if high bit of low s16 is set. -void ARGBShadeRow_NEON(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value) { - asm volatile ( - "dup v0.4s, %w3 \n" // duplicate scale value. - "zip1 v0.8b, v0.8b, v0.8b \n" // v0.8b aarrggbb. - "ushr v0.8h, v0.8h, #1 \n" // scale / 2. - - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%0], #32 \n" // load 8 ARGB pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "uxtl v4.8h, v4.8b \n" // b (0 .. 255) - "uxtl v5.8h, v5.8b \n" - "uxtl v6.8h, v6.8b \n" - "uxtl v7.8h, v7.8b \n" - "sqrdmulh v4.8h, v4.8h, v0.h[0] \n" // b * scale * 2 - "sqrdmulh v5.8h, v5.8h, v0.h[1] \n" // g - "sqrdmulh v6.8h, v6.8h, v0.h[2] \n" // r - "sqrdmulh v7.8h, v7.8h, v0.h[3] \n" // a - "uqxtn v4.8b, v4.8h \n" - "uqxtn v5.8b, v5.8h \n" - "uqxtn v6.8b, v6.8h \n" - "uqxtn v7.8b, v7.8h \n" - MEMACCESS(1) - "st4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%1], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(value) // %3 - : "cc", "memory", "v0", "v4", "v5", "v6", "v7" - ); -} - -// Convert 8 ARGB pixels (64 bytes) to 8 Gray ARGB pixels -// Similar to ARGBToYJ but stores ARGB. -// C code is (15 * b + 75 * g + 38 * r + 64) >> 7; -void ARGBGrayRow_NEON(const uint8* src_argb, uint8* dst_argb, int width) { - asm volatile ( - "movi v24.8b, #15 \n" // B * 0.11400 coefficient - "movi v25.8b, #75 \n" // G * 0.58700 coefficient - "movi v26.8b, #38 \n" // R * 0.29900 coefficient - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "umull v4.8h, v0.8b, v24.8b \n" // B - "umlal v4.8h, v1.8b, v25.8b \n" // G - "umlal v4.8h, v2.8b, v26.8b \n" // R - "sqrshrun v0.8b, v4.8h, #7 \n" // 15 bit to 8 bit B - "orr v1.8b, v0.8b, v0.8b \n" // G - "orr v2.8b, v0.8b, v0.8b \n" // R - MEMACCESS(1) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%1], #32 \n" // store 8 pixels. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v24", "v25", "v26" - ); -} - -// Convert 8 ARGB pixels (32 bytes) to 8 Sepia ARGB pixels. -// b = (r * 35 + g * 68 + b * 17) >> 7 -// g = (r * 45 + g * 88 + b * 22) >> 7 -// r = (r * 50 + g * 98 + b * 24) >> 7 - -void ARGBSepiaRow_NEON(uint8* dst_argb, int width) { - asm volatile ( - "movi v20.8b, #17 \n" // BB coefficient - "movi v21.8b, #68 \n" // BG coefficient - "movi v22.8b, #35 \n" // BR coefficient - "movi v24.8b, #22 \n" // GB coefficient - "movi v25.8b, #88 \n" // GG coefficient - "movi v26.8b, #45 \n" // GR coefficient - "movi v28.8b, #24 \n" // BB coefficient - "movi v29.8b, #98 \n" // BG coefficient - "movi v30.8b, #50 \n" // BR coefficient - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0] \n" // load 8 ARGB pixels. - "subs %w1, %w1, #8 \n" // 8 processed per loop. - "umull v4.8h, v0.8b, v20.8b \n" // B to Sepia B - "umlal v4.8h, v1.8b, v21.8b \n" // G - "umlal v4.8h, v2.8b, v22.8b \n" // R - "umull v5.8h, v0.8b, v24.8b \n" // B to Sepia G - "umlal v5.8h, v1.8b, v25.8b \n" // G - "umlal v5.8h, v2.8b, v26.8b \n" // R - "umull v6.8h, v0.8b, v28.8b \n" // B to Sepia R - "umlal v6.8h, v1.8b, v29.8b \n" // G - "umlal v6.8h, v2.8b, v30.8b \n" // R - "uqshrn v0.8b, v4.8h, #7 \n" // 16 bit to 8 bit B - "uqshrn v1.8b, v5.8h, #7 \n" // 16 bit to 8 bit G - "uqshrn v2.8b, v6.8h, #7 \n" // 16 bit to 8 bit R - MEMACCESS(0) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // store 8 pixels. - "b.gt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(width) // %1 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v24", "v25", "v26", "v28", "v29", "v30" - ); -} - -// Tranform 8 ARGB pixels (32 bytes) with color matrix. -// TODO(fbarchard): Was same as Sepia except matrix is provided. This function -// needs to saturate. Consider doing a non-saturating version. -void ARGBColorMatrixRow_NEON(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width) { - asm volatile ( - MEMACCESS(3) - "ld1 {v2.16b}, [%3] \n" // load 3 ARGB vectors. - "sxtl v0.8h, v2.8b \n" // B,G coefficients s16. - "sxtl2 v1.8h, v2.16b \n" // R,A coefficients s16. - - "1: \n" - MEMACCESS(0) - "ld4 {v16.8b,v17.8b,v18.8b,v19.8b}, [%0], #32 \n" // load 8 pixels. - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "uxtl v16.8h, v16.8b \n" // b (0 .. 255) 16 bit - "uxtl v17.8h, v17.8b \n" // g - "uxtl v18.8h, v18.8b \n" // r - "uxtl v19.8h, v19.8b \n" // a - "mul v22.8h, v16.8h, v0.h[0] \n" // B = B * Matrix B - "mul v23.8h, v16.8h, v0.h[4] \n" // G = B * Matrix G - "mul v24.8h, v16.8h, v1.h[0] \n" // R = B * Matrix R - "mul v25.8h, v16.8h, v1.h[4] \n" // A = B * Matrix A - "mul v4.8h, v17.8h, v0.h[1] \n" // B += G * Matrix B - "mul v5.8h, v17.8h, v0.h[5] \n" // G += G * Matrix G - "mul v6.8h, v17.8h, v1.h[1] \n" // R += G * Matrix R - "mul v7.8h, v17.8h, v1.h[5] \n" // A += G * Matrix A - "sqadd v22.8h, v22.8h, v4.8h \n" // Accumulate B - "sqadd v23.8h, v23.8h, v5.8h \n" // Accumulate G - "sqadd v24.8h, v24.8h, v6.8h \n" // Accumulate R - "sqadd v25.8h, v25.8h, v7.8h \n" // Accumulate A - "mul v4.8h, v18.8h, v0.h[2] \n" // B += R * Matrix B - "mul v5.8h, v18.8h, v0.h[6] \n" // G += R * Matrix G - "mul v6.8h, v18.8h, v1.h[2] \n" // R += R * Matrix R - "mul v7.8h, v18.8h, v1.h[6] \n" // A += R * Matrix A - "sqadd v22.8h, v22.8h, v4.8h \n" // Accumulate B - "sqadd v23.8h, v23.8h, v5.8h \n" // Accumulate G - "sqadd v24.8h, v24.8h, v6.8h \n" // Accumulate R - "sqadd v25.8h, v25.8h, v7.8h \n" // Accumulate A - "mul v4.8h, v19.8h, v0.h[3] \n" // B += A * Matrix B - "mul v5.8h, v19.8h, v0.h[7] \n" // G += A * Matrix G - "mul v6.8h, v19.8h, v1.h[3] \n" // R += A * Matrix R - "mul v7.8h, v19.8h, v1.h[7] \n" // A += A * Matrix A - "sqadd v22.8h, v22.8h, v4.8h \n" // Accumulate B - "sqadd v23.8h, v23.8h, v5.8h \n" // Accumulate G - "sqadd v24.8h, v24.8h, v6.8h \n" // Accumulate R - "sqadd v25.8h, v25.8h, v7.8h \n" // Accumulate A - "sqshrun v16.8b, v22.8h, #6 \n" // 16 bit to 8 bit B - "sqshrun v17.8b, v23.8h, #6 \n" // 16 bit to 8 bit G - "sqshrun v18.8b, v24.8h, #6 \n" // 16 bit to 8 bit R - "sqshrun v19.8b, v25.8h, #6 \n" // 16 bit to 8 bit A - MEMACCESS(1) - "st4 {v16.8b,v17.8b,v18.8b,v19.8b}, [%1], #32 \n" // store 8 pixels. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width) // %2 - : "r"(matrix_argb) // %3 - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", "v17", - "v18", "v19", "v22", "v23", "v24", "v25" - ); -} - -// TODO(fbarchard): fix vqshrun in ARGBMultiplyRow_NEON and reenable. -// Multiply 2 rows of ARGB pixels together, 8 pixels at a time. -void ARGBMultiplyRow_NEON(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB pixels. - MEMACCESS(1) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%1], #32 \n" // load 8 more pixels. - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "umull v0.8h, v0.8b, v4.8b \n" // multiply B - "umull v1.8h, v1.8b, v5.8b \n" // multiply G - "umull v2.8h, v2.8b, v6.8b \n" // multiply R - "umull v3.8h, v3.8b, v7.8b \n" // multiply A - "rshrn v0.8b, v0.8h, #8 \n" // 16 bit to 8 bit B - "rshrn v1.8b, v1.8h, #8 \n" // 16 bit to 8 bit G - "rshrn v2.8b, v2.8h, #8 \n" // 16 bit to 8 bit R - "rshrn v3.8b, v3.8h, #8 \n" // 16 bit to 8 bit A - MEMACCESS(2) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%2], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7" - ); -} - -// Add 2 rows of ARGB pixels together, 8 pixels at a time. -void ARGBAddRow_NEON(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB pixels. - MEMACCESS(1) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%1], #32 \n" // load 8 more pixels. - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "uqadd v0.8b, v0.8b, v4.8b \n" - "uqadd v1.8b, v1.8b, v5.8b \n" - "uqadd v2.8b, v2.8b, v6.8b \n" - "uqadd v3.8b, v3.8b, v7.8b \n" - MEMACCESS(2) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%2], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7" - ); -} - -// Subtract 2 rows of ARGB pixels, 8 pixels at a time. -void ARGBSubtractRow_NEON(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - asm volatile ( - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // load 8 ARGB pixels. - MEMACCESS(1) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%1], #32 \n" // load 8 more pixels. - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "uqsub v0.8b, v0.8b, v4.8b \n" - "uqsub v1.8b, v1.8b, v5.8b \n" - "uqsub v2.8b, v2.8b, v6.8b \n" - "uqsub v3.8b, v3.8b, v7.8b \n" - MEMACCESS(2) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%2], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - - : "+r"(src_argb0), // %0 - "+r"(src_argb1), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7" - ); -} - -// Adds Sobel X and Sobel Y and stores Sobel into ARGB. -// A = 255 -// R = Sobel -// G = Sobel -// B = Sobel -void SobelRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - asm volatile ( - "movi v3.8b, #255 \n" // alpha - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "ld1 {v0.8b}, [%0], #8 \n" // load 8 sobelx. - MEMACCESS(1) - "ld1 {v1.8b}, [%1], #8 \n" // load 8 sobely. - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "uqadd v0.8b, v0.8b, v1.8b \n" // add - "orr v1.8b, v0.8b, v0.8b \n" - "orr v2.8b, v0.8b, v0.8b \n" - MEMACCESS(2) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%2], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3" - ); -} - -// Adds Sobel X and Sobel Y and stores Sobel into plane. -void SobelToPlaneRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width) { - asm volatile ( - // 16 pixel loop. - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load 16 sobelx. - MEMACCESS(1) - "ld1 {v1.16b}, [%1], #16 \n" // load 16 sobely. - "subs %w3, %w3, #16 \n" // 16 processed per loop. - "uqadd v0.16b, v0.16b, v1.16b \n" // add - MEMACCESS(2) - "st1 {v0.16b}, [%2], #16 \n" // store 16 pixels. - "b.gt 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_y), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1" - ); -} - -// Mixes Sobel X, Sobel Y and Sobel into ARGB. -// A = 255 -// R = Sobel X -// G = Sobel -// B = Sobel Y -void SobelXYRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - asm volatile ( - "movi v3.8b, #255 \n" // alpha - // 8 pixel loop. - "1: \n" - MEMACCESS(0) - "ld1 {v2.8b}, [%0], #8 \n" // load 8 sobelx. - MEMACCESS(1) - "ld1 {v0.8b}, [%1], #8 \n" // load 8 sobely. - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "uqadd v1.8b, v0.8b, v2.8b \n" // add - MEMACCESS(2) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%2], #32 \n" // store 8 ARGB pixels - "b.gt 1b \n" - : "+r"(src_sobelx), // %0 - "+r"(src_sobely), // %1 - "+r"(dst_argb), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3" - ); -} - -// SobelX as a matrix is -// -1 0 1 -// -2 0 2 -// -1 0 1 -void SobelXRow_NEON(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld1 {v0.8b}, [%0],%5 \n" // top - MEMACCESS(0) - "ld1 {v1.8b}, [%0],%6 \n" - "usubl v0.8h, v0.8b, v1.8b \n" - MEMACCESS(1) - "ld1 {v2.8b}, [%1],%5 \n" // center * 2 - MEMACCESS(1) - "ld1 {v3.8b}, [%1],%6 \n" - "usubl v1.8h, v2.8b, v3.8b \n" - "add v0.8h, v0.8h, v1.8h \n" - "add v0.8h, v0.8h, v1.8h \n" - MEMACCESS(2) - "ld1 {v2.8b}, [%2],%5 \n" // bottom - MEMACCESS(2) - "ld1 {v3.8b}, [%2],%6 \n" - "subs %w4, %w4, #8 \n" // 8 pixels - "usubl v1.8h, v2.8b, v3.8b \n" - "add v0.8h, v0.8h, v1.8h \n" - "abs v0.8h, v0.8h \n" - "uqxtn v0.8b, v0.8h \n" - MEMACCESS(3) - "st1 {v0.8b}, [%3], #8 \n" // store 8 sobelx - "b.gt 1b \n" - : "+r"(src_y0), // %0 - "+r"(src_y1), // %1 - "+r"(src_y2), // %2 - "+r"(dst_sobelx), // %3 - "+r"(width) // %4 - : "r"(2LL), // %5 - "r"(6LL) // %6 - : "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -// SobelY as a matrix is -// -1 -2 -1 -// 0 0 0 -// 1 2 1 -void SobelYRow_NEON(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld1 {v0.8b}, [%0],%4 \n" // left - MEMACCESS(1) - "ld1 {v1.8b}, [%1],%4 \n" - "usubl v0.8h, v0.8b, v1.8b \n" - MEMACCESS(0) - "ld1 {v2.8b}, [%0],%4 \n" // center * 2 - MEMACCESS(1) - "ld1 {v3.8b}, [%1],%4 \n" - "usubl v1.8h, v2.8b, v3.8b \n" - "add v0.8h, v0.8h, v1.8h \n" - "add v0.8h, v0.8h, v1.8h \n" - MEMACCESS(0) - "ld1 {v2.8b}, [%0],%5 \n" // right - MEMACCESS(1) - "ld1 {v3.8b}, [%1],%5 \n" - "subs %w3, %w3, #8 \n" // 8 pixels - "usubl v1.8h, v2.8b, v3.8b \n" - "add v0.8h, v0.8h, v1.8h \n" - "abs v0.8h, v0.8h \n" - "uqxtn v0.8b, v0.8h \n" - MEMACCESS(2) - "st1 {v0.8b}, [%2], #8 \n" // store 8 sobely - "b.gt 1b \n" - : "+r"(src_y0), // %0 - "+r"(src_y1), // %1 - "+r"(dst_sobely), // %2 - "+r"(width) // %3 - : "r"(1LL), // %4 - "r"(6LL) // %5 - : "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List - ); -} -#endif // !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/row_win.cc b/telegramgallery/src/main/cpp/libyuv/source/row_win.cc deleted file mode 100644 index cdb7606..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/row_win.cc +++ /dev/null @@ -1,6269 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" - -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_X64) && \ - defined(_MSC_VER) && !defined(__clang__) -#include -#include // For _mm_maddubs_epi16 -#endif - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for Visual C 32/64 bit and clangcl 32 bit -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || (defined(_M_X64) && !defined(__clang__))) - -// 64 bit -#if defined(_M_X64) - -// Read 4 UV from 422, upsample to 8 UV. -#define READYUV422 \ - xmm0 = _mm_cvtsi32_si128(*(uint32*)u_buf); \ - xmm1 = _mm_cvtsi32_si128(*(uint32*)(u_buf + offset)); \ - xmm0 = _mm_unpacklo_epi8(xmm0, xmm1); \ - xmm0 = _mm_unpacklo_epi16(xmm0, xmm0); \ - u_buf += 4; \ - xmm4 = _mm_loadl_epi64((__m128i*)y_buf); \ - xmm4 = _mm_unpacklo_epi8(xmm4, xmm4); \ - y_buf += 8; - -// Read 4 UV from 422, upsample to 8 UV. With 8 Alpha. -#define READYUVA422 \ - xmm0 = _mm_cvtsi32_si128(*(uint32*)u_buf); \ - xmm1 = _mm_cvtsi32_si128(*(uint32*)(u_buf + offset)); \ - xmm0 = _mm_unpacklo_epi8(xmm0, xmm1); \ - xmm0 = _mm_unpacklo_epi16(xmm0, xmm0); \ - u_buf += 4; \ - xmm4 = _mm_loadl_epi64((__m128i*)y_buf); \ - xmm4 = _mm_unpacklo_epi8(xmm4, xmm4); \ - y_buf += 8; \ - xmm5 = _mm_loadl_epi64((__m128i*)a_buf); \ - a_buf += 8; - -// Convert 8 pixels: 8 UV and 8 Y. -#define YUVTORGB(yuvconstants) \ - xmm1 = _mm_loadu_si128(&xmm0); \ - xmm2 = _mm_loadu_si128(&xmm0); \ - xmm0 = _mm_maddubs_epi16(xmm0, *(__m128i*)yuvconstants->kUVToB); \ - xmm1 = _mm_maddubs_epi16(xmm1, *(__m128i*)yuvconstants->kUVToG); \ - xmm2 = _mm_maddubs_epi16(xmm2, *(__m128i*)yuvconstants->kUVToR); \ - xmm0 = _mm_sub_epi16(*(__m128i*)yuvconstants->kUVBiasB, xmm0); \ - xmm1 = _mm_sub_epi16(*(__m128i*)yuvconstants->kUVBiasG, xmm1); \ - xmm2 = _mm_sub_epi16(*(__m128i*)yuvconstants->kUVBiasR, xmm2); \ - xmm4 = _mm_mulhi_epu16(xmm4, *(__m128i*)yuvconstants->kYToRgb); \ - xmm0 = _mm_adds_epi16(xmm0, xmm4); \ - xmm1 = _mm_adds_epi16(xmm1, xmm4); \ - xmm2 = _mm_adds_epi16(xmm2, xmm4); \ - xmm0 = _mm_srai_epi16(xmm0, 6); \ - xmm1 = _mm_srai_epi16(xmm1, 6); \ - xmm2 = _mm_srai_epi16(xmm2, 6); \ - xmm0 = _mm_packus_epi16(xmm0, xmm0); \ - xmm1 = _mm_packus_epi16(xmm1, xmm1); \ - xmm2 = _mm_packus_epi16(xmm2, xmm2); - -// Store 8 ARGB values. -#define STOREARGB \ - xmm0 = _mm_unpacklo_epi8(xmm0, xmm1); \ - xmm2 = _mm_unpacklo_epi8(xmm2, xmm5); \ - xmm1 = _mm_loadu_si128(&xmm0); \ - xmm0 = _mm_unpacklo_epi16(xmm0, xmm2); \ - xmm1 = _mm_unpackhi_epi16(xmm1, xmm2); \ - _mm_storeu_si128((__m128i *)dst_argb, xmm0); \ - _mm_storeu_si128((__m128i *)(dst_argb + 16), xmm1); \ - dst_argb += 32; - - -#if defined(HAS_I422TOARGBROW_SSSE3) -void I422ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __m128i xmm0, xmm1, xmm2, xmm4; - const __m128i xmm5 = _mm_set1_epi8(-1); - const ptrdiff_t offset = (uint8*)v_buf - (uint8*)u_buf; - while (width > 0) { - READYUV422 - YUVTORGB(yuvconstants) - STOREARGB - width -= 8; - } -} -#endif - -#if defined(HAS_I422ALPHATOARGBROW_SSSE3) -void I422AlphaToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __m128i xmm0, xmm1, xmm2, xmm4, xmm5; - const ptrdiff_t offset = (uint8*)v_buf - (uint8*)u_buf; - while (width > 0) { - READYUVA422 - YUVTORGB(yuvconstants) - STOREARGB - width -= 8; - } -} -#endif - -// 32 bit -#else // defined(_M_X64) -#ifdef HAS_ARGBTOYROW_SSSE3 - -// Constants for ARGB. -static const vec8 kARGBToY = { - 13, 65, 33, 0, 13, 65, 33, 0, 13, 65, 33, 0, 13, 65, 33, 0 -}; - -// JPeg full range. -static const vec8 kARGBToYJ = { - 15, 75, 38, 0, 15, 75, 38, 0, 15, 75, 38, 0, 15, 75, 38, 0 -}; - -static const vec8 kARGBToU = { - 112, -74, -38, 0, 112, -74, -38, 0, 112, -74, -38, 0, 112, -74, -38, 0 -}; - -static const vec8 kARGBToUJ = { - 127, -84, -43, 0, 127, -84, -43, 0, 127, -84, -43, 0, 127, -84, -43, 0 -}; - -static const vec8 kARGBToV = { - -18, -94, 112, 0, -18, -94, 112, 0, -18, -94, 112, 0, -18, -94, 112, 0, -}; - -static const vec8 kARGBToVJ = { - -20, -107, 127, 0, -20, -107, 127, 0, -20, -107, 127, 0, -20, -107, 127, 0 -}; - -// vpshufb for vphaddw + vpackuswb packed to shorts. -static const lvec8 kShufARGBToUV_AVX = { - 0, 1, 8, 9, 2, 3, 10, 11, 4, 5, 12, 13, 6, 7, 14, 15, - 0, 1, 8, 9, 2, 3, 10, 11, 4, 5, 12, 13, 6, 7, 14, 15 -}; - -// Constants for BGRA. -static const vec8 kBGRAToY = { - 0, 33, 65, 13, 0, 33, 65, 13, 0, 33, 65, 13, 0, 33, 65, 13 -}; - -static const vec8 kBGRAToU = { - 0, -38, -74, 112, 0, -38, -74, 112, 0, -38, -74, 112, 0, -38, -74, 112 -}; - -static const vec8 kBGRAToV = { - 0, 112, -94, -18, 0, 112, -94, -18, 0, 112, -94, -18, 0, 112, -94, -18 -}; - -// Constants for ABGR. -static const vec8 kABGRToY = { - 33, 65, 13, 0, 33, 65, 13, 0, 33, 65, 13, 0, 33, 65, 13, 0 -}; - -static const vec8 kABGRToU = { - -38, -74, 112, 0, -38, -74, 112, 0, -38, -74, 112, 0, -38, -74, 112, 0 -}; - -static const vec8 kABGRToV = { - 112, -94, -18, 0, 112, -94, -18, 0, 112, -94, -18, 0, 112, -94, -18, 0 -}; - -// Constants for RGBA. -static const vec8 kRGBAToY = { - 0, 13, 65, 33, 0, 13, 65, 33, 0, 13, 65, 33, 0, 13, 65, 33 -}; - -static const vec8 kRGBAToU = { - 0, 112, -74, -38, 0, 112, -74, -38, 0, 112, -74, -38, 0, 112, -74, -38 -}; - -static const vec8 kRGBAToV = { - 0, -18, -94, 112, 0, -18, -94, 112, 0, -18, -94, 112, 0, -18, -94, 112 -}; - -static const uvec8 kAddY16 = { - 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u, 16u -}; - -// 7 bit fixed point 0.5. -static const vec16 kAddYJ64 = { - 64, 64, 64, 64, 64, 64, 64, 64 -}; - -static const uvec8 kAddUV128 = { - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u, - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u -}; - -static const uvec16 kAddUVJ128 = { - 0x8080u, 0x8080u, 0x8080u, 0x8080u, 0x8080u, 0x8080u, 0x8080u, 0x8080u -}; - -// Shuffle table for converting RGB24 to ARGB. -static const uvec8 kShuffleMaskRGB24ToARGB = { - 0u, 1u, 2u, 12u, 3u, 4u, 5u, 13u, 6u, 7u, 8u, 14u, 9u, 10u, 11u, 15u -}; - -// Shuffle table for converting RAW to ARGB. -static const uvec8 kShuffleMaskRAWToARGB = { - 2u, 1u, 0u, 12u, 5u, 4u, 3u, 13u, 8u, 7u, 6u, 14u, 11u, 10u, 9u, 15u -}; - -// Shuffle table for converting RAW to RGB24. First 8. -static const uvec8 kShuffleMaskRAWToRGB24_0 = { - 2u, 1u, 0u, 5u, 4u, 3u, 8u, 7u, - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting RAW to RGB24. Middle 8. -static const uvec8 kShuffleMaskRAWToRGB24_1 = { - 2u, 7u, 6u, 5u, 10u, 9u, 8u, 13u, - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting RAW to RGB24. Last 8. -static const uvec8 kShuffleMaskRAWToRGB24_2 = { - 8u, 7u, 12u, 11u, 10u, 15u, 14u, 13u, - 128u, 128u, 128u, 128u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting ARGB to RGB24. -static const uvec8 kShuffleMaskARGBToRGB24 = { - 0u, 1u, 2u, 4u, 5u, 6u, 8u, 9u, 10u, 12u, 13u, 14u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting ARGB to RAW. -static const uvec8 kShuffleMaskARGBToRAW = { - 2u, 1u, 0u, 6u, 5u, 4u, 10u, 9u, 8u, 14u, 13u, 12u, 128u, 128u, 128u, 128u -}; - -// Shuffle table for converting ARGBToRGB24 for I422ToRGB24. First 8 + next 4 -static const uvec8 kShuffleMaskARGBToRGB24_0 = { - 0u, 1u, 2u, 4u, 5u, 6u, 8u, 9u, 128u, 128u, 128u, 128u, 10u, 12u, 13u, 14u -}; - -// YUY2 shuf 16 Y to 32 Y. -static const lvec8 kShuffleYUY2Y = { - 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, - 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14 -}; - -// YUY2 shuf 8 UV to 16 UV. -static const lvec8 kShuffleYUY2UV = { - 1, 3, 1, 3, 5, 7, 5, 7, 9, 11, 9, 11, 13, 15, 13, 15, - 1, 3, 1, 3, 5, 7, 5, 7, 9, 11, 9, 11, 13, 15, 13, 15 -}; - -// UYVY shuf 16 Y to 32 Y. -static const lvec8 kShuffleUYVYY = { - 1, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11, 13, 13, 15, 15, - 1, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11, 13, 13, 15, 15 -}; - -// UYVY shuf 8 UV to 16 UV. -static const lvec8 kShuffleUYVYUV = { - 0, 2, 0, 2, 4, 6, 4, 6, 8, 10, 8, 10, 12, 14, 12, 14, - 0, 2, 0, 2, 4, 6, 4, 6, 8, 10, 8, 10, 12, 14, 12, 14 -}; - -// NV21 shuf 8 VU to 16 UV. -static const lvec8 kShuffleNV21 = { - 1, 0, 1, 0, 3, 2, 3, 2, 5, 4, 5, 4, 7, 6, 7, 6, - 1, 0, 1, 0, 3, 2, 3, 2, 5, 4, 5, 4, 7, 6, 7, 6, -}; - -// Duplicates gray value 3 times and fills in alpha opaque. -__declspec(naked) -void J400ToARGBRow_SSE2(const uint8* src_y, uint8* dst_argb, int width) { - __asm { - mov eax, [esp + 4] // src_y - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - pcmpeqb xmm5, xmm5 // generate mask 0xff000000 - pslld xmm5, 24 - - convertloop: - movq xmm0, qword ptr [eax] - lea eax, [eax + 8] - punpcklbw xmm0, xmm0 - movdqa xmm1, xmm0 - punpcklwd xmm0, xmm0 - punpckhwd xmm1, xmm1 - por xmm0, xmm5 - por xmm1, xmm5 - movdqu [edx], xmm0 - movdqu [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - ret - } -} - -#ifdef HAS_J400TOARGBROW_AVX2 -// Duplicates gray value 3 times and fills in alpha opaque. -__declspec(naked) -void J400ToARGBRow_AVX2(const uint8* src_y, uint8* dst_argb, int width) { - __asm { - mov eax, [esp + 4] // src_y - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0xff000000 - vpslld ymm5, ymm5, 24 - - convertloop: - vmovdqu xmm0, [eax] - lea eax, [eax + 16] - vpermq ymm0, ymm0, 0xd8 - vpunpcklbw ymm0, ymm0, ymm0 - vpermq ymm0, ymm0, 0xd8 - vpunpckhwd ymm1, ymm0, ymm0 - vpunpcklwd ymm0, ymm0, ymm0 - vpor ymm0, ymm0, ymm5 - vpor ymm1, ymm1, ymm5 - vmovdqu [edx], ymm0 - vmovdqu [edx + 32], ymm1 - lea edx, [edx + 64] - sub ecx, 16 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_J400TOARGBROW_AVX2 - -__declspec(naked) -void RGB24ToARGBRow_SSSE3(const uint8* src_rgb24, uint8* dst_argb, int width) { - __asm { - mov eax, [esp + 4] // src_rgb24 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - pcmpeqb xmm5, xmm5 // generate mask 0xff000000 - pslld xmm5, 24 - movdqa xmm4, xmmword ptr kShuffleMaskRGB24ToARGB - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm3, [eax + 32] - lea eax, [eax + 48] - movdqa xmm2, xmm3 - palignr xmm2, xmm1, 8 // xmm2 = { xmm3[0:3] xmm1[8:15]} - pshufb xmm2, xmm4 - por xmm2, xmm5 - palignr xmm1, xmm0, 12 // xmm1 = { xmm3[0:7] xmm0[12:15]} - pshufb xmm0, xmm4 - movdqu [edx + 32], xmm2 - por xmm0, xmm5 - pshufb xmm1, xmm4 - movdqu [edx], xmm0 - por xmm1, xmm5 - palignr xmm3, xmm3, 4 // xmm3 = { xmm3[4:15]} - pshufb xmm3, xmm4 - movdqu [edx + 16], xmm1 - por xmm3, xmm5 - movdqu [edx + 48], xmm3 - lea edx, [edx + 64] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void RAWToARGBRow_SSSE3(const uint8* src_raw, uint8* dst_argb, - int width) { - __asm { - mov eax, [esp + 4] // src_raw - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - pcmpeqb xmm5, xmm5 // generate mask 0xff000000 - pslld xmm5, 24 - movdqa xmm4, xmmword ptr kShuffleMaskRAWToARGB - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm3, [eax + 32] - lea eax, [eax + 48] - movdqa xmm2, xmm3 - palignr xmm2, xmm1, 8 // xmm2 = { xmm3[0:3] xmm1[8:15]} - pshufb xmm2, xmm4 - por xmm2, xmm5 - palignr xmm1, xmm0, 12 // xmm1 = { xmm3[0:7] xmm0[12:15]} - pshufb xmm0, xmm4 - movdqu [edx + 32], xmm2 - por xmm0, xmm5 - pshufb xmm1, xmm4 - movdqu [edx], xmm0 - por xmm1, xmm5 - palignr xmm3, xmm3, 4 // xmm3 = { xmm3[4:15]} - pshufb xmm3, xmm4 - movdqu [edx + 16], xmm1 - por xmm3, xmm5 - movdqu [edx + 48], xmm3 - lea edx, [edx + 64] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void RAWToRGB24Row_SSSE3(const uint8* src_raw, uint8* dst_rgb24, int width) { - __asm { - mov eax, [esp + 4] // src_raw - mov edx, [esp + 8] // dst_rgb24 - mov ecx, [esp + 12] // width - movdqa xmm3, xmmword ptr kShuffleMaskRAWToRGB24_0 - movdqa xmm4, xmmword ptr kShuffleMaskRAWToRGB24_1 - movdqa xmm5, xmmword ptr kShuffleMaskRAWToRGB24_2 - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 4] - movdqu xmm2, [eax + 8] - lea eax, [eax + 24] - pshufb xmm0, xmm3 - pshufb xmm1, xmm4 - pshufb xmm2, xmm5 - movq qword ptr [edx], xmm0 - movq qword ptr [edx + 8], xmm1 - movq qword ptr [edx + 16], xmm2 - lea edx, [edx + 24] - sub ecx, 8 - jg convertloop - ret - } -} - -// pmul method to replicate bits. -// Math to replicate bits: -// (v << 8) | (v << 3) -// v * 256 + v * 8 -// v * (256 + 8) -// G shift of 5 is incorporated, so shift is 5 + 8 and 5 + 3 -// 20 instructions. -__declspec(naked) -void RGB565ToARGBRow_SSE2(const uint8* src_rgb565, uint8* dst_argb, - int width) { - __asm { - mov eax, 0x01080108 // generate multiplier to repeat 5 bits - movd xmm5, eax - pshufd xmm5, xmm5, 0 - mov eax, 0x20802080 // multiplier shift by 5 and then repeat 6 bits - movd xmm6, eax - pshufd xmm6, xmm6, 0 - pcmpeqb xmm3, xmm3 // generate mask 0xf800f800 for Red - psllw xmm3, 11 - pcmpeqb xmm4, xmm4 // generate mask 0x07e007e0 for Green - psllw xmm4, 10 - psrlw xmm4, 5 - pcmpeqb xmm7, xmm7 // generate mask 0xff00ff00 for Alpha - psllw xmm7, 8 - - mov eax, [esp + 4] // src_rgb565 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - sub edx, eax - sub edx, eax - - convertloop: - movdqu xmm0, [eax] // fetch 8 pixels of bgr565 - movdqa xmm1, xmm0 - movdqa xmm2, xmm0 - pand xmm1, xmm3 // R in upper 5 bits - psllw xmm2, 11 // B in upper 5 bits - pmulhuw xmm1, xmm5 // * (256 + 8) - pmulhuw xmm2, xmm5 // * (256 + 8) - psllw xmm1, 8 - por xmm1, xmm2 // RB - pand xmm0, xmm4 // G in middle 6 bits - pmulhuw xmm0, xmm6 // << 5 * (256 + 4) - por xmm0, xmm7 // AG - movdqa xmm2, xmm1 - punpcklbw xmm1, xmm0 - punpckhbw xmm2, xmm0 - movdqu [eax * 2 + edx], xmm1 // store 4 pixels of ARGB - movdqu [eax * 2 + edx + 16], xmm2 // store next 4 pixels of ARGB - lea eax, [eax + 16] - sub ecx, 8 - jg convertloop - ret - } -} - -#ifdef HAS_RGB565TOARGBROW_AVX2 -// pmul method to replicate bits. -// Math to replicate bits: -// (v << 8) | (v << 3) -// v * 256 + v * 8 -// v * (256 + 8) -// G shift of 5 is incorporated, so shift is 5 + 8 and 5 + 3 -__declspec(naked) -void RGB565ToARGBRow_AVX2(const uint8* src_rgb565, uint8* dst_argb, - int width) { - __asm { - mov eax, 0x01080108 // generate multiplier to repeat 5 bits - vmovd xmm5, eax - vbroadcastss ymm5, xmm5 - mov eax, 0x20802080 // multiplier shift by 5 and then repeat 6 bits - vmovd xmm6, eax - vbroadcastss ymm6, xmm6 - vpcmpeqb ymm3, ymm3, ymm3 // generate mask 0xf800f800 for Red - vpsllw ymm3, ymm3, 11 - vpcmpeqb ymm4, ymm4, ymm4 // generate mask 0x07e007e0 for Green - vpsllw ymm4, ymm4, 10 - vpsrlw ymm4, ymm4, 5 - vpcmpeqb ymm7, ymm7, ymm7 // generate mask 0xff00ff00 for Alpha - vpsllw ymm7, ymm7, 8 - - mov eax, [esp + 4] // src_rgb565 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - sub edx, eax - sub edx, eax - - convertloop: - vmovdqu ymm0, [eax] // fetch 16 pixels of bgr565 - vpand ymm1, ymm0, ymm3 // R in upper 5 bits - vpsllw ymm2, ymm0, 11 // B in upper 5 bits - vpmulhuw ymm1, ymm1, ymm5 // * (256 + 8) - vpmulhuw ymm2, ymm2, ymm5 // * (256 + 8) - vpsllw ymm1, ymm1, 8 - vpor ymm1, ymm1, ymm2 // RB - vpand ymm0, ymm0, ymm4 // G in middle 6 bits - vpmulhuw ymm0, ymm0, ymm6 // << 5 * (256 + 4) - vpor ymm0, ymm0, ymm7 // AG - vpermq ymm0, ymm0, 0xd8 // mutate for unpack - vpermq ymm1, ymm1, 0xd8 - vpunpckhbw ymm2, ymm1, ymm0 - vpunpcklbw ymm1, ymm1, ymm0 - vmovdqu [eax * 2 + edx], ymm1 // store 4 pixels of ARGB - vmovdqu [eax * 2 + edx + 32], ymm2 // store next 4 pixels of ARGB - lea eax, [eax + 32] - sub ecx, 16 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_RGB565TOARGBROW_AVX2 - -#ifdef HAS_ARGB1555TOARGBROW_AVX2 -__declspec(naked) -void ARGB1555ToARGBRow_AVX2(const uint8* src_argb1555, uint8* dst_argb, - int width) { - __asm { - mov eax, 0x01080108 // generate multiplier to repeat 5 bits - vmovd xmm5, eax - vbroadcastss ymm5, xmm5 - mov eax, 0x42004200 // multiplier shift by 6 and then repeat 5 bits - vmovd xmm6, eax - vbroadcastss ymm6, xmm6 - vpcmpeqb ymm3, ymm3, ymm3 // generate mask 0xf800f800 for Red - vpsllw ymm3, ymm3, 11 - vpsrlw ymm4, ymm3, 6 // generate mask 0x03e003e0 for Green - vpcmpeqb ymm7, ymm7, ymm7 // generate mask 0xff00ff00 for Alpha - vpsllw ymm7, ymm7, 8 - - mov eax, [esp + 4] // src_argb1555 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - sub edx, eax - sub edx, eax - - convertloop: - vmovdqu ymm0, [eax] // fetch 16 pixels of 1555 - vpsllw ymm1, ymm0, 1 // R in upper 5 bits - vpsllw ymm2, ymm0, 11 // B in upper 5 bits - vpand ymm1, ymm1, ymm3 - vpmulhuw ymm2, ymm2, ymm5 // * (256 + 8) - vpmulhuw ymm1, ymm1, ymm5 // * (256 + 8) - vpsllw ymm1, ymm1, 8 - vpor ymm1, ymm1, ymm2 // RB - vpsraw ymm2, ymm0, 8 // A - vpand ymm0, ymm0, ymm4 // G in middle 5 bits - vpmulhuw ymm0, ymm0, ymm6 // << 6 * (256 + 8) - vpand ymm2, ymm2, ymm7 - vpor ymm0, ymm0, ymm2 // AG - vpermq ymm0, ymm0, 0xd8 // mutate for unpack - vpermq ymm1, ymm1, 0xd8 - vpunpckhbw ymm2, ymm1, ymm0 - vpunpcklbw ymm1, ymm1, ymm0 - vmovdqu [eax * 2 + edx], ymm1 // store 8 pixels of ARGB - vmovdqu [eax * 2 + edx + 32], ymm2 // store next 8 pixels of ARGB - lea eax, [eax + 32] - sub ecx, 16 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGB1555TOARGBROW_AVX2 - -#ifdef HAS_ARGB4444TOARGBROW_AVX2 -__declspec(naked) -void ARGB4444ToARGBRow_AVX2(const uint8* src_argb4444, uint8* dst_argb, - int width) { - __asm { - mov eax, 0x0f0f0f0f // generate mask 0x0f0f0f0f - vmovd xmm4, eax - vbroadcastss ymm4, xmm4 - vpslld ymm5, ymm4, 4 // 0xf0f0f0f0 for high nibbles - mov eax, [esp + 4] // src_argb4444 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - sub edx, eax - sub edx, eax - - convertloop: - vmovdqu ymm0, [eax] // fetch 16 pixels of bgra4444 - vpand ymm2, ymm0, ymm5 // mask high nibbles - vpand ymm0, ymm0, ymm4 // mask low nibbles - vpsrlw ymm3, ymm2, 4 - vpsllw ymm1, ymm0, 4 - vpor ymm2, ymm2, ymm3 - vpor ymm0, ymm0, ymm1 - vpermq ymm0, ymm0, 0xd8 // mutate for unpack - vpermq ymm2, ymm2, 0xd8 - vpunpckhbw ymm1, ymm0, ymm2 - vpunpcklbw ymm0, ymm0, ymm2 - vmovdqu [eax * 2 + edx], ymm0 // store 8 pixels of ARGB - vmovdqu [eax * 2 + edx + 32], ymm1 // store next 8 pixels of ARGB - lea eax, [eax + 32] - sub ecx, 16 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGB4444TOARGBROW_AVX2 - -// 24 instructions -__declspec(naked) -void ARGB1555ToARGBRow_SSE2(const uint8* src_argb1555, uint8* dst_argb, - int width) { - __asm { - mov eax, 0x01080108 // generate multiplier to repeat 5 bits - movd xmm5, eax - pshufd xmm5, xmm5, 0 - mov eax, 0x42004200 // multiplier shift by 6 and then repeat 5 bits - movd xmm6, eax - pshufd xmm6, xmm6, 0 - pcmpeqb xmm3, xmm3 // generate mask 0xf800f800 for Red - psllw xmm3, 11 - movdqa xmm4, xmm3 // generate mask 0x03e003e0 for Green - psrlw xmm4, 6 - pcmpeqb xmm7, xmm7 // generate mask 0xff00ff00 for Alpha - psllw xmm7, 8 - - mov eax, [esp + 4] // src_argb1555 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - sub edx, eax - sub edx, eax - - convertloop: - movdqu xmm0, [eax] // fetch 8 pixels of 1555 - movdqa xmm1, xmm0 - movdqa xmm2, xmm0 - psllw xmm1, 1 // R in upper 5 bits - psllw xmm2, 11 // B in upper 5 bits - pand xmm1, xmm3 - pmulhuw xmm2, xmm5 // * (256 + 8) - pmulhuw xmm1, xmm5 // * (256 + 8) - psllw xmm1, 8 - por xmm1, xmm2 // RB - movdqa xmm2, xmm0 - pand xmm0, xmm4 // G in middle 5 bits - psraw xmm2, 8 // A - pmulhuw xmm0, xmm6 // << 6 * (256 + 8) - pand xmm2, xmm7 - por xmm0, xmm2 // AG - movdqa xmm2, xmm1 - punpcklbw xmm1, xmm0 - punpckhbw xmm2, xmm0 - movdqu [eax * 2 + edx], xmm1 // store 4 pixels of ARGB - movdqu [eax * 2 + edx + 16], xmm2 // store next 4 pixels of ARGB - lea eax, [eax + 16] - sub ecx, 8 - jg convertloop - ret - } -} - -// 18 instructions. -__declspec(naked) -void ARGB4444ToARGBRow_SSE2(const uint8* src_argb4444, uint8* dst_argb, - int width) { - __asm { - mov eax, 0x0f0f0f0f // generate mask 0x0f0f0f0f - movd xmm4, eax - pshufd xmm4, xmm4, 0 - movdqa xmm5, xmm4 // 0xf0f0f0f0 for high nibbles - pslld xmm5, 4 - mov eax, [esp + 4] // src_argb4444 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - sub edx, eax - sub edx, eax - - convertloop: - movdqu xmm0, [eax] // fetch 8 pixels of bgra4444 - movdqa xmm2, xmm0 - pand xmm0, xmm4 // mask low nibbles - pand xmm2, xmm5 // mask high nibbles - movdqa xmm1, xmm0 - movdqa xmm3, xmm2 - psllw xmm1, 4 - psrlw xmm3, 4 - por xmm0, xmm1 - por xmm2, xmm3 - movdqa xmm1, xmm0 - punpcklbw xmm0, xmm2 - punpckhbw xmm1, xmm2 - movdqu [eax * 2 + edx], xmm0 // store 4 pixels of ARGB - movdqu [eax * 2 + edx + 16], xmm1 // store next 4 pixels of ARGB - lea eax, [eax + 16] - sub ecx, 8 - jg convertloop - ret - } -} - -__declspec(naked) -void ARGBToRGB24Row_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - mov ecx, [esp + 12] // width - movdqa xmm6, xmmword ptr kShuffleMaskARGBToRGB24 - - convertloop: - movdqu xmm0, [eax] // fetch 16 pixels of argb - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - lea eax, [eax + 64] - pshufb xmm0, xmm6 // pack 16 bytes of ARGB to 12 bytes of RGB - pshufb xmm1, xmm6 - pshufb xmm2, xmm6 - pshufb xmm3, xmm6 - movdqa xmm4, xmm1 // 4 bytes from 1 for 0 - psrldq xmm1, 4 // 8 bytes from 1 - pslldq xmm4, 12 // 4 bytes from 1 for 0 - movdqa xmm5, xmm2 // 8 bytes from 2 for 1 - por xmm0, xmm4 // 4 bytes from 1 for 0 - pslldq xmm5, 8 // 8 bytes from 2 for 1 - movdqu [edx], xmm0 // store 0 - por xmm1, xmm5 // 8 bytes from 2 for 1 - psrldq xmm2, 8 // 4 bytes from 2 - pslldq xmm3, 4 // 12 bytes from 3 for 2 - por xmm2, xmm3 // 12 bytes from 3 for 2 - movdqu [edx + 16], xmm1 // store 1 - movdqu [edx + 32], xmm2 // store 2 - lea edx, [edx + 48] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void ARGBToRAWRow_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - mov ecx, [esp + 12] // width - movdqa xmm6, xmmword ptr kShuffleMaskARGBToRAW - - convertloop: - movdqu xmm0, [eax] // fetch 16 pixels of argb - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - lea eax, [eax + 64] - pshufb xmm0, xmm6 // pack 16 bytes of ARGB to 12 bytes of RGB - pshufb xmm1, xmm6 - pshufb xmm2, xmm6 - pshufb xmm3, xmm6 - movdqa xmm4, xmm1 // 4 bytes from 1 for 0 - psrldq xmm1, 4 // 8 bytes from 1 - pslldq xmm4, 12 // 4 bytes from 1 for 0 - movdqa xmm5, xmm2 // 8 bytes from 2 for 1 - por xmm0, xmm4 // 4 bytes from 1 for 0 - pslldq xmm5, 8 // 8 bytes from 2 for 1 - movdqu [edx], xmm0 // store 0 - por xmm1, xmm5 // 8 bytes from 2 for 1 - psrldq xmm2, 8 // 4 bytes from 2 - pslldq xmm3, 4 // 12 bytes from 3 for 2 - por xmm2, xmm3 // 12 bytes from 3 for 2 - movdqu [edx + 16], xmm1 // store 1 - movdqu [edx + 32], xmm2 // store 2 - lea edx, [edx + 48] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void ARGBToRGB565Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - mov ecx, [esp + 12] // width - pcmpeqb xmm3, xmm3 // generate mask 0x0000001f - psrld xmm3, 27 - pcmpeqb xmm4, xmm4 // generate mask 0x000007e0 - psrld xmm4, 26 - pslld xmm4, 5 - pcmpeqb xmm5, xmm5 // generate mask 0xfffff800 - pslld xmm5, 11 - - convertloop: - movdqu xmm0, [eax] // fetch 4 pixels of argb - movdqa xmm1, xmm0 // B - movdqa xmm2, xmm0 // G - pslld xmm0, 8 // R - psrld xmm1, 3 // B - psrld xmm2, 5 // G - psrad xmm0, 16 // R - pand xmm1, xmm3 // B - pand xmm2, xmm4 // G - pand xmm0, xmm5 // R - por xmm1, xmm2 // BG - por xmm0, xmm1 // BGR - packssdw xmm0, xmm0 - lea eax, [eax + 16] - movq qword ptr [edx], xmm0 // store 4 pixels of RGB565 - lea edx, [edx + 8] - sub ecx, 4 - jg convertloop - ret - } -} - -__declspec(naked) -void ARGBToRGB565DitherRow_SSE2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width) { - __asm { - - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - movd xmm6, [esp + 12] // dither4 - mov ecx, [esp + 16] // width - punpcklbw xmm6, xmm6 // make dither 16 bytes - movdqa xmm7, xmm6 - punpcklwd xmm6, xmm6 - punpckhwd xmm7, xmm7 - pcmpeqb xmm3, xmm3 // generate mask 0x0000001f - psrld xmm3, 27 - pcmpeqb xmm4, xmm4 // generate mask 0x000007e0 - psrld xmm4, 26 - pslld xmm4, 5 - pcmpeqb xmm5, xmm5 // generate mask 0xfffff800 - pslld xmm5, 11 - - convertloop: - movdqu xmm0, [eax] // fetch 4 pixels of argb - paddusb xmm0, xmm6 // add dither - movdqa xmm1, xmm0 // B - movdqa xmm2, xmm0 // G - pslld xmm0, 8 // R - psrld xmm1, 3 // B - psrld xmm2, 5 // G - psrad xmm0, 16 // R - pand xmm1, xmm3 // B - pand xmm2, xmm4 // G - pand xmm0, xmm5 // R - por xmm1, xmm2 // BG - por xmm0, xmm1 // BGR - packssdw xmm0, xmm0 - lea eax, [eax + 16] - movq qword ptr [edx], xmm0 // store 4 pixels of RGB565 - lea edx, [edx + 8] - sub ecx, 4 - jg convertloop - ret - } -} - -#ifdef HAS_ARGBTORGB565DITHERROW_AVX2 -__declspec(naked) -void ARGBToRGB565DitherRow_AVX2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - vbroadcastss xmm6, [esp + 12] // dither4 - mov ecx, [esp + 16] // width - vpunpcklbw xmm6, xmm6, xmm6 // make dither 32 bytes - vpermq ymm6, ymm6, 0xd8 - vpunpcklwd ymm6, ymm6, ymm6 - vpcmpeqb ymm3, ymm3, ymm3 // generate mask 0x0000001f - vpsrld ymm3, ymm3, 27 - vpcmpeqb ymm4, ymm4, ymm4 // generate mask 0x000007e0 - vpsrld ymm4, ymm4, 26 - vpslld ymm4, ymm4, 5 - vpslld ymm5, ymm3, 11 // generate mask 0x0000f800 - - convertloop: - vmovdqu ymm0, [eax] // fetch 8 pixels of argb - vpaddusb ymm0, ymm0, ymm6 // add dither - vpsrld ymm2, ymm0, 5 // G - vpsrld ymm1, ymm0, 3 // B - vpsrld ymm0, ymm0, 8 // R - vpand ymm2, ymm2, ymm4 // G - vpand ymm1, ymm1, ymm3 // B - vpand ymm0, ymm0, ymm5 // R - vpor ymm1, ymm1, ymm2 // BG - vpor ymm0, ymm0, ymm1 // BGR - vpackusdw ymm0, ymm0, ymm0 - vpermq ymm0, ymm0, 0xd8 - lea eax, [eax + 32] - vmovdqu [edx], xmm0 // store 8 pixels of RGB565 - lea edx, [edx + 16] - sub ecx, 8 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGBTORGB565DITHERROW_AVX2 - -// TODO(fbarchard): Improve sign extension/packing. -__declspec(naked) -void ARGBToARGB1555Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - mov ecx, [esp + 12] // width - pcmpeqb xmm4, xmm4 // generate mask 0x0000001f - psrld xmm4, 27 - movdqa xmm5, xmm4 // generate mask 0x000003e0 - pslld xmm5, 5 - movdqa xmm6, xmm4 // generate mask 0x00007c00 - pslld xmm6, 10 - pcmpeqb xmm7, xmm7 // generate mask 0xffff8000 - pslld xmm7, 15 - - convertloop: - movdqu xmm0, [eax] // fetch 4 pixels of argb - movdqa xmm1, xmm0 // B - movdqa xmm2, xmm0 // G - movdqa xmm3, xmm0 // R - psrad xmm0, 16 // A - psrld xmm1, 3 // B - psrld xmm2, 6 // G - psrld xmm3, 9 // R - pand xmm0, xmm7 // A - pand xmm1, xmm4 // B - pand xmm2, xmm5 // G - pand xmm3, xmm6 // R - por xmm0, xmm1 // BA - por xmm2, xmm3 // GR - por xmm0, xmm2 // BGRA - packssdw xmm0, xmm0 - lea eax, [eax + 16] - movq qword ptr [edx], xmm0 // store 4 pixels of ARGB1555 - lea edx, [edx + 8] - sub ecx, 4 - jg convertloop - ret - } -} - -__declspec(naked) -void ARGBToARGB4444Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - mov ecx, [esp + 12] // width - pcmpeqb xmm4, xmm4 // generate mask 0xf000f000 - psllw xmm4, 12 - movdqa xmm3, xmm4 // generate mask 0x00f000f0 - psrlw xmm3, 8 - - convertloop: - movdqu xmm0, [eax] // fetch 4 pixels of argb - movdqa xmm1, xmm0 - pand xmm0, xmm3 // low nibble - pand xmm1, xmm4 // high nibble - psrld xmm0, 4 - psrld xmm1, 8 - por xmm0, xmm1 - packuswb xmm0, xmm0 - lea eax, [eax + 16] - movq qword ptr [edx], xmm0 // store 4 pixels of ARGB4444 - lea edx, [edx + 8] - sub ecx, 4 - jg convertloop - ret - } -} - -#ifdef HAS_ARGBTORGB565ROW_AVX2 -__declspec(naked) -void ARGBToRGB565Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - mov ecx, [esp + 12] // width - vpcmpeqb ymm3, ymm3, ymm3 // generate mask 0x0000001f - vpsrld ymm3, ymm3, 27 - vpcmpeqb ymm4, ymm4, ymm4 // generate mask 0x000007e0 - vpsrld ymm4, ymm4, 26 - vpslld ymm4, ymm4, 5 - vpslld ymm5, ymm3, 11 // generate mask 0x0000f800 - - convertloop: - vmovdqu ymm0, [eax] // fetch 8 pixels of argb - vpsrld ymm2, ymm0, 5 // G - vpsrld ymm1, ymm0, 3 // B - vpsrld ymm0, ymm0, 8 // R - vpand ymm2, ymm2, ymm4 // G - vpand ymm1, ymm1, ymm3 // B - vpand ymm0, ymm0, ymm5 // R - vpor ymm1, ymm1, ymm2 // BG - vpor ymm0, ymm0, ymm1 // BGR - vpackusdw ymm0, ymm0, ymm0 - vpermq ymm0, ymm0, 0xd8 - lea eax, [eax + 32] - vmovdqu [edx], xmm0 // store 8 pixels of RGB565 - lea edx, [edx + 16] - sub ecx, 8 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGBTORGB565ROW_AVX2 - -#ifdef HAS_ARGBTOARGB1555ROW_AVX2 -__declspec(naked) -void ARGBToARGB1555Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - mov ecx, [esp + 12] // width - vpcmpeqb ymm4, ymm4, ymm4 - vpsrld ymm4, ymm4, 27 // generate mask 0x0000001f - vpslld ymm5, ymm4, 5 // generate mask 0x000003e0 - vpslld ymm6, ymm4, 10 // generate mask 0x00007c00 - vpcmpeqb ymm7, ymm7, ymm7 // generate mask 0xffff8000 - vpslld ymm7, ymm7, 15 - - convertloop: - vmovdqu ymm0, [eax] // fetch 8 pixels of argb - vpsrld ymm3, ymm0, 9 // R - vpsrld ymm2, ymm0, 6 // G - vpsrld ymm1, ymm0, 3 // B - vpsrad ymm0, ymm0, 16 // A - vpand ymm3, ymm3, ymm6 // R - vpand ymm2, ymm2, ymm5 // G - vpand ymm1, ymm1, ymm4 // B - vpand ymm0, ymm0, ymm7 // A - vpor ymm0, ymm0, ymm1 // BA - vpor ymm2, ymm2, ymm3 // GR - vpor ymm0, ymm0, ymm2 // BGRA - vpackssdw ymm0, ymm0, ymm0 - vpermq ymm0, ymm0, 0xd8 - lea eax, [eax + 32] - vmovdqu [edx], xmm0 // store 8 pixels of ARGB1555 - lea edx, [edx + 16] - sub ecx, 8 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGBTOARGB1555ROW_AVX2 - -#ifdef HAS_ARGBTOARGB4444ROW_AVX2 -__declspec(naked) -void ARGBToARGB4444Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_rgb - mov ecx, [esp + 12] // width - vpcmpeqb ymm4, ymm4, ymm4 // generate mask 0xf000f000 - vpsllw ymm4, ymm4, 12 - vpsrlw ymm3, ymm4, 8 // generate mask 0x00f000f0 - - convertloop: - vmovdqu ymm0, [eax] // fetch 8 pixels of argb - vpand ymm1, ymm0, ymm4 // high nibble - vpand ymm0, ymm0, ymm3 // low nibble - vpsrld ymm1, ymm1, 8 - vpsrld ymm0, ymm0, 4 - vpor ymm0, ymm0, ymm1 - vpackuswb ymm0, ymm0, ymm0 - vpermq ymm0, ymm0, 0xd8 - lea eax, [eax + 32] - vmovdqu [edx], xmm0 // store 8 pixels of ARGB4444 - lea edx, [edx + 16] - sub ecx, 8 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGBTOARGB4444ROW_AVX2 - -// Convert 16 ARGB pixels (64 bytes) to 16 Y values. -__declspec(naked) -void ARGBToYRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_y */ - mov ecx, [esp + 12] /* width */ - movdqa xmm4, xmmword ptr kARGBToY - movdqa xmm5, xmmword ptr kAddY16 - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - pmaddubsw xmm0, xmm4 - pmaddubsw xmm1, xmm4 - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - lea eax, [eax + 64] - phaddw xmm0, xmm1 - phaddw xmm2, xmm3 - psrlw xmm0, 7 - psrlw xmm2, 7 - packuswb xmm0, xmm2 - paddb xmm0, xmm5 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - ret - } -} - -// Convert 16 ARGB pixels (64 bytes) to 16 YJ values. -// Same as ARGBToYRow but different coefficients, no add 16, but do rounding. -__declspec(naked) -void ARGBToYJRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_y */ - mov ecx, [esp + 12] /* width */ - movdqa xmm4, xmmword ptr kARGBToYJ - movdqa xmm5, xmmword ptr kAddYJ64 - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - pmaddubsw xmm0, xmm4 - pmaddubsw xmm1, xmm4 - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - lea eax, [eax + 64] - phaddw xmm0, xmm1 - phaddw xmm2, xmm3 - paddw xmm0, xmm5 // Add .5 for rounding. - paddw xmm2, xmm5 - psrlw xmm0, 7 - psrlw xmm2, 7 - packuswb xmm0, xmm2 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - ret - } -} - -#ifdef HAS_ARGBTOYROW_AVX2 -// vpermd for vphaddw + vpackuswb vpermd. -static const lvec32 kPermdARGBToY_AVX = { - 0, 4, 1, 5, 2, 6, 3, 7 -}; - -// Convert 32 ARGB pixels (128 bytes) to 32 Y values. -__declspec(naked) -void ARGBToYRow_AVX2(const uint8* src_argb, uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_y */ - mov ecx, [esp + 12] /* width */ - vbroadcastf128 ymm4, xmmword ptr kARGBToY - vbroadcastf128 ymm5, xmmword ptr kAddY16 - vmovdqu ymm6, ymmword ptr kPermdARGBToY_AVX - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - vmovdqu ymm2, [eax + 64] - vmovdqu ymm3, [eax + 96] - vpmaddubsw ymm0, ymm0, ymm4 - vpmaddubsw ymm1, ymm1, ymm4 - vpmaddubsw ymm2, ymm2, ymm4 - vpmaddubsw ymm3, ymm3, ymm4 - lea eax, [eax + 128] - vphaddw ymm0, ymm0, ymm1 // mutates. - vphaddw ymm2, ymm2, ymm3 - vpsrlw ymm0, ymm0, 7 - vpsrlw ymm2, ymm2, 7 - vpackuswb ymm0, ymm0, ymm2 // mutates. - vpermd ymm0, ymm6, ymm0 // For vphaddw + vpackuswb mutation. - vpaddb ymm0, ymm0, ymm5 // add 16 for Y - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 32 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGBTOYROW_AVX2 - -#ifdef HAS_ARGBTOYJROW_AVX2 -// Convert 32 ARGB pixels (128 bytes) to 32 Y values. -__declspec(naked) -void ARGBToYJRow_AVX2(const uint8* src_argb, uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_y */ - mov ecx, [esp + 12] /* width */ - vbroadcastf128 ymm4, xmmword ptr kARGBToYJ - vbroadcastf128 ymm5, xmmword ptr kAddYJ64 - vmovdqu ymm6, ymmword ptr kPermdARGBToY_AVX - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - vmovdqu ymm2, [eax + 64] - vmovdqu ymm3, [eax + 96] - vpmaddubsw ymm0, ymm0, ymm4 - vpmaddubsw ymm1, ymm1, ymm4 - vpmaddubsw ymm2, ymm2, ymm4 - vpmaddubsw ymm3, ymm3, ymm4 - lea eax, [eax + 128] - vphaddw ymm0, ymm0, ymm1 // mutates. - vphaddw ymm2, ymm2, ymm3 - vpaddw ymm0, ymm0, ymm5 // Add .5 for rounding. - vpaddw ymm2, ymm2, ymm5 - vpsrlw ymm0, ymm0, 7 - vpsrlw ymm2, ymm2, 7 - vpackuswb ymm0, ymm0, ymm2 // mutates. - vpermd ymm0, ymm6, ymm0 // For vphaddw + vpackuswb mutation. - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 32 - jg convertloop - - vzeroupper - ret - } -} -#endif // HAS_ARGBTOYJROW_AVX2 - -__declspec(naked) -void BGRAToYRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_y */ - mov ecx, [esp + 12] /* width */ - movdqa xmm4, xmmword ptr kBGRAToY - movdqa xmm5, xmmword ptr kAddY16 - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - pmaddubsw xmm0, xmm4 - pmaddubsw xmm1, xmm4 - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - lea eax, [eax + 64] - phaddw xmm0, xmm1 - phaddw xmm2, xmm3 - psrlw xmm0, 7 - psrlw xmm2, 7 - packuswb xmm0, xmm2 - paddb xmm0, xmm5 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void ABGRToYRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_y */ - mov ecx, [esp + 12] /* width */ - movdqa xmm4, xmmword ptr kABGRToY - movdqa xmm5, xmmword ptr kAddY16 - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - pmaddubsw xmm0, xmm4 - pmaddubsw xmm1, xmm4 - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - lea eax, [eax + 64] - phaddw xmm0, xmm1 - phaddw xmm2, xmm3 - psrlw xmm0, 7 - psrlw xmm2, 7 - packuswb xmm0, xmm2 - paddb xmm0, xmm5 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void RGBAToYRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_y */ - mov ecx, [esp + 12] /* width */ - movdqa xmm4, xmmword ptr kRGBAToY - movdqa xmm5, xmmword ptr kAddY16 - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - pmaddubsw xmm0, xmm4 - pmaddubsw xmm1, xmm4 - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - lea eax, [eax + 64] - phaddw xmm0, xmm1 - phaddw xmm2, xmm3 - psrlw xmm0, 7 - psrlw xmm2, 7 - packuswb xmm0, xmm2 - paddb xmm0, xmm5 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void ARGBToUVRow_SSSE3(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_argb - mov esi, [esp + 8 + 8] // src_stride_argb - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - movdqa xmm5, xmmword ptr kAddUV128 - movdqa xmm6, xmmword ptr kARGBToV - movdqa xmm7, xmmword ptr kARGBToU - sub edi, edx // stride from u to v - - convertloop: - /* step 1 - subsample 16x2 argb pixels to 8x1 */ - movdqu xmm0, [eax] - movdqu xmm4, [eax + esi] - pavgb xmm0, xmm4 - movdqu xmm1, [eax + 16] - movdqu xmm4, [eax + esi + 16] - pavgb xmm1, xmm4 - movdqu xmm2, [eax + 32] - movdqu xmm4, [eax + esi + 32] - pavgb xmm2, xmm4 - movdqu xmm3, [eax + 48] - movdqu xmm4, [eax + esi + 48] - pavgb xmm3, xmm4 - - lea eax, [eax + 64] - movdqa xmm4, xmm0 - shufps xmm0, xmm1, 0x88 - shufps xmm4, xmm1, 0xdd - pavgb xmm0, xmm4 - movdqa xmm4, xmm2 - shufps xmm2, xmm3, 0x88 - shufps xmm4, xmm3, 0xdd - pavgb xmm2, xmm4 - - // step 2 - convert to U and V - // from here down is very similar to Y code except - // instead of 16 different pixels, its 8 pixels of U and 8 of V - movdqa xmm1, xmm0 - movdqa xmm3, xmm2 - pmaddubsw xmm0, xmm7 // U - pmaddubsw xmm2, xmm7 - pmaddubsw xmm1, xmm6 // V - pmaddubsw xmm3, xmm6 - phaddw xmm0, xmm2 - phaddw xmm1, xmm3 - psraw xmm0, 8 - psraw xmm1, 8 - packsswb xmm0, xmm1 - paddb xmm0, xmm5 // -> unsigned - - // step 3 - store 8 U and 8 V values - movlps qword ptr [edx], xmm0 // U - movhps qword ptr [edx + edi], xmm0 // V - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} - -__declspec(naked) -void ARGBToUVJRow_SSSE3(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_argb - mov esi, [esp + 8 + 8] // src_stride_argb - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - movdqa xmm5, xmmword ptr kAddUVJ128 - movdqa xmm6, xmmword ptr kARGBToVJ - movdqa xmm7, xmmword ptr kARGBToUJ - sub edi, edx // stride from u to v - - convertloop: - /* step 1 - subsample 16x2 argb pixels to 8x1 */ - movdqu xmm0, [eax] - movdqu xmm4, [eax + esi] - pavgb xmm0, xmm4 - movdqu xmm1, [eax + 16] - movdqu xmm4, [eax + esi + 16] - pavgb xmm1, xmm4 - movdqu xmm2, [eax + 32] - movdqu xmm4, [eax + esi + 32] - pavgb xmm2, xmm4 - movdqu xmm3, [eax + 48] - movdqu xmm4, [eax + esi + 48] - pavgb xmm3, xmm4 - - lea eax, [eax + 64] - movdqa xmm4, xmm0 - shufps xmm0, xmm1, 0x88 - shufps xmm4, xmm1, 0xdd - pavgb xmm0, xmm4 - movdqa xmm4, xmm2 - shufps xmm2, xmm3, 0x88 - shufps xmm4, xmm3, 0xdd - pavgb xmm2, xmm4 - - // step 2 - convert to U and V - // from here down is very similar to Y code except - // instead of 16 different pixels, its 8 pixels of U and 8 of V - movdqa xmm1, xmm0 - movdqa xmm3, xmm2 - pmaddubsw xmm0, xmm7 // U - pmaddubsw xmm2, xmm7 - pmaddubsw xmm1, xmm6 // V - pmaddubsw xmm3, xmm6 - phaddw xmm0, xmm2 - phaddw xmm1, xmm3 - paddw xmm0, xmm5 // +.5 rounding -> unsigned - paddw xmm1, xmm5 - psraw xmm0, 8 - psraw xmm1, 8 - packsswb xmm0, xmm1 - - // step 3 - store 8 U and 8 V values - movlps qword ptr [edx], xmm0 // U - movhps qword ptr [edx + edi], xmm0 // V - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} - -#ifdef HAS_ARGBTOUVROW_AVX2 -__declspec(naked) -void ARGBToUVRow_AVX2(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_argb - mov esi, [esp + 8 + 8] // src_stride_argb - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - vbroadcastf128 ymm5, xmmword ptr kAddUV128 - vbroadcastf128 ymm6, xmmword ptr kARGBToV - vbroadcastf128 ymm7, xmmword ptr kARGBToU - sub edi, edx // stride from u to v - - convertloop: - /* step 1 - subsample 32x2 argb pixels to 16x1 */ - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - vmovdqu ymm2, [eax + 64] - vmovdqu ymm3, [eax + 96] - vpavgb ymm0, ymm0, [eax + esi] - vpavgb ymm1, ymm1, [eax + esi + 32] - vpavgb ymm2, ymm2, [eax + esi + 64] - vpavgb ymm3, ymm3, [eax + esi + 96] - lea eax, [eax + 128] - vshufps ymm4, ymm0, ymm1, 0x88 - vshufps ymm0, ymm0, ymm1, 0xdd - vpavgb ymm0, ymm0, ymm4 // mutated by vshufps - vshufps ymm4, ymm2, ymm3, 0x88 - vshufps ymm2, ymm2, ymm3, 0xdd - vpavgb ymm2, ymm2, ymm4 // mutated by vshufps - - // step 2 - convert to U and V - // from here down is very similar to Y code except - // instead of 32 different pixels, its 16 pixels of U and 16 of V - vpmaddubsw ymm1, ymm0, ymm7 // U - vpmaddubsw ymm3, ymm2, ymm7 - vpmaddubsw ymm0, ymm0, ymm6 // V - vpmaddubsw ymm2, ymm2, ymm6 - vphaddw ymm1, ymm1, ymm3 // mutates - vphaddw ymm0, ymm0, ymm2 - vpsraw ymm1, ymm1, 8 - vpsraw ymm0, ymm0, 8 - vpacksswb ymm0, ymm1, ymm0 // mutates - vpermq ymm0, ymm0, 0xd8 // For vpacksswb - vpshufb ymm0, ymm0, ymmword ptr kShufARGBToUV_AVX // for vshufps/vphaddw - vpaddb ymm0, ymm0, ymm5 // -> unsigned - - // step 3 - store 16 U and 16 V values - vextractf128 [edx], ymm0, 0 // U - vextractf128 [edx + edi], ymm0, 1 // V - lea edx, [edx + 16] - sub ecx, 32 - jg convertloop - - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_ARGBTOUVROW_AVX2 - -#ifdef HAS_ARGBTOUVJROW_AVX2 -__declspec(naked) -void ARGBToUVJRow_AVX2(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_argb - mov esi, [esp + 8 + 8] // src_stride_argb - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - vbroadcastf128 ymm5, xmmword ptr kAddUV128 - vbroadcastf128 ymm6, xmmword ptr kARGBToV - vbroadcastf128 ymm7, xmmword ptr kARGBToU - sub edi, edx // stride from u to v - - convertloop: - /* step 1 - subsample 32x2 argb pixels to 16x1 */ - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - vmovdqu ymm2, [eax + 64] - vmovdqu ymm3, [eax + 96] - vpavgb ymm0, ymm0, [eax + esi] - vpavgb ymm1, ymm1, [eax + esi + 32] - vpavgb ymm2, ymm2, [eax + esi + 64] - vpavgb ymm3, ymm3, [eax + esi + 96] - lea eax, [eax + 128] - vshufps ymm4, ymm0, ymm1, 0x88 - vshufps ymm0, ymm0, ymm1, 0xdd - vpavgb ymm0, ymm0, ymm4 // mutated by vshufps - vshufps ymm4, ymm2, ymm3, 0x88 - vshufps ymm2, ymm2, ymm3, 0xdd - vpavgb ymm2, ymm2, ymm4 // mutated by vshufps - - // step 2 - convert to U and V - // from here down is very similar to Y code except - // instead of 32 different pixels, its 16 pixels of U and 16 of V - vpmaddubsw ymm1, ymm0, ymm7 // U - vpmaddubsw ymm3, ymm2, ymm7 - vpmaddubsw ymm0, ymm0, ymm6 // V - vpmaddubsw ymm2, ymm2, ymm6 - vphaddw ymm1, ymm1, ymm3 // mutates - vphaddw ymm0, ymm0, ymm2 - vpaddw ymm1, ymm1, ymm5 // +.5 rounding -> unsigned - vpaddw ymm0, ymm0, ymm5 - vpsraw ymm1, ymm1, 8 - vpsraw ymm0, ymm0, 8 - vpacksswb ymm0, ymm1, ymm0 // mutates - vpermq ymm0, ymm0, 0xd8 // For vpacksswb - vpshufb ymm0, ymm0, ymmword ptr kShufARGBToUV_AVX // for vshufps/vphaddw - - // step 3 - store 16 U and 16 V values - vextractf128 [edx], ymm0, 0 // U - vextractf128 [edx + edi], ymm0, 1 // V - lea edx, [edx + 16] - sub ecx, 32 - jg convertloop - - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_ARGBTOUVJROW_AVX2 - -__declspec(naked) -void ARGBToUV444Row_SSSE3(const uint8* src_argb0, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_argb - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - movdqa xmm5, xmmword ptr kAddUV128 - movdqa xmm6, xmmword ptr kARGBToV - movdqa xmm7, xmmword ptr kARGBToU - sub edi, edx // stride from u to v - - convertloop: - /* convert to U and V */ - movdqu xmm0, [eax] // U - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - pmaddubsw xmm0, xmm7 - pmaddubsw xmm1, xmm7 - pmaddubsw xmm2, xmm7 - pmaddubsw xmm3, xmm7 - phaddw xmm0, xmm1 - phaddw xmm2, xmm3 - psraw xmm0, 8 - psraw xmm2, 8 - packsswb xmm0, xmm2 - paddb xmm0, xmm5 - movdqu [edx], xmm0 - - movdqu xmm0, [eax] // V - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - pmaddubsw xmm0, xmm6 - pmaddubsw xmm1, xmm6 - pmaddubsw xmm2, xmm6 - pmaddubsw xmm3, xmm6 - phaddw xmm0, xmm1 - phaddw xmm2, xmm3 - psraw xmm0, 8 - psraw xmm2, 8 - packsswb xmm0, xmm2 - paddb xmm0, xmm5 - lea eax, [eax + 64] - movdqu [edx + edi], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - - pop edi - ret - } -} - -__declspec(naked) -void BGRAToUVRow_SSSE3(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_argb - mov esi, [esp + 8 + 8] // src_stride_argb - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - movdqa xmm5, xmmword ptr kAddUV128 - movdqa xmm6, xmmword ptr kBGRAToV - movdqa xmm7, xmmword ptr kBGRAToU - sub edi, edx // stride from u to v - - convertloop: - /* step 1 - subsample 16x2 argb pixels to 8x1 */ - movdqu xmm0, [eax] - movdqu xmm4, [eax + esi] - pavgb xmm0, xmm4 - movdqu xmm1, [eax + 16] - movdqu xmm4, [eax + esi + 16] - pavgb xmm1, xmm4 - movdqu xmm2, [eax + 32] - movdqu xmm4, [eax + esi + 32] - pavgb xmm2, xmm4 - movdqu xmm3, [eax + 48] - movdqu xmm4, [eax + esi + 48] - pavgb xmm3, xmm4 - - lea eax, [eax + 64] - movdqa xmm4, xmm0 - shufps xmm0, xmm1, 0x88 - shufps xmm4, xmm1, 0xdd - pavgb xmm0, xmm4 - movdqa xmm4, xmm2 - shufps xmm2, xmm3, 0x88 - shufps xmm4, xmm3, 0xdd - pavgb xmm2, xmm4 - - // step 2 - convert to U and V - // from here down is very similar to Y code except - // instead of 16 different pixels, its 8 pixels of U and 8 of V - movdqa xmm1, xmm0 - movdqa xmm3, xmm2 - pmaddubsw xmm0, xmm7 // U - pmaddubsw xmm2, xmm7 - pmaddubsw xmm1, xmm6 // V - pmaddubsw xmm3, xmm6 - phaddw xmm0, xmm2 - phaddw xmm1, xmm3 - psraw xmm0, 8 - psraw xmm1, 8 - packsswb xmm0, xmm1 - paddb xmm0, xmm5 // -> unsigned - - // step 3 - store 8 U and 8 V values - movlps qword ptr [edx], xmm0 // U - movhps qword ptr [edx + edi], xmm0 // V - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} - -__declspec(naked) -void ABGRToUVRow_SSSE3(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_argb - mov esi, [esp + 8 + 8] // src_stride_argb - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - movdqa xmm5, xmmword ptr kAddUV128 - movdqa xmm6, xmmword ptr kABGRToV - movdqa xmm7, xmmword ptr kABGRToU - sub edi, edx // stride from u to v - - convertloop: - /* step 1 - subsample 16x2 argb pixels to 8x1 */ - movdqu xmm0, [eax] - movdqu xmm4, [eax + esi] - pavgb xmm0, xmm4 - movdqu xmm1, [eax + 16] - movdqu xmm4, [eax + esi + 16] - pavgb xmm1, xmm4 - movdqu xmm2, [eax + 32] - movdqu xmm4, [eax + esi + 32] - pavgb xmm2, xmm4 - movdqu xmm3, [eax + 48] - movdqu xmm4, [eax + esi + 48] - pavgb xmm3, xmm4 - - lea eax, [eax + 64] - movdqa xmm4, xmm0 - shufps xmm0, xmm1, 0x88 - shufps xmm4, xmm1, 0xdd - pavgb xmm0, xmm4 - movdqa xmm4, xmm2 - shufps xmm2, xmm3, 0x88 - shufps xmm4, xmm3, 0xdd - pavgb xmm2, xmm4 - - // step 2 - convert to U and V - // from here down is very similar to Y code except - // instead of 16 different pixels, its 8 pixels of U and 8 of V - movdqa xmm1, xmm0 - movdqa xmm3, xmm2 - pmaddubsw xmm0, xmm7 // U - pmaddubsw xmm2, xmm7 - pmaddubsw xmm1, xmm6 // V - pmaddubsw xmm3, xmm6 - phaddw xmm0, xmm2 - phaddw xmm1, xmm3 - psraw xmm0, 8 - psraw xmm1, 8 - packsswb xmm0, xmm1 - paddb xmm0, xmm5 // -> unsigned - - // step 3 - store 8 U and 8 V values - movlps qword ptr [edx], xmm0 // U - movhps qword ptr [edx + edi], xmm0 // V - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} - -__declspec(naked) -void RGBAToUVRow_SSSE3(const uint8* src_argb0, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_argb - mov esi, [esp + 8 + 8] // src_stride_argb - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - movdqa xmm5, xmmword ptr kAddUV128 - movdqa xmm6, xmmword ptr kRGBAToV - movdqa xmm7, xmmword ptr kRGBAToU - sub edi, edx // stride from u to v - - convertloop: - /* step 1 - subsample 16x2 argb pixels to 8x1 */ - movdqu xmm0, [eax] - movdqu xmm4, [eax + esi] - pavgb xmm0, xmm4 - movdqu xmm1, [eax + 16] - movdqu xmm4, [eax + esi + 16] - pavgb xmm1, xmm4 - movdqu xmm2, [eax + 32] - movdqu xmm4, [eax + esi + 32] - pavgb xmm2, xmm4 - movdqu xmm3, [eax + 48] - movdqu xmm4, [eax + esi + 48] - pavgb xmm3, xmm4 - - lea eax, [eax + 64] - movdqa xmm4, xmm0 - shufps xmm0, xmm1, 0x88 - shufps xmm4, xmm1, 0xdd - pavgb xmm0, xmm4 - movdqa xmm4, xmm2 - shufps xmm2, xmm3, 0x88 - shufps xmm4, xmm3, 0xdd - pavgb xmm2, xmm4 - - // step 2 - convert to U and V - // from here down is very similar to Y code except - // instead of 16 different pixels, its 8 pixels of U and 8 of V - movdqa xmm1, xmm0 - movdqa xmm3, xmm2 - pmaddubsw xmm0, xmm7 // U - pmaddubsw xmm2, xmm7 - pmaddubsw xmm1, xmm6 // V - pmaddubsw xmm3, xmm6 - phaddw xmm0, xmm2 - phaddw xmm1, xmm3 - psraw xmm0, 8 - psraw xmm1, 8 - packsswb xmm0, xmm1 - paddb xmm0, xmm5 // -> unsigned - - // step 3 - store 8 U and 8 V values - movlps qword ptr [edx], xmm0 // U - movhps qword ptr [edx + edi], xmm0 // V - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} -#endif // HAS_ARGBTOYROW_SSSE3 - -// Read 16 UV from 444 -#define READYUV444_AVX2 __asm { \ - __asm vmovdqu xmm0, [esi] /* U */ \ - __asm vmovdqu xmm1, [esi + edi] /* V */ \ - __asm lea esi, [esi + 16] \ - __asm vpermq ymm0, ymm0, 0xd8 \ - __asm vpermq ymm1, ymm1, 0xd8 \ - __asm vpunpcklbw ymm0, ymm0, ymm1 /* UV */ \ - __asm vmovdqu xmm4, [eax] /* Y */ \ - __asm vpermq ymm4, ymm4, 0xd8 \ - __asm vpunpcklbw ymm4, ymm4, ymm4 \ - __asm lea eax, [eax + 16] \ - } - -// Read 8 UV from 422, upsample to 16 UV. -#define READYUV422_AVX2 __asm { \ - __asm vmovq xmm0, qword ptr [esi] /* U */ \ - __asm vmovq xmm1, qword ptr [esi + edi] /* V */ \ - __asm lea esi, [esi + 8] \ - __asm vpunpcklbw ymm0, ymm0, ymm1 /* UV */ \ - __asm vpermq ymm0, ymm0, 0xd8 \ - __asm vpunpcklwd ymm0, ymm0, ymm0 /* UVUV (upsample) */ \ - __asm vmovdqu xmm4, [eax] /* Y */ \ - __asm vpermq ymm4, ymm4, 0xd8 \ - __asm vpunpcklbw ymm4, ymm4, ymm4 \ - __asm lea eax, [eax + 16] \ - } - -// Read 8 UV from 422, upsample to 16 UV. With 16 Alpha. -#define READYUVA422_AVX2 __asm { \ - __asm vmovq xmm0, qword ptr [esi] /* U */ \ - __asm vmovq xmm1, qword ptr [esi + edi] /* V */ \ - __asm lea esi, [esi + 8] \ - __asm vpunpcklbw ymm0, ymm0, ymm1 /* UV */ \ - __asm vpermq ymm0, ymm0, 0xd8 \ - __asm vpunpcklwd ymm0, ymm0, ymm0 /* UVUV (upsample) */ \ - __asm vmovdqu xmm4, [eax] /* Y */ \ - __asm vpermq ymm4, ymm4, 0xd8 \ - __asm vpunpcklbw ymm4, ymm4, ymm4 \ - __asm lea eax, [eax + 16] \ - __asm vmovdqu xmm5, [ebp] /* A */ \ - __asm vpermq ymm5, ymm5, 0xd8 \ - __asm lea ebp, [ebp + 16] \ - } - -// Read 4 UV from 411, upsample to 16 UV. -#define READYUV411_AVX2 __asm { \ - __asm vmovd xmm0, dword ptr [esi] /* U */ \ - __asm vmovd xmm1, dword ptr [esi + edi] /* V */ \ - __asm lea esi, [esi + 4] \ - __asm vpunpcklbw ymm0, ymm0, ymm1 /* UV */ \ - __asm vpunpcklwd ymm0, ymm0, ymm0 /* UVUV (upsample) */ \ - __asm vpermq ymm0, ymm0, 0xd8 \ - __asm vpunpckldq ymm0, ymm0, ymm0 /* UVUVUVUV (upsample) */ \ - __asm vmovdqu xmm4, [eax] /* Y */ \ - __asm vpermq ymm4, ymm4, 0xd8 \ - __asm vpunpcklbw ymm4, ymm4, ymm4 \ - __asm lea eax, [eax + 16] \ - } - -// Read 8 UV from NV12, upsample to 16 UV. -#define READNV12_AVX2 __asm { \ - __asm vmovdqu xmm0, [esi] /* UV */ \ - __asm lea esi, [esi + 16] \ - __asm vpermq ymm0, ymm0, 0xd8 \ - __asm vpunpcklwd ymm0, ymm0, ymm0 /* UVUV (upsample) */ \ - __asm vmovdqu xmm4, [eax] /* Y */ \ - __asm vpermq ymm4, ymm4, 0xd8 \ - __asm vpunpcklbw ymm4, ymm4, ymm4 \ - __asm lea eax, [eax + 16] \ - } - -// Read 8 UV from NV21, upsample to 16 UV. -#define READNV21_AVX2 __asm { \ - __asm vmovdqu xmm0, [esi] /* UV */ \ - __asm lea esi, [esi + 16] \ - __asm vpermq ymm0, ymm0, 0xd8 \ - __asm vpshufb ymm0, ymm0, ymmword ptr kShuffleNV21 \ - __asm vmovdqu xmm4, [eax] /* Y */ \ - __asm vpermq ymm4, ymm4, 0xd8 \ - __asm vpunpcklbw ymm4, ymm4, ymm4 \ - __asm lea eax, [eax + 16] \ - } - -// Read 8 YUY2 with 16 Y and upsample 8 UV to 16 UV. -#define READYUY2_AVX2 __asm { \ - __asm vmovdqu ymm4, [eax] /* YUY2 */ \ - __asm vpshufb ymm4, ymm4, ymmword ptr kShuffleYUY2Y \ - __asm vmovdqu ymm0, [eax] /* UV */ \ - __asm vpshufb ymm0, ymm0, ymmword ptr kShuffleYUY2UV \ - __asm lea eax, [eax + 32] \ - } - -// Read 8 UYVY with 16 Y and upsample 8 UV to 16 UV. -#define READUYVY_AVX2 __asm { \ - __asm vmovdqu ymm4, [eax] /* UYVY */ \ - __asm vpshufb ymm4, ymm4, ymmword ptr kShuffleUYVYY \ - __asm vmovdqu ymm0, [eax] /* UV */ \ - __asm vpshufb ymm0, ymm0, ymmword ptr kShuffleUYVYUV \ - __asm lea eax, [eax + 32] \ - } - -// Convert 16 pixels: 16 UV and 16 Y. -#define YUVTORGB_AVX2(YuvConstants) __asm { \ - __asm vpmaddubsw ymm2, ymm0, ymmword ptr [YuvConstants + KUVTOR] /* R UV */\ - __asm vpmaddubsw ymm1, ymm0, ymmword ptr [YuvConstants + KUVTOG] /* G UV */\ - __asm vpmaddubsw ymm0, ymm0, ymmword ptr [YuvConstants + KUVTOB] /* B UV */\ - __asm vmovdqu ymm3, ymmword ptr [YuvConstants + KUVBIASR] \ - __asm vpsubw ymm2, ymm3, ymm2 \ - __asm vmovdqu ymm3, ymmword ptr [YuvConstants + KUVBIASG] \ - __asm vpsubw ymm1, ymm3, ymm1 \ - __asm vmovdqu ymm3, ymmword ptr [YuvConstants + KUVBIASB] \ - __asm vpsubw ymm0, ymm3, ymm0 \ - /* Step 2: Find Y contribution to 16 R,G,B values */ \ - __asm vpmulhuw ymm4, ymm4, ymmword ptr [YuvConstants + KYTORGB] \ - __asm vpaddsw ymm0, ymm0, ymm4 /* B += Y */ \ - __asm vpaddsw ymm1, ymm1, ymm4 /* G += Y */ \ - __asm vpaddsw ymm2, ymm2, ymm4 /* R += Y */ \ - __asm vpsraw ymm0, ymm0, 6 \ - __asm vpsraw ymm1, ymm1, 6 \ - __asm vpsraw ymm2, ymm2, 6 \ - __asm vpackuswb ymm0, ymm0, ymm0 /* B */ \ - __asm vpackuswb ymm1, ymm1, ymm1 /* G */ \ - __asm vpackuswb ymm2, ymm2, ymm2 /* R */ \ - } - -// Store 16 ARGB values. -#define STOREARGB_AVX2 __asm { \ - __asm vpunpcklbw ymm0, ymm0, ymm1 /* BG */ \ - __asm vpermq ymm0, ymm0, 0xd8 \ - __asm vpunpcklbw ymm2, ymm2, ymm5 /* RA */ \ - __asm vpermq ymm2, ymm2, 0xd8 \ - __asm vpunpcklwd ymm1, ymm0, ymm2 /* BGRA first 8 pixels */ \ - __asm vpunpckhwd ymm0, ymm0, ymm2 /* BGRA next 8 pixels */ \ - __asm vmovdqu 0[edx], ymm1 \ - __asm vmovdqu 32[edx], ymm0 \ - __asm lea edx, [edx + 64] \ - } - -// Store 16 RGBA values. -#define STORERGBA_AVX2 __asm { \ - __asm vpunpcklbw ymm1, ymm1, ymm2 /* GR */ \ - __asm vpermq ymm1, ymm1, 0xd8 \ - __asm vpunpcklbw ymm2, ymm5, ymm0 /* AB */ \ - __asm vpermq ymm2, ymm2, 0xd8 \ - __asm vpunpcklwd ymm0, ymm2, ymm1 /* ABGR first 8 pixels */ \ - __asm vpunpckhwd ymm1, ymm2, ymm1 /* ABGR next 8 pixels */ \ - __asm vmovdqu [edx], ymm0 \ - __asm vmovdqu [edx + 32], ymm1 \ - __asm lea edx, [edx + 64] \ - } - -#ifdef HAS_I422TOARGBROW_AVX2 -// 16 pixels -// 8 UV values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). -__declspec(naked) -void I422ToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // argb - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - vpcmpeqb ymm5, ymm5, ymm5 // generate 0xffffffffffffffff for alpha - - convertloop: - READYUV422_AVX2 - YUVTORGB_AVX2(ebx) - STOREARGB_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebx - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_I422TOARGBROW_AVX2 - -#ifdef HAS_I422ALPHATOARGBROW_AVX2 -// 16 pixels -// 8 UV values upsampled to 16 UV, mixed with 16 Y and 16 A producing 16 ARGB. -__declspec(naked) -void I422AlphaToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - push ebp - mov eax, [esp + 16 + 4] // Y - mov esi, [esp + 16 + 8] // U - mov edi, [esp + 16 + 12] // V - mov ebp, [esp + 16 + 16] // A - mov edx, [esp + 16 + 20] // argb - mov ebx, [esp + 16 + 24] // yuvconstants - mov ecx, [esp + 16 + 28] // width - sub edi, esi - - convertloop: - READYUVA422_AVX2 - YUVTORGB_AVX2(ebx) - STOREARGB_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebp - pop ebx - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_I422ALPHATOARGBROW_AVX2 - -#ifdef HAS_I444TOARGBROW_AVX2 -// 16 pixels -// 16 UV values with 16 Y producing 16 ARGB (64 bytes). -__declspec(naked) -void I444ToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // argb - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - vpcmpeqb ymm5, ymm5, ymm5 // generate 0xffffffffffffffff for alpha - convertloop: - READYUV444_AVX2 - YUVTORGB_AVX2(ebx) - STOREARGB_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebx - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_I444TOARGBROW_AVX2 - -#ifdef HAS_I411TOARGBROW_AVX2 -// 16 pixels -// 4 UV values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). -__declspec(naked) -void I411ToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // abgr - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - vpcmpeqb ymm5, ymm5, ymm5 // generate 0xffffffffffffffff for alpha - - convertloop: - READYUV411_AVX2 - YUVTORGB_AVX2(ebx) - STOREARGB_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebx - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_I411TOARGBROW_AVX2 - -#ifdef HAS_NV12TOARGBROW_AVX2 -// 16 pixels. -// 8 UV values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). -__declspec(naked) -void NV12ToARGBRow_AVX2(const uint8* y_buf, - const uint8* uv_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push ebx - mov eax, [esp + 8 + 4] // Y - mov esi, [esp + 8 + 8] // UV - mov edx, [esp + 8 + 12] // argb - mov ebx, [esp + 8 + 16] // yuvconstants - mov ecx, [esp + 8 + 20] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate 0xffffffffffffffff for alpha - - convertloop: - READNV12_AVX2 - YUVTORGB_AVX2(ebx) - STOREARGB_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebx - pop esi - vzeroupper - ret - } -} -#endif // HAS_NV12TOARGBROW_AVX2 - -#ifdef HAS_NV21TOARGBROW_AVX2 -// 16 pixels. -// 8 VU values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). -__declspec(naked) -void NV21ToARGBRow_AVX2(const uint8* y_buf, - const uint8* vu_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push ebx - mov eax, [esp + 8 + 4] // Y - mov esi, [esp + 8 + 8] // VU - mov edx, [esp + 8 + 12] // argb - mov ebx, [esp + 8 + 16] // yuvconstants - mov ecx, [esp + 8 + 20] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate 0xffffffffffffffff for alpha - - convertloop: - READNV21_AVX2 - YUVTORGB_AVX2(ebx) - STOREARGB_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebx - pop esi - vzeroupper - ret - } -} -#endif // HAS_NV21TOARGBROW_AVX2 - -#ifdef HAS_YUY2TOARGBROW_AVX2 -// 16 pixels. -// 8 YUY2 values with 16 Y and 8 UV producing 16 ARGB (64 bytes). -__declspec(naked) -void YUY2ToARGBRow_AVX2(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push ebx - mov eax, [esp + 4 + 4] // yuy2 - mov edx, [esp + 4 + 8] // argb - mov ebx, [esp + 4 + 12] // yuvconstants - mov ecx, [esp + 4 + 16] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate 0xffffffffffffffff for alpha - - convertloop: - READYUY2_AVX2 - YUVTORGB_AVX2(ebx) - STOREARGB_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebx - vzeroupper - ret - } -} -#endif // HAS_YUY2TOARGBROW_AVX2 - -#ifdef HAS_UYVYTOARGBROW_AVX2 -// 16 pixels. -// 8 UYVY values with 16 Y and 8 UV producing 16 ARGB (64 bytes). -__declspec(naked) -void UYVYToARGBRow_AVX2(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push ebx - mov eax, [esp + 4 + 4] // uyvy - mov edx, [esp + 4 + 8] // argb - mov ebx, [esp + 4 + 12] // yuvconstants - mov ecx, [esp + 4 + 16] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate 0xffffffffffffffff for alpha - - convertloop: - READUYVY_AVX2 - YUVTORGB_AVX2(ebx) - STOREARGB_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebx - vzeroupper - ret - } -} -#endif // HAS_UYVYTOARGBROW_AVX2 - -#ifdef HAS_I422TORGBAROW_AVX2 -// 16 pixels -// 8 UV values upsampled to 16 UV, mixed with 16 Y producing 16 RGBA (64 bytes). -__declspec(naked) -void I422ToRGBARow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // abgr - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - vpcmpeqb ymm5, ymm5, ymm5 // generate 0xffffffffffffffff for alpha - - convertloop: - READYUV422_AVX2 - YUVTORGB_AVX2(ebx) - STORERGBA_AVX2 - - sub ecx, 16 - jg convertloop - - pop ebx - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_I422TORGBAROW_AVX2 - -#if defined(HAS_I422TOARGBROW_SSSE3) -// TODO(fbarchard): Read that does half size on Y and treats 420 as 444. -// Allows a conversion with half size scaling. - -// Read 8 UV from 444. -#define READYUV444 __asm { \ - __asm movq xmm0, qword ptr [esi] /* U */ \ - __asm movq xmm1, qword ptr [esi + edi] /* V */ \ - __asm lea esi, [esi + 8] \ - __asm punpcklbw xmm0, xmm1 /* UV */ \ - __asm movq xmm4, qword ptr [eax] \ - __asm punpcklbw xmm4, xmm4 \ - __asm lea eax, [eax + 8] \ - } - -// Read 4 UV from 422, upsample to 8 UV. -#define READYUV422 __asm { \ - __asm movd xmm0, [esi] /* U */ \ - __asm movd xmm1, [esi + edi] /* V */ \ - __asm lea esi, [esi + 4] \ - __asm punpcklbw xmm0, xmm1 /* UV */ \ - __asm punpcklwd xmm0, xmm0 /* UVUV (upsample) */ \ - __asm movq xmm4, qword ptr [eax] \ - __asm punpcklbw xmm4, xmm4 \ - __asm lea eax, [eax + 8] \ - } - -// Read 4 UV from 422, upsample to 8 UV. With 8 Alpha. -#define READYUVA422 __asm { \ - __asm movd xmm0, [esi] /* U */ \ - __asm movd xmm1, [esi + edi] /* V */ \ - __asm lea esi, [esi + 4] \ - __asm punpcklbw xmm0, xmm1 /* UV */ \ - __asm punpcklwd xmm0, xmm0 /* UVUV (upsample) */ \ - __asm movq xmm4, qword ptr [eax] /* Y */ \ - __asm punpcklbw xmm4, xmm4 \ - __asm lea eax, [eax + 8] \ - __asm movq xmm5, qword ptr [ebp] /* A */ \ - __asm lea ebp, [ebp + 8] \ - } - -// Read 2 UV from 411, upsample to 8 UV. -// drmemory fails with memory fault if pinsrw used. libyuv bug: 525 -// __asm pinsrw xmm0, [esi], 0 /* U */ -// __asm pinsrw xmm1, [esi + edi], 0 /* V */ -#define READYUV411_EBX __asm { \ - __asm movzx ebx, word ptr [esi] /* U */ \ - __asm movd xmm0, ebx \ - __asm movzx ebx, word ptr [esi + edi] /* V */ \ - __asm movd xmm1, ebx \ - __asm lea esi, [esi + 2] \ - __asm punpcklbw xmm0, xmm1 /* UV */ \ - __asm punpcklwd xmm0, xmm0 /* UVUV (upsample) */ \ - __asm punpckldq xmm0, xmm0 /* UVUVUVUV (upsample) */ \ - __asm movq xmm4, qword ptr [eax] \ - __asm punpcklbw xmm4, xmm4 \ - __asm lea eax, [eax + 8] \ - } - -// Read 4 UV from NV12, upsample to 8 UV. -#define READNV12 __asm { \ - __asm movq xmm0, qword ptr [esi] /* UV */ \ - __asm lea esi, [esi + 8] \ - __asm punpcklwd xmm0, xmm0 /* UVUV (upsample) */ \ - __asm movq xmm4, qword ptr [eax] \ - __asm punpcklbw xmm4, xmm4 \ - __asm lea eax, [eax + 8] \ - } - -// Read 4 VU from NV21, upsample to 8 UV. -#define READNV21 __asm { \ - __asm movq xmm0, qword ptr [esi] /* UV */ \ - __asm lea esi, [esi + 8] \ - __asm pshufb xmm0, xmmword ptr kShuffleNV21 \ - __asm movq xmm4, qword ptr [eax] \ - __asm punpcklbw xmm4, xmm4 \ - __asm lea eax, [eax + 8] \ - } - -// Read 4 YUY2 with 8 Y and upsample 4 UV to 8 UV. -#define READYUY2 __asm { \ - __asm movdqu xmm4, [eax] /* YUY2 */ \ - __asm pshufb xmm4, xmmword ptr kShuffleYUY2Y \ - __asm movdqu xmm0, [eax] /* UV */ \ - __asm pshufb xmm0, xmmword ptr kShuffleYUY2UV \ - __asm lea eax, [eax + 16] \ - } - -// Read 4 UYVY with 8 Y and upsample 4 UV to 8 UV. -#define READUYVY __asm { \ - __asm movdqu xmm4, [eax] /* UYVY */ \ - __asm pshufb xmm4, xmmword ptr kShuffleUYVYY \ - __asm movdqu xmm0, [eax] /* UV */ \ - __asm pshufb xmm0, xmmword ptr kShuffleUYVYUV \ - __asm lea eax, [eax + 16] \ - } - -// Convert 8 pixels: 8 UV and 8 Y. -#define YUVTORGB(YuvConstants) __asm { \ - __asm movdqa xmm1, xmm0 \ - __asm movdqa xmm2, xmm0 \ - __asm movdqa xmm3, xmm0 \ - __asm movdqa xmm0, xmmword ptr [YuvConstants + KUVBIASB] \ - __asm pmaddubsw xmm1, xmmword ptr [YuvConstants + KUVTOB] \ - __asm psubw xmm0, xmm1 \ - __asm movdqa xmm1, xmmword ptr [YuvConstants + KUVBIASG] \ - __asm pmaddubsw xmm2, xmmword ptr [YuvConstants + KUVTOG] \ - __asm psubw xmm1, xmm2 \ - __asm movdqa xmm2, xmmword ptr [YuvConstants + KUVBIASR] \ - __asm pmaddubsw xmm3, xmmword ptr [YuvConstants + KUVTOR] \ - __asm psubw xmm2, xmm3 \ - __asm pmulhuw xmm4, xmmword ptr [YuvConstants + KYTORGB] \ - __asm paddsw xmm0, xmm4 /* B += Y */ \ - __asm paddsw xmm1, xmm4 /* G += Y */ \ - __asm paddsw xmm2, xmm4 /* R += Y */ \ - __asm psraw xmm0, 6 \ - __asm psraw xmm1, 6 \ - __asm psraw xmm2, 6 \ - __asm packuswb xmm0, xmm0 /* B */ \ - __asm packuswb xmm1, xmm1 /* G */ \ - __asm packuswb xmm2, xmm2 /* R */ \ - } - -// Store 8 ARGB values. -#define STOREARGB __asm { \ - __asm punpcklbw xmm0, xmm1 /* BG */ \ - __asm punpcklbw xmm2, xmm5 /* RA */ \ - __asm movdqa xmm1, xmm0 \ - __asm punpcklwd xmm0, xmm2 /* BGRA first 4 pixels */ \ - __asm punpckhwd xmm1, xmm2 /* BGRA next 4 pixels */ \ - __asm movdqu 0[edx], xmm0 \ - __asm movdqu 16[edx], xmm1 \ - __asm lea edx, [edx + 32] \ - } - -// Store 8 BGRA values. -#define STOREBGRA __asm { \ - __asm pcmpeqb xmm5, xmm5 /* generate 0xffffffff for alpha */ \ - __asm punpcklbw xmm1, xmm0 /* GB */ \ - __asm punpcklbw xmm5, xmm2 /* AR */ \ - __asm movdqa xmm0, xmm5 \ - __asm punpcklwd xmm5, xmm1 /* BGRA first 4 pixels */ \ - __asm punpckhwd xmm0, xmm1 /* BGRA next 4 pixels */ \ - __asm movdqu 0[edx], xmm5 \ - __asm movdqu 16[edx], xmm0 \ - __asm lea edx, [edx + 32] \ - } - -// Store 8 RGBA values. -#define STORERGBA __asm { \ - __asm pcmpeqb xmm5, xmm5 /* generate 0xffffffff for alpha */ \ - __asm punpcklbw xmm1, xmm2 /* GR */ \ - __asm punpcklbw xmm5, xmm0 /* AB */ \ - __asm movdqa xmm0, xmm5 \ - __asm punpcklwd xmm5, xmm1 /* RGBA first 4 pixels */ \ - __asm punpckhwd xmm0, xmm1 /* RGBA next 4 pixels */ \ - __asm movdqu 0[edx], xmm5 \ - __asm movdqu 16[edx], xmm0 \ - __asm lea edx, [edx + 32] \ - } - -// Store 8 RGB24 values. -#define STORERGB24 __asm { \ - /* Weave into RRGB */ \ - __asm punpcklbw xmm0, xmm1 /* BG */ \ - __asm punpcklbw xmm2, xmm2 /* RR */ \ - __asm movdqa xmm1, xmm0 \ - __asm punpcklwd xmm0, xmm2 /* BGRR first 4 pixels */ \ - __asm punpckhwd xmm1, xmm2 /* BGRR next 4 pixels */ \ - /* RRGB -> RGB24 */ \ - __asm pshufb xmm0, xmm5 /* Pack first 8 and last 4 bytes. */ \ - __asm pshufb xmm1, xmm6 /* Pack first 12 bytes. */ \ - __asm palignr xmm1, xmm0, 12 /* last 4 bytes of xmm0 + 12 xmm1 */ \ - __asm movq qword ptr 0[edx], xmm0 /* First 8 bytes */ \ - __asm movdqu 8[edx], xmm1 /* Last 16 bytes */ \ - __asm lea edx, [edx + 24] \ - } - -// Store 8 RGB565 values. -#define STORERGB565 __asm { \ - /* Weave into RRGB */ \ - __asm punpcklbw xmm0, xmm1 /* BG */ \ - __asm punpcklbw xmm2, xmm2 /* RR */ \ - __asm movdqa xmm1, xmm0 \ - __asm punpcklwd xmm0, xmm2 /* BGRR first 4 pixels */ \ - __asm punpckhwd xmm1, xmm2 /* BGRR next 4 pixels */ \ - /* RRGB -> RGB565 */ \ - __asm movdqa xmm3, xmm0 /* B first 4 pixels of argb */ \ - __asm movdqa xmm2, xmm0 /* G */ \ - __asm pslld xmm0, 8 /* R */ \ - __asm psrld xmm3, 3 /* B */ \ - __asm psrld xmm2, 5 /* G */ \ - __asm psrad xmm0, 16 /* R */ \ - __asm pand xmm3, xmm5 /* B */ \ - __asm pand xmm2, xmm6 /* G */ \ - __asm pand xmm0, xmm7 /* R */ \ - __asm por xmm3, xmm2 /* BG */ \ - __asm por xmm0, xmm3 /* BGR */ \ - __asm movdqa xmm3, xmm1 /* B next 4 pixels of argb */ \ - __asm movdqa xmm2, xmm1 /* G */ \ - __asm pslld xmm1, 8 /* R */ \ - __asm psrld xmm3, 3 /* B */ \ - __asm psrld xmm2, 5 /* G */ \ - __asm psrad xmm1, 16 /* R */ \ - __asm pand xmm3, xmm5 /* B */ \ - __asm pand xmm2, xmm6 /* G */ \ - __asm pand xmm1, xmm7 /* R */ \ - __asm por xmm3, xmm2 /* BG */ \ - __asm por xmm1, xmm3 /* BGR */ \ - __asm packssdw xmm0, xmm1 \ - __asm movdqu 0[edx], xmm0 /* store 8 pixels of RGB565 */ \ - __asm lea edx, [edx + 16] \ - } - -// 8 pixels. -// 8 UV values, mixed with 8 Y producing 8 ARGB (32 bytes). -__declspec(naked) -void I444ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // argb - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - pcmpeqb xmm5, xmm5 // generate 0xffffffff for alpha - - convertloop: - READYUV444 - YUVTORGB(ebx) - STOREARGB - - sub ecx, 8 - jg convertloop - - pop ebx - pop edi - pop esi - ret - } -} - -// 8 pixels. -// 4 UV values upsampled to 8 UV, mixed with 8 Y producing 8 RGB24 (24 bytes). -__declspec(naked) -void I422ToRGB24Row_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // argb - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - movdqa xmm5, xmmword ptr kShuffleMaskARGBToRGB24_0 - movdqa xmm6, xmmword ptr kShuffleMaskARGBToRGB24 - - convertloop: - READYUV422 - YUVTORGB(ebx) - STORERGB24 - - sub ecx, 8 - jg convertloop - - pop ebx - pop edi - pop esi - ret - } -} - -// 8 pixels -// 4 UV values upsampled to 8 UV, mixed with 8 Y producing 8 RGB565 (16 bytes). -__declspec(naked) -void I422ToRGB565Row_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb565_buf, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // argb - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - pcmpeqb xmm5, xmm5 // generate mask 0x0000001f - psrld xmm5, 27 - pcmpeqb xmm6, xmm6 // generate mask 0x000007e0 - psrld xmm6, 26 - pslld xmm6, 5 - pcmpeqb xmm7, xmm7 // generate mask 0xfffff800 - pslld xmm7, 11 - - convertloop: - READYUV422 - YUVTORGB(ebx) - STORERGB565 - - sub ecx, 8 - jg convertloop - - pop ebx - pop edi - pop esi - ret - } -} - -// 8 pixels. -// 4 UV values upsampled to 8 UV, mixed with 8 Y producing 8 ARGB (32 bytes). -__declspec(naked) -void I422ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // argb - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - pcmpeqb xmm5, xmm5 // generate 0xffffffff for alpha - - convertloop: - READYUV422 - YUVTORGB(ebx) - STOREARGB - - sub ecx, 8 - jg convertloop - - pop ebx - pop edi - pop esi - ret - } -} - -// 8 pixels. -// 4 UV values upsampled to 8 UV, mixed with 8 Y and 8 A producing 8 ARGB. -__declspec(naked) -void I422AlphaToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - push ebp - mov eax, [esp + 16 + 4] // Y - mov esi, [esp + 16 + 8] // U - mov edi, [esp + 16 + 12] // V - mov ebp, [esp + 16 + 16] // A - mov edx, [esp + 16 + 20] // argb - mov ebx, [esp + 16 + 24] // yuvconstants - mov ecx, [esp + 16 + 28] // width - sub edi, esi - - convertloop: - READYUVA422 - YUVTORGB(ebx) - STOREARGB - - sub ecx, 8 - jg convertloop - - pop ebp - pop ebx - pop edi - pop esi - ret - } -} - -// 8 pixels. -// 2 UV values upsampled to 8 UV, mixed with 8 Y producing 8 ARGB (32 bytes). -// Similar to I420 but duplicate UV once more. -__declspec(naked) -void I411ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - push ebp - mov eax, [esp + 16 + 4] // Y - mov esi, [esp + 16 + 8] // U - mov edi, [esp + 16 + 12] // V - mov edx, [esp + 16 + 16] // abgr - mov ebp, [esp + 16 + 20] // yuvconstants - mov ecx, [esp + 16 + 24] // width - sub edi, esi - pcmpeqb xmm5, xmm5 // generate 0xffffffff for alpha - - convertloop: - READYUV411_EBX - YUVTORGB(ebp) - STOREARGB - - sub ecx, 8 - jg convertloop - - pop ebp - pop ebx - pop edi - pop esi - ret - } -} - -// 8 pixels. -// 4 UV values upsampled to 8 UV, mixed with 8 Y producing 8 ARGB (32 bytes). -__declspec(naked) -void NV12ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* uv_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push ebx - mov eax, [esp + 8 + 4] // Y - mov esi, [esp + 8 + 8] // UV - mov edx, [esp + 8 + 12] // argb - mov ebx, [esp + 8 + 16] // yuvconstants - mov ecx, [esp + 8 + 20] // width - pcmpeqb xmm5, xmm5 // generate 0xffffffff for alpha - - convertloop: - READNV12 - YUVTORGB(ebx) - STOREARGB - - sub ecx, 8 - jg convertloop - - pop ebx - pop esi - ret - } -} - -// 8 pixels. -// 4 UV values upsampled to 8 UV, mixed with 8 Y producing 8 ARGB (32 bytes). -__declspec(naked) -void NV21ToARGBRow_SSSE3(const uint8* y_buf, - const uint8* vu_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push ebx - mov eax, [esp + 8 + 4] // Y - mov esi, [esp + 8 + 8] // VU - mov edx, [esp + 8 + 12] // argb - mov ebx, [esp + 8 + 16] // yuvconstants - mov ecx, [esp + 8 + 20] // width - pcmpeqb xmm5, xmm5 // generate 0xffffffff for alpha - - convertloop: - READNV21 - YUVTORGB(ebx) - STOREARGB - - sub ecx, 8 - jg convertloop - - pop ebx - pop esi - ret - } -} - -// 8 pixels. -// 4 YUY2 values with 8 Y and 4 UV producing 8 ARGB (32 bytes). -__declspec(naked) -void YUY2ToARGBRow_SSSE3(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push ebx - mov eax, [esp + 4 + 4] // yuy2 - mov edx, [esp + 4 + 8] // argb - mov ebx, [esp + 4 + 12] // yuvconstants - mov ecx, [esp + 4 + 16] // width - pcmpeqb xmm5, xmm5 // generate 0xffffffff for alpha - - convertloop: - READYUY2 - YUVTORGB(ebx) - STOREARGB - - sub ecx, 8 - jg convertloop - - pop ebx - ret - } -} - -// 8 pixels. -// 4 UYVY values with 8 Y and 4 UV producing 8 ARGB (32 bytes). -__declspec(naked) -void UYVYToARGBRow_SSSE3(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push ebx - mov eax, [esp + 4 + 4] // uyvy - mov edx, [esp + 4 + 8] // argb - mov ebx, [esp + 4 + 12] // yuvconstants - mov ecx, [esp + 4 + 16] // width - pcmpeqb xmm5, xmm5 // generate 0xffffffff for alpha - - convertloop: - READUYVY - YUVTORGB(ebx) - STOREARGB - - sub ecx, 8 - jg convertloop - - pop ebx - ret - } -} - -__declspec(naked) -void I422ToRGBARow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width) { - __asm { - push esi - push edi - push ebx - mov eax, [esp + 12 + 4] // Y - mov esi, [esp + 12 + 8] // U - mov edi, [esp + 12 + 12] // V - mov edx, [esp + 12 + 16] // argb - mov ebx, [esp + 12 + 20] // yuvconstants - mov ecx, [esp + 12 + 24] // width - sub edi, esi - - convertloop: - READYUV422 - YUVTORGB(ebx) - STORERGBA - - sub ecx, 8 - jg convertloop - - pop ebx - pop edi - pop esi - ret - } -} -#endif // HAS_I422TOARGBROW_SSSE3 - -#ifdef HAS_I400TOARGBROW_SSE2 -// 8 pixels of Y converted to 8 pixels of ARGB (32 bytes). -__declspec(naked) -void I400ToARGBRow_SSE2(const uint8* y_buf, - uint8* rgb_buf, - int width) { - __asm { - mov eax, 0x4a354a35 // 4a35 = 18997 = round(1.164 * 64 * 256) - movd xmm2, eax - pshufd xmm2, xmm2,0 - mov eax, 0x04880488 // 0488 = 1160 = round(1.164 * 64 * 16) - movd xmm3, eax - pshufd xmm3, xmm3, 0 - pcmpeqb xmm4, xmm4 // generate mask 0xff000000 - pslld xmm4, 24 - - mov eax, [esp + 4] // Y - mov edx, [esp + 8] // rgb - mov ecx, [esp + 12] // width - - convertloop: - // Step 1: Scale Y contribution to 8 G values. G = (y - 16) * 1.164 - movq xmm0, qword ptr [eax] - lea eax, [eax + 8] - punpcklbw xmm0, xmm0 // Y.Y - pmulhuw xmm0, xmm2 - psubusw xmm0, xmm3 - psrlw xmm0, 6 - packuswb xmm0, xmm0 // G - - // Step 2: Weave into ARGB - punpcklbw xmm0, xmm0 // GG - movdqa xmm1, xmm0 - punpcklwd xmm0, xmm0 // BGRA first 4 pixels - punpckhwd xmm1, xmm1 // BGRA next 4 pixels - por xmm0, xmm4 - por xmm1, xmm4 - movdqu [edx], xmm0 - movdqu [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - ret - } -} -#endif // HAS_I400TOARGBROW_SSE2 - -#ifdef HAS_I400TOARGBROW_AVX2 -// 16 pixels of Y converted to 16 pixels of ARGB (64 bytes). -// note: vpunpcklbw mutates and vpackuswb unmutates. -__declspec(naked) -void I400ToARGBRow_AVX2(const uint8* y_buf, - uint8* rgb_buf, - int width) { - __asm { - mov eax, 0x4a354a35 // 4a35 = 18997 = round(1.164 * 64 * 256) - vmovd xmm2, eax - vbroadcastss ymm2, xmm2 - mov eax, 0x04880488 // 0488 = 1160 = round(1.164 * 64 * 16) - vmovd xmm3, eax - vbroadcastss ymm3, xmm3 - vpcmpeqb ymm4, ymm4, ymm4 // generate mask 0xff000000 - vpslld ymm4, ymm4, 24 - - mov eax, [esp + 4] // Y - mov edx, [esp + 8] // rgb - mov ecx, [esp + 12] // width - - convertloop: - // Step 1: Scale Y contriportbution to 16 G values. G = (y - 16) * 1.164 - vmovdqu xmm0, [eax] - lea eax, [eax + 16] - vpermq ymm0, ymm0, 0xd8 // vpunpcklbw mutates - vpunpcklbw ymm0, ymm0, ymm0 // Y.Y - vpmulhuw ymm0, ymm0, ymm2 - vpsubusw ymm0, ymm0, ymm3 - vpsrlw ymm0, ymm0, 6 - vpackuswb ymm0, ymm0, ymm0 // G. still mutated: 3120 - - // TODO(fbarchard): Weave alpha with unpack. - // Step 2: Weave into ARGB - vpunpcklbw ymm1, ymm0, ymm0 // GG - mutates - vpermq ymm1, ymm1, 0xd8 - vpunpcklwd ymm0, ymm1, ymm1 // GGGG first 8 pixels - vpunpckhwd ymm1, ymm1, ymm1 // GGGG next 8 pixels - vpor ymm0, ymm0, ymm4 - vpor ymm1, ymm1, ymm4 - vmovdqu [edx], ymm0 - vmovdqu [edx + 32], ymm1 - lea edx, [edx + 64] - sub ecx, 16 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_I400TOARGBROW_AVX2 - -#ifdef HAS_MIRRORROW_SSSE3 -// Shuffle table for reversing the bytes. -static const uvec8 kShuffleMirror = { - 15u, 14u, 13u, 12u, 11u, 10u, 9u, 8u, 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u -}; - -// TODO(fbarchard): Replace lea with -16 offset. -__declspec(naked) -void MirrorRow_SSSE3(const uint8* src, uint8* dst, int width) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // width - movdqa xmm5, xmmword ptr kShuffleMirror - - convertloop: - movdqu xmm0, [eax - 16 + ecx] - pshufb xmm0, xmm5 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - ret - } -} -#endif // HAS_MIRRORROW_SSSE3 - -#ifdef HAS_MIRRORROW_AVX2 -__declspec(naked) -void MirrorRow_AVX2(const uint8* src, uint8* dst, int width) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // width - vbroadcastf128 ymm5, xmmword ptr kShuffleMirror - - convertloop: - vmovdqu ymm0, [eax - 32 + ecx] - vpshufb ymm0, ymm0, ymm5 - vpermq ymm0, ymm0, 0x4e // swap high and low halfs - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 32 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_MIRRORROW_AVX2 - -#ifdef HAS_MIRRORUVROW_SSSE3 -// Shuffle table for reversing the bytes of UV channels. -static const uvec8 kShuffleMirrorUV = { - 14u, 12u, 10u, 8u, 6u, 4u, 2u, 0u, 15u, 13u, 11u, 9u, 7u, 5u, 3u, 1u -}; - -__declspec(naked) -void MirrorUVRow_SSSE3(const uint8* src, uint8* dst_u, uint8* dst_v, - int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - movdqa xmm1, xmmword ptr kShuffleMirrorUV - lea eax, [eax + ecx * 2 - 16] - sub edi, edx - - convertloop: - movdqu xmm0, [eax] - lea eax, [eax - 16] - pshufb xmm0, xmm1 - movlpd qword ptr [edx], xmm0 - movhpd qword ptr [edx + edi], xmm0 - lea edx, [edx + 8] - sub ecx, 8 - jg convertloop - - pop edi - ret - } -} -#endif // HAS_MIRRORUVROW_SSSE3 - -#ifdef HAS_ARGBMIRRORROW_SSE2 -__declspec(naked) -void ARGBMirrorRow_SSE2(const uint8* src, uint8* dst, int width) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // width - lea eax, [eax - 16 + ecx * 4] // last 4 pixels. - - convertloop: - movdqu xmm0, [eax] - lea eax, [eax - 16] - pshufd xmm0, xmm0, 0x1b - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg convertloop - ret - } -} -#endif // HAS_ARGBMIRRORROW_SSE2 - -#ifdef HAS_ARGBMIRRORROW_AVX2 -// Shuffle table for reversing the bytes. -static const ulvec32 kARGBShuffleMirror_AVX2 = { - 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u -}; - -__declspec(naked) -void ARGBMirrorRow_AVX2(const uint8* src, uint8* dst, int width) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // width - vmovdqu ymm5, ymmword ptr kARGBShuffleMirror_AVX2 - - convertloop: - vpermd ymm0, ymm5, [eax - 32 + ecx * 4] // permute dword order - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGBMIRRORROW_AVX2 - -#ifdef HAS_SPLITUVROW_SSE2 -__declspec(naked) -void SplitUVRow_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_uv - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - pcmpeqb xmm5, xmm5 // generate mask 0x00ff00ff - psrlw xmm5, 8 - sub edi, edx - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - movdqa xmm2, xmm0 - movdqa xmm3, xmm1 - pand xmm0, xmm5 // even bytes - pand xmm1, xmm5 - packuswb xmm0, xmm1 - psrlw xmm2, 8 // odd bytes - psrlw xmm3, 8 - packuswb xmm2, xmm3 - movdqu [edx], xmm0 - movdqu [edx + edi], xmm2 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - - pop edi - ret - } -} - -#endif // HAS_SPLITUVROW_SSE2 - -#ifdef HAS_SPLITUVROW_AVX2 -__declspec(naked) -void SplitUVRow_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_uv - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0x00ff00ff - vpsrlw ymm5, ymm5, 8 - sub edi, edx - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpsrlw ymm2, ymm0, 8 // odd bytes - vpsrlw ymm3, ymm1, 8 - vpand ymm0, ymm0, ymm5 // even bytes - vpand ymm1, ymm1, ymm5 - vpackuswb ymm0, ymm0, ymm1 - vpackuswb ymm2, ymm2, ymm3 - vpermq ymm0, ymm0, 0xd8 - vpermq ymm2, ymm2, 0xd8 - vmovdqu [edx], ymm0 - vmovdqu [edx + edi], ymm2 - lea edx, [edx + 32] - sub ecx, 32 - jg convertloop - - pop edi - vzeroupper - ret - } -} -#endif // HAS_SPLITUVROW_AVX2 - -#ifdef HAS_MERGEUVROW_SSE2 -__declspec(naked) -void MergeUVRow_SSE2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_u - mov edx, [esp + 4 + 8] // src_v - mov edi, [esp + 4 + 12] // dst_uv - mov ecx, [esp + 4 + 16] // width - sub edx, eax - - convertloop: - movdqu xmm0, [eax] // read 16 U's - movdqu xmm1, [eax + edx] // and 16 V's - lea eax, [eax + 16] - movdqa xmm2, xmm0 - punpcklbw xmm0, xmm1 // first 8 UV pairs - punpckhbw xmm2, xmm1 // next 8 UV pairs - movdqu [edi], xmm0 - movdqu [edi + 16], xmm2 - lea edi, [edi + 32] - sub ecx, 16 - jg convertloop - - pop edi - ret - } -} -#endif // HAS_MERGEUVROW_SSE2 - -#ifdef HAS_MERGEUVROW_AVX2 -__declspec(naked) -void MergeUVRow_AVX2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_u - mov edx, [esp + 4 + 8] // src_v - mov edi, [esp + 4 + 12] // dst_uv - mov ecx, [esp + 4 + 16] // width - sub edx, eax - - convertloop: - vmovdqu ymm0, [eax] // read 32 U's - vmovdqu ymm1, [eax + edx] // and 32 V's - lea eax, [eax + 32] - vpunpcklbw ymm2, ymm0, ymm1 // low 16 UV pairs. mutated qqword 0,2 - vpunpckhbw ymm0, ymm0, ymm1 // high 16 UV pairs. mutated qqword 1,3 - vextractf128 [edi], ymm2, 0 // bytes 0..15 - vextractf128 [edi + 16], ymm0, 0 // bytes 16..31 - vextractf128 [edi + 32], ymm2, 1 // bytes 32..47 - vextractf128 [edi + 48], ymm0, 1 // bytes 47..63 - lea edi, [edi + 64] - sub ecx, 32 - jg convertloop - - pop edi - vzeroupper - ret - } -} -#endif // HAS_MERGEUVROW_AVX2 - -#ifdef HAS_COPYROW_SSE2 -// CopyRow copys 'count' bytes using a 16 byte load/store, 32 bytes at time. -__declspec(naked) -void CopyRow_SSE2(const uint8* src, uint8* dst, int count) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // count - test eax, 15 - jne convertloopu - test edx, 15 - jne convertloopu - - convertloopa: - movdqa xmm0, [eax] - movdqa xmm1, [eax + 16] - lea eax, [eax + 32] - movdqa [edx], xmm0 - movdqa [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 32 - jg convertloopa - ret - - convertloopu: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - movdqu [edx], xmm0 - movdqu [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 32 - jg convertloopu - ret - } -} -#endif // HAS_COPYROW_SSE2 - -#ifdef HAS_COPYROW_AVX -// CopyRow copys 'count' bytes using a 32 byte load/store, 64 bytes at time. -__declspec(naked) -void CopyRow_AVX(const uint8* src, uint8* dst, int count) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // count - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vmovdqu [edx], ymm0 - vmovdqu [edx + 32], ymm1 - lea edx, [edx + 64] - sub ecx, 64 - jg convertloop - - vzeroupper - ret - } -} -#endif // HAS_COPYROW_AVX - -// Multiple of 1. -__declspec(naked) -void CopyRow_ERMS(const uint8* src, uint8* dst, int count) { - __asm { - mov eax, esi - mov edx, edi - mov esi, [esp + 4] // src - mov edi, [esp + 8] // dst - mov ecx, [esp + 12] // count - rep movsb - mov edi, edx - mov esi, eax - ret - } -} - -#ifdef HAS_ARGBCOPYALPHAROW_SSE2 -// width in pixels -__declspec(naked) -void ARGBCopyAlphaRow_SSE2(const uint8* src, uint8* dst, int width) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // count - pcmpeqb xmm0, xmm0 // generate mask 0xff000000 - pslld xmm0, 24 - pcmpeqb xmm1, xmm1 // generate mask 0x00ffffff - psrld xmm1, 8 - - convertloop: - movdqu xmm2, [eax] - movdqu xmm3, [eax + 16] - lea eax, [eax + 32] - movdqu xmm4, [edx] - movdqu xmm5, [edx + 16] - pand xmm2, xmm0 - pand xmm3, xmm0 - pand xmm4, xmm1 - pand xmm5, xmm1 - por xmm2, xmm4 - por xmm3, xmm5 - movdqu [edx], xmm2 - movdqu [edx + 16], xmm3 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - - ret - } -} -#endif // HAS_ARGBCOPYALPHAROW_SSE2 - -#ifdef HAS_ARGBCOPYALPHAROW_AVX2 -// width in pixels -__declspec(naked) -void ARGBCopyAlphaRow_AVX2(const uint8* src, uint8* dst, int width) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // count - vpcmpeqb ymm0, ymm0, ymm0 - vpsrld ymm0, ymm0, 8 // generate mask 0x00ffffff - - convertloop: - vmovdqu ymm1, [eax] - vmovdqu ymm2, [eax + 32] - lea eax, [eax + 64] - vpblendvb ymm1, ymm1, [edx], ymm0 - vpblendvb ymm2, ymm2, [edx + 32], ymm0 - vmovdqu [edx], ymm1 - vmovdqu [edx + 32], ymm2 - lea edx, [edx + 64] - sub ecx, 16 - jg convertloop - - vzeroupper - ret - } -} -#endif // HAS_ARGBCOPYALPHAROW_AVX2 - -#ifdef HAS_ARGBEXTRACTALPHAROW_SSE2 -// width in pixels -__declspec(naked) -void ARGBExtractAlphaRow_SSE2(const uint8* src_argb, uint8* dst_a, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_a - mov ecx, [esp + 12] // width - - extractloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - psrld xmm0, 24 - psrld xmm1, 24 - packssdw xmm0, xmm1 - packuswb xmm0, xmm0 - movq qword ptr [edx], xmm0 - lea edx, [edx + 8] - sub ecx, 8 - jg extractloop - - ret - } -} -#endif // HAS_ARGBEXTRACTALPHAROW_SSE2 - -#ifdef HAS_ARGBCOPYYTOALPHAROW_SSE2 -// width in pixels -__declspec(naked) -void ARGBCopyYToAlphaRow_SSE2(const uint8* src, uint8* dst, int width) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // count - pcmpeqb xmm0, xmm0 // generate mask 0xff000000 - pslld xmm0, 24 - pcmpeqb xmm1, xmm1 // generate mask 0x00ffffff - psrld xmm1, 8 - - convertloop: - movq xmm2, qword ptr [eax] // 8 Y's - lea eax, [eax + 8] - punpcklbw xmm2, xmm2 - punpckhwd xmm3, xmm2 - punpcklwd xmm2, xmm2 - movdqu xmm4, [edx] - movdqu xmm5, [edx + 16] - pand xmm2, xmm0 - pand xmm3, xmm0 - pand xmm4, xmm1 - pand xmm5, xmm1 - por xmm2, xmm4 - por xmm3, xmm5 - movdqu [edx], xmm2 - movdqu [edx + 16], xmm3 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - - ret - } -} -#endif // HAS_ARGBCOPYYTOALPHAROW_SSE2 - -#ifdef HAS_ARGBCOPYYTOALPHAROW_AVX2 -// width in pixels -__declspec(naked) -void ARGBCopyYToAlphaRow_AVX2(const uint8* src, uint8* dst, int width) { - __asm { - mov eax, [esp + 4] // src - mov edx, [esp + 8] // dst - mov ecx, [esp + 12] // count - vpcmpeqb ymm0, ymm0, ymm0 - vpsrld ymm0, ymm0, 8 // generate mask 0x00ffffff - - convertloop: - vpmovzxbd ymm1, qword ptr [eax] - vpmovzxbd ymm2, qword ptr [eax + 8] - lea eax, [eax + 16] - vpslld ymm1, ymm1, 24 - vpslld ymm2, ymm2, 24 - vpblendvb ymm1, ymm1, [edx], ymm0 - vpblendvb ymm2, ymm2, [edx + 32], ymm0 - vmovdqu [edx], ymm1 - vmovdqu [edx + 32], ymm2 - lea edx, [edx + 64] - sub ecx, 16 - jg convertloop - - vzeroupper - ret - } -} -#endif // HAS_ARGBCOPYYTOALPHAROW_AVX2 - -#ifdef HAS_SETROW_X86 -// Write 'count' bytes using an 8 bit value repeated. -// Count should be multiple of 4. -__declspec(naked) -void SetRow_X86(uint8* dst, uint8 v8, int count) { - __asm { - movzx eax, byte ptr [esp + 8] // v8 - mov edx, 0x01010101 // Duplicate byte to all bytes. - mul edx // overwrites edx with upper part of result. - mov edx, edi - mov edi, [esp + 4] // dst - mov ecx, [esp + 12] // count - shr ecx, 2 - rep stosd - mov edi, edx - ret - } -} - -// Write 'count' bytes using an 8 bit value repeated. -__declspec(naked) -void SetRow_ERMS(uint8* dst, uint8 v8, int count) { - __asm { - mov edx, edi - mov edi, [esp + 4] // dst - mov eax, [esp + 8] // v8 - mov ecx, [esp + 12] // count - rep stosb - mov edi, edx - ret - } -} - -// Write 'count' 32 bit values. -__declspec(naked) -void ARGBSetRow_X86(uint8* dst_argb, uint32 v32, int count) { - __asm { - mov edx, edi - mov edi, [esp + 4] // dst - mov eax, [esp + 8] // v32 - mov ecx, [esp + 12] // count - rep stosd - mov edi, edx - ret - } -} -#endif // HAS_SETROW_X86 - -#ifdef HAS_YUY2TOYROW_AVX2 -__declspec(naked) -void YUY2ToYRow_AVX2(const uint8* src_yuy2, uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] // src_yuy2 - mov edx, [esp + 8] // dst_y - mov ecx, [esp + 12] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0x00ff00ff - vpsrlw ymm5, ymm5, 8 - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpand ymm0, ymm0, ymm5 // even bytes are Y - vpand ymm1, ymm1, ymm5 - vpackuswb ymm0, ymm0, ymm1 // mutates. - vpermq ymm0, ymm0, 0xd8 - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 32 - jg convertloop - vzeroupper - ret - } -} - -__declspec(naked) -void YUY2ToUVRow_AVX2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_yuy2 - mov esi, [esp + 8 + 8] // stride_yuy2 - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0x00ff00ff - vpsrlw ymm5, ymm5, 8 - sub edi, edx - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - vpavgb ymm0, ymm0, [eax + esi] - vpavgb ymm1, ymm1, [eax + esi + 32] - lea eax, [eax + 64] - vpsrlw ymm0, ymm0, 8 // YUYV -> UVUV - vpsrlw ymm1, ymm1, 8 - vpackuswb ymm0, ymm0, ymm1 // mutates. - vpermq ymm0, ymm0, 0xd8 - vpand ymm1, ymm0, ymm5 // U - vpsrlw ymm0, ymm0, 8 // V - vpackuswb ymm1, ymm1, ymm1 // mutates. - vpackuswb ymm0, ymm0, ymm0 // mutates. - vpermq ymm1, ymm1, 0xd8 - vpermq ymm0, ymm0, 0xd8 - vextractf128 [edx], ymm1, 0 // U - vextractf128 [edx + edi], ymm0, 0 // V - lea edx, [edx + 16] - sub ecx, 32 - jg convertloop - - pop edi - pop esi - vzeroupper - ret - } -} - -__declspec(naked) -void YUY2ToUV422Row_AVX2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_yuy2 - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0x00ff00ff - vpsrlw ymm5, ymm5, 8 - sub edi, edx - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpsrlw ymm0, ymm0, 8 // YUYV -> UVUV - vpsrlw ymm1, ymm1, 8 - vpackuswb ymm0, ymm0, ymm1 // mutates. - vpermq ymm0, ymm0, 0xd8 - vpand ymm1, ymm0, ymm5 // U - vpsrlw ymm0, ymm0, 8 // V - vpackuswb ymm1, ymm1, ymm1 // mutates. - vpackuswb ymm0, ymm0, ymm0 // mutates. - vpermq ymm1, ymm1, 0xd8 - vpermq ymm0, ymm0, 0xd8 - vextractf128 [edx], ymm1, 0 // U - vextractf128 [edx + edi], ymm0, 0 // V - lea edx, [edx + 16] - sub ecx, 32 - jg convertloop - - pop edi - vzeroupper - ret - } -} - -__declspec(naked) -void UYVYToYRow_AVX2(const uint8* src_uyvy, - uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] // src_uyvy - mov edx, [esp + 8] // dst_y - mov ecx, [esp + 12] // width - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpsrlw ymm0, ymm0, 8 // odd bytes are Y - vpsrlw ymm1, ymm1, 8 - vpackuswb ymm0, ymm0, ymm1 // mutates. - vpermq ymm0, ymm0, 0xd8 - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 32 - jg convertloop - vzeroupper - ret - } -} - -__declspec(naked) -void UYVYToUVRow_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_yuy2 - mov esi, [esp + 8 + 8] // stride_yuy2 - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0x00ff00ff - vpsrlw ymm5, ymm5, 8 - sub edi, edx - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - vpavgb ymm0, ymm0, [eax + esi] - vpavgb ymm1, ymm1, [eax + esi + 32] - lea eax, [eax + 64] - vpand ymm0, ymm0, ymm5 // UYVY -> UVUV - vpand ymm1, ymm1, ymm5 - vpackuswb ymm0, ymm0, ymm1 // mutates. - vpermq ymm0, ymm0, 0xd8 - vpand ymm1, ymm0, ymm5 // U - vpsrlw ymm0, ymm0, 8 // V - vpackuswb ymm1, ymm1, ymm1 // mutates. - vpackuswb ymm0, ymm0, ymm0 // mutates. - vpermq ymm1, ymm1, 0xd8 - vpermq ymm0, ymm0, 0xd8 - vextractf128 [edx], ymm1, 0 // U - vextractf128 [edx + edi], ymm0, 0 // V - lea edx, [edx + 16] - sub ecx, 32 - jg convertloop - - pop edi - pop esi - vzeroupper - ret - } -} - -__declspec(naked) -void UYVYToUV422Row_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_yuy2 - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0x00ff00ff - vpsrlw ymm5, ymm5, 8 - sub edi, edx - - convertloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpand ymm0, ymm0, ymm5 // UYVY -> UVUV - vpand ymm1, ymm1, ymm5 - vpackuswb ymm0, ymm0, ymm1 // mutates. - vpermq ymm0, ymm0, 0xd8 - vpand ymm1, ymm0, ymm5 // U - vpsrlw ymm0, ymm0, 8 // V - vpackuswb ymm1, ymm1, ymm1 // mutates. - vpackuswb ymm0, ymm0, ymm0 // mutates. - vpermq ymm1, ymm1, 0xd8 - vpermq ymm0, ymm0, 0xd8 - vextractf128 [edx], ymm1, 0 // U - vextractf128 [edx + edi], ymm0, 0 // V - lea edx, [edx + 16] - sub ecx, 32 - jg convertloop - - pop edi - vzeroupper - ret - } -} -#endif // HAS_YUY2TOYROW_AVX2 - -#ifdef HAS_YUY2TOYROW_SSE2 -__declspec(naked) -void YUY2ToYRow_SSE2(const uint8* src_yuy2, - uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] // src_yuy2 - mov edx, [esp + 8] // dst_y - mov ecx, [esp + 12] // width - pcmpeqb xmm5, xmm5 // generate mask 0x00ff00ff - psrlw xmm5, 8 - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - pand xmm0, xmm5 // even bytes are Y - pand xmm1, xmm5 - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void YUY2ToUVRow_SSE2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_yuy2 - mov esi, [esp + 8 + 8] // stride_yuy2 - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - pcmpeqb xmm5, xmm5 // generate mask 0x00ff00ff - psrlw xmm5, 8 - sub edi, edx - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + esi] - movdqu xmm3, [eax + esi + 16] - lea eax, [eax + 32] - pavgb xmm0, xmm2 - pavgb xmm1, xmm3 - psrlw xmm0, 8 // YUYV -> UVUV - psrlw xmm1, 8 - packuswb xmm0, xmm1 - movdqa xmm1, xmm0 - pand xmm0, xmm5 // U - packuswb xmm0, xmm0 - psrlw xmm1, 8 // V - packuswb xmm1, xmm1 - movq qword ptr [edx], xmm0 - movq qword ptr [edx + edi], xmm1 - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} - -__declspec(naked) -void YUY2ToUV422Row_SSE2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_yuy2 - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - pcmpeqb xmm5, xmm5 // generate mask 0x00ff00ff - psrlw xmm5, 8 - sub edi, edx - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - psrlw xmm0, 8 // YUYV -> UVUV - psrlw xmm1, 8 - packuswb xmm0, xmm1 - movdqa xmm1, xmm0 - pand xmm0, xmm5 // U - packuswb xmm0, xmm0 - psrlw xmm1, 8 // V - packuswb xmm1, xmm1 - movq qword ptr [edx], xmm0 - movq qword ptr [edx + edi], xmm1 - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - ret - } -} - -__declspec(naked) -void UYVYToYRow_SSE2(const uint8* src_uyvy, - uint8* dst_y, int width) { - __asm { - mov eax, [esp + 4] // src_uyvy - mov edx, [esp + 8] // dst_y - mov ecx, [esp + 12] // width - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - psrlw xmm0, 8 // odd bytes are Y - psrlw xmm1, 8 - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - ret - } -} - -__declspec(naked) -void UYVYToUVRow_SSE2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_yuy2 - mov esi, [esp + 8 + 8] // stride_yuy2 - mov edx, [esp + 8 + 12] // dst_u - mov edi, [esp + 8 + 16] // dst_v - mov ecx, [esp + 8 + 20] // width - pcmpeqb xmm5, xmm5 // generate mask 0x00ff00ff - psrlw xmm5, 8 - sub edi, edx - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + esi] - movdqu xmm3, [eax + esi + 16] - lea eax, [eax + 32] - pavgb xmm0, xmm2 - pavgb xmm1, xmm3 - pand xmm0, xmm5 // UYVY -> UVUV - pand xmm1, xmm5 - packuswb xmm0, xmm1 - movdqa xmm1, xmm0 - pand xmm0, xmm5 // U - packuswb xmm0, xmm0 - psrlw xmm1, 8 // V - packuswb xmm1, xmm1 - movq qword ptr [edx], xmm0 - movq qword ptr [edx + edi], xmm1 - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} - -__declspec(naked) -void UYVYToUV422Row_SSE2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_yuy2 - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - pcmpeqb xmm5, xmm5 // generate mask 0x00ff00ff - psrlw xmm5, 8 - sub edi, edx - - convertloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - pand xmm0, xmm5 // UYVY -> UVUV - pand xmm1, xmm5 - packuswb xmm0, xmm1 - movdqa xmm1, xmm0 - pand xmm0, xmm5 // U - packuswb xmm0, xmm0 - psrlw xmm1, 8 // V - packuswb xmm1, xmm1 - movq qword ptr [edx], xmm0 - movq qword ptr [edx + edi], xmm1 - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - ret - } -} -#endif // HAS_YUY2TOYROW_SSE2 - -#ifdef HAS_BLENDPLANEROW_SSSE3 -// Blend 8 pixels at a time. -// unsigned version of math -// =((A2*C2)+(B2*(255-C2))+255)/256 -// signed version of math -// =(((A2-128)*C2)+((B2-128)*(255-C2))+32768+127)/256 -__declspec(naked) -void BlendPlaneRow_SSSE3(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width) { - __asm { - push esi - push edi - pcmpeqb xmm5, xmm5 // generate mask 0xff00ff00 - psllw xmm5, 8 - mov eax, 0x80808080 // 128 for biasing image to signed. - movd xmm6, eax - pshufd xmm6, xmm6, 0x00 - - mov eax, 0x807f807f // 32768 + 127 for unbias and round. - movd xmm7, eax - pshufd xmm7, xmm7, 0x00 - mov eax, [esp + 8 + 4] // src0 - mov edx, [esp + 8 + 8] // src1 - mov esi, [esp + 8 + 12] // alpha - mov edi, [esp + 8 + 16] // dst - mov ecx, [esp + 8 + 20] // width - sub eax, esi - sub edx, esi - sub edi, esi - - // 8 pixel loop. - convertloop8: - movq xmm0, qword ptr [esi] // alpha - punpcklbw xmm0, xmm0 - pxor xmm0, xmm5 // a, 255-a - movq xmm1, qword ptr [eax + esi] // src0 - movq xmm2, qword ptr [edx + esi] // src1 - punpcklbw xmm1, xmm2 - psubb xmm1, xmm6 // bias src0/1 - 128 - pmaddubsw xmm0, xmm1 - paddw xmm0, xmm7 // unbias result - 32768 and round. - psrlw xmm0, 8 - packuswb xmm0, xmm0 - movq qword ptr [edi + esi], xmm0 - lea esi, [esi + 8] - sub ecx, 8 - jg convertloop8 - - pop edi - pop esi - ret - } -} -#endif // HAS_BLENDPLANEROW_SSSE3 - -#ifdef HAS_BLENDPLANEROW_AVX2 -// Blend 32 pixels at a time. -// unsigned version of math -// =((A2*C2)+(B2*(255-C2))+255)/256 -// signed version of math -// =(((A2-128)*C2)+((B2-128)*(255-C2))+32768+127)/256 -__declspec(naked) -void BlendPlaneRow_AVX2(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width) { - __asm { - push esi - push edi - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0xff00ff00 - vpsllw ymm5, ymm5, 8 - mov eax, 0x80808080 // 128 for biasing image to signed. - vmovd xmm6, eax - vbroadcastss ymm6, xmm6 - mov eax, 0x807f807f // 32768 + 127 for unbias and round. - vmovd xmm7, eax - vbroadcastss ymm7, xmm7 - mov eax, [esp + 8 + 4] // src0 - mov edx, [esp + 8 + 8] // src1 - mov esi, [esp + 8 + 12] // alpha - mov edi, [esp + 8 + 16] // dst - mov ecx, [esp + 8 + 20] // width - sub eax, esi - sub edx, esi - sub edi, esi - - // 32 pixel loop. - convertloop32: - vmovdqu ymm0, [esi] // alpha - vpunpckhbw ymm3, ymm0, ymm0 // 8..15, 24..31 - vpunpcklbw ymm0, ymm0, ymm0 // 0..7, 16..23 - vpxor ymm3, ymm3, ymm5 // a, 255-a - vpxor ymm0, ymm0, ymm5 // a, 255-a - vmovdqu ymm1, [eax + esi] // src0 - vmovdqu ymm2, [edx + esi] // src1 - vpunpckhbw ymm4, ymm1, ymm2 - vpunpcklbw ymm1, ymm1, ymm2 - vpsubb ymm4, ymm4, ymm6 // bias src0/1 - 128 - vpsubb ymm1, ymm1, ymm6 // bias src0/1 - 128 - vpmaddubsw ymm3, ymm3, ymm4 - vpmaddubsw ymm0, ymm0, ymm1 - vpaddw ymm3, ymm3, ymm7 // unbias result - 32768 and round. - vpaddw ymm0, ymm0, ymm7 // unbias result - 32768 and round. - vpsrlw ymm3, ymm3, 8 - vpsrlw ymm0, ymm0, 8 - vpackuswb ymm0, ymm0, ymm3 - vmovdqu [edi + esi], ymm0 - lea esi, [esi + 32] - sub ecx, 32 - jg convertloop32 - - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_BLENDPLANEROW_AVX2 - -#ifdef HAS_ARGBBLENDROW_SSSE3 -// Shuffle table for isolating alpha. -static const uvec8 kShuffleAlpha = { - 3u, 0x80, 3u, 0x80, 7u, 0x80, 7u, 0x80, - 11u, 0x80, 11u, 0x80, 15u, 0x80, 15u, 0x80 -}; - -// Blend 8 pixels at a time. -__declspec(naked) -void ARGBBlendRow_SSSE3(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_argb0 - mov esi, [esp + 4 + 8] // src_argb1 - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - pcmpeqb xmm7, xmm7 // generate constant 0x0001 - psrlw xmm7, 15 - pcmpeqb xmm6, xmm6 // generate mask 0x00ff00ff - psrlw xmm6, 8 - pcmpeqb xmm5, xmm5 // generate mask 0xff00ff00 - psllw xmm5, 8 - pcmpeqb xmm4, xmm4 // generate mask 0xff000000 - pslld xmm4, 24 - sub ecx, 4 - jl convertloop4b // less than 4 pixels? - - // 4 pixel loop. - convertloop4: - movdqu xmm3, [eax] // src argb - lea eax, [eax + 16] - movdqa xmm0, xmm3 // src argb - pxor xmm3, xmm4 // ~alpha - movdqu xmm2, [esi] // _r_b - pshufb xmm3, xmmword ptr kShuffleAlpha // alpha - pand xmm2, xmm6 // _r_b - paddw xmm3, xmm7 // 256 - alpha - pmullw xmm2, xmm3 // _r_b * alpha - movdqu xmm1, [esi] // _a_g - lea esi, [esi + 16] - psrlw xmm1, 8 // _a_g - por xmm0, xmm4 // set alpha to 255 - pmullw xmm1, xmm3 // _a_g * alpha - psrlw xmm2, 8 // _r_b convert to 8 bits again - paddusb xmm0, xmm2 // + src argb - pand xmm1, xmm5 // a_g_ convert to 8 bits again - paddusb xmm0, xmm1 // + src argb - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jge convertloop4 - - convertloop4b: - add ecx, 4 - 1 - jl convertloop1b - - // 1 pixel loop. - convertloop1: - movd xmm3, [eax] // src argb - lea eax, [eax + 4] - movdqa xmm0, xmm3 // src argb - pxor xmm3, xmm4 // ~alpha - movd xmm2, [esi] // _r_b - pshufb xmm3, xmmword ptr kShuffleAlpha // alpha - pand xmm2, xmm6 // _r_b - paddw xmm3, xmm7 // 256 - alpha - pmullw xmm2, xmm3 // _r_b * alpha - movd xmm1, [esi] // _a_g - lea esi, [esi + 4] - psrlw xmm1, 8 // _a_g - por xmm0, xmm4 // set alpha to 255 - pmullw xmm1, xmm3 // _a_g * alpha - psrlw xmm2, 8 // _r_b convert to 8 bits again - paddusb xmm0, xmm2 // + src argb - pand xmm1, xmm5 // a_g_ convert to 8 bits again - paddusb xmm0, xmm1 // + src argb - movd [edx], xmm0 - lea edx, [edx + 4] - sub ecx, 1 - jge convertloop1 - - convertloop1b: - pop esi - ret - } -} -#endif // HAS_ARGBBLENDROW_SSSE3 - -#ifdef HAS_ARGBATTENUATEROW_SSSE3 -// Shuffle table duplicating alpha. -static const uvec8 kShuffleAlpha0 = { - 3u, 3u, 3u, 3u, 3u, 3u, 128u, 128u, 7u, 7u, 7u, 7u, 7u, 7u, 128u, 128u, -}; -static const uvec8 kShuffleAlpha1 = { - 11u, 11u, 11u, 11u, 11u, 11u, 128u, 128u, - 15u, 15u, 15u, 15u, 15u, 15u, 128u, 128u, -}; -__declspec(naked) -void ARGBAttenuateRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width) { - __asm { - mov eax, [esp + 4] // src_argb0 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - pcmpeqb xmm3, xmm3 // generate mask 0xff000000 - pslld xmm3, 24 - movdqa xmm4, xmmword ptr kShuffleAlpha0 - movdqa xmm5, xmmword ptr kShuffleAlpha1 - - convertloop: - movdqu xmm0, [eax] // read 4 pixels - pshufb xmm0, xmm4 // isolate first 2 alphas - movdqu xmm1, [eax] // read 4 pixels - punpcklbw xmm1, xmm1 // first 2 pixel rgbs - pmulhuw xmm0, xmm1 // rgb * a - movdqu xmm1, [eax] // read 4 pixels - pshufb xmm1, xmm5 // isolate next 2 alphas - movdqu xmm2, [eax] // read 4 pixels - punpckhbw xmm2, xmm2 // next 2 pixel rgbs - pmulhuw xmm1, xmm2 // rgb * a - movdqu xmm2, [eax] // mask original alpha - lea eax, [eax + 16] - pand xmm2, xmm3 - psrlw xmm0, 8 - psrlw xmm1, 8 - packuswb xmm0, xmm1 - por xmm0, xmm2 // copy original alpha - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg convertloop - - ret - } -} -#endif // HAS_ARGBATTENUATEROW_SSSE3 - -#ifdef HAS_ARGBATTENUATEROW_AVX2 -// Shuffle table duplicating alpha. -static const uvec8 kShuffleAlpha_AVX2 = { - 6u, 7u, 6u, 7u, 6u, 7u, 128u, 128u, 14u, 15u, 14u, 15u, 14u, 15u, 128u, 128u -}; -__declspec(naked) -void ARGBAttenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width) { - __asm { - mov eax, [esp + 4] // src_argb0 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - sub edx, eax - vbroadcastf128 ymm4, xmmword ptr kShuffleAlpha_AVX2 - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0xff000000 - vpslld ymm5, ymm5, 24 - - convertloop: - vmovdqu ymm6, [eax] // read 8 pixels. - vpunpcklbw ymm0, ymm6, ymm6 // low 4 pixels. mutated. - vpunpckhbw ymm1, ymm6, ymm6 // high 4 pixels. mutated. - vpshufb ymm2, ymm0, ymm4 // low 4 alphas - vpshufb ymm3, ymm1, ymm4 // high 4 alphas - vpmulhuw ymm0, ymm0, ymm2 // rgb * a - vpmulhuw ymm1, ymm1, ymm3 // rgb * a - vpand ymm6, ymm6, ymm5 // isolate alpha - vpsrlw ymm0, ymm0, 8 - vpsrlw ymm1, ymm1, 8 - vpackuswb ymm0, ymm0, ymm1 // unmutated. - vpor ymm0, ymm0, ymm6 // copy original alpha - vmovdqu [eax + edx], ymm0 - lea eax, [eax + 32] - sub ecx, 8 - jg convertloop - - vzeroupper - ret - } -} -#endif // HAS_ARGBATTENUATEROW_AVX2 - -#ifdef HAS_ARGBUNATTENUATEROW_SSE2 -// Unattenuate 4 pixels at a time. -__declspec(naked) -void ARGBUnattenuateRow_SSE2(const uint8* src_argb, uint8* dst_argb, - int width) { - __asm { - push ebx - push esi - push edi - mov eax, [esp + 12 + 4] // src_argb - mov edx, [esp + 12 + 8] // dst_argb - mov ecx, [esp + 12 + 12] // width - lea ebx, fixed_invtbl8 - - convertloop: - movdqu xmm0, [eax] // read 4 pixels - movzx esi, byte ptr [eax + 3] // first alpha - movzx edi, byte ptr [eax + 7] // second alpha - punpcklbw xmm0, xmm0 // first 2 - movd xmm2, dword ptr [ebx + esi * 4] - movd xmm3, dword ptr [ebx + edi * 4] - pshuflw xmm2, xmm2, 040h // first 4 inv_alpha words. 1, a, a, a - pshuflw xmm3, xmm3, 040h // next 4 inv_alpha words - movlhps xmm2, xmm3 - pmulhuw xmm0, xmm2 // rgb * a - - movdqu xmm1, [eax] // read 4 pixels - movzx esi, byte ptr [eax + 11] // third alpha - movzx edi, byte ptr [eax + 15] // forth alpha - punpckhbw xmm1, xmm1 // next 2 - movd xmm2, dword ptr [ebx + esi * 4] - movd xmm3, dword ptr [ebx + edi * 4] - pshuflw xmm2, xmm2, 040h // first 4 inv_alpha words - pshuflw xmm3, xmm3, 040h // next 4 inv_alpha words - movlhps xmm2, xmm3 - pmulhuw xmm1, xmm2 // rgb * a - lea eax, [eax + 16] - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg convertloop - - pop edi - pop esi - pop ebx - ret - } -} -#endif // HAS_ARGBUNATTENUATEROW_SSE2 - -#ifdef HAS_ARGBUNATTENUATEROW_AVX2 -// Shuffle table duplicating alpha. -static const uvec8 kUnattenShuffleAlpha_AVX2 = { - 0u, 1u, 0u, 1u, 0u, 1u, 6u, 7u, 8u, 9u, 8u, 9u, 8u, 9u, 14u, 15u -}; -// TODO(fbarchard): Enable USE_GATHER for future hardware if faster. -// USE_GATHER is not on by default, due to being a slow instruction. -#ifdef USE_GATHER -__declspec(naked) -void ARGBUnattenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, - int width) { - __asm { - mov eax, [esp + 4] // src_argb0 - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - sub edx, eax - vbroadcastf128 ymm4, xmmword ptr kUnattenShuffleAlpha_AVX2 - - convertloop: - vmovdqu ymm6, [eax] // read 8 pixels. - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0xffffffff for gather. - vpsrld ymm2, ymm6, 24 // alpha in low 8 bits. - vpunpcklbw ymm0, ymm6, ymm6 // low 4 pixels. mutated. - vpunpckhbw ymm1, ymm6, ymm6 // high 4 pixels. mutated. - vpgatherdd ymm3, [ymm2 * 4 + fixed_invtbl8], ymm5 // ymm5 cleared. 1, a - vpunpcklwd ymm2, ymm3, ymm3 // low 4 inverted alphas. mutated. 1, 1, a, a - vpunpckhwd ymm3, ymm3, ymm3 // high 4 inverted alphas. mutated. - vpshufb ymm2, ymm2, ymm4 // replicate low 4 alphas. 1, a, a, a - vpshufb ymm3, ymm3, ymm4 // replicate high 4 alphas - vpmulhuw ymm0, ymm0, ymm2 // rgb * ia - vpmulhuw ymm1, ymm1, ymm3 // rgb * ia - vpackuswb ymm0, ymm0, ymm1 // unmutated. - vmovdqu [eax + edx], ymm0 - lea eax, [eax + 32] - sub ecx, 8 - jg convertloop - - vzeroupper - ret - } -} -#else // USE_GATHER -__declspec(naked) -void ARGBUnattenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, - int width) { - __asm { - - push ebx - push esi - push edi - mov eax, [esp + 12 + 4] // src_argb - mov edx, [esp + 12 + 8] // dst_argb - mov ecx, [esp + 12 + 12] // width - sub edx, eax - lea ebx, fixed_invtbl8 - vbroadcastf128 ymm5, xmmword ptr kUnattenShuffleAlpha_AVX2 - - convertloop: - // replace VPGATHER - movzx esi, byte ptr [eax + 3] // alpha0 - movzx edi, byte ptr [eax + 7] // alpha1 - vmovd xmm0, dword ptr [ebx + esi * 4] // [1,a0] - vmovd xmm1, dword ptr [ebx + edi * 4] // [1,a1] - movzx esi, byte ptr [eax + 11] // alpha2 - movzx edi, byte ptr [eax + 15] // alpha3 - vpunpckldq xmm6, xmm0, xmm1 // [1,a1,1,a0] - vmovd xmm2, dword ptr [ebx + esi * 4] // [1,a2] - vmovd xmm3, dword ptr [ebx + edi * 4] // [1,a3] - movzx esi, byte ptr [eax + 19] // alpha4 - movzx edi, byte ptr [eax + 23] // alpha5 - vpunpckldq xmm7, xmm2, xmm3 // [1,a3,1,a2] - vmovd xmm0, dword ptr [ebx + esi * 4] // [1,a4] - vmovd xmm1, dword ptr [ebx + edi * 4] // [1,a5] - movzx esi, byte ptr [eax + 27] // alpha6 - movzx edi, byte ptr [eax + 31] // alpha7 - vpunpckldq xmm0, xmm0, xmm1 // [1,a5,1,a4] - vmovd xmm2, dword ptr [ebx + esi * 4] // [1,a6] - vmovd xmm3, dword ptr [ebx + edi * 4] // [1,a7] - vpunpckldq xmm2, xmm2, xmm3 // [1,a7,1,a6] - vpunpcklqdq xmm3, xmm6, xmm7 // [1,a3,1,a2,1,a1,1,a0] - vpunpcklqdq xmm0, xmm0, xmm2 // [1,a7,1,a6,1,a5,1,a4] - vinserti128 ymm3, ymm3, xmm0, 1 // [1,a7,1,a6,1,a5,1,a4,1,a3,1,a2,1,a1,1,a0] - // end of VPGATHER - - vmovdqu ymm6, [eax] // read 8 pixels. - vpunpcklbw ymm0, ymm6, ymm6 // low 4 pixels. mutated. - vpunpckhbw ymm1, ymm6, ymm6 // high 4 pixels. mutated. - vpunpcklwd ymm2, ymm3, ymm3 // low 4 inverted alphas. mutated. 1, 1, a, a - vpunpckhwd ymm3, ymm3, ymm3 // high 4 inverted alphas. mutated. - vpshufb ymm2, ymm2, ymm5 // replicate low 4 alphas. 1, a, a, a - vpshufb ymm3, ymm3, ymm5 // replicate high 4 alphas - vpmulhuw ymm0, ymm0, ymm2 // rgb * ia - vpmulhuw ymm1, ymm1, ymm3 // rgb * ia - vpackuswb ymm0, ymm0, ymm1 // unmutated. - vmovdqu [eax + edx], ymm0 - lea eax, [eax + 32] - sub ecx, 8 - jg convertloop - - pop edi - pop esi - pop ebx - vzeroupper - ret - } -} -#endif // USE_GATHER -#endif // HAS_ARGBATTENUATEROW_AVX2 - -#ifdef HAS_ARGBGRAYROW_SSSE3 -// Convert 8 ARGB pixels (64 bytes) to 8 Gray ARGB pixels. -__declspec(naked) -void ARGBGrayRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_argb */ - mov ecx, [esp + 12] /* width */ - movdqa xmm4, xmmword ptr kARGBToYJ - movdqa xmm5, xmmword ptr kAddYJ64 - - convertloop: - movdqu xmm0, [eax] // G - movdqu xmm1, [eax + 16] - pmaddubsw xmm0, xmm4 - pmaddubsw xmm1, xmm4 - phaddw xmm0, xmm1 - paddw xmm0, xmm5 // Add .5 for rounding. - psrlw xmm0, 7 - packuswb xmm0, xmm0 // 8 G bytes - movdqu xmm2, [eax] // A - movdqu xmm3, [eax + 16] - lea eax, [eax + 32] - psrld xmm2, 24 - psrld xmm3, 24 - packuswb xmm2, xmm3 - packuswb xmm2, xmm2 // 8 A bytes - movdqa xmm3, xmm0 // Weave into GG, GA, then GGGA - punpcklbw xmm0, xmm0 // 8 GG words - punpcklbw xmm3, xmm2 // 8 GA words - movdqa xmm1, xmm0 - punpcklwd xmm0, xmm3 // GGGA first 4 - punpckhwd xmm1, xmm3 // GGGA next 4 - movdqu [edx], xmm0 - movdqu [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - ret - } -} -#endif // HAS_ARGBGRAYROW_SSSE3 - -#ifdef HAS_ARGBSEPIAROW_SSSE3 -// b = (r * 35 + g * 68 + b * 17) >> 7 -// g = (r * 45 + g * 88 + b * 22) >> 7 -// r = (r * 50 + g * 98 + b * 24) >> 7 -// Constant for ARGB color to sepia tone. -static const vec8 kARGBToSepiaB = { - 17, 68, 35, 0, 17, 68, 35, 0, 17, 68, 35, 0, 17, 68, 35, 0 -}; - -static const vec8 kARGBToSepiaG = { - 22, 88, 45, 0, 22, 88, 45, 0, 22, 88, 45, 0, 22, 88, 45, 0 -}; - -static const vec8 kARGBToSepiaR = { - 24, 98, 50, 0, 24, 98, 50, 0, 24, 98, 50, 0, 24, 98, 50, 0 -}; - -// Convert 8 ARGB pixels (32 bytes) to 8 Sepia ARGB pixels. -__declspec(naked) -void ARGBSepiaRow_SSSE3(uint8* dst_argb, int width) { - __asm { - mov eax, [esp + 4] /* dst_argb */ - mov ecx, [esp + 8] /* width */ - movdqa xmm2, xmmword ptr kARGBToSepiaB - movdqa xmm3, xmmword ptr kARGBToSepiaG - movdqa xmm4, xmmword ptr kARGBToSepiaR - - convertloop: - movdqu xmm0, [eax] // B - movdqu xmm6, [eax + 16] - pmaddubsw xmm0, xmm2 - pmaddubsw xmm6, xmm2 - phaddw xmm0, xmm6 - psrlw xmm0, 7 - packuswb xmm0, xmm0 // 8 B values - movdqu xmm5, [eax] // G - movdqu xmm1, [eax + 16] - pmaddubsw xmm5, xmm3 - pmaddubsw xmm1, xmm3 - phaddw xmm5, xmm1 - psrlw xmm5, 7 - packuswb xmm5, xmm5 // 8 G values - punpcklbw xmm0, xmm5 // 8 BG values - movdqu xmm5, [eax] // R - movdqu xmm1, [eax + 16] - pmaddubsw xmm5, xmm4 - pmaddubsw xmm1, xmm4 - phaddw xmm5, xmm1 - psrlw xmm5, 7 - packuswb xmm5, xmm5 // 8 R values - movdqu xmm6, [eax] // A - movdqu xmm1, [eax + 16] - psrld xmm6, 24 - psrld xmm1, 24 - packuswb xmm6, xmm1 - packuswb xmm6, xmm6 // 8 A values - punpcklbw xmm5, xmm6 // 8 RA values - movdqa xmm1, xmm0 // Weave BG, RA together - punpcklwd xmm0, xmm5 // BGRA first 4 - punpckhwd xmm1, xmm5 // BGRA next 4 - movdqu [eax], xmm0 - movdqu [eax + 16], xmm1 - lea eax, [eax + 32] - sub ecx, 8 - jg convertloop - ret - } -} -#endif // HAS_ARGBSEPIAROW_SSSE3 - -#ifdef HAS_ARGBCOLORMATRIXROW_SSSE3 -// Tranform 8 ARGB pixels (32 bytes) with color matrix. -// Same as Sepia except matrix is provided. -// TODO(fbarchard): packuswbs only use half of the reg. To make RGBA, combine R -// and B into a high and low, then G/A, unpackl/hbw and then unpckl/hwd. -__declspec(naked) -void ARGBColorMatrixRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_argb */ - mov ecx, [esp + 12] /* matrix_argb */ - movdqu xmm5, [ecx] - pshufd xmm2, xmm5, 0x00 - pshufd xmm3, xmm5, 0x55 - pshufd xmm4, xmm5, 0xaa - pshufd xmm5, xmm5, 0xff - mov ecx, [esp + 16] /* width */ - - convertloop: - movdqu xmm0, [eax] // B - movdqu xmm7, [eax + 16] - pmaddubsw xmm0, xmm2 - pmaddubsw xmm7, xmm2 - movdqu xmm6, [eax] // G - movdqu xmm1, [eax + 16] - pmaddubsw xmm6, xmm3 - pmaddubsw xmm1, xmm3 - phaddsw xmm0, xmm7 // B - phaddsw xmm6, xmm1 // G - psraw xmm0, 6 // B - psraw xmm6, 6 // G - packuswb xmm0, xmm0 // 8 B values - packuswb xmm6, xmm6 // 8 G values - punpcklbw xmm0, xmm6 // 8 BG values - movdqu xmm1, [eax] // R - movdqu xmm7, [eax + 16] - pmaddubsw xmm1, xmm4 - pmaddubsw xmm7, xmm4 - phaddsw xmm1, xmm7 // R - movdqu xmm6, [eax] // A - movdqu xmm7, [eax + 16] - pmaddubsw xmm6, xmm5 - pmaddubsw xmm7, xmm5 - phaddsw xmm6, xmm7 // A - psraw xmm1, 6 // R - psraw xmm6, 6 // A - packuswb xmm1, xmm1 // 8 R values - packuswb xmm6, xmm6 // 8 A values - punpcklbw xmm1, xmm6 // 8 RA values - movdqa xmm6, xmm0 // Weave BG, RA together - punpcklwd xmm0, xmm1 // BGRA first 4 - punpckhwd xmm6, xmm1 // BGRA next 4 - movdqu [edx], xmm0 - movdqu [edx + 16], xmm6 - lea eax, [eax + 32] - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - ret - } -} -#endif // HAS_ARGBCOLORMATRIXROW_SSSE3 - -#ifdef HAS_ARGBQUANTIZEROW_SSE2 -// Quantize 4 ARGB pixels (16 bytes). -__declspec(naked) -void ARGBQuantizeRow_SSE2(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width) { - __asm { - mov eax, [esp + 4] /* dst_argb */ - movd xmm2, [esp + 8] /* scale */ - movd xmm3, [esp + 12] /* interval_size */ - movd xmm4, [esp + 16] /* interval_offset */ - mov ecx, [esp + 20] /* width */ - pshuflw xmm2, xmm2, 040h - pshufd xmm2, xmm2, 044h - pshuflw xmm3, xmm3, 040h - pshufd xmm3, xmm3, 044h - pshuflw xmm4, xmm4, 040h - pshufd xmm4, xmm4, 044h - pxor xmm5, xmm5 // constant 0 - pcmpeqb xmm6, xmm6 // generate mask 0xff000000 - pslld xmm6, 24 - - convertloop: - movdqu xmm0, [eax] // read 4 pixels - punpcklbw xmm0, xmm5 // first 2 pixels - pmulhuw xmm0, xmm2 // pixel * scale >> 16 - movdqu xmm1, [eax] // read 4 pixels - punpckhbw xmm1, xmm5 // next 2 pixels - pmulhuw xmm1, xmm2 - pmullw xmm0, xmm3 // * interval_size - movdqu xmm7, [eax] // read 4 pixels - pmullw xmm1, xmm3 - pand xmm7, xmm6 // mask alpha - paddw xmm0, xmm4 // + interval_size / 2 - paddw xmm1, xmm4 - packuswb xmm0, xmm1 - por xmm0, xmm7 - movdqu [eax], xmm0 - lea eax, [eax + 16] - sub ecx, 4 - jg convertloop - ret - } -} -#endif // HAS_ARGBQUANTIZEROW_SSE2 - -#ifdef HAS_ARGBSHADEROW_SSE2 -// Shade 4 pixels at a time by specified value. -__declspec(naked) -void ARGBShadeRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // width - movd xmm2, [esp + 16] // value - punpcklbw xmm2, xmm2 - punpcklqdq xmm2, xmm2 - - convertloop: - movdqu xmm0, [eax] // read 4 pixels - lea eax, [eax + 16] - movdqa xmm1, xmm0 - punpcklbw xmm0, xmm0 // first 2 - punpckhbw xmm1, xmm1 // next 2 - pmulhuw xmm0, xmm2 // argb * value - pmulhuw xmm1, xmm2 // argb * value - psrlw xmm0, 8 - psrlw xmm1, 8 - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg convertloop - - ret - } -} -#endif // HAS_ARGBSHADEROW_SSE2 - -#ifdef HAS_ARGBMULTIPLYROW_SSE2 -// Multiply 2 rows of ARGB pixels together, 4 pixels at a time. -__declspec(naked) -void ARGBMultiplyRow_SSE2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_argb0 - mov esi, [esp + 4 + 8] // src_argb1 - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - pxor xmm5, xmm5 // constant 0 - - convertloop: - movdqu xmm0, [eax] // read 4 pixels from src_argb0 - movdqu xmm2, [esi] // read 4 pixels from src_argb1 - movdqu xmm1, xmm0 - movdqu xmm3, xmm2 - punpcklbw xmm0, xmm0 // first 2 - punpckhbw xmm1, xmm1 // next 2 - punpcklbw xmm2, xmm5 // first 2 - punpckhbw xmm3, xmm5 // next 2 - pmulhuw xmm0, xmm2 // src_argb0 * src_argb1 first 2 - pmulhuw xmm1, xmm3 // src_argb0 * src_argb1 next 2 - lea eax, [eax + 16] - lea esi, [esi + 16] - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg convertloop - - pop esi - ret - } -} -#endif // HAS_ARGBMULTIPLYROW_SSE2 - -#ifdef HAS_ARGBADDROW_SSE2 -// Add 2 rows of ARGB pixels together, 4 pixels at a time. -// TODO(fbarchard): Port this to posix, neon and other math functions. -__declspec(naked) -void ARGBAddRow_SSE2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_argb0 - mov esi, [esp + 4 + 8] // src_argb1 - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - - sub ecx, 4 - jl convertloop49 - - convertloop4: - movdqu xmm0, [eax] // read 4 pixels from src_argb0 - lea eax, [eax + 16] - movdqu xmm1, [esi] // read 4 pixels from src_argb1 - lea esi, [esi + 16] - paddusb xmm0, xmm1 // src_argb0 + src_argb1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jge convertloop4 - - convertloop49: - add ecx, 4 - 1 - jl convertloop19 - - convertloop1: - movd xmm0, [eax] // read 1 pixels from src_argb0 - lea eax, [eax + 4] - movd xmm1, [esi] // read 1 pixels from src_argb1 - lea esi, [esi + 4] - paddusb xmm0, xmm1 // src_argb0 + src_argb1 - movd [edx], xmm0 - lea edx, [edx + 4] - sub ecx, 1 - jge convertloop1 - - convertloop19: - pop esi - ret - } -} -#endif // HAS_ARGBADDROW_SSE2 - -#ifdef HAS_ARGBSUBTRACTROW_SSE2 -// Subtract 2 rows of ARGB pixels together, 4 pixels at a time. -__declspec(naked) -void ARGBSubtractRow_SSE2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_argb0 - mov esi, [esp + 4 + 8] // src_argb1 - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - - convertloop: - movdqu xmm0, [eax] // read 4 pixels from src_argb0 - lea eax, [eax + 16] - movdqu xmm1, [esi] // read 4 pixels from src_argb1 - lea esi, [esi + 16] - psubusb xmm0, xmm1 // src_argb0 - src_argb1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg convertloop - - pop esi - ret - } -} -#endif // HAS_ARGBSUBTRACTROW_SSE2 - -#ifdef HAS_ARGBMULTIPLYROW_AVX2 -// Multiply 2 rows of ARGB pixels together, 8 pixels at a time. -__declspec(naked) -void ARGBMultiplyRow_AVX2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_argb0 - mov esi, [esp + 4 + 8] // src_argb1 - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - vpxor ymm5, ymm5, ymm5 // constant 0 - - convertloop: - vmovdqu ymm1, [eax] // read 8 pixels from src_argb0 - lea eax, [eax + 32] - vmovdqu ymm3, [esi] // read 8 pixels from src_argb1 - lea esi, [esi + 32] - vpunpcklbw ymm0, ymm1, ymm1 // low 4 - vpunpckhbw ymm1, ymm1, ymm1 // high 4 - vpunpcklbw ymm2, ymm3, ymm5 // low 4 - vpunpckhbw ymm3, ymm3, ymm5 // high 4 - vpmulhuw ymm0, ymm0, ymm2 // src_argb0 * src_argb1 low 4 - vpmulhuw ymm1, ymm1, ymm3 // src_argb0 * src_argb1 high 4 - vpackuswb ymm0, ymm0, ymm1 - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - - pop esi - vzeroupper - ret - } -} -#endif // HAS_ARGBMULTIPLYROW_AVX2 - -#ifdef HAS_ARGBADDROW_AVX2 -// Add 2 rows of ARGB pixels together, 8 pixels at a time. -__declspec(naked) -void ARGBAddRow_AVX2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_argb0 - mov esi, [esp + 4 + 8] // src_argb1 - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - - convertloop: - vmovdqu ymm0, [eax] // read 8 pixels from src_argb0 - lea eax, [eax + 32] - vpaddusb ymm0, ymm0, [esi] // add 8 pixels from src_argb1 - lea esi, [esi + 32] - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - - pop esi - vzeroupper - ret - } -} -#endif // HAS_ARGBADDROW_AVX2 - -#ifdef HAS_ARGBSUBTRACTROW_AVX2 -// Subtract 2 rows of ARGB pixels together, 8 pixels at a time. -__declspec(naked) -void ARGBSubtractRow_AVX2(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_argb0 - mov esi, [esp + 4 + 8] // src_argb1 - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - - convertloop: - vmovdqu ymm0, [eax] // read 8 pixels from src_argb0 - lea eax, [eax + 32] - vpsubusb ymm0, ymm0, [esi] // src_argb0 - src_argb1 - lea esi, [esi + 32] - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 8 - jg convertloop - - pop esi - vzeroupper - ret - } -} -#endif // HAS_ARGBSUBTRACTROW_AVX2 - -#ifdef HAS_SOBELXROW_SSE2 -// SobelX as a matrix is -// -1 0 1 -// -2 0 2 -// -1 0 1 -__declspec(naked) -void SobelXRow_SSE2(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_y0 - mov esi, [esp + 8 + 8] // src_y1 - mov edi, [esp + 8 + 12] // src_y2 - mov edx, [esp + 8 + 16] // dst_sobelx - mov ecx, [esp + 8 + 20] // width - sub esi, eax - sub edi, eax - sub edx, eax - pxor xmm5, xmm5 // constant 0 - - convertloop: - movq xmm0, qword ptr [eax] // read 8 pixels from src_y0[0] - movq xmm1, qword ptr [eax + 2] // read 8 pixels from src_y0[2] - punpcklbw xmm0, xmm5 - punpcklbw xmm1, xmm5 - psubw xmm0, xmm1 - movq xmm1, qword ptr [eax + esi] // read 8 pixels from src_y1[0] - movq xmm2, qword ptr [eax + esi + 2] // read 8 pixels from src_y1[2] - punpcklbw xmm1, xmm5 - punpcklbw xmm2, xmm5 - psubw xmm1, xmm2 - movq xmm2, qword ptr [eax + edi] // read 8 pixels from src_y2[0] - movq xmm3, qword ptr [eax + edi + 2] // read 8 pixels from src_y2[2] - punpcklbw xmm2, xmm5 - punpcklbw xmm3, xmm5 - psubw xmm2, xmm3 - paddw xmm0, xmm2 - paddw xmm0, xmm1 - paddw xmm0, xmm1 - pxor xmm1, xmm1 // abs = max(xmm0, -xmm0). SSSE3 could use pabsw - psubw xmm1, xmm0 - pmaxsw xmm0, xmm1 - packuswb xmm0, xmm0 - movq qword ptr [eax + edx], xmm0 - lea eax, [eax + 8] - sub ecx, 8 - jg convertloop - - pop edi - pop esi - ret - } -} -#endif // HAS_SOBELXROW_SSE2 - -#ifdef HAS_SOBELYROW_SSE2 -// SobelY as a matrix is -// -1 -2 -1 -// 0 0 0 -// 1 2 1 -__declspec(naked) -void SobelYRow_SSE2(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_y0 - mov esi, [esp + 4 + 8] // src_y1 - mov edx, [esp + 4 + 12] // dst_sobely - mov ecx, [esp + 4 + 16] // width - sub esi, eax - sub edx, eax - pxor xmm5, xmm5 // constant 0 - - convertloop: - movq xmm0, qword ptr [eax] // read 8 pixels from src_y0[0] - movq xmm1, qword ptr [eax + esi] // read 8 pixels from src_y1[0] - punpcklbw xmm0, xmm5 - punpcklbw xmm1, xmm5 - psubw xmm0, xmm1 - movq xmm1, qword ptr [eax + 1] // read 8 pixels from src_y0[1] - movq xmm2, qword ptr [eax + esi + 1] // read 8 pixels from src_y1[1] - punpcklbw xmm1, xmm5 - punpcklbw xmm2, xmm5 - psubw xmm1, xmm2 - movq xmm2, qword ptr [eax + 2] // read 8 pixels from src_y0[2] - movq xmm3, qword ptr [eax + esi + 2] // read 8 pixels from src_y1[2] - punpcklbw xmm2, xmm5 - punpcklbw xmm3, xmm5 - psubw xmm2, xmm3 - paddw xmm0, xmm2 - paddw xmm0, xmm1 - paddw xmm0, xmm1 - pxor xmm1, xmm1 // abs = max(xmm0, -xmm0). SSSE3 could use pabsw - psubw xmm1, xmm0 - pmaxsw xmm0, xmm1 - packuswb xmm0, xmm0 - movq qword ptr [eax + edx], xmm0 - lea eax, [eax + 8] - sub ecx, 8 - jg convertloop - - pop esi - ret - } -} -#endif // HAS_SOBELYROW_SSE2 - -#ifdef HAS_SOBELROW_SSE2 -// Adds Sobel X and Sobel Y and stores Sobel into ARGB. -// A = 255 -// R = Sobel -// G = Sobel -// B = Sobel -__declspec(naked) -void SobelRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_sobelx - mov esi, [esp + 4 + 8] // src_sobely - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - sub esi, eax - pcmpeqb xmm5, xmm5 // alpha 255 - pslld xmm5, 24 // 0xff000000 - - convertloop: - movdqu xmm0, [eax] // read 16 pixels src_sobelx - movdqu xmm1, [eax + esi] // read 16 pixels src_sobely - lea eax, [eax + 16] - paddusb xmm0, xmm1 // sobel = sobelx + sobely - movdqa xmm2, xmm0 // GG - punpcklbw xmm2, xmm0 // First 8 - punpckhbw xmm0, xmm0 // Next 8 - movdqa xmm1, xmm2 // GGGG - punpcklwd xmm1, xmm2 // First 4 - punpckhwd xmm2, xmm2 // Next 4 - por xmm1, xmm5 // GGGA - por xmm2, xmm5 - movdqa xmm3, xmm0 // GGGG - punpcklwd xmm3, xmm0 // Next 4 - punpckhwd xmm0, xmm0 // Last 4 - por xmm3, xmm5 // GGGA - por xmm0, xmm5 - movdqu [edx], xmm1 - movdqu [edx + 16], xmm2 - movdqu [edx + 32], xmm3 - movdqu [edx + 48], xmm0 - lea edx, [edx + 64] - sub ecx, 16 - jg convertloop - - pop esi - ret - } -} -#endif // HAS_SOBELROW_SSE2 - -#ifdef HAS_SOBELTOPLANEROW_SSE2 -// Adds Sobel X and Sobel Y and stores Sobel into a plane. -__declspec(naked) -void SobelToPlaneRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_sobelx - mov esi, [esp + 4 + 8] // src_sobely - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - sub esi, eax - - convertloop: - movdqu xmm0, [eax] // read 16 pixels src_sobelx - movdqu xmm1, [eax + esi] // read 16 pixels src_sobely - lea eax, [eax + 16] - paddusb xmm0, xmm1 // sobel = sobelx + sobely - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg convertloop - - pop esi - ret - } -} -#endif // HAS_SOBELTOPLANEROW_SSE2 - -#ifdef HAS_SOBELXYROW_SSE2 -// Mixes Sobel X, Sobel Y and Sobel into ARGB. -// A = 255 -// R = Sobel X -// G = Sobel -// B = Sobel Y -__declspec(naked) -void SobelXYRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_sobelx - mov esi, [esp + 4 + 8] // src_sobely - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // width - sub esi, eax - pcmpeqb xmm5, xmm5 // alpha 255 - - convertloop: - movdqu xmm0, [eax] // read 16 pixels src_sobelx - movdqu xmm1, [eax + esi] // read 16 pixels src_sobely - lea eax, [eax + 16] - movdqa xmm2, xmm0 - paddusb xmm2, xmm1 // sobel = sobelx + sobely - movdqa xmm3, xmm0 // XA - punpcklbw xmm3, xmm5 - punpckhbw xmm0, xmm5 - movdqa xmm4, xmm1 // YS - punpcklbw xmm4, xmm2 - punpckhbw xmm1, xmm2 - movdqa xmm6, xmm4 // YSXA - punpcklwd xmm6, xmm3 // First 4 - punpckhwd xmm4, xmm3 // Next 4 - movdqa xmm7, xmm1 // YSXA - punpcklwd xmm7, xmm0 // Next 4 - punpckhwd xmm1, xmm0 // Last 4 - movdqu [edx], xmm6 - movdqu [edx + 16], xmm4 - movdqu [edx + 32], xmm7 - movdqu [edx + 48], xmm1 - lea edx, [edx + 64] - sub ecx, 16 - jg convertloop - - pop esi - ret - } -} -#endif // HAS_SOBELXYROW_SSE2 - -#ifdef HAS_CUMULATIVESUMTOAVERAGEROW_SSE2 -// Consider float CumulativeSum. -// Consider calling CumulativeSum one row at time as needed. -// Consider circular CumulativeSum buffer of radius * 2 + 1 height. -// Convert cumulative sum for an area to an average for 1 pixel. -// topleft is pointer to top left of CumulativeSum buffer for area. -// botleft is pointer to bottom left of CumulativeSum buffer. -// width is offset from left to right of area in CumulativeSum buffer measured -// in number of ints. -// area is the number of pixels in the area being averaged. -// dst points to pixel to store result to. -// count is number of averaged pixels to produce. -// Does 4 pixels at a time. -// This function requires alignment on accumulation buffer pointers. -void CumulativeSumToAverageRow_SSE2(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, - int count) { - __asm { - mov eax, topleft // eax topleft - mov esi, botleft // esi botleft - mov edx, width - movd xmm5, area - mov edi, dst - mov ecx, count - cvtdq2ps xmm5, xmm5 - rcpss xmm4, xmm5 // 1.0f / area - pshufd xmm4, xmm4, 0 - sub ecx, 4 - jl l4b - - cmp area, 128 // 128 pixels will not overflow 15 bits. - ja l4 - - pshufd xmm5, xmm5, 0 // area - pcmpeqb xmm6, xmm6 // constant of 65536.0 - 1 = 65535.0 - psrld xmm6, 16 - cvtdq2ps xmm6, xmm6 - addps xmm5, xmm6 // (65536.0 + area - 1) - mulps xmm5, xmm4 // (65536.0 + area - 1) * 1 / area - cvtps2dq xmm5, xmm5 // 0.16 fixed point - packssdw xmm5, xmm5 // 16 bit shorts - - // 4 pixel loop small blocks. - s4: - // top left - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - - // - top right - psubd xmm0, [eax + edx * 4] - psubd xmm1, [eax + edx * 4 + 16] - psubd xmm2, [eax + edx * 4 + 32] - psubd xmm3, [eax + edx * 4 + 48] - lea eax, [eax + 64] - - // - bottom left - psubd xmm0, [esi] - psubd xmm1, [esi + 16] - psubd xmm2, [esi + 32] - psubd xmm3, [esi + 48] - - // + bottom right - paddd xmm0, [esi + edx * 4] - paddd xmm1, [esi + edx * 4 + 16] - paddd xmm2, [esi + edx * 4 + 32] - paddd xmm3, [esi + edx * 4 + 48] - lea esi, [esi + 64] - - packssdw xmm0, xmm1 // pack 4 pixels into 2 registers - packssdw xmm2, xmm3 - - pmulhuw xmm0, xmm5 - pmulhuw xmm2, xmm5 - - packuswb xmm0, xmm2 - movdqu [edi], xmm0 - lea edi, [edi + 16] - sub ecx, 4 - jge s4 - - jmp l4b - - // 4 pixel loop - l4: - // top left - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - - // - top right - psubd xmm0, [eax + edx * 4] - psubd xmm1, [eax + edx * 4 + 16] - psubd xmm2, [eax + edx * 4 + 32] - psubd xmm3, [eax + edx * 4 + 48] - lea eax, [eax + 64] - - // - bottom left - psubd xmm0, [esi] - psubd xmm1, [esi + 16] - psubd xmm2, [esi + 32] - psubd xmm3, [esi + 48] - - // + bottom right - paddd xmm0, [esi + edx * 4] - paddd xmm1, [esi + edx * 4 + 16] - paddd xmm2, [esi + edx * 4 + 32] - paddd xmm3, [esi + edx * 4 + 48] - lea esi, [esi + 64] - - cvtdq2ps xmm0, xmm0 // Average = Sum * 1 / Area - cvtdq2ps xmm1, xmm1 - mulps xmm0, xmm4 - mulps xmm1, xmm4 - cvtdq2ps xmm2, xmm2 - cvtdq2ps xmm3, xmm3 - mulps xmm2, xmm4 - mulps xmm3, xmm4 - cvtps2dq xmm0, xmm0 - cvtps2dq xmm1, xmm1 - cvtps2dq xmm2, xmm2 - cvtps2dq xmm3, xmm3 - packssdw xmm0, xmm1 - packssdw xmm2, xmm3 - packuswb xmm0, xmm2 - movdqu [edi], xmm0 - lea edi, [edi + 16] - sub ecx, 4 - jge l4 - - l4b: - add ecx, 4 - 1 - jl l1b - - // 1 pixel loop - l1: - movdqu xmm0, [eax] - psubd xmm0, [eax + edx * 4] - lea eax, [eax + 16] - psubd xmm0, [esi] - paddd xmm0, [esi + edx * 4] - lea esi, [esi + 16] - cvtdq2ps xmm0, xmm0 - mulps xmm0, xmm4 - cvtps2dq xmm0, xmm0 - packssdw xmm0, xmm0 - packuswb xmm0, xmm0 - movd dword ptr [edi], xmm0 - lea edi, [edi + 4] - sub ecx, 1 - jge l1 - l1b: - } -} -#endif // HAS_CUMULATIVESUMTOAVERAGEROW_SSE2 - -#ifdef HAS_COMPUTECUMULATIVESUMROW_SSE2 -// Creates a table of cumulative sums where each value is a sum of all values -// above and to the left of the value. -void ComputeCumulativeSumRow_SSE2(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width) { - __asm { - mov eax, row - mov edx, cumsum - mov esi, previous_cumsum - mov ecx, width - pxor xmm0, xmm0 - pxor xmm1, xmm1 - - sub ecx, 4 - jl l4b - test edx, 15 - jne l4b - - // 4 pixel loop - l4: - movdqu xmm2, [eax] // 4 argb pixels 16 bytes. - lea eax, [eax + 16] - movdqa xmm4, xmm2 - - punpcklbw xmm2, xmm1 - movdqa xmm3, xmm2 - punpcklwd xmm2, xmm1 - punpckhwd xmm3, xmm1 - - punpckhbw xmm4, xmm1 - movdqa xmm5, xmm4 - punpcklwd xmm4, xmm1 - punpckhwd xmm5, xmm1 - - paddd xmm0, xmm2 - movdqu xmm2, [esi] // previous row above. - paddd xmm2, xmm0 - - paddd xmm0, xmm3 - movdqu xmm3, [esi + 16] - paddd xmm3, xmm0 - - paddd xmm0, xmm4 - movdqu xmm4, [esi + 32] - paddd xmm4, xmm0 - - paddd xmm0, xmm5 - movdqu xmm5, [esi + 48] - lea esi, [esi + 64] - paddd xmm5, xmm0 - - movdqu [edx], xmm2 - movdqu [edx + 16], xmm3 - movdqu [edx + 32], xmm4 - movdqu [edx + 48], xmm5 - - lea edx, [edx + 64] - sub ecx, 4 - jge l4 - - l4b: - add ecx, 4 - 1 - jl l1b - - // 1 pixel loop - l1: - movd xmm2, dword ptr [eax] // 1 argb pixel 4 bytes. - lea eax, [eax + 4] - punpcklbw xmm2, xmm1 - punpcklwd xmm2, xmm1 - paddd xmm0, xmm2 - movdqu xmm2, [esi] - lea esi, [esi + 16] - paddd xmm2, xmm0 - movdqu [edx], xmm2 - lea edx, [edx + 16] - sub ecx, 1 - jge l1 - - l1b: - } -} -#endif // HAS_COMPUTECUMULATIVESUMROW_SSE2 - -#ifdef HAS_ARGBAFFINEROW_SSE2 -// Copy ARGB pixels from source image with slope to a row of destination. -__declspec(naked) -LIBYUV_API -void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width) { - __asm { - push esi - push edi - mov eax, [esp + 12] // src_argb - mov esi, [esp + 16] // stride - mov edx, [esp + 20] // dst_argb - mov ecx, [esp + 24] // pointer to uv_dudv - movq xmm2, qword ptr [ecx] // uv - movq xmm7, qword ptr [ecx + 8] // dudv - mov ecx, [esp + 28] // width - shl esi, 16 // 4, stride - add esi, 4 - movd xmm5, esi - sub ecx, 4 - jl l4b - - // setup for 4 pixel loop - pshufd xmm7, xmm7, 0x44 // dup dudv - pshufd xmm5, xmm5, 0 // dup 4, stride - movdqa xmm0, xmm2 // x0, y0, x1, y1 - addps xmm0, xmm7 - movlhps xmm2, xmm0 - movdqa xmm4, xmm7 - addps xmm4, xmm4 // dudv *= 2 - movdqa xmm3, xmm2 // x2, y2, x3, y3 - addps xmm3, xmm4 - addps xmm4, xmm4 // dudv *= 4 - - // 4 pixel loop - l4: - cvttps2dq xmm0, xmm2 // x, y float to int first 2 - cvttps2dq xmm1, xmm3 // x, y float to int next 2 - packssdw xmm0, xmm1 // x, y as 8 shorts - pmaddwd xmm0, xmm5 // offsets = x * 4 + y * stride. - movd esi, xmm0 - pshufd xmm0, xmm0, 0x39 // shift right - movd edi, xmm0 - pshufd xmm0, xmm0, 0x39 // shift right - movd xmm1, [eax + esi] // read pixel 0 - movd xmm6, [eax + edi] // read pixel 1 - punpckldq xmm1, xmm6 // combine pixel 0 and 1 - addps xmm2, xmm4 // x, y += dx, dy first 2 - movq qword ptr [edx], xmm1 - movd esi, xmm0 - pshufd xmm0, xmm0, 0x39 // shift right - movd edi, xmm0 - movd xmm6, [eax + esi] // read pixel 2 - movd xmm0, [eax + edi] // read pixel 3 - punpckldq xmm6, xmm0 // combine pixel 2 and 3 - addps xmm3, xmm4 // x, y += dx, dy next 2 - movq qword ptr 8[edx], xmm6 - lea edx, [edx + 16] - sub ecx, 4 - jge l4 - - l4b: - add ecx, 4 - 1 - jl l1b - - // 1 pixel loop - l1: - cvttps2dq xmm0, xmm2 // x, y float to int - packssdw xmm0, xmm0 // x, y as shorts - pmaddwd xmm0, xmm5 // offset = x * 4 + y * stride - addps xmm2, xmm7 // x, y += dx, dy - movd esi, xmm0 - movd xmm0, [eax + esi] // copy a pixel - movd [edx], xmm0 - lea edx, [edx + 4] - sub ecx, 1 - jge l1 - l1b: - pop edi - pop esi - ret - } -} -#endif // HAS_ARGBAFFINEROW_SSE2 - -#ifdef HAS_INTERPOLATEROW_AVX2 -// Bilinear filter 32x2 -> 32x1 -__declspec(naked) -void InterpolateRow_AVX2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, - int source_y_fraction) { - __asm { - push esi - push edi - mov edi, [esp + 8 + 4] // dst_ptr - mov esi, [esp + 8 + 8] // src_ptr - mov edx, [esp + 8 + 12] // src_stride - mov ecx, [esp + 8 + 16] // dst_width - mov eax, [esp + 8 + 20] // source_y_fraction (0..255) - // Dispatch to specialized filters if applicable. - cmp eax, 0 - je xloop100 // 0 / 256. Blend 100 / 0. - sub edi, esi - cmp eax, 128 - je xloop50 // 128 /256 is 0.50. Blend 50 / 50. - - vmovd xmm0, eax // high fraction 0..255 - neg eax - add eax, 256 - vmovd xmm5, eax // low fraction 256..1 - vpunpcklbw xmm5, xmm5, xmm0 - vpunpcklwd xmm5, xmm5, xmm5 - vbroadcastss ymm5, xmm5 - - mov eax, 0x80808080 // 128b for bias and rounding. - vmovd xmm4, eax - vbroadcastss ymm4, xmm4 - - xloop: - vmovdqu ymm0, [esi] - vmovdqu ymm2, [esi + edx] - vpunpckhbw ymm1, ymm0, ymm2 // mutates - vpunpcklbw ymm0, ymm0, ymm2 - vpsubb ymm1, ymm1, ymm4 // bias to signed image - vpsubb ymm0, ymm0, ymm4 - vpmaddubsw ymm1, ymm5, ymm1 - vpmaddubsw ymm0, ymm5, ymm0 - vpaddw ymm1, ymm1, ymm4 // unbias and round - vpaddw ymm0, ymm0, ymm4 - vpsrlw ymm1, ymm1, 8 - vpsrlw ymm0, ymm0, 8 - vpackuswb ymm0, ymm0, ymm1 // unmutates - vmovdqu [esi + edi], ymm0 - lea esi, [esi + 32] - sub ecx, 32 - jg xloop - jmp xloop99 - - // Blend 50 / 50. - xloop50: - vmovdqu ymm0, [esi] - vpavgb ymm0, ymm0, [esi + edx] - vmovdqu [esi + edi], ymm0 - lea esi, [esi + 32] - sub ecx, 32 - jg xloop50 - jmp xloop99 - - // Blend 100 / 0 - Copy row unchanged. - xloop100: - rep movsb - - xloop99: - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_INTERPOLATEROW_AVX2 - -// Bilinear filter 16x2 -> 16x1 -// TODO(fbarchard): Consider allowing 256 using memcpy. -__declspec(naked) -void InterpolateRow_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, - int source_y_fraction) { - __asm { - push esi - push edi - - mov edi, [esp + 8 + 4] // dst_ptr - mov esi, [esp + 8 + 8] // src_ptr - mov edx, [esp + 8 + 12] // src_stride - mov ecx, [esp + 8 + 16] // dst_width - mov eax, [esp + 8 + 20] // source_y_fraction (0..255) - sub edi, esi - // Dispatch to specialized filters if applicable. - cmp eax, 0 - je xloop100 // 0 /256. Blend 100 / 0. - cmp eax, 128 - je xloop50 // 128 / 256 is 0.50. Blend 50 / 50. - - movd xmm0, eax // high fraction 0..255 - neg eax - add eax, 256 - movd xmm5, eax // low fraction 255..1 - punpcklbw xmm5, xmm0 - punpcklwd xmm5, xmm5 - pshufd xmm5, xmm5, 0 - mov eax, 0x80808080 // 128 for biasing image to signed. - movd xmm4, eax - pshufd xmm4, xmm4, 0x00 - - xloop: - movdqu xmm0, [esi] - movdqu xmm2, [esi + edx] - movdqu xmm1, xmm0 - punpcklbw xmm0, xmm2 - punpckhbw xmm1, xmm2 - psubb xmm0, xmm4 // bias image by -128 - psubb xmm1, xmm4 - movdqa xmm2, xmm5 - movdqa xmm3, xmm5 - pmaddubsw xmm2, xmm0 - pmaddubsw xmm3, xmm1 - paddw xmm2, xmm4 - paddw xmm3, xmm4 - psrlw xmm2, 8 - psrlw xmm3, 8 - packuswb xmm2, xmm3 - movdqu [esi + edi], xmm2 - lea esi, [esi + 16] - sub ecx, 16 - jg xloop - jmp xloop99 - - // Blend 50 / 50. - xloop50: - movdqu xmm0, [esi] - movdqu xmm1, [esi + edx] - pavgb xmm0, xmm1 - movdqu [esi + edi], xmm0 - lea esi, [esi + 16] - sub ecx, 16 - jg xloop50 - jmp xloop99 - - // Blend 100 / 0 - Copy row unchanged. - xloop100: - movdqu xmm0, [esi] - movdqu [esi + edi], xmm0 - lea esi, [esi + 16] - sub ecx, 16 - jg xloop100 - - xloop99: - pop edi - pop esi - ret - } -} - -// For BGRAToARGB, ABGRToARGB, RGBAToARGB, and ARGBToRGBA. -__declspec(naked) -void ARGBShuffleRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // shuffler - movdqu xmm5, [ecx] - mov ecx, [esp + 16] // width - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - pshufb xmm0, xmm5 - pshufb xmm1, xmm5 - movdqu [edx], xmm0 - movdqu [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 8 - jg wloop - ret - } -} - -#ifdef HAS_ARGBSHUFFLEROW_AVX2 -__declspec(naked) -void ARGBShuffleRow_AVX2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - __asm { - mov eax, [esp + 4] // src_argb - mov edx, [esp + 8] // dst_argb - mov ecx, [esp + 12] // shuffler - vbroadcastf128 ymm5, [ecx] // same shuffle in high as low. - mov ecx, [esp + 16] // width - - wloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpshufb ymm0, ymm0, ymm5 - vpshufb ymm1, ymm1, ymm5 - vmovdqu [edx], ymm0 - vmovdqu [edx + 32], ymm1 - lea edx, [edx + 64] - sub ecx, 16 - jg wloop - - vzeroupper - ret - } -} -#endif // HAS_ARGBSHUFFLEROW_AVX2 - -__declspec(naked) -void ARGBShuffleRow_SSE2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width) { - __asm { - push ebx - push esi - mov eax, [esp + 8 + 4] // src_argb - mov edx, [esp + 8 + 8] // dst_argb - mov esi, [esp + 8 + 12] // shuffler - mov ecx, [esp + 8 + 16] // width - pxor xmm5, xmm5 - - mov ebx, [esi] // shuffler - cmp ebx, 0x03000102 - je shuf_3012 - cmp ebx, 0x00010203 - je shuf_0123 - cmp ebx, 0x00030201 - je shuf_0321 - cmp ebx, 0x02010003 - je shuf_2103 - - // TODO(fbarchard): Use one source pointer and 3 offsets. - shuf_any1: - movzx ebx, byte ptr [esi] - movzx ebx, byte ptr [eax + ebx] - mov [edx], bl - movzx ebx, byte ptr [esi + 1] - movzx ebx, byte ptr [eax + ebx] - mov [edx + 1], bl - movzx ebx, byte ptr [esi + 2] - movzx ebx, byte ptr [eax + ebx] - mov [edx + 2], bl - movzx ebx, byte ptr [esi + 3] - movzx ebx, byte ptr [eax + ebx] - mov [edx + 3], bl - lea eax, [eax + 4] - lea edx, [edx + 4] - sub ecx, 1 - jg shuf_any1 - jmp shuf99 - - shuf_0123: - movdqu xmm0, [eax] - lea eax, [eax + 16] - movdqa xmm1, xmm0 - punpcklbw xmm0, xmm5 - punpckhbw xmm1, xmm5 - pshufhw xmm0, xmm0, 01Bh // 1B = 00011011 = 0x0123 = BGRAToARGB - pshuflw xmm0, xmm0, 01Bh - pshufhw xmm1, xmm1, 01Bh - pshuflw xmm1, xmm1, 01Bh - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg shuf_0123 - jmp shuf99 - - shuf_0321: - movdqu xmm0, [eax] - lea eax, [eax + 16] - movdqa xmm1, xmm0 - punpcklbw xmm0, xmm5 - punpckhbw xmm1, xmm5 - pshufhw xmm0, xmm0, 039h // 39 = 00111001 = 0x0321 = RGBAToARGB - pshuflw xmm0, xmm0, 039h - pshufhw xmm1, xmm1, 039h - pshuflw xmm1, xmm1, 039h - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg shuf_0321 - jmp shuf99 - - shuf_2103: - movdqu xmm0, [eax] - lea eax, [eax + 16] - movdqa xmm1, xmm0 - punpcklbw xmm0, xmm5 - punpckhbw xmm1, xmm5 - pshufhw xmm0, xmm0, 093h // 93 = 10010011 = 0x2103 = ARGBToRGBA - pshuflw xmm0, xmm0, 093h - pshufhw xmm1, xmm1, 093h - pshuflw xmm1, xmm1, 093h - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg shuf_2103 - jmp shuf99 - - shuf_3012: - movdqu xmm0, [eax] - lea eax, [eax + 16] - movdqa xmm1, xmm0 - punpcklbw xmm0, xmm5 - punpckhbw xmm1, xmm5 - pshufhw xmm0, xmm0, 0C6h // C6 = 11000110 = 0x3012 = ABGRToARGB - pshuflw xmm0, xmm0, 0C6h - pshufhw xmm1, xmm1, 0C6h - pshuflw xmm1, xmm1, 0C6h - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg shuf_3012 - - shuf99: - pop esi - pop ebx - ret - } -} - -// YUY2 - Macro-pixel = 2 image pixels -// Y0U0Y1V0....Y2U2Y3V2...Y4U4Y5V4.... - -// UYVY - Macro-pixel = 2 image pixels -// U0Y0V0Y1 - -__declspec(naked) -void I422ToYUY2Row_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_frame, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_y - mov esi, [esp + 8 + 8] // src_u - mov edx, [esp + 8 + 12] // src_v - mov edi, [esp + 8 + 16] // dst_frame - mov ecx, [esp + 8 + 20] // width - sub edx, esi - - convertloop: - movq xmm2, qword ptr [esi] // U - movq xmm3, qword ptr [esi + edx] // V - lea esi, [esi + 8] - punpcklbw xmm2, xmm3 // UV - movdqu xmm0, [eax] // Y - lea eax, [eax + 16] - movdqa xmm1, xmm0 - punpcklbw xmm0, xmm2 // YUYV - punpckhbw xmm1, xmm2 - movdqu [edi], xmm0 - movdqu [edi + 16], xmm1 - lea edi, [edi + 32] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} - -__declspec(naked) -void I422ToUYVYRow_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_frame, int width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_y - mov esi, [esp + 8 + 8] // src_u - mov edx, [esp + 8 + 12] // src_v - mov edi, [esp + 8 + 16] // dst_frame - mov ecx, [esp + 8 + 20] // width - sub edx, esi - - convertloop: - movq xmm2, qword ptr [esi] // U - movq xmm3, qword ptr [esi + edx] // V - lea esi, [esi + 8] - punpcklbw xmm2, xmm3 // UV - movdqu xmm0, [eax] // Y - movdqa xmm1, xmm2 - lea eax, [eax + 16] - punpcklbw xmm1, xmm0 // UYVY - punpckhbw xmm2, xmm0 - movdqu [edi], xmm1 - movdqu [edi + 16], xmm2 - lea edi, [edi + 32] - sub ecx, 16 - jg convertloop - - pop edi - pop esi - ret - } -} - -#ifdef HAS_ARGBPOLYNOMIALROW_SSE2 -__declspec(naked) -void ARGBPolynomialRow_SSE2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] /* src_argb */ - mov edx, [esp + 4 + 8] /* dst_argb */ - mov esi, [esp + 4 + 12] /* poly */ - mov ecx, [esp + 4 + 16] /* width */ - pxor xmm3, xmm3 // 0 constant for zero extending bytes to ints. - - // 2 pixel loop. - convertloop: -// pmovzxbd xmm0, dword ptr [eax] // BGRA pixel -// pmovzxbd xmm4, dword ptr [eax + 4] // BGRA pixel - movq xmm0, qword ptr [eax] // BGRABGRA - lea eax, [eax + 8] - punpcklbw xmm0, xmm3 - movdqa xmm4, xmm0 - punpcklwd xmm0, xmm3 // pixel 0 - punpckhwd xmm4, xmm3 // pixel 1 - cvtdq2ps xmm0, xmm0 // 4 floats - cvtdq2ps xmm4, xmm4 - movdqa xmm1, xmm0 // X - movdqa xmm5, xmm4 - mulps xmm0, [esi + 16] // C1 * X - mulps xmm4, [esi + 16] - addps xmm0, [esi] // result = C0 + C1 * X - addps xmm4, [esi] - movdqa xmm2, xmm1 - movdqa xmm6, xmm5 - mulps xmm2, xmm1 // X * X - mulps xmm6, xmm5 - mulps xmm1, xmm2 // X * X * X - mulps xmm5, xmm6 - mulps xmm2, [esi + 32] // C2 * X * X - mulps xmm6, [esi + 32] - mulps xmm1, [esi + 48] // C3 * X * X * X - mulps xmm5, [esi + 48] - addps xmm0, xmm2 // result += C2 * X * X - addps xmm4, xmm6 - addps xmm0, xmm1 // result += C3 * X * X * X - addps xmm4, xmm5 - cvttps2dq xmm0, xmm0 - cvttps2dq xmm4, xmm4 - packuswb xmm0, xmm4 - packuswb xmm0, xmm0 - movq qword ptr [edx], xmm0 - lea edx, [edx + 8] - sub ecx, 2 - jg convertloop - pop esi - ret - } -} -#endif // HAS_ARGBPOLYNOMIALROW_SSE2 - -#ifdef HAS_ARGBPOLYNOMIALROW_AVX2 -__declspec(naked) -void ARGBPolynomialRow_AVX2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width) { - __asm { - mov eax, [esp + 4] /* src_argb */ - mov edx, [esp + 8] /* dst_argb */ - mov ecx, [esp + 12] /* poly */ - vbroadcastf128 ymm4, [ecx] // C0 - vbroadcastf128 ymm5, [ecx + 16] // C1 - vbroadcastf128 ymm6, [ecx + 32] // C2 - vbroadcastf128 ymm7, [ecx + 48] // C3 - mov ecx, [esp + 16] /* width */ - - // 2 pixel loop. - convertloop: - vpmovzxbd ymm0, qword ptr [eax] // 2 BGRA pixels - lea eax, [eax + 8] - vcvtdq2ps ymm0, ymm0 // X 8 floats - vmulps ymm2, ymm0, ymm0 // X * X - vmulps ymm3, ymm0, ymm7 // C3 * X - vfmadd132ps ymm0, ymm4, ymm5 // result = C0 + C1 * X - vfmadd231ps ymm0, ymm2, ymm6 // result += C2 * X * X - vfmadd231ps ymm0, ymm2, ymm3 // result += C3 * X * X * X - vcvttps2dq ymm0, ymm0 - vpackusdw ymm0, ymm0, ymm0 // b0g0r0a0_00000000_b0g0r0a0_00000000 - vpermq ymm0, ymm0, 0xd8 // b0g0r0a0_b0g0r0a0_00000000_00000000 - vpackuswb xmm0, xmm0, xmm0 // bgrabgra_00000000_00000000_00000000 - vmovq qword ptr [edx], xmm0 - lea edx, [edx + 8] - sub ecx, 2 - jg convertloop - vzeroupper - ret - } -} -#endif // HAS_ARGBPOLYNOMIALROW_AVX2 - -#ifdef HAS_ARGBCOLORTABLEROW_X86 -// Tranform ARGB pixels with color table. -__declspec(naked) -void ARGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, - int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] /* dst_argb */ - mov esi, [esp + 4 + 8] /* table_argb */ - mov ecx, [esp + 4 + 12] /* width */ - - // 1 pixel loop. - convertloop: - movzx edx, byte ptr [eax] - lea eax, [eax + 4] - movzx edx, byte ptr [esi + edx * 4] - mov byte ptr [eax - 4], dl - movzx edx, byte ptr [eax - 4 + 1] - movzx edx, byte ptr [esi + edx * 4 + 1] - mov byte ptr [eax - 4 + 1], dl - movzx edx, byte ptr [eax - 4 + 2] - movzx edx, byte ptr [esi + edx * 4 + 2] - mov byte ptr [eax - 4 + 2], dl - movzx edx, byte ptr [eax - 4 + 3] - movzx edx, byte ptr [esi + edx * 4 + 3] - mov byte ptr [eax - 4 + 3], dl - dec ecx - jg convertloop - pop esi - ret - } -} -#endif // HAS_ARGBCOLORTABLEROW_X86 - -#ifdef HAS_RGBCOLORTABLEROW_X86 -// Tranform RGB pixels with color table. -__declspec(naked) -void RGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width) { - __asm { - push esi - mov eax, [esp + 4 + 4] /* dst_argb */ - mov esi, [esp + 4 + 8] /* table_argb */ - mov ecx, [esp + 4 + 12] /* width */ - - // 1 pixel loop. - convertloop: - movzx edx, byte ptr [eax] - lea eax, [eax + 4] - movzx edx, byte ptr [esi + edx * 4] - mov byte ptr [eax - 4], dl - movzx edx, byte ptr [eax - 4 + 1] - movzx edx, byte ptr [esi + edx * 4 + 1] - mov byte ptr [eax - 4 + 1], dl - movzx edx, byte ptr [eax - 4 + 2] - movzx edx, byte ptr [esi + edx * 4 + 2] - mov byte ptr [eax - 4 + 2], dl - dec ecx - jg convertloop - - pop esi - ret - } -} -#endif // HAS_RGBCOLORTABLEROW_X86 - -#ifdef HAS_ARGBLUMACOLORTABLEROW_SSSE3 -// Tranform RGB pixels with luma table. -__declspec(naked) -void ARGBLumaColorTableRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - int width, - const uint8* luma, uint32 lumacoeff) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] /* src_argb */ - mov edi, [esp + 8 + 8] /* dst_argb */ - mov ecx, [esp + 8 + 12] /* width */ - movd xmm2, dword ptr [esp + 8 + 16] // luma table - movd xmm3, dword ptr [esp + 8 + 20] // lumacoeff - pshufd xmm2, xmm2, 0 - pshufd xmm3, xmm3, 0 - pcmpeqb xmm4, xmm4 // generate mask 0xff00ff00 - psllw xmm4, 8 - pxor xmm5, xmm5 - - // 4 pixel loop. - convertloop: - movdqu xmm0, xmmword ptr [eax] // generate luma ptr - pmaddubsw xmm0, xmm3 - phaddw xmm0, xmm0 - pand xmm0, xmm4 // mask out low bits - punpcklwd xmm0, xmm5 - paddd xmm0, xmm2 // add table base - movd esi, xmm0 - pshufd xmm0, xmm0, 0x39 // 00111001 to rotate right 32 - - movzx edx, byte ptr [eax] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi], dl - movzx edx, byte ptr [eax + 1] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 1], dl - movzx edx, byte ptr [eax + 2] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 2], dl - movzx edx, byte ptr [eax + 3] // copy alpha. - mov byte ptr [edi + 3], dl - - movd esi, xmm0 - pshufd xmm0, xmm0, 0x39 // 00111001 to rotate right 32 - - movzx edx, byte ptr [eax + 4] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 4], dl - movzx edx, byte ptr [eax + 5] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 5], dl - movzx edx, byte ptr [eax + 6] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 6], dl - movzx edx, byte ptr [eax + 7] // copy alpha. - mov byte ptr [edi + 7], dl - - movd esi, xmm0 - pshufd xmm0, xmm0, 0x39 // 00111001 to rotate right 32 - - movzx edx, byte ptr [eax + 8] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 8], dl - movzx edx, byte ptr [eax + 9] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 9], dl - movzx edx, byte ptr [eax + 10] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 10], dl - movzx edx, byte ptr [eax + 11] // copy alpha. - mov byte ptr [edi + 11], dl - - movd esi, xmm0 - - movzx edx, byte ptr [eax + 12] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 12], dl - movzx edx, byte ptr [eax + 13] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 13], dl - movzx edx, byte ptr [eax + 14] - movzx edx, byte ptr [esi + edx] - mov byte ptr [edi + 14], dl - movzx edx, byte ptr [eax + 15] // copy alpha. - mov byte ptr [edi + 15], dl - - lea eax, [eax + 16] - lea edi, [edi + 16] - sub ecx, 4 - jg convertloop - - pop edi - pop esi - ret - } -} -#endif // HAS_ARGBLUMACOLORTABLEROW_SSSE3 - -#endif // defined(_M_X64) -#endif // !defined(LIBYUV_DISABLE_X86) && (defined(_M_IX86) || defined(_M_X64)) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale.cc b/telegramgallery/src/main/cpp/libyuv/source/scale.cc deleted file mode 100644 index 36e3fe5..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale.cc +++ /dev/null @@ -1,1672 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/scale.h" - -#include -#include - -#include "libyuv/cpu_id.h" -#include "libyuv/planar_functions.h" // For CopyPlane -#include "libyuv/row.h" -#include "libyuv/scale_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -static __inline int Abs(int v) { - return v >= 0 ? v : -v; -} - -#define SUBSAMPLE(v, a, s) (v < 0) ? (-((-v + a) >> s)) : ((v + a) >> s) - -// Scale plane, 1/2 -// This is an optimized version for scaling down a plane to 1/2 of -// its original size. - -static void ScalePlaneDown2(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_ptr, uint8* dst_ptr, - enum FilterMode filtering) { - int y; - void (*ScaleRowDown2)(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) = - filtering == kFilterNone ? ScaleRowDown2_C : - (filtering == kFilterLinear ? ScaleRowDown2Linear_C : ScaleRowDown2Box_C); - int row_stride = src_stride << 1; - if (!filtering) { - src_ptr += src_stride; // Point to odd rows. - src_stride = 0; - } - -#if defined(HAS_SCALEROWDOWN2_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_Any_NEON : - (filtering == kFilterLinear ? ScaleRowDown2Linear_Any_NEON : - ScaleRowDown2Box_Any_NEON); - if (IS_ALIGNED(dst_width, 16)) { - ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_NEON : - (filtering == kFilterLinear ? ScaleRowDown2Linear_NEON : - ScaleRowDown2Box_NEON); - } - } -#endif -#if defined(HAS_SCALEROWDOWN2_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_Any_SSSE3 : - (filtering == kFilterLinear ? ScaleRowDown2Linear_Any_SSSE3 : - ScaleRowDown2Box_Any_SSSE3); - if (IS_ALIGNED(dst_width, 16)) { - ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_SSSE3 : - (filtering == kFilterLinear ? ScaleRowDown2Linear_SSSE3 : - ScaleRowDown2Box_SSSE3); - } - } -#endif -#if defined(HAS_SCALEROWDOWN2_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_Any_AVX2 : - (filtering == kFilterLinear ? ScaleRowDown2Linear_Any_AVX2 : - ScaleRowDown2Box_Any_AVX2); - if (IS_ALIGNED(dst_width, 32)) { - ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_AVX2 : - (filtering == kFilterLinear ? ScaleRowDown2Linear_AVX2 : - ScaleRowDown2Box_AVX2); - } - } -#endif -#if defined(HAS_SCALEROWDOWN2_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_ptr, 4) && - IS_ALIGNED(src_stride, 4) && IS_ALIGNED(row_stride, 4) && - IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { - ScaleRowDown2 = filtering ? - ScaleRowDown2Box_DSPR2 : ScaleRowDown2_DSPR2; - } -#endif - - if (filtering == kFilterLinear) { - src_stride = 0; - } - // TODO(fbarchard): Loop through source height to allow odd height. - for (y = 0; y < dst_height; ++y) { - ScaleRowDown2(src_ptr, src_stride, dst_ptr, dst_width); - src_ptr += row_stride; - dst_ptr += dst_stride; - } -} - -static void ScalePlaneDown2_16(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_ptr, uint16* dst_ptr, - enum FilterMode filtering) { - int y; - void (*ScaleRowDown2)(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width) = - filtering == kFilterNone ? ScaleRowDown2_16_C : - (filtering == kFilterLinear ? ScaleRowDown2Linear_16_C : - ScaleRowDown2Box_16_C); - int row_stride = src_stride << 1; - if (!filtering) { - src_ptr += src_stride; // Point to odd rows. - src_stride = 0; - } - -#if defined(HAS_SCALEROWDOWN2_16_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(dst_width, 16)) { - ScaleRowDown2 = filtering ? ScaleRowDown2Box_16_NEON : - ScaleRowDown2_16_NEON; - } -#endif -#if defined(HAS_SCALEROWDOWN2_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 16)) { - ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_16_SSE2 : - (filtering == kFilterLinear ? ScaleRowDown2Linear_16_SSE2 : - ScaleRowDown2Box_16_SSE2); - } -#endif -#if defined(HAS_SCALEROWDOWN2_16_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_ptr, 4) && - IS_ALIGNED(src_stride, 4) && IS_ALIGNED(row_stride, 4) && - IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { - ScaleRowDown2 = filtering ? - ScaleRowDown2Box_16_DSPR2 : ScaleRowDown2_16_DSPR2; - } -#endif - - if (filtering == kFilterLinear) { - src_stride = 0; - } - // TODO(fbarchard): Loop through source height to allow odd height. - for (y = 0; y < dst_height; ++y) { - ScaleRowDown2(src_ptr, src_stride, dst_ptr, dst_width); - src_ptr += row_stride; - dst_ptr += dst_stride; - } -} - -// Scale plane, 1/4 -// This is an optimized version for scaling down a plane to 1/4 of -// its original size. - -static void ScalePlaneDown4(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_ptr, uint8* dst_ptr, - enum FilterMode filtering) { - int y; - void (*ScaleRowDown4)(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) = - filtering ? ScaleRowDown4Box_C : ScaleRowDown4_C; - int row_stride = src_stride << 2; - if (!filtering) { - src_ptr += src_stride * 2; // Point to row 2. - src_stride = 0; - } -#if defined(HAS_SCALEROWDOWN4_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleRowDown4 = filtering ? - ScaleRowDown4Box_Any_NEON : ScaleRowDown4_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleRowDown4 = filtering ? ScaleRowDown4Box_NEON : ScaleRowDown4_NEON; - } - } -#endif -#if defined(HAS_SCALEROWDOWN4_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ScaleRowDown4 = filtering ? - ScaleRowDown4Box_Any_SSSE3 : ScaleRowDown4_Any_SSSE3; - if (IS_ALIGNED(dst_width, 8)) { - ScaleRowDown4 = filtering ? ScaleRowDown4Box_SSSE3 : ScaleRowDown4_SSSE3; - } - } -#endif -#if defined(HAS_SCALEROWDOWN4_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ScaleRowDown4 = filtering ? - ScaleRowDown4Box_Any_AVX2 : ScaleRowDown4_Any_AVX2; - if (IS_ALIGNED(dst_width, 16)) { - ScaleRowDown4 = filtering ? ScaleRowDown4Box_AVX2 : ScaleRowDown4_AVX2; - } - } -#endif -#if defined(HAS_SCALEROWDOWN4_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(row_stride, 4) && - IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { - ScaleRowDown4 = filtering ? - ScaleRowDown4Box_DSPR2 : ScaleRowDown4_DSPR2; - } -#endif - - if (filtering == kFilterLinear) { - src_stride = 0; - } - for (y = 0; y < dst_height; ++y) { - ScaleRowDown4(src_ptr, src_stride, dst_ptr, dst_width); - src_ptr += row_stride; - dst_ptr += dst_stride; - } -} - -static void ScalePlaneDown4_16(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_ptr, uint16* dst_ptr, - enum FilterMode filtering) { - int y; - void (*ScaleRowDown4)(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width) = - filtering ? ScaleRowDown4Box_16_C : ScaleRowDown4_16_C; - int row_stride = src_stride << 2; - if (!filtering) { - src_ptr += src_stride * 2; // Point to row 2. - src_stride = 0; - } -#if defined(HAS_SCALEROWDOWN4_16_NEON) - if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(dst_width, 8)) { - ScaleRowDown4 = filtering ? ScaleRowDown4Box_16_NEON : - ScaleRowDown4_16_NEON; - } -#endif -#if defined(HAS_SCALEROWDOWN4_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 8)) { - ScaleRowDown4 = filtering ? ScaleRowDown4Box_16_SSE2 : - ScaleRowDown4_16_SSE2; - } -#endif -#if defined(HAS_SCALEROWDOWN4_16_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(row_stride, 4) && - IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { - ScaleRowDown4 = filtering ? - ScaleRowDown4Box_16_DSPR2 : ScaleRowDown4_16_DSPR2; - } -#endif - - if (filtering == kFilterLinear) { - src_stride = 0; - } - for (y = 0; y < dst_height; ++y) { - ScaleRowDown4(src_ptr, src_stride, dst_ptr, dst_width); - src_ptr += row_stride; - dst_ptr += dst_stride; - } -} - -// Scale plane down, 3/4 - -static void ScalePlaneDown34(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_ptr, uint8* dst_ptr, - enum FilterMode filtering) { - int y; - void (*ScaleRowDown34_0)(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - void (*ScaleRowDown34_1)(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - const int filter_stride = (filtering == kFilterLinear) ? 0 : src_stride; - assert(dst_width % 3 == 0); - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_C; - ScaleRowDown34_1 = ScaleRowDown34_C; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_C; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_C; - } -#if defined(HAS_SCALEROWDOWN34_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_Any_NEON; - ScaleRowDown34_1 = ScaleRowDown34_Any_NEON; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_Any_NEON; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_Any_NEON; - } - if (dst_width % 24 == 0) { - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_NEON; - ScaleRowDown34_1 = ScaleRowDown34_NEON; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_NEON; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_NEON; - } - } - } -#endif -#if defined(HAS_SCALEROWDOWN34_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_Any_SSSE3; - ScaleRowDown34_1 = ScaleRowDown34_Any_SSSE3; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_Any_SSSE3; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_Any_SSSE3; - } - if (dst_width % 24 == 0) { - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_SSSE3; - ScaleRowDown34_1 = ScaleRowDown34_SSSE3; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_SSSE3; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_SSSE3; - } - } - } -#endif -#if defined(HAS_SCALEROWDOWN34_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && (dst_width % 24 == 0) && - IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_DSPR2; - ScaleRowDown34_1 = ScaleRowDown34_DSPR2; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_DSPR2; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_DSPR2; - } - } -#endif - - for (y = 0; y < dst_height - 2; y += 3) { - ScaleRowDown34_0(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride; - dst_ptr += dst_stride; - ScaleRowDown34_1(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride; - dst_ptr += dst_stride; - ScaleRowDown34_0(src_ptr + src_stride, -filter_stride, - dst_ptr, dst_width); - src_ptr += src_stride * 2; - dst_ptr += dst_stride; - } - - // Remainder 1 or 2 rows with last row vertically unfiltered - if ((dst_height % 3) == 2) { - ScaleRowDown34_0(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride; - dst_ptr += dst_stride; - ScaleRowDown34_1(src_ptr, 0, dst_ptr, dst_width); - } else if ((dst_height % 3) == 1) { - ScaleRowDown34_0(src_ptr, 0, dst_ptr, dst_width); - } -} - -static void ScalePlaneDown34_16(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_ptr, uint16* dst_ptr, - enum FilterMode filtering) { - int y; - void (*ScaleRowDown34_0)(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); - void (*ScaleRowDown34_1)(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); - const int filter_stride = (filtering == kFilterLinear) ? 0 : src_stride; - assert(dst_width % 3 == 0); - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_16_C; - ScaleRowDown34_1 = ScaleRowDown34_16_C; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_16_C; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_16_C; - } -#if defined(HAS_SCALEROWDOWN34_16_NEON) - if (TestCpuFlag(kCpuHasNEON) && (dst_width % 24 == 0)) { - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_16_NEON; - ScaleRowDown34_1 = ScaleRowDown34_16_NEON; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_16_NEON; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_16_NEON; - } - } -#endif -#if defined(HAS_SCALEROWDOWN34_16_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && (dst_width % 24 == 0)) { - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_16_SSSE3; - ScaleRowDown34_1 = ScaleRowDown34_16_SSSE3; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_16_SSSE3; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_16_SSSE3; - } - } -#endif -#if defined(HAS_SCALEROWDOWN34_16_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && (dst_width % 24 == 0) && - IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { - if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_16_DSPR2; - ScaleRowDown34_1 = ScaleRowDown34_16_DSPR2; - } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_16_DSPR2; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_16_DSPR2; - } - } -#endif - - for (y = 0; y < dst_height - 2; y += 3) { - ScaleRowDown34_0(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride; - dst_ptr += dst_stride; - ScaleRowDown34_1(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride; - dst_ptr += dst_stride; - ScaleRowDown34_0(src_ptr + src_stride, -filter_stride, - dst_ptr, dst_width); - src_ptr += src_stride * 2; - dst_ptr += dst_stride; - } - - // Remainder 1 or 2 rows with last row vertically unfiltered - if ((dst_height % 3) == 2) { - ScaleRowDown34_0(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride; - dst_ptr += dst_stride; - ScaleRowDown34_1(src_ptr, 0, dst_ptr, dst_width); - } else if ((dst_height % 3) == 1) { - ScaleRowDown34_0(src_ptr, 0, dst_ptr, dst_width); - } -} - - -// Scale plane, 3/8 -// This is an optimized version for scaling down a plane to 3/8 -// of its original size. -// -// Uses box filter arranges like this -// aaabbbcc -> abc -// aaabbbcc def -// aaabbbcc ghi -// dddeeeff -// dddeeeff -// dddeeeff -// ggghhhii -// ggghhhii -// Boxes are 3x3, 2x3, 3x2 and 2x2 - -static void ScalePlaneDown38(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_ptr, uint8* dst_ptr, - enum FilterMode filtering) { - int y; - void (*ScaleRowDown38_3)(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - void (*ScaleRowDown38_2)(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - const int filter_stride = (filtering == kFilterLinear) ? 0 : src_stride; - assert(dst_width % 3 == 0); - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_C; - ScaleRowDown38_2 = ScaleRowDown38_C; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_C; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_C; - } - -#if defined(HAS_SCALEROWDOWN38_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_Any_NEON; - ScaleRowDown38_2 = ScaleRowDown38_Any_NEON; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_Any_NEON; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_Any_NEON; - } - if (dst_width % 12 == 0) { - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_NEON; - ScaleRowDown38_2 = ScaleRowDown38_NEON; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_NEON; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_NEON; - } - } - } -#endif -#if defined(HAS_SCALEROWDOWN38_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_Any_SSSE3; - ScaleRowDown38_2 = ScaleRowDown38_Any_SSSE3; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_Any_SSSE3; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_Any_SSSE3; - } - if (dst_width % 12 == 0 && !filtering) { - ScaleRowDown38_3 = ScaleRowDown38_SSSE3; - ScaleRowDown38_2 = ScaleRowDown38_SSSE3; - } - if (dst_width % 6 == 0 && filtering) { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_SSSE3; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_SSSE3; - } - } -#endif -#if defined(HAS_SCALEROWDOWN38_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && (dst_width % 12 == 0) && - IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_DSPR2; - ScaleRowDown38_2 = ScaleRowDown38_DSPR2; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_DSPR2; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_DSPR2; - } - } -#endif - - for (y = 0; y < dst_height - 2; y += 3) { - ScaleRowDown38_3(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride * 3; - dst_ptr += dst_stride; - ScaleRowDown38_3(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride * 3; - dst_ptr += dst_stride; - ScaleRowDown38_2(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride * 2; - dst_ptr += dst_stride; - } - - // Remainder 1 or 2 rows with last row vertically unfiltered - if ((dst_height % 3) == 2) { - ScaleRowDown38_3(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride * 3; - dst_ptr += dst_stride; - ScaleRowDown38_3(src_ptr, 0, dst_ptr, dst_width); - } else if ((dst_height % 3) == 1) { - ScaleRowDown38_3(src_ptr, 0, dst_ptr, dst_width); - } -} - -static void ScalePlaneDown38_16(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_ptr, uint16* dst_ptr, - enum FilterMode filtering) { - int y; - void (*ScaleRowDown38_3)(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); - void (*ScaleRowDown38_2)(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); - const int filter_stride = (filtering == kFilterLinear) ? 0 : src_stride; - assert(dst_width % 3 == 0); - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_16_C; - ScaleRowDown38_2 = ScaleRowDown38_16_C; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_16_C; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_16_C; - } -#if defined(HAS_SCALEROWDOWN38_16_NEON) - if (TestCpuFlag(kCpuHasNEON) && (dst_width % 12 == 0)) { - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_16_NEON; - ScaleRowDown38_2 = ScaleRowDown38_16_NEON; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_16_NEON; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_16_NEON; - } - } -#endif -#if defined(HAS_SCALEROWDOWN38_16_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && (dst_width % 24 == 0)) { - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_16_SSSE3; - ScaleRowDown38_2 = ScaleRowDown38_16_SSSE3; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_16_SSSE3; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_16_SSSE3; - } - } -#endif -#if defined(HAS_SCALEROWDOWN38_16_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && (dst_width % 12 == 0) && - IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { - if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_16_DSPR2; - ScaleRowDown38_2 = ScaleRowDown38_16_DSPR2; - } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_16_DSPR2; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_16_DSPR2; - } - } -#endif - - for (y = 0; y < dst_height - 2; y += 3) { - ScaleRowDown38_3(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride * 3; - dst_ptr += dst_stride; - ScaleRowDown38_3(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride * 3; - dst_ptr += dst_stride; - ScaleRowDown38_2(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride * 2; - dst_ptr += dst_stride; - } - - // Remainder 1 or 2 rows with last row vertically unfiltered - if ((dst_height % 3) == 2) { - ScaleRowDown38_3(src_ptr, filter_stride, dst_ptr, dst_width); - src_ptr += src_stride * 3; - dst_ptr += dst_stride; - ScaleRowDown38_3(src_ptr, 0, dst_ptr, dst_width); - } else if ((dst_height % 3) == 1) { - ScaleRowDown38_3(src_ptr, 0, dst_ptr, dst_width); - } -} - -#define MIN1(x) ((x) < 1 ? 1 : (x)) - -static __inline uint32 SumPixels(int iboxwidth, const uint16* src_ptr) { - uint32 sum = 0u; - int x; - assert(iboxwidth > 0); - for (x = 0; x < iboxwidth; ++x) { - sum += src_ptr[x]; - } - return sum; -} - -static __inline uint32 SumPixels_16(int iboxwidth, const uint32* src_ptr) { - uint32 sum = 0u; - int x; - assert(iboxwidth > 0); - for (x = 0; x < iboxwidth; ++x) { - sum += src_ptr[x]; - } - return sum; -} - -static void ScaleAddCols2_C(int dst_width, int boxheight, int x, int dx, - const uint16* src_ptr, uint8* dst_ptr) { - int i; - int scaletbl[2]; - int minboxwidth = dx >> 16; - int boxwidth; - scaletbl[0] = 65536 / (MIN1(minboxwidth) * boxheight); - scaletbl[1] = 65536 / (MIN1(minboxwidth + 1) * boxheight); - for (i = 0; i < dst_width; ++i) { - int ix = x >> 16; - x += dx; - boxwidth = MIN1((x >> 16) - ix); - *dst_ptr++ = SumPixels(boxwidth, src_ptr + ix) * - scaletbl[boxwidth - minboxwidth] >> 16; - } -} - -static void ScaleAddCols2_16_C(int dst_width, int boxheight, int x, int dx, - const uint32* src_ptr, uint16* dst_ptr) { - int i; - int scaletbl[2]; - int minboxwidth = dx >> 16; - int boxwidth; - scaletbl[0] = 65536 / (MIN1(minboxwidth) * boxheight); - scaletbl[1] = 65536 / (MIN1(minboxwidth + 1) * boxheight); - for (i = 0; i < dst_width; ++i) { - int ix = x >> 16; - x += dx; - boxwidth = MIN1((x >> 16) - ix); - *dst_ptr++ = SumPixels_16(boxwidth, src_ptr + ix) * - scaletbl[boxwidth - minboxwidth] >> 16; - } -} - -static void ScaleAddCols0_C(int dst_width, int boxheight, int x, int, - const uint16* src_ptr, uint8* dst_ptr) { - int scaleval = 65536 / boxheight; - int i; - src_ptr += (x >> 16); - for (i = 0; i < dst_width; ++i) { - *dst_ptr++ = src_ptr[i] * scaleval >> 16; - } -} - -static void ScaleAddCols1_C(int dst_width, int boxheight, int x, int dx, - const uint16* src_ptr, uint8* dst_ptr) { - int boxwidth = MIN1(dx >> 16); - int scaleval = 65536 / (boxwidth * boxheight); - int i; - x >>= 16; - for (i = 0; i < dst_width; ++i) { - *dst_ptr++ = SumPixels(boxwidth, src_ptr + x) * scaleval >> 16; - x += boxwidth; - } -} - -static void ScaleAddCols1_16_C(int dst_width, int boxheight, int x, int dx, - const uint32* src_ptr, uint16* dst_ptr) { - int boxwidth = MIN1(dx >> 16); - int scaleval = 65536 / (boxwidth * boxheight); - int i; - for (i = 0; i < dst_width; ++i) { - *dst_ptr++ = SumPixels_16(boxwidth, src_ptr + x) * scaleval >> 16; - x += boxwidth; - } -} - -// Scale plane down to any dimensions, with interpolation. -// (boxfilter). -// -// Same method as SimpleScale, which is fixed point, outputting -// one pixel of destination using fixed point (16.16) to step -// through source, sampling a box of pixel with simple -// averaging. -static void ScalePlaneBox(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_ptr, uint8* dst_ptr) { - int j, k; - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - const int max_y = (src_height << 16); - ScaleSlope(src_width, src_height, dst_width, dst_height, kFilterBox, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - { - // Allocate a row buffer of uint16. - align_buffer_64(row16, src_width * 2); - void (*ScaleAddCols)(int dst_width, int boxheight, int x, int dx, - const uint16* src_ptr, uint8* dst_ptr) = - (dx & 0xffff) ? ScaleAddCols2_C: - ((dx != 0x10000) ? ScaleAddCols1_C : ScaleAddCols0_C); - void (*ScaleAddRow)(const uint8* src_ptr, uint16* dst_ptr, int src_width) = - ScaleAddRow_C; -#if defined(HAS_SCALEADDROW_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ScaleAddRow = ScaleAddRow_Any_SSE2; - if (IS_ALIGNED(src_width, 16)) { - ScaleAddRow = ScaleAddRow_SSE2; - } - } -#endif -#if defined(HAS_SCALEADDROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - ScaleAddRow = ScaleAddRow_Any_AVX2; - if (IS_ALIGNED(src_width, 32)) { - ScaleAddRow = ScaleAddRow_AVX2; - } - } -#endif -#if defined(HAS_SCALEADDROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleAddRow = ScaleAddRow_Any_NEON; - if (IS_ALIGNED(src_width, 16)) { - ScaleAddRow = ScaleAddRow_NEON; - } - } -#endif - - for (j = 0; j < dst_height; ++j) { - int boxheight; - int iy = y >> 16; - const uint8* src = src_ptr + iy * src_stride; - y += dy; - if (y > max_y) { - y = max_y; - } - boxheight = MIN1((y >> 16) - iy); - memset(row16, 0, src_width * 2); - for (k = 0; k < boxheight; ++k) { - ScaleAddRow(src, (uint16 *)(row16), src_width); - src += src_stride; - } - ScaleAddCols(dst_width, boxheight, x, dx, (uint16*)(row16), dst_ptr); - dst_ptr += dst_stride; - } - free_aligned_buffer_64(row16); - } -} - -static void ScalePlaneBox_16(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_ptr, uint16* dst_ptr) { - int j, k; - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - const int max_y = (src_height << 16); - ScaleSlope(src_width, src_height, dst_width, dst_height, kFilterBox, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - { - // Allocate a row buffer of uint32. - align_buffer_64(row32, src_width * 4); - void (*ScaleAddCols)(int dst_width, int boxheight, int x, int dx, - const uint32* src_ptr, uint16* dst_ptr) = - (dx & 0xffff) ? ScaleAddCols2_16_C: ScaleAddCols1_16_C; - void (*ScaleAddRow)(const uint16* src_ptr, uint32* dst_ptr, int src_width) = - ScaleAddRow_16_C; - -#if defined(HAS_SCALEADDROW_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(src_width, 16)) { - ScaleAddRow = ScaleAddRow_16_SSE2; - } -#endif - - for (j = 0; j < dst_height; ++j) { - int boxheight; - int iy = y >> 16; - const uint16* src = src_ptr + iy * src_stride; - y += dy; - if (y > max_y) { - y = max_y; - } - boxheight = MIN1((y >> 16) - iy); - memset(row32, 0, src_width * 4); - for (k = 0; k < boxheight; ++k) { - ScaleAddRow(src, (uint32 *)(row32), src_width); - src += src_stride; - } - ScaleAddCols(dst_width, boxheight, x, dx, (uint32*)(row32), dst_ptr); - dst_ptr += dst_stride; - } - free_aligned_buffer_64(row32); - } -} - -// Scale plane down with bilinear interpolation. -void ScalePlaneBilinearDown(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_ptr, uint8* dst_ptr, - enum FilterMode filtering) { - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - // TODO(fbarchard): Consider not allocating row buffer for kFilterLinear. - // Allocate a row buffer. - align_buffer_64(row, src_width); - - const int max_y = (src_height - 1) << 16; - int j; - void (*ScaleFilterCols)(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) = - (src_width >= 32768) ? ScaleFilterCols64_C : ScaleFilterCols_C; - void (*InterpolateRow)(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_C; - ScaleSlope(src_width, src_height, dst_width, dst_height, filtering, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(src_width, 16)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(src_width, 32)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(src_width, 16)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2)) { - InterpolateRow = InterpolateRow_Any_DSPR2; - if (IS_ALIGNED(src_width, 4)) { - InterpolateRow = InterpolateRow_DSPR2; - } - } -#endif - - -#if defined(HAS_SCALEFILTERCOLS_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && src_width < 32768) { - ScaleFilterCols = ScaleFilterCols_SSSE3; - } -#endif -#if defined(HAS_SCALEFILTERCOLS_NEON) - if (TestCpuFlag(kCpuHasNEON) && src_width < 32768) { - ScaleFilterCols = ScaleFilterCols_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleFilterCols = ScaleFilterCols_NEON; - } - } -#endif - if (y > max_y) { - y = max_y; - } - - for (j = 0; j < dst_height; ++j) { - int yi = y >> 16; - const uint8* src = src_ptr + yi * src_stride; - if (filtering == kFilterLinear) { - ScaleFilterCols(dst_ptr, src, dst_width, x, dx); - } else { - int yf = (y >> 8) & 255; - InterpolateRow(row, src, src_stride, src_width, yf); - ScaleFilterCols(dst_ptr, row, dst_width, x, dx); - } - dst_ptr += dst_stride; - y += dy; - if (y > max_y) { - y = max_y; - } - } - free_aligned_buffer_64(row); -} - -void ScalePlaneBilinearDown_16(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_ptr, uint16* dst_ptr, - enum FilterMode filtering) { - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - // TODO(fbarchard): Consider not allocating row buffer for kFilterLinear. - // Allocate a row buffer. - align_buffer_64(row, src_width * 2); - - const int max_y = (src_height - 1) << 16; - int j; - void (*ScaleFilterCols)(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx) = - (src_width >= 32768) ? ScaleFilterCols64_16_C : ScaleFilterCols_16_C; - void (*InterpolateRow)(uint16* dst_ptr, const uint16* src_ptr, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_16_C; - ScaleSlope(src_width, src_height, dst_width, dst_height, filtering, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - -#if defined(HAS_INTERPOLATEROW_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - InterpolateRow = InterpolateRow_Any_16_SSE2; - if (IS_ALIGNED(src_width, 16)) { - InterpolateRow = InterpolateRow_16_SSE2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_16_SSSE3; - if (IS_ALIGNED(src_width, 16)) { - InterpolateRow = InterpolateRow_16_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_16_AVX2; - if (IS_ALIGNED(src_width, 32)) { - InterpolateRow = InterpolateRow_16_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_16_NEON; - if (IS_ALIGNED(src_width, 16)) { - InterpolateRow = InterpolateRow_16_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2)) { - InterpolateRow = InterpolateRow_Any_16_DSPR2; - if (IS_ALIGNED(src_width, 4)) { - InterpolateRow = InterpolateRow_16_DSPR2; - } - } -#endif - - -#if defined(HAS_SCALEFILTERCOLS_16_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && src_width < 32768) { - ScaleFilterCols = ScaleFilterCols_16_SSSE3; - } -#endif - if (y > max_y) { - y = max_y; - } - - for (j = 0; j < dst_height; ++j) { - int yi = y >> 16; - const uint16* src = src_ptr + yi * src_stride; - if (filtering == kFilterLinear) { - ScaleFilterCols(dst_ptr, src, dst_width, x, dx); - } else { - int yf = (y >> 8) & 255; - InterpolateRow((uint16*)row, src, src_stride, src_width, yf); - ScaleFilterCols(dst_ptr, (uint16*)row, dst_width, x, dx); - } - dst_ptr += dst_stride; - y += dy; - if (y > max_y) { - y = max_y; - } - } - free_aligned_buffer_64(row); -} - -// Scale up down with bilinear interpolation. -void ScalePlaneBilinearUp(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_ptr, uint8* dst_ptr, - enum FilterMode filtering) { - int j; - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - const int max_y = (src_height - 1) << 16; - void (*InterpolateRow)(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_C; - void (*ScaleFilterCols)(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) = - filtering ? ScaleFilterCols_C : ScaleCols_C; - ScaleSlope(src_width, src_height, dst_width, dst_height, filtering, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(dst_width, 16)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(dst_width, 32)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(dst_width, 16)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2)) { - InterpolateRow = InterpolateRow_Any_DSPR2; - if (IS_ALIGNED(dst_width, 4)) { - InterpolateRow = InterpolateRow_DSPR2; - } - } -#endif - - if (filtering && src_width >= 32768) { - ScaleFilterCols = ScaleFilterCols64_C; - } -#if defined(HAS_SCALEFILTERCOLS_SSSE3) - if (filtering && TestCpuFlag(kCpuHasSSSE3) && src_width < 32768) { - ScaleFilterCols = ScaleFilterCols_SSSE3; - } -#endif -#if defined(HAS_SCALEFILTERCOLS_NEON) - if (filtering && TestCpuFlag(kCpuHasNEON) && src_width < 32768) { - ScaleFilterCols = ScaleFilterCols_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleFilterCols = ScaleFilterCols_NEON; - } - } -#endif - if (!filtering && src_width * 2 == dst_width && x < 0x8000) { - ScaleFilterCols = ScaleColsUp2_C; -#if defined(HAS_SCALECOLS_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 8)) { - ScaleFilterCols = ScaleColsUp2_SSE2; - } -#endif - } - - if (y > max_y) { - y = max_y; - } - { - int yi = y >> 16; - const uint8* src = src_ptr + yi * src_stride; - - // Allocate 2 row buffers. - const int kRowSize = (dst_width + 31) & ~31; - align_buffer_64(row, kRowSize * 2); - - uint8* rowptr = row; - int rowstride = kRowSize; - int lasty = yi; - - ScaleFilterCols(rowptr, src, dst_width, x, dx); - if (src_height > 1) { - src += src_stride; - } - ScaleFilterCols(rowptr + rowstride, src, dst_width, x, dx); - src += src_stride; - - for (j = 0; j < dst_height; ++j) { - yi = y >> 16; - if (yi != lasty) { - if (y > max_y) { - y = max_y; - yi = y >> 16; - src = src_ptr + yi * src_stride; - } - if (yi != lasty) { - ScaleFilterCols(rowptr, src, dst_width, x, dx); - rowptr += rowstride; - rowstride = -rowstride; - lasty = yi; - src += src_stride; - } - } - if (filtering == kFilterLinear) { - InterpolateRow(dst_ptr, rowptr, 0, dst_width, 0); - } else { - int yf = (y >> 8) & 255; - InterpolateRow(dst_ptr, rowptr, rowstride, dst_width, yf); - } - dst_ptr += dst_stride; - y += dy; - } - free_aligned_buffer_64(row); - } -} - -void ScalePlaneBilinearUp_16(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_ptr, uint16* dst_ptr, - enum FilterMode filtering) { - int j; - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - const int max_y = (src_height - 1) << 16; - void (*InterpolateRow)(uint16* dst_ptr, const uint16* src_ptr, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_16_C; - void (*ScaleFilterCols)(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx) = - filtering ? ScaleFilterCols_16_C : ScaleCols_16_C; - ScaleSlope(src_width, src_height, dst_width, dst_height, filtering, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - -#if defined(HAS_INTERPOLATEROW_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - InterpolateRow = InterpolateRow_Any_16_SSE2; - if (IS_ALIGNED(dst_width, 16)) { - InterpolateRow = InterpolateRow_16_SSE2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_16_SSSE3; - if (IS_ALIGNED(dst_width, 16)) { - InterpolateRow = InterpolateRow_16_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_16_AVX2; - if (IS_ALIGNED(dst_width, 32)) { - InterpolateRow = InterpolateRow_16_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_16_NEON; - if (IS_ALIGNED(dst_width, 16)) { - InterpolateRow = InterpolateRow_16_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2)) { - InterpolateRow = InterpolateRow_Any_16_DSPR2; - if (IS_ALIGNED(dst_width, 4)) { - InterpolateRow = InterpolateRow_16_DSPR2; - } - } -#endif - - if (filtering && src_width >= 32768) { - ScaleFilterCols = ScaleFilterCols64_16_C; - } -#if defined(HAS_SCALEFILTERCOLS_16_SSSE3) - if (filtering && TestCpuFlag(kCpuHasSSSE3) && src_width < 32768) { - ScaleFilterCols = ScaleFilterCols_16_SSSE3; - } -#endif - if (!filtering && src_width * 2 == dst_width && x < 0x8000) { - ScaleFilterCols = ScaleColsUp2_16_C; -#if defined(HAS_SCALECOLS_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 8)) { - ScaleFilterCols = ScaleColsUp2_16_SSE2; - } -#endif - } - - if (y > max_y) { - y = max_y; - } - { - int yi = y >> 16; - const uint16* src = src_ptr + yi * src_stride; - - // Allocate 2 row buffers. - const int kRowSize = (dst_width + 31) & ~31; - align_buffer_64(row, kRowSize * 4); - - uint16* rowptr = (uint16*)row; - int rowstride = kRowSize; - int lasty = yi; - - ScaleFilterCols(rowptr, src, dst_width, x, dx); - if (src_height > 1) { - src += src_stride; - } - ScaleFilterCols(rowptr + rowstride, src, dst_width, x, dx); - src += src_stride; - - for (j = 0; j < dst_height; ++j) { - yi = y >> 16; - if (yi != lasty) { - if (y > max_y) { - y = max_y; - yi = y >> 16; - src = src_ptr + yi * src_stride; - } - if (yi != lasty) { - ScaleFilterCols(rowptr, src, dst_width, x, dx); - rowptr += rowstride; - rowstride = -rowstride; - lasty = yi; - src += src_stride; - } - } - if (filtering == kFilterLinear) { - InterpolateRow(dst_ptr, rowptr, 0, dst_width, 0); - } else { - int yf = (y >> 8) & 255; - InterpolateRow(dst_ptr, rowptr, rowstride, dst_width, yf); - } - dst_ptr += dst_stride; - y += dy; - } - free_aligned_buffer_64(row); - } -} - -// Scale Plane to/from any dimensions, without interpolation. -// Fixed point math is used for performance: The upper 16 bits -// of x and dx is the integer part of the source position and -// the lower 16 bits are the fixed decimal part. - -static void ScalePlaneSimple(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_ptr, uint8* dst_ptr) { - int i; - void (*ScaleCols)(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) = ScaleCols_C; - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - ScaleSlope(src_width, src_height, dst_width, dst_height, kFilterNone, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - - if (src_width * 2 == dst_width && x < 0x8000) { - ScaleCols = ScaleColsUp2_C; -#if defined(HAS_SCALECOLS_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 8)) { - ScaleCols = ScaleColsUp2_SSE2; - } -#endif - } - - for (i = 0; i < dst_height; ++i) { - ScaleCols(dst_ptr, src_ptr + (y >> 16) * src_stride, dst_width, x, dx); - dst_ptr += dst_stride; - y += dy; - } -} - -static void ScalePlaneSimple_16(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_ptr, uint16* dst_ptr) { - int i; - void (*ScaleCols)(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx) = ScaleCols_16_C; - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - ScaleSlope(src_width, src_height, dst_width, dst_height, kFilterNone, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - - if (src_width * 2 == dst_width && x < 0x8000) { - ScaleCols = ScaleColsUp2_16_C; -#if defined(HAS_SCALECOLS_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 8)) { - ScaleCols = ScaleColsUp2_16_SSE2; - } -#endif - } - - for (i = 0; i < dst_height; ++i) { - ScaleCols(dst_ptr, src_ptr + (y >> 16) * src_stride, - dst_width, x, dx); - dst_ptr += dst_stride; - y += dy; - } -} - -// Scale a plane. -// This function dispatches to a specialized scaler based on scale factor. - -LIBYUV_API -void ScalePlane(const uint8* src, int src_stride, - int src_width, int src_height, - uint8* dst, int dst_stride, - int dst_width, int dst_height, - enum FilterMode filtering) { - // Simplify filtering when possible. - filtering = ScaleFilterReduce(src_width, src_height, - dst_width, dst_height, filtering); - - // Negative height means invert the image. - if (src_height < 0) { - src_height = -src_height; - src = src + (src_height - 1) * src_stride; - src_stride = -src_stride; - } - - // Use specialized scales to improve performance for common resolutions. - // For example, all the 1/2 scalings will use ScalePlaneDown2() - if (dst_width == src_width && dst_height == src_height) { - // Straight copy. - CopyPlane(src, src_stride, dst, dst_stride, dst_width, dst_height); - return; - } - if (dst_width == src_width && filtering != kFilterBox) { - int dy = FixedDiv(src_height, dst_height); - // Arbitrary scale vertically, but unscaled horizontally. - ScalePlaneVertical(src_height, - dst_width, dst_height, - src_stride, dst_stride, src, dst, - 0, 0, dy, 1, filtering); - return; - } - if (dst_width <= Abs(src_width) && dst_height <= src_height) { - // Scale down. - if (4 * dst_width == 3 * src_width && - 4 * dst_height == 3 * src_height) { - // optimized, 3/4 - ScalePlaneDown34(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - if (2 * dst_width == src_width && 2 * dst_height == src_height) { - // optimized, 1/2 - ScalePlaneDown2(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - // 3/8 rounded up for odd sized chroma height. - if (8 * dst_width == 3 * src_width && - dst_height == ((src_height * 3 + 7) / 8)) { - // optimized, 3/8 - ScalePlaneDown38(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - if (4 * dst_width == src_width && 4 * dst_height == src_height && - (filtering == kFilterBox || filtering == kFilterNone)) { - // optimized, 1/4 - ScalePlaneDown4(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - } - if (filtering == kFilterBox && dst_height * 2 < src_height) { - ScalePlaneBox(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst); - return; - } - if (filtering && dst_height > src_height) { - ScalePlaneBilinearUp(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - if (filtering) { - ScalePlaneBilinearDown(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - ScalePlaneSimple(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst); -} - -LIBYUV_API -void ScalePlane_16(const uint16* src, int src_stride, - int src_width, int src_height, - uint16* dst, int dst_stride, - int dst_width, int dst_height, - enum FilterMode filtering) { - // Simplify filtering when possible. - filtering = ScaleFilterReduce(src_width, src_height, - dst_width, dst_height, filtering); - - // Negative height means invert the image. - if (src_height < 0) { - src_height = -src_height; - src = src + (src_height - 1) * src_stride; - src_stride = -src_stride; - } - - // Use specialized scales to improve performance for common resolutions. - // For example, all the 1/2 scalings will use ScalePlaneDown2() - if (dst_width == src_width && dst_height == src_height) { - // Straight copy. - CopyPlane_16(src, src_stride, dst, dst_stride, dst_width, dst_height); - return; - } - if (dst_width == src_width) { - int dy = FixedDiv(src_height, dst_height); - // Arbitrary scale vertically, but unscaled vertically. - ScalePlaneVertical_16(src_height, - dst_width, dst_height, - src_stride, dst_stride, src, dst, - 0, 0, dy, 1, filtering); - return; - } - if (dst_width <= Abs(src_width) && dst_height <= src_height) { - // Scale down. - if (4 * dst_width == 3 * src_width && - 4 * dst_height == 3 * src_height) { - // optimized, 3/4 - ScalePlaneDown34_16(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - if (2 * dst_width == src_width && 2 * dst_height == src_height) { - // optimized, 1/2 - ScalePlaneDown2_16(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - // 3/8 rounded up for odd sized chroma height. - if (8 * dst_width == 3 * src_width && - dst_height == ((src_height * 3 + 7) / 8)) { - // optimized, 3/8 - ScalePlaneDown38_16(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - if (4 * dst_width == src_width && 4 * dst_height == src_height && - filtering != kFilterBilinear) { - // optimized, 1/4 - ScalePlaneDown4_16(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - } - if (filtering == kFilterBox && dst_height * 2 < src_height) { - ScalePlaneBox_16(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst); - return; - } - if (filtering && dst_height > src_height) { - ScalePlaneBilinearUp_16(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - if (filtering) { - ScalePlaneBilinearDown_16(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst, filtering); - return; - } - ScalePlaneSimple_16(src_width, src_height, dst_width, dst_height, - src_stride, dst_stride, src, dst); -} - -// Scale an I420 image. -// This function in turn calls a scaling function for each plane. - -LIBYUV_API -int I420Scale(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - int src_width, int src_height, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int dst_width, int dst_height, - enum FilterMode filtering) { - int src_halfwidth = SUBSAMPLE(src_width, 1, 1); - int src_halfheight = SUBSAMPLE(src_height, 1, 1); - int dst_halfwidth = SUBSAMPLE(dst_width, 1, 1); - int dst_halfheight = SUBSAMPLE(dst_height, 1, 1); - if (!src_y || !src_u || !src_v || src_width == 0 || src_height == 0 || - src_width > 32768 || src_height > 32768 || - !dst_y || !dst_u || !dst_v || dst_width <= 0 || dst_height <= 0) { - return -1; - } - - ScalePlane(src_y, src_stride_y, src_width, src_height, - dst_y, dst_stride_y, dst_width, dst_height, - filtering); - ScalePlane(src_u, src_stride_u, src_halfwidth, src_halfheight, - dst_u, dst_stride_u, dst_halfwidth, dst_halfheight, - filtering); - ScalePlane(src_v, src_stride_v, src_halfwidth, src_halfheight, - dst_v, dst_stride_v, dst_halfwidth, dst_halfheight, - filtering); - return 0; -} - -LIBYUV_API -int I420Scale_16(const uint16* src_y, int src_stride_y, - const uint16* src_u, int src_stride_u, - const uint16* src_v, int src_stride_v, - int src_width, int src_height, - uint16* dst_y, int dst_stride_y, - uint16* dst_u, int dst_stride_u, - uint16* dst_v, int dst_stride_v, - int dst_width, int dst_height, - enum FilterMode filtering) { - int src_halfwidth = SUBSAMPLE(src_width, 1, 1); - int src_halfheight = SUBSAMPLE(src_height, 1, 1); - int dst_halfwidth = SUBSAMPLE(dst_width, 1, 1); - int dst_halfheight = SUBSAMPLE(dst_height, 1, 1); - if (!src_y || !src_u || !src_v || src_width == 0 || src_height == 0 || - src_width > 32768 || src_height > 32768 || - !dst_y || !dst_u || !dst_v || dst_width <= 0 || dst_height <= 0) { - return -1; - } - - ScalePlane_16(src_y, src_stride_y, src_width, src_height, - dst_y, dst_stride_y, dst_width, dst_height, - filtering); - ScalePlane_16(src_u, src_stride_u, src_halfwidth, src_halfheight, - dst_u, dst_stride_u, dst_halfwidth, dst_halfheight, - filtering); - ScalePlane_16(src_v, src_stride_v, src_halfwidth, src_halfheight, - dst_v, dst_stride_v, dst_halfwidth, dst_halfheight, - filtering); - return 0; -} - -// Deprecated api -LIBYUV_API -int Scale(const uint8* src_y, const uint8* src_u, const uint8* src_v, - int src_stride_y, int src_stride_u, int src_stride_v, - int src_width, int src_height, - uint8* dst_y, uint8* dst_u, uint8* dst_v, - int dst_stride_y, int dst_stride_u, int dst_stride_v, - int dst_width, int dst_height, - LIBYUV_BOOL interpolate) { - return I420Scale(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - src_width, src_height, - dst_y, dst_stride_y, - dst_u, dst_stride_u, - dst_v, dst_stride_v, - dst_width, dst_height, - interpolate ? kFilterBox : kFilterNone); -} - -// Deprecated api -LIBYUV_API -int ScaleOffset(const uint8* src, int src_width, int src_height, - uint8* dst, int dst_width, int dst_height, int dst_yoffset, - LIBYUV_BOOL interpolate) { - // Chroma requires offset to multiple of 2. - int dst_yoffset_even = dst_yoffset & ~1; - int src_halfwidth = SUBSAMPLE(src_width, 1, 1); - int src_halfheight = SUBSAMPLE(src_height, 1, 1); - int dst_halfwidth = SUBSAMPLE(dst_width, 1, 1); - int dst_halfheight = SUBSAMPLE(dst_height, 1, 1); - int aheight = dst_height - dst_yoffset_even * 2; // actual output height - const uint8* src_y = src; - const uint8* src_u = src + src_width * src_height; - const uint8* src_v = src + src_width * src_height + - src_halfwidth * src_halfheight; - uint8* dst_y = dst + dst_yoffset_even * dst_width; - uint8* dst_u = dst + dst_width * dst_height + - (dst_yoffset_even >> 1) * dst_halfwidth; - uint8* dst_v = dst + dst_width * dst_height + dst_halfwidth * dst_halfheight + - (dst_yoffset_even >> 1) * dst_halfwidth; - if (!src || src_width <= 0 || src_height <= 0 || - !dst || dst_width <= 0 || dst_height <= 0 || dst_yoffset_even < 0 || - dst_yoffset_even >= dst_height) { - return -1; - } - return I420Scale(src_y, src_width, - src_u, src_halfwidth, - src_v, src_halfwidth, - src_width, src_height, - dst_y, dst_width, - dst_u, dst_halfwidth, - dst_v, dst_halfwidth, - dst_width, aheight, - interpolate ? kFilterBox : kFilterNone); -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale_any.cc b/telegramgallery/src/main/cpp/libyuv/source/scale_any.cc deleted file mode 100644 index ed76a9e..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale_any.cc +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2015 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/scale.h" -#include "libyuv/scale_row.h" - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Definition for ScaleFilterCols, ScaleARGBCols and ScaleARGBFilterCols -#define CANY(NAMEANY, TERP_SIMD, TERP_C, BPP, MASK) \ - void NAMEANY(uint8* dst_ptr, const uint8* src_ptr, \ - int dst_width, int x, int dx) { \ - int n = dst_width & ~MASK; \ - if (n > 0) { \ - TERP_SIMD(dst_ptr, src_ptr, n, x, dx); \ - } \ - TERP_C(dst_ptr + n * BPP, src_ptr, \ - dst_width & MASK, x + n * dx, dx); \ - } - -#ifdef HAS_SCALEFILTERCOLS_NEON -CANY(ScaleFilterCols_Any_NEON, ScaleFilterCols_NEON, ScaleFilterCols_C, 1, 7) -#endif -#ifdef HAS_SCALEARGBCOLS_NEON -CANY(ScaleARGBCols_Any_NEON, ScaleARGBCols_NEON, ScaleARGBCols_C, 4, 7) -#endif -#ifdef HAS_SCALEARGBFILTERCOLS_NEON -CANY(ScaleARGBFilterCols_Any_NEON, ScaleARGBFilterCols_NEON, - ScaleARGBFilterCols_C, 4, 3) -#endif -#undef CANY - -// Fixed scale down. -#define SDANY(NAMEANY, SCALEROWDOWN_SIMD, SCALEROWDOWN_C, FACTOR, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, ptrdiff_t src_stride, \ - uint8* dst_ptr, int dst_width) { \ - int r = (int)((unsigned int)dst_width % (MASK + 1)); \ - int n = dst_width - r; \ - if (n > 0) { \ - SCALEROWDOWN_SIMD(src_ptr, src_stride, dst_ptr, n); \ - } \ - SCALEROWDOWN_C(src_ptr + (n * FACTOR) * BPP, src_stride, \ - dst_ptr + n * BPP, r); \ - } - -// Fixed scale down for odd source width. Used by I420Blend subsampling. -// Since dst_width is (width + 1) / 2, this function scales one less pixel -// and copies the last pixel. -#define SDODD(NAMEANY, SCALEROWDOWN_SIMD, SCALEROWDOWN_C, FACTOR, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, ptrdiff_t src_stride, \ - uint8* dst_ptr, int dst_width) { \ - int r = (int)((unsigned int)(dst_width - 1) % (MASK + 1)); \ - int n = dst_width - r; \ - if (n > 0) { \ - SCALEROWDOWN_SIMD(src_ptr, src_stride, dst_ptr, n); \ - } \ - SCALEROWDOWN_C(src_ptr + (n * FACTOR) * BPP, src_stride, \ - dst_ptr + n * BPP, r); \ - } - -#ifdef HAS_SCALEROWDOWN2_SSSE3 -SDANY(ScaleRowDown2_Any_SSSE3, ScaleRowDown2_SSSE3, ScaleRowDown2_C, 2, 1, 15) -SDANY(ScaleRowDown2Linear_Any_SSSE3, ScaleRowDown2Linear_SSSE3, - ScaleRowDown2Linear_C, 2, 1, 15) -SDANY(ScaleRowDown2Box_Any_SSSE3, ScaleRowDown2Box_SSSE3, ScaleRowDown2Box_C, - 2, 1, 15) -SDODD(ScaleRowDown2Box_Odd_SSSE3, ScaleRowDown2Box_SSSE3, - ScaleRowDown2Box_Odd_C, 2, 1, 15) -#endif -#ifdef HAS_SCALEROWDOWN2_AVX2 -SDANY(ScaleRowDown2_Any_AVX2, ScaleRowDown2_AVX2, ScaleRowDown2_C, 2, 1, 31) -SDANY(ScaleRowDown2Linear_Any_AVX2, ScaleRowDown2Linear_AVX2, - ScaleRowDown2Linear_C, 2, 1, 31) -SDANY(ScaleRowDown2Box_Any_AVX2, ScaleRowDown2Box_AVX2, ScaleRowDown2Box_C, - 2, 1, 31) -SDODD(ScaleRowDown2Box_Odd_AVX2, ScaleRowDown2Box_AVX2, ScaleRowDown2Box_Odd_C, - 2, 1, 31) -#endif -#ifdef HAS_SCALEROWDOWN2_NEON -SDANY(ScaleRowDown2_Any_NEON, ScaleRowDown2_NEON, ScaleRowDown2_C, 2, 1, 15) -SDANY(ScaleRowDown2Linear_Any_NEON, ScaleRowDown2Linear_NEON, - ScaleRowDown2Linear_C, 2, 1, 15) -SDANY(ScaleRowDown2Box_Any_NEON, ScaleRowDown2Box_NEON, - ScaleRowDown2Box_C, 2, 1, 15) -SDODD(ScaleRowDown2Box_Odd_NEON, ScaleRowDown2Box_NEON, - ScaleRowDown2Box_Odd_C, 2, 1, 15) -#endif -#ifdef HAS_SCALEROWDOWN4_SSSE3 -SDANY(ScaleRowDown4_Any_SSSE3, ScaleRowDown4_SSSE3, ScaleRowDown4_C, 4, 1, 7) -SDANY(ScaleRowDown4Box_Any_SSSE3, ScaleRowDown4Box_SSSE3, ScaleRowDown4Box_C, - 4, 1, 7) -#endif -#ifdef HAS_SCALEROWDOWN4_AVX2 -SDANY(ScaleRowDown4_Any_AVX2, ScaleRowDown4_AVX2, ScaleRowDown4_C, 4, 1, 15) -SDANY(ScaleRowDown4Box_Any_AVX2, ScaleRowDown4Box_AVX2, ScaleRowDown4Box_C, - 4, 1, 15) -#endif -#ifdef HAS_SCALEROWDOWN4_NEON -SDANY(ScaleRowDown4_Any_NEON, ScaleRowDown4_NEON, ScaleRowDown4_C, 4, 1, 7) -SDANY(ScaleRowDown4Box_Any_NEON, ScaleRowDown4Box_NEON, ScaleRowDown4Box_C, - 4, 1, 7) -#endif -#ifdef HAS_SCALEROWDOWN34_SSSE3 -SDANY(ScaleRowDown34_Any_SSSE3, ScaleRowDown34_SSSE3, - ScaleRowDown34_C, 4 / 3, 1, 23) -SDANY(ScaleRowDown34_0_Box_Any_SSSE3, ScaleRowDown34_0_Box_SSSE3, - ScaleRowDown34_0_Box_C, 4 / 3, 1, 23) -SDANY(ScaleRowDown34_1_Box_Any_SSSE3, ScaleRowDown34_1_Box_SSSE3, - ScaleRowDown34_1_Box_C, 4 / 3, 1, 23) -#endif -#ifdef HAS_SCALEROWDOWN34_NEON -SDANY(ScaleRowDown34_Any_NEON, ScaleRowDown34_NEON, - ScaleRowDown34_C, 4 / 3, 1, 23) -SDANY(ScaleRowDown34_0_Box_Any_NEON, ScaleRowDown34_0_Box_NEON, - ScaleRowDown34_0_Box_C, 4 / 3, 1, 23) -SDANY(ScaleRowDown34_1_Box_Any_NEON, ScaleRowDown34_1_Box_NEON, - ScaleRowDown34_1_Box_C, 4 / 3, 1, 23) -#endif -#ifdef HAS_SCALEROWDOWN38_SSSE3 -SDANY(ScaleRowDown38_Any_SSSE3, ScaleRowDown38_SSSE3, - ScaleRowDown38_C, 8 / 3, 1, 11) -SDANY(ScaleRowDown38_3_Box_Any_SSSE3, ScaleRowDown38_3_Box_SSSE3, - ScaleRowDown38_3_Box_C, 8 / 3, 1, 5) -SDANY(ScaleRowDown38_2_Box_Any_SSSE3, ScaleRowDown38_2_Box_SSSE3, - ScaleRowDown38_2_Box_C, 8 / 3, 1, 5) -#endif -#ifdef HAS_SCALEROWDOWN38_NEON -SDANY(ScaleRowDown38_Any_NEON, ScaleRowDown38_NEON, - ScaleRowDown38_C, 8 / 3, 1, 11) -SDANY(ScaleRowDown38_3_Box_Any_NEON, ScaleRowDown38_3_Box_NEON, - ScaleRowDown38_3_Box_C, 8 / 3, 1, 11) -SDANY(ScaleRowDown38_2_Box_Any_NEON, ScaleRowDown38_2_Box_NEON, - ScaleRowDown38_2_Box_C, 8 / 3, 1, 11) -#endif - -#ifdef HAS_SCALEARGBROWDOWN2_SSE2 -SDANY(ScaleARGBRowDown2_Any_SSE2, ScaleARGBRowDown2_SSE2, - ScaleARGBRowDown2_C, 2, 4, 3) -SDANY(ScaleARGBRowDown2Linear_Any_SSE2, ScaleARGBRowDown2Linear_SSE2, - ScaleARGBRowDown2Linear_C, 2, 4, 3) -SDANY(ScaleARGBRowDown2Box_Any_SSE2, ScaleARGBRowDown2Box_SSE2, - ScaleARGBRowDown2Box_C, 2, 4, 3) -#endif -#ifdef HAS_SCALEARGBROWDOWN2_NEON -SDANY(ScaleARGBRowDown2_Any_NEON, ScaleARGBRowDown2_NEON, - ScaleARGBRowDown2_C, 2, 4, 7) -SDANY(ScaleARGBRowDown2Linear_Any_NEON, ScaleARGBRowDown2Linear_NEON, - ScaleARGBRowDown2Linear_C, 2, 4, 7) -SDANY(ScaleARGBRowDown2Box_Any_NEON, ScaleARGBRowDown2Box_NEON, - ScaleARGBRowDown2Box_C, 2, 4, 7) -#endif -#undef SDANY - -// Scale down by even scale factor. -#define SDAANY(NAMEANY, SCALEROWDOWN_SIMD, SCALEROWDOWN_C, BPP, MASK) \ - void NAMEANY(const uint8* src_ptr, ptrdiff_t src_stride, int src_stepx, \ - uint8* dst_ptr, int dst_width) { \ - int r = (int)((unsigned int)dst_width % (MASK + 1)); \ - int n = dst_width - r; \ - if (n > 0) { \ - SCALEROWDOWN_SIMD(src_ptr, src_stride, src_stepx, dst_ptr, n); \ - } \ - SCALEROWDOWN_C(src_ptr + (n * src_stepx) * BPP, src_stride, \ - src_stepx, dst_ptr + n * BPP, r); \ - } - -#ifdef HAS_SCALEARGBROWDOWNEVEN_SSE2 -SDAANY(ScaleARGBRowDownEven_Any_SSE2, ScaleARGBRowDownEven_SSE2, - ScaleARGBRowDownEven_C, 4, 3) -SDAANY(ScaleARGBRowDownEvenBox_Any_SSE2, ScaleARGBRowDownEvenBox_SSE2, - ScaleARGBRowDownEvenBox_C, 4, 3) -#endif -#ifdef HAS_SCALEARGBROWDOWNEVEN_NEON -SDAANY(ScaleARGBRowDownEven_Any_NEON, ScaleARGBRowDownEven_NEON, - ScaleARGBRowDownEven_C, 4, 3) -SDAANY(ScaleARGBRowDownEvenBox_Any_NEON, ScaleARGBRowDownEvenBox_NEON, - ScaleARGBRowDownEvenBox_C, 4, 3) -#endif - -// Add rows box filter scale down. -#define SAANY(NAMEANY, SCALEADDROW_SIMD, SCALEADDROW_C, MASK) \ - void NAMEANY(const uint8* src_ptr, uint16* dst_ptr, int src_width) { \ - int n = src_width & ~MASK; \ - if (n > 0) { \ - SCALEADDROW_SIMD(src_ptr, dst_ptr, n); \ - } \ - SCALEADDROW_C(src_ptr + n, dst_ptr + n, src_width & MASK); \ - } - -#ifdef HAS_SCALEADDROW_SSE2 -SAANY(ScaleAddRow_Any_SSE2, ScaleAddRow_SSE2, ScaleAddRow_C, 15) -#endif -#ifdef HAS_SCALEADDROW_AVX2 -SAANY(ScaleAddRow_Any_AVX2, ScaleAddRow_AVX2, ScaleAddRow_C, 31) -#endif -#ifdef HAS_SCALEADDROW_NEON -SAANY(ScaleAddRow_Any_NEON, ScaleAddRow_NEON, ScaleAddRow_C, 15) -#endif -#undef SAANY - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - - - - - diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale_argb.cc b/telegramgallery/src/main/cpp/libyuv/source/scale_argb.cc deleted file mode 100644 index 17f51ae..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale_argb.cc +++ /dev/null @@ -1,859 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/scale.h" - -#include -#include - -#include "libyuv/cpu_id.h" -#include "libyuv/planar_functions.h" // For CopyARGB -#include "libyuv/row.h" -#include "libyuv/scale_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -static __inline int Abs(int v) { - return v >= 0 ? v : -v; -} - -// ScaleARGB ARGB, 1/2 -// This is an optimized version for scaling down a ARGB to 1/2 of -// its original size. -static void ScaleARGBDown2(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int dx, int y, int dy, - enum FilterMode filtering) { - int j; - int row_stride = src_stride * (dy >> 16); - void (*ScaleARGBRowDown2)(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) = - filtering == kFilterNone ? ScaleARGBRowDown2_C : - (filtering == kFilterLinear ? ScaleARGBRowDown2Linear_C : - ScaleARGBRowDown2Box_C); - assert(dx == 65536 * 2); // Test scale factor of 2. - assert((dy & 0x1ffff) == 0); // Test vertical scale is multiple of 2. - // Advance to odd row, even column. - if (filtering == kFilterBilinear) { - src_argb += (y >> 16) * src_stride + (x >> 16) * 4; - } else { - src_argb += (y >> 16) * src_stride + ((x >> 16) - 1) * 4; - } - -#if defined(HAS_SCALEARGBROWDOWN2_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ScaleARGBRowDown2 = filtering == kFilterNone ? ScaleARGBRowDown2_Any_SSE2 : - (filtering == kFilterLinear ? ScaleARGBRowDown2Linear_Any_SSE2 : - ScaleARGBRowDown2Box_Any_SSE2); - if (IS_ALIGNED(dst_width, 4)) { - ScaleARGBRowDown2 = filtering == kFilterNone ? ScaleARGBRowDown2_SSE2 : - (filtering == kFilterLinear ? ScaleARGBRowDown2Linear_SSE2 : - ScaleARGBRowDown2Box_SSE2); - } - } -#endif -#if defined(HAS_SCALEARGBROWDOWN2_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleARGBRowDown2 = filtering == kFilterNone ? ScaleARGBRowDown2_Any_NEON : - (filtering == kFilterLinear ? ScaleARGBRowDown2Linear_Any_NEON : - ScaleARGBRowDown2Box_Any_NEON); - if (IS_ALIGNED(dst_width, 8)) { - ScaleARGBRowDown2 = filtering == kFilterNone ? ScaleARGBRowDown2_NEON : - (filtering == kFilterLinear ? ScaleARGBRowDown2Linear_NEON : - ScaleARGBRowDown2Box_NEON); - } - } -#endif - - if (filtering == kFilterLinear) { - src_stride = 0; - } - for (j = 0; j < dst_height; ++j) { - ScaleARGBRowDown2(src_argb, src_stride, dst_argb, dst_width); - src_argb += row_stride; - dst_argb += dst_stride; - } -} - -// ScaleARGB ARGB, 1/4 -// This is an optimized version for scaling down a ARGB to 1/4 of -// its original size. -static void ScaleARGBDown4Box(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int dx, int y, int dy) { - int j; - // Allocate 2 rows of ARGB. - const int kRowSize = (dst_width * 2 * 4 + 31) & ~31; - align_buffer_64(row, kRowSize * 2); - int row_stride = src_stride * (dy >> 16); - void (*ScaleARGBRowDown2)(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) = ScaleARGBRowDown2Box_C; - // Advance to odd row, even column. - src_argb += (y >> 16) * src_stride + (x >> 16) * 4; - assert(dx == 65536 * 4); // Test scale factor of 4. - assert((dy & 0x3ffff) == 0); // Test vertical scale is multiple of 4. -#if defined(HAS_SCALEARGBROWDOWN2_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ScaleARGBRowDown2 = ScaleARGBRowDown2Box_Any_SSE2; - if (IS_ALIGNED(dst_width, 4)) { - ScaleARGBRowDown2 = ScaleARGBRowDown2Box_SSE2; - } - } -#endif -#if defined(HAS_SCALEARGBROWDOWN2_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleARGBRowDown2 = ScaleARGBRowDown2Box_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleARGBRowDown2 = ScaleARGBRowDown2Box_NEON; - } - } -#endif - - for (j = 0; j < dst_height; ++j) { - ScaleARGBRowDown2(src_argb, src_stride, row, dst_width * 2); - ScaleARGBRowDown2(src_argb + src_stride * 2, src_stride, - row + kRowSize, dst_width * 2); - ScaleARGBRowDown2(row, kRowSize, dst_argb, dst_width); - src_argb += row_stride; - dst_argb += dst_stride; - } - free_aligned_buffer_64(row); -} - -// ScaleARGB ARGB Even -// This is an optimized version for scaling down a ARGB to even -// multiple of its original size. -static void ScaleARGBDownEven(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int dx, int y, int dy, - enum FilterMode filtering) { - int j; - int col_step = dx >> 16; - int row_stride = (dy >> 16) * src_stride; - void (*ScaleARGBRowDownEven)(const uint8* src_argb, ptrdiff_t src_stride, - int src_step, uint8* dst_argb, int dst_width) = - filtering ? ScaleARGBRowDownEvenBox_C : ScaleARGBRowDownEven_C; - assert(IS_ALIGNED(src_width, 2)); - assert(IS_ALIGNED(src_height, 2)); - src_argb += (y >> 16) * src_stride + (x >> 16) * 4; -#if defined(HAS_SCALEARGBROWDOWNEVEN_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - ScaleARGBRowDownEven = filtering ? ScaleARGBRowDownEvenBox_Any_SSE2 : - ScaleARGBRowDownEven_Any_SSE2; - if (IS_ALIGNED(dst_width, 4)) { - ScaleARGBRowDownEven = filtering ? ScaleARGBRowDownEvenBox_SSE2 : - ScaleARGBRowDownEven_SSE2; - } - } -#endif -#if defined(HAS_SCALEARGBROWDOWNEVEN_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleARGBRowDownEven = filtering ? ScaleARGBRowDownEvenBox_Any_NEON : - ScaleARGBRowDownEven_Any_NEON; - if (IS_ALIGNED(dst_width, 4)) { - ScaleARGBRowDownEven = filtering ? ScaleARGBRowDownEvenBox_NEON : - ScaleARGBRowDownEven_NEON; - } - } -#endif - - if (filtering == kFilterLinear) { - src_stride = 0; - } - for (j = 0; j < dst_height; ++j) { - ScaleARGBRowDownEven(src_argb, src_stride, col_step, dst_argb, dst_width); - src_argb += row_stride; - dst_argb += dst_stride; - } -} - -// Scale ARGB down with bilinear interpolation. -static void ScaleARGBBilinearDown(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int dx, int y, int dy, - enum FilterMode filtering) { - int j; - void (*InterpolateRow)(uint8* dst_argb, const uint8* src_argb, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_C; - void (*ScaleARGBFilterCols)(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) = - (src_width >= 32768) ? ScaleARGBFilterCols64_C : ScaleARGBFilterCols_C; - int64 xlast = x + (int64)(dst_width - 1) * dx; - int64 xl = (dx >= 0) ? x : xlast; - int64 xr = (dx >= 0) ? xlast : x; - int clip_src_width; - xl = (xl >> 16) & ~3; // Left edge aligned. - xr = (xr >> 16) + 1; // Right most pixel used. Bilinear uses 2 pixels. - xr = (xr + 1 + 3) & ~3; // 1 beyond 4 pixel aligned right most pixel. - if (xr > src_width) { - xr = src_width; - } - clip_src_width = (int)(xr - xl) * 4; // Width aligned to 4. - src_argb += xl * 4; - x -= (int)(xl << 16); -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(clip_src_width, 16)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(clip_src_width, 32)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(clip_src_width, 16)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(src_argb, 4) && IS_ALIGNED(src_stride, 4)) { - InterpolateRow = InterpolateRow_Any_DSPR2; - if (IS_ALIGNED(clip_src_width, 4)) { - InterpolateRow = InterpolateRow_DSPR2; - } - } -#endif -#if defined(HAS_SCALEARGBFILTERCOLS_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3) && src_width < 32768) { - ScaleARGBFilterCols = ScaleARGBFilterCols_SSSE3; - } -#endif -#if defined(HAS_SCALEARGBFILTERCOLS_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleARGBFilterCols = ScaleARGBFilterCols_Any_NEON; - if (IS_ALIGNED(dst_width, 4)) { - ScaleARGBFilterCols = ScaleARGBFilterCols_NEON; - } - } -#endif - // TODO(fbarchard): Consider not allocating row buffer for kFilterLinear. - // Allocate a row of ARGB. - { - align_buffer_64(row, clip_src_width * 4); - - const int max_y = (src_height - 1) << 16; - if (y > max_y) { - y = max_y; - } - for (j = 0; j < dst_height; ++j) { - int yi = y >> 16; - const uint8* src = src_argb + yi * src_stride; - if (filtering == kFilterLinear) { - ScaleARGBFilterCols(dst_argb, src, dst_width, x, dx); - } else { - int yf = (y >> 8) & 255; - InterpolateRow(row, src, src_stride, clip_src_width, yf); - ScaleARGBFilterCols(dst_argb, row, dst_width, x, dx); - } - dst_argb += dst_stride; - y += dy; - if (y > max_y) { - y = max_y; - } - } - free_aligned_buffer_64(row); - } -} - -// Scale ARGB up with bilinear interpolation. -static void ScaleARGBBilinearUp(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int dx, int y, int dy, - enum FilterMode filtering) { - int j; - void (*InterpolateRow)(uint8* dst_argb, const uint8* src_argb, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_C; - void (*ScaleARGBFilterCols)(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) = - filtering ? ScaleARGBFilterCols_C : ScaleARGBCols_C; - const int max_y = (src_height - 1) << 16; -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(dst_width, 4)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(dst_width, 8)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(dst_width, 4)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride, 4)) { - InterpolateRow = InterpolateRow_DSPR2; - } -#endif - if (src_width >= 32768) { - ScaleARGBFilterCols = filtering ? - ScaleARGBFilterCols64_C : ScaleARGBCols64_C; - } -#if defined(HAS_SCALEARGBFILTERCOLS_SSSE3) - if (filtering && TestCpuFlag(kCpuHasSSSE3) && src_width < 32768) { - ScaleARGBFilterCols = ScaleARGBFilterCols_SSSE3; - } -#endif -#if defined(HAS_SCALEARGBFILTERCOLS_NEON) - if (filtering && TestCpuFlag(kCpuHasNEON)) { - ScaleARGBFilterCols = ScaleARGBFilterCols_Any_NEON; - if (IS_ALIGNED(dst_width, 4)) { - ScaleARGBFilterCols = ScaleARGBFilterCols_NEON; - } - } -#endif -#if defined(HAS_SCALEARGBCOLS_SSE2) - if (!filtering && TestCpuFlag(kCpuHasSSE2) && src_width < 32768) { - ScaleARGBFilterCols = ScaleARGBCols_SSE2; - } -#endif -#if defined(HAS_SCALEARGBCOLS_NEON) - if (!filtering && TestCpuFlag(kCpuHasNEON)) { - ScaleARGBFilterCols = ScaleARGBCols_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleARGBFilterCols = ScaleARGBCols_NEON; - } - } -#endif - if (!filtering && src_width * 2 == dst_width && x < 0x8000) { - ScaleARGBFilterCols = ScaleARGBColsUp2_C; -#if defined(HAS_SCALEARGBCOLSUP2_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 8)) { - ScaleARGBFilterCols = ScaleARGBColsUp2_SSE2; - } -#endif - } - - if (y > max_y) { - y = max_y; - } - - { - int yi = y >> 16; - const uint8* src = src_argb + yi * src_stride; - - // Allocate 2 rows of ARGB. - const int kRowSize = (dst_width * 4 + 31) & ~31; - align_buffer_64(row, kRowSize * 2); - - uint8* rowptr = row; - int rowstride = kRowSize; - int lasty = yi; - - ScaleARGBFilterCols(rowptr, src, dst_width, x, dx); - if (src_height > 1) { - src += src_stride; - } - ScaleARGBFilterCols(rowptr + rowstride, src, dst_width, x, dx); - src += src_stride; - - for (j = 0; j < dst_height; ++j) { - yi = y >> 16; - if (yi != lasty) { - if (y > max_y) { - y = max_y; - yi = y >> 16; - src = src_argb + yi * src_stride; - } - if (yi != lasty) { - ScaleARGBFilterCols(rowptr, src, dst_width, x, dx); - rowptr += rowstride; - rowstride = -rowstride; - lasty = yi; - src += src_stride; - } - } - if (filtering == kFilterLinear) { - InterpolateRow(dst_argb, rowptr, 0, dst_width * 4, 0); - } else { - int yf = (y >> 8) & 255; - InterpolateRow(dst_argb, rowptr, rowstride, dst_width * 4, yf); - } - dst_argb += dst_stride; - y += dy; - } - free_aligned_buffer_64(row); - } -} - -#ifdef YUVSCALEUP -// Scale YUV to ARGB up with bilinear interpolation. -static void ScaleYUVToARGBBilinearUp(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride_y, - int src_stride_u, - int src_stride_v, - int dst_stride_argb, - const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - int x, int dx, int y, int dy, - enum FilterMode filtering) { - int j; - void (*I422ToARGBRow)(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - uint8* rgb_buf, - int width) = I422ToARGBRow_C; -#if defined(HAS_I422TOARGBROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - I422ToARGBRow = I422ToARGBRow_Any_SSSE3; - if (IS_ALIGNED(src_width, 8)) { - I422ToARGBRow = I422ToARGBRow_SSSE3; - } - } -#endif -#if defined(HAS_I422TOARGBROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - I422ToARGBRow = I422ToARGBRow_Any_AVX2; - if (IS_ALIGNED(src_width, 16)) { - I422ToARGBRow = I422ToARGBRow_AVX2; - } - } -#endif -#if defined(HAS_I422TOARGBROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - I422ToARGBRow = I422ToARGBRow_Any_NEON; - if (IS_ALIGNED(src_width, 8)) { - I422ToARGBRow = I422ToARGBRow_NEON; - } - } -#endif -#if defined(HAS_I422TOARGBROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_width, 4) && - IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && - IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && - IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && - IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - I422ToARGBRow = I422ToARGBRow_DSPR2; - } -#endif - - void (*InterpolateRow)(uint8* dst_argb, const uint8* src_argb, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_C; -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(dst_width, 4)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(dst_width, 8)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(dst_width, 4)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - InterpolateRow = InterpolateRow_DSPR2; - } -#endif - - void (*ScaleARGBFilterCols)(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) = - filtering ? ScaleARGBFilterCols_C : ScaleARGBCols_C; - if (src_width >= 32768) { - ScaleARGBFilterCols = filtering ? - ScaleARGBFilterCols64_C : ScaleARGBCols64_C; - } -#if defined(HAS_SCALEARGBFILTERCOLS_SSSE3) - if (filtering && TestCpuFlag(kCpuHasSSSE3) && src_width < 32768) { - ScaleARGBFilterCols = ScaleARGBFilterCols_SSSE3; - } -#endif -#if defined(HAS_SCALEARGBFILTERCOLS_NEON) - if (filtering && TestCpuFlag(kCpuHasNEON)) { - ScaleARGBFilterCols = ScaleARGBFilterCols_Any_NEON; - if (IS_ALIGNED(dst_width, 4)) { - ScaleARGBFilterCols = ScaleARGBFilterCols_NEON; - } - } -#endif -#if defined(HAS_SCALEARGBCOLS_SSE2) - if (!filtering && TestCpuFlag(kCpuHasSSE2) && src_width < 32768) { - ScaleARGBFilterCols = ScaleARGBCols_SSE2; - } -#endif -#if defined(HAS_SCALEARGBCOLS_NEON) - if (!filtering && TestCpuFlag(kCpuHasNEON)) { - ScaleARGBFilterCols = ScaleARGBCols_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleARGBFilterCols = ScaleARGBCols_NEON; - } - } -#endif - if (!filtering && src_width * 2 == dst_width && x < 0x8000) { - ScaleARGBFilterCols = ScaleARGBColsUp2_C; -#if defined(HAS_SCALEARGBCOLSUP2_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 8)) { - ScaleARGBFilterCols = ScaleARGBColsUp2_SSE2; - } -#endif - } - - const int max_y = (src_height - 1) << 16; - if (y > max_y) { - y = max_y; - } - const int kYShift = 1; // Shift Y by 1 to convert Y plane to UV coordinate. - int yi = y >> 16; - int uv_yi = yi >> kYShift; - const uint8* src_row_y = src_y + yi * src_stride_y; - const uint8* src_row_u = src_u + uv_yi * src_stride_u; - const uint8* src_row_v = src_v + uv_yi * src_stride_v; - - // Allocate 2 rows of ARGB. - const int kRowSize = (dst_width * 4 + 31) & ~31; - align_buffer_64(row, kRowSize * 2); - - // Allocate 1 row of ARGB for source conversion. - align_buffer_64(argb_row, src_width * 4); - - uint8* rowptr = row; - int rowstride = kRowSize; - int lasty = yi; - - // TODO(fbarchard): Convert first 2 rows of YUV to ARGB. - ScaleARGBFilterCols(rowptr, src_row_y, dst_width, x, dx); - if (src_height > 1) { - src_row_y += src_stride_y; - if (yi & 1) { - src_row_u += src_stride_u; - src_row_v += src_stride_v; - } - } - ScaleARGBFilterCols(rowptr + rowstride, src_row_y, dst_width, x, dx); - if (src_height > 2) { - src_row_y += src_stride_y; - if (!(yi & 1)) { - src_row_u += src_stride_u; - src_row_v += src_stride_v; - } - } - - for (j = 0; j < dst_height; ++j) { - yi = y >> 16; - if (yi != lasty) { - if (y > max_y) { - y = max_y; - yi = y >> 16; - uv_yi = yi >> kYShift; - src_row_y = src_y + yi * src_stride_y; - src_row_u = src_u + uv_yi * src_stride_u; - src_row_v = src_v + uv_yi * src_stride_v; - } - if (yi != lasty) { - // TODO(fbarchard): Convert the clipped region of row. - I422ToARGBRow(src_row_y, src_row_u, src_row_v, argb_row, src_width); - ScaleARGBFilterCols(rowptr, argb_row, dst_width, x, dx); - rowptr += rowstride; - rowstride = -rowstride; - lasty = yi; - src_row_y += src_stride_y; - if (yi & 1) { - src_row_u += src_stride_u; - src_row_v += src_stride_v; - } - } - } - if (filtering == kFilterLinear) { - InterpolateRow(dst_argb, rowptr, 0, dst_width * 4, 0); - } else { - int yf = (y >> 8) & 255; - InterpolateRow(dst_argb, rowptr, rowstride, dst_width * 4, yf); - } - dst_argb += dst_stride_argb; - y += dy; - } - free_aligned_buffer_64(row); - free_aligned_buffer_64(row_argb); -} -#endif - -// Scale ARGB to/from any dimensions, without interpolation. -// Fixed point math is used for performance: The upper 16 bits -// of x and dx is the integer part of the source position and -// the lower 16 bits are the fixed decimal part. - -static void ScaleARGBSimple(int src_width, int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int dx, int y, int dy) { - int j; - void (*ScaleARGBCols)(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) = - (src_width >= 32768) ? ScaleARGBCols64_C : ScaleARGBCols_C; -#if defined(HAS_SCALEARGBCOLS_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && src_width < 32768) { - ScaleARGBCols = ScaleARGBCols_SSE2; - } -#endif -#if defined(HAS_SCALEARGBCOLS_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleARGBCols = ScaleARGBCols_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleARGBCols = ScaleARGBCols_NEON; - } - } -#endif - if (src_width * 2 == dst_width && x < 0x8000) { - ScaleARGBCols = ScaleARGBColsUp2_C; -#if defined(HAS_SCALEARGBCOLSUP2_SSE2) - if (TestCpuFlag(kCpuHasSSE2) && IS_ALIGNED(dst_width, 8)) { - ScaleARGBCols = ScaleARGBColsUp2_SSE2; - } -#endif - } - - for (j = 0; j < dst_height; ++j) { - ScaleARGBCols(dst_argb, src_argb + (y >> 16) * src_stride, - dst_width, x, dx); - dst_argb += dst_stride; - y += dy; - } -} - -// ScaleARGB a ARGB. -// This function in turn calls a scaling function -// suitable for handling the desired resolutions. -static void ScaleARGB(const uint8* src, int src_stride, - int src_width, int src_height, - uint8* dst, int dst_stride, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering) { - // Initial source x/y coordinate and step values as 16.16 fixed point. - int x = 0; - int y = 0; - int dx = 0; - int dy = 0; - // ARGB does not support box filter yet, but allow the user to pass it. - // Simplify filtering when possible. - filtering = ScaleFilterReduce(src_width, src_height, - dst_width, dst_height, - filtering); - - // Negative src_height means invert the image. - if (src_height < 0) { - src_height = -src_height; - src = src + (src_height - 1) * src_stride; - src_stride = -src_stride; - } - ScaleSlope(src_width, src_height, dst_width, dst_height, filtering, - &x, &y, &dx, &dy); - src_width = Abs(src_width); - if (clip_x) { - int64 clipf = (int64)(clip_x) * dx; - x += (clipf & 0xffff); - src += (clipf >> 16) * 4; - dst += clip_x * 4; - } - if (clip_y) { - int64 clipf = (int64)(clip_y) * dy; - y += (clipf & 0xffff); - src += (clipf >> 16) * src_stride; - dst += clip_y * dst_stride; - } - - // Special case for integer step values. - if (((dx | dy) & 0xffff) == 0) { - if (!dx || !dy) { // 1 pixel wide and/or tall. - filtering = kFilterNone; - } else { - // Optimized even scale down. ie 2, 4, 6, 8, 10x. - if (!(dx & 0x10000) && !(dy & 0x10000)) { - if (dx == 0x20000) { - // Optimized 1/2 downsample. - ScaleARGBDown2(src_width, src_height, - clip_width, clip_height, - src_stride, dst_stride, src, dst, - x, dx, y, dy, filtering); - return; - } - if (dx == 0x40000 && filtering == kFilterBox) { - // Optimized 1/4 box downsample. - ScaleARGBDown4Box(src_width, src_height, - clip_width, clip_height, - src_stride, dst_stride, src, dst, - x, dx, y, dy); - return; - } - ScaleARGBDownEven(src_width, src_height, - clip_width, clip_height, - src_stride, dst_stride, src, dst, - x, dx, y, dy, filtering); - return; - } - // Optimized odd scale down. ie 3, 5, 7, 9x. - if ((dx & 0x10000) && (dy & 0x10000)) { - filtering = kFilterNone; - if (dx == 0x10000 && dy == 0x10000) { - // Straight copy. - ARGBCopy(src + (y >> 16) * src_stride + (x >> 16) * 4, src_stride, - dst, dst_stride, clip_width, clip_height); - return; - } - } - } - } - if (dx == 0x10000 && (x & 0xffff) == 0) { - // Arbitrary scale vertically, but unscaled vertically. - ScalePlaneVertical(src_height, - clip_width, clip_height, - src_stride, dst_stride, src, dst, - x, y, dy, 4, filtering); - return; - } - if (filtering && dy < 65536) { - ScaleARGBBilinearUp(src_width, src_height, - clip_width, clip_height, - src_stride, dst_stride, src, dst, - x, dx, y, dy, filtering); - return; - } - if (filtering) { - ScaleARGBBilinearDown(src_width, src_height, - clip_width, clip_height, - src_stride, dst_stride, src, dst, - x, dx, y, dy, filtering); - return; - } - ScaleARGBSimple(src_width, src_height, clip_width, clip_height, - src_stride, dst_stride, src, dst, - x, dx, y, dy); -} - -LIBYUV_API -int ARGBScaleClip(const uint8* src_argb, int src_stride_argb, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering) { - if (!src_argb || src_width == 0 || src_height == 0 || - !dst_argb || dst_width <= 0 || dst_height <= 0 || - clip_x < 0 || clip_y < 0 || - clip_width > 32768 || clip_height > 32768 || - (clip_x + clip_width) > dst_width || - (clip_y + clip_height) > dst_height) { - return -1; - } - ScaleARGB(src_argb, src_stride_argb, src_width, src_height, - dst_argb, dst_stride_argb, dst_width, dst_height, - clip_x, clip_y, clip_width, clip_height, filtering); - return 0; -} - -// Scale an ARGB image. -LIBYUV_API -int ARGBScale(const uint8* src_argb, int src_stride_argb, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - int dst_width, int dst_height, - enum FilterMode filtering) { - if (!src_argb || src_width == 0 || src_height == 0 || - src_width > 32768 || src_height > 32768 || - !dst_argb || dst_width <= 0 || dst_height <= 0) { - return -1; - } - ScaleARGB(src_argb, src_stride_argb, src_width, src_height, - dst_argb, dst_stride_argb, dst_width, dst_height, - 0, 0, dst_width, dst_height, filtering); - return 0; -} - -// Scale with YUV conversion to ARGB and clipping. -LIBYUV_API -int YUVToARGBScaleClip(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint32 src_fourcc, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - uint32 dst_fourcc, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering) { - uint8* argb_buffer = (uint8*)malloc(src_width * src_height * 4); - int r; - I420ToARGB(src_y, src_stride_y, - src_u, src_stride_u, - src_v, src_stride_v, - argb_buffer, src_width * 4, - src_width, src_height); - - r = ARGBScaleClip(argb_buffer, src_width * 4, - src_width, src_height, - dst_argb, dst_stride_argb, - dst_width, dst_height, - clip_x, clip_y, clip_width, clip_height, - filtering); - free(argb_buffer); - return r; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale_common.cc b/telegramgallery/src/main/cpp/libyuv/source/scale_common.cc deleted file mode 100644 index d3992df..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale_common.cc +++ /dev/null @@ -1,1151 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/scale.h" - -#include -#include - -#include "libyuv/cpu_id.h" -#include "libyuv/planar_functions.h" // For CopyARGB -#include "libyuv/row.h" -#include "libyuv/scale_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -static __inline int Abs(int v) { - return v >= 0 ? v : -v; -} - -// CPU agnostic row functions -void ScaleRowDown2_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = src_ptr[1]; - dst[1] = src_ptr[3]; - dst += 2; - src_ptr += 4; - } - if (dst_width & 1) { - dst[0] = src_ptr[1]; - } -} - -void ScaleRowDown2_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width) { - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = src_ptr[1]; - dst[1] = src_ptr[3]; - dst += 2; - src_ptr += 4; - } - if (dst_width & 1) { - dst[0] = src_ptr[1]; - } -} - -void ScaleRowDown2Linear_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - const uint8* s = src_ptr; - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = (s[0] + s[1] + 1) >> 1; - dst[1] = (s[2] + s[3] + 1) >> 1; - dst += 2; - s += 4; - } - if (dst_width & 1) { - dst[0] = (s[0] + s[1] + 1) >> 1; - } -} - -void ScaleRowDown2Linear_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width) { - const uint16* s = src_ptr; - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = (s[0] + s[1] + 1) >> 1; - dst[1] = (s[2] + s[3] + 1) >> 1; - dst += 2; - s += 4; - } - if (dst_width & 1) { - dst[0] = (s[0] + s[1] + 1) >> 1; - } -} - -void ScaleRowDown2Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - const uint8* s = src_ptr; - const uint8* t = src_ptr + src_stride; - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = (s[0] + s[1] + t[0] + t[1] + 2) >> 2; - dst[1] = (s[2] + s[3] + t[2] + t[3] + 2) >> 2; - dst += 2; - s += 4; - t += 4; - } - if (dst_width & 1) { - dst[0] = (s[0] + s[1] + t[0] + t[1] + 2) >> 2; - } -} - -void ScaleRowDown2Box_Odd_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - const uint8* s = src_ptr; - const uint8* t = src_ptr + src_stride; - int x; - dst_width -= 1; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = (s[0] + s[1] + t[0] + t[1] + 2) >> 2; - dst[1] = (s[2] + s[3] + t[2] + t[3] + 2) >> 2; - dst += 2; - s += 4; - t += 4; - } - if (dst_width & 1) { - dst[0] = (s[0] + s[1] + t[0] + t[1] + 2) >> 2; - dst += 1; - s += 2; - t += 2; - } - dst[0] = (s[0] + t[0] + 1) >> 1; -} - -void ScaleRowDown2Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width) { - const uint16* s = src_ptr; - const uint16* t = src_ptr + src_stride; - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = (s[0] + s[1] + t[0] + t[1] + 2) >> 2; - dst[1] = (s[2] + s[3] + t[2] + t[3] + 2) >> 2; - dst += 2; - s += 4; - t += 4; - } - if (dst_width & 1) { - dst[0] = (s[0] + s[1] + t[0] + t[1] + 2) >> 2; - } -} - -void ScaleRowDown4_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = src_ptr[2]; - dst[1] = src_ptr[6]; - dst += 2; - src_ptr += 8; - } - if (dst_width & 1) { - dst[0] = src_ptr[2]; - } -} - -void ScaleRowDown4_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width) { - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = src_ptr[2]; - dst[1] = src_ptr[6]; - dst += 2; - src_ptr += 8; - } - if (dst_width & 1) { - dst[0] = src_ptr[2]; - } -} - -void ScaleRowDown4Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - intptr_t stride = src_stride; - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = (src_ptr[0] + src_ptr[1] + src_ptr[2] + src_ptr[3] + - src_ptr[stride + 0] + src_ptr[stride + 1] + - src_ptr[stride + 2] + src_ptr[stride + 3] + - src_ptr[stride * 2 + 0] + src_ptr[stride * 2 + 1] + - src_ptr[stride * 2 + 2] + src_ptr[stride * 2 + 3] + - src_ptr[stride * 3 + 0] + src_ptr[stride * 3 + 1] + - src_ptr[stride * 3 + 2] + src_ptr[stride * 3 + 3] + - 8) >> 4; - dst[1] = (src_ptr[4] + src_ptr[5] + src_ptr[6] + src_ptr[7] + - src_ptr[stride + 4] + src_ptr[stride + 5] + - src_ptr[stride + 6] + src_ptr[stride + 7] + - src_ptr[stride * 2 + 4] + src_ptr[stride * 2 + 5] + - src_ptr[stride * 2 + 6] + src_ptr[stride * 2 + 7] + - src_ptr[stride * 3 + 4] + src_ptr[stride * 3 + 5] + - src_ptr[stride * 3 + 6] + src_ptr[stride * 3 + 7] + - 8) >> 4; - dst += 2; - src_ptr += 8; - } - if (dst_width & 1) { - dst[0] = (src_ptr[0] + src_ptr[1] + src_ptr[2] + src_ptr[3] + - src_ptr[stride + 0] + src_ptr[stride + 1] + - src_ptr[stride + 2] + src_ptr[stride + 3] + - src_ptr[stride * 2 + 0] + src_ptr[stride * 2 + 1] + - src_ptr[stride * 2 + 2] + src_ptr[stride * 2 + 3] + - src_ptr[stride * 3 + 0] + src_ptr[stride * 3 + 1] + - src_ptr[stride * 3 + 2] + src_ptr[stride * 3 + 3] + - 8) >> 4; - } -} - -void ScaleRowDown4Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width) { - intptr_t stride = src_stride; - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = (src_ptr[0] + src_ptr[1] + src_ptr[2] + src_ptr[3] + - src_ptr[stride + 0] + src_ptr[stride + 1] + - src_ptr[stride + 2] + src_ptr[stride + 3] + - src_ptr[stride * 2 + 0] + src_ptr[stride * 2 + 1] + - src_ptr[stride * 2 + 2] + src_ptr[stride * 2 + 3] + - src_ptr[stride * 3 + 0] + src_ptr[stride * 3 + 1] + - src_ptr[stride * 3 + 2] + src_ptr[stride * 3 + 3] + - 8) >> 4; - dst[1] = (src_ptr[4] + src_ptr[5] + src_ptr[6] + src_ptr[7] + - src_ptr[stride + 4] + src_ptr[stride + 5] + - src_ptr[stride + 6] + src_ptr[stride + 7] + - src_ptr[stride * 2 + 4] + src_ptr[stride * 2 + 5] + - src_ptr[stride * 2 + 6] + src_ptr[stride * 2 + 7] + - src_ptr[stride * 3 + 4] + src_ptr[stride * 3 + 5] + - src_ptr[stride * 3 + 6] + src_ptr[stride * 3 + 7] + - 8) >> 4; - dst += 2; - src_ptr += 8; - } - if (dst_width & 1) { - dst[0] = (src_ptr[0] + src_ptr[1] + src_ptr[2] + src_ptr[3] + - src_ptr[stride + 0] + src_ptr[stride + 1] + - src_ptr[stride + 2] + src_ptr[stride + 3] + - src_ptr[stride * 2 + 0] + src_ptr[stride * 2 + 1] + - src_ptr[stride * 2 + 2] + src_ptr[stride * 2 + 3] + - src_ptr[stride * 3 + 0] + src_ptr[stride * 3 + 1] + - src_ptr[stride * 3 + 2] + src_ptr[stride * 3 + 3] + - 8) >> 4; - } -} - -void ScaleRowDown34_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - int x; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (x = 0; x < dst_width; x += 3) { - dst[0] = src_ptr[0]; - dst[1] = src_ptr[1]; - dst[2] = src_ptr[3]; - dst += 3; - src_ptr += 4; - } -} - -void ScaleRowDown34_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width) { - int x; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (x = 0; x < dst_width; x += 3) { - dst[0] = src_ptr[0]; - dst[1] = src_ptr[1]; - dst[2] = src_ptr[3]; - dst += 3; - src_ptr += 4; - } -} - -// Filter rows 0 and 1 together, 3 : 1 -void ScaleRowDown34_0_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width) { - const uint8* s = src_ptr; - const uint8* t = src_ptr + src_stride; - int x; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (x = 0; x < dst_width; x += 3) { - uint8 a0 = (s[0] * 3 + s[1] * 1 + 2) >> 2; - uint8 a1 = (s[1] * 1 + s[2] * 1 + 1) >> 1; - uint8 a2 = (s[2] * 1 + s[3] * 3 + 2) >> 2; - uint8 b0 = (t[0] * 3 + t[1] * 1 + 2) >> 2; - uint8 b1 = (t[1] * 1 + t[2] * 1 + 1) >> 1; - uint8 b2 = (t[2] * 1 + t[3] * 3 + 2) >> 2; - d[0] = (a0 * 3 + b0 + 2) >> 2; - d[1] = (a1 * 3 + b1 + 2) >> 2; - d[2] = (a2 * 3 + b2 + 2) >> 2; - d += 3; - s += 4; - t += 4; - } -} - -void ScaleRowDown34_0_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* d, int dst_width) { - const uint16* s = src_ptr; - const uint16* t = src_ptr + src_stride; - int x; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (x = 0; x < dst_width; x += 3) { - uint16 a0 = (s[0] * 3 + s[1] * 1 + 2) >> 2; - uint16 a1 = (s[1] * 1 + s[2] * 1 + 1) >> 1; - uint16 a2 = (s[2] * 1 + s[3] * 3 + 2) >> 2; - uint16 b0 = (t[0] * 3 + t[1] * 1 + 2) >> 2; - uint16 b1 = (t[1] * 1 + t[2] * 1 + 1) >> 1; - uint16 b2 = (t[2] * 1 + t[3] * 3 + 2) >> 2; - d[0] = (a0 * 3 + b0 + 2) >> 2; - d[1] = (a1 * 3 + b1 + 2) >> 2; - d[2] = (a2 * 3 + b2 + 2) >> 2; - d += 3; - s += 4; - t += 4; - } -} - -// Filter rows 1 and 2 together, 1 : 1 -void ScaleRowDown34_1_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width) { - const uint8* s = src_ptr; - const uint8* t = src_ptr + src_stride; - int x; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (x = 0; x < dst_width; x += 3) { - uint8 a0 = (s[0] * 3 + s[1] * 1 + 2) >> 2; - uint8 a1 = (s[1] * 1 + s[2] * 1 + 1) >> 1; - uint8 a2 = (s[2] * 1 + s[3] * 3 + 2) >> 2; - uint8 b0 = (t[0] * 3 + t[1] * 1 + 2) >> 2; - uint8 b1 = (t[1] * 1 + t[2] * 1 + 1) >> 1; - uint8 b2 = (t[2] * 1 + t[3] * 3 + 2) >> 2; - d[0] = (a0 + b0 + 1) >> 1; - d[1] = (a1 + b1 + 1) >> 1; - d[2] = (a2 + b2 + 1) >> 1; - d += 3; - s += 4; - t += 4; - } -} - -void ScaleRowDown34_1_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* d, int dst_width) { - const uint16* s = src_ptr; - const uint16* t = src_ptr + src_stride; - int x; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (x = 0; x < dst_width; x += 3) { - uint16 a0 = (s[0] * 3 + s[1] * 1 + 2) >> 2; - uint16 a1 = (s[1] * 1 + s[2] * 1 + 1) >> 1; - uint16 a2 = (s[2] * 1 + s[3] * 3 + 2) >> 2; - uint16 b0 = (t[0] * 3 + t[1] * 1 + 2) >> 2; - uint16 b1 = (t[1] * 1 + t[2] * 1 + 1) >> 1; - uint16 b2 = (t[2] * 1 + t[3] * 3 + 2) >> 2; - d[0] = (a0 + b0 + 1) >> 1; - d[1] = (a1 + b1 + 1) >> 1; - d[2] = (a2 + b2 + 1) >> 1; - d += 3; - s += 4; - t += 4; - } -} - -// Scales a single row of pixels using point sampling. -void ScaleCols_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - int j; - for (j = 0; j < dst_width - 1; j += 2) { - dst_ptr[0] = src_ptr[x >> 16]; - x += dx; - dst_ptr[1] = src_ptr[x >> 16]; - x += dx; - dst_ptr += 2; - } - if (dst_width & 1) { - dst_ptr[0] = src_ptr[x >> 16]; - } -} - -void ScaleCols_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx) { - int j; - for (j = 0; j < dst_width - 1; j += 2) { - dst_ptr[0] = src_ptr[x >> 16]; - x += dx; - dst_ptr[1] = src_ptr[x >> 16]; - x += dx; - dst_ptr += 2; - } - if (dst_width & 1) { - dst_ptr[0] = src_ptr[x >> 16]; - } -} - -// Scales a single row of pixels up by 2x using point sampling. -void ScaleColsUp2_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - int j; - for (j = 0; j < dst_width - 1; j += 2) { - dst_ptr[1] = dst_ptr[0] = src_ptr[0]; - src_ptr += 1; - dst_ptr += 2; - } - if (dst_width & 1) { - dst_ptr[0] = src_ptr[0]; - } -} - -void ScaleColsUp2_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx) { - int j; - for (j = 0; j < dst_width - 1; j += 2) { - dst_ptr[1] = dst_ptr[0] = src_ptr[0]; - src_ptr += 1; - dst_ptr += 2; - } - if (dst_width & 1) { - dst_ptr[0] = src_ptr[0]; - } -} - -// (1-f)a + fb can be replaced with a + f(b-a) -#define BLENDER(a, b, f) (uint8)((int)(a) + \ - ((int)(f) * ((int)(b) - (int)(a)) >> 16)) - -void ScaleFilterCols_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - int j; - for (j = 0; j < dst_width - 1; j += 2) { - int xi = x >> 16; - int a = src_ptr[xi]; - int b = src_ptr[xi + 1]; - dst_ptr[0] = BLENDER(a, b, x & 0xffff); - x += dx; - xi = x >> 16; - a = src_ptr[xi]; - b = src_ptr[xi + 1]; - dst_ptr[1] = BLENDER(a, b, x & 0xffff); - x += dx; - dst_ptr += 2; - } - if (dst_width & 1) { - int xi = x >> 16; - int a = src_ptr[xi]; - int b = src_ptr[xi + 1]; - dst_ptr[0] = BLENDER(a, b, x & 0xffff); - } -} - -void ScaleFilterCols64_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x32, int dx) { - int64 x = (int64)(x32); - int j; - for (j = 0; j < dst_width - 1; j += 2) { - int64 xi = x >> 16; - int a = src_ptr[xi]; - int b = src_ptr[xi + 1]; - dst_ptr[0] = BLENDER(a, b, x & 0xffff); - x += dx; - xi = x >> 16; - a = src_ptr[xi]; - b = src_ptr[xi + 1]; - dst_ptr[1] = BLENDER(a, b, x & 0xffff); - x += dx; - dst_ptr += 2; - } - if (dst_width & 1) { - int64 xi = x >> 16; - int a = src_ptr[xi]; - int b = src_ptr[xi + 1]; - dst_ptr[0] = BLENDER(a, b, x & 0xffff); - } -} -#undef BLENDER - -#define BLENDER(a, b, f) (uint16)((int)(a) + \ - ((int)(f) * ((int)(b) - (int)(a)) >> 16)) - -void ScaleFilterCols_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx) { - int j; - for (j = 0; j < dst_width - 1; j += 2) { - int xi = x >> 16; - int a = src_ptr[xi]; - int b = src_ptr[xi + 1]; - dst_ptr[0] = BLENDER(a, b, x & 0xffff); - x += dx; - xi = x >> 16; - a = src_ptr[xi]; - b = src_ptr[xi + 1]; - dst_ptr[1] = BLENDER(a, b, x & 0xffff); - x += dx; - dst_ptr += 2; - } - if (dst_width & 1) { - int xi = x >> 16; - int a = src_ptr[xi]; - int b = src_ptr[xi + 1]; - dst_ptr[0] = BLENDER(a, b, x & 0xffff); - } -} - -void ScaleFilterCols64_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x32, int dx) { - int64 x = (int64)(x32); - int j; - for (j = 0; j < dst_width - 1; j += 2) { - int64 xi = x >> 16; - int a = src_ptr[xi]; - int b = src_ptr[xi + 1]; - dst_ptr[0] = BLENDER(a, b, x & 0xffff); - x += dx; - xi = x >> 16; - a = src_ptr[xi]; - b = src_ptr[xi + 1]; - dst_ptr[1] = BLENDER(a, b, x & 0xffff); - x += dx; - dst_ptr += 2; - } - if (dst_width & 1) { - int64 xi = x >> 16; - int a = src_ptr[xi]; - int b = src_ptr[xi + 1]; - dst_ptr[0] = BLENDER(a, b, x & 0xffff); - } -} -#undef BLENDER - -void ScaleRowDown38_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - int x; - assert(dst_width % 3 == 0); - for (x = 0; x < dst_width; x += 3) { - dst[0] = src_ptr[0]; - dst[1] = src_ptr[3]; - dst[2] = src_ptr[6]; - dst += 3; - src_ptr += 8; - } -} - -void ScaleRowDown38_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width) { - int x; - assert(dst_width % 3 == 0); - for (x = 0; x < dst_width; x += 3) { - dst[0] = src_ptr[0]; - dst[1] = src_ptr[3]; - dst[2] = src_ptr[6]; - dst += 3; - src_ptr += 8; - } -} - -// 8x3 -> 3x1 -void ScaleRowDown38_3_Box_C(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - intptr_t stride = src_stride; - int i; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (i = 0; i < dst_width; i += 3) { - dst_ptr[0] = (src_ptr[0] + src_ptr[1] + src_ptr[2] + - src_ptr[stride + 0] + src_ptr[stride + 1] + - src_ptr[stride + 2] + src_ptr[stride * 2 + 0] + - src_ptr[stride * 2 + 1] + src_ptr[stride * 2 + 2]) * - (65536 / 9) >> 16; - dst_ptr[1] = (src_ptr[3] + src_ptr[4] + src_ptr[5] + - src_ptr[stride + 3] + src_ptr[stride + 4] + - src_ptr[stride + 5] + src_ptr[stride * 2 + 3] + - src_ptr[stride * 2 + 4] + src_ptr[stride * 2 + 5]) * - (65536 / 9) >> 16; - dst_ptr[2] = (src_ptr[6] + src_ptr[7] + - src_ptr[stride + 6] + src_ptr[stride + 7] + - src_ptr[stride * 2 + 6] + src_ptr[stride * 2 + 7]) * - (65536 / 6) >> 16; - src_ptr += 8; - dst_ptr += 3; - } -} - -void ScaleRowDown38_3_Box_16_C(const uint16* src_ptr, - ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width) { - intptr_t stride = src_stride; - int i; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (i = 0; i < dst_width; i += 3) { - dst_ptr[0] = (src_ptr[0] + src_ptr[1] + src_ptr[2] + - src_ptr[stride + 0] + src_ptr[stride + 1] + - src_ptr[stride + 2] + src_ptr[stride * 2 + 0] + - src_ptr[stride * 2 + 1] + src_ptr[stride * 2 + 2]) * - (65536 / 9) >> 16; - dst_ptr[1] = (src_ptr[3] + src_ptr[4] + src_ptr[5] + - src_ptr[stride + 3] + src_ptr[stride + 4] + - src_ptr[stride + 5] + src_ptr[stride * 2 + 3] + - src_ptr[stride * 2 + 4] + src_ptr[stride * 2 + 5]) * - (65536 / 9) >> 16; - dst_ptr[2] = (src_ptr[6] + src_ptr[7] + - src_ptr[stride + 6] + src_ptr[stride + 7] + - src_ptr[stride * 2 + 6] + src_ptr[stride * 2 + 7]) * - (65536 / 6) >> 16; - src_ptr += 8; - dst_ptr += 3; - } -} - -// 8x2 -> 3x1 -void ScaleRowDown38_2_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - intptr_t stride = src_stride; - int i; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (i = 0; i < dst_width; i += 3) { - dst_ptr[0] = (src_ptr[0] + src_ptr[1] + src_ptr[2] + - src_ptr[stride + 0] + src_ptr[stride + 1] + - src_ptr[stride + 2]) * (65536 / 6) >> 16; - dst_ptr[1] = (src_ptr[3] + src_ptr[4] + src_ptr[5] + - src_ptr[stride + 3] + src_ptr[stride + 4] + - src_ptr[stride + 5]) * (65536 / 6) >> 16; - dst_ptr[2] = (src_ptr[6] + src_ptr[7] + - src_ptr[stride + 6] + src_ptr[stride + 7]) * - (65536 / 4) >> 16; - src_ptr += 8; - dst_ptr += 3; - } -} - -void ScaleRowDown38_2_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width) { - intptr_t stride = src_stride; - int i; - assert((dst_width % 3 == 0) && (dst_width > 0)); - for (i = 0; i < dst_width; i += 3) { - dst_ptr[0] = (src_ptr[0] + src_ptr[1] + src_ptr[2] + - src_ptr[stride + 0] + src_ptr[stride + 1] + - src_ptr[stride + 2]) * (65536 / 6) >> 16; - dst_ptr[1] = (src_ptr[3] + src_ptr[4] + src_ptr[5] + - src_ptr[stride + 3] + src_ptr[stride + 4] + - src_ptr[stride + 5]) * (65536 / 6) >> 16; - dst_ptr[2] = (src_ptr[6] + src_ptr[7] + - src_ptr[stride + 6] + src_ptr[stride + 7]) * - (65536 / 4) >> 16; - src_ptr += 8; - dst_ptr += 3; - } -} - -void ScaleAddRow_C(const uint8* src_ptr, uint16* dst_ptr, int src_width) { - int x; - assert(src_width > 0); - for (x = 0; x < src_width - 1; x += 2) { - dst_ptr[0] += src_ptr[0]; - dst_ptr[1] += src_ptr[1]; - src_ptr += 2; - dst_ptr += 2; - } - if (src_width & 1) { - dst_ptr[0] += src_ptr[0]; - } -} - -void ScaleAddRow_16_C(const uint16* src_ptr, uint32* dst_ptr, int src_width) { - int x; - assert(src_width > 0); - for (x = 0; x < src_width - 1; x += 2) { - dst_ptr[0] += src_ptr[0]; - dst_ptr[1] += src_ptr[1]; - src_ptr += 2; - dst_ptr += 2; - } - if (src_width & 1) { - dst_ptr[0] += src_ptr[0]; - } -} - -void ScaleARGBRowDown2_C(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - const uint32* src = (const uint32*)(src_argb); - uint32* dst = (uint32*)(dst_argb); - - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = src[1]; - dst[1] = src[3]; - src += 4; - dst += 2; - } - if (dst_width & 1) { - dst[0] = src[1]; - } -} - -void ScaleARGBRowDown2Linear_C(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - int x; - for (x = 0; x < dst_width; ++x) { - dst_argb[0] = (src_argb[0] + src_argb[4] + 1) >> 1; - dst_argb[1] = (src_argb[1] + src_argb[5] + 1) >> 1; - dst_argb[2] = (src_argb[2] + src_argb[6] + 1) >> 1; - dst_argb[3] = (src_argb[3] + src_argb[7] + 1) >> 1; - src_argb += 8; - dst_argb += 4; - } -} - -void ScaleARGBRowDown2Box_C(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - int x; - for (x = 0; x < dst_width; ++x) { - dst_argb[0] = (src_argb[0] + src_argb[4] + - src_argb[src_stride] + src_argb[src_stride + 4] + 2) >> 2; - dst_argb[1] = (src_argb[1] + src_argb[5] + - src_argb[src_stride + 1] + src_argb[src_stride + 5] + 2) >> 2; - dst_argb[2] = (src_argb[2] + src_argb[6] + - src_argb[src_stride + 2] + src_argb[src_stride + 6] + 2) >> 2; - dst_argb[3] = (src_argb[3] + src_argb[7] + - src_argb[src_stride + 3] + src_argb[src_stride + 7] + 2) >> 2; - src_argb += 8; - dst_argb += 4; - } -} - -void ScaleARGBRowDownEven_C(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width) { - const uint32* src = (const uint32*)(src_argb); - uint32* dst = (uint32*)(dst_argb); - - int x; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = src[0]; - dst[1] = src[src_stepx]; - src += src_stepx * 2; - dst += 2; - } - if (dst_width & 1) { - dst[0] = src[0]; - } -} - -void ScaleARGBRowDownEvenBox_C(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width) { - int x; - for (x = 0; x < dst_width; ++x) { - dst_argb[0] = (src_argb[0] + src_argb[4] + - src_argb[src_stride] + src_argb[src_stride + 4] + 2) >> 2; - dst_argb[1] = (src_argb[1] + src_argb[5] + - src_argb[src_stride + 1] + src_argb[src_stride + 5] + 2) >> 2; - dst_argb[2] = (src_argb[2] + src_argb[6] + - src_argb[src_stride + 2] + src_argb[src_stride + 6] + 2) >> 2; - dst_argb[3] = (src_argb[3] + src_argb[7] + - src_argb[src_stride + 3] + src_argb[src_stride + 7] + 2) >> 2; - src_argb += src_stepx * 4; - dst_argb += 4; - } -} - -// Scales a single row of pixels using point sampling. -void ScaleARGBCols_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - const uint32* src = (const uint32*)(src_argb); - uint32* dst = (uint32*)(dst_argb); - int j; - for (j = 0; j < dst_width - 1; j += 2) { - dst[0] = src[x >> 16]; - x += dx; - dst[1] = src[x >> 16]; - x += dx; - dst += 2; - } - if (dst_width & 1) { - dst[0] = src[x >> 16]; - } -} - -void ScaleARGBCols64_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x32, int dx) { - int64 x = (int64)(x32); - const uint32* src = (const uint32*)(src_argb); - uint32* dst = (uint32*)(dst_argb); - int j; - for (j = 0; j < dst_width - 1; j += 2) { - dst[0] = src[x >> 16]; - x += dx; - dst[1] = src[x >> 16]; - x += dx; - dst += 2; - } - if (dst_width & 1) { - dst[0] = src[x >> 16]; - } -} - -// Scales a single row of pixels up by 2x using point sampling. -void ScaleARGBColsUp2_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - const uint32* src = (const uint32*)(src_argb); - uint32* dst = (uint32*)(dst_argb); - int j; - for (j = 0; j < dst_width - 1; j += 2) { - dst[1] = dst[0] = src[0]; - src += 1; - dst += 2; - } - if (dst_width & 1) { - dst[0] = src[0]; - } -} - -// Mimics SSSE3 blender -#define BLENDER1(a, b, f) ((a) * (0x7f ^ f) + (b) * f) >> 7 -#define BLENDERC(a, b, f, s) (uint32)( \ - BLENDER1(((a) >> s) & 255, ((b) >> s) & 255, f) << s) -#define BLENDER(a, b, f) \ - BLENDERC(a, b, f, 24) | BLENDERC(a, b, f, 16) | \ - BLENDERC(a, b, f, 8) | BLENDERC(a, b, f, 0) - -void ScaleARGBFilterCols_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - const uint32* src = (const uint32*)(src_argb); - uint32* dst = (uint32*)(dst_argb); - int j; - for (j = 0; j < dst_width - 1; j += 2) { - int xi = x >> 16; - int xf = (x >> 9) & 0x7f; - uint32 a = src[xi]; - uint32 b = src[xi + 1]; - dst[0] = BLENDER(a, b, xf); - x += dx; - xi = x >> 16; - xf = (x >> 9) & 0x7f; - a = src[xi]; - b = src[xi + 1]; - dst[1] = BLENDER(a, b, xf); - x += dx; - dst += 2; - } - if (dst_width & 1) { - int xi = x >> 16; - int xf = (x >> 9) & 0x7f; - uint32 a = src[xi]; - uint32 b = src[xi + 1]; - dst[0] = BLENDER(a, b, xf); - } -} - -void ScaleARGBFilterCols64_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x32, int dx) { - int64 x = (int64)(x32); - const uint32* src = (const uint32*)(src_argb); - uint32* dst = (uint32*)(dst_argb); - int j; - for (j = 0; j < dst_width - 1; j += 2) { - int64 xi = x >> 16; - int xf = (x >> 9) & 0x7f; - uint32 a = src[xi]; - uint32 b = src[xi + 1]; - dst[0] = BLENDER(a, b, xf); - x += dx; - xi = x >> 16; - xf = (x >> 9) & 0x7f; - a = src[xi]; - b = src[xi + 1]; - dst[1] = BLENDER(a, b, xf); - x += dx; - dst += 2; - } - if (dst_width & 1) { - int64 xi = x >> 16; - int xf = (x >> 9) & 0x7f; - uint32 a = src[xi]; - uint32 b = src[xi + 1]; - dst[0] = BLENDER(a, b, xf); - } -} -#undef BLENDER1 -#undef BLENDERC -#undef BLENDER - -// Scale plane vertically with bilinear interpolation. -void ScalePlaneVertical(int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int y, int dy, - int bpp, enum FilterMode filtering) { - // TODO(fbarchard): Allow higher bpp. - int dst_width_bytes = dst_width * bpp; - void (*InterpolateRow)(uint8* dst_argb, const uint8* src_argb, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_C; - const int max_y = (src_height > 1) ? ((src_height - 1) << 16) - 1 : 0; - int j; - assert(bpp >= 1 && bpp <= 4); - assert(src_height != 0); - assert(dst_width > 0); - assert(dst_height > 0); - src_argb += (x >> 16) * bpp; -#if defined(HAS_INTERPOLATEROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_SSSE3; - if (IS_ALIGNED(dst_width_bytes, 16)) { - InterpolateRow = InterpolateRow_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_AVX2; - if (IS_ALIGNED(dst_width_bytes, 32)) { - InterpolateRow = InterpolateRow_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_NEON; - if (IS_ALIGNED(dst_width_bytes, 16)) { - InterpolateRow = InterpolateRow_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(src_argb, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride, 4)) { - InterpolateRow = InterpolateRow_Any_DSPR2; - if (IS_ALIGNED(dst_width_bytes, 4)) { - InterpolateRow = InterpolateRow_DSPR2; - } - } -#endif - for (j = 0; j < dst_height; ++j) { - int yi; - int yf; - if (y > max_y) { - y = max_y; - } - yi = y >> 16; - yf = filtering ? ((y >> 8) & 255) : 0; - InterpolateRow(dst_argb, src_argb + yi * src_stride, - src_stride, dst_width_bytes, yf); - dst_argb += dst_stride; - y += dy; - } -} -void ScalePlaneVertical_16(int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_argb, uint16* dst_argb, - int x, int y, int dy, - int wpp, enum FilterMode filtering) { - // TODO(fbarchard): Allow higher wpp. - int dst_width_words = dst_width * wpp; - void (*InterpolateRow)(uint16* dst_argb, const uint16* src_argb, - ptrdiff_t src_stride, int dst_width, int source_y_fraction) = - InterpolateRow_16_C; - const int max_y = (src_height > 1) ? ((src_height - 1) << 16) - 1 : 0; - int j; - assert(wpp >= 1 && wpp <= 2); - assert(src_height != 0); - assert(dst_width > 0); - assert(dst_height > 0); - src_argb += (x >> 16) * wpp; -#if defined(HAS_INTERPOLATEROW_16_SSE2) - if (TestCpuFlag(kCpuHasSSE2)) { - InterpolateRow = InterpolateRow_Any_16_SSE2; - if (IS_ALIGNED(dst_width_bytes, 16)) { - InterpolateRow = InterpolateRow_16_SSE2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - InterpolateRow = InterpolateRow_Any_16_SSSE3; - if (IS_ALIGNED(dst_width_bytes, 16)) { - InterpolateRow = InterpolateRow_16_SSSE3; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_AVX2) - if (TestCpuFlag(kCpuHasAVX2)) { - InterpolateRow = InterpolateRow_Any_16_AVX2; - if (IS_ALIGNED(dst_width_bytes, 32)) { - InterpolateRow = InterpolateRow_16_AVX2; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - InterpolateRow = InterpolateRow_Any_16_NEON; - if (IS_ALIGNED(dst_width_bytes, 16)) { - InterpolateRow = InterpolateRow_16_NEON; - } - } -#endif -#if defined(HAS_INTERPOLATEROW_16_DSPR2) - if (TestCpuFlag(kCpuHasDSPR2) && - IS_ALIGNED(src_argb, 4) && IS_ALIGNED(src_stride, 4) && - IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride, 4)) { - InterpolateRow = InterpolateRow_Any_16_DSPR2; - if (IS_ALIGNED(dst_width_bytes, 4)) { - InterpolateRow = InterpolateRow_16_DSPR2; - } - } -#endif - for (j = 0; j < dst_height; ++j) { - int yi; - int yf; - if (y > max_y) { - y = max_y; - } - yi = y >> 16; - yf = filtering ? ((y >> 8) & 255) : 0; - InterpolateRow(dst_argb, src_argb + yi * src_stride, - src_stride, dst_width_words, yf); - dst_argb += dst_stride; - y += dy; - } -} - -// Simplify the filtering based on scale factors. -enum FilterMode ScaleFilterReduce(int src_width, int src_height, - int dst_width, int dst_height, - enum FilterMode filtering) { - if (src_width < 0) { - src_width = -src_width; - } - if (src_height < 0) { - src_height = -src_height; - } - if (filtering == kFilterBox) { - // If scaling both axis to 0.5 or larger, switch from Box to Bilinear. - if (dst_width * 2 >= src_width && dst_height * 2 >= src_height) { - filtering = kFilterBilinear; - } - } - if (filtering == kFilterBilinear) { - if (src_height == 1) { - filtering = kFilterLinear; - } - // TODO(fbarchard): Detect any odd scale factor and reduce to Linear. - if (dst_height == src_height || dst_height * 3 == src_height) { - filtering = kFilterLinear; - } - // TODO(fbarchard): Remove 1 pixel wide filter restriction, which is to - // avoid reading 2 pixels horizontally that causes memory exception. - if (src_width == 1) { - filtering = kFilterNone; - } - } - if (filtering == kFilterLinear) { - if (src_width == 1) { - filtering = kFilterNone; - } - // TODO(fbarchard): Detect any odd scale factor and reduce to None. - if (dst_width == src_width || dst_width * 3 == src_width) { - filtering = kFilterNone; - } - } - return filtering; -} - -// Divide num by div and return as 16.16 fixed point result. -int FixedDiv_C(int num, int div) { - return (int)(((int64)(num) << 16) / div); -} - -// Divide num by div and return as 16.16 fixed point result. -int FixedDiv1_C(int num, int div) { - return (int)((((int64)(num) << 16) - 0x00010001) / - (div - 1)); -} - -#define CENTERSTART(dx, s) (dx < 0) ? -((-dx >> 1) + s) : ((dx >> 1) + s) - -// Compute slope values for stepping. -void ScaleSlope(int src_width, int src_height, - int dst_width, int dst_height, - enum FilterMode filtering, - int* x, int* y, int* dx, int* dy) { - assert(x != NULL); - assert(y != NULL); - assert(dx != NULL); - assert(dy != NULL); - assert(src_width != 0); - assert(src_height != 0); - assert(dst_width > 0); - assert(dst_height > 0); - // Check for 1 pixel and avoid FixedDiv overflow. - if (dst_width == 1 && src_width >= 32768) { - dst_width = src_width; - } - if (dst_height == 1 && src_height >= 32768) { - dst_height = src_height; - } - if (filtering == kFilterBox) { - // Scale step for point sampling duplicates all pixels equally. - *dx = FixedDiv(Abs(src_width), dst_width); - *dy = FixedDiv(src_height, dst_height); - *x = 0; - *y = 0; - } else if (filtering == kFilterBilinear) { - // Scale step for bilinear sampling renders last pixel once for upsample. - if (dst_width <= Abs(src_width)) { - *dx = FixedDiv(Abs(src_width), dst_width); - *x = CENTERSTART(*dx, -32768); // Subtract 0.5 (32768) to center filter. - } else if (dst_width > 1) { - *dx = FixedDiv1(Abs(src_width), dst_width); - *x = 0; - } - if (dst_height <= src_height) { - *dy = FixedDiv(src_height, dst_height); - *y = CENTERSTART(*dy, -32768); // Subtract 0.5 (32768) to center filter. - } else if (dst_height > 1) { - *dy = FixedDiv1(src_height, dst_height); - *y = 0; - } - } else if (filtering == kFilterLinear) { - // Scale step for bilinear sampling renders last pixel once for upsample. - if (dst_width <= Abs(src_width)) { - *dx = FixedDiv(Abs(src_width), dst_width); - *x = CENTERSTART(*dx, -32768); // Subtract 0.5 (32768) to center filter. - } else if (dst_width > 1) { - *dx = FixedDiv1(Abs(src_width), dst_width); - *x = 0; - } - *dy = FixedDiv(src_height, dst_height); - *y = *dy >> 1; - } else { - // Scale step for point sampling duplicates all pixels equally. - *dx = FixedDiv(Abs(src_width), dst_width); - *dy = FixedDiv(src_height, dst_height); - *x = CENTERSTART(*dx, 0); - *y = CENTERSTART(*dy, 0); - } - // Negative src_width means horizontally mirror. - if (src_width < 0) { - *x += (dst_width - 1) * *dx; - *dx = -*dx; - // src_width = -src_width; // Caller must do this. - } -} -#undef CENTERSTART - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale_gcc.cc b/telegramgallery/src/main/cpp/libyuv/source/scale_gcc.cc deleted file mode 100644 index 400f2fd..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale_gcc.cc +++ /dev/null @@ -1,1292 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" -#include "libyuv/scale_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC x86 and x64. -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) && !defined(_MSC_VER))) - -// Offsets for source bytes 0 to 9 -static uvec8 kShuf0 = - { 0, 1, 3, 4, 5, 7, 8, 9, 128, 128, 128, 128, 128, 128, 128, 128 }; - -// Offsets for source bytes 11 to 20 with 8 subtracted = 3 to 12. -static uvec8 kShuf1 = - { 3, 4, 5, 7, 8, 9, 11, 12, 128, 128, 128, 128, 128, 128, 128, 128 }; - -// Offsets for source bytes 21 to 31 with 16 subtracted = 5 to 31. -static uvec8 kShuf2 = - { 5, 7, 8, 9, 11, 12, 13, 15, 128, 128, 128, 128, 128, 128, 128, 128 }; - -// Offsets for source bytes 0 to 10 -static uvec8 kShuf01 = - { 0, 1, 1, 2, 2, 3, 4, 5, 5, 6, 6, 7, 8, 9, 9, 10 }; - -// Offsets for source bytes 10 to 21 with 8 subtracted = 3 to 13. -static uvec8 kShuf11 = - { 2, 3, 4, 5, 5, 6, 6, 7, 8, 9, 9, 10, 10, 11, 12, 13 }; - -// Offsets for source bytes 21 to 31 with 16 subtracted = 5 to 31. -static uvec8 kShuf21 = - { 5, 6, 6, 7, 8, 9, 9, 10, 10, 11, 12, 13, 13, 14, 14, 15 }; - -// Coefficients for source bytes 0 to 10 -static uvec8 kMadd01 = - { 3, 1, 2, 2, 1, 3, 3, 1, 2, 2, 1, 3, 3, 1, 2, 2 }; - -// Coefficients for source bytes 10 to 21 -static uvec8 kMadd11 = - { 1, 3, 3, 1, 2, 2, 1, 3, 3, 1, 2, 2, 1, 3, 3, 1 }; - -// Coefficients for source bytes 21 to 31 -static uvec8 kMadd21 = - { 2, 2, 1, 3, 3, 1, 2, 2, 1, 3, 3, 1, 2, 2, 1, 3 }; - -// Coefficients for source bytes 21 to 31 -static vec16 kRound34 = - { 2, 2, 2, 2, 2, 2, 2, 2 }; - -static uvec8 kShuf38a = - { 0, 3, 6, 8, 11, 14, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }; - -static uvec8 kShuf38b = - { 128, 128, 128, 128, 128, 128, 0, 3, 6, 8, 11, 14, 128, 128, 128, 128 }; - -// Arrange words 0,3,6 into 0,1,2 -static uvec8 kShufAc = - { 0, 1, 6, 7, 12, 13, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }; - -// Arrange words 0,3,6 into 3,4,5 -static uvec8 kShufAc3 = - { 128, 128, 128, 128, 128, 128, 0, 1, 6, 7, 12, 13, 128, 128, 128, 128 }; - -// Scaling values for boxes of 3x3 and 2x3 -static uvec16 kScaleAc33 = - { 65536 / 9, 65536 / 9, 65536 / 6, 65536 / 9, 65536 / 9, 65536 / 6, 0, 0 }; - -// Arrange first value for pixels 0,1,2,3,4,5 -static uvec8 kShufAb0 = - { 0, 128, 3, 128, 6, 128, 8, 128, 11, 128, 14, 128, 128, 128, 128, 128 }; - -// Arrange second value for pixels 0,1,2,3,4,5 -static uvec8 kShufAb1 = - { 1, 128, 4, 128, 7, 128, 9, 128, 12, 128, 15, 128, 128, 128, 128, 128 }; - -// Arrange third value for pixels 0,1,2,3,4,5 -static uvec8 kShufAb2 = - { 2, 128, 5, 128, 128, 128, 10, 128, 13, 128, 128, 128, 128, 128, 128, 128 }; - -// Scaling values for boxes of 3x2 and 2x2 -static uvec16 kScaleAb2 = - { 65536 / 3, 65536 / 3, 65536 / 2, 65536 / 3, 65536 / 3, 65536 / 2, 0, 0 }; - -// GCC versions of row functions are verbatim conversions from Visual C. -// Generated using gcc disassembly on Visual C object file: -// objdump -D yuvscaler.obj >yuvscaler.txt - -void ScaleRowDown2_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "psrlw $0x8,%%xmm0 \n" - "psrlw $0x8,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1" - ); -} - -void ScaleRowDown2Linear_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "pcmpeqb %%xmm4,%%xmm4 \n" - "psrlw $0xf,%%xmm4 \n" - "packuswb %%xmm4,%%xmm4 \n" - "pxor %%xmm5,%%xmm5 \n" - - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10, 0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pavgw %%xmm5,%%xmm0 \n" - "pavgw %%xmm5,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1", "xmm4", "xmm5" - ); -} - -void ScaleRowDown2Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "pcmpeqb %%xmm4,%%xmm4 \n" - "psrlw $0xf,%%xmm4 \n" - "packuswb %%xmm4,%%xmm4 \n" - "pxor %%xmm5,%%xmm5 \n" - - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x00,0,3,1,xmm2) // movdqu (%0,%3,1),%%xmm2 - MEMOPREG(movdqu,0x10,0,3,1,xmm3) // movdqu 0x10(%0,%3,1),%%xmm3 - "lea " MEMLEA(0x20,0) ",%0 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "paddw %%xmm2,%%xmm0 \n" - "paddw %%xmm3,%%xmm1 \n" - "psrlw $0x1,%%xmm0 \n" - "psrlw $0x1,%%xmm1 \n" - "pavgw %%xmm5,%%xmm0 \n" - "pavgw %%xmm5,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"((intptr_t)(src_stride)) // %3 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} - -#ifdef HAS_SCALEROWDOWN2_AVX2 -void ScaleRowDown2_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpsrlw $0x8,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1" - ); -} - -void ScaleRowDown2Linear_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "vpcmpeqb %%ymm4,%%ymm4,%%ymm4 \n" - "vpsrlw $0xf,%%ymm4,%%ymm4 \n" - "vpackuswb %%ymm4,%%ymm4,%%ymm4 \n" - "vpxor %%ymm5,%%ymm5,%%ymm5 \n" - - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20, 0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpmaddubsw %%ymm4,%%ymm0,%%ymm0 \n" - "vpmaddubsw %%ymm4,%%ymm1,%%ymm1 \n" - "vpavgw %%ymm5,%%ymm0,%%ymm0 \n" - "vpavgw %%ymm5,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1", "xmm4", "xmm5" - ); -} - -void ScaleRowDown2Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "vpcmpeqb %%ymm4,%%ymm4,%%ymm4 \n" - "vpsrlw $0xf,%%ymm4,%%ymm4 \n" - "vpackuswb %%ymm4,%%ymm4,%%ymm4 \n" - "vpxor %%ymm5,%%ymm5,%%ymm5 \n" - - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - MEMOPREG(vmovdqu,0x00,0,3,1,ymm2) // vmovdqu (%0,%3,1),%%ymm2 - MEMOPREG(vmovdqu,0x20,0,3,1,ymm3) // vmovdqu 0x20(%0,%3,1),%%ymm3 - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpmaddubsw %%ymm4,%%ymm0,%%ymm0 \n" - "vpmaddubsw %%ymm4,%%ymm1,%%ymm1 \n" - "vpmaddubsw %%ymm4,%%ymm2,%%ymm2 \n" - "vpmaddubsw %%ymm4,%%ymm3,%%ymm3 \n" - "vpaddw %%ymm2,%%ymm0,%%ymm0 \n" - "vpaddw %%ymm3,%%ymm1,%%ymm1 \n" - "vpsrlw $0x1,%%ymm0,%%ymm0 \n" - "vpsrlw $0x1,%%ymm1,%%ymm1 \n" - "vpavgw %%ymm5,%%ymm0,%%ymm0 \n" - "vpavgw %%ymm5,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"((intptr_t)(src_stride)) // %3 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} -#endif // HAS_SCALEROWDOWN2_AVX2 - -void ScaleRowDown4_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "pcmpeqb %%xmm5,%%xmm5 \n" - "psrld $0x18,%%xmm5 \n" - "pslld $0x10,%%xmm5 \n" - - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "pand %%xmm5,%%xmm0 \n" - "pand %%xmm5,%%xmm1 \n" - "packuswb %%xmm1,%%xmm0 \n" - "psrlw $0x8,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1", "xmm5" - ); -} - -void ScaleRowDown4Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - intptr_t stridex3; - asm volatile ( - "pcmpeqb %%xmm4,%%xmm4 \n" - "psrlw $0xf,%%xmm4 \n" - "movdqa %%xmm4,%%xmm5 \n" - "packuswb %%xmm4,%%xmm4 \n" - "psllw $0x3,%%xmm5 \n" - "lea " MEMLEA4(0x00,4,4,2) ",%3 \n" - - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x00,0,4,1,xmm2) // movdqu (%0,%4,1),%%xmm2 - MEMOPREG(movdqu,0x10,0,4,1,xmm3) // movdqu 0x10(%0,%4,1),%%xmm3 - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm1 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "paddw %%xmm2,%%xmm0 \n" - "paddw %%xmm3,%%xmm1 \n" - MEMOPREG(movdqu,0x00,0,4,2,xmm2) // movdqu (%0,%4,2),%%xmm2 - MEMOPREG(movdqu,0x10,0,4,2,xmm3) // movdqu 0x10(%0,%4,2),%%xmm3 - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "paddw %%xmm2,%%xmm0 \n" - "paddw %%xmm3,%%xmm1 \n" - MEMOPREG(movdqu,0x00,0,3,1,xmm2) // movdqu (%0,%3,1),%%xmm2 - MEMOPREG(movdqu,0x10,0,3,1,xmm3) // movdqu 0x10(%0,%3,1),%%xmm3 - "lea " MEMLEA(0x20,0) ",%0 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm4,%%xmm3 \n" - "paddw %%xmm2,%%xmm0 \n" - "paddw %%xmm3,%%xmm1 \n" - "phaddw %%xmm1,%%xmm0 \n" - "paddw %%xmm5,%%xmm0 \n" - "psrlw $0x4,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width), // %2 - "=&r"(stridex3) // %3 - : "r"((intptr_t)(src_stride)) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - - -#ifdef HAS_SCALEROWDOWN4_AVX2 -void ScaleRowDown4_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" - "vpsrld $0x18,%%ymm5,%%ymm5 \n" - "vpslld $0x10,%%ymm5,%%ymm5 \n" - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpand %%ymm5,%%ymm0,%%ymm0 \n" - "vpand %%ymm5,%%ymm1,%%ymm1 \n" - "vpackuswb %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpsrlw $0x8,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vmovdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1", "xmm5" - ); -} - -void ScaleRowDown4Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "vpcmpeqb %%ymm4,%%ymm4,%%ymm4 \n" - "vpsrlw $0xf,%%ymm4,%%ymm4 \n" - "vpsllw $0x3,%%ymm4,%%ymm5 \n" - "vpackuswb %%ymm4,%%ymm4,%%ymm4 \n" - - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" - "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" - MEMOPREG(vmovdqu,0x00,0,3,1,ymm2) // vmovdqu (%0,%3,1),%%ymm2 - MEMOPREG(vmovdqu,0x20,0,3,1,ymm3) // vmovdqu 0x20(%0,%3,1),%%ymm3 - "vpmaddubsw %%ymm4,%%ymm0,%%ymm0 \n" - "vpmaddubsw %%ymm4,%%ymm1,%%ymm1 \n" - "vpmaddubsw %%ymm4,%%ymm2,%%ymm2 \n" - "vpmaddubsw %%ymm4,%%ymm3,%%ymm3 \n" - "vpaddw %%ymm2,%%ymm0,%%ymm0 \n" - "vpaddw %%ymm3,%%ymm1,%%ymm1 \n" - MEMOPREG(vmovdqu,0x00,0,3,2,ymm2) // vmovdqu (%0,%3,2),%%ymm2 - MEMOPREG(vmovdqu,0x20,0,3,2,ymm3) // vmovdqu 0x20(%0,%3,2),%%ymm3 - "vpmaddubsw %%ymm4,%%ymm2,%%ymm2 \n" - "vpmaddubsw %%ymm4,%%ymm3,%%ymm3 \n" - "vpaddw %%ymm2,%%ymm0,%%ymm0 \n" - "vpaddw %%ymm3,%%ymm1,%%ymm1 \n" - MEMOPREG(vmovdqu,0x00,0,4,1,ymm2) // vmovdqu (%0,%4,1),%%ymm2 - MEMOPREG(vmovdqu,0x20,0,4,1,ymm3) // vmovdqu 0x20(%0,%4,1),%%ymm3 - "lea " MEMLEA(0x40,0) ",%0 \n" - "vpmaddubsw %%ymm4,%%ymm2,%%ymm2 \n" - "vpmaddubsw %%ymm4,%%ymm3,%%ymm3 \n" - "vpaddw %%ymm2,%%ymm0,%%ymm0 \n" - "vpaddw %%ymm3,%%ymm1,%%ymm1 \n" - "vphaddw %%ymm1,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vpaddw %%ymm5,%%ymm0,%%ymm0 \n" - "vpsrlw $0x4,%%ymm0,%%ymm0 \n" - "vpackuswb %%ymm0,%%ymm0,%%ymm0 \n" - "vpermq $0xd8,%%ymm0,%%ymm0 \n" - "vmovdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"((intptr_t)(src_stride)), // %3 - "r"((intptr_t)(src_stride * 3)) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} -#endif // HAS_SCALEROWDOWN4_AVX2 - -void ScaleRowDown34_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "movdqa %0,%%xmm3 \n" - "movdqa %1,%%xmm4 \n" - "movdqa %2,%%xmm5 \n" - : - : "m"(kShuf0), // %0 - "m"(kShuf1), // %1 - "m"(kShuf2) // %2 - ); - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm2 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "movdqa %%xmm2,%%xmm1 \n" - "palignr $0x8,%%xmm0,%%xmm1 \n" - "pshufb %%xmm3,%%xmm0 \n" - "pshufb %%xmm4,%%xmm1 \n" - "pshufb %%xmm5,%%xmm2 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "movq %%xmm1," MEMACCESS2(0x8,1) " \n" - "movq %%xmm2," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x18,1) ",%1 \n" - "sub $0x18,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" - ); -} - -void ScaleRowDown34_1_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "movdqa %0,%%xmm2 \n" // kShuf01 - "movdqa %1,%%xmm3 \n" // kShuf11 - "movdqa %2,%%xmm4 \n" // kShuf21 - : - : "m"(kShuf01), // %0 - "m"(kShuf11), // %1 - "m"(kShuf21) // %2 - ); - asm volatile ( - "movdqa %0,%%xmm5 \n" // kMadd01 - "movdqa %1,%%xmm0 \n" // kMadd11 - "movdqa %2,%%xmm1 \n" // kRound34 - : - : "m"(kMadd01), // %0 - "m"(kMadd11), // %1 - "m"(kRound34) // %2 - ); - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x00,0,3,1,xmm7) // movdqu (%0,%3),%%xmm7 - "pavgb %%xmm7,%%xmm6 \n" - "pshufb %%xmm2,%%xmm6 \n" - "pmaddubsw %%xmm5,%%xmm6 \n" - "paddsw %%xmm1,%%xmm6 \n" - "psrlw $0x2,%%xmm6 \n" - "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6," MEMACCESS(1) " \n" - "movdqu " MEMACCESS2(0x8,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x8,0,3,1,xmm7) // movdqu 0x8(%0,%3),%%xmm7 - "pavgb %%xmm7,%%xmm6 \n" - "pshufb %%xmm3,%%xmm6 \n" - "pmaddubsw %%xmm0,%%xmm6 \n" - "paddsw %%xmm1,%%xmm6 \n" - "psrlw $0x2,%%xmm6 \n" - "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6," MEMACCESS2(0x8,1) " \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x10,0,3,1,xmm7) // movdqu 0x10(%0,%3),%%xmm7 - "lea " MEMLEA(0x20,0) ",%0 \n" - "pavgb %%xmm7,%%xmm6 \n" - "pshufb %%xmm4,%%xmm6 \n" - "pmaddubsw %4,%%xmm6 \n" - "paddsw %%xmm1,%%xmm6 \n" - "psrlw $0x2,%%xmm6 \n" - "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x18,1) ",%1 \n" - "sub $0x18,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"((intptr_t)(src_stride)), // %3 - "m"(kMadd21) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} - -void ScaleRowDown34_0_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "movdqa %0,%%xmm2 \n" // kShuf01 - "movdqa %1,%%xmm3 \n" // kShuf11 - "movdqa %2,%%xmm4 \n" // kShuf21 - : - : "m"(kShuf01), // %0 - "m"(kShuf11), // %1 - "m"(kShuf21) // %2 - ); - asm volatile ( - "movdqa %0,%%xmm5 \n" // kMadd01 - "movdqa %1,%%xmm0 \n" // kMadd11 - "movdqa %2,%%xmm1 \n" // kRound34 - : - : "m"(kMadd01), // %0 - "m"(kMadd11), // %1 - "m"(kRound34) // %2 - ); - - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x00,0,3,1,xmm7) // movdqu (%0,%3,1),%%xmm7 - "pavgb %%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm6 \n" - "pshufb %%xmm2,%%xmm6 \n" - "pmaddubsw %%xmm5,%%xmm6 \n" - "paddsw %%xmm1,%%xmm6 \n" - "psrlw $0x2,%%xmm6 \n" - "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6," MEMACCESS(1) " \n" - "movdqu " MEMACCESS2(0x8,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x8,0,3,1,xmm7) // movdqu 0x8(%0,%3,1),%%xmm7 - "pavgb %%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm6 \n" - "pshufb %%xmm3,%%xmm6 \n" - "pmaddubsw %%xmm0,%%xmm6 \n" - "paddsw %%xmm1,%%xmm6 \n" - "psrlw $0x2,%%xmm6 \n" - "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6," MEMACCESS2(0x8,1) " \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm6 \n" - MEMOPREG(movdqu,0x10,0,3,1,xmm7) // movdqu 0x10(%0,%3,1),%%xmm7 - "lea " MEMLEA(0x20,0) ",%0 \n" - "pavgb %%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm6 \n" - "pshufb %%xmm4,%%xmm6 \n" - "pmaddubsw %4,%%xmm6 \n" - "paddsw %%xmm1,%%xmm6 \n" - "psrlw $0x2,%%xmm6 \n" - "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x18,1) ",%1 \n" - "sub $0x18,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"((intptr_t)(src_stride)), // %3 - "m"(kMadd21) // %4 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} - -void ScaleRowDown38_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "movdqa %3,%%xmm4 \n" - "movdqa %4,%%xmm5 \n" - - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "pshufb %%xmm4,%%xmm0 \n" - "pshufb %%xmm5,%%xmm1 \n" - "paddusb %%xmm1,%%xmm0 \n" - "movq %%xmm0," MEMACCESS(1) " \n" - "movhlps %%xmm0,%%xmm1 \n" - "movd %%xmm1," MEMACCESS2(0x8,1) " \n" - "lea " MEMLEA(0xc,1) ",%1 \n" - "sub $0xc,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "m"(kShuf38a), // %3 - "m"(kShuf38b) // %4 - : "memory", "cc", "xmm0", "xmm1", "xmm4", "xmm5" - ); -} - -void ScaleRowDown38_2_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "movdqa %0,%%xmm2 \n" - "movdqa %1,%%xmm3 \n" - "movdqa %2,%%xmm4 \n" - "movdqa %3,%%xmm5 \n" - : - : "m"(kShufAb0), // %0 - "m"(kShufAb1), // %1 - "m"(kShufAb2), // %2 - "m"(kScaleAb2) // %3 - ); - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,3,1,xmm1) // movdqu (%0,%3,1),%%xmm1 - "lea " MEMLEA(0x10,0) ",%0 \n" - "pavgb %%xmm1,%%xmm0 \n" - "movdqa %%xmm0,%%xmm1 \n" - "pshufb %%xmm2,%%xmm1 \n" - "movdqa %%xmm0,%%xmm6 \n" - "pshufb %%xmm3,%%xmm6 \n" - "paddusw %%xmm6,%%xmm1 \n" - "pshufb %%xmm4,%%xmm0 \n" - "paddusw %%xmm0,%%xmm1 \n" - "pmulhuw %%xmm5,%%xmm1 \n" - "packuswb %%xmm1,%%xmm1 \n" - "movd %%xmm1," MEMACCESS(1) " \n" - "psrlq $0x10,%%xmm1 \n" - "movd %%xmm1," MEMACCESS2(0x2,1) " \n" - "lea " MEMLEA(0x6,1) ",%1 \n" - "sub $0x6,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"((intptr_t)(src_stride)) // %3 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} - -void ScaleRowDown38_3_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "movdqa %0,%%xmm2 \n" - "movdqa %1,%%xmm3 \n" - "movdqa %2,%%xmm4 \n" - "pxor %%xmm5,%%xmm5 \n" - : - : "m"(kShufAc), // %0 - "m"(kShufAc3), // %1 - "m"(kScaleAc33) // %2 - ); - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movdqu,0x00,0,3,1,xmm6) // movdqu (%0,%3,1),%%xmm6 - "movhlps %%xmm0,%%xmm1 \n" - "movhlps %%xmm6,%%xmm7 \n" - "punpcklbw %%xmm5,%%xmm0 \n" - "punpcklbw %%xmm5,%%xmm1 \n" - "punpcklbw %%xmm5,%%xmm6 \n" - "punpcklbw %%xmm5,%%xmm7 \n" - "paddusw %%xmm6,%%xmm0 \n" - "paddusw %%xmm7,%%xmm1 \n" - MEMOPREG(movdqu,0x00,0,3,2,xmm6) // movdqu (%0,%3,2),%%xmm6 - "lea " MEMLEA(0x10,0) ",%0 \n" - "movhlps %%xmm6,%%xmm7 \n" - "punpcklbw %%xmm5,%%xmm6 \n" - "punpcklbw %%xmm5,%%xmm7 \n" - "paddusw %%xmm6,%%xmm0 \n" - "paddusw %%xmm7,%%xmm1 \n" - "movdqa %%xmm0,%%xmm6 \n" - "psrldq $0x2,%%xmm0 \n" - "paddusw %%xmm0,%%xmm6 \n" - "psrldq $0x2,%%xmm0 \n" - "paddusw %%xmm0,%%xmm6 \n" - "pshufb %%xmm2,%%xmm6 \n" - "movdqa %%xmm1,%%xmm7 \n" - "psrldq $0x2,%%xmm1 \n" - "paddusw %%xmm1,%%xmm7 \n" - "psrldq $0x2,%%xmm1 \n" - "paddusw %%xmm1,%%xmm7 \n" - "pshufb %%xmm3,%%xmm7 \n" - "paddusw %%xmm7,%%xmm6 \n" - "pmulhuw %%xmm4,%%xmm6 \n" - "packuswb %%xmm6,%%xmm6 \n" - "movd %%xmm6," MEMACCESS(1) " \n" - "psrlq $0x10,%%xmm6 \n" - "movd %%xmm6," MEMACCESS2(0x2,1) " \n" - "lea " MEMLEA(0x6,1) ",%1 \n" - "sub $0x6,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"((intptr_t)(src_stride)) // %3 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" - ); -} - -// Reads 16xN bytes and produces 16 shorts at a time. -void ScaleAddRow_SSE2(const uint8* src_ptr, uint16* dst_ptr, int src_width) { - asm volatile ( - "pxor %%xmm5,%%xmm5 \n" - - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm3 \n" - "lea " MEMLEA(0x10,0) ",%0 \n" // src_ptr += 16 - "movdqu " MEMACCESS(1) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,1) ",%%xmm1 \n" - "movdqa %%xmm3,%%xmm2 \n" - "punpcklbw %%xmm5,%%xmm2 \n" - "punpckhbw %%xmm5,%%xmm3 \n" - "paddusw %%xmm2,%%xmm0 \n" - "paddusw %%xmm3,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,1) " \n" - "lea " MEMLEA(0x20,1) ",%1 \n" - "sub $0x10,%2 \n" - "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(src_width) // %2 - : - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} - - -#ifdef HAS_SCALEADDROW_AVX2 -// Reads 32 bytes and accumulates to 32 shorts at a time. -void ScaleAddRow_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width) { - asm volatile ( - "vpxor %%ymm5,%%ymm5,%%ymm5 \n" - - LABELALIGN - "1: \n" - "vmovdqu " MEMACCESS(0) ",%%ymm3 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" // src_ptr += 32 - "vpermq $0xd8,%%ymm3,%%ymm3 \n" - "vpunpcklbw %%ymm5,%%ymm3,%%ymm2 \n" - "vpunpckhbw %%ymm5,%%ymm3,%%ymm3 \n" - "vpaddusw " MEMACCESS(1) ",%%ymm2,%%ymm0 \n" - "vpaddusw " MEMACCESS2(0x20,1) ",%%ymm3,%%ymm1 \n" - "vmovdqu %%ymm0," MEMACCESS(1) " \n" - "vmovdqu %%ymm1," MEMACCESS2(0x20,1) " \n" - "lea " MEMLEA(0x40,1) ",%1 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - "vzeroupper \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(src_width) // %2 - : - : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm5" - ); -} -#endif // HAS_SCALEADDROW_AVX2 - -// Bilinear column filtering. SSSE3 version. -void ScaleFilterCols_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - intptr_t x0, x1, temp_pixel; - asm volatile ( - "movd %6,%%xmm2 \n" - "movd %7,%%xmm3 \n" - "movl $0x04040000,%k2 \n" - "movd %k2,%%xmm5 \n" - "pcmpeqb %%xmm6,%%xmm6 \n" - "psrlw $0x9,%%xmm6 \n" - "pextrw $0x1,%%xmm2,%k3 \n" - "subl $0x2,%5 \n" - "jl 29f \n" - "movdqa %%xmm2,%%xmm0 \n" - "paddd %%xmm3,%%xmm0 \n" - "punpckldq %%xmm0,%%xmm2 \n" - "punpckldq %%xmm3,%%xmm3 \n" - "paddd %%xmm3,%%xmm3 \n" - "pextrw $0x3,%%xmm2,%k4 \n" - - LABELALIGN - "2: \n" - "movdqa %%xmm2,%%xmm1 \n" - "paddd %%xmm3,%%xmm2 \n" - MEMOPARG(movzwl,0x00,1,3,1,k2) // movzwl (%1,%3,1),%k2 - "movd %k2,%%xmm0 \n" - "psrlw $0x9,%%xmm1 \n" - MEMOPARG(movzwl,0x00,1,4,1,k2) // movzwl (%1,%4,1),%k2 - "movd %k2,%%xmm4 \n" - "pshufb %%xmm5,%%xmm1 \n" - "punpcklwd %%xmm4,%%xmm0 \n" - "pxor %%xmm6,%%xmm1 \n" - "pmaddubsw %%xmm1,%%xmm0 \n" - "pextrw $0x1,%%xmm2,%k3 \n" - "pextrw $0x3,%%xmm2,%k4 \n" - "psrlw $0x7,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movd %%xmm0,%k2 \n" - "mov %w2," MEMACCESS(0) " \n" - "lea " MEMLEA(0x2,0) ",%0 \n" - "sub $0x2,%5 \n" - "jge 2b \n" - - LABELALIGN - "29: \n" - "addl $0x1,%5 \n" - "jl 99f \n" - MEMOPARG(movzwl,0x00,1,3,1,k2) // movzwl (%1,%3,1),%k2 - "movd %k2,%%xmm0 \n" - "psrlw $0x9,%%xmm2 \n" - "pshufb %%xmm5,%%xmm2 \n" - "pxor %%xmm6,%%xmm2 \n" - "pmaddubsw %%xmm2,%%xmm0 \n" - "psrlw $0x7,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movd %%xmm0,%k2 \n" - "mov %b2," MEMACCESS(0) " \n" - "99: \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "=&a"(temp_pixel), // %2 - "=&r"(x0), // %3 - "=&r"(x1), // %4 - "+rm"(dst_width) // %5 - : "rm"(x), // %6 - "rm"(dx) // %7 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} - -// Reads 4 pixels, duplicates them and writes 8 pixels. -// Alignment requirement: src_argb 16 byte aligned, dst_argb 16 byte aligned. -void ScaleColsUp2_SSE2(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(1) ",%%xmm0 \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpcklbw %%xmm0,%%xmm0 \n" - "punpckhbw %%xmm1,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(0) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,0) " \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "sub $0x20,%2 \n" - "jg 1b \n" - - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1" - ); -} - -void ScaleARGBRowDown2_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "shufps $0xdd,%%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1" - ); -} - -void ScaleARGBRowDown2Linear_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "movdqa %%xmm0,%%xmm2 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm2 \n" - "pavgb %%xmm2,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", "xmm0", "xmm1" - ); -} - -void ScaleARGBRowDown2Box_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - MEMOPREG(movdqu,0x00,0,3,1,xmm2) // movdqu (%0,%3,1),%%xmm2 - MEMOPREG(movdqu,0x10,0,3,1,xmm3) // movdqu 0x10(%0,%3,1),%%xmm3 - "lea " MEMLEA(0x20,0) ",%0 \n" - "pavgb %%xmm2,%%xmm0 \n" - "pavgb %%xmm3,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm2 \n" - "pavgb %%xmm2,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(1) " \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "sub $0x4,%2 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(dst_width) // %2 - : "r"((intptr_t)(src_stride)) // %3 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3" - ); -} - -// Reads 4 pixels at a time. -// Alignment requirement: dst_argb 16 byte aligned. -void ScaleARGBRowDownEven_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, uint8* dst_argb, int dst_width) { - intptr_t src_stepx_x4 = (intptr_t)(src_stepx); - intptr_t src_stepx_x12; - asm volatile ( - "lea " MEMLEA3(0x00,1,4) ",%1 \n" - "lea " MEMLEA4(0x00,1,1,2) ",%4 \n" - LABELALIGN - "1: \n" - "movd " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movd,0x00,0,1,1,xmm1) // movd (%0,%1,1),%%xmm1 - "punpckldq %%xmm1,%%xmm0 \n" - MEMOPREG(movd,0x00,0,1,2,xmm2) // movd (%0,%1,2),%%xmm2 - MEMOPREG(movd,0x00,0,4,1,xmm3) // movd (%0,%4,1),%%xmm3 - "lea " MEMLEA4(0x00,0,1,4) ",%0 \n" - "punpckldq %%xmm3,%%xmm2 \n" - "punpcklqdq %%xmm2,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%3 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_stepx_x4), // %1 - "+r"(dst_argb), // %2 - "+r"(dst_width), // %3 - "=&r"(src_stepx_x12) // %4 - :: "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3" - ); -} - -// Blends four 2x2 to 4x1. -// Alignment requirement: dst_argb 16 byte aligned. -void ScaleARGBRowDownEvenBox_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, int src_stepx, - uint8* dst_argb, int dst_width) { - intptr_t src_stepx_x4 = (intptr_t)(src_stepx); - intptr_t src_stepx_x12; - intptr_t row1 = (intptr_t)(src_stride); - asm volatile ( - "lea " MEMLEA3(0x00,1,4) ",%1 \n" - "lea " MEMLEA4(0x00,1,1,2) ",%4 \n" - "lea " MEMLEA4(0x00,0,5,1) ",%5 \n" - - LABELALIGN - "1: \n" - "movq " MEMACCESS(0) ",%%xmm0 \n" - MEMOPREG(movhps,0x00,0,1,1,xmm0) // movhps (%0,%1,1),%%xmm0 - MEMOPREG(movq,0x00,0,1,2,xmm1) // movq (%0,%1,2),%%xmm1 - MEMOPREG(movhps,0x00,0,4,1,xmm1) // movhps (%0,%4,1),%%xmm1 - "lea " MEMLEA4(0x00,0,1,4) ",%0 \n" - "movq " MEMACCESS(5) ",%%xmm2 \n" - MEMOPREG(movhps,0x00,5,1,1,xmm2) // movhps (%5,%1,1),%%xmm2 - MEMOPREG(movq,0x00,5,1,2,xmm3) // movq (%5,%1,2),%%xmm3 - MEMOPREG(movhps,0x00,5,4,1,xmm3) // movhps (%5,%4,1),%%xmm3 - "lea " MEMLEA4(0x00,5,1,4) ",%5 \n" - "pavgb %%xmm2,%%xmm0 \n" - "pavgb %%xmm3,%%xmm1 \n" - "movdqa %%xmm0,%%xmm2 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm2 \n" - "pavgb %%xmm2,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%3 \n" - "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_stepx_x4), // %1 - "+r"(dst_argb), // %2 - "+rm"(dst_width), // %3 - "=&r"(src_stepx_x12), // %4 - "+r"(row1) // %5 - :: "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3" - ); -} - -void ScaleARGBCols_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - intptr_t x0, x1; - asm volatile ( - "movd %5,%%xmm2 \n" - "movd %6,%%xmm3 \n" - "pshufd $0x0,%%xmm2,%%xmm2 \n" - "pshufd $0x11,%%xmm3,%%xmm0 \n" - "paddd %%xmm0,%%xmm2 \n" - "paddd %%xmm3,%%xmm3 \n" - "pshufd $0x5,%%xmm3,%%xmm0 \n" - "paddd %%xmm0,%%xmm2 \n" - "paddd %%xmm3,%%xmm3 \n" - "pshufd $0x0,%%xmm3,%%xmm3 \n" - "pextrw $0x1,%%xmm2,%k0 \n" - "pextrw $0x3,%%xmm2,%k1 \n" - "cmp $0x0,%4 \n" - "jl 99f \n" - "sub $0x4,%4 \n" - "jl 49f \n" - - LABELALIGN - "40: \n" - MEMOPREG(movd,0x00,3,0,4,xmm0) // movd (%3,%0,4),%%xmm0 - MEMOPREG(movd,0x00,3,1,4,xmm1) // movd (%3,%1,4),%%xmm1 - "pextrw $0x5,%%xmm2,%k0 \n" - "pextrw $0x7,%%xmm2,%k1 \n" - "paddd %%xmm3,%%xmm2 \n" - "punpckldq %%xmm1,%%xmm0 \n" - MEMOPREG(movd,0x00,3,0,4,xmm1) // movd (%3,%0,4),%%xmm1 - MEMOPREG(movd,0x00,3,1,4,xmm4) // movd (%3,%1,4),%%xmm4 - "pextrw $0x1,%%xmm2,%k0 \n" - "pextrw $0x3,%%xmm2,%k1 \n" - "punpckldq %%xmm4,%%xmm1 \n" - "punpcklqdq %%xmm1,%%xmm0 \n" - "movdqu %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x10,2) ",%2 \n" - "sub $0x4,%4 \n" - "jge 40b \n" - - "49: \n" - "test $0x2,%4 \n" - "je 29f \n" - MEMOPREG(movd,0x00,3,0,4,xmm0) // movd (%3,%0,4),%%xmm0 - MEMOPREG(movd,0x00,3,1,4,xmm1) // movd (%3,%1,4),%%xmm1 - "pextrw $0x5,%%xmm2,%k0 \n" - "punpckldq %%xmm1,%%xmm0 \n" - "movq %%xmm0," MEMACCESS(2) " \n" - "lea " MEMLEA(0x8,2) ",%2 \n" - "29: \n" - "test $0x1,%4 \n" - "je 99f \n" - MEMOPREG(movd,0x00,3,0,4,xmm0) // movd (%3,%0,4),%%xmm0 - "movd %%xmm0," MEMACCESS(2) " \n" - "99: \n" - : "=&a"(x0), // %0 - "=&d"(x1), // %1 - "+r"(dst_argb), // %2 - "+r"(src_argb), // %3 - "+r"(dst_width) // %4 - : "rm"(x), // %5 - "rm"(dx) // %6 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4" - ); -} - -// Reads 4 pixels, duplicates them and writes 8 pixels. -// Alignment requirement: src_argb 16 byte aligned, dst_argb 16 byte aligned. -void ScaleARGBColsUp2_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - asm volatile ( - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(1) ",%%xmm0 \n" - "lea " MEMLEA(0x10,1) ",%1 \n" - "movdqa %%xmm0,%%xmm1 \n" - "punpckldq %%xmm0,%%xmm0 \n" - "punpckhdq %%xmm1,%%xmm1 \n" - "movdqu %%xmm0," MEMACCESS(0) " \n" - "movdqu %%xmm1," MEMACCESS2(0x10,0) " \n" - "lea " MEMLEA(0x20,0) ",%0 \n" - "sub $0x8,%2 \n" - "jg 1b \n" - - : "+r"(dst_argb), // %0 - "+r"(src_argb), // %1 - "+r"(dst_width) // %2 - :: "memory", "cc", NACL_R14 - "xmm0", "xmm1" - ); -} - -// Shuffle table for arranging 2 pixels into pairs for pmaddubsw -static uvec8 kShuffleColARGB = { - 0u, 4u, 1u, 5u, 2u, 6u, 3u, 7u, // bbggrraa 1st pixel - 8u, 12u, 9u, 13u, 10u, 14u, 11u, 15u // bbggrraa 2nd pixel -}; - -// Shuffle table for duplicating 2 fractions into 8 bytes each -static uvec8 kShuffleFractions = { - 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 4u, 4u, 4u, 4u, 4u, 4u, 4u, 4u, -}; - -// Bilinear row filtering combines 4x2 -> 4x1. SSSE3 version -void ScaleARGBFilterCols_SSSE3(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - intptr_t x0, x1; - asm volatile ( - "movdqa %0,%%xmm4 \n" - "movdqa %1,%%xmm5 \n" - : - : "m"(kShuffleColARGB), // %0 - "m"(kShuffleFractions) // %1 - ); - - asm volatile ( - "movd %5,%%xmm2 \n" - "movd %6,%%xmm3 \n" - "pcmpeqb %%xmm6,%%xmm6 \n" - "psrlw $0x9,%%xmm6 \n" - "pextrw $0x1,%%xmm2,%k3 \n" - "sub $0x2,%2 \n" - "jl 29f \n" - "movdqa %%xmm2,%%xmm0 \n" - "paddd %%xmm3,%%xmm0 \n" - "punpckldq %%xmm0,%%xmm2 \n" - "punpckldq %%xmm3,%%xmm3 \n" - "paddd %%xmm3,%%xmm3 \n" - "pextrw $0x3,%%xmm2,%k4 \n" - - LABELALIGN - "2: \n" - "movdqa %%xmm2,%%xmm1 \n" - "paddd %%xmm3,%%xmm2 \n" - MEMOPREG(movq,0x00,1,3,4,xmm0) // movq (%1,%3,4),%%xmm0 - "psrlw $0x9,%%xmm1 \n" - MEMOPREG(movhps,0x00,1,4,4,xmm0) // movhps (%1,%4,4),%%xmm0 - "pshufb %%xmm5,%%xmm1 \n" - "pshufb %%xmm4,%%xmm0 \n" - "pxor %%xmm6,%%xmm1 \n" - "pmaddubsw %%xmm1,%%xmm0 \n" - "psrlw $0x7,%%xmm0 \n" - "pextrw $0x1,%%xmm2,%k3 \n" - "pextrw $0x3,%%xmm2,%k4 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movq %%xmm0," MEMACCESS(0) " \n" - "lea " MEMLEA(0x8,0) ",%0 \n" - "sub $0x2,%2 \n" - "jge 2b \n" - - LABELALIGN - "29: \n" - "add $0x1,%2 \n" - "jl 99f \n" - "psrlw $0x9,%%xmm2 \n" - MEMOPREG(movq,0x00,1,3,4,xmm0) // movq (%1,%3,4),%%xmm0 - "pshufb %%xmm5,%%xmm2 \n" - "pshufb %%xmm4,%%xmm0 \n" - "pxor %%xmm6,%%xmm2 \n" - "pmaddubsw %%xmm2,%%xmm0 \n" - "psrlw $0x7,%%xmm0 \n" - "packuswb %%xmm0,%%xmm0 \n" - "movd %%xmm0," MEMACCESS(0) " \n" - - LABELALIGN - "99: \n" - : "+r"(dst_argb), // %0 - "+r"(src_argb), // %1 - "+rm"(dst_width), // %2 - "=&r"(x0), // %3 - "=&r"(x1) // %4 - : "rm"(x), // %5 - "rm"(dx) // %6 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" - ); -} - -// Divide num by div and return as 16.16 fixed point result. -int FixedDiv_X86(int num, int div) { - asm volatile ( - "cdq \n" - "shld $0x10,%%eax,%%edx \n" - "shl $0x10,%%eax \n" - "idiv %1 \n" - "mov %0, %%eax \n" - : "+a"(num) // %0 - : "c"(div) // %1 - : "memory", "cc", "edx" - ); - return num; -} - -// Divide num - 1 by div - 1 and return as 16.16 fixed point result. -int FixedDiv1_X86(int num, int div) { - asm volatile ( - "cdq \n" - "shld $0x10,%%eax,%%edx \n" - "shl $0x10,%%eax \n" - "sub $0x10001,%%eax \n" - "sbb $0x0,%%edx \n" - "sub $0x1,%1 \n" - "idiv %1 \n" - "mov %0, %%eax \n" - : "+a"(num) // %0 - : "c"(div) // %1 - : "memory", "cc", "edx" - ); - return num; -} - -#endif // defined(__x86_64__) || defined(__i386__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale_mips.cc b/telegramgallery/src/main/cpp/libyuv/source/scale_mips.cc deleted file mode 100644 index ae95307..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale_mips.cc +++ /dev/null @@ -1,644 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/basic_types.h" -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC MIPS DSPR2 -#if !defined(LIBYUV_DISABLE_MIPS) && \ - defined(__mips_dsp) && (__mips_dsp_rev >= 2) && \ - (_MIPS_SIM == _MIPS_SIM_ABI32) - -void ScaleRowDown2_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - __asm__ __volatile__( - ".set push \n" - ".set noreorder \n" - - "srl $t9, %[dst_width], 4 \n" // iterations -> by 16 - "beqz $t9, 2f \n" - " nop \n" - - "1: \n" - "lw $t0, 0(%[src_ptr]) \n" // |3|2|1|0| - "lw $t1, 4(%[src_ptr]) \n" // |7|6|5|4| - "lw $t2, 8(%[src_ptr]) \n" // |11|10|9|8| - "lw $t3, 12(%[src_ptr]) \n" // |15|14|13|12| - "lw $t4, 16(%[src_ptr]) \n" // |19|18|17|16| - "lw $t5, 20(%[src_ptr]) \n" // |23|22|21|20| - "lw $t6, 24(%[src_ptr]) \n" // |27|26|25|24| - "lw $t7, 28(%[src_ptr]) \n" // |31|30|29|28| - // TODO(fbarchard): Use odd pixels instead of even. - "precr.qb.ph $t8, $t1, $t0 \n" // |6|4|2|0| - "precr.qb.ph $t0, $t3, $t2 \n" // |14|12|10|8| - "precr.qb.ph $t1, $t5, $t4 \n" // |22|20|18|16| - "precr.qb.ph $t2, $t7, $t6 \n" // |30|28|26|24| - "addiu %[src_ptr], %[src_ptr], 32 \n" - "addiu $t9, $t9, -1 \n" - "sw $t8, 0(%[dst]) \n" - "sw $t0, 4(%[dst]) \n" - "sw $t1, 8(%[dst]) \n" - "sw $t2, 12(%[dst]) \n" - "bgtz $t9, 1b \n" - " addiu %[dst], %[dst], 16 \n" - - "2: \n" - "andi $t9, %[dst_width], 0xf \n" // residue - "beqz $t9, 3f \n" - " nop \n" - - "21: \n" - "lbu $t0, 0(%[src_ptr]) \n" - "addiu %[src_ptr], %[src_ptr], 2 \n" - "addiu $t9, $t9, -1 \n" - "sb $t0, 0(%[dst]) \n" - "bgtz $t9, 21b \n" - " addiu %[dst], %[dst], 1 \n" - - "3: \n" - ".set pop \n" - : [src_ptr] "+r" (src_ptr), - [dst] "+r" (dst) - : [dst_width] "r" (dst_width) - : "t0", "t1", "t2", "t3", "t4", "t5", - "t6", "t7", "t8", "t9" - ); -} - -void ScaleRowDown2Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - const uint8* t = src_ptr + src_stride; - - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "srl $t9, %[dst_width], 3 \n" // iterations -> step 8 - "bltz $t9, 2f \n" - " nop \n" - - "1: \n" - "lw $t0, 0(%[src_ptr]) \n" // |3|2|1|0| - "lw $t1, 4(%[src_ptr]) \n" // |7|6|5|4| - "lw $t2, 8(%[src_ptr]) \n" // |11|10|9|8| - "lw $t3, 12(%[src_ptr]) \n" // |15|14|13|12| - "lw $t4, 0(%[t]) \n" // |19|18|17|16| - "lw $t5, 4(%[t]) \n" // |23|22|21|20| - "lw $t6, 8(%[t]) \n" // |27|26|25|24| - "lw $t7, 12(%[t]) \n" // |31|30|29|28| - "addiu $t9, $t9, -1 \n" - "srl $t8, $t0, 16 \n" // |X|X|3|2| - "ins $t0, $t4, 16, 16 \n" // |17|16|1|0| - "ins $t4, $t8, 0, 16 \n" // |19|18|3|2| - "raddu.w.qb $t0, $t0 \n" // |17+16+1+0| - "raddu.w.qb $t4, $t4 \n" // |19+18+3+2| - "shra_r.w $t0, $t0, 2 \n" // |t0+2|>>2 - "shra_r.w $t4, $t4, 2 \n" // |t4+2|>>2 - "srl $t8, $t1, 16 \n" // |X|X|7|6| - "ins $t1, $t5, 16, 16 \n" // |21|20|5|4| - "ins $t5, $t8, 0, 16 \n" // |22|23|7|6| - "raddu.w.qb $t1, $t1 \n" // |21+20+5+4| - "raddu.w.qb $t5, $t5 \n" // |23+22+7+6| - "shra_r.w $t1, $t1, 2 \n" // |t1+2|>>2 - "shra_r.w $t5, $t5, 2 \n" // |t5+2|>>2 - "srl $t8, $t2, 16 \n" // |X|X|11|10| - "ins $t2, $t6, 16, 16 \n" // |25|24|9|8| - "ins $t6, $t8, 0, 16 \n" // |27|26|11|10| - "raddu.w.qb $t2, $t2 \n" // |25+24+9+8| - "raddu.w.qb $t6, $t6 \n" // |27+26+11+10| - "shra_r.w $t2, $t2, 2 \n" // |t2+2|>>2 - "shra_r.w $t6, $t6, 2 \n" // |t5+2|>>2 - "srl $t8, $t3, 16 \n" // |X|X|15|14| - "ins $t3, $t7, 16, 16 \n" // |29|28|13|12| - "ins $t7, $t8, 0, 16 \n" // |31|30|15|14| - "raddu.w.qb $t3, $t3 \n" // |29+28+13+12| - "raddu.w.qb $t7, $t7 \n" // |31+30+15+14| - "shra_r.w $t3, $t3, 2 \n" // |t3+2|>>2 - "shra_r.w $t7, $t7, 2 \n" // |t7+2|>>2 - "addiu %[src_ptr], %[src_ptr], 16 \n" - "addiu %[t], %[t], 16 \n" - "sb $t0, 0(%[dst]) \n" - "sb $t4, 1(%[dst]) \n" - "sb $t1, 2(%[dst]) \n" - "sb $t5, 3(%[dst]) \n" - "sb $t2, 4(%[dst]) \n" - "sb $t6, 5(%[dst]) \n" - "sb $t3, 6(%[dst]) \n" - "sb $t7, 7(%[dst]) \n" - "bgtz $t9, 1b \n" - " addiu %[dst], %[dst], 8 \n" - - "2: \n" - "andi $t9, %[dst_width], 0x7 \n" // x = residue - "beqz $t9, 3f \n" - " nop \n" - - "21: \n" - "lwr $t1, 0(%[src_ptr]) \n" - "lwl $t1, 3(%[src_ptr]) \n" - "lwr $t2, 0(%[t]) \n" - "lwl $t2, 3(%[t]) \n" - "srl $t8, $t1, 16 \n" - "ins $t1, $t2, 16, 16 \n" - "ins $t2, $t8, 0, 16 \n" - "raddu.w.qb $t1, $t1 \n" - "raddu.w.qb $t2, $t2 \n" - "shra_r.w $t1, $t1, 2 \n" - "shra_r.w $t2, $t2, 2 \n" - "sb $t1, 0(%[dst]) \n" - "sb $t2, 1(%[dst]) \n" - "addiu %[src_ptr], %[src_ptr], 4 \n" - "addiu $t9, $t9, -2 \n" - "addiu %[t], %[t], 4 \n" - "bgtz $t9, 21b \n" - " addiu %[dst], %[dst], 2 \n" - - "3: \n" - ".set pop \n" - - : [src_ptr] "+r" (src_ptr), - [dst] "+r" (dst), [t] "+r" (t) - : [dst_width] "r" (dst_width) - : "t0", "t1", "t2", "t3", "t4", "t5", - "t6", "t7", "t8", "t9" - ); -} - -void ScaleRowDown4_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "srl $t9, %[dst_width], 3 \n" - "beqz $t9, 2f \n" - " nop \n" - - "1: \n" - "lw $t1, 0(%[src_ptr]) \n" // |3|2|1|0| - "lw $t2, 4(%[src_ptr]) \n" // |7|6|5|4| - "lw $t3, 8(%[src_ptr]) \n" // |11|10|9|8| - "lw $t4, 12(%[src_ptr]) \n" // |15|14|13|12| - "lw $t5, 16(%[src_ptr]) \n" // |19|18|17|16| - "lw $t6, 20(%[src_ptr]) \n" // |23|22|21|20| - "lw $t7, 24(%[src_ptr]) \n" // |27|26|25|24| - "lw $t8, 28(%[src_ptr]) \n" // |31|30|29|28| - "precr.qb.ph $t1, $t2, $t1 \n" // |6|4|2|0| - "precr.qb.ph $t2, $t4, $t3 \n" // |14|12|10|8| - "precr.qb.ph $t5, $t6, $t5 \n" // |22|20|18|16| - "precr.qb.ph $t6, $t8, $t7 \n" // |30|28|26|24| - "precr.qb.ph $t1, $t2, $t1 \n" // |12|8|4|0| - "precr.qb.ph $t5, $t6, $t5 \n" // |28|24|20|16| - "addiu %[src_ptr], %[src_ptr], 32 \n" - "addiu $t9, $t9, -1 \n" - "sw $t1, 0(%[dst]) \n" - "sw $t5, 4(%[dst]) \n" - "bgtz $t9, 1b \n" - " addiu %[dst], %[dst], 8 \n" - - "2: \n" - "andi $t9, %[dst_width], 7 \n" // residue - "beqz $t9, 3f \n" - " nop \n" - - "21: \n" - "lbu $t1, 0(%[src_ptr]) \n" - "addiu %[src_ptr], %[src_ptr], 4 \n" - "addiu $t9, $t9, -1 \n" - "sb $t1, 0(%[dst]) \n" - "bgtz $t9, 21b \n" - " addiu %[dst], %[dst], 1 \n" - - "3: \n" - ".set pop \n" - : [src_ptr] "+r" (src_ptr), - [dst] "+r" (dst) - : [dst_width] "r" (dst_width) - : "t1", "t2", "t3", "t4", "t5", - "t6", "t7", "t8", "t9" - ); -} - -void ScaleRowDown4Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - intptr_t stride = src_stride; - const uint8* s1 = src_ptr + stride; - const uint8* s2 = s1 + stride; - const uint8* s3 = s2 + stride; - - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "srl $t9, %[dst_width], 1 \n" - "andi $t8, %[dst_width], 1 \n" - - "1: \n" - "lw $t0, 0(%[src_ptr]) \n" // |3|2|1|0| - "lw $t1, 0(%[s1]) \n" // |7|6|5|4| - "lw $t2, 0(%[s2]) \n" // |11|10|9|8| - "lw $t3, 0(%[s3]) \n" // |15|14|13|12| - "lw $t4, 4(%[src_ptr]) \n" // |19|18|17|16| - "lw $t5, 4(%[s1]) \n" // |23|22|21|20| - "lw $t6, 4(%[s2]) \n" // |27|26|25|24| - "lw $t7, 4(%[s3]) \n" // |31|30|29|28| - "raddu.w.qb $t0, $t0 \n" // |3 + 2 + 1 + 0| - "raddu.w.qb $t1, $t1 \n" // |7 + 6 + 5 + 4| - "raddu.w.qb $t2, $t2 \n" // |11 + 10 + 9 + 8| - "raddu.w.qb $t3, $t3 \n" // |15 + 14 + 13 + 12| - "raddu.w.qb $t4, $t4 \n" // |19 + 18 + 17 + 16| - "raddu.w.qb $t5, $t5 \n" // |23 + 22 + 21 + 20| - "raddu.w.qb $t6, $t6 \n" // |27 + 26 + 25 + 24| - "raddu.w.qb $t7, $t7 \n" // |31 + 30 + 29 + 28| - "add $t0, $t0, $t1 \n" - "add $t1, $t2, $t3 \n" - "add $t0, $t0, $t1 \n" - "add $t4, $t4, $t5 \n" - "add $t6, $t6, $t7 \n" - "add $t4, $t4, $t6 \n" - "shra_r.w $t0, $t0, 4 \n" - "shra_r.w $t4, $t4, 4 \n" - "sb $t0, 0(%[dst]) \n" - "sb $t4, 1(%[dst]) \n" - "addiu %[src_ptr], %[src_ptr], 8 \n" - "addiu %[s1], %[s1], 8 \n" - "addiu %[s2], %[s2], 8 \n" - "addiu %[s3], %[s3], 8 \n" - "addiu $t9, $t9, -1 \n" - "bgtz $t9, 1b \n" - " addiu %[dst], %[dst], 2 \n" - "beqz $t8, 2f \n" - " nop \n" - - "lw $t0, 0(%[src_ptr]) \n" // |3|2|1|0| - "lw $t1, 0(%[s1]) \n" // |7|6|5|4| - "lw $t2, 0(%[s2]) \n" // |11|10|9|8| - "lw $t3, 0(%[s3]) \n" // |15|14|13|12| - "raddu.w.qb $t0, $t0 \n" // |3 + 2 + 1 + 0| - "raddu.w.qb $t1, $t1 \n" // |7 + 6 + 5 + 4| - "raddu.w.qb $t2, $t2 \n" // |11 + 10 + 9 + 8| - "raddu.w.qb $t3, $t3 \n" // |15 + 14 + 13 + 12| - "add $t0, $t0, $t1 \n" - "add $t1, $t2, $t3 \n" - "add $t0, $t0, $t1 \n" - "shra_r.w $t0, $t0, 4 \n" - "sb $t0, 0(%[dst]) \n" - - "2: \n" - ".set pop \n" - - : [src_ptr] "+r" (src_ptr), - [dst] "+r" (dst), - [s1] "+r" (s1), - [s2] "+r" (s2), - [s3] "+r" (s3) - : [dst_width] "r" (dst_width) - : "t0", "t1", "t2", "t3", "t4", "t5", - "t6","t7", "t8", "t9" - ); -} - -void ScaleRowDown34_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - "1: \n" - "lw $t1, 0(%[src_ptr]) \n" // |3|2|1|0| - "lw $t2, 4(%[src_ptr]) \n" // |7|6|5|4| - "lw $t3, 8(%[src_ptr]) \n" // |11|10|9|8| - "lw $t4, 12(%[src_ptr]) \n" // |15|14|13|12| - "lw $t5, 16(%[src_ptr]) \n" // |19|18|17|16| - "lw $t6, 20(%[src_ptr]) \n" // |23|22|21|20| - "lw $t7, 24(%[src_ptr]) \n" // |27|26|25|24| - "lw $t8, 28(%[src_ptr]) \n" // |31|30|29|28| - "precrq.qb.ph $t0, $t2, $t4 \n" // |7|5|15|13| - "precrq.qb.ph $t9, $t6, $t8 \n" // |23|21|31|30| - "addiu %[dst_width], %[dst_width], -24 \n" - "ins $t1, $t1, 8, 16 \n" // |3|1|0|X| - "ins $t4, $t0, 8, 16 \n" // |X|15|13|12| - "ins $t5, $t5, 8, 16 \n" // |19|17|16|X| - "ins $t8, $t9, 8, 16 \n" // |X|31|29|28| - "addiu %[src_ptr], %[src_ptr], 32 \n" - "packrl.ph $t0, $t3, $t0 \n" // |9|8|7|5| - "packrl.ph $t9, $t7, $t9 \n" // |25|24|23|21| - "prepend $t1, $t2, 8 \n" // |4|3|1|0| - "prepend $t3, $t4, 24 \n" // |15|13|12|11| - "prepend $t5, $t6, 8 \n" // |20|19|17|16| - "prepend $t7, $t8, 24 \n" // |31|29|28|27| - "sw $t1, 0(%[dst]) \n" - "sw $t0, 4(%[dst]) \n" - "sw $t3, 8(%[dst]) \n" - "sw $t5, 12(%[dst]) \n" - "sw $t9, 16(%[dst]) \n" - "sw $t7, 20(%[dst]) \n" - "bnez %[dst_width], 1b \n" - " addiu %[dst], %[dst], 24 \n" - ".set pop \n" - : [src_ptr] "+r" (src_ptr), - [dst] "+r" (dst), - [dst_width] "+r" (dst_width) - : - : "t0", "t1", "t2", "t3", "t4", "t5", - "t6","t7", "t8", "t9" - ); -} - -void ScaleRowDown34_0_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - "repl.ph $t3, 3 \n" // 0x00030003 - - "1: \n" - "lw $t0, 0(%[src_ptr]) \n" // |S3|S2|S1|S0| - "lwx $t1, %[src_stride](%[src_ptr]) \n" // |T3|T2|T1|T0| - "rotr $t2, $t0, 8 \n" // |S0|S3|S2|S1| - "rotr $t6, $t1, 8 \n" // |T0|T3|T2|T1| - "muleu_s.ph.qbl $t4, $t2, $t3 \n" // |S0*3|S3*3| - "muleu_s.ph.qbl $t5, $t6, $t3 \n" // |T0*3|T3*3| - "andi $t0, $t2, 0xFFFF \n" // |0|0|S2|S1| - "andi $t1, $t6, 0xFFFF \n" // |0|0|T2|T1| - "raddu.w.qb $t0, $t0 \n" - "raddu.w.qb $t1, $t1 \n" - "shra_r.w $t0, $t0, 1 \n" - "shra_r.w $t1, $t1, 1 \n" - "preceu.ph.qbr $t2, $t2 \n" // |0|S2|0|S1| - "preceu.ph.qbr $t6, $t6 \n" // |0|T2|0|T1| - "rotr $t2, $t2, 16 \n" // |0|S1|0|S2| - "rotr $t6, $t6, 16 \n" // |0|T1|0|T2| - "addu.ph $t2, $t2, $t4 \n" - "addu.ph $t6, $t6, $t5 \n" - "sll $t5, $t0, 1 \n" - "add $t0, $t5, $t0 \n" - "shra_r.ph $t2, $t2, 2 \n" - "shra_r.ph $t6, $t6, 2 \n" - "shll.ph $t4, $t2, 1 \n" - "addq.ph $t4, $t4, $t2 \n" - "addu $t0, $t0, $t1 \n" - "addiu %[src_ptr], %[src_ptr], 4 \n" - "shra_r.w $t0, $t0, 2 \n" - "addu.ph $t6, $t6, $t4 \n" - "shra_r.ph $t6, $t6, 2 \n" - "srl $t1, $t6, 16 \n" - "addiu %[dst_width], %[dst_width], -3 \n" - "sb $t1, 0(%[d]) \n" - "sb $t0, 1(%[d]) \n" - "sb $t6, 2(%[d]) \n" - "bgtz %[dst_width], 1b \n" - " addiu %[d], %[d], 3 \n" - "3: \n" - ".set pop \n" - : [src_ptr] "+r" (src_ptr), - [src_stride] "+r" (src_stride), - [d] "+r" (d), - [dst_width] "+r" (dst_width) - : - : "t0", "t1", "t2", "t3", - "t4", "t5", "t6" - ); -} - -void ScaleRowDown34_1_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - "repl.ph $t2, 3 \n" // 0x00030003 - - "1: \n" - "lw $t0, 0(%[src_ptr]) \n" // |S3|S2|S1|S0| - "lwx $t1, %[src_stride](%[src_ptr]) \n" // |T3|T2|T1|T0| - "rotr $t4, $t0, 8 \n" // |S0|S3|S2|S1| - "rotr $t6, $t1, 8 \n" // |T0|T3|T2|T1| - "muleu_s.ph.qbl $t3, $t4, $t2 \n" // |S0*3|S3*3| - "muleu_s.ph.qbl $t5, $t6, $t2 \n" // |T0*3|T3*3| - "andi $t0, $t4, 0xFFFF \n" // |0|0|S2|S1| - "andi $t1, $t6, 0xFFFF \n" // |0|0|T2|T1| - "raddu.w.qb $t0, $t0 \n" - "raddu.w.qb $t1, $t1 \n" - "shra_r.w $t0, $t0, 1 \n" - "shra_r.w $t1, $t1, 1 \n" - "preceu.ph.qbr $t4, $t4 \n" // |0|S2|0|S1| - "preceu.ph.qbr $t6, $t6 \n" // |0|T2|0|T1| - "rotr $t4, $t4, 16 \n" // |0|S1|0|S2| - "rotr $t6, $t6, 16 \n" // |0|T1|0|T2| - "addu.ph $t4, $t4, $t3 \n" - "addu.ph $t6, $t6, $t5 \n" - "shra_r.ph $t6, $t6, 2 \n" - "shra_r.ph $t4, $t4, 2 \n" - "addu.ph $t6, $t6, $t4 \n" - "addiu %[src_ptr], %[src_ptr], 4 \n" - "shra_r.ph $t6, $t6, 1 \n" - "addu $t0, $t0, $t1 \n" - "addiu %[dst_width], %[dst_width], -3 \n" - "shra_r.w $t0, $t0, 1 \n" - "srl $t1, $t6, 16 \n" - "sb $t1, 0(%[d]) \n" - "sb $t0, 1(%[d]) \n" - "sb $t6, 2(%[d]) \n" - "bgtz %[dst_width], 1b \n" - " addiu %[d], %[d], 3 \n" - "3: \n" - ".set pop \n" - : [src_ptr] "+r" (src_ptr), - [src_stride] "+r" (src_stride), - [d] "+r" (d), - [dst_width] "+r" (dst_width) - : - : "t0", "t1", "t2", "t3", - "t4", "t5", "t6" - ); -} - -void ScaleRowDown38_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "1: \n" - "lw $t0, 0(%[src_ptr]) \n" // |3|2|1|0| - "lw $t1, 4(%[src_ptr]) \n" // |7|6|5|4| - "lw $t2, 8(%[src_ptr]) \n" // |11|10|9|8| - "lw $t3, 12(%[src_ptr]) \n" // |15|14|13|12| - "lw $t4, 16(%[src_ptr]) \n" // |19|18|17|16| - "lw $t5, 20(%[src_ptr]) \n" // |23|22|21|20| - "lw $t6, 24(%[src_ptr]) \n" // |27|26|25|24| - "lw $t7, 28(%[src_ptr]) \n" // |31|30|29|28| - "wsbh $t0, $t0 \n" // |2|3|0|1| - "wsbh $t6, $t6 \n" // |26|27|24|25| - "srl $t0, $t0, 8 \n" // |X|2|3|0| - "srl $t3, $t3, 16 \n" // |X|X|15|14| - "srl $t5, $t5, 16 \n" // |X|X|23|22| - "srl $t7, $t7, 16 \n" // |X|X|31|30| - "ins $t1, $t2, 24, 8 \n" // |8|6|5|4| - "ins $t6, $t5, 0, 8 \n" // |26|27|24|22| - "ins $t1, $t0, 0, 16 \n" // |8|6|3|0| - "ins $t6, $t7, 24, 8 \n" // |30|27|24|22| - "prepend $t2, $t3, 24 \n" // |X|15|14|11| - "ins $t4, $t4, 16, 8 \n" // |19|16|17|X| - "ins $t4, $t2, 0, 16 \n" // |19|16|14|11| - "addiu %[src_ptr], %[src_ptr], 32 \n" - "addiu %[dst_width], %[dst_width], -12 \n" - "addiu $t8,%[dst_width], -12 \n" - "sw $t1, 0(%[dst]) \n" - "sw $t4, 4(%[dst]) \n" - "sw $t6, 8(%[dst]) \n" - "bgez $t8, 1b \n" - " addiu %[dst], %[dst], 12 \n" - ".set pop \n" - : [src_ptr] "+r" (src_ptr), - [dst] "+r" (dst), - [dst_width] "+r" (dst_width) - : - : "t0", "t1", "t2", "t3", "t4", - "t5", "t6", "t7", "t8" - ); -} - -void ScaleRowDown38_2_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - intptr_t stride = src_stride; - const uint8* t = src_ptr + stride; - const int c = 0x2AAA; - - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "1: \n" - "lw $t0, 0(%[src_ptr]) \n" // |S3|S2|S1|S0| - "lw $t1, 4(%[src_ptr]) \n" // |S7|S6|S5|S4| - "lw $t2, 0(%[t]) \n" // |T3|T2|T1|T0| - "lw $t3, 4(%[t]) \n" // |T7|T6|T5|T4| - "rotr $t1, $t1, 16 \n" // |S5|S4|S7|S6| - "packrl.ph $t4, $t1, $t3 \n" // |S7|S6|T7|T6| - "packrl.ph $t5, $t3, $t1 \n" // |T5|T4|S5|S4| - "raddu.w.qb $t4, $t4 \n" // S7+S6+T7+T6 - "raddu.w.qb $t5, $t5 \n" // T5+T4+S5+S4 - "precrq.qb.ph $t6, $t0, $t2 \n" // |S3|S1|T3|T1| - "precrq.qb.ph $t6, $t6, $t6 \n" // |S3|T3|S3|T3| - "srl $t4, $t4, 2 \n" // t4 / 4 - "srl $t6, $t6, 16 \n" // |0|0|S3|T3| - "raddu.w.qb $t6, $t6 \n" // 0+0+S3+T3 - "addu $t6, $t5, $t6 \n" - "mul $t6, $t6, %[c] \n" // t6 * 0x2AAA - "sll $t0, $t0, 8 \n" // |S2|S1|S0|0| - "sll $t2, $t2, 8 \n" // |T2|T1|T0|0| - "raddu.w.qb $t0, $t0 \n" // S2+S1+S0+0 - "raddu.w.qb $t2, $t2 \n" // T2+T1+T0+0 - "addu $t0, $t0, $t2 \n" - "mul $t0, $t0, %[c] \n" // t0 * 0x2AAA - "addiu %[src_ptr], %[src_ptr], 8 \n" - "addiu %[t], %[t], 8 \n" - "addiu %[dst_width], %[dst_width], -3 \n" - "addiu %[dst_ptr], %[dst_ptr], 3 \n" - "srl $t6, $t6, 16 \n" - "srl $t0, $t0, 16 \n" - "sb $t4, -1(%[dst_ptr]) \n" - "sb $t6, -2(%[dst_ptr]) \n" - "bgtz %[dst_width], 1b \n" - " sb $t0, -3(%[dst_ptr]) \n" - ".set pop \n" - : [src_ptr] "+r" (src_ptr), - [dst_ptr] "+r" (dst_ptr), - [t] "+r" (t), - [dst_width] "+r" (dst_width) - : [c] "r" (c) - : "t0", "t1", "t2", "t3", "t4", "t5", "t6" - ); -} - -void ScaleRowDown38_3_Box_DSPR2(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - intptr_t stride = src_stride; - const uint8* s1 = src_ptr + stride; - stride += stride; - const uint8* s2 = src_ptr + stride; - const int c1 = 0x1C71; - const int c2 = 0x2AAA; - - __asm__ __volatile__ ( - ".set push \n" - ".set noreorder \n" - - "1: \n" - "lw $t0, 0(%[src_ptr]) \n" // |S3|S2|S1|S0| - "lw $t1, 4(%[src_ptr]) \n" // |S7|S6|S5|S4| - "lw $t2, 0(%[s1]) \n" // |T3|T2|T1|T0| - "lw $t3, 4(%[s1]) \n" // |T7|T6|T5|T4| - "lw $t4, 0(%[s2]) \n" // |R3|R2|R1|R0| - "lw $t5, 4(%[s2]) \n" // |R7|R6|R5|R4| - "rotr $t1, $t1, 16 \n" // |S5|S4|S7|S6| - "packrl.ph $t6, $t1, $t3 \n" // |S7|S6|T7|T6| - "raddu.w.qb $t6, $t6 \n" // S7+S6+T7+T6 - "packrl.ph $t7, $t3, $t1 \n" // |T5|T4|S5|S4| - "raddu.w.qb $t7, $t7 \n" // T5+T4+S5+S4 - "sll $t8, $t5, 16 \n" // |R5|R4|0|0| - "raddu.w.qb $t8, $t8 \n" // R5+R4 - "addu $t7, $t7, $t8 \n" - "srl $t8, $t5, 16 \n" // |0|0|R7|R6| - "raddu.w.qb $t8, $t8 \n" // R7 + R6 - "addu $t6, $t6, $t8 \n" - "mul $t6, $t6, %[c2] \n" // t6 * 0x2AAA - "precrq.qb.ph $t8, $t0, $t2 \n" // |S3|S1|T3|T1| - "precrq.qb.ph $t8, $t8, $t4 \n" // |S3|T3|R3|R1| - "srl $t8, $t8, 8 \n" // |0|S3|T3|R3| - "raddu.w.qb $t8, $t8 \n" // S3 + T3 + R3 - "addu $t7, $t7, $t8 \n" - "mul $t7, $t7, %[c1] \n" // t7 * 0x1C71 - "sll $t0, $t0, 8 \n" // |S2|S1|S0|0| - "sll $t2, $t2, 8 \n" // |T2|T1|T0|0| - "sll $t4, $t4, 8 \n" // |R2|R1|R0|0| - "raddu.w.qb $t0, $t0 \n" - "raddu.w.qb $t2, $t2 \n" - "raddu.w.qb $t4, $t4 \n" - "addu $t0, $t0, $t2 \n" - "addu $t0, $t0, $t4 \n" - "mul $t0, $t0, %[c1] \n" // t0 * 0x1C71 - "addiu %[src_ptr], %[src_ptr], 8 \n" - "addiu %[s1], %[s1], 8 \n" - "addiu %[s2], %[s2], 8 \n" - "addiu %[dst_width], %[dst_width], -3 \n" - "addiu %[dst_ptr], %[dst_ptr], 3 \n" - "srl $t6, $t6, 16 \n" - "srl $t7, $t7, 16 \n" - "srl $t0, $t0, 16 \n" - "sb $t6, -1(%[dst_ptr]) \n" - "sb $t7, -2(%[dst_ptr]) \n" - "bgtz %[dst_width], 1b \n" - " sb $t0, -3(%[dst_ptr]) \n" - ".set pop \n" - : [src_ptr] "+r" (src_ptr), - [dst_ptr] "+r" (dst_ptr), - [s1] "+r" (s1), - [s2] "+r" (s2), - [dst_width] "+r" (dst_width) - : [c1] "r" (c1), [c2] "r" (c2) - : "t0", "t1", "t2", "t3", "t4", - "t5", "t6", "t7", "t8" - ); -} - -#endif // defined(__mips_dsp) && (__mips_dsp_rev >= 2) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale_neon.cc b/telegramgallery/src/main/cpp/libyuv/source/scale_neon.cc deleted file mode 100644 index 95f3362..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale_neon.cc +++ /dev/null @@ -1,1017 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC Neon. -#if !defined(LIBYUV_DISABLE_NEON) && defined(__ARM_NEON__) && \ - !defined(__aarch64__) - -// NEON downscalers with interpolation. -// Provided by Fritz Koenig - -// Read 32x1 throw away even pixels, and write 16x1. -void ScaleRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - "1: \n" - // load even pixels into q0, odd into q1 - MEMACCESS(0) - "vld2.8 {q0, q1}, [%0]! \n" - "subs %2, %2, #16 \n" // 16 processed per loop - MEMACCESS(1) - "vst1.8 {q1}, [%1]! \n" // store odd pixels - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst), // %1 - "+r"(dst_width) // %2 - : - : "q0", "q1" // Clobber List - ); -} - -// Read 32x1 average down and write 16x1. -void ScaleRowDown2Linear_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld1.8 {q0, q1}, [%0]! \n" // load pixels and post inc - "subs %2, %2, #16 \n" // 16 processed per loop - "vpaddl.u8 q0, q0 \n" // add adjacent - "vpaddl.u8 q1, q1 \n" - "vrshrn.u16 d0, q0, #1 \n" // downshift, round and pack - "vrshrn.u16 d1, q1, #1 \n" - MEMACCESS(1) - "vst1.8 {q0}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst), // %1 - "+r"(dst_width) // %2 - : - : "q0", "q1" // Clobber List - ); -} - -// Read 32x2 average down and write 16x1. -void ScaleRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - // change the stride to row 2 pointer - "add %1, %0 \n" - "1: \n" - MEMACCESS(0) - "vld1.8 {q0, q1}, [%0]! \n" // load row 1 and post inc - MEMACCESS(1) - "vld1.8 {q2, q3}, [%1]! \n" // load row 2 and post inc - "subs %3, %3, #16 \n" // 16 processed per loop - "vpaddl.u8 q0, q0 \n" // row 1 add adjacent - "vpaddl.u8 q1, q1 \n" - "vpadal.u8 q0, q2 \n" // row 2 add adjacent + row1 - "vpadal.u8 q1, q3 \n" - "vrshrn.u16 d0, q0, #2 \n" // downshift, round and pack - "vrshrn.u16 d1, q1, #2 \n" - MEMACCESS(2) - "vst1.8 {q0}, [%2]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(src_stride), // %1 - "+r"(dst), // %2 - "+r"(dst_width) // %3 - : - : "q0", "q1", "q2", "q3" // Clobber List - ); -} - -void ScaleRowDown4_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // src line 0 - "subs %2, %2, #8 \n" // 8 processed per loop - MEMACCESS(1) - "vst1.8 {d2}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : - : "q0", "q1", "memory", "cc" - ); -} - -void ScaleRowDown4Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - const uint8* src_ptr1 = src_ptr + src_stride; - const uint8* src_ptr2 = src_ptr + src_stride * 2; - const uint8* src_ptr3 = src_ptr + src_stride * 3; -asm volatile ( - "1: \n" - MEMACCESS(0) - "vld1.8 {q0}, [%0]! \n" // load up 16x4 - MEMACCESS(3) - "vld1.8 {q1}, [%3]! \n" - MEMACCESS(4) - "vld1.8 {q2}, [%4]! \n" - MEMACCESS(5) - "vld1.8 {q3}, [%5]! \n" - "subs %2, %2, #4 \n" - "vpaddl.u8 q0, q0 \n" - "vpadal.u8 q0, q1 \n" - "vpadal.u8 q0, q2 \n" - "vpadal.u8 q0, q3 \n" - "vpaddl.u16 q0, q0 \n" - "vrshrn.u32 d0, q0, #4 \n" // divide by 16 w/rounding - "vmovn.u16 d0, q0 \n" - MEMACCESS(1) - "vst1.32 {d0[0]}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(src_ptr1), // %3 - "+r"(src_ptr2), // %4 - "+r"(src_ptr3) // %5 - : - : "q0", "q1", "q2", "q3", "memory", "cc" - ); -} - -// Down scale from 4 to 3 pixels. Use the neon multilane read/write -// to load up the every 4th pixel into a 4 different registers. -// Point samples 32 pixels to 24 pixels. -void ScaleRowDown34_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // src line 0 - "subs %2, %2, #24 \n" - "vmov d2, d3 \n" // order d0, d1, d2 - MEMACCESS(1) - "vst3.8 {d0, d1, d2}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : - : "d0", "d1", "d2", "d3", "memory", "cc" - ); -} - -void ScaleRowDown34_0_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "vmov.u8 d24, #3 \n" - "add %3, %0 \n" - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // src line 0 - MEMACCESS(3) - "vld4.8 {d4, d5, d6, d7}, [%3]! \n" // src line 1 - "subs %2, %2, #24 \n" - - // filter src line 0 with src line 1 - // expand chars to shorts to allow for room - // when adding lines together - "vmovl.u8 q8, d4 \n" - "vmovl.u8 q9, d5 \n" - "vmovl.u8 q10, d6 \n" - "vmovl.u8 q11, d7 \n" - - // 3 * line_0 + line_1 - "vmlal.u8 q8, d0, d24 \n" - "vmlal.u8 q9, d1, d24 \n" - "vmlal.u8 q10, d2, d24 \n" - "vmlal.u8 q11, d3, d24 \n" - - // (3 * line_0 + line_1) >> 2 - "vqrshrn.u16 d0, q8, #2 \n" - "vqrshrn.u16 d1, q9, #2 \n" - "vqrshrn.u16 d2, q10, #2 \n" - "vqrshrn.u16 d3, q11, #2 \n" - - // a0 = (src[0] * 3 + s[1] * 1) >> 2 - "vmovl.u8 q8, d1 \n" - "vmlal.u8 q8, d0, d24 \n" - "vqrshrn.u16 d0, q8, #2 \n" - - // a1 = (src[1] * 1 + s[2] * 1) >> 1 - "vrhadd.u8 d1, d1, d2 \n" - - // a2 = (src[2] * 1 + s[3] * 3) >> 2 - "vmovl.u8 q8, d2 \n" - "vmlal.u8 q8, d3, d24 \n" - "vqrshrn.u16 d2, q8, #2 \n" - - MEMACCESS(1) - "vst3.8 {d0, d1, d2}, [%1]! \n" - - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(src_stride) // %3 - : - : "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "d24", "memory", "cc" - ); -} - -void ScaleRowDown34_1_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "vmov.u8 d24, #3 \n" - "add %3, %0 \n" - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" // src line 0 - MEMACCESS(3) - "vld4.8 {d4, d5, d6, d7}, [%3]! \n" // src line 1 - "subs %2, %2, #24 \n" - // average src line 0 with src line 1 - "vrhadd.u8 q0, q0, q2 \n" - "vrhadd.u8 q1, q1, q3 \n" - - // a0 = (src[0] * 3 + s[1] * 1) >> 2 - "vmovl.u8 q3, d1 \n" - "vmlal.u8 q3, d0, d24 \n" - "vqrshrn.u16 d0, q3, #2 \n" - - // a1 = (src[1] * 1 + s[2] * 1) >> 1 - "vrhadd.u8 d1, d1, d2 \n" - - // a2 = (src[2] * 1 + s[3] * 3) >> 2 - "vmovl.u8 q3, d2 \n" - "vmlal.u8 q3, d3, d24 \n" - "vqrshrn.u16 d2, q3, #2 \n" - - MEMACCESS(1) - "vst3.8 {d0, d1, d2}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(src_stride) // %3 - : - : "r4", "q0", "q1", "q2", "q3", "d24", "memory", "cc" - ); -} - -#define HAS_SCALEROWDOWN38_NEON -static uvec8 kShuf38 = - { 0, 3, 6, 8, 11, 14, 16, 19, 22, 24, 27, 30, 0, 0, 0, 0 }; -static uvec8 kShuf38_2 = - { 0, 8, 16, 2, 10, 17, 4, 12, 18, 6, 14, 19, 0, 0, 0, 0 }; -static vec16 kMult38_Div6 = - { 65536 / 12, 65536 / 12, 65536 / 12, 65536 / 12, - 65536 / 12, 65536 / 12, 65536 / 12, 65536 / 12 }; -static vec16 kMult38_Div9 = - { 65536 / 18, 65536 / 18, 65536 / 18, 65536 / 18, - 65536 / 18, 65536 / 18, 65536 / 18, 65536 / 18 }; - -// 32 -> 12 -void ScaleRowDown38_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - MEMACCESS(3) - "vld1.8 {q3}, [%3] \n" - "1: \n" - MEMACCESS(0) - "vld1.8 {d0, d1, d2, d3}, [%0]! \n" - "subs %2, %2, #12 \n" - "vtbl.u8 d4, {d0, d1, d2, d3}, d6 \n" - "vtbl.u8 d5, {d0, d1, d2, d3}, d7 \n" - MEMACCESS(1) - "vst1.8 {d4}, [%1]! \n" - MEMACCESS(1) - "vst1.32 {d5[0]}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"(&kShuf38) // %3 - : "d0", "d1", "d2", "d3", "d4", "d5", "memory", "cc" - ); -} - -// 32x3 -> 12x1 -void OMITFP ScaleRowDown38_3_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - const uint8* src_ptr1 = src_ptr + src_stride * 2; - - asm volatile ( - MEMACCESS(5) - "vld1.16 {q13}, [%5] \n" - MEMACCESS(6) - "vld1.8 {q14}, [%6] \n" - MEMACCESS(7) - "vld1.8 {q15}, [%7] \n" - "add %3, %0 \n" - "1: \n" - - // d0 = 00 40 01 41 02 42 03 43 - // d1 = 10 50 11 51 12 52 13 53 - // d2 = 20 60 21 61 22 62 23 63 - // d3 = 30 70 31 71 32 72 33 73 - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" - MEMACCESS(3) - "vld4.8 {d4, d5, d6, d7}, [%3]! \n" - MEMACCESS(4) - "vld4.8 {d16, d17, d18, d19}, [%4]! \n" - "subs %2, %2, #12 \n" - - // Shuffle the input data around to get align the data - // so adjacent data can be added. 0,1 - 2,3 - 4,5 - 6,7 - // d0 = 00 10 01 11 02 12 03 13 - // d1 = 40 50 41 51 42 52 43 53 - "vtrn.u8 d0, d1 \n" - "vtrn.u8 d4, d5 \n" - "vtrn.u8 d16, d17 \n" - - // d2 = 20 30 21 31 22 32 23 33 - // d3 = 60 70 61 71 62 72 63 73 - "vtrn.u8 d2, d3 \n" - "vtrn.u8 d6, d7 \n" - "vtrn.u8 d18, d19 \n" - - // d0 = 00+10 01+11 02+12 03+13 - // d2 = 40+50 41+51 42+52 43+53 - "vpaddl.u8 q0, q0 \n" - "vpaddl.u8 q2, q2 \n" - "vpaddl.u8 q8, q8 \n" - - // d3 = 60+70 61+71 62+72 63+73 - "vpaddl.u8 d3, d3 \n" - "vpaddl.u8 d7, d7 \n" - "vpaddl.u8 d19, d19 \n" - - // combine source lines - "vadd.u16 q0, q2 \n" - "vadd.u16 q0, q8 \n" - "vadd.u16 d4, d3, d7 \n" - "vadd.u16 d4, d19 \n" - - // dst_ptr[3] = (s[6 + st * 0] + s[7 + st * 0] - // + s[6 + st * 1] + s[7 + st * 1] - // + s[6 + st * 2] + s[7 + st * 2]) / 6 - "vqrdmulh.s16 q2, q2, q13 \n" - "vmovn.u16 d4, q2 \n" - - // Shuffle 2,3 reg around so that 2 can be added to the - // 0,1 reg and 3 can be added to the 4,5 reg. This - // requires expanding from u8 to u16 as the 0,1 and 4,5 - // registers are already expanded. Then do transposes - // to get aligned. - // q2 = xx 20 xx 30 xx 21 xx 31 xx 22 xx 32 xx 23 xx 33 - "vmovl.u8 q1, d2 \n" - "vmovl.u8 q3, d6 \n" - "vmovl.u8 q9, d18 \n" - - // combine source lines - "vadd.u16 q1, q3 \n" - "vadd.u16 q1, q9 \n" - - // d4 = xx 20 xx 30 xx 22 xx 32 - // d5 = xx 21 xx 31 xx 23 xx 33 - "vtrn.u32 d2, d3 \n" - - // d4 = xx 20 xx 21 xx 22 xx 23 - // d5 = xx 30 xx 31 xx 32 xx 33 - "vtrn.u16 d2, d3 \n" - - // 0+1+2, 3+4+5 - "vadd.u16 q0, q1 \n" - - // Need to divide, but can't downshift as the the value - // isn't a power of 2. So multiply by 65536 / n - // and take the upper 16 bits. - "vqrdmulh.s16 q0, q0, q15 \n" - - // Align for table lookup, vtbl requires registers to - // be adjacent - "vmov.u8 d2, d4 \n" - - "vtbl.u8 d3, {d0, d1, d2}, d28 \n" - "vtbl.u8 d4, {d0, d1, d2}, d29 \n" - - MEMACCESS(1) - "vst1.8 {d3}, [%1]! \n" - MEMACCESS(1) - "vst1.32 {d4[0]}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(src_stride), // %3 - "+r"(src_ptr1) // %4 - : "r"(&kMult38_Div6), // %5 - "r"(&kShuf38_2), // %6 - "r"(&kMult38_Div9) // %7 - : "q0", "q1", "q2", "q3", "q8", "q9", "q13", "q14", "q15", "memory", "cc" - ); -} - -// 32x2 -> 12x1 -void ScaleRowDown38_2_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - MEMACCESS(4) - "vld1.16 {q13}, [%4] \n" - MEMACCESS(5) - "vld1.8 {q14}, [%5] \n" - "add %3, %0 \n" - "1: \n" - - // d0 = 00 40 01 41 02 42 03 43 - // d1 = 10 50 11 51 12 52 13 53 - // d2 = 20 60 21 61 22 62 23 63 - // d3 = 30 70 31 71 32 72 33 73 - MEMACCESS(0) - "vld4.8 {d0, d1, d2, d3}, [%0]! \n" - MEMACCESS(3) - "vld4.8 {d4, d5, d6, d7}, [%3]! \n" - "subs %2, %2, #12 \n" - - // Shuffle the input data around to get align the data - // so adjacent data can be added. 0,1 - 2,3 - 4,5 - 6,7 - // d0 = 00 10 01 11 02 12 03 13 - // d1 = 40 50 41 51 42 52 43 53 - "vtrn.u8 d0, d1 \n" - "vtrn.u8 d4, d5 \n" - - // d2 = 20 30 21 31 22 32 23 33 - // d3 = 60 70 61 71 62 72 63 73 - "vtrn.u8 d2, d3 \n" - "vtrn.u8 d6, d7 \n" - - // d0 = 00+10 01+11 02+12 03+13 - // d2 = 40+50 41+51 42+52 43+53 - "vpaddl.u8 q0, q0 \n" - "vpaddl.u8 q2, q2 \n" - - // d3 = 60+70 61+71 62+72 63+73 - "vpaddl.u8 d3, d3 \n" - "vpaddl.u8 d7, d7 \n" - - // combine source lines - "vadd.u16 q0, q2 \n" - "vadd.u16 d4, d3, d7 \n" - - // dst_ptr[3] = (s[6] + s[7] + s[6+st] + s[7+st]) / 4 - "vqrshrn.u16 d4, q2, #2 \n" - - // Shuffle 2,3 reg around so that 2 can be added to the - // 0,1 reg and 3 can be added to the 4,5 reg. This - // requires expanding from u8 to u16 as the 0,1 and 4,5 - // registers are already expanded. Then do transposes - // to get aligned. - // q2 = xx 20 xx 30 xx 21 xx 31 xx 22 xx 32 xx 23 xx 33 - "vmovl.u8 q1, d2 \n" - "vmovl.u8 q3, d6 \n" - - // combine source lines - "vadd.u16 q1, q3 \n" - - // d4 = xx 20 xx 30 xx 22 xx 32 - // d5 = xx 21 xx 31 xx 23 xx 33 - "vtrn.u32 d2, d3 \n" - - // d4 = xx 20 xx 21 xx 22 xx 23 - // d5 = xx 30 xx 31 xx 32 xx 33 - "vtrn.u16 d2, d3 \n" - - // 0+1+2, 3+4+5 - "vadd.u16 q0, q1 \n" - - // Need to divide, but can't downshift as the the value - // isn't a power of 2. So multiply by 65536 / n - // and take the upper 16 bits. - "vqrdmulh.s16 q0, q0, q13 \n" - - // Align for table lookup, vtbl requires registers to - // be adjacent - "vmov.u8 d2, d4 \n" - - "vtbl.u8 d3, {d0, d1, d2}, d28 \n" - "vtbl.u8 d4, {d0, d1, d2}, d29 \n" - - MEMACCESS(1) - "vst1.8 {d3}, [%1]! \n" - MEMACCESS(1) - "vst1.32 {d4[0]}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(src_stride) // %3 - : "r"(&kMult38_Div6), // %4 - "r"(&kShuf38_2) // %5 - : "q0", "q1", "q2", "q3", "q13", "q14", "memory", "cc" - ); -} - -void ScaleAddRows_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int src_width, int src_height) { - const uint8* src_tmp; - asm volatile ( - "1: \n" - "mov %0, %1 \n" - "mov r12, %5 \n" - "veor q2, q2, q2 \n" - "veor q3, q3, q3 \n" - "2: \n" - // load 16 pixels into q0 - MEMACCESS(0) - "vld1.8 {q0}, [%0], %3 \n" - "vaddw.u8 q3, q3, d1 \n" - "vaddw.u8 q2, q2, d0 \n" - "subs r12, r12, #1 \n" - "bgt 2b \n" - MEMACCESS(2) - "vst1.16 {q2, q3}, [%2]! \n" // store pixels - "add %1, %1, #16 \n" - "subs %4, %4, #16 \n" // 16 processed per loop - "bgt 1b \n" - : "=&r"(src_tmp), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_ptr), // %2 - "+r"(src_stride), // %3 - "+r"(src_width), // %4 - "+r"(src_height) // %5 - : - : "memory", "cc", "r12", "q0", "q1", "q2", "q3" // Clobber List - ); -} - -// TODO(Yang Zhang): Investigate less load instructions for -// the x/dx stepping -#define LOAD2_DATA8_LANE(n) \ - "lsr %5, %3, #16 \n" \ - "add %6, %1, %5 \n" \ - "add %3, %3, %4 \n" \ - MEMACCESS(6) \ - "vld2.8 {d6["#n"], d7["#n"]}, [%6] \n" - -void ScaleFilterCols_NEON(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - int dx_offset[4] = {0, 1, 2, 3}; - int* tmp = dx_offset; - const uint8* src_tmp = src_ptr; - asm volatile ( - "vdup.32 q0, %3 \n" // x - "vdup.32 q1, %4 \n" // dx - "vld1.32 {q2}, [%5] \n" // 0 1 2 3 - "vshl.i32 q3, q1, #2 \n" // 4 * dx - "vmul.s32 q1, q1, q2 \n" - // x , x + 1 * dx, x + 2 * dx, x + 3 * dx - "vadd.s32 q1, q1, q0 \n" - // x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx - "vadd.s32 q2, q1, q3 \n" - "vshl.i32 q0, q3, #1 \n" // 8 * dx - "1: \n" - LOAD2_DATA8_LANE(0) - LOAD2_DATA8_LANE(1) - LOAD2_DATA8_LANE(2) - LOAD2_DATA8_LANE(3) - LOAD2_DATA8_LANE(4) - LOAD2_DATA8_LANE(5) - LOAD2_DATA8_LANE(6) - LOAD2_DATA8_LANE(7) - "vmov q10, q1 \n" - "vmov q11, q2 \n" - "vuzp.16 q10, q11 \n" - "vmovl.u8 q8, d6 \n" - "vmovl.u8 q9, d7 \n" - "vsubl.s16 q11, d18, d16 \n" - "vsubl.s16 q12, d19, d17 \n" - "vmovl.u16 q13, d20 \n" - "vmovl.u16 q10, d21 \n" - "vmul.s32 q11, q11, q13 \n" - "vmul.s32 q12, q12, q10 \n" - "vshrn.s32 d18, q11, #16 \n" - "vshrn.s32 d19, q12, #16 \n" - "vadd.s16 q8, q8, q9 \n" - "vmovn.s16 d6, q8 \n" - - MEMACCESS(0) - "vst1.8 {d6}, [%0]! \n" // store pixels - "vadd.s32 q1, q1, q0 \n" - "vadd.s32 q2, q2, q0 \n" - "subs %2, %2, #8 \n" // 8 processed per loop - "bgt 1b \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(x), // %3 - "+r"(dx), // %4 - "+r"(tmp), // %5 - "+r"(src_tmp) // %6 - : - : "memory", "cc", "q0", "q1", "q2", "q3", - "q8", "q9", "q10", "q11", "q12", "q13" - ); -} - -#undef LOAD2_DATA8_LANE - -// 16x2 -> 16x1 -void ScaleFilterRows_NEON(uint8* dst_ptr, - const uint8* src_ptr, ptrdiff_t src_stride, - int dst_width, int source_y_fraction) { - asm volatile ( - "cmp %4, #0 \n" - "beq 100f \n" - "add %2, %1 \n" - "cmp %4, #64 \n" - "beq 75f \n" - "cmp %4, #128 \n" - "beq 50f \n" - "cmp %4, #192 \n" - "beq 25f \n" - - "vdup.8 d5, %4 \n" - "rsb %4, #256 \n" - "vdup.8 d4, %4 \n" - // General purpose row blend. - "1: \n" - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" - MEMACCESS(2) - "vld1.8 {q1}, [%2]! \n" - "subs %3, %3, #16 \n" - "vmull.u8 q13, d0, d4 \n" - "vmull.u8 q14, d1, d4 \n" - "vmlal.u8 q13, d2, d5 \n" - "vmlal.u8 q14, d3, d5 \n" - "vrshrn.u16 d0, q13, #8 \n" - "vrshrn.u16 d1, q14, #8 \n" - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" - "bgt 1b \n" - "b 99f \n" - - // Blend 25 / 75. - "25: \n" - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" - MEMACCESS(2) - "vld1.8 {q1}, [%2]! \n" - "subs %3, %3, #16 \n" - "vrhadd.u8 q0, q1 \n" - "vrhadd.u8 q0, q1 \n" - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" - "bgt 25b \n" - "b 99f \n" - - // Blend 50 / 50. - "50: \n" - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" - MEMACCESS(2) - "vld1.8 {q1}, [%2]! \n" - "subs %3, %3, #16 \n" - "vrhadd.u8 q0, q1 \n" - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" - "bgt 50b \n" - "b 99f \n" - - // Blend 75 / 25. - "75: \n" - MEMACCESS(1) - "vld1.8 {q1}, [%1]! \n" - MEMACCESS(2) - "vld1.8 {q0}, [%2]! \n" - "subs %3, %3, #16 \n" - "vrhadd.u8 q0, q1 \n" - "vrhadd.u8 q0, q1 \n" - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" - "bgt 75b \n" - "b 99f \n" - - // Blend 100 / 0 - Copy row unchanged. - "100: \n" - MEMACCESS(1) - "vld1.8 {q0}, [%1]! \n" - "subs %3, %3, #16 \n" - MEMACCESS(0) - "vst1.8 {q0}, [%0]! \n" - "bgt 100b \n" - - "99: \n" - MEMACCESS(0) - "vst1.8 {d1[7]}, [%0] \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(src_stride), // %2 - "+r"(dst_width), // %3 - "+r"(source_y_fraction) // %4 - : - : "q0", "q1", "d4", "d5", "q13", "q14", "memory", "cc" - ); -} - -void ScaleARGBRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - "1: \n" - // load even pixels into q0, odd into q1 - MEMACCESS(0) - "vld2.32 {q0, q1}, [%0]! \n" - MEMACCESS(0) - "vld2.32 {q2, q3}, [%0]! \n" - "subs %2, %2, #8 \n" // 8 processed per loop - MEMACCESS(1) - "vst1.8 {q1}, [%1]! \n" // store odd pixels - MEMACCESS(1) - "vst1.8 {q3}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst), // %1 - "+r"(dst_width) // %2 - : - : "memory", "cc", "q0", "q1", "q2", "q3" // Clobber List - ); -} - -void ScaleARGBRowDown2Linear_NEON(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ARGB pixels. - "subs %2, %2, #8 \n" // 8 processed per loop - "vpaddl.u8 q0, q0 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q2 \n" // R 16 bytes -> 8 shorts. - "vpaddl.u8 q3, q3 \n" // A 16 bytes -> 8 shorts. - "vrshrn.u16 d0, q0, #1 \n" // downshift, round and pack - "vrshrn.u16 d1, q1, #1 \n" - "vrshrn.u16 d2, q2, #1 \n" - "vrshrn.u16 d3, q3, #1 \n" - MEMACCESS(1) - "vst4.8 {d0, d1, d2, d3}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(dst_width) // %2 - : - : "memory", "cc", "q0", "q1", "q2", "q3" // Clobber List - ); -} - -void ScaleARGBRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - // change the stride to row 2 pointer - "add %1, %1, %0 \n" - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ARGB pixels. - "subs %3, %3, #8 \n" // 8 processed per loop. - "vpaddl.u8 q0, q0 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q2 \n" // R 16 bytes -> 8 shorts. - "vpaddl.u8 q3, q3 \n" // A 16 bytes -> 8 shorts. - MEMACCESS(1) - "vld4.8 {d16, d18, d20, d22}, [%1]! \n" // load 8 more ARGB pixels. - MEMACCESS(1) - "vld4.8 {d17, d19, d21, d23}, [%1]! \n" // load last 8 ARGB pixels. - "vpadal.u8 q0, q8 \n" // B 16 bytes -> 8 shorts. - "vpadal.u8 q1, q9 \n" // G 16 bytes -> 8 shorts. - "vpadal.u8 q2, q10 \n" // R 16 bytes -> 8 shorts. - "vpadal.u8 q3, q11 \n" // A 16 bytes -> 8 shorts. - "vrshrn.u16 d0, q0, #2 \n" // downshift, round and pack - "vrshrn.u16 d1, q1, #2 \n" - "vrshrn.u16 d2, q2, #2 \n" - "vrshrn.u16 d3, q3, #2 \n" - MEMACCESS(2) - "vst4.8 {d0, d1, d2, d3}, [%2]! \n" - "bgt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(src_stride), // %1 - "+r"(dst), // %2 - "+r"(dst_width) // %3 - : - : "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11" - ); -} - -// Reads 4 pixels at a time. -// Alignment requirement: src_argb 4 byte aligned. -void ScaleARGBRowDownEven_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, uint8* dst_argb, int dst_width) { - asm volatile ( - "mov r12, %3, lsl #2 \n" - "1: \n" - MEMACCESS(0) - "vld1.32 {d0[0]}, [%0], r12 \n" - MEMACCESS(0) - "vld1.32 {d0[1]}, [%0], r12 \n" - MEMACCESS(0) - "vld1.32 {d1[0]}, [%0], r12 \n" - MEMACCESS(0) - "vld1.32 {d1[1]}, [%0], r12 \n" - "subs %2, %2, #4 \n" // 4 pixels per loop. - MEMACCESS(1) - "vst1.8 {q0}, [%1]! \n" - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(dst_width) // %2 - : "r"(src_stepx) // %3 - : "memory", "cc", "r12", "q0" - ); -} - -// Reads 4 pixels at a time. -// Alignment requirement: src_argb 4 byte aligned. -void ScaleARGBRowDownEvenBox_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width) { - asm volatile ( - "mov r12, %4, lsl #2 \n" - "add %1, %1, %0 \n" - "1: \n" - MEMACCESS(0) - "vld1.8 {d0}, [%0], r12 \n" // Read 4 2x2 blocks -> 2x1 - MEMACCESS(1) - "vld1.8 {d1}, [%1], r12 \n" - MEMACCESS(0) - "vld1.8 {d2}, [%0], r12 \n" - MEMACCESS(1) - "vld1.8 {d3}, [%1], r12 \n" - MEMACCESS(0) - "vld1.8 {d4}, [%0], r12 \n" - MEMACCESS(1) - "vld1.8 {d5}, [%1], r12 \n" - MEMACCESS(0) - "vld1.8 {d6}, [%0], r12 \n" - MEMACCESS(1) - "vld1.8 {d7}, [%1], r12 \n" - "vaddl.u8 q0, d0, d1 \n" - "vaddl.u8 q1, d2, d3 \n" - "vaddl.u8 q2, d4, d5 \n" - "vaddl.u8 q3, d6, d7 \n" - "vswp.8 d1, d2 \n" // ab_cd -> ac_bd - "vswp.8 d5, d6 \n" // ef_gh -> eg_fh - "vadd.u16 q0, q0, q1 \n" // (a+b)_(c+d) - "vadd.u16 q2, q2, q3 \n" // (e+f)_(g+h) - "vrshrn.u16 d0, q0, #2 \n" // first 2 pixels. - "vrshrn.u16 d1, q2, #2 \n" // next 2 pixels. - "subs %3, %3, #4 \n" // 4 pixels per loop. - MEMACCESS(2) - "vst1.8 {q0}, [%2]! \n" - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_stride), // %1 - "+r"(dst_argb), // %2 - "+r"(dst_width) // %3 - : "r"(src_stepx) // %4 - : "memory", "cc", "r12", "q0", "q1", "q2", "q3" - ); -} - -// TODO(Yang Zhang): Investigate less load instructions for -// the x/dx stepping -#define LOAD1_DATA32_LANE(dn, n) \ - "lsr %5, %3, #16 \n" \ - "add %6, %1, %5, lsl #2 \n" \ - "add %3, %3, %4 \n" \ - MEMACCESS(6) \ - "vld1.32 {"#dn"["#n"]}, [%6] \n" - -void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - int tmp; - const uint8* src_tmp = src_argb; - asm volatile ( - "1: \n" - LOAD1_DATA32_LANE(d0, 0) - LOAD1_DATA32_LANE(d0, 1) - LOAD1_DATA32_LANE(d1, 0) - LOAD1_DATA32_LANE(d1, 1) - LOAD1_DATA32_LANE(d2, 0) - LOAD1_DATA32_LANE(d2, 1) - LOAD1_DATA32_LANE(d3, 0) - LOAD1_DATA32_LANE(d3, 1) - - MEMACCESS(0) - "vst1.32 {q0, q1}, [%0]! \n" // store pixels - "subs %2, %2, #8 \n" // 8 processed per loop - "bgt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(src_argb), // %1 - "+r"(dst_width), // %2 - "+r"(x), // %3 - "+r"(dx), // %4 - "=&r"(tmp), // %5 - "+r"(src_tmp) // %6 - : - : "memory", "cc", "q0", "q1" - ); -} - -#undef LOAD1_DATA32_LANE - -// TODO(Yang Zhang): Investigate less load instructions for -// the x/dx stepping -#define LOAD2_DATA32_LANE(dn1, dn2, n) \ - "lsr %5, %3, #16 \n" \ - "add %6, %1, %5, lsl #2 \n" \ - "add %3, %3, %4 \n" \ - MEMACCESS(6) \ - "vld2.32 {"#dn1"["#n"], "#dn2"["#n"]}, [%6] \n" - -void ScaleARGBFilterCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - int dx_offset[4] = {0, 1, 2, 3}; - int* tmp = dx_offset; - const uint8* src_tmp = src_argb; - asm volatile ( - "vdup.32 q0, %3 \n" // x - "vdup.32 q1, %4 \n" // dx - "vld1.32 {q2}, [%5] \n" // 0 1 2 3 - "vshl.i32 q9, q1, #2 \n" // 4 * dx - "vmul.s32 q1, q1, q2 \n" - "vmov.i8 q3, #0x7f \n" // 0x7F - "vmov.i16 q15, #0x7f \n" // 0x7F - // x , x + 1 * dx, x + 2 * dx, x + 3 * dx - "vadd.s32 q8, q1, q0 \n" - "1: \n" - // d0, d1: a - // d2, d3: b - LOAD2_DATA32_LANE(d0, d2, 0) - LOAD2_DATA32_LANE(d0, d2, 1) - LOAD2_DATA32_LANE(d1, d3, 0) - LOAD2_DATA32_LANE(d1, d3, 1) - "vshrn.i32 d22, q8, #9 \n" - "vand.16 d22, d22, d30 \n" - "vdup.8 d24, d22[0] \n" - "vdup.8 d25, d22[2] \n" - "vdup.8 d26, d22[4] \n" - "vdup.8 d27, d22[6] \n" - "vext.8 d4, d24, d25, #4 \n" - "vext.8 d5, d26, d27, #4 \n" // f - "veor.8 q10, q2, q3 \n" // 0x7f ^ f - "vmull.u8 q11, d0, d20 \n" - "vmull.u8 q12, d1, d21 \n" - "vmull.u8 q13, d2, d4 \n" - "vmull.u8 q14, d3, d5 \n" - "vadd.i16 q11, q11, q13 \n" - "vadd.i16 q12, q12, q14 \n" - "vshrn.i16 d0, q11, #7 \n" - "vshrn.i16 d1, q12, #7 \n" - - MEMACCESS(0) - "vst1.32 {d0, d1}, [%0]! \n" // store pixels - "vadd.s32 q8, q8, q9 \n" - "subs %2, %2, #4 \n" // 4 processed per loop - "bgt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(src_argb), // %1 - "+r"(dst_width), // %2 - "+r"(x), // %3 - "+r"(dx), // %4 - "+r"(tmp), // %5 - "+r"(src_tmp) // %6 - : - : "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", - "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - -#undef LOAD2_DATA32_LANE - -#endif // defined(__ARM_NEON__) && !defined(__aarch64__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale_neon64.cc b/telegramgallery/src/main/cpp/libyuv/source/scale_neon64.cc deleted file mode 100644 index 3a62db5..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale_neon64.cc +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * Copyright 2014 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/scale.h" -#include "libyuv/row.h" -#include "libyuv/scale_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for GCC Neon armv8 64 bit. -#if !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) - -// Read 32x1 throw away even pixels, and write 16x1. -void ScaleRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - "1: \n" - // load even pixels into v0, odd into v1 - MEMACCESS(0) - "ld2 {v0.16b,v1.16b}, [%0], #32 \n" - "subs %w2, %w2, #16 \n" // 16 processed per loop - MEMACCESS(1) - "st1 {v1.16b}, [%1], #16 \n" // store odd pixels - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst), // %1 - "+r"(dst_width) // %2 - : - : "v0", "v1" // Clobber List - ); -} - -// Read 32x1 average down and write 16x1. -void ScaleRowDown2Linear_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b,v1.16b}, [%0], #32 \n" // load pixels and post inc - "subs %w2, %w2, #16 \n" // 16 processed per loop - "uaddlp v0.8h, v0.16b \n" // add adjacent - "uaddlp v1.8h, v1.16b \n" - "rshrn v0.8b, v0.8h, #1 \n" // downshift, round and pack - "rshrn2 v0.16b, v1.8h, #1 \n" - MEMACCESS(1) - "st1 {v0.16b}, [%1], #16 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst), // %1 - "+r"(dst_width) // %2 - : - : "v0", "v1" // Clobber List - ); -} - -// Read 32x2 average down and write 16x1. -void ScaleRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - // change the stride to row 2 pointer - "add %1, %1, %0 \n" - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b,v1.16b}, [%0], #32 \n" // load row 1 and post inc - MEMACCESS(1) - "ld1 {v2.16b, v3.16b}, [%1], #32 \n" // load row 2 and post inc - "subs %w3, %w3, #16 \n" // 16 processed per loop - "uaddlp v0.8h, v0.16b \n" // row 1 add adjacent - "uaddlp v1.8h, v1.16b \n" - "uadalp v0.8h, v2.16b \n" // row 2 add adjacent + row1 - "uadalp v1.8h, v3.16b \n" - "rshrn v0.8b, v0.8h, #2 \n" // downshift, round and pack - "rshrn2 v0.16b, v1.8h, #2 \n" - MEMACCESS(2) - "st1 {v0.16b}, [%2], #16 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(src_stride), // %1 - "+r"(dst), // %2 - "+r"(dst_width) // %3 - : - : "v0", "v1", "v2", "v3" // Clobber List - ); -} - -void ScaleRowDown4_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // src line 0 - "subs %w2, %w2, #8 \n" // 8 processed per loop - MEMACCESS(1) - "st1 {v2.8b}, [%1], #8 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : - : "v0", "v1", "v2", "v3", "memory", "cc" - ); -} - -void ScaleRowDown4Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - const uint8* src_ptr1 = src_ptr + src_stride; - const uint8* src_ptr2 = src_ptr + src_stride * 2; - const uint8* src_ptr3 = src_ptr + src_stride * 3; -asm volatile ( - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b}, [%0], #16 \n" // load up 16x4 - MEMACCESS(3) - "ld1 {v1.16b}, [%2], #16 \n" - MEMACCESS(4) - "ld1 {v2.16b}, [%3], #16 \n" - MEMACCESS(5) - "ld1 {v3.16b}, [%4], #16 \n" - "subs %w5, %w5, #4 \n" - "uaddlp v0.8h, v0.16b \n" - "uadalp v0.8h, v1.16b \n" - "uadalp v0.8h, v2.16b \n" - "uadalp v0.8h, v3.16b \n" - "addp v0.8h, v0.8h, v0.8h \n" - "rshrn v0.8b, v0.8h, #4 \n" // divide by 16 w/rounding - MEMACCESS(1) - "st1 {v0.s}[0], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(src_ptr1), // %2 - "+r"(src_ptr2), // %3 - "+r"(src_ptr3), // %4 - "+r"(dst_width) // %5 - : - : "v0", "v1", "v2", "v3", "memory", "cc" - ); -} - -// Down scale from 4 to 3 pixels. Use the neon multilane read/write -// to load up the every 4th pixel into a 4 different registers. -// Point samples 32 pixels to 24 pixels. -void ScaleRowDown34_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // src line 0 - "subs %w2, %w2, #24 \n" - "orr v2.16b, v3.16b, v3.16b \n" // order v0, v1, v2 - MEMACCESS(1) - "st3 {v0.8b,v1.8b,v2.8b}, [%1], #24 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : - : "v0", "v1", "v2", "v3", "memory", "cc" - ); -} - -void ScaleRowDown34_0_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "movi v20.8b, #3 \n" - "add %3, %3, %0 \n" - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // src line 0 - MEMACCESS(3) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%3], #32 \n" // src line 1 - "subs %w2, %w2, #24 \n" - - // filter src line 0 with src line 1 - // expand chars to shorts to allow for room - // when adding lines together - "ushll v16.8h, v4.8b, #0 \n" - "ushll v17.8h, v5.8b, #0 \n" - "ushll v18.8h, v6.8b, #0 \n" - "ushll v19.8h, v7.8b, #0 \n" - - // 3 * line_0 + line_1 - "umlal v16.8h, v0.8b, v20.8b \n" - "umlal v17.8h, v1.8b, v20.8b \n" - "umlal v18.8h, v2.8b, v20.8b \n" - "umlal v19.8h, v3.8b, v20.8b \n" - - // (3 * line_0 + line_1) >> 2 - "uqrshrn v0.8b, v16.8h, #2 \n" - "uqrshrn v1.8b, v17.8h, #2 \n" - "uqrshrn v2.8b, v18.8h, #2 \n" - "uqrshrn v3.8b, v19.8h, #2 \n" - - // a0 = (src[0] * 3 + s[1] * 1) >> 2 - "ushll v16.8h, v1.8b, #0 \n" - "umlal v16.8h, v0.8b, v20.8b \n" - "uqrshrn v0.8b, v16.8h, #2 \n" - - // a1 = (src[1] * 1 + s[2] * 1) >> 1 - "urhadd v1.8b, v1.8b, v2.8b \n" - - // a2 = (src[2] * 1 + s[3] * 3) >> 2 - "ushll v16.8h, v2.8b, #0 \n" - "umlal v16.8h, v3.8b, v20.8b \n" - "uqrshrn v2.8b, v16.8h, #2 \n" - - MEMACCESS(1) - "st3 {v0.8b,v1.8b,v2.8b}, [%1], #24 \n" - - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(src_stride) // %3 - : - : "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", "v17", "v18", "v19", - "v20", "memory", "cc" - ); -} - -void ScaleRowDown34_1_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - "movi v20.8b, #3 \n" - "add %3, %3, %0 \n" - "1: \n" - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" // src line 0 - MEMACCESS(3) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%3], #32 \n" // src line 1 - "subs %w2, %w2, #24 \n" - // average src line 0 with src line 1 - "urhadd v0.8b, v0.8b, v4.8b \n" - "urhadd v1.8b, v1.8b, v5.8b \n" - "urhadd v2.8b, v2.8b, v6.8b \n" - "urhadd v3.8b, v3.8b, v7.8b \n" - - // a0 = (src[0] * 3 + s[1] * 1) >> 2 - "ushll v4.8h, v1.8b, #0 \n" - "umlal v4.8h, v0.8b, v20.8b \n" - "uqrshrn v0.8b, v4.8h, #2 \n" - - // a1 = (src[1] * 1 + s[2] * 1) >> 1 - "urhadd v1.8b, v1.8b, v2.8b \n" - - // a2 = (src[2] * 1 + s[3] * 3) >> 2 - "ushll v4.8h, v2.8b, #0 \n" - "umlal v4.8h, v3.8b, v20.8b \n" - "uqrshrn v2.8b, v4.8h, #2 \n" - - MEMACCESS(1) - "st3 {v0.8b,v1.8b,v2.8b}, [%1], #24 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(src_stride) // %3 - : - : "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v20", "memory", "cc" - ); -} - -static uvec8 kShuf38 = - { 0, 3, 6, 8, 11, 14, 16, 19, 22, 24, 27, 30, 0, 0, 0, 0 }; -static uvec8 kShuf38_2 = - { 0, 16, 32, 2, 18, 33, 4, 20, 34, 6, 22, 35, 0, 0, 0, 0 }; -static vec16 kMult38_Div6 = - { 65536 / 12, 65536 / 12, 65536 / 12, 65536 / 12, - 65536 / 12, 65536 / 12, 65536 / 12, 65536 / 12 }; -static vec16 kMult38_Div9 = - { 65536 / 18, 65536 / 18, 65536 / 18, 65536 / 18, - 65536 / 18, 65536 / 18, 65536 / 18, 65536 / 18 }; - -// 32 -> 12 -void ScaleRowDown38_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - asm volatile ( - MEMACCESS(3) - "ld1 {v3.16b}, [%3] \n" - "1: \n" - MEMACCESS(0) - "ld1 {v0.16b,v1.16b}, [%0], #32 \n" - "subs %w2, %w2, #12 \n" - "tbl v2.16b, {v0.16b,v1.16b}, v3.16b \n" - MEMACCESS(1) - "st1 {v2.8b}, [%1], #8 \n" - MEMACCESS(1) - "st1 {v2.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"(&kShuf38) // %3 - : "v0", "v1", "v2", "v3", "memory", "cc" - ); -} - -// 32x3 -> 12x1 -void OMITFP ScaleRowDown38_3_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - const uint8* src_ptr1 = src_ptr + src_stride * 2; - ptrdiff_t tmp_src_stride = src_stride; - - asm volatile ( - MEMACCESS(5) - "ld1 {v29.8h}, [%5] \n" - MEMACCESS(6) - "ld1 {v30.16b}, [%6] \n" - MEMACCESS(7) - "ld1 {v31.8h}, [%7] \n" - "add %2, %2, %0 \n" - "1: \n" - - // 00 40 01 41 02 42 03 43 - // 10 50 11 51 12 52 13 53 - // 20 60 21 61 22 62 23 63 - // 30 70 31 71 32 72 33 73 - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" - MEMACCESS(3) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%2], #32 \n" - MEMACCESS(4) - "ld4 {v16.8b,v17.8b,v18.8b,v19.8b}, [%3], #32 \n" - "subs %w4, %w4, #12 \n" - - // Shuffle the input data around to get align the data - // so adjacent data can be added. 0,1 - 2,3 - 4,5 - 6,7 - // 00 10 01 11 02 12 03 13 - // 40 50 41 51 42 52 43 53 - "trn1 v20.8b, v0.8b, v1.8b \n" - "trn2 v21.8b, v0.8b, v1.8b \n" - "trn1 v22.8b, v4.8b, v5.8b \n" - "trn2 v23.8b, v4.8b, v5.8b \n" - "trn1 v24.8b, v16.8b, v17.8b \n" - "trn2 v25.8b, v16.8b, v17.8b \n" - - // 20 30 21 31 22 32 23 33 - // 60 70 61 71 62 72 63 73 - "trn1 v0.8b, v2.8b, v3.8b \n" - "trn2 v1.8b, v2.8b, v3.8b \n" - "trn1 v4.8b, v6.8b, v7.8b \n" - "trn2 v5.8b, v6.8b, v7.8b \n" - "trn1 v16.8b, v18.8b, v19.8b \n" - "trn2 v17.8b, v18.8b, v19.8b \n" - - // 00+10 01+11 02+12 03+13 - // 40+50 41+51 42+52 43+53 - "uaddlp v20.4h, v20.8b \n" - "uaddlp v21.4h, v21.8b \n" - "uaddlp v22.4h, v22.8b \n" - "uaddlp v23.4h, v23.8b \n" - "uaddlp v24.4h, v24.8b \n" - "uaddlp v25.4h, v25.8b \n" - - // 60+70 61+71 62+72 63+73 - "uaddlp v1.4h, v1.8b \n" - "uaddlp v5.4h, v5.8b \n" - "uaddlp v17.4h, v17.8b \n" - - // combine source lines - "add v20.4h, v20.4h, v22.4h \n" - "add v21.4h, v21.4h, v23.4h \n" - "add v20.4h, v20.4h, v24.4h \n" - "add v21.4h, v21.4h, v25.4h \n" - "add v2.4h, v1.4h, v5.4h \n" - "add v2.4h, v2.4h, v17.4h \n" - - // dst_ptr[3] = (s[6 + st * 0] + s[7 + st * 0] - // + s[6 + st * 1] + s[7 + st * 1] - // + s[6 + st * 2] + s[7 + st * 2]) / 6 - "sqrdmulh v2.8h, v2.8h, v29.8h \n" - "xtn v2.8b, v2.8h \n" - - // Shuffle 2,3 reg around so that 2 can be added to the - // 0,1 reg and 3 can be added to the 4,5 reg. This - // requires expanding from u8 to u16 as the 0,1 and 4,5 - // registers are already expanded. Then do transposes - // to get aligned. - // xx 20 xx 30 xx 21 xx 31 xx 22 xx 32 xx 23 xx 33 - "ushll v16.8h, v16.8b, #0 \n" - "uaddl v0.8h, v0.8b, v4.8b \n" - - // combine source lines - "add v0.8h, v0.8h, v16.8h \n" - - // xx 20 xx 21 xx 22 xx 23 - // xx 30 xx 31 xx 32 xx 33 - "trn1 v1.8h, v0.8h, v0.8h \n" - "trn2 v4.8h, v0.8h, v0.8h \n" - "xtn v0.4h, v1.4s \n" - "xtn v4.4h, v4.4s \n" - - // 0+1+2, 3+4+5 - "add v20.8h, v20.8h, v0.8h \n" - "add v21.8h, v21.8h, v4.8h \n" - - // Need to divide, but can't downshift as the the value - // isn't a power of 2. So multiply by 65536 / n - // and take the upper 16 bits. - "sqrdmulh v0.8h, v20.8h, v31.8h \n" - "sqrdmulh v1.8h, v21.8h, v31.8h \n" - - // Align for table lookup, vtbl requires registers to - // be adjacent - "tbl v3.16b, {v0.16b, v1.16b, v2.16b}, v30.16b \n" - - MEMACCESS(1) - "st1 {v3.8b}, [%1], #8 \n" - MEMACCESS(1) - "st1 {v3.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(tmp_src_stride), // %2 - "+r"(src_ptr1), // %3 - "+r"(dst_width) // %4 - : "r"(&kMult38_Div6), // %5 - "r"(&kShuf38_2), // %6 - "r"(&kMult38_Div9) // %7 - : "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", "v17", - "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", "v29", - "v30", "v31", "memory", "cc" - ); -} - -// 32x2 -> 12x1 -void ScaleRowDown38_2_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - // TODO(fbarchard): use src_stride directly for clang 3.5+. - ptrdiff_t tmp_src_stride = src_stride; - asm volatile ( - MEMACCESS(4) - "ld1 {v30.8h}, [%4] \n" - MEMACCESS(5) - "ld1 {v31.16b}, [%5] \n" - "add %2, %2, %0 \n" - "1: \n" - - // 00 40 01 41 02 42 03 43 - // 10 50 11 51 12 52 13 53 - // 20 60 21 61 22 62 23 63 - // 30 70 31 71 32 72 33 73 - MEMACCESS(0) - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" - MEMACCESS(3) - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%2], #32 \n" - "subs %w3, %w3, #12 \n" - - // Shuffle the input data around to get align the data - // so adjacent data can be added. 0,1 - 2,3 - 4,5 - 6,7 - // 00 10 01 11 02 12 03 13 - // 40 50 41 51 42 52 43 53 - "trn1 v16.8b, v0.8b, v1.8b \n" - "trn2 v17.8b, v0.8b, v1.8b \n" - "trn1 v18.8b, v4.8b, v5.8b \n" - "trn2 v19.8b, v4.8b, v5.8b \n" - - // 20 30 21 31 22 32 23 33 - // 60 70 61 71 62 72 63 73 - "trn1 v0.8b, v2.8b, v3.8b \n" - "trn2 v1.8b, v2.8b, v3.8b \n" - "trn1 v4.8b, v6.8b, v7.8b \n" - "trn2 v5.8b, v6.8b, v7.8b \n" - - // 00+10 01+11 02+12 03+13 - // 40+50 41+51 42+52 43+53 - "uaddlp v16.4h, v16.8b \n" - "uaddlp v17.4h, v17.8b \n" - "uaddlp v18.4h, v18.8b \n" - "uaddlp v19.4h, v19.8b \n" - - // 60+70 61+71 62+72 63+73 - "uaddlp v1.4h, v1.8b \n" - "uaddlp v5.4h, v5.8b \n" - - // combine source lines - "add v16.4h, v16.4h, v18.4h \n" - "add v17.4h, v17.4h, v19.4h \n" - "add v2.4h, v1.4h, v5.4h \n" - - // dst_ptr[3] = (s[6] + s[7] + s[6+st] + s[7+st]) / 4 - "uqrshrn v2.8b, v2.8h, #2 \n" - - // Shuffle 2,3 reg around so that 2 can be added to the - // 0,1 reg and 3 can be added to the 4,5 reg. This - // requires expanding from u8 to u16 as the 0,1 and 4,5 - // registers are already expanded. Then do transposes - // to get aligned. - // xx 20 xx 30 xx 21 xx 31 xx 22 xx 32 xx 23 xx 33 - - // combine source lines - "uaddl v0.8h, v0.8b, v4.8b \n" - - // xx 20 xx 21 xx 22 xx 23 - // xx 30 xx 31 xx 32 xx 33 - "trn1 v1.8h, v0.8h, v0.8h \n" - "trn2 v4.8h, v0.8h, v0.8h \n" - "xtn v0.4h, v1.4s \n" - "xtn v4.4h, v4.4s \n" - - // 0+1+2, 3+4+5 - "add v16.8h, v16.8h, v0.8h \n" - "add v17.8h, v17.8h, v4.8h \n" - - // Need to divide, but can't downshift as the the value - // isn't a power of 2. So multiply by 65536 / n - // and take the upper 16 bits. - "sqrdmulh v0.8h, v16.8h, v30.8h \n" - "sqrdmulh v1.8h, v17.8h, v30.8h \n" - - // Align for table lookup, vtbl requires registers to - // be adjacent - - "tbl v3.16b, {v0.16b, v1.16b, v2.16b}, v31.16b \n" - - MEMACCESS(1) - "st1 {v3.8b}, [%1], #8 \n" - MEMACCESS(1) - "st1 {v3.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(tmp_src_stride), // %2 - "+r"(dst_width) // %3 - : "r"(&kMult38_Div6), // %4 - "r"(&kShuf38_2) // %5 - : "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", "v17", - "v18", "v19", "v30", "v31", "memory", "cc" - ); -} - -void ScaleAddRows_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int src_width, int src_height) { - const uint8* src_tmp; - asm volatile ( - "1: \n" - "mov %0, %1 \n" - "mov w12, %w5 \n" - "eor v2.16b, v2.16b, v2.16b \n" - "eor v3.16b, v3.16b, v3.16b \n" - "2: \n" - // load 16 pixels into q0 - MEMACCESS(0) - "ld1 {v0.16b}, [%0], %3 \n" - "uaddw2 v3.8h, v3.8h, v0.16b \n" - "uaddw v2.8h, v2.8h, v0.8b \n" - "subs w12, w12, #1 \n" - "b.gt 2b \n" - MEMACCESS(2) - "st1 {v2.8h, v3.8h}, [%2], #32 \n" // store pixels - "add %1, %1, #16 \n" - "subs %w4, %w4, #16 \n" // 16 processed per loop - "b.gt 1b \n" - : "=&r"(src_tmp), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_ptr), // %2 - "+r"(src_stride), // %3 - "+r"(src_width), // %4 - "+r"(src_height) // %5 - : - : "memory", "cc", "w12", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -// TODO(Yang Zhang): Investigate less load instructions for -// the x/dx stepping -#define LOAD2_DATA8_LANE(n) \ - "lsr %5, %3, #16 \n" \ - "add %6, %1, %5 \n" \ - "add %3, %3, %4 \n" \ - MEMACCESS(6) \ - "ld2 {v4.b, v5.b}["#n"], [%6] \n" - -void ScaleFilterCols_NEON(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - int dx_offset[4] = {0, 1, 2, 3}; - int* tmp = dx_offset; - const uint8* src_tmp = src_ptr; - int64 dst_width64 = (int64) dst_width; // Work around ios 64 bit warning. - int64 x64 = (int64) x; - int64 dx64 = (int64) dx; - asm volatile ( - "dup v0.4s, %w3 \n" // x - "dup v1.4s, %w4 \n" // dx - "ld1 {v2.4s}, [%5] \n" // 0 1 2 3 - "shl v3.4s, v1.4s, #2 \n" // 4 * dx - "mul v1.4s, v1.4s, v2.4s \n" - // x , x + 1 * dx, x + 2 * dx, x + 3 * dx - "add v1.4s, v1.4s, v0.4s \n" - // x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx - "add v2.4s, v1.4s, v3.4s \n" - "shl v0.4s, v3.4s, #1 \n" // 8 * dx - "1: \n" - LOAD2_DATA8_LANE(0) - LOAD2_DATA8_LANE(1) - LOAD2_DATA8_LANE(2) - LOAD2_DATA8_LANE(3) - LOAD2_DATA8_LANE(4) - LOAD2_DATA8_LANE(5) - LOAD2_DATA8_LANE(6) - LOAD2_DATA8_LANE(7) - "mov v6.16b, v1.16b \n" - "mov v7.16b, v2.16b \n" - "uzp1 v6.8h, v6.8h, v7.8h \n" - "ushll v4.8h, v4.8b, #0 \n" - "ushll v5.8h, v5.8b, #0 \n" - "ssubl v16.4s, v5.4h, v4.4h \n" - "ssubl2 v17.4s, v5.8h, v4.8h \n" - "ushll v7.4s, v6.4h, #0 \n" - "ushll2 v6.4s, v6.8h, #0 \n" - "mul v16.4s, v16.4s, v7.4s \n" - "mul v17.4s, v17.4s, v6.4s \n" - "shrn v6.4h, v16.4s, #16 \n" - "shrn2 v6.8h, v17.4s, #16 \n" - "add v4.8h, v4.8h, v6.8h \n" - "xtn v4.8b, v4.8h \n" - - MEMACCESS(0) - "st1 {v4.8b}, [%0], #8 \n" // store pixels - "add v1.4s, v1.4s, v0.4s \n" - "add v2.4s, v2.4s, v0.4s \n" - "subs %w2, %w2, #8 \n" // 8 processed per loop - "b.gt 1b \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_width64), // %2 - "+r"(x64), // %3 - "+r"(dx64), // %4 - "+r"(tmp), // %5 - "+r"(src_tmp) // %6 - : - : "memory", "cc", "v0", "v1", "v2", "v3", - "v4", "v5", "v6", "v7", "v16", "v17" - ); -} - -#undef LOAD2_DATA8_LANE - -// 16x2 -> 16x1 -void ScaleFilterRows_NEON(uint8* dst_ptr, - const uint8* src_ptr, ptrdiff_t src_stride, - int dst_width, int source_y_fraction) { - int y_fraction = 256 - source_y_fraction; - asm volatile ( - "cmp %w4, #0 \n" - "b.eq 100f \n" - "add %2, %2, %1 \n" - "cmp %w4, #64 \n" - "b.eq 75f \n" - "cmp %w4, #128 \n" - "b.eq 50f \n" - "cmp %w4, #192 \n" - "b.eq 25f \n" - - "dup v5.8b, %w4 \n" - "dup v4.8b, %w5 \n" - // General purpose row blend. - "1: \n" - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" - MEMACCESS(2) - "ld1 {v1.16b}, [%2], #16 \n" - "subs %w3, %w3, #16 \n" - "umull v6.8h, v0.8b, v4.8b \n" - "umull2 v7.8h, v0.16b, v4.16b \n" - "umlal v6.8h, v1.8b, v5.8b \n" - "umlal2 v7.8h, v1.16b, v5.16b \n" - "rshrn v0.8b, v6.8h, #8 \n" - "rshrn2 v0.16b, v7.8h, #8 \n" - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" - "b.gt 1b \n" - "b 99f \n" - - // Blend 25 / 75. - "25: \n" - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" - MEMACCESS(2) - "ld1 {v1.16b}, [%2], #16 \n" - "subs %w3, %w3, #16 \n" - "urhadd v0.16b, v0.16b, v1.16b \n" - "urhadd v0.16b, v0.16b, v1.16b \n" - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" - "b.gt 25b \n" - "b 99f \n" - - // Blend 50 / 50. - "50: \n" - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" - MEMACCESS(2) - "ld1 {v1.16b}, [%2], #16 \n" - "subs %w3, %w3, #16 \n" - "urhadd v0.16b, v0.16b, v1.16b \n" - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" - "b.gt 50b \n" - "b 99f \n" - - // Blend 75 / 25. - "75: \n" - MEMACCESS(1) - "ld1 {v1.16b}, [%1], #16 \n" - MEMACCESS(2) - "ld1 {v0.16b}, [%2], #16 \n" - "subs %w3, %w3, #16 \n" - "urhadd v0.16b, v0.16b, v1.16b \n" - "urhadd v0.16b, v0.16b, v1.16b \n" - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" - "b.gt 75b \n" - "b 99f \n" - - // Blend 100 / 0 - Copy row unchanged. - "100: \n" - MEMACCESS(1) - "ld1 {v0.16b}, [%1], #16 \n" - "subs %w3, %w3, #16 \n" - MEMACCESS(0) - "st1 {v0.16b}, [%0], #16 \n" - "b.gt 100b \n" - - "99: \n" - MEMACCESS(0) - "st1 {v0.b}[15], [%0] \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(src_stride), // %2 - "+r"(dst_width), // %3 - "+r"(source_y_fraction),// %4 - "+r"(y_fraction) // %5 - : - : "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "memory", "cc" - ); -} - -void ScaleARGBRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - "1: \n" - // load even pixels into q0, odd into q1 - MEMACCESS (0) - "ld2 {v0.4s, v1.4s}, [%0], #32 \n" - MEMACCESS (0) - "ld2 {v2.4s, v3.4s}, [%0], #32 \n" - "subs %w2, %w2, #8 \n" // 8 processed per loop - MEMACCESS (1) - "st1 {v1.16b}, [%1], #16 \n" // store odd pixels - MEMACCESS (1) - "st1 {v3.16b}, [%1], #16 \n" - "b.gt 1b \n" - : "+r" (src_ptr), // %0 - "+r" (dst), // %1 - "+r" (dst_width) // %2 - : - : "memory", "cc", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -void ScaleARGBRowDown2Linear_NEON(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS (0) - // load 8 ARGB pixels. - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" - "subs %w2, %w2, #8 \n" // 8 processed per loop. - "uaddlp v0.8h, v0.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v2.16b \n" // R 16 bytes -> 8 shorts. - "uaddlp v3.8h, v3.16b \n" // A 16 bytes -> 8 shorts. - "rshrn v0.8b, v0.8h, #1 \n" // downshift, round and pack - "rshrn v1.8b, v1.8h, #1 \n" - "rshrn v2.8b, v2.8h, #1 \n" - "rshrn v3.8b, v3.8h, #1 \n" - MEMACCESS (1) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%1], #32 \n" - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(dst_width) // %2 - : - : "memory", "cc", "v0", "v1", "v2", "v3" // Clobber List - ); -} - -void ScaleARGBRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { - asm volatile ( - // change the stride to row 2 pointer - "add %1, %1, %0 \n" - "1: \n" - MEMACCESS (0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load 8 ARGB pixels. - "subs %w3, %w3, #8 \n" // 8 processed per loop. - "uaddlp v0.8h, v0.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v2.16b \n" // R 16 bytes -> 8 shorts. - "uaddlp v3.8h, v3.16b \n" // A 16 bytes -> 8 shorts. - MEMACCESS (1) - "ld4 {v16.16b,v17.16b,v18.16b,v19.16b}, [%1], #64 \n" // load 8 more ARGB pixels. - "uadalp v0.8h, v16.16b \n" // B 16 bytes -> 8 shorts. - "uadalp v1.8h, v17.16b \n" // G 16 bytes -> 8 shorts. - "uadalp v2.8h, v18.16b \n" // R 16 bytes -> 8 shorts. - "uadalp v3.8h, v19.16b \n" // A 16 bytes -> 8 shorts. - "rshrn v0.8b, v0.8h, #2 \n" // downshift, round and pack - "rshrn v1.8b, v1.8h, #2 \n" - "rshrn v2.8b, v2.8h, #2 \n" - "rshrn v3.8b, v3.8h, #2 \n" - MEMACCESS (2) - "st4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%2], #32 \n" - "b.gt 1b \n" - : "+r" (src_ptr), // %0 - "+r" (src_stride), // %1 - "+r" (dst), // %2 - "+r" (dst_width) // %3 - : - : "memory", "cc", "v0", "v1", "v2", "v3", "v16", "v17", "v18", "v19" - ); -} - -// Reads 4 pixels at a time. -// Alignment requirement: src_argb 4 byte aligned. -void ScaleARGBRowDownEven_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, uint8* dst_argb, int dst_width) { - asm volatile ( - "1: \n" - MEMACCESS(0) - "ld1 {v0.s}[0], [%0], %3 \n" - MEMACCESS(0) - "ld1 {v0.s}[1], [%0], %3 \n" - MEMACCESS(0) - "ld1 {v0.s}[2], [%0], %3 \n" - MEMACCESS(0) - "ld1 {v0.s}[3], [%0], %3 \n" - "subs %w2, %w2, #4 \n" // 4 pixels per loop. - MEMACCESS(1) - "st1 {v0.16b}, [%1], #16 \n" - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(dst_width) // %2 - : "r"((int64)(src_stepx * 4)) // %3 - : "memory", "cc", "v0" - ); -} - -// Reads 4 pixels at a time. -// Alignment requirement: src_argb 4 byte aligned. -// TODO(Yang Zhang): Might be worth another optimization pass in future. -// It could be upgraded to 8 pixels at a time to start with. -void ScaleARGBRowDownEvenBox_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width) { - asm volatile ( - "add %1, %1, %0 \n" - "1: \n" - MEMACCESS(0) - "ld1 {v0.8b}, [%0], %4 \n" // Read 4 2x2 blocks -> 2x1 - MEMACCESS(1) - "ld1 {v1.8b}, [%1], %4 \n" - MEMACCESS(0) - "ld1 {v2.8b}, [%0], %4 \n" - MEMACCESS(1) - "ld1 {v3.8b}, [%1], %4 \n" - MEMACCESS(0) - "ld1 {v4.8b}, [%0], %4 \n" - MEMACCESS(1) - "ld1 {v5.8b}, [%1], %4 \n" - MEMACCESS(0) - "ld1 {v6.8b}, [%0], %4 \n" - MEMACCESS(1) - "ld1 {v7.8b}, [%1], %4 \n" - "uaddl v0.8h, v0.8b, v1.8b \n" - "uaddl v2.8h, v2.8b, v3.8b \n" - "uaddl v4.8h, v4.8b, v5.8b \n" - "uaddl v6.8h, v6.8b, v7.8b \n" - "mov v16.d[1], v0.d[1] \n" // ab_cd -> ac_bd - "mov v0.d[1], v2.d[0] \n" - "mov v2.d[0], v16.d[1] \n" - "mov v16.d[1], v4.d[1] \n" // ef_gh -> eg_fh - "mov v4.d[1], v6.d[0] \n" - "mov v6.d[0], v16.d[1] \n" - "add v0.8h, v0.8h, v2.8h \n" // (a+b)_(c+d) - "add v4.8h, v4.8h, v6.8h \n" // (e+f)_(g+h) - "rshrn v0.8b, v0.8h, #2 \n" // first 2 pixels. - "rshrn2 v0.16b, v4.8h, #2 \n" // next 2 pixels. - "subs %w3, %w3, #4 \n" // 4 pixels per loop. - MEMACCESS(2) - "st1 {v0.16b}, [%2], #16 \n" - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_stride), // %1 - "+r"(dst_argb), // %2 - "+r"(dst_width) // %3 - : "r"((int64)(src_stepx * 4)) // %4 - : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16" - ); -} - -// TODO(Yang Zhang): Investigate less load instructions for -// the x/dx stepping -#define LOAD1_DATA32_LANE(vn, n) \ - "lsr %5, %3, #16 \n" \ - "add %6, %1, %5, lsl #2 \n" \ - "add %3, %3, %4 \n" \ - MEMACCESS(6) \ - "ld1 {"#vn".s}["#n"], [%6] \n" - -void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - const uint8* src_tmp = src_argb; - int64 dst_width64 = (int64) dst_width; // Work around ios 64 bit warning. - int64 x64 = (int64) x; - int64 dx64 = (int64) dx; - int64 tmp64; - asm volatile ( - "1: \n" - LOAD1_DATA32_LANE(v0, 0) - LOAD1_DATA32_LANE(v0, 1) - LOAD1_DATA32_LANE(v0, 2) - LOAD1_DATA32_LANE(v0, 3) - LOAD1_DATA32_LANE(v1, 0) - LOAD1_DATA32_LANE(v1, 1) - LOAD1_DATA32_LANE(v1, 2) - LOAD1_DATA32_LANE(v1, 3) - - MEMACCESS(0) - "st1 {v0.4s, v1.4s}, [%0], #32 \n" // store pixels - "subs %w2, %w2, #8 \n" // 8 processed per loop - "b.gt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(src_argb), // %1 - "+r"(dst_width64), // %2 - "+r"(x64), // %3 - "+r"(dx64), // %4 - "=&r"(tmp64), // %5 - "+r"(src_tmp) // %6 - : - : "memory", "cc", "v0", "v1" - ); -} - -#undef LOAD1_DATA32_LANE - -// TODO(Yang Zhang): Investigate less load instructions for -// the x/dx stepping -#define LOAD2_DATA32_LANE(vn1, vn2, n) \ - "lsr %5, %3, #16 \n" \ - "add %6, %1, %5, lsl #2 \n" \ - "add %3, %3, %4 \n" \ - MEMACCESS(6) \ - "ld2 {"#vn1".s, "#vn2".s}["#n"], [%6] \n" - -void ScaleARGBFilterCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - int dx_offset[4] = {0, 1, 2, 3}; - int* tmp = dx_offset; - const uint8* src_tmp = src_argb; - int64 dst_width64 = (int64) dst_width; // Work around ios 64 bit warning. - int64 x64 = (int64) x; - int64 dx64 = (int64) dx; - asm volatile ( - "dup v0.4s, %w3 \n" // x - "dup v1.4s, %w4 \n" // dx - "ld1 {v2.4s}, [%5] \n" // 0 1 2 3 - "shl v6.4s, v1.4s, #2 \n" // 4 * dx - "mul v1.4s, v1.4s, v2.4s \n" - "movi v3.16b, #0x7f \n" // 0x7F - "movi v4.8h, #0x7f \n" // 0x7F - // x , x + 1 * dx, x + 2 * dx, x + 3 * dx - "add v5.4s, v1.4s, v0.4s \n" - "1: \n" - // d0, d1: a - // d2, d3: b - LOAD2_DATA32_LANE(v0, v1, 0) - LOAD2_DATA32_LANE(v0, v1, 1) - LOAD2_DATA32_LANE(v0, v1, 2) - LOAD2_DATA32_LANE(v0, v1, 3) - "shrn v2.4h, v5.4s, #9 \n" - "and v2.8b, v2.8b, v4.8b \n" - "dup v16.8b, v2.b[0] \n" - "dup v17.8b, v2.b[2] \n" - "dup v18.8b, v2.b[4] \n" - "dup v19.8b, v2.b[6] \n" - "ext v2.8b, v16.8b, v17.8b, #4 \n" - "ext v17.8b, v18.8b, v19.8b, #4 \n" - "ins v2.d[1], v17.d[0] \n" // f - "eor v7.16b, v2.16b, v3.16b \n" // 0x7f ^ f - "umull v16.8h, v0.8b, v7.8b \n" - "umull2 v17.8h, v0.16b, v7.16b \n" - "umull v18.8h, v1.8b, v2.8b \n" - "umull2 v19.8h, v1.16b, v2.16b \n" - "add v16.8h, v16.8h, v18.8h \n" - "add v17.8h, v17.8h, v19.8h \n" - "shrn v0.8b, v16.8h, #7 \n" - "shrn2 v0.16b, v17.8h, #7 \n" - - MEMACCESS(0) - "st1 {v0.4s}, [%0], #16 \n" // store pixels - "add v5.4s, v5.4s, v6.4s \n" - "subs %w2, %w2, #4 \n" // 4 processed per loop - "b.gt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(src_argb), // %1 - "+r"(dst_width64), // %2 - "+r"(x64), // %3 - "+r"(dx64), // %4 - "+r"(tmp), // %5 - "+r"(src_tmp) // %6 - : - : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", - "v6", "v7", "v16", "v17", "v18", "v19" - ); -} - -#undef LOAD2_DATA32_LANE - -#endif // !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/scale_win.cc b/telegramgallery/src/main/cpp/libyuv/source/scale_win.cc deleted file mode 100644 index 21b1ed9..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/scale_win.cc +++ /dev/null @@ -1,1357 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "libyuv/row.h" -#include "libyuv/scale_row.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// This module is for 32 bit Visual C x86 and clangcl -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) - -// Offsets for source bytes 0 to 9 -static uvec8 kShuf0 = - { 0, 1, 3, 4, 5, 7, 8, 9, 128, 128, 128, 128, 128, 128, 128, 128 }; - -// Offsets for source bytes 11 to 20 with 8 subtracted = 3 to 12. -static uvec8 kShuf1 = - { 3, 4, 5, 7, 8, 9, 11, 12, 128, 128, 128, 128, 128, 128, 128, 128 }; - -// Offsets for source bytes 21 to 31 with 16 subtracted = 5 to 31. -static uvec8 kShuf2 = - { 5, 7, 8, 9, 11, 12, 13, 15, 128, 128, 128, 128, 128, 128, 128, 128 }; - -// Offsets for source bytes 0 to 10 -static uvec8 kShuf01 = - { 0, 1, 1, 2, 2, 3, 4, 5, 5, 6, 6, 7, 8, 9, 9, 10 }; - -// Offsets for source bytes 10 to 21 with 8 subtracted = 3 to 13. -static uvec8 kShuf11 = - { 2, 3, 4, 5, 5, 6, 6, 7, 8, 9, 9, 10, 10, 11, 12, 13 }; - -// Offsets for source bytes 21 to 31 with 16 subtracted = 5 to 31. -static uvec8 kShuf21 = - { 5, 6, 6, 7, 8, 9, 9, 10, 10, 11, 12, 13, 13, 14, 14, 15 }; - -// Coefficients for source bytes 0 to 10 -static uvec8 kMadd01 = - { 3, 1, 2, 2, 1, 3, 3, 1, 2, 2, 1, 3, 3, 1, 2, 2 }; - -// Coefficients for source bytes 10 to 21 -static uvec8 kMadd11 = - { 1, 3, 3, 1, 2, 2, 1, 3, 3, 1, 2, 2, 1, 3, 3, 1 }; - -// Coefficients for source bytes 21 to 31 -static uvec8 kMadd21 = - { 2, 2, 1, 3, 3, 1, 2, 2, 1, 3, 3, 1, 2, 2, 1, 3 }; - -// Coefficients for source bytes 21 to 31 -static vec16 kRound34 = - { 2, 2, 2, 2, 2, 2, 2, 2 }; - -static uvec8 kShuf38a = - { 0, 3, 6, 8, 11, 14, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }; - -static uvec8 kShuf38b = - { 128, 128, 128, 128, 128, 128, 0, 3, 6, 8, 11, 14, 128, 128, 128, 128 }; - -// Arrange words 0,3,6 into 0,1,2 -static uvec8 kShufAc = - { 0, 1, 6, 7, 12, 13, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }; - -// Arrange words 0,3,6 into 3,4,5 -static uvec8 kShufAc3 = - { 128, 128, 128, 128, 128, 128, 0, 1, 6, 7, 12, 13, 128, 128, 128, 128 }; - -// Scaling values for boxes of 3x3 and 2x3 -static uvec16 kScaleAc33 = - { 65536 / 9, 65536 / 9, 65536 / 6, 65536 / 9, 65536 / 9, 65536 / 6, 0, 0 }; - -// Arrange first value for pixels 0,1,2,3,4,5 -static uvec8 kShufAb0 = - { 0, 128, 3, 128, 6, 128, 8, 128, 11, 128, 14, 128, 128, 128, 128, 128 }; - -// Arrange second value for pixels 0,1,2,3,4,5 -static uvec8 kShufAb1 = - { 1, 128, 4, 128, 7, 128, 9, 128, 12, 128, 15, 128, 128, 128, 128, 128 }; - -// Arrange third value for pixels 0,1,2,3,4,5 -static uvec8 kShufAb2 = - { 2, 128, 5, 128, 128, 128, 10, 128, 13, 128, 128, 128, 128, 128, 128, 128 }; - -// Scaling values for boxes of 3x2 and 2x2 -static uvec16 kScaleAb2 = - { 65536 / 3, 65536 / 3, 65536 / 2, 65536 / 3, 65536 / 3, 65536 / 2, 0, 0 }; - -// Reads 32 pixels, throws half away and writes 16 pixels. -__declspec(naked) -void ScaleRowDown2_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - mov eax, [esp + 4] // src_ptr - // src_stride ignored - mov edx, [esp + 12] // dst_ptr - mov ecx, [esp + 16] // dst_width - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - psrlw xmm0, 8 // isolate odd pixels. - psrlw xmm1, 8 - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg wloop - - ret - } -} - -// Blends 32x1 rectangle to 16x1. -__declspec(naked) -void ScaleRowDown2Linear_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - mov eax, [esp + 4] // src_ptr - // src_stride - mov edx, [esp + 12] // dst_ptr - mov ecx, [esp + 16] // dst_width - - pcmpeqb xmm4, xmm4 // constant 0x0101 - psrlw xmm4, 15 - packuswb xmm4, xmm4 - pxor xmm5, xmm5 // constant 0 - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - pmaddubsw xmm0, xmm4 // horizontal add - pmaddubsw xmm1, xmm4 - pavgw xmm0, xmm5 // (x + 1) / 2 - pavgw xmm1, xmm5 - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg wloop - - ret - } -} - -// Blends 32x2 rectangle to 16x1. -__declspec(naked) -void ScaleRowDown2Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_ptr - mov esi, [esp + 4 + 8] // src_stride - mov edx, [esp + 4 + 12] // dst_ptr - mov ecx, [esp + 4 + 16] // dst_width - - pcmpeqb xmm4, xmm4 // constant 0x0101 - psrlw xmm4, 15 - packuswb xmm4, xmm4 - pxor xmm5, xmm5 // constant 0 - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + esi] - movdqu xmm3, [eax + esi + 16] - lea eax, [eax + 32] - pmaddubsw xmm0, xmm4 // horizontal add - pmaddubsw xmm1, xmm4 - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - paddw xmm0, xmm2 // vertical add - paddw xmm1, xmm3 - psrlw xmm0, 1 - psrlw xmm1, 1 - pavgw xmm0, xmm5 // (x + 1) / 2 - pavgw xmm1, xmm5 - packuswb xmm0, xmm1 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg wloop - - pop esi - ret - } -} - -#ifdef HAS_SCALEROWDOWN2_AVX2 -// Reads 64 pixels, throws half away and writes 32 pixels. -__declspec(naked) -void ScaleRowDown2_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - mov eax, [esp + 4] // src_ptr - // src_stride ignored - mov edx, [esp + 12] // dst_ptr - mov ecx, [esp + 16] // dst_width - - wloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpsrlw ymm0, ymm0, 8 // isolate odd pixels. - vpsrlw ymm1, ymm1, 8 - vpackuswb ymm0, ymm0, ymm1 - vpermq ymm0, ymm0, 0xd8 // unmutate vpackuswb - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 32 - jg wloop - - vzeroupper - ret - } -} - -// Blends 64x1 rectangle to 32x1. -__declspec(naked) -void ScaleRowDown2Linear_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - mov eax, [esp + 4] // src_ptr - // src_stride - mov edx, [esp + 12] // dst_ptr - mov ecx, [esp + 16] // dst_width - - vpcmpeqb ymm4, ymm4, ymm4 // '1' constant, 8b - vpsrlw ymm4, ymm4, 15 - vpackuswb ymm4, ymm4, ymm4 - vpxor ymm5, ymm5, ymm5 // constant 0 - - wloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpmaddubsw ymm0, ymm0, ymm4 // horizontal add - vpmaddubsw ymm1, ymm1, ymm4 - vpavgw ymm0, ymm0, ymm5 // (x + 1) / 2 - vpavgw ymm1, ymm1, ymm5 - vpackuswb ymm0, ymm0, ymm1 - vpermq ymm0, ymm0, 0xd8 // unmutate vpackuswb - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 32 - jg wloop - - vzeroupper - ret - } -} - -// For rounding, average = (sum + 2) / 4 -// becomes average((sum >> 1), 0) -// Blends 64x2 rectangle to 32x1. -__declspec(naked) -void ScaleRowDown2Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_ptr - mov esi, [esp + 4 + 8] // src_stride - mov edx, [esp + 4 + 12] // dst_ptr - mov ecx, [esp + 4 + 16] // dst_width - - vpcmpeqb ymm4, ymm4, ymm4 // '1' constant, 8b - vpsrlw ymm4, ymm4, 15 - vpackuswb ymm4, ymm4, ymm4 - vpxor ymm5, ymm5, ymm5 // constant 0 - - wloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - vmovdqu ymm2, [eax + esi] - vmovdqu ymm3, [eax + esi + 32] - lea eax, [eax + 64] - vpmaddubsw ymm0, ymm0, ymm4 // horizontal add - vpmaddubsw ymm1, ymm1, ymm4 - vpmaddubsw ymm2, ymm2, ymm4 - vpmaddubsw ymm3, ymm3, ymm4 - vpaddw ymm0, ymm0, ymm2 // vertical add - vpaddw ymm1, ymm1, ymm3 - vpsrlw ymm0, ymm0, 1 // (x + 2) / 4 = (x / 2 + 1) / 2 - vpsrlw ymm1, ymm1, 1 - vpavgw ymm0, ymm0, ymm5 // (x + 1) / 2 - vpavgw ymm1, ymm1, ymm5 - vpackuswb ymm0, ymm0, ymm1 - vpermq ymm0, ymm0, 0xd8 // unmutate vpackuswb - vmovdqu [edx], ymm0 - lea edx, [edx + 32] - sub ecx, 32 - jg wloop - - pop esi - vzeroupper - ret - } -} -#endif // HAS_SCALEROWDOWN2_AVX2 - -// Point samples 32 pixels to 8 pixels. -__declspec(naked) -void ScaleRowDown4_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - mov eax, [esp + 4] // src_ptr - // src_stride ignored - mov edx, [esp + 12] // dst_ptr - mov ecx, [esp + 16] // dst_width - pcmpeqb xmm5, xmm5 // generate mask 0x00ff0000 - psrld xmm5, 24 - pslld xmm5, 16 - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - pand xmm0, xmm5 - pand xmm1, xmm5 - packuswb xmm0, xmm1 - psrlw xmm0, 8 - packuswb xmm0, xmm0 - movq qword ptr [edx], xmm0 - lea edx, [edx + 8] - sub ecx, 8 - jg wloop - - ret - } -} - -// Blends 32x4 rectangle to 8x1. -__declspec(naked) -void ScaleRowDown4Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_ptr - mov esi, [esp + 8 + 8] // src_stride - mov edx, [esp + 8 + 12] // dst_ptr - mov ecx, [esp + 8 + 16] // dst_width - lea edi, [esi + esi * 2] // src_stride * 3 - pcmpeqb xmm4, xmm4 // constant 0x0101 - psrlw xmm4, 15 - movdqa xmm5, xmm4 - packuswb xmm4, xmm4 - psllw xmm5, 3 // constant 0x0008 - - wloop: - movdqu xmm0, [eax] // average rows - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + esi] - movdqu xmm3, [eax + esi + 16] - pmaddubsw xmm0, xmm4 // horizontal add - pmaddubsw xmm1, xmm4 - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - paddw xmm0, xmm2 // vertical add rows 0, 1 - paddw xmm1, xmm3 - movdqu xmm2, [eax + esi * 2] - movdqu xmm3, [eax + esi * 2 + 16] - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - paddw xmm0, xmm2 // add row 2 - paddw xmm1, xmm3 - movdqu xmm2, [eax + edi] - movdqu xmm3, [eax + edi + 16] - lea eax, [eax + 32] - pmaddubsw xmm2, xmm4 - pmaddubsw xmm3, xmm4 - paddw xmm0, xmm2 // add row 3 - paddw xmm1, xmm3 - phaddw xmm0, xmm1 - paddw xmm0, xmm5 // + 8 for round - psrlw xmm0, 4 // /16 for average of 4 * 4 - packuswb xmm0, xmm0 - movq qword ptr [edx], xmm0 - lea edx, [edx + 8] - sub ecx, 8 - jg wloop - - pop edi - pop esi - ret - } -} - -#ifdef HAS_SCALEROWDOWN4_AVX2 -// Point samples 64 pixels to 16 pixels. -__declspec(naked) -void ScaleRowDown4_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - mov eax, [esp + 4] // src_ptr - // src_stride ignored - mov edx, [esp + 12] // dst_ptr - mov ecx, [esp + 16] // dst_width - vpcmpeqb ymm5, ymm5, ymm5 // generate mask 0x00ff0000 - vpsrld ymm5, ymm5, 24 - vpslld ymm5, ymm5, 16 - - wloop: - vmovdqu ymm0, [eax] - vmovdqu ymm1, [eax + 32] - lea eax, [eax + 64] - vpand ymm0, ymm0, ymm5 - vpand ymm1, ymm1, ymm5 - vpackuswb ymm0, ymm0, ymm1 - vpermq ymm0, ymm0, 0xd8 // unmutate vpackuswb - vpsrlw ymm0, ymm0, 8 - vpackuswb ymm0, ymm0, ymm0 - vpermq ymm0, ymm0, 0xd8 // unmutate vpackuswb - vmovdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg wloop - - vzeroupper - ret - } -} - -// Blends 64x4 rectangle to 16x1. -__declspec(naked) -void ScaleRowDown4Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - push esi - push edi - mov eax, [esp + 8 + 4] // src_ptr - mov esi, [esp + 8 + 8] // src_stride - mov edx, [esp + 8 + 12] // dst_ptr - mov ecx, [esp + 8 + 16] // dst_width - lea edi, [esi + esi * 2] // src_stride * 3 - vpcmpeqb ymm4, ymm4, ymm4 // constant 0x0101 - vpsrlw ymm4, ymm4, 15 - vpsllw ymm5, ymm4, 3 // constant 0x0008 - vpackuswb ymm4, ymm4, ymm4 - - wloop: - vmovdqu ymm0, [eax] // average rows - vmovdqu ymm1, [eax + 32] - vmovdqu ymm2, [eax + esi] - vmovdqu ymm3, [eax + esi + 32] - vpmaddubsw ymm0, ymm0, ymm4 // horizontal add - vpmaddubsw ymm1, ymm1, ymm4 - vpmaddubsw ymm2, ymm2, ymm4 - vpmaddubsw ymm3, ymm3, ymm4 - vpaddw ymm0, ymm0, ymm2 // vertical add rows 0, 1 - vpaddw ymm1, ymm1, ymm3 - vmovdqu ymm2, [eax + esi * 2] - vmovdqu ymm3, [eax + esi * 2 + 32] - vpmaddubsw ymm2, ymm2, ymm4 - vpmaddubsw ymm3, ymm3, ymm4 - vpaddw ymm0, ymm0, ymm2 // add row 2 - vpaddw ymm1, ymm1, ymm3 - vmovdqu ymm2, [eax + edi] - vmovdqu ymm3, [eax + edi + 32] - lea eax, [eax + 64] - vpmaddubsw ymm2, ymm2, ymm4 - vpmaddubsw ymm3, ymm3, ymm4 - vpaddw ymm0, ymm0, ymm2 // add row 3 - vpaddw ymm1, ymm1, ymm3 - vphaddw ymm0, ymm0, ymm1 // mutates - vpermq ymm0, ymm0, 0xd8 // unmutate vphaddw - vpaddw ymm0, ymm0, ymm5 // + 8 for round - vpsrlw ymm0, ymm0, 4 // /32 for average of 4 * 4 - vpackuswb ymm0, ymm0, ymm0 - vpermq ymm0, ymm0, 0xd8 // unmutate vpackuswb - vmovdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 16 - jg wloop - - pop edi - pop esi - vzeroupper - ret - } -} -#endif // HAS_SCALEROWDOWN4_AVX2 - -// Point samples 32 pixels to 24 pixels. -// Produces three 8 byte values. For each 8 bytes, 16 bytes are read. -// Then shuffled to do the scaling. - -__declspec(naked) -void ScaleRowDown34_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - mov eax, [esp + 4] // src_ptr - // src_stride ignored - mov edx, [esp + 12] // dst_ptr - mov ecx, [esp + 16] // dst_width - movdqa xmm3, xmmword ptr kShuf0 - movdqa xmm4, xmmword ptr kShuf1 - movdqa xmm5, xmmword ptr kShuf2 - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - movdqa xmm2, xmm1 - palignr xmm1, xmm0, 8 - pshufb xmm0, xmm3 - pshufb xmm1, xmm4 - pshufb xmm2, xmm5 - movq qword ptr [edx], xmm0 - movq qword ptr [edx + 8], xmm1 - movq qword ptr [edx + 16], xmm2 - lea edx, [edx + 24] - sub ecx, 24 - jg wloop - - ret - } -} - -// Blends 32x2 rectangle to 24x1 -// Produces three 8 byte values. For each 8 bytes, 16 bytes are read. -// Then shuffled to do the scaling. - -// Register usage: -// xmm0 src_row 0 -// xmm1 src_row 1 -// xmm2 shuf 0 -// xmm3 shuf 1 -// xmm4 shuf 2 -// xmm5 madd 0 -// xmm6 madd 1 -// xmm7 kRound34 - -// Note that movdqa+palign may be better than movdqu. -__declspec(naked) -void ScaleRowDown34_1_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_ptr - mov esi, [esp + 4 + 8] // src_stride - mov edx, [esp + 4 + 12] // dst_ptr - mov ecx, [esp + 4 + 16] // dst_width - movdqa xmm2, xmmword ptr kShuf01 - movdqa xmm3, xmmword ptr kShuf11 - movdqa xmm4, xmmword ptr kShuf21 - movdqa xmm5, xmmword ptr kMadd01 - movdqa xmm6, xmmword ptr kMadd11 - movdqa xmm7, xmmword ptr kRound34 - - wloop: - movdqu xmm0, [eax] // pixels 0..7 - movdqu xmm1, [eax + esi] - pavgb xmm0, xmm1 - pshufb xmm0, xmm2 - pmaddubsw xmm0, xmm5 - paddsw xmm0, xmm7 - psrlw xmm0, 2 - packuswb xmm0, xmm0 - movq qword ptr [edx], xmm0 - movdqu xmm0, [eax + 8] // pixels 8..15 - movdqu xmm1, [eax + esi + 8] - pavgb xmm0, xmm1 - pshufb xmm0, xmm3 - pmaddubsw xmm0, xmm6 - paddsw xmm0, xmm7 - psrlw xmm0, 2 - packuswb xmm0, xmm0 - movq qword ptr [edx + 8], xmm0 - movdqu xmm0, [eax + 16] // pixels 16..23 - movdqu xmm1, [eax + esi + 16] - lea eax, [eax + 32] - pavgb xmm0, xmm1 - pshufb xmm0, xmm4 - movdqa xmm1, xmmword ptr kMadd21 - pmaddubsw xmm0, xmm1 - paddsw xmm0, xmm7 - psrlw xmm0, 2 - packuswb xmm0, xmm0 - movq qword ptr [edx + 16], xmm0 - lea edx, [edx + 24] - sub ecx, 24 - jg wloop - - pop esi - ret - } -} - -// Note that movdqa+palign may be better than movdqu. -__declspec(naked) -void ScaleRowDown34_0_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_ptr - mov esi, [esp + 4 + 8] // src_stride - mov edx, [esp + 4 + 12] // dst_ptr - mov ecx, [esp + 4 + 16] // dst_width - movdqa xmm2, xmmword ptr kShuf01 - movdqa xmm3, xmmword ptr kShuf11 - movdqa xmm4, xmmword ptr kShuf21 - movdqa xmm5, xmmword ptr kMadd01 - movdqa xmm6, xmmword ptr kMadd11 - movdqa xmm7, xmmword ptr kRound34 - - wloop: - movdqu xmm0, [eax] // pixels 0..7 - movdqu xmm1, [eax + esi] - pavgb xmm1, xmm0 - pavgb xmm0, xmm1 - pshufb xmm0, xmm2 - pmaddubsw xmm0, xmm5 - paddsw xmm0, xmm7 - psrlw xmm0, 2 - packuswb xmm0, xmm0 - movq qword ptr [edx], xmm0 - movdqu xmm0, [eax + 8] // pixels 8..15 - movdqu xmm1, [eax + esi + 8] - pavgb xmm1, xmm0 - pavgb xmm0, xmm1 - pshufb xmm0, xmm3 - pmaddubsw xmm0, xmm6 - paddsw xmm0, xmm7 - psrlw xmm0, 2 - packuswb xmm0, xmm0 - movq qword ptr [edx + 8], xmm0 - movdqu xmm0, [eax + 16] // pixels 16..23 - movdqu xmm1, [eax + esi + 16] - lea eax, [eax + 32] - pavgb xmm1, xmm0 - pavgb xmm0, xmm1 - pshufb xmm0, xmm4 - movdqa xmm1, xmmword ptr kMadd21 - pmaddubsw xmm0, xmm1 - paddsw xmm0, xmm7 - psrlw xmm0, 2 - packuswb xmm0, xmm0 - movq qword ptr [edx + 16], xmm0 - lea edx, [edx+24] - sub ecx, 24 - jg wloop - - pop esi - ret - } -} - -// 3/8 point sampler - -// Scale 32 pixels to 12 -__declspec(naked) -void ScaleRowDown38_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - mov eax, [esp + 4] // src_ptr - // src_stride ignored - mov edx, [esp + 12] // dst_ptr - mov ecx, [esp + 16] // dst_width - movdqa xmm4, xmmword ptr kShuf38a - movdqa xmm5, xmmword ptr kShuf38b - - xloop: - movdqu xmm0, [eax] // 16 pixels -> 0,1,2,3,4,5 - movdqu xmm1, [eax + 16] // 16 pixels -> 6,7,8,9,10,11 - lea eax, [eax + 32] - pshufb xmm0, xmm4 - pshufb xmm1, xmm5 - paddusb xmm0, xmm1 - - movq qword ptr [edx], xmm0 // write 12 pixels - movhlps xmm1, xmm0 - movd [edx + 8], xmm1 - lea edx, [edx + 12] - sub ecx, 12 - jg xloop - - ret - } -} - -// Scale 16x3 pixels to 6x1 with interpolation -__declspec(naked) -void ScaleRowDown38_3_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_ptr - mov esi, [esp + 4 + 8] // src_stride - mov edx, [esp + 4 + 12] // dst_ptr - mov ecx, [esp + 4 + 16] // dst_width - movdqa xmm2, xmmword ptr kShufAc - movdqa xmm3, xmmword ptr kShufAc3 - movdqa xmm4, xmmword ptr kScaleAc33 - pxor xmm5, xmm5 - - xloop: - movdqu xmm0, [eax] // sum up 3 rows into xmm0/1 - movdqu xmm6, [eax + esi] - movhlps xmm1, xmm0 - movhlps xmm7, xmm6 - punpcklbw xmm0, xmm5 - punpcklbw xmm1, xmm5 - punpcklbw xmm6, xmm5 - punpcklbw xmm7, xmm5 - paddusw xmm0, xmm6 - paddusw xmm1, xmm7 - movdqu xmm6, [eax + esi * 2] - lea eax, [eax + 16] - movhlps xmm7, xmm6 - punpcklbw xmm6, xmm5 - punpcklbw xmm7, xmm5 - paddusw xmm0, xmm6 - paddusw xmm1, xmm7 - - movdqa xmm6, xmm0 // 8 pixels -> 0,1,2 of xmm6 - psrldq xmm0, 2 - paddusw xmm6, xmm0 - psrldq xmm0, 2 - paddusw xmm6, xmm0 - pshufb xmm6, xmm2 - - movdqa xmm7, xmm1 // 8 pixels -> 3,4,5 of xmm6 - psrldq xmm1, 2 - paddusw xmm7, xmm1 - psrldq xmm1, 2 - paddusw xmm7, xmm1 - pshufb xmm7, xmm3 - paddusw xmm6, xmm7 - - pmulhuw xmm6, xmm4 // divide by 9,9,6, 9,9,6 - packuswb xmm6, xmm6 - - movd [edx], xmm6 // write 6 pixels - psrlq xmm6, 16 - movd [edx + 2], xmm6 - lea edx, [edx + 6] - sub ecx, 6 - jg xloop - - pop esi - ret - } -} - -// Scale 16x2 pixels to 6x1 with interpolation -__declspec(naked) -void ScaleRowDown38_2_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_ptr - mov esi, [esp + 4 + 8] // src_stride - mov edx, [esp + 4 + 12] // dst_ptr - mov ecx, [esp + 4 + 16] // dst_width - movdqa xmm2, xmmword ptr kShufAb0 - movdqa xmm3, xmmword ptr kShufAb1 - movdqa xmm4, xmmword ptr kShufAb2 - movdqa xmm5, xmmword ptr kScaleAb2 - - xloop: - movdqu xmm0, [eax] // average 2 rows into xmm0 - movdqu xmm1, [eax + esi] - lea eax, [eax + 16] - pavgb xmm0, xmm1 - - movdqa xmm1, xmm0 // 16 pixels -> 0,1,2,3,4,5 of xmm1 - pshufb xmm1, xmm2 - movdqa xmm6, xmm0 - pshufb xmm6, xmm3 - paddusw xmm1, xmm6 - pshufb xmm0, xmm4 - paddusw xmm1, xmm0 - - pmulhuw xmm1, xmm5 // divide by 3,3,2, 3,3,2 - packuswb xmm1, xmm1 - - movd [edx], xmm1 // write 6 pixels - psrlq xmm1, 16 - movd [edx + 2], xmm1 - lea edx, [edx + 6] - sub ecx, 6 - jg xloop - - pop esi - ret - } -} - -// Reads 16 bytes and accumulates to 16 shorts at a time. -__declspec(naked) -void ScaleAddRow_SSE2(const uint8* src_ptr, uint16* dst_ptr, int src_width) { - __asm { - mov eax, [esp + 4] // src_ptr - mov edx, [esp + 8] // dst_ptr - mov ecx, [esp + 12] // src_width - pxor xmm5, xmm5 - - // sum rows - xloop: - movdqu xmm3, [eax] // read 16 bytes - lea eax, [eax + 16] - movdqu xmm0, [edx] // read 16 words from destination - movdqu xmm1, [edx + 16] - movdqa xmm2, xmm3 - punpcklbw xmm2, xmm5 - punpckhbw xmm3, xmm5 - paddusw xmm0, xmm2 // sum 16 words - paddusw xmm1, xmm3 - movdqu [edx], xmm0 // write 16 words to destination - movdqu [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 16 - jg xloop - ret - } -} - -#ifdef HAS_SCALEADDROW_AVX2 -// Reads 32 bytes and accumulates to 32 shorts at a time. -__declspec(naked) -void ScaleAddRow_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width) { - __asm { - mov eax, [esp + 4] // src_ptr - mov edx, [esp + 8] // dst_ptr - mov ecx, [esp + 12] // src_width - vpxor ymm5, ymm5, ymm5 - - // sum rows - xloop: - vmovdqu ymm3, [eax] // read 32 bytes - lea eax, [eax + 32] - vpermq ymm3, ymm3, 0xd8 // unmutate for vpunpck - vpunpcklbw ymm2, ymm3, ymm5 - vpunpckhbw ymm3, ymm3, ymm5 - vpaddusw ymm0, ymm2, [edx] // sum 16 words - vpaddusw ymm1, ymm3, [edx + 32] - vmovdqu [edx], ymm0 // write 32 words to destination - vmovdqu [edx + 32], ymm1 - lea edx, [edx + 64] - sub ecx, 32 - jg xloop - - vzeroupper - ret - } -} -#endif // HAS_SCALEADDROW_AVX2 - -// Bilinear column filtering. SSSE3 version. -__declspec(naked) -void ScaleFilterCols_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - __asm { - push ebx - push esi - push edi - mov edi, [esp + 12 + 4] // dst_ptr - mov esi, [esp + 12 + 8] // src_ptr - mov ecx, [esp + 12 + 12] // dst_width - movd xmm2, [esp + 12 + 16] // x - movd xmm3, [esp + 12 + 20] // dx - mov eax, 0x04040000 // shuffle to line up fractions with pixel. - movd xmm5, eax - pcmpeqb xmm6, xmm6 // generate 0x007f for inverting fraction. - psrlw xmm6, 9 - pextrw eax, xmm2, 1 // get x0 integer. preroll - sub ecx, 2 - jl xloop29 - - movdqa xmm0, xmm2 // x1 = x0 + dx - paddd xmm0, xmm3 - punpckldq xmm2, xmm0 // x0 x1 - punpckldq xmm3, xmm3 // dx dx - paddd xmm3, xmm3 // dx * 2, dx * 2 - pextrw edx, xmm2, 3 // get x1 integer. preroll - - // 2 Pixel loop. - xloop2: - movdqa xmm1, xmm2 // x0, x1 fractions. - paddd xmm2, xmm3 // x += dx - movzx ebx, word ptr [esi + eax] // 2 source x0 pixels - movd xmm0, ebx - psrlw xmm1, 9 // 7 bit fractions. - movzx ebx, word ptr [esi + edx] // 2 source x1 pixels - movd xmm4, ebx - pshufb xmm1, xmm5 // 0011 - punpcklwd xmm0, xmm4 - pxor xmm1, xmm6 // 0..7f and 7f..0 - pmaddubsw xmm0, xmm1 // 16 bit, 2 pixels. - pextrw eax, xmm2, 1 // get x0 integer. next iteration. - pextrw edx, xmm2, 3 // get x1 integer. next iteration. - psrlw xmm0, 7 // 8.7 fixed point to low 8 bits. - packuswb xmm0, xmm0 // 8 bits, 2 pixels. - movd ebx, xmm0 - mov [edi], bx - lea edi, [edi + 2] - sub ecx, 2 // 2 pixels - jge xloop2 - - xloop29: - - add ecx, 2 - 1 - jl xloop99 - - // 1 pixel remainder - movzx ebx, word ptr [esi + eax] // 2 source x0 pixels - movd xmm0, ebx - psrlw xmm2, 9 // 7 bit fractions. - pshufb xmm2, xmm5 // 0011 - pxor xmm2, xmm6 // 0..7f and 7f..0 - pmaddubsw xmm0, xmm2 // 16 bit - psrlw xmm0, 7 // 8.7 fixed point to low 8 bits. - packuswb xmm0, xmm0 // 8 bits - movd ebx, xmm0 - mov [edi], bl - - xloop99: - - pop edi - pop esi - pop ebx - ret - } -} - -// Reads 16 pixels, duplicates them and writes 32 pixels. -__declspec(naked) -void ScaleColsUp2_SSE2(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx) { - __asm { - mov edx, [esp + 4] // dst_ptr - mov eax, [esp + 8] // src_ptr - mov ecx, [esp + 12] // dst_width - - wloop: - movdqu xmm0, [eax] - lea eax, [eax + 16] - movdqa xmm1, xmm0 - punpcklbw xmm0, xmm0 - punpckhbw xmm1, xmm1 - movdqu [edx], xmm0 - movdqu [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 32 - jg wloop - - ret - } -} - -// Reads 8 pixels, throws half away and writes 4 even pixels (0, 2, 4, 6) -__declspec(naked) -void ScaleARGBRowDown2_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - __asm { - mov eax, [esp + 4] // src_argb - // src_stride ignored - mov edx, [esp + 12] // dst_argb - mov ecx, [esp + 16] // dst_width - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - shufps xmm0, xmm1, 0xdd - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg wloop - - ret - } -} - -// Blends 8x1 rectangle to 4x1. -__declspec(naked) -void ScaleARGBRowDown2Linear_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - __asm { - mov eax, [esp + 4] // src_argb - // src_stride ignored - mov edx, [esp + 12] // dst_argb - mov ecx, [esp + 16] // dst_width - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - lea eax, [eax + 32] - movdqa xmm2, xmm0 - shufps xmm0, xmm1, 0x88 // even pixels - shufps xmm2, xmm1, 0xdd // odd pixels - pavgb xmm0, xmm2 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg wloop - - ret - } -} - -// Blends 8x2 rectangle to 4x1. -__declspec(naked) -void ScaleARGBRowDown2Box_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width) { - __asm { - push esi - mov eax, [esp + 4 + 4] // src_argb - mov esi, [esp + 4 + 8] // src_stride - mov edx, [esp + 4 + 12] // dst_argb - mov ecx, [esp + 4 + 16] // dst_width - - wloop: - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + esi] - movdqu xmm3, [eax + esi + 16] - lea eax, [eax + 32] - pavgb xmm0, xmm2 // average rows - pavgb xmm1, xmm3 - movdqa xmm2, xmm0 // average columns (8 to 4 pixels) - shufps xmm0, xmm1, 0x88 // even pixels - shufps xmm2, xmm1, 0xdd // odd pixels - pavgb xmm0, xmm2 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg wloop - - pop esi - ret - } -} - -// Reads 4 pixels at a time. -__declspec(naked) -void ScaleARGBRowDownEven_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width) { - __asm { - push ebx - push edi - mov eax, [esp + 8 + 4] // src_argb - // src_stride ignored - mov ebx, [esp + 8 + 12] // src_stepx - mov edx, [esp + 8 + 16] // dst_argb - mov ecx, [esp + 8 + 20] // dst_width - lea ebx, [ebx * 4] - lea edi, [ebx + ebx * 2] - - wloop: - movd xmm0, [eax] - movd xmm1, [eax + ebx] - punpckldq xmm0, xmm1 - movd xmm2, [eax + ebx * 2] - movd xmm3, [eax + edi] - lea eax, [eax + ebx * 4] - punpckldq xmm2, xmm3 - punpcklqdq xmm0, xmm2 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg wloop - - pop edi - pop ebx - ret - } -} - -// Blends four 2x2 to 4x1. -__declspec(naked) -void ScaleARGBRowDownEvenBox_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width) { - __asm { - push ebx - push esi - push edi - mov eax, [esp + 12 + 4] // src_argb - mov esi, [esp + 12 + 8] // src_stride - mov ebx, [esp + 12 + 12] // src_stepx - mov edx, [esp + 12 + 16] // dst_argb - mov ecx, [esp + 12 + 20] // dst_width - lea esi, [eax + esi] // row1 pointer - lea ebx, [ebx * 4] - lea edi, [ebx + ebx * 2] - - wloop: - movq xmm0, qword ptr [eax] // row0 4 pairs - movhps xmm0, qword ptr [eax + ebx] - movq xmm1, qword ptr [eax + ebx * 2] - movhps xmm1, qword ptr [eax + edi] - lea eax, [eax + ebx * 4] - movq xmm2, qword ptr [esi] // row1 4 pairs - movhps xmm2, qword ptr [esi + ebx] - movq xmm3, qword ptr [esi + ebx * 2] - movhps xmm3, qword ptr [esi + edi] - lea esi, [esi + ebx * 4] - pavgb xmm0, xmm2 // average rows - pavgb xmm1, xmm3 - movdqa xmm2, xmm0 // average columns (8 to 4 pixels) - shufps xmm0, xmm1, 0x88 // even pixels - shufps xmm2, xmm1, 0xdd // odd pixels - pavgb xmm0, xmm2 - movdqu [edx], xmm0 - lea edx, [edx + 16] - sub ecx, 4 - jg wloop - - pop edi - pop esi - pop ebx - ret - } -} - -// Column scaling unfiltered. SSE2 version. -__declspec(naked) -void ScaleARGBCols_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - __asm { - push edi - push esi - mov edi, [esp + 8 + 4] // dst_argb - mov esi, [esp + 8 + 8] // src_argb - mov ecx, [esp + 8 + 12] // dst_width - movd xmm2, [esp + 8 + 16] // x - movd xmm3, [esp + 8 + 20] // dx - - pshufd xmm2, xmm2, 0 // x0 x0 x0 x0 - pshufd xmm0, xmm3, 0x11 // dx 0 dx 0 - paddd xmm2, xmm0 - paddd xmm3, xmm3 // 0, 0, 0, dx * 2 - pshufd xmm0, xmm3, 0x05 // dx * 2, dx * 2, 0, 0 - paddd xmm2, xmm0 // x3 x2 x1 x0 - paddd xmm3, xmm3 // 0, 0, 0, dx * 4 - pshufd xmm3, xmm3, 0 // dx * 4, dx * 4, dx * 4, dx * 4 - - pextrw eax, xmm2, 1 // get x0 integer. - pextrw edx, xmm2, 3 // get x1 integer. - - cmp ecx, 0 - jle xloop99 - sub ecx, 4 - jl xloop49 - - // 4 Pixel loop. - xloop4: - movd xmm0, [esi + eax * 4] // 1 source x0 pixels - movd xmm1, [esi + edx * 4] // 1 source x1 pixels - pextrw eax, xmm2, 5 // get x2 integer. - pextrw edx, xmm2, 7 // get x3 integer. - paddd xmm2, xmm3 // x += dx - punpckldq xmm0, xmm1 // x0 x1 - - movd xmm1, [esi + eax * 4] // 1 source x2 pixels - movd xmm4, [esi + edx * 4] // 1 source x3 pixels - pextrw eax, xmm2, 1 // get x0 integer. next iteration. - pextrw edx, xmm2, 3 // get x1 integer. next iteration. - punpckldq xmm1, xmm4 // x2 x3 - punpcklqdq xmm0, xmm1 // x0 x1 x2 x3 - movdqu [edi], xmm0 - lea edi, [edi + 16] - sub ecx, 4 // 4 pixels - jge xloop4 - - xloop49: - test ecx, 2 - je xloop29 - - // 2 Pixels. - movd xmm0, [esi + eax * 4] // 1 source x0 pixels - movd xmm1, [esi + edx * 4] // 1 source x1 pixels - pextrw eax, xmm2, 5 // get x2 integer. - punpckldq xmm0, xmm1 // x0 x1 - - movq qword ptr [edi], xmm0 - lea edi, [edi + 8] - - xloop29: - test ecx, 1 - je xloop99 - - // 1 Pixels. - movd xmm0, [esi + eax * 4] // 1 source x2 pixels - movd dword ptr [edi], xmm0 - xloop99: - - pop esi - pop edi - ret - } -} - -// Bilinear row filtering combines 2x1 -> 1x1. SSSE3 version. -// TODO(fbarchard): Port to Neon - -// Shuffle table for arranging 2 pixels into pairs for pmaddubsw -static uvec8 kShuffleColARGB = { - 0u, 4u, 1u, 5u, 2u, 6u, 3u, 7u, // bbggrraa 1st pixel - 8u, 12u, 9u, 13u, 10u, 14u, 11u, 15u // bbggrraa 2nd pixel -}; - -// Shuffle table for duplicating 2 fractions into 8 bytes each -static uvec8 kShuffleFractions = { - 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 4u, 4u, 4u, 4u, 4u, 4u, 4u, 4u, -}; - -__declspec(naked) -void ScaleARGBFilterCols_SSSE3(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - __asm { - push esi - push edi - mov edi, [esp + 8 + 4] // dst_argb - mov esi, [esp + 8 + 8] // src_argb - mov ecx, [esp + 8 + 12] // dst_width - movd xmm2, [esp + 8 + 16] // x - movd xmm3, [esp + 8 + 20] // dx - movdqa xmm4, xmmword ptr kShuffleColARGB - movdqa xmm5, xmmword ptr kShuffleFractions - pcmpeqb xmm6, xmm6 // generate 0x007f for inverting fraction. - psrlw xmm6, 9 - pextrw eax, xmm2, 1 // get x0 integer. preroll - sub ecx, 2 - jl xloop29 - - movdqa xmm0, xmm2 // x1 = x0 + dx - paddd xmm0, xmm3 - punpckldq xmm2, xmm0 // x0 x1 - punpckldq xmm3, xmm3 // dx dx - paddd xmm3, xmm3 // dx * 2, dx * 2 - pextrw edx, xmm2, 3 // get x1 integer. preroll - - // 2 Pixel loop. - xloop2: - movdqa xmm1, xmm2 // x0, x1 fractions. - paddd xmm2, xmm3 // x += dx - movq xmm0, qword ptr [esi + eax * 4] // 2 source x0 pixels - psrlw xmm1, 9 // 7 bit fractions. - movhps xmm0, qword ptr [esi + edx * 4] // 2 source x1 pixels - pshufb xmm1, xmm5 // 0000000011111111 - pshufb xmm0, xmm4 // arrange pixels into pairs - pxor xmm1, xmm6 // 0..7f and 7f..0 - pmaddubsw xmm0, xmm1 // argb_argb 16 bit, 2 pixels. - pextrw eax, xmm2, 1 // get x0 integer. next iteration. - pextrw edx, xmm2, 3 // get x1 integer. next iteration. - psrlw xmm0, 7 // argb 8.7 fixed point to low 8 bits. - packuswb xmm0, xmm0 // argb_argb 8 bits, 2 pixels. - movq qword ptr [edi], xmm0 - lea edi, [edi + 8] - sub ecx, 2 // 2 pixels - jge xloop2 - - xloop29: - - add ecx, 2 - 1 - jl xloop99 - - // 1 pixel remainder - psrlw xmm2, 9 // 7 bit fractions. - movq xmm0, qword ptr [esi + eax * 4] // 2 source x0 pixels - pshufb xmm2, xmm5 // 00000000 - pshufb xmm0, xmm4 // arrange pixels into pairs - pxor xmm2, xmm6 // 0..7f and 7f..0 - pmaddubsw xmm0, xmm2 // argb 16 bit, 1 pixel. - psrlw xmm0, 7 - packuswb xmm0, xmm0 // argb 8 bits, 1 pixel. - movd [edi], xmm0 - - xloop99: - - pop edi - pop esi - ret - } -} - -// Reads 4 pixels, duplicates them and writes 8 pixels. -__declspec(naked) -void ScaleARGBColsUp2_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx) { - __asm { - mov edx, [esp + 4] // dst_argb - mov eax, [esp + 8] // src_argb - mov ecx, [esp + 12] // dst_width - - wloop: - movdqu xmm0, [eax] - lea eax, [eax + 16] - movdqa xmm1, xmm0 - punpckldq xmm0, xmm0 - punpckhdq xmm1, xmm1 - movdqu [edx], xmm0 - movdqu [edx + 16], xmm1 - lea edx, [edx + 32] - sub ecx, 8 - jg wloop - - ret - } -} - -// Divide num by div and return as 16.16 fixed point result. -__declspec(naked) -int FixedDiv_X86(int num, int div) { - __asm { - mov eax, [esp + 4] // num - cdq // extend num to 64 bits - shld edx, eax, 16 // 32.16 - shl eax, 16 - idiv dword ptr [esp + 8] - ret - } -} - -// Divide num by div and return as 16.16 fixed point result. -__declspec(naked) -int FixedDiv1_X86(int num, int div) { - __asm { - mov eax, [esp + 4] // num - mov ecx, [esp + 8] // denom - cdq // extend num to 64 bits - shld edx, eax, 16 // 32.16 - shl eax, 16 - sub eax, 0x00010001 - sbb edx, 0 - sub ecx, 1 - idiv ecx - ret - } -} -#endif // !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif diff --git a/telegramgallery/src/main/cpp/libyuv/source/video_common.cc b/telegramgallery/src/main/cpp/libyuv/source/video_common.cc deleted file mode 100644 index 00fb71e..0000000 --- a/telegramgallery/src/main/cpp/libyuv/source/video_common.cc +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - - -#include "libyuv/video_common.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#define ARRAY_SIZE(x) (int)(sizeof(x) / sizeof(x[0])) - -struct FourCCAliasEntry { - uint32 alias; - uint32 canonical; -}; - -static const struct FourCCAliasEntry kFourCCAliases[] = { - {FOURCC_IYUV, FOURCC_I420}, - {FOURCC_YU12, FOURCC_I420}, - {FOURCC_YU16, FOURCC_I422}, - {FOURCC_YU24, FOURCC_I444}, - {FOURCC_YUYV, FOURCC_YUY2}, - {FOURCC_YUVS, FOURCC_YUY2}, // kCMPixelFormat_422YpCbCr8_yuvs - {FOURCC_HDYC, FOURCC_UYVY}, - {FOURCC_2VUY, FOURCC_UYVY}, // kCMPixelFormat_422YpCbCr8 - {FOURCC_JPEG, FOURCC_MJPG}, // Note: JPEG has DHT while MJPG does not. - {FOURCC_DMB1, FOURCC_MJPG}, - {FOURCC_BA81, FOURCC_BGGR}, // deprecated. - {FOURCC_RGB3, FOURCC_RAW }, - {FOURCC_BGR3, FOURCC_24BG}, - {FOURCC_CM32, FOURCC_BGRA}, // kCMPixelFormat_32ARGB - {FOURCC_CM24, FOURCC_RAW }, // kCMPixelFormat_24RGB - {FOURCC_L555, FOURCC_RGBO}, // kCMPixelFormat_16LE555 - {FOURCC_L565, FOURCC_RGBP}, // kCMPixelFormat_16LE565 - {FOURCC_5551, FOURCC_RGBO}, // kCMPixelFormat_16LE5551 -}; -// TODO(fbarchard): Consider mapping kCMPixelFormat_32BGRA to FOURCC_ARGB. -// {FOURCC_BGRA, FOURCC_ARGB}, // kCMPixelFormat_32BGRA - -LIBYUV_API -uint32 CanonicalFourCC(uint32 fourcc) { - int i; - for (i = 0; i < ARRAY_SIZE(kFourCCAliases); ++i) { - if (kFourCCAliases[i].alias == fourcc) { - return kFourCCAliases[i].canonical; - } - } - // Not an alias, so return it as-is. - return fourcc; -} - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/AnimatedFileDrawable.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/AnimatedFileDrawable.java index 11531f8..5a9916e 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/AnimatedFileDrawable.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/AnimatedFileDrawable.java @@ -15,35 +15,38 @@ import android.os.Looper; import android.view.View; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; import java.io.File; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; -public class AnimatedFileDrawable extends BitmapDrawable implements Animatable { - - static { - System.loadLibrary("gly"); - } +import static com.tangxiaolv.telegramgallery.utils.VideoUtils.createDecoder; +import static com.tangxiaolv.telegramgallery.utils.VideoUtils.destroyDecoder; +import static com.tangxiaolv.telegramgallery.utils.VideoUtils.getVideoFrame; - private static native int createDecoder(String src, int[] params); - private static native void destroyDecoder(int ptr); - private static native int getVideoFrame(int ptr, Bitmap bitmap, int[] params); +public class AnimatedFileDrawable extends BitmapDrawable implements Animatable { private long lastFrameTime; private int lastTimeStamp; private int invalidateAfter = 50; - private final int[] metaData = new int[3]; + private final int[] metaData = new int[4];//0 with 1 heigh 2 rotate private Runnable loadFrameTask; private Bitmap renderingBitmap; private Bitmap nextRenderingBitmap; private Bitmap backgroundBitmap; private boolean destroyWhenDone; private boolean decoderCreated; + private boolean decodeSingleFrame; + private boolean singleFrameDecoded; private File path; private boolean recycleWithSecond; + private long lastFrameDecodeTime; + + private RectF actualDrawRect = new RectF(); + private BitmapShader renderingShader; private BitmapShader nextRenderingShader; private BitmapShader backgroundShader; @@ -56,7 +59,7 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable { private float scaleX = 1.0f; private float scaleY = 1.0f; private boolean applyTransformation; - private final android.graphics.Rect dstRect = new android.graphics.Rect(); + private final Rect dstRect = new Rect(); private static final Handler uiHandler = new Handler(Looper.getMainLooper()); private volatile boolean isRunning; private volatile boolean isRecycled; @@ -85,27 +88,33 @@ public void run() { nativePtr = 0; } if (nativePtr == 0) { + if (renderingBitmap != null) { + renderingBitmap.recycle(); + renderingBitmap = null; + } if (backgroundBitmap != null) { backgroundBitmap.recycle(); backgroundBitmap = null; } return; } + singleFrameDecoded = true; loadFrameTask = null; nextRenderingBitmap = backgroundBitmap; nextRenderingShader = backgroundShader; - if (metaData[2] < lastTimeStamp) { + if (metaData[3] < lastTimeStamp) { lastTimeStamp = 0; } - if (metaData[2] - lastTimeStamp != 0) { - invalidateAfter = metaData[2] - lastTimeStamp; + if (metaData[3] - lastTimeStamp != 0) { + invalidateAfter = metaData[3] - lastTimeStamp; } - lastTimeStamp = metaData[2]; + lastTimeStamp = metaData[3]; if (secondParentView != null) { secondParentView.invalidate(); } else if (parentView != null) { parentView.invalidate(); } + scheduleNextGetFrame(); } }; @@ -129,7 +138,9 @@ public void run() { } } if (backgroundBitmap != null) { + lastFrameDecodeTime = System.currentTimeMillis(); getVideoFrame(nativePtr, backgroundBitmap, metaData); + } } catch (Throwable e) { e.printStackTrace(); @@ -158,10 +169,6 @@ public AnimatedFileDrawable(File file, boolean createDecoder) { } } - protected void postToDecodeQueue(Runnable runnable) { - executor.execute(runnable); - } - public void setParentView(View view) { parentView = view; } @@ -173,6 +180,14 @@ public void setSecondParentView(View view) { } } + //添加 + public void setAllowDecodeSingleFrame(boolean value) { + decodeSingleFrame = value; + if (decodeSingleFrame) { + scheduleNextGetFrame(); + } + } + public void recycle() { if (secondParentView != null) { recycleWithSecond = true; @@ -185,6 +200,10 @@ public void recycle() { destroyDecoder(nativePtr); nativePtr = 0; } + if (renderingBitmap != null) { + renderingBitmap.recycle(); + renderingBitmap = null; + } if (nextRenderingBitmap != null) { nextRenderingBitmap.recycle(); nextRenderingBitmap = null; @@ -192,10 +211,6 @@ public void recycle() { } else { destroyWhenDone = true; } - if (renderingBitmap != null) { - renderingBitmap.recycle(); - renderingBitmap = null; - } } protected static void runOnUiThread(Runnable task) { @@ -226,17 +241,19 @@ public void start() { return; } isRunning = true; - if (renderingBitmap == null) { - scheduleNextGetFrame(); - } + scheduleNextGetFrame(); runOnUiThread(mStartTask); } private void scheduleNextGetFrame() { - if (loadFrameTask != null || nativePtr == 0 && decoderCreated || destroyWhenDone) { + if (loadFrameTask != null || nativePtr == 0 && decoderCreated || destroyWhenDone || !isRunning && (!decodeSingleFrame || decodeSingleFrame && singleFrameDecoded)) { return; } - postToDecodeQueue(loadFrameTask = loadFrameRunnable); + long ms = 0; + if (lastFrameDecodeTime != 0) { + ms = Math.min(invalidateAfter, Math.max(0, invalidateAfter - (System.currentTimeMillis() - lastFrameDecodeTime))); + } + executor.schedule(loadFrameTask = loadFrameRunnable, ms, TimeUnit.MILLISECONDS); } @Override @@ -251,12 +268,12 @@ public boolean isRunning() { @Override public int getIntrinsicHeight() { - return decoderCreated ? metaData[1] : AndroidUtilities.dp(100); + return decoderCreated ? (metaData[2] == 90 || metaData[2] == 270 ? metaData[0] : metaData[1]) : AndroidUtilities.dp(100); } @Override public int getIntrinsicWidth() { - return decoderCreated ? metaData[0] : AndroidUtilities.dp(100); + return decoderCreated ? (metaData[2] == 90 || metaData[2] == 270 ? metaData[1] : metaData[0]) : AndroidUtilities.dp(100); } @Override @@ -270,31 +287,42 @@ public void draw(Canvas canvas) { if (nativePtr == 0 && decoderCreated || destroyWhenDone) { return; } + long now = System.currentTimeMillis(); if (isRunning) { if (renderingBitmap == null && nextRenderingBitmap == null) { scheduleNextGetFrame(); - } else if (Math.abs(System.currentTimeMillis() - lastFrameTime) >= invalidateAfter) { + } else if (Math.abs(now - lastFrameTime) >= invalidateAfter) { if (nextRenderingBitmap != null) { - scheduleNextGetFrame(); renderingBitmap = nextRenderingBitmap; renderingShader = nextRenderingShader; nextRenderingBitmap = null; nextRenderingShader = null; - lastFrameTime = System.currentTimeMillis(); + lastFrameTime = now; } } + } else if (!isRunning && decodeSingleFrame && Math.abs(now - lastFrameTime) >= invalidateAfter && nextRenderingBitmap != null) { + renderingBitmap = nextRenderingBitmap; + renderingShader = nextRenderingShader; + nextRenderingBitmap = null; + nextRenderingShader = null; + lastFrameTime = now; } if (renderingBitmap != null) { if (applyTransformation) { + int bitmapW = renderingBitmap.getWidth(); + int bitmapH = renderingBitmap.getHeight(); + if (metaData[2] == 90 || metaData[2] == 270) { + int temp = bitmapW; + bitmapW = bitmapH; + bitmapH = temp; + } dstRect.set(getBounds()); - scaleX = (float) dstRect.width() / renderingBitmap.getWidth(); - scaleY = (float) dstRect.height() / renderingBitmap.getHeight(); + scaleX = (float) dstRect.width() / bitmapW; + scaleY = (float) dstRect.height() / bitmapH; applyTransformation = false; } if (roundRadius != 0) { - int bitmapW = renderingBitmap.getWidth(); - int bitmapH = renderingBitmap.getHeight(); float scale = Math.max(scaleX, scaleY); if (renderingShader == null) { @@ -304,35 +332,55 @@ public void draw(Canvas canvas) { roundRect.set(dstRect); shaderMatrix.reset(); if (Math.abs(scaleX - scaleY) > 0.00001f) { - int w = (int) Math.floor(dstRect.width() / scale); - int h = (int) Math.floor(dstRect.height() / scale); - bitmapRect.set((bitmapW - w) / 2, (bitmapH - h) / 2, w, h); - shaderMatrix.setRectToRect(bitmapRect, roundRect, Matrix.ScaleToFit.START); + int w; + int h; + if (metaData[2] == 90 || metaData[2] == 270) { + w = (int) Math.floor(dstRect.height() / scale); + h = (int) Math.floor(dstRect.width() / scale); + } else { + w = (int) Math.floor(dstRect.width() / scale); + h = (int) Math.floor(dstRect.height() / scale); + } + bitmapRect.set((renderingBitmap.getWidth() - w) / 2, (renderingBitmap.getHeight() - h) / 2, w, h); + AndroidUtilities.setRectToRect(shaderMatrix, bitmapRect, roundRect, metaData[2], Matrix.ScaleToFit.START); } else { bitmapRect.set(0, 0, renderingBitmap.getWidth(), renderingBitmap.getHeight()); - shaderMatrix.setRectToRect(bitmapRect, roundRect, Matrix.ScaleToFit.FILL); + AndroidUtilities.setRectToRect(shaderMatrix, bitmapRect, roundRect, metaData[2], Matrix.ScaleToFit.FILL); } renderingShader.setLocalMatrix(shaderMatrix); - canvas.drawRoundRect(roundRect, roundRadius, roundRadius, getPaint()); + + canvas.drawRoundRect(actualDrawRect, roundRadius, roundRadius, getPaint()); } else { canvas.translate(dstRect.left, dstRect.top); + if (metaData[2] == 90) { + canvas.rotate(90); + canvas.translate(0, -dstRect.width()); + } else if (metaData[2] == 180) { + canvas.rotate(180); + canvas.translate(-dstRect.width(), -dstRect.height()); + } else if (metaData[2] == 270) { + canvas.rotate(270); + canvas.translate(-dstRect.height(), 0); + } canvas.scale(scaleX, scaleY); canvas.drawBitmap(renderingBitmap, 0, 0, getPaint()); } if (isRunning) { - uiHandler.postDelayed(mInvalidateTask, invalidateAfter); + long timeToNextFrame = Math.max(1, invalidateAfter - (now - lastFrameTime) - 17); + uiHandler.removeCallbacks(mInvalidateTask); + uiHandler.postDelayed(mInvalidateTask, Math.min(timeToNextFrame, invalidateAfter)); } } } @Override public int getMinimumHeight() { - return decoderCreated ? metaData[1] : AndroidUtilities.dp(100); + return decoderCreated ? (metaData[2] == 90 || metaData[2] == 270 ? metaData[0] : metaData[1]) : AndroidUtilities.dp(100); } @Override public int getMinimumWidth() { - return decoderCreated ? metaData[0] : AndroidUtilities.dp(100); + return decoderCreated ? (metaData[2] == 90 || metaData[2] == 270 ? metaData[1] : metaData[0]) : AndroidUtilities.dp(100); } public Bitmap getAnimatedBitmap() { @@ -344,6 +392,11 @@ public Bitmap getAnimatedBitmap() { return null; } + //添加 + public void setActualDrawRect(int x, int y, int width, int height) { + actualDrawRect.set(x, y, x + width, y + height); + } + public void setRoundRadius(int value) { roundRadius = value; getPaint().setFlags(Paint.ANTI_ALIAS_FLAG); @@ -353,6 +406,12 @@ public boolean hasBitmap() { return nativePtr != 0 && (renderingBitmap != null || nextRenderingBitmap != null); } + //添加 + public int getOrientation() { + //return metaData[2]; + return 0; + } + public AnimatedFileDrawable makeCopy() { AnimatedFileDrawable drawable = new AnimatedFileDrawable(path, false); drawable.metaData[0] = metaData[0]; diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoCropView.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoCropView.java deleted file mode 100644 index 269062d..0000000 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoCropView.java +++ /dev/null @@ -1,615 +0,0 @@ -package com.tangxiaolv.telegramgallery.Components; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.RectF; -import android.view.MotionEvent; -import android.widget.FrameLayout; - -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; - -public class PhotoCropView extends FrameLayout { - - public interface PhotoCropViewDelegate { - void needMoveImageTo(float x, float y, float s, boolean animated); - Bitmap getBitmap(); - } - - private boolean freeformCrop = true; - private Paint rectPaint; - private Paint circlePaint; - private Paint halfPaint; - private Paint shadowPaint; - private float rectSizeX = 600; - private float rectSizeY = 600; - private int draggingState = 0; - private int orientation; - private float oldX = 0, oldY = 0; - private int bitmapWidth = 1, bitmapHeight = 1, bitmapX, bitmapY; - private float rectX = -1, rectY = -1; - private float bitmapGlobalScale = 1; - private float bitmapGlobalX = 0; - private float bitmapGlobalY = 0; - private PhotoCropViewDelegate delegate; - private Bitmap bitmapToEdit; - - private RectF animationStartValues; - private RectF animationEndValues; - private Runnable animationRunnable; - - public PhotoCropView(Context context) { - super(context); - - rectPaint = new Paint(); - rectPaint.setColor(0xb2ffffff); - rectPaint.setStrokeWidth(AndroidUtilities.dp(2)); - rectPaint.setStyle(Paint.Style.STROKE); - circlePaint = new Paint(); - circlePaint.setColor(0xffffffff); - halfPaint = new Paint(); - halfPaint.setColor(0x7f000000); - shadowPaint = new Paint(); - shadowPaint.setColor(0x1a000000); - setWillNotDraw(false); - } - - public void setBitmap(Bitmap bitmap, int rotation, boolean freeform) { - bitmapToEdit = bitmap; - rectSizeX = 600; - rectSizeY = 600; - draggingState = 0; - oldX = 0; - oldY = 0; - bitmapWidth = 1; - bitmapHeight = 1; - rectX = -1; - rectY = -1; - freeformCrop = freeform; - orientation = rotation; - requestLayout(); - } - - public void setOrientation(int rotation) { - orientation = rotation; - rectX = -1; - rectY = -1; - rectSizeX = 600; - rectSizeY = 600; - delegate.needMoveImageTo(0, 0, 1, false); - requestLayout(); - - } - - public boolean onTouch(MotionEvent motionEvent) { - if (motionEvent == null) { - draggingState = 0; - return false; - } - float x = motionEvent.getX(); - float y = motionEvent.getY(); - int cornerSide = AndroidUtilities.dp(20); - if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { - if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide < y && rectY + cornerSide > y) { - draggingState = 1; - } else if (rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x && rectY - cornerSide < y && rectY + cornerSide > y) { - draggingState = 2; - } else if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { - draggingState = 3; - } else if (rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { - draggingState = 4; - } else { - if (freeformCrop) { - if (rectX + cornerSide < x && rectX - cornerSide + rectSizeX > x && rectY - cornerSide < y && rectY + cornerSide > y) { - draggingState = 5; - } else if (rectY + cornerSide < y && rectY - cornerSide + rectSizeY > y && rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x) { - draggingState = 6; - } else if (rectY + cornerSide < y && rectY - cornerSide + rectSizeY > y && rectX - cornerSide < x && rectX + cornerSide > x) { - draggingState = 7; - } else if (rectX + cornerSide < x && rectX - cornerSide + rectSizeX > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { - draggingState = 8; - } - } else { - draggingState = 0; - } - } - if (draggingState != 0) { - cancelAnimationRunnable(); - PhotoCropView.this.requestDisallowInterceptTouchEvent(true); - } - oldX = x; - oldY = y; - } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) { - if (draggingState != 0) { - draggingState = 0; - startAnimationRunnable(); - return true; - } - } else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE && draggingState != 0) { - float diffX = x - oldX; - float diffY = y - oldY; - float bitmapScaledWidth = bitmapWidth * bitmapGlobalScale; - float bitmapScaledHeight = bitmapHeight * bitmapGlobalScale; - float bitmapStartX = (getWidth() - AndroidUtilities.dp(28) - bitmapScaledWidth) / 2 + bitmapGlobalX + AndroidUtilities.dp(14); - float bitmapStartY = (getHeight() - AndroidUtilities.dp(28) - bitmapScaledHeight) / 2 + bitmapGlobalY + AndroidUtilities.dp(14); - float bitmapEndX = bitmapStartX + bitmapScaledWidth; - float bitmapEndY = bitmapStartY + bitmapScaledHeight; - - float minSide = AndroidUtilities.getPixelsInCM(0.9f, true); - - if (draggingState == 1 || draggingState == 5) { - if (draggingState != 5) { - if (rectSizeX - diffX < minSide) { - diffX = rectSizeX - minSide; - } - if (rectX + diffX < bitmapX) { - diffX = bitmapX - rectX; - } - if (rectX + diffX < bitmapStartX) { - bitmapGlobalX -= bitmapStartX - rectX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - } - if (!freeformCrop) { - if (rectY + diffX < bitmapY) { - diffX = bitmapY - rectY; - } - if (rectY + diffX < bitmapStartY) { - bitmapGlobalY -= bitmapStartY - rectY - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectX += diffX; - rectY += diffX; - rectSizeX -= diffX; - rectSizeY -= diffX; - } else { - if (rectSizeY - diffY < minSide) { - diffY = rectSizeY - minSide; - } - if (rectY + diffY < bitmapY) { - diffY = bitmapY - rectY; - } - if (rectY + diffY < bitmapStartY) { - bitmapGlobalY -= bitmapStartY - rectY - diffY; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - if (draggingState != 5) { - rectX += diffX; - rectSizeX -= diffX; - } - rectY += diffY; - rectSizeY -= diffY; - } - } else if (draggingState == 2 || draggingState == 6) { - if (rectSizeX + diffX < minSide) { - diffX = -(rectSizeX - minSide); - } - if (rectX + rectSizeX + diffX > bitmapX + bitmapWidth) { - diffX = bitmapX + bitmapWidth - rectX - rectSizeX; - } - if (rectX + rectSizeX + diffX > bitmapEndX) { - bitmapGlobalX -= bitmapEndX - rectX - rectSizeX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - if (!freeformCrop) { - if (rectY - diffX < bitmapY) { - diffX = rectY - bitmapY; - } - if (rectY - diffX < bitmapStartY) { - bitmapGlobalY -= bitmapStartY - rectY + diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectY -= diffX; - rectSizeX += diffX; - rectSizeY += diffX; - } else { - if (draggingState != 6) { - if (rectSizeY - diffY < minSide) { - diffY = rectSizeY - minSide; - } - if (rectY + diffY < bitmapY) { - diffY = bitmapY - rectY; - } - if (rectY + diffY < bitmapStartY) { - bitmapGlobalY -= bitmapStartY - rectY - diffY; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectY += diffY; - rectSizeY -= diffY; - } - rectSizeX += diffX; - } - } else if (draggingState == 3 || draggingState == 7) { - if (rectSizeX - diffX < minSide) { - diffX = rectSizeX - minSide; - } - if (rectX + diffX < bitmapX) { - diffX = bitmapX - rectX; - } - if (rectX + diffX < bitmapStartX) { - bitmapGlobalX -= bitmapStartX - rectX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - if (!freeformCrop) { - if (rectY + rectSizeX - diffX > bitmapY + bitmapHeight) { - diffX = rectY + rectSizeX - bitmapY - bitmapHeight; - } - if (rectY + rectSizeX - diffX > bitmapEndY) { - bitmapGlobalY -= bitmapEndY - rectY - rectSizeX + diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectX += diffX; - rectSizeX -= diffX; - rectSizeY -= diffX; - } else { - if (draggingState != 7) { - if (rectY + rectSizeY + diffY > bitmapY + bitmapHeight) { - diffY = bitmapY + bitmapHeight - rectY - rectSizeY; - } - if (rectY + rectSizeY + diffY > bitmapEndY) { - bitmapGlobalY -= bitmapEndY - rectY - rectSizeY - diffY; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectSizeY += diffY; - if (rectSizeY < minSide) { - rectSizeY = minSide; - } - } - rectX += diffX; - rectSizeX -= diffX; - } - } else if (draggingState == 4 || draggingState == 8) { - if (draggingState != 8) { - if (rectX + rectSizeX + diffX > bitmapX + bitmapWidth) { - diffX = bitmapX + bitmapWidth - rectX - rectSizeX; - } - if (rectX + rectSizeX + diffX > bitmapEndX) { - bitmapGlobalX -= bitmapEndX - rectX - rectSizeX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - } - if (!freeformCrop) { - if (rectY + rectSizeX + diffX > bitmapY + bitmapHeight) { - diffX = bitmapY + bitmapHeight - rectY - rectSizeX; - } - if (rectY + rectSizeX + diffX > bitmapEndY) { - bitmapGlobalY -= bitmapEndY - rectY - rectSizeX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectSizeX += diffX; - rectSizeY += diffX; - } else { - if (rectY + rectSizeY + diffY > bitmapY + bitmapHeight) { - diffY = bitmapY + bitmapHeight - rectY - rectSizeY; - } - if (rectY + rectSizeY + diffY > bitmapEndY) { - bitmapGlobalY -= bitmapEndY - rectY - rectSizeY - diffY; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - if (draggingState != 8) { - rectSizeX += diffX; - } - rectSizeY += diffY; - } - if (rectSizeX < minSide) { - rectSizeX = minSide; - } - if (rectSizeY < minSide) { - rectSizeY = minSide; - } - } - - oldX = x; - oldY = y; - invalidate(); - } - return draggingState != 0; - } - - public float getRectX() { - return rectX - AndroidUtilities.dp(14); - } - - public float getRectY() { - return rectY - AndroidUtilities.dp(14); - } - - public float getRectSizeX() { - return rectSizeX; - } - - public float getRectSizeY() { - return rectSizeY; - } - - public float getBitmapX() { - return bitmapX - AndroidUtilities.dp(14); - } - - public float getBitmapY() { - return bitmapY - AndroidUtilities.dp(14); - } - - public float getLimitX() { - return rectX - ((int) Math.max(0, Math.ceil((getWidth() - AndroidUtilities.dp(28) - bitmapWidth * bitmapGlobalScale) / 2)) + AndroidUtilities.dp(14)); - } - - public float getLimitY() { - return rectY - ((int) Math.max(0, Math.ceil((getHeight() - AndroidUtilities.dp(28) - bitmapHeight * bitmapGlobalScale) / 2)) + AndroidUtilities.dp(14)); - } - - public float getLimitWidth() { - return getWidth() - AndroidUtilities.dp(14) - rectX - (int) Math.max(0, Math.ceil((getWidth() - AndroidUtilities.dp(28) - bitmapWidth * bitmapGlobalScale) / 2)) - rectSizeX; - } - - public float getLimitHeight() { - return getHeight() - AndroidUtilities.dp(14) - rectY - (int) Math.max(0, Math.ceil((getHeight() - AndroidUtilities.dp(28) - bitmapHeight * bitmapGlobalScale) / 2)) - rectSizeY; - } - - private Bitmap createBitmap(int x, int y, int w, int h) { - Bitmap newBimap = delegate.getBitmap(); - if (newBimap != null) { - bitmapToEdit = newBimap; - } - - Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG); - - Matrix matrix = new Matrix(); - matrix.setTranslate(-bitmapToEdit.getWidth() / 2, -bitmapToEdit.getHeight() / 2); - matrix.postRotate(orientation); - if (orientation % 360 == 90 || orientation % 360 == 270) { - matrix.postTranslate(bitmapToEdit.getHeight() / 2 - x, bitmapToEdit.getWidth() / 2 - y); - } else { - matrix.postTranslate(bitmapToEdit.getWidth() / 2 - x, bitmapToEdit.getHeight() / 2 - y); - } - canvas.drawBitmap(bitmapToEdit, matrix, paint); - try { - canvas.setBitmap(null); - } catch (Exception e) { - //don't promt, this will crash on 2.x - } - - return bitmap; - } - - public Bitmap getBitmap() { - Bitmap newBimap = delegate.getBitmap(); - if (newBimap != null) { - bitmapToEdit = newBimap; - } - - float bitmapScaledWidth = bitmapWidth * bitmapGlobalScale; - float bitmapScaledHeight = bitmapHeight * bitmapGlobalScale; - float bitmapStartX = (getWidth() - AndroidUtilities.dp(28) - bitmapScaledWidth) / 2 + bitmapGlobalX + AndroidUtilities.dp(14); - float bitmapStartY = (getHeight() - AndroidUtilities.dp(28) - bitmapScaledHeight) / 2 + bitmapGlobalY + AndroidUtilities.dp(14); - - float percX = (rectX - bitmapStartX) / bitmapScaledWidth; - float percY = (rectY - bitmapStartY) / bitmapScaledHeight; - float percSizeX = rectSizeX / bitmapScaledWidth; - float percSizeY = rectSizeY / bitmapScaledHeight; - - int width; - int height; - if (orientation % 360 == 90 || orientation % 360 == 270) { - width = bitmapToEdit.getHeight(); - height = bitmapToEdit.getWidth(); - } else { - width = bitmapToEdit.getWidth(); - height = bitmapToEdit.getHeight(); - } - - int x = (int) (percX * width); - int y = (int) (percY * height); - int sizeX = (int) (percSizeX * width); - int sizeY = (int) (percSizeY * height); - if (x < 0) { - x = 0; - } - if (y < 0) { - y = 0; - } - if (x + sizeX > width) { - sizeX = width - x; - } - if (y + sizeY > height) { - sizeY = height - y; - } - try { - return createBitmap(x, y, sizeX, sizeY); - } catch (Throwable e) { - e.printStackTrace(); - System.gc(); - try { - return createBitmap(x, y, sizeX, sizeY); - } catch (Throwable e2) { - e.printStackTrace(); - } - } - return null; - } - - @Override - protected void onDraw(Canvas canvas) { - canvas.drawRect(0, 0, getWidth(), rectY, halfPaint); - canvas.drawRect(0, rectY, rectX, rectY + rectSizeY, halfPaint); - canvas.drawRect(rectX + rectSizeX, rectY, getWidth(), rectY + rectSizeY, halfPaint); - canvas.drawRect(0, rectY + rectSizeY, getWidth(), getHeight(), halfPaint); - - int side = AndroidUtilities.dp(1); - canvas.drawRect(rectX - side * 2, rectY - side * 2, rectX - side * 2 + AndroidUtilities.dp(20), rectY, circlePaint); - canvas.drawRect(rectX - side * 2, rectY - side * 2, rectX, rectY - side * 2 + AndroidUtilities.dp(20), circlePaint); - - canvas.drawRect(rectX + rectSizeX + side * 2 - AndroidUtilities.dp(20), rectY - side * 2, rectX + rectSizeX + side * 2, rectY, circlePaint); - canvas.drawRect(rectX + rectSizeX, rectY - side * 2, rectX + rectSizeX + side * 2, rectY - side * 2 + AndroidUtilities.dp(20), circlePaint); - - canvas.drawRect(rectX - side * 2, rectY + rectSizeY + side * 2 - AndroidUtilities.dp(20), rectX, rectY + rectSizeY + side * 2, circlePaint); - canvas.drawRect(rectX - side * 2, rectY + rectSizeY, rectX - side * 2 + AndroidUtilities.dp(20), rectY + rectSizeY + side * 2, circlePaint); - - canvas.drawRect(rectX + rectSizeX + side * 2 - AndroidUtilities.dp(20), rectY + rectSizeY, rectX + rectSizeX + side * 2, rectY + rectSizeY + side * 2, circlePaint); - canvas.drawRect(rectX + rectSizeX, rectY + rectSizeY + side * 2 - AndroidUtilities.dp(20), rectX + rectSizeX + side * 2, rectY + rectSizeY + side * 2, circlePaint); - - for (int a = 1; a < 3; a++) { - canvas.drawRect(rectX + rectSizeX / 3 * a - side, rectY, rectX + side * 2 + rectSizeX / 3 * a, rectY + rectSizeY, shadowPaint); - canvas.drawRect(rectX, rectY + rectSizeY / 3 * a - side, rectX + rectSizeX, rectY + rectSizeY / 3 * a + side * 2, shadowPaint); - } - - for (int a = 1; a < 3; a++) { - canvas.drawRect(rectX + rectSizeX / 3 * a, rectY, rectX + side + rectSizeX / 3 * a, rectY + rectSizeY, circlePaint); - canvas.drawRect(rectX, rectY + rectSizeY / 3 * a, rectX + rectSizeX, rectY + rectSizeY / 3 * a + side, circlePaint); - } - - canvas.drawRect(rectX, rectY, rectX + rectSizeX, rectY + rectSizeY, rectPaint); - } - - public void setBitmapParams(float scale, float x, float y) { - bitmapGlobalScale = scale; - bitmapGlobalX = x; - bitmapGlobalY = y; - } - - public void startAnimationRunnable() { - if (animationRunnable != null) { - return; - } - animationRunnable = new Runnable() { - @Override - public void run() { - if (animationRunnable == this) { - animationRunnable = null; - moveToFill(true); - } - } - }; - AndroidUtilities.runOnUIThread(animationRunnable, 1500); - } - - public void cancelAnimationRunnable() { - if (animationRunnable != null) { - AndroidUtilities.cancelRunOnUIThread(animationRunnable); - animationRunnable = null; - animationStartValues = null; - animationEndValues = null; - } - } - - public void setAnimationProgress(float animationProgress) { - if (animationStartValues != null) { - if (animationProgress == 1) { - rectX = animationEndValues.left; - rectY = animationEndValues.top; - rectSizeX = animationEndValues.right; - rectSizeY = animationEndValues.bottom; - animationStartValues = null; - animationEndValues = null; - } else { - rectX = animationStartValues.left + (animationEndValues.left - animationStartValues.left) * animationProgress; - rectY = animationStartValues.top + (animationEndValues.top - animationStartValues.top) * animationProgress; - rectSizeX = animationStartValues.right + (animationEndValues.right - animationStartValues.right) * animationProgress; - rectSizeY = animationStartValues.bottom + (animationEndValues.bottom - animationStartValues.bottom) * animationProgress; - } - invalidate(); - } - } - - public void moveToFill(boolean animated) { - float scaleToX = bitmapWidth / rectSizeX; - float scaleToY = bitmapHeight / rectSizeY; - float scaleTo = scaleToX > scaleToY ? scaleToY : scaleToX; - if (scaleTo > 1 && scaleTo * bitmapGlobalScale > 3) { - scaleTo = 3 / bitmapGlobalScale; - } else if (scaleTo < 1 && scaleTo * bitmapGlobalScale < 1) { - scaleTo = 1 / bitmapGlobalScale; - } - float newSizeX = rectSizeX * scaleTo; - float newSizeY = rectSizeY * scaleTo; - float newX = (getWidth() - AndroidUtilities.dp(28) - newSizeX) / 2 + AndroidUtilities.dp(14); - float newY = (getHeight() - AndroidUtilities.dp(28) - newSizeY) / 2 + AndroidUtilities.dp(14); - animationStartValues = new RectF(rectX, rectY, rectSizeX, rectSizeY); - animationEndValues = new RectF(newX, newY, newSizeX, newSizeY); - - float newBitmapGlobalX = newX + getWidth() / 2 * (scaleTo - 1) + (bitmapGlobalX - rectX) * scaleTo; - float newBitmapGlobalY = newY + getHeight() / 2 * (scaleTo - 1) + (bitmapGlobalY - rectY) * scaleTo; - - delegate.needMoveImageTo(newBitmapGlobalX, newBitmapGlobalY, bitmapGlobalScale * scaleTo, animated); - } - - public void setDelegate(PhotoCropViewDelegate delegate) { - this.delegate = delegate; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - Bitmap newBimap = delegate.getBitmap(); - if (newBimap != null) { - bitmapToEdit = newBimap; - } - - if (bitmapToEdit == null) { - return; - } - - int viewWidth = getWidth() - AndroidUtilities.dp(28); - int viewHeight = getHeight() - AndroidUtilities.dp(28); - - float bitmapW; - float bitmapH; - if (orientation % 360 == 90 || orientation % 360 == 270) { - bitmapW = bitmapToEdit.getHeight(); - bitmapH = bitmapToEdit.getWidth(); - } else { - bitmapW = bitmapToEdit.getWidth(); - bitmapH = bitmapToEdit.getHeight(); - } - float scaleX = viewWidth / bitmapW; - float scaleY = viewHeight / bitmapH; - if (scaleX > scaleY) { - bitmapH = viewHeight; - bitmapW = (int) Math.ceil(bitmapW * scaleY); - } else { - bitmapW = viewWidth; - bitmapH = (int) Math.ceil(bitmapH * scaleX); - } - - float percX = (rectX - bitmapX) / bitmapWidth; - float percY = (rectY - bitmapY) / bitmapHeight; - float percSizeX = rectSizeX / bitmapWidth; - float percSizeY = rectSizeY / bitmapHeight; - bitmapWidth = (int) bitmapW; - bitmapHeight = (int) bitmapH; - - bitmapX = (int) Math.ceil((viewWidth - bitmapWidth) / 2 + AndroidUtilities.dp(14)); - bitmapY = (int) Math.ceil((viewHeight - bitmapHeight) / 2 + AndroidUtilities.dp(14)); - - if (rectX == -1 && rectY == -1) { - if (freeformCrop) { - rectY = bitmapY; - rectX = bitmapX; - rectSizeX = bitmapWidth; - rectSizeY = bitmapHeight; - } else { - if (bitmapWidth > bitmapHeight) { - rectY = bitmapY; - rectX = (viewWidth - bitmapHeight) / 2 + AndroidUtilities.dp(14); - rectSizeX = bitmapHeight; - rectSizeY = bitmapHeight; - } else { - rectX = bitmapX; - rectY = (viewHeight - bitmapWidth) / 2 + AndroidUtilities.dp(14); - rectSizeX = bitmapWidth; - rectSizeY = bitmapWidth; - } - } - } else { - rectX = percX * bitmapWidth + bitmapX; - rectY = percY * bitmapHeight + bitmapY; - rectSizeX = percSizeX * bitmapWidth; - rectSizeY = percSizeY * bitmapHeight; - } - } -} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PickerBottomLayout.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PickerBottomLayout.java deleted file mode 100644 index d113fd0..0000000 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PickerBottomLayout.java +++ /dev/null @@ -1,107 +0,0 @@ - -package com.tangxiaolv.telegramgallery.Components; - -import android.content.Context; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.tangxiaolv.telegramgallery.PhotoAlbumPickerActivity; -import com.tangxiaolv.telegramgallery.R; -import com.tangxiaolv.telegramgallery.Theme; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; - -public class PickerBottomLayout extends FrameLayout { - - public LinearLayout doneButton; - public TextView cancelButton; - public TextView doneButtonTextView; - public TextView doneButtonBadgeTextView; - - private boolean isDarkTheme; - - public PickerBottomLayout(Context context) { - this(context, true); - } - - public PickerBottomLayout(Context context, boolean darkTheme) { - super(context); - isDarkTheme = darkTheme; - - setBackgroundColor(isDarkTheme ? 0xff1a1a1a : 0xffffffff); - - cancelButton = new TextView(context); - cancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); - cancelButton.setTextColor(isDarkTheme ? 0xffffffff : 0xff007aff); - cancelButton.setGravity(Gravity.CENTER); - cancelButton.setBackgroundDrawable( - Theme.createBarSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR - : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); - cancelButton.setPadding(AndroidUtilities.dp(29), 0, AndroidUtilities.dp(29), 0); - cancelButton.setText(R.string.Preview); - // cancelButton.getPaint().setFakeBoldText(true); - addView(cancelButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, - LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - - doneButton = new LinearLayout(context); - doneButton.setOrientation(LinearLayout.HORIZONTAL); - doneButton.setBackgroundDrawable( - Theme.createBarSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR - : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); - doneButton.setPadding(AndroidUtilities.dp(29), 0, AndroidUtilities.dp(29), 0); - addView(doneButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, - LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); - - doneButtonBadgeTextView = new TextView(context); - // doneButtonBadgeTextView.getPaint().setFakeBoldText(true); - doneButtonBadgeTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); - doneButtonBadgeTextView.setTextColor(0xffffffff); - doneButtonBadgeTextView.setGravity(Gravity.CENTER); - doneButtonBadgeTextView.setBackgroundResource( - isDarkTheme ? R.drawable.photobadge_new : R.drawable.photobadge_new); - doneButtonBadgeTextView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), AndroidUtilities.dp(1)); - doneButton.addView(doneButtonBadgeTextView, LayoutHelper.createLinear(24, 24, Gravity.CENTER_VERTICAL, 0, 0, 8, 0)); - - doneButtonTextView = new TextView(context); - doneButtonTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); - doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : 0xff007aff); - doneButtonTextView.setGravity(Gravity.CENTER); - doneButtonTextView.setCompoundDrawablePadding(AndroidUtilities.dp(8)); - doneButtonTextView.setText(R.string.Send); - // doneButtonTextView.getPaint().setFakeBoldText(true); - doneButton.addView(doneButtonTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, - LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL)); - } - - public void updateSelectedCount(int count, boolean disable) { - if (count == 0) { - doneButtonBadgeTextView.setVisibility(View.GONE); - - if (disable) { - doneButtonTextView.setTextColor(0xff999999); - cancelButton.setTextColor(0xff999999); - doneButton.setEnabled(false); - cancelButton.setEnabled(false); - } else { - // doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : 0xff19a7e8); - doneButtonTextView.setTextColor( - PhotoAlbumPickerActivity.limitPickPhoto == 1 ? 0xffffffff : 0xff999999); - } - } else { - doneButtonTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - doneButtonBadgeTextView.setVisibility(View.VISIBLE); - doneButtonBadgeTextView.setText(String.format("%d", count)); - - doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : 0xff007aff); - cancelButton.setTextColor(isDarkTheme ? 0xffffffff : 0xff007aff); - if (disable) { - doneButton.setEnabled(true); - cancelButton.setEnabled(true); - } - } - } -} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Gallery.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Gallery.java index d0313ff..34df947 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Gallery.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Gallery.java @@ -4,10 +4,15 @@ import android.app.Application; import android.os.Handler; +/** + * 相册常量 + */ public class Gallery { public volatile static Application applicationContext; public volatile static Handler applicationHandler; + public static boolean sOriginChecked = false; + public static boolean sListItemClickable = true; public static void init(Application application) { if (applicationContext == null) { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/GalleryActivity.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/GalleryActivity.java index e1ba0d6..77b67f0 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/GalleryActivity.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/GalleryActivity.java @@ -6,12 +6,15 @@ import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; import android.view.KeyEvent; import android.widget.FrameLayout; +import android.widget.Toast; -import com.tangxiaolv.telegramgallery.Actionbar.ActionBarLayout; -import com.tangxiaolv.telegramgallery.Actionbar.BaseFragment; -import com.tangxiaolv.telegramgallery.Utils.ImageLoader; +import com.tangxiaolv.telegramgallery.actionbar.ActionBarLayout; +import com.tangxiaolv.telegramgallery.actionbar.BaseFragment; +import com.tangxiaolv.telegramgallery.entity.MediaInfo; +import com.tangxiaolv.telegramgallery.utils.GalleryImageLoader; import java.util.ArrayList; @@ -23,9 +26,12 @@ public class GalleryActivity extends Activity implements ActionBarLayout.ActionBarLayoutDelegate { public static final String PHOTOS = "PHOTOS"; + public static final String IS_ORIGINAL = "IS_ORIGINAL"; public static final String VIDEO = "VIDEOS"; + public static final String MEDIA_INFO = "MEDIA_INFO"; - private static final String GALLERY_CONFIG = "GALLERY_CONFIG"; + private static final String GALLERY_CONFIG = "GalleryConfig"; + private static GalleryConfig CONFIG; private ArrayList mainFragmentsStack = new ArrayList<>(); private ActionBarLayout actionBarLayout; @@ -35,11 +41,15 @@ public class GalleryActivity extends Activity implements ActionBarLayout.ActionB protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gallery); + if (getIntent() == null || getIntent().getSerializableExtra(GALLERY_CONFIG) == null) { + return; + } Gallery.init(getApplication()); + CONFIG = (GalleryConfig) getIntent().getSerializableExtra(GALLERY_CONFIG); - FrameLayout mian = (FrameLayout) findViewById(R.id.mian); + FrameLayout main = (FrameLayout) findViewById(R.id.mian); actionBarLayout = new ActionBarLayout(this); - mian.addView(actionBarLayout); + main.addView(actionBarLayout); actionBarLayout.init(mainFragmentsStack); actionBarLayout.setDelegate(this); @@ -47,7 +57,7 @@ protected void onCreate(Bundle savedInstanceState) { if (checkCallingOrSelfPermission( READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (Build.VERSION.SDK_INT >= 23) { - requestPermissions(new String[] { + requestPermissions(new String[]{ READ_EXTERNAL_STORAGE }, 1); return; @@ -57,42 +67,34 @@ protected void onCreate(Bundle savedInstanceState) { } @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { showContent(); + if (grantResults.length > 0) { + Toast.makeText(this, R.string.album_read_fail, Toast.LENGTH_SHORT).show(); + } } private void showContent() { - Intent intent = getIntent(); - GalleryConfig config = intent.getParcelableExtra(GALLERY_CONFIG); - albumPickerActivity = new PhotoAlbumPickerActivity( - config.getFilterMimeTypes(), - config.getLimitPickPhoto(), - config.isSinglePhoto(), - config.getHintOfPick(), - false); + albumPickerActivity = new PhotoAlbumPickerActivity(true); albumPickerActivity.setDelegate(mPhotoAlbumPickerActivityDelegate); actionBarLayout.presentFragment(albumPickerActivity, false, true, true); } private PhotoAlbumPickerActivity.PhotoAlbumPickerActivityDelegate mPhotoAlbumPickerActivityDelegate = new PhotoAlbumPickerActivity.PhotoAlbumPickerActivityDelegate() { - @Override - public void didSelectPhotos(ArrayList photos, ArrayList captions) { - Intent intent = new Intent(); - intent.putExtra(PHOTOS, photos); - setResult(Activity.RESULT_OK, intent); - } - - @Override - public boolean didSelectVideo(String path) { - Intent intent = new Intent(); - intent.putExtra(VIDEO, path); - setResult(Activity.RESULT_OK, intent); - return true; - } @Override - public void startPhotoSelectActivity() { + public void didSelectMedia(ArrayList medias) { + if (medias.size() > 0) { + ArrayList paths = new ArrayList<>(); + for (MediaInfo info : medias) { + paths.add(info.getPath()); + } + Intent intent = new Intent(); + intent.putExtra(PHOTOS, paths); + intent.putExtra(IS_ORIGINAL, Gallery.sOriginChecked); + intent.putExtra(MEDIA_INFO, medias); + setResult(Activity.RESULT_OK, intent); + } } }; @@ -106,11 +108,14 @@ public void onLowMemory() { public void onBackPressed() { if (PhotoViewer.getInstance().isVisible()) { PhotoViewer.getInstance().closePhoto(true, false); + } else if (mainFragmentsStack.size() <= 2) { + finish(); } else { actionBarLayout.onBackPressed(); } } + @Override protected void onPause() { super.onPause(); @@ -123,6 +128,9 @@ protected void onPause() { @Override protected void onResume() { super.onResume(); + if (null == actionBarLayout) { + return; + } actionBarLayout.onResume(); if (PhotoViewer.getInstance().isVisible()) { PhotoViewer.getInstance().onResume(); @@ -139,8 +147,7 @@ public boolean onPreIme() { } @Override - public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, - boolean forceWithoutAnimation, ActionBarLayout layout) { + public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, ActionBarLayout layout) { return true; } @@ -172,52 +179,48 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { @Override protected void onDestroy() { + super.onDestroy(); PhotoViewer.getInstance().destroyPhotoViewer(); - ImageLoader.getInstance().clearMemory(); - albumPickerActivity.removeSelfFromStack(); - actionBarLayout.clear(); - mainFragmentsStack.clear(); - mainFragmentsStack = null; + GalleryImageLoader.getInstance().clearMemory(); + if (null != albumPickerActivity) { + albumPickerActivity.removeSelfFromStack(); + albumPickerActivity = null; + } + if (null != mainFragmentsStack) { + mainFragmentsStack.clear(); + mainFragmentsStack = null; + } actionBarLayout = null; - albumPickerActivity = null; - super.onDestroy(); + //相册状态量复位 + Gallery.sOriginChecked = false; + Gallery.sListItemClickable = true; } /** * open gallery - * - * @param activity parent activity + * + * @param activity parent activity * @param requestCode {@link Activity#onActivityResult} - * @param config {@link GalleryConfig} + * @param config {@link GalleryConfig} */ public static void openActivity(Activity activity, int requestCode, GalleryConfig config) { Intent intent = new Intent(activity, GalleryActivity.class); intent.putExtra(GALLERY_CONFIG, config); activity.startActivityForResult(intent, requestCode); + activity.overridePendingTransition( + config.getEnterAnim() != -1 ? config.getEnterAnim() : R.anim.push_bottom_in, 0); + activity.overridePendingTransition(R.anim.push_bottom_in, 0); } - @Deprecated - public static void openActivity( - Activity activity, - String[] filterMimeTypes, - boolean singlePhoto, - int limitPickPhoto, - int requestCode) { - GalleryConfig.Build build = new GalleryConfig.Build(); - build.filterMimeTypes(filterMimeTypes) - .singlePhoto(singlePhoto) - .limitPickPhoto(limitPickPhoto); - openActivity(activity, requestCode, build.build()); - } - - @Deprecated - public static void openActivity(Activity activity, boolean singlePhoto, int limitPickPhoto, - int requestCode) { - openActivity(activity, null, singlePhoto, limitPickPhoto, requestCode); + @Override + public void finish() { + super.finish(); + overridePendingTransition( + 0, CONFIG.getExitAnim() != -1 ? CONFIG.getExitAnim() : R.anim.push_bottom_out); + overridePendingTransition(0, R.anim.push_bottom_out); } - @Deprecated - public static void openActivity(Activity activity, boolean singlePhoto, int requestCode) { - openActivity(activity, null, singlePhoto, 1, requestCode); + public static GalleryConfig getConfig() { + return CONFIG; } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/GalleryConfig.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/GalleryConfig.java index 4f7a76e..9ef159d 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/GalleryConfig.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/GalleryConfig.java @@ -1,126 +1,194 @@ package com.tangxiaolv.telegramgallery; -import android.os.Parcel; -import android.os.Parcelable; +import android.support.annotation.AnimRes; + +import java.io.Serializable; /** * the {@link GalleryActivity} of buidler. */ -public class GalleryConfig implements Parcelable { +public class GalleryConfig implements Serializable { private String[] filterMimeTypes; - private String hintOfPick; + private boolean singlePhoto; + + private boolean hasOriginalPic; + private int limitPickPhoto; - private GalleryConfig(){ + private boolean videoEditMode; - } + private boolean hasVideo; - private GalleryConfig(String[] filterMimeTypes, String hintOfPick, boolean singlePhoto, - int limitPickPhoto) { - this.filterMimeTypes = filterMimeTypes; - this.hintOfPick = hintOfPick; - this.singlePhoto = singlePhoto; - this.limitPickPhoto = limitPickPhoto; - } + private int maxVideoTime; + + private long maxImageSize; + + private int enterAnim; + + private int exitAnim; public String[] getFilterMimeTypes() { return filterMimeTypes; } - public String getHintOfPick() { - return hintOfPick; - } - public boolean isSinglePhoto() { return singlePhoto; } + public boolean hasOriginalPic() { + return hasOriginalPic; + } + public int getLimitPickPhoto() { return limitPickPhoto; } + public int getEnterAnim() { + return enterAnim; + } + + public int getExitAnim() { + return exitAnim; + } + + public boolean isVideoEditMode() { + return videoEditMode; + } + + public int getMaxVideoTime() { + return maxVideoTime; + } + + public long getMaxImageSize() { + return maxImageSize; + } + + public boolean hasVideo() { + return hasVideo; + } + + private GalleryConfig(String[] filterMimeTypes, + boolean singlePhoto, + boolean hasOriginalPic, + int limitPickPhoto, + int enterAnim, int exitAnim, + boolean videoEditMode, + boolean hasVideo, + int maxVideoTime, + long maxImageSize) { + this.filterMimeTypes = filterMimeTypes; + this.singlePhoto = singlePhoto; + this.hasOriginalPic = hasOriginalPic; + this.limitPickPhoto = limitPickPhoto; + this.enterAnim = enterAnim; + this.exitAnim = exitAnim; + this.videoEditMode = videoEditMode; + this.hasVideo = hasVideo; + this.maxVideoTime = maxVideoTime; + this.maxImageSize = maxImageSize; + } + public static class Build { private String[] filterMimeTypes; - private String hintOfPick; private boolean singlePhoto = false; + private boolean hasOriginalPic = false; private int limitPickPhoto = 9; + private int enterAnim = -1; + private int exitAnim = -1; + private boolean videoEditMode = false; + private boolean hasVideo = false; + private int maxVideoTime = 1024 * 1024;//sec + private long maxImageSize = 1024 * 1024 * 1024; /** - * @param filterMimeTypes filter of media type, based on MimeType standards: - * {http://www.w3school.com.cn/media/media_mimeref.asp} - *
  • eg:new string[]{"image/gif","image/jpeg"} + * @param filterMimeTypes filter media types,base on MimeType。 + * @see {http://www.w3school.com.cn/media/media_mimeref.asp} eg:new string[]{"image/gif","image/jpeg"} + * */ - public Build filterMimeTypes(String[] filterMimeTypes) { + public Build setFilterMimeTypes(String[] filterMimeTypes) { this.filterMimeTypes = filterMimeTypes; return this; } + /** - * @param hintOfPick hint of Toast when limit is reached + * @param singlePhoto is select of single */ - public Build hintOfPick(String hintOfPick) { - this.hintOfPick = hintOfPick; + public Build setSinglePhoto(boolean singlePhoto) { + this.singlePhoto = singlePhoto; return this; } /** - * @param singlePhoto true:single pick false:multi pick + * show button of named 'origin' */ - public Build singlePhoto(boolean singlePhoto) { - this.singlePhoto = singlePhoto; + public Build setHasOriginalPic(boolean hasOriginalPic) { + this.hasOriginalPic = hasOriginalPic; return this; } /** * @param limitPickPhoto the limit of photos those can be selected */ - public Build limitPickPhoto(int limitPickPhoto) { + public Build setLimitPickPhoto(int limitPickPhoto) { this.limitPickPhoto = limitPickPhoto; return this; } - public GalleryConfig build() { - this.limitPickPhoto = singlePhoto ? 1 : limitPickPhoto > 0 ? limitPickPhoto : 1; - return new GalleryConfig( - filterMimeTypes, - hintOfPick, - singlePhoto, - limitPickPhoto); + /** + * entry animation + */ + public Build setAnimation(@AnimRes int enterAnim, @AnimRes int exitAnim) { + this.enterAnim = enterAnim; + this.exitAnim = exitAnim; + return this; } - } - @Override - public int describeContents() { - return 0; - } + /*public Build setVideoEditMode(boolean videoEditMode) { + this.videoEditMode = videoEditMode; + return this; + }*/ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeStringArray(this.filterMimeTypes); - dest.writeString(this.hintOfPick); - dest.writeByte(this.singlePhoto ? (byte) 1 : (byte) 0); - dest.writeInt(this.limitPickPhoto); - } + /** + * @param hasVideo has list video + */ + public Build setHasVideo(boolean hasVideo) { + this.hasVideo = hasVideo; + return this; + } - protected GalleryConfig(Parcel in) { - this.filterMimeTypes = in.createStringArray(); - this.hintOfPick = in.readString(); - this.singlePhoto = in.readByte() != 0; - this.limitPickPhoto = in.readInt(); - } + /** + * @param maxVideoTime limit time of be send video unit:sec + */ + public Build setMaxVideoTime(int maxVideoTime) { + this.maxVideoTime = maxVideoTime; + return this; + } - public static final Creator CREATOR = new Creator() { - @Override - public GalleryConfig createFromParcel(Parcel source) { - return new GalleryConfig(source); + /** + * @param maxImageSize limit size of be send image unit:bytes + */ + public Build setMaxImageSize(int maxImageSize) { + this.maxImageSize = maxImageSize; + return this; } - @Override - public GalleryConfig[] newArray(int size) { - return new GalleryConfig[size]; + public GalleryConfig build() { + this.limitPickPhoto = singlePhoto ? 1 : limitPickPhoto > 0 ? limitPickPhoto : 1; + return new GalleryConfig( + filterMimeTypes, + singlePhoto, + hasOriginalPic, + limitPickPhoto, + enterAnim, + exitAnim, + videoEditMode, + hasVideo, + maxVideoTime, + maxImageSize); } - }; + } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/ImageReceiver.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/ImageReceiver.java index b249b9f..4e34487 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/ImageReceiver.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/ImageReceiver.java @@ -15,11 +15,13 @@ import android.graphics.drawable.Drawable; import android.view.View; -import com.tangxiaolv.telegramgallery.TL.FileLocation; -import com.tangxiaolv.telegramgallery.TL.TLObject; -import com.tangxiaolv.telegramgallery.Utils.ImageLoader; -import com.tangxiaolv.telegramgallery.Utils.NotificationCenter; -import com.tangxiaolv.telegramgallery.Utils.Utilities; +import com.tangxiaolv.telegramgallery.tl.Document; +import com.tangxiaolv.telegramgallery.tl.FileLocation; +import com.tangxiaolv.telegramgallery.tl.TLObject; +import com.tangxiaolv.telegramgallery.utils.FileLog; +import com.tangxiaolv.telegramgallery.utils.GalleryImageLoader; +import com.tangxiaolv.telegramgallery.utils.NotificationCenter; +import com.tangxiaolv.telegramgallery.utils.Utilities; public class ImageReceiver implements NotificationCenter.NotificationCenterDelegate { @@ -35,7 +37,7 @@ private class SetImageBackup { public FileLocation thumbLocation; public String thumbFilter; public int size; - public boolean cacheOnly; + public int cacheType; public String ext; } @@ -43,8 +45,7 @@ private class SetImageBackup { private Integer tag; private Integer thumbTag; private boolean canceledLoading; - private static PorterDuffColorFilter selectedColorFilter = new PorterDuffColorFilter(0xffdddddd, - PorterDuff.Mode.MULTIPLY); + private static PorterDuffColorFilter selectedColorFilter = new PorterDuffColorFilter(0xffdddddd, PorterDuff.Mode.MULTIPLY); private SetImageBackup setImageBackup; @@ -57,11 +58,12 @@ private class SetImageBackup { private String currentExt; private FileLocation currentThumbLocation; private int currentSize; - private boolean currentCacheOnly; + private int currentCacheType; private Drawable currentImage; private Drawable currentThumb; private Drawable staticThumb; private boolean allowStartAnimation = true; + private boolean allowDecodeSingleFrame; private boolean needsQualityThumb; private boolean shouldGenerateQualityThumb; @@ -72,6 +74,7 @@ private class SetImageBackup { private boolean isVisible = true; private boolean isAspectFit; private boolean forcePreview; + private boolean forceCrossfade; private int roundRadius; private BitmapShader bitmapShader; private BitmapShader bitmapShaderThumb; @@ -87,6 +90,7 @@ private class SetImageBackup { private float currentAlpha; private long lastUpdateAlphaTime; private byte crossfadeAlpha = 1; + private boolean manualAlphaAnimator; private boolean crossfadeWithThumb; private ColorFilter colorFilter; @@ -102,38 +106,31 @@ public ImageReceiver(View view) { } public void cancelLoadImage() { - ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); + GalleryImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); canceledLoading = true; } - public void setImage(TLObject path, String filter, Drawable thumb, String ext, - boolean cacheOnly) { - setImage(path, null, filter, thumb, null, null, 0, ext, cacheOnly); + public void setImage(TLObject path, String filter, Drawable thumb, String ext, int cacheType) { + setImage(path, null, filter, thumb, null, null, 0, ext, cacheType); } - public void setImage(TLObject path, String filter, Drawable thumb, int size, String ext, - boolean cacheOnly) { - setImage(path, null, filter, thumb, null, null, size, ext, cacheOnly); + public void setImage(TLObject path, String filter, Drawable thumb, int size, String ext, int cacheType) { + setImage(path, null, filter, thumb, null, null, size, ext, cacheType); } public void setImage(String httpUrl, String filter, Drawable thumb, String ext, int size) { - setImage(null, httpUrl, filter, thumb, null, null, size, ext, true); + setImage(null, httpUrl, filter, thumb, null, null, size, ext, 1); } - public void setImage(TLObject fileLocation, String filter, FileLocation thumbLocation, - String thumbFilter, String ext, boolean cacheOnly) { - setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, 0, ext, cacheOnly); + public void setImage(TLObject fileLocation, String filter, FileLocation thumbLocation, String thumbFilter, String ext, int cacheType) { + setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, 0, ext, cacheType); } - public void setImage(TLObject fileLocation, String filter, FileLocation thumbLocation, - String thumbFilter, int size, String ext, boolean cacheOnly) { - setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, size, ext, - cacheOnly); + public void setImage(TLObject fileLocation, String filter, FileLocation thumbLocation, String thumbFilter, int size, String ext, int cacheType) { + setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, size, ext, cacheType); } - public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawable thumb, - FileLocation thumbLocation, String thumbFilter, int size, String ext, - boolean cacheOnly) { + public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawable thumb, FileLocation thumbLocation, String thumbFilter, int size, String ext, int cacheType) { if (setImageBackup != null) { setImageBackup.fileLocation = null; setImageBackup.httpUrl = null; @@ -143,7 +140,8 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa if ((fileLocation == null && httpUrl == null && thumbLocation == null) || (fileLocation != null && !(fileLocation instanceof FileLocation.TL_fileLocation) - && !(fileLocation instanceof FileLocation.TL_fileEncryptedLocation))) { + && !(fileLocation instanceof FileLocation.TL_fileEncryptedLocation) + && !(fileLocation instanceof Document.TL_document))) { recycleBitmap(null, false); recycleBitmap(null, true); currentKey = null; @@ -153,7 +151,7 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa currentImageLocation = null; currentHttpUrl = null; currentFilter = null; - currentCacheOnly = false; + currentCacheType = 0; staticThumb = thumb; currentAlpha = 1; currentThumbLocation = null; @@ -161,7 +159,7 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa currentImage = null; bitmapShader = null; bitmapShaderThumb = null; - ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); + GalleryImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); if (parentView != null) { if (invalidateAll) { parentView.invalidate(); @@ -170,9 +168,7 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa } } if (delegate != null) { - delegate.didSetImage(this, - currentImage != null || currentThumb != null || staticThumb != null, - currentImage == null); + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); } return; } @@ -187,7 +183,16 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa FileLocation location = (FileLocation) fileLocation; key = location.volume_id + "_" + location.local_id; } else { - fileLocation = null; + Document location = (Document) fileLocation; + if (location.dc_id != 0) { + if (location.version == 0) { + key = location.dc_id + "_" + location.id; + } else { + key = location.dc_id + "_" + location.id + "_" + location.version; + } + } else { + fileLocation = null; + } } } else if (httpUrl != null) { key = Utilities.MD5(httpUrl); @@ -200,9 +205,7 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa if (currentKey != null && key != null && currentKey.equals(key)) { if (delegate != null) { - delegate.didSetImage(this, - currentImage != null || currentThumb != null || staticThumb != null, - currentImage == null); + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); } if (!canceledLoading && !forcePreview) { return; @@ -228,7 +231,7 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa currentFilter = filter; currentThumbFilter = thumbFilter; currentSize = size; - currentCacheOnly = cacheOnly; + currentCacheType = cacheType; currentThumbLocation = thumbLocation; staticThumb = thumb; bitmapShader = null; @@ -236,12 +239,10 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa currentAlpha = 1.0f; if (delegate != null) { - delegate.didSetImage(this, - currentImage != null || currentThumb != null || staticThumb != null, - currentImage == null); + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); } - ImageLoader.getInstance().loadImageForImageReceiver(this); + GalleryImageLoader.getInstance().loadImageForImageReceiver(this); if (parentView != null) { if (invalidateAll) { parentView.invalidate(); @@ -282,6 +283,19 @@ public void setInvalidateAll(boolean value) { invalidateAll = value; } + public Drawable getStaticThumb() { + return staticThumb; + } + + public int getAnimatedOrientation() { + if (currentImage instanceof AnimatedFileDrawable) { + return ((AnimatedFileDrawable) currentImage).getOrientation(); + } else if (staticThumb instanceof AnimatedFileDrawable) { + return ((AnimatedFileDrawable) staticThumb).getOrientation(); + } + return 0; + } + public int getOrientation() { return orientation; } @@ -291,7 +305,7 @@ public void setImageBitmap(Bitmap bitmap) { } public void setImageBitmap(Drawable bitmap) { - ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); + GalleryImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); recycleBitmap(null, false); recycleBitmap(null, true); staticThumb = bitmap; @@ -305,7 +319,7 @@ public void setImageBitmap(Drawable bitmap) { currentHttpUrl = null; currentFilter = null; currentSize = 0; - currentCacheOnly = false; + currentCacheType = 0; bitmapShader = null; bitmapShaderThumb = null; if (setImageBackup != null) { @@ -331,15 +345,13 @@ public void clearImage() { recycleBitmap(null, false); recycleBitmap(null, true); if (needsQualityThumb) { - NotificationCenter.getInstance().removeObserver(this, - NotificationCenter.messageThumbGenerated); - ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated); + GalleryImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); } } public void onDetachedFromWindow() { - if (currentImageLocation != null || currentHttpUrl != null || currentThumbLocation != null - || staticThumb != null) { + if (currentImageLocation != null || currentHttpUrl != null || currentThumbLocation != null || staticThumb != null) { if (setImageBackup == null) { setImageBackup = new SetImageBackup(); } @@ -351,22 +363,19 @@ public void onDetachedFromWindow() { setImageBackup.thumbFilter = currentThumbFilter; setImageBackup.size = currentSize; setImageBackup.ext = currentExt; - setImageBackup.cacheOnly = currentCacheOnly; + setImageBackup.cacheType = currentCacheType; } - NotificationCenter.getInstance().removeObserver(this, - NotificationCenter.didReplacedPhotoInMemCache); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReplacedPhotoInMemCache); clearImage(); } public boolean onAttachedToWindow() { - NotificationCenter.getInstance().addObserver(this, - NotificationCenter.didReplacedPhotoInMemCache); - if (setImageBackup != null - && (setImageBackup.fileLocation != null || setImageBackup.httpUrl != null - || setImageBackup.thumbLocation != null || setImageBackup.thumb != null)) { - setImage(setImageBackup.fileLocation, setImageBackup.httpUrl, setImageBackup.filter, - setImageBackup.thumb, setImageBackup.thumbLocation, setImageBackup.thumbFilter, - setImageBackup.size, setImageBackup.ext, setImageBackup.cacheOnly); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReplacedPhotoInMemCache); + if (needsQualityThumb) { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageThumbGenerated); + } + if (setImageBackup != null && (setImageBackup.fileLocation != null || setImageBackup.httpUrl != null || setImageBackup.thumbLocation != null || setImageBackup.thumb != null)) { + setImage(setImageBackup.fileLocation, setImageBackup.httpUrl, setImageBackup.filter, setImageBackup.thumb, setImageBackup.thumbLocation, setImageBackup.thumbFilter, setImageBackup.size, setImageBackup.ext, setImageBackup.cacheType); return true; } return false; @@ -386,7 +395,7 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha if (hasFilter && !isPressed) { if (shader != null) { roundPaint.setColorFilter(null); - } else { + } else if (staticThumb != drawable) { bitmapDrawable.setColorFilter(null); } } else if (!hasFilter && isPressed) { @@ -432,11 +441,9 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha shaderMatrix.reset(); if (Math.abs(scaleW - scaleH) > 0.00001f) { if (bitmapW / scaleH > imageW) { - drawRegion.set(imageX - ((int) (bitmapW / scaleH) - imageW) / 2, imageY, - imageX + ((int) (bitmapW / scaleH) + imageW) / 2, imageY + imageH); + drawRegion.set(imageX - ((int) (bitmapW / scaleH) - imageW) / 2, imageY, imageX + ((int) (bitmapW / scaleH) + imageW) / 2, imageY + imageH); } else { - drawRegion.set(imageX, imageY - ((int) (bitmapH / scaleW) - imageH) / 2, - imageX + imageW, imageY + ((int) (bitmapH / scaleW) + imageH) / 2); + drawRegion.set(imageX, imageY - ((int) (bitmapH / scaleW) - imageH) / 2, imageX + imageW, imageY + ((int) (bitmapH / scaleW) + imageH) / 2); } } else { drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH); @@ -445,8 +452,7 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha if (Math.abs(scaleW - scaleH) > 0.00001f) { int w = (int) Math.floor(imageW * scale); int h = (int) Math.floor(imageH * scale); - bitmapRect.set((bitmapW - w) / 2, (bitmapH - h) / 2, (bitmapW + w) / 2, - (bitmapH + h) / 2); + bitmapRect.set((bitmapW - w) / 2, (bitmapH - h) / 2, (bitmapW + w) / 2, (bitmapH + h) / 2); shaderMatrix.setRectToRect(bitmapRect, roundRect, Matrix.ScaleToFit.START); } else { bitmapRect.set(0, 0, bitmapW, bitmapH); @@ -462,24 +468,21 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha canvas.save(); bitmapW /= scale; bitmapH /= scale; - drawRegion.set(imageX + (imageW - bitmapW) / 2, imageY + (imageH - bitmapH) / 2, - imageX + (imageW + bitmapW) / 2, imageY + (imageH + bitmapH) / 2); + drawRegion.set(imageX + (imageW - bitmapW) / 2, imageY + (imageH - bitmapH) / 2, imageX + (imageW + bitmapW) / 2, imageY + (imageH + bitmapH) / 2); bitmapDrawable.setBounds(drawRegion); try { bitmapDrawable.setAlpha(alpha); bitmapDrawable.draw(canvas); } catch (Exception e) { if (bitmapDrawable == currentImage && currentKey != null) { - ImageLoader.getInstance().removeImage(currentKey); + GalleryImageLoader.getInstance().removeImage(currentKey); currentKey = null; } else if (bitmapDrawable == currentThumb && currentThumbKey != null) { - ImageLoader.getInstance().removeImage(currentThumbKey); + GalleryImageLoader.getInstance().removeImage(currentThumbKey); currentThumbKey = null; } - setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, - currentThumbLocation, currentThumbFilter, currentSize, currentExt, - currentCacheOnly); - e.printStackTrace(); + setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheType); + FileLog.e(e); } canvas.restore(); } else { @@ -497,20 +500,20 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha if (bitmapW / scaleH > imageW) { bitmapW /= scaleH; - drawRegion.set(imageX - (bitmapW - imageW) / 2, imageY, - imageX + (bitmapW + imageW) / 2, imageY + imageH); + drawRegion.set(imageX - (bitmapW - imageW) / 2, imageY, imageX + (bitmapW + imageW) / 2, imageY + imageH); } else { bitmapH /= scaleW; - drawRegion.set(imageX, imageY - (bitmapH - imageH) / 2, imageX + imageW, - imageY + (bitmapH + imageH) / 2); + drawRegion.set(imageX, imageY - (bitmapH - imageH) / 2, imageX + imageW, imageY + (bitmapH + imageH) / 2); + } + if (bitmapDrawable instanceof AnimatedFileDrawable) { + ((AnimatedFileDrawable) bitmapDrawable).setActualDrawRect(imageX, imageY, imageW, imageH); } if (orientation % 360 == 90 || orientation % 360 == 270) { int width = (drawRegion.right - drawRegion.left) / 2; int height = (drawRegion.bottom - drawRegion.top) / 2; int centerX = (drawRegion.right + drawRegion.left) / 2; int centerY = (drawRegion.top + drawRegion.bottom) / 2; - bitmapDrawable.setBounds(centerX - height, centerY - width, - centerX + height, centerY + width); + bitmapDrawable.setBounds(centerX - height, centerY - width, centerX + height, centerY + width); } else { bitmapDrawable.setBounds(drawRegion); } @@ -520,17 +523,14 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha bitmapDrawable.draw(canvas); } catch (Exception e) { if (bitmapDrawable == currentImage && currentKey != null) { - ImageLoader.getInstance().removeImage(currentKey); + GalleryImageLoader.getInstance().removeImage(currentKey); currentKey = null; - } else if (bitmapDrawable == currentThumb - && currentThumbKey != null) { - ImageLoader.getInstance().removeImage(currentThumbKey); + } else if (bitmapDrawable == currentThumb && currentThumbKey != null) { + GalleryImageLoader.getInstance().removeImage(currentThumbKey); currentThumbKey = null; } - setImage(currentImageLocation, currentHttpUrl, currentFilter, - currentThumb, currentThumbLocation, currentThumbFilter, - currentSize, currentExt, currentCacheOnly); - e.printStackTrace(); + setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheType); + FileLog.e(e); } } @@ -545,13 +545,15 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha } } drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH); + if (bitmapDrawable instanceof AnimatedFileDrawable) { + ((AnimatedFileDrawable) bitmapDrawable).setActualDrawRect(imageX, imageY, imageW, imageH); + } if (orientation % 360 == 90 || orientation % 360 == 270) { int width = (drawRegion.right - drawRegion.left) / 2; int height = (drawRegion.bottom - drawRegion.top) / 2; int centerX = (drawRegion.right + drawRegion.left) / 2; int centerY = (drawRegion.top + drawRegion.bottom) / 2; - bitmapDrawable.setBounds(centerX - height, centerY - width, - centerX + height, centerY + width); + bitmapDrawable.setBounds(centerX - height, centerY - width, centerX + height, centerY + width); } else { bitmapDrawable.setBounds(drawRegion); } @@ -561,17 +563,14 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha bitmapDrawable.draw(canvas); } catch (Exception e) { if (bitmapDrawable == currentImage && currentKey != null) { - ImageLoader.getInstance().removeImage(currentKey); + GalleryImageLoader.getInstance().removeImage(currentKey); currentKey = null; - } else if (bitmapDrawable == currentThumb - && currentThumbKey != null) { - ImageLoader.getInstance().removeImage(currentThumbKey); + } else if (bitmapDrawable == currentThumb && currentThumbKey != null) { + GalleryImageLoader.getInstance().removeImage(currentThumbKey); currentThumbKey = null; } - setImage(currentImageLocation, currentHttpUrl, currentFilter, - currentThumb, currentThumbLocation, currentThumbFilter, - currentSize, currentExt, currentCacheOnly); - e.printStackTrace(); + setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheType); + FileLog.e(e); } } canvas.restore(); @@ -586,13 +585,16 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha drawable.setAlpha(alpha); drawable.draw(canvas); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } } } private void checkAlphaAnimation(boolean skip) { + if (manualAlphaAnimator) { + return; + } if (currentAlpha != 1) { if (!skip) { long currentTime = System.currentTimeMillis(); @@ -619,8 +621,7 @@ private void checkAlphaAnimation(boolean skip) { public boolean draw(Canvas canvas) { try { Drawable drawable = null; - boolean animationNotReady = currentImage instanceof AnimatedFileDrawable - && !((AnimatedFileDrawable) currentImage).hasBitmap(); + boolean animationNotReady = currentImage instanceof AnimatedFileDrawable && !((AnimatedFileDrawable) currentImage).hasBitmap(); boolean isThumb = false; if (!forcePreview && currentImage != null && !animationNotReady) { drawable = currentImage; @@ -634,8 +635,7 @@ public boolean draw(Canvas canvas) { if (drawable != null) { if (crossfadeAlpha != 0) { if (crossfadeWithThumb && animationNotReady) { - drawDrawable(canvas, drawable, (int) (overrideAlpha * 255), - bitmapShaderThumb); + drawDrawable(canvas, drawable, (int) (overrideAlpha * 255), bitmapShaderThumb); } else { if (crossfadeWithThumb && currentAlpha != 1.0f) { Drawable thumbDrawable = null; @@ -651,16 +651,13 @@ public boolean draw(Canvas canvas) { } } if (thumbDrawable != null) { - drawDrawable(canvas, thumbDrawable, (int) (overrideAlpha * 255), - bitmapShaderThumb); + drawDrawable(canvas, thumbDrawable, (int) (overrideAlpha * 255), bitmapShaderThumb); } } - drawDrawable(canvas, drawable, (int) (overrideAlpha * currentAlpha * 255), - isThumb ? bitmapShaderThumb : bitmapShader); + drawDrawable(canvas, drawable, (int) (overrideAlpha * currentAlpha * 255), isThumb ? bitmapShaderThumb : bitmapShader); } } else { - drawDrawable(canvas, drawable, (int) (overrideAlpha * 255), - isThumb ? bitmapShaderThumb : bitmapShader); + drawDrawable(canvas, drawable, (int) (overrideAlpha * 255), isThumb ? bitmapShaderThumb : bitmapShader); } checkAlphaAnimation(animationNotReady && crossfadeWithThumb); @@ -673,11 +670,23 @@ public boolean draw(Canvas canvas) { checkAlphaAnimation(animationNotReady); } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } return false; } + public void setManualAlphaAnimator(boolean value) { + manualAlphaAnimator = value; + } + + public float getCurrentAlpha() { + return currentAlpha; + } + + public void setCurrentAlpha(float value) { + currentAlpha = value; + } + public Bitmap getBitmap() { if (currentImage instanceof AnimatedFileDrawable) { return ((AnimatedFileDrawable) currentImage).getAnimatedBitmap(); @@ -693,30 +702,45 @@ public Bitmap getBitmap() { return null; } + public Bitmap getThumbBitmap() { + if (currentThumb instanceof BitmapDrawable) { + return ((BitmapDrawable) currentThumb).getBitmap(); + } else if (staticThumb instanceof BitmapDrawable) { + return ((BitmapDrawable) staticThumb).getBitmap(); + } + return null; + } + public int getBitmapWidth() { if (currentImage instanceof AnimatedFileDrawable) { - return orientation % 360 == 0 || orientation % 360 == 180 - ? currentImage.getIntrinsicWidth() : currentImage.getIntrinsicHeight(); + return orientation % 360 == 0 || orientation % 360 == 180 ? currentImage.getIntrinsicWidth() : currentImage.getIntrinsicHeight(); } else if (staticThumb instanceof AnimatedFileDrawable) { - return orientation % 360 == 0 || orientation % 360 == 180 - ? staticThumb.getIntrinsicWidth() : staticThumb.getIntrinsicHeight(); + return orientation % 360 == 0 || orientation % 360 == 180 ? staticThumb.getIntrinsicWidth() : staticThumb.getIntrinsicHeight(); } Bitmap bitmap = getBitmap(); - return orientation % 360 == 0 || orientation % 360 == 180 ? bitmap.getWidth() - : bitmap.getHeight(); + if (bitmap == null) { + if (staticThumb != null) { + return staticThumb.getIntrinsicWidth(); + } + return 1; + } + return orientation % 360 == 0 || orientation % 360 == 180 ? bitmap.getWidth() : bitmap.getHeight(); } public int getBitmapHeight() { if (currentImage instanceof AnimatedFileDrawable) { - return orientation % 360 == 0 || orientation % 360 == 180 - ? currentImage.getIntrinsicHeight() : currentImage.getIntrinsicWidth(); + return orientation % 360 == 0 || orientation % 360 == 180 ? currentImage.getIntrinsicHeight() : currentImage.getIntrinsicWidth(); } else if (staticThumb instanceof AnimatedFileDrawable) { - return orientation % 360 == 0 || orientation % 360 == 180 - ? staticThumb.getIntrinsicHeight() : staticThumb.getIntrinsicWidth(); + return orientation % 360 == 0 || orientation % 360 == 180 ? staticThumb.getIntrinsicHeight() : staticThumb.getIntrinsicWidth(); } Bitmap bitmap = getBitmap(); - return orientation % 360 == 0 || orientation % 360 == 180 ? bitmap.getHeight() - : bitmap.getWidth(); + if (bitmap == null) { + if (staticThumb != null) { + return staticThumb.getIntrinsicHeight(); + } + return 1; + } + return orientation % 360 == 0 || orientation % 360 == 180 ? bitmap.getHeight() : bitmap.getWidth(); } public void setVisible(boolean value, boolean invalidate) { @@ -746,8 +770,7 @@ public void setCrossfadeAlpha(byte value) { } public boolean hasImage() { - return currentImage != null || currentThumb != null || currentKey != null - || currentHttpUrl != null || staticThumb != null; + return currentImage != null || currentThumb != null || currentKey != null || currentHttpUrl != null || staticThumb != null; } public boolean hasBitmapImage() { @@ -766,6 +789,10 @@ public void setParentView(View view) { } } + public void setImageY(int y) { + imageY = y; + } + public void setImageCoords(int x, int y, int width, int height) { imageX = x; imageY = y; @@ -773,6 +800,14 @@ public void setImageCoords(int x, int y, int width, int height) { imageH = height; } + public float getCenterX() { + return imageX + imageW / 2.0f; + } + + public float getCenterY() { + return imageY + imageH / 2.0f; + } + public int getImageX() { return imageX; } @@ -841,14 +876,18 @@ public String getHttpImageLocation() { return currentHttpUrl; } - public boolean getCacheOnly() { - return currentCacheOnly; + public int getCacheType() { + return currentCacheType; } public void setForcePreview(boolean value) { forcePreview = value; } + public void setForceCrossfade(boolean value) { + forceCrossfade = value; + } + public boolean isForcePreview() { return forcePreview; } @@ -864,11 +903,9 @@ public int getRoundRadius() { public void setNeedsQualityThumb(boolean value) { needsQualityThumb = value; if (needsQualityThumb) { - NotificationCenter.getInstance().addObserver(this, - NotificationCenter.messageThumbGenerated); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageThumbGenerated); } else { - NotificationCenter.getInstance().removeObserver(this, - NotificationCenter.messageThumbGenerated); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated); } } @@ -888,6 +925,10 @@ public void setAllowStartAnimation(boolean value) { allowStartAnimation = value; } + public void setAllowDecodeSingleFrame(boolean value) { + allowDecodeSingleFrame = value; + } + public boolean isAllowStartAnimation() { return allowStartAnimation; } @@ -905,13 +946,11 @@ public void stopAnimation() { } public boolean isAnimationRunning() { - return currentImage instanceof AnimatedFileDrawable - && ((AnimatedFileDrawable) currentImage).isRunning(); + return currentImage instanceof AnimatedFileDrawable && ((AnimatedFileDrawable) currentImage).isRunning(); } public AnimatedFileDrawable getAnimation() { - return currentImage instanceof AnimatedFileDrawable ? (AnimatedFileDrawable) currentImage - : null; + return currentImage instanceof AnimatedFileDrawable ? (AnimatedFileDrawable) currentImage : null; } public Integer getTag(boolean thumb) { @@ -930,8 +969,7 @@ public void setTag(Integer value, boolean thumb) { } } - public boolean setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean thumb, - boolean memCache) { + public boolean setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean thumb, boolean memCache) { if (bitmap == null || key == null) { return false; } @@ -940,7 +978,7 @@ public boolean setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean th return false; } if (!(bitmap instanceof AnimatedFileDrawable)) { - ImageLoader.getInstance().incrementUseCount(currentKey); + GalleryImageLoader.getInstance().incrementUseCount(currentKey); } currentImage = bitmap; if (roundRadius != 0 && bitmap instanceof BitmapDrawable) { @@ -948,15 +986,14 @@ public boolean setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean th ((AnimatedFileDrawable) bitmap).setRoundRadius(roundRadius); } else { Bitmap object = bitmap.getBitmap(); - bitmapShader = new BitmapShader(object, Shader.TileMode.CLAMP, - Shader.TileMode.CLAMP); + bitmapShader = new BitmapShader(object, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); } } else { bitmapShader = null; } - if (!memCache && !forcePreview) { - if (currentThumb == null && staticThumb == null || currentAlpha == 1.0f) { + if (!memCache && !forcePreview || forceCrossfade) { + if (currentThumb == null && staticThumb == null || currentAlpha == 1.0f || forceCrossfade) { currentAlpha = 0.0f; lastUpdateAlphaTime = System.currentTimeMillis(); crossfadeWithThumb = currentThumb != null || staticThumb != null; @@ -969,6 +1006,8 @@ public boolean setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean th fileDrawable.setParentView(parentView); if (allowStartAnimation) { fileDrawable.start(); + } else { + fileDrawable.setAllowDecodeSingleFrame(allowDecodeSingleFrame); } } @@ -979,25 +1018,20 @@ public boolean setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean th parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH); } } - } else if (currentThumb == null - && (currentImage == null - || (currentImage instanceof AnimatedFileDrawable - && !((AnimatedFileDrawable) currentImage).hasBitmap()) - || forcePreview)) { + } else if (currentThumb == null && (currentImage == null || (currentImage instanceof AnimatedFileDrawable && !((AnimatedFileDrawable) currentImage).hasBitmap()) || forcePreview)) { if (currentThumbKey == null || !key.equals(currentThumbKey)) { return false; } - ImageLoader.getInstance().incrementUseCount(currentThumbKey); + GalleryImageLoader.getInstance().incrementUseCount(currentThumbKey); currentThumb = bitmap; - if (roundRadius != 0 && currentImage == null && bitmap instanceof BitmapDrawable) { + if (roundRadius != 0 && bitmap instanceof BitmapDrawable) { if (bitmap instanceof AnimatedFileDrawable) { ((AnimatedFileDrawable) bitmap).setRoundRadius(roundRadius); } else { Bitmap object = bitmap.getBitmap(); - bitmapShaderThumb = new BitmapShader(object, Shader.TileMode.CLAMP, - Shader.TileMode.CLAMP); + bitmapShaderThumb = new BitmapShader(object, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); } } else { bitmapShaderThumb = null; @@ -1021,9 +1055,7 @@ public boolean setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean th } if (delegate != null) { - delegate.didSetImage(this, - currentImage != null || currentThumb != null || staticThumb != null, - currentImage == null); + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); } return true; } @@ -1044,8 +1076,8 @@ private void recycleBitmap(String newKey, boolean thumb) { fileDrawable.recycle(); } else if (image instanceof BitmapDrawable) { Bitmap bitmap = ((BitmapDrawable) image).getBitmap(); - boolean canDelete = ImageLoader.getInstance().decrementUseCount(key); - if (!ImageLoader.getInstance().isInCache(key)) { + boolean canDelete = GalleryImageLoader.getInstance().decrementUseCount(key); + if (!GalleryImageLoader.getInstance().isInCache(key)) { if (canDelete) { bitmap.recycle(); } @@ -1067,15 +1099,12 @@ public void didReceivedNotification(int id, Object... args) { String key = (String) args[1]; if (currentThumbKey != null && currentThumbKey.equals(key)) { if (currentThumb == null) { - ImageLoader.getInstance().incrementUseCount(currentThumbKey); + GalleryImageLoader.getInstance().incrementUseCount(currentThumbKey); } currentThumb = (BitmapDrawable) args[0]; - if (roundRadius != 0 && currentImage == null - && currentThumb instanceof BitmapDrawable - && !(currentThumb instanceof AnimatedFileDrawable)) { + if (roundRadius != 0 && currentImage == null && currentThumb != null && !(currentThumb instanceof AnimatedFileDrawable)) { Bitmap object = ((BitmapDrawable) currentThumb).getBitmap(); - bitmapShaderThumb = new BitmapShader(object, Shader.TileMode.CLAMP, - Shader.TileMode.CLAMP); + bitmapShaderThumb = new BitmapShader(object, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); } else { bitmapShaderThumb = null; } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/LruCache.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/LruCache.java index 5349331..7f42dfe 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/LruCache.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/LruCache.java @@ -1,9 +1,9 @@ package com.tangxiaolv.telegramgallery; import android.graphics.drawable.BitmapDrawable; +import android.util.ArrayMap; import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -111,12 +111,12 @@ public BitmapDrawable put(String key, BitmapDrawable value) { */ private void trimToSize(int maxSize, String justAdded) { synchronized (this) { - Iterator> iterator = map.entrySet().iterator(); + Iterator> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { if (size <= maxSize || map.isEmpty()) { break; } - HashMap.Entry entry = iterator.next(); + ArrayMap.Entry entry = iterator.next(); String key = entry.getKey(); if (justAdded != null && justAdded.equals(key)) { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoAlbumPickerActivity.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoAlbumPickerActivity.java index b0fb8b1..203a6e9 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoAlbumPickerActivity.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoAlbumPickerActivity.java @@ -4,9 +4,10 @@ import android.app.Activity; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.drawable.ColorDrawable; import android.os.Build; -import android.text.TextUtils; +import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; import android.view.Surface; @@ -19,42 +20,38 @@ import android.widget.ProgressBar; import android.widget.TextView; -import com.tangxiaolv.telegramgallery.Actionbar.ActionBar; -import com.tangxiaolv.telegramgallery.Actionbar.ActionBarMenu; -import com.tangxiaolv.telegramgallery.Actionbar.ActionBarMenuItem; -import com.tangxiaolv.telegramgallery.Actionbar.BaseFragment; -import com.tangxiaolv.telegramgallery.Components.PhotoPickerAlbumsCell; -import com.tangxiaolv.telegramgallery.Components.PhotoPickerSearchCell; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; -import com.tangxiaolv.telegramgallery.Utils.MediaController; -import com.tangxiaolv.telegramgallery.Utils.NotificationCenter; +import com.tangxiaolv.telegramgallery.actionbar.ActionBar; +import com.tangxiaolv.telegramgallery.actionbar.ActionBarMenuItem; +import com.tangxiaolv.telegramgallery.actionbar.BaseFragment; +import com.tangxiaolv.telegramgallery.components.PhotoPickerAlbumsCell; +import com.tangxiaolv.telegramgallery.tl.FileLocation; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.LocaleController; +import com.tangxiaolv.telegramgallery.utils.MediaController; +import com.tangxiaolv.telegramgallery.utils.NotificationCenter; +import com.tangxiaolv.telegramgallery.entity.MediaInfo; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import static com.tangxiaolv.telegramgallery.GalleryActivity.getConfig; +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; +import static com.tangxiaolv.telegramgallery.utils.VideoUtils.createDecoder; +import static com.tangxiaolv.telegramgallery.utils.VideoUtils.destroyDecoder; + public class PhotoAlbumPickerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { - - public interface PhotoAlbumPickerActivityDelegate { - void didSelectPhotos(ArrayList photos, ArrayList captions); - - boolean didSelectVideo(String path); - - void startPhotoSelectActivity(); + interface PhotoAlbumPickerActivityDelegate { + void didSelectMedia(ArrayList medias); } - public static int limitPickPhoto; - public static String sHintOfPick; - public static boolean DarkTheme = true; - private ArrayList albumsSorted = null; private ArrayList videoAlbumsSorted = null; - private HashMap selectedPhotos = new HashMap<>(); - private HashMap checkboxTag = new HashMap<>(); - private List selectedPhotosSortEnd = new ArrayList<>(); + private LinkedHashMap selectedPhotos = + new LinkedHashMap<>(getConfig().getLimitPickPhoto()); private boolean loading = false; private int columnsCount = 2; @@ -69,9 +66,7 @@ public interface PhotoAlbumPickerActivityDelegate { private boolean singlePhoto; private boolean allowGifs; private int selectedMode; - private final String[] filterMimeTypes; - - private final int[] imageCheckIndexArr; + private int limitPickPhoto; private PhotoAlbumPickerActivityDelegate delegate; private PhotoPickerActivity currentPhotoPickerActivity; @@ -79,24 +74,20 @@ public interface PhotoAlbumPickerActivityDelegate { private final static int item_photos = 2; private final static int item_video = 3; - public PhotoAlbumPickerActivity(String[] filterMimeTypes, - int limitPick, - boolean singlePhoto, - String hintOfPick, - boolean allowGifs) { + //非预览中被选择的图片imageId:corner + private final LinkedHashMap unPreviewCheckeds = new LinkedHashMap<>(limitPickPhoto); + + public PhotoAlbumPickerActivity(boolean allowGifs) { super(); - limitPickPhoto = limitPick; - sHintOfPick = hintOfPick; - this.filterMimeTypes = filterMimeTypes; - this.imageCheckIndexArr = new int[limitPick]; - this.singlePhoto = singlePhoto; + this.limitPickPhoto = getConfig().getLimitPickPhoto(); + this.singlePhoto = getConfig().isSinglePhoto(); this.allowGifs = allowGifs; } @Override public boolean onFragmentCreate() { loading = true; - MediaController.loadGalleryPhotosAlbums(classGuid, filterMimeTypes); + MediaController.loadGalleryPhotosAlbums(classGuid, getConfig().getFilterMimeTypes()); NotificationCenter.getInstance().addObserver(this, NotificationCenter.albumsDidLoaded); return super.onFragmentCreate(); } @@ -110,10 +101,10 @@ public void onFragmentDestroy() { @SuppressWarnings("unchecked") @Override public View createView(Context context) { - actionBar.setBackgroundColor(Theme.ACTION_BAR_MEDIA_PICKER_COLOR); + actionBar.setBackgroundColor(DARK_THEME ? Theme.ACTION_BAR_MEDIA_PICKER_COLOR : 0xfff9f9f9); actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR); - actionBar.setBackButtonImage(R.drawable.ic_ab_back); - //actionBar.setBackText(context.getString(R.string.Cancel)); + // actionBar.setBackButtonImage(R.drawable.album_ab_back); + actionBar.setBackText(LocaleController.getString("Cancel", R.string.Cancel)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { @@ -122,7 +113,6 @@ public void onItemClick(int id) { } else if (id == 1) { if (delegate != null) { finishFragment(false); - delegate.startPhotoSelectActivity(); } } else if (id == item_photos) { if (selectedMode == 0) { @@ -130,8 +120,8 @@ public void onItemClick(int id) { } selectedMode = 0; dropDown.setText( - R.string.PickerPhotos); - emptyView.setText(R.string.NoPhotos); + LocaleController.getString("PickerPhotos", R.string.PickerPhotos)); + emptyView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos)); listAdapter.notifyDataSetChanged(); } else if (id == item_video) { if (selectedMode == 1) { @@ -139,65 +129,19 @@ public void onItemClick(int id) { } selectedMode = 1; dropDown.setText( - R.string.PickerVideo); - emptyView.setText(R.string.NoVideo); + LocaleController.getString("PickerVideo", R.string.PickerVideo)); + emptyView.setText(LocaleController.getString("NoVideo", R.string.NoVideo)); listAdapter.notifyDataSetChanged(); } } }); - fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(DarkTheme ? 0xff000000 : 0xffffffff); - //==============videos pick==================== - if (!singlePhoto) { - selectedMode = 0; - - ActionBarMenu menu = actionBar.createMenu(); - dropDownContainer = new ActionBarMenuItem(context, menu, 0); - dropDownContainer.setSubMenuOpenSide(1); - dropDownContainer.addSubItem(item_photos, context.getString(R.string.PickerPhotos), 0); - dropDownContainer.addSubItem(item_video, context.getString(R.string.PickerVideo), 0); - actionBar.addView(dropDownContainer); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) dropDownContainer.getLayoutParams(); - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.rightMargin = AndroidUtilities.dp(40); -// layoutParams.leftMargin = AndroidUtilities.getRealScreenSize().x / 2; - layoutParams.leftMargin = AndroidUtilities.dp(56); - layoutParams.gravity = Gravity.TOP | Gravity.LEFT; - dropDownContainer.setLayoutParams(layoutParams); - dropDownContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dropDownContainer.toggleSubMenu(); - } - }); + frameLayout.setBackgroundColor(DARK_THEME ? 0xff000000 : 0xffffffff); - dropDown = new TextView(context); - dropDown.setGravity(Gravity.LEFT); - dropDown.setSingleLine(true); - dropDown.setLines(1); - dropDown.setMaxLines(1); - dropDown.setEllipsize(TextUtils.TruncateAt.END); - dropDown.setTextColor(0xffffffff); - // dropDown.getPaint().setFakeBoldText(true); - dropDown.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_arrow_drop_down, 0); - dropDown.setCompoundDrawablePadding(AndroidUtilities.dp(4)); -// dropDown.setPadding(0, 0, AndroidUtilities.dp(10), 0); - dropDown.setText(R.string.PickerPhotos); - dropDownContainer.addView(dropDown); - layoutParams = (FrameLayout.LayoutParams) dropDown.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; -// layoutParams.leftMargin = AndroidUtilities.dp(dropDown.getTextSize()); - layoutParams.gravity = Gravity.CENTER_VERTICAL; - dropDown.setLayoutParams(layoutParams); - } else { - actionBar.setTitle(context.getString(R.string.Album)); - } + actionBar.setTitle(LocaleController.getString("Gallery", R.string.Gallery)); listView = new ListView(context); listView.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), @@ -222,10 +166,10 @@ public void onClick(View view) { emptyView = new TextView(context); emptyView.setTextColor(0xff808080); - emptyView.setTextSize(20); + emptyView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); emptyView.setGravity(Gravity.CENTER); emptyView.setVisibility(View.GONE); - emptyView.setText(R.string.NoPhotos); + emptyView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos)); frameLayout.addView(emptyView); layoutParams = (FrameLayout.LayoutParams) emptyView.getLayoutParams(); layoutParams.width = LayoutHelper.MATCH_PARENT; @@ -256,29 +200,7 @@ public boolean onTouch(View v, MotionEvent event) { layoutParams.gravity = Gravity.CENTER; progressView.setLayoutParams(layoutParams); - // pickerBottomLayout = new PickerBottomLayout(context); - // pickerBottomLayout.cancelButton.setVisibility(singlePhoto ? View.GONE : View.VISIBLE); - // frameLayout.addView(pickerBottomLayout); - // layoutParams = (FrameLayout.LayoutParams) pickerBottomLayout.getLayoutParams(); - // layoutParams.width = LayoutHelper.MATCH_PARENT; - // layoutParams.height = AndroidUtilities.dp(48); - // layoutParams.gravity = Gravity.BOTTOM; - // pickerBottomLayout.setLayoutParams(layoutParams); - // pickerBottomLayout.cancelButton.setOnClickListener(new View.OnClickListener() { - // @Override - // public void onClick(View view) { - // openPreview(); - // } - // }); - // pickerBottomLayout.doneButton.setOnClickListener(new View.OnClickListener() { - // @Override - // public void onClick(View view) { - // sendSelectedPhotos(); - // finishFragment(); - // } - // }); - - if (loading && (albumsSorted == null || albumsSorted != null && albumsSorted.isEmpty())) { + if (loading && (albumsSorted == null || albumsSorted.isEmpty())) { progressView.setVisibility(View.VISIBLE); listView.setEmptyView(null); } else { @@ -286,33 +208,62 @@ public boolean onTouch(View v, MotionEvent event) { listView.setEmptyView(emptyView); } - // pickerBottomLayout.updateSelectedCount(selectedPhotos.size() + selectedWebPhotos.size(), - // true); - return fragmentView; } - public void openPreview() { + private void openPreview() { final List selectPhotos = computeSelectPhotos(); if (selectPhotos != null) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhotoForSelect(selectPhotos, true, 0, - singlePhoto ? 1 : 0, new CustomProvider(selectPhotos)); + PhotoViewer.getInstance().openPhotoForSelect(selectPhotos, true, 0, new CustomProvider(selectPhotos)); } } public class CustomProvider extends PhotoViewer.PreviewEmptyPhotoViewerProvider { + private MediaController.PhotoEntry[] originArr; private MediaController.PhotoEntry[] selectArr; private MediaController.PhotoEntry[] removedArr; public CustomProvider(List selectPhotos) { int size = selectPhotos.size(); + originArr = new MediaController.PhotoEntry[size]; selectArr = new MediaController.PhotoEntry[size]; removedArr = new MediaController.PhotoEntry[size]; for (int i = 0; i < size; i++) { selectArr[i] = (MediaController.PhotoEntry) selectPhotos.get(i); } + System.arraycopy(selectArr, 0, originArr, 0, size); + } + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(FileLocation fileLocation, int index) { + MediaController.PhotoEntry entry = originArr[index]; + PhotoViewer.PlaceProviderObject cell = currentPhotoPickerActivity.getPlaceForPhoto(entry.imageId); + if (cell == null) { + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = 0; + object.viewY = 0; + object.imageReceiver = new ImageReceiver(); + object.parentView = listView; + return object; + } + return cell; + } + + @Override + public void willSwitchFromPhoto(FileLocation fileLocation, int index) { + currentPhotoPickerActivity.willSwitchFromPhoto(fileLocation, index); + } + + @Override + public Bitmap getThumbForPhoto(FileLocation fileLocation, int index) { + return currentPhotoPickerActivity.getThumbForPhoto(fileLocation, index); + } + + @Override + public void willHidePhotoViewer() { + currentPhotoPickerActivity.willHidePhotoViewer(); } @Override @@ -328,8 +279,7 @@ public boolean isPhotoChecked(int index) { @Override public void sendButtonPressed(int index) { selectedPhotos.clear(); - for (int i = 0; i < selectArr.length; i++) { - MediaController.PhotoEntry photoEntry = selectArr[i]; + for (MediaController.PhotoEntry photoEntry : selectArr) { if (photoEntry != null) { selectedPhotos.put(photoEntry.imageId, photoEntry); } @@ -371,59 +321,30 @@ public void selectChanged(int index, boolean checked) { } @Override - public void previewExit() { - super.previewExit(); + public void setIsOriginal(boolean isOriginal) { + currentPhotoPickerActivity.setIsOriginal(isOriginal); + } + + @Override + public List getSelectedPhotos() { + return computeSelectPhotos(); } private int getRealCount() { - int count = 0; - for (int i = 0; i < selectArr.length; i++) { - if (selectArr[i] != null) { - count++; - } - } - return count; + return selectedPhotos.size(); } } - public List computeSelectPhotos() { + private List computeSelectPhotos() { int size = selectedPhotos.size(); if (size > 0) { - Object[] obj = new Object[limitPickPhoto]; - for (Integer i : selectedPhotos.keySet()) { - MediaController.PhotoEntry entry = selectedPhotos.get(i); - obj[entry.sortindex - 1] = entry; - } - selectedPhotosSortEnd.clear(); - List list = new ArrayList<>(); - for (int i = 0; i < limitPickPhoto; i++) { - Object o = obj[i]; - if (o != null) { - list.add(o); - selectedPhotosSortEnd.add((MediaController.PhotoEntry) o); - } - } - - return list; + ArrayList result = new ArrayList<>(limitPickPhoto); + result.addAll(selectedPhotos.values()); + return result; } return null; } - private void fillSelectedPhotosSortEnd() { - Object[] obj = new Object[limitPickPhoto]; - for (Integer i : selectedPhotos.keySet()) { - MediaController.PhotoEntry entry = selectedPhotos.get(i); - obj[entry.sortindex - 1] = entry; - } - selectedPhotosSortEnd.clear(); - for (int i = 0; i < limitPickPhoto; i++) { - Object o = obj[i]; - if (o != null) { - selectedPhotosSortEnd.add((MediaController.PhotoEntry) o); - } - } - } - @Override public void onPause() { super.onPause(); @@ -481,20 +402,38 @@ private void sendSelectedPhotos() { if (selectedPhotos.isEmpty() && delegate == null || sendPressed) { return; } - fillSelectedPhotosSortEnd(); sendPressed = true; - ArrayList photos = new ArrayList<>(); - ArrayList captions = new ArrayList<>(); - for (MediaController.PhotoEntry photoEntry : selectedPhotosSortEnd) { - if (photoEntry.imagePath != null) { - photos.add(photoEntry.imagePath); - captions.add(photoEntry.caption != null ? photoEntry.caption.toString() : null); - } else if (photoEntry.path != null) { - photos.add(photoEntry.path); - captions.add(photoEntry.caption != null ? photoEntry.caption.toString() : null); + ArrayList medias = new ArrayList<>(); + List selects = computeSelectPhotos(); + if (selects == null) { + return; + } + + int[] metaData = new int[4]; + for (Object entry : selects) { + MediaController.PhotoEntry media = (MediaController.PhotoEntry) entry; + MediaInfo info = new MediaInfo(); + if (media.imagePath != null) { + info.setPath(media.imagePath); + } else if (media.path != null) { + info.setPath(media.path); + } + info.setSize(media.size); + info.setVideoDuration(media.duration); + info.setMimeType(media.mimeType); + info.setThumbPath(media.thumbPath); + info.setTitle(media.title); + if (media.mimeType.contains("video")){ + int ptr = createDecoder(info.getPath(), metaData); + destroyDecoder(ptr); + info.setWidth(metaData[0]); + info.setHeight(metaData[1]); + info.setRotation(metaData[2]); } + medias.add(info); } - delegate.didSelectPhotos(photos, captions); + delegate.didSelectMedia(medias); + AndroidUtilities.cancelToast(); } private void fixLayout() { @@ -532,93 +471,102 @@ private void fixLayoutInternal() { if (!AndroidUtilities.isTablet()) { FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) dropDownContainer .getLayoutParams(); - /*layoutParams.topMargin = (Build.VERSION.SDK_INT >= 21 - ? AndroidUtilities.statusBarHeight : 0);*/ + layoutParams.topMargin = (Build.VERSION.SDK_INT >= 21 + ? AndroidUtilities.statusBarHeight : 0); dropDownContainer.setLayoutParams(layoutParams); } if (!AndroidUtilities.isTablet() && Gallery.applicationContext.getResources() .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - dropDown.setTextSize(18); + dropDown.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); } else { - dropDown.setTextSize(20); + dropDown.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); } } } private void openPhotoPicker(MediaController.AlbumEntry albumEntry, int type, boolean withAnim) { - currentPhotoPickerActivity = new PhotoPickerActivity(type, limitPickPhoto, albumEntry, - selectedPhotos, null, singlePhoto); - currentPhotoPickerActivity - .setDelegate(new PhotoPickerActivity.PhotoPickerActivityDelegate() { - @Override - public void selectedPhotosChanged() { - // if (pickerBottomLayout != null) { - // pickerBottomLayout.updateSelectedCount( - // selectedPhotos.size() + selectedWebPhotos.size(), true); - // } - } + currentPhotoPickerActivity = new PhotoPickerActivity(type, albumEntry, selectedPhotos, singlePhoto); + currentPhotoPickerActivity.setDelegate(new PhotoPickerActivity.PhotoPickerActivityDelegate() { + //预览中已选择集合,-1为未被选择index:corner + private final int[] previewCheckeds = new int[limitPickPhoto]; - @Override - public void actionButtonPressed(boolean canceled) { - if (!canceled) { - sendSelectedPhotos(); - } else { - getParentActivity().finish(); - } - removeSelfFromStack(); - } + @Override + public void actionButtonPressed(boolean canceled) { + if (!canceled) { + sendSelectedPhotos(); + } else if (getParentActivity() != null) { + getParentActivity().finish(); + } + removeSelfFromStack(); + } - @Override - public boolean didSelectVideo(String path) { - removeSelfFromStack(); - return delegate.didSelectVideo(path); - } + @Override + public boolean didSelectVideo(String path) { + removeSelfFromStack(); + return false; + } - @Override - public int getCheckboxTag(int imageId) { - Integer cornerIndex = checkboxTag.get(imageId); - return cornerIndex == null ? -1 : cornerIndex; - } + @Override + public int getCheckboxTag(int imageId) { + //返回角标,未选中为-1 + Integer cornerIndex = unPreviewCheckeds.get(imageId); + return cornerIndex == null ? -1 : cornerIndex; + } - @Override - public void putCheckboxTag(int imageId, int cornerIndex) { - imageCheckIndexArr[cornerIndex - 1] = cornerIndex; - checkboxTag.put(imageId, cornerIndex); - } + @Override + public void putCheckboxTag(int imageId, int cornerIndex) { + if (PhotoViewer.getInstance().isInPreviewMode()) { + previewCheckeds[cornerIndex - 1] = cornerIndex; + } + unPreviewCheckeds.put(imageId, cornerIndex); + } - @Override - public void removeCheckboxTag(int imageId) { - Integer remove = checkboxTag.remove(imageId); - if (remove != null) { - imageCheckIndexArr[remove - 1] = -1; - } - } + @Override + public void removeCheckboxTag(int imageId) { + Integer remove = unPreviewCheckeds.remove(imageId); + if (remove != null && PhotoViewer.getInstance().isInPreviewMode()) { + + previewCheckeds[remove - 1] = -1; + } + int corner = 1; + for (Integer id : unPreviewCheckeds.keySet()) { + unPreviewCheckeds.put(id, corner); + corner++; + } + } - @Override - public int generateCheckCorner() { - int length = imageCheckIndexArr.length; - for (int i = 0; i < length; i++) { - if (imageCheckIndexArr[i] <= 0) { - return i + 1; - } + @Override + public int generateCheckCorner() { + if (PhotoViewer.getInstance().isInPreviewMode()) { + for (int i = 0; i < limitPickPhoto; i++) { + if (previewCheckeds[i] <= 0) { + return i + 1; } - return -1; } + return -1; + } + return unPreviewCheckeds.size() + 1; + } - @Override - public void openPreview() { - PhotoAlbumPickerActivity.this.openPreview(); - } - }); + @Override + public void openPreview() { + PhotoAlbumPickerActivity.this.openPreview(); + } + + @Override + public PhotoAlbumPickerActivity getPhotoAlbumPickerActivity() { + return PhotoAlbumPickerActivity.this; + } + }); presentFragment(currentPhotoPickerActivity, false, withAnim); } private class ListAdapter extends BaseFragmentAdapter { private Context mContext; - public ListAdapter(Context context) { + ListAdapter(Context context) { mContext = context; } @@ -635,14 +583,11 @@ public boolean isEnabled(int i) { @Override public int getCount() { if (singlePhoto || selectedMode == 0) { - int count = albumsSorted != null - ? (int) Math.ceil(albumsSorted.size() / (float) columnsCount) : 0; - return count; + return albumsSorted != null ? (int) Math.ceil(albumsSorted.size() / (float) columnsCount) : 0; // return 1 + (albumsSorted != null // ? (int) Math.ceil(albumsSorted.size() / (float) columnsCount) : 0); } else { - return (videoAlbumsSorted != null - ? (int) Math.ceil(videoAlbumsSorted.size() / (float) columnsCount) : 0); + return (videoAlbumsSorted != null ? (int) Math.ceil(videoAlbumsSorted.size() / (float) columnsCount) : 0); } } @@ -704,18 +649,6 @@ public void didSelectAlbum(MediaController.AlbumEntry albumEntry) { } } photoPickerAlbumsCell.requestLayout(); - } else if (type == 1) { - // 图片搜索 - if (view == null) { - view = new PhotoPickerSearchCell(mContext, allowGifs); - ((PhotoPickerSearchCell) view) - .setDelegate(new PhotoPickerSearchCell.PhotoPickerSearchCellDelegate() { - @Override - public void didPressedSearchButton(int index) { - openPhotoPicker(null, index, false); - } - }); - } } return view; } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoPickerActivity.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoPickerActivity.java index 08d4b6d..9350f06 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoPickerActivity.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoPickerActivity.java @@ -2,13 +2,10 @@ package com.tangxiaolv.telegramgallery; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.graphics.Bitmap; -import android.text.TextUtils; +import android.util.TypedValue; import android.view.Gravity; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.View; @@ -23,32 +20,33 @@ import android.widget.ProgressBar; import android.widget.TextView; -import com.tangxiaolv.telegramgallery.Actionbar.ActionBar; -import com.tangxiaolv.telegramgallery.Actionbar.ActionBarMenu; -import com.tangxiaolv.telegramgallery.Actionbar.ActionBarMenuItem; -import com.tangxiaolv.telegramgallery.Actionbar.BaseFragment; -import com.tangxiaolv.telegramgallery.Components.BackupImageView; -import com.tangxiaolv.telegramgallery.Components.PhotoPickerPhotoCell; -import com.tangxiaolv.telegramgallery.Components.PickerBottomLayout; -import com.tangxiaolv.telegramgallery.TL.FileLocation; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.FileLoader; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; -import com.tangxiaolv.telegramgallery.Utils.MediaController; -import com.tangxiaolv.telegramgallery.Utils.NotificationCenter; +import com.tangxiaolv.telegramgallery.actionbar.ActionBar; +import com.tangxiaolv.telegramgallery.actionbar.ActionBarMenu; +import com.tangxiaolv.telegramgallery.actionbar.BaseFragment; +import com.tangxiaolv.telegramgallery.components.BackupImageView; +import com.tangxiaolv.telegramgallery.components.PhotoPickerPhotoCell; +import com.tangxiaolv.telegramgallery.components.PickerBottomLayout; +import com.tangxiaolv.telegramgallery.tl.FileLocation; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.Constants; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.LocaleController; +import com.tangxiaolv.telegramgallery.utils.MediaController; +import com.tangxiaolv.telegramgallery.utils.NotificationCenter; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; -import static com.tangxiaolv.telegramgallery.PhotoAlbumPickerActivity.DarkTheme; -import static com.tangxiaolv.telegramgallery.PhotoAlbumPickerActivity.sHintOfPick; +import static com.tangxiaolv.telegramgallery.GalleryActivity.getConfig; +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; public class PhotoPickerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider { public interface PhotoPickerActivityDelegate { - void selectedPhotosChanged(); - void actionButtonPressed(boolean canceled); boolean didSelectVideo(String path); @@ -62,48 +60,36 @@ public interface PhotoPickerActivityDelegate { int generateCheckCorner(); void openPreview(); + + PhotoAlbumPickerActivity getPhotoAlbumPickerActivity(); } private int type; - private HashMap selectedPhotos; - private ArrayList recentImages; - - private ArrayList searchResult = new ArrayList<>(); - - private boolean searching; - private String nextSearchBingString; - private boolean giphySearchEndReached = true; - private String lastSearchString; - private boolean loadingRecent; - private int nextGiphySearchOffset; - private int giphyReqId; - private int lastSearchToken; - private final int limitPickPhoto; + // 已选择的图片 + private LinkedHashMap selectedPhotos; + private final int limitPickPhoto; private MediaController.AlbumEntry selectedAlbum; - private GridView listView; private ListAdapter listAdapter; private PickerBottomLayout pickerBottomLayout; private FrameLayout progressView; private TextView emptyView; - private ActionBarMenuItem searchItem; + private PhotoPickerActivityDelegate delegate; private int itemWidth = 100; private boolean sendPressed; private boolean singlePhoto; + private int currentVideoEditId; - private PhotoPickerActivityDelegate delegate; - - public PhotoPickerActivity(int type, int limitPickPhoto, - MediaController.AlbumEntry selectedAlbum, - HashMap selectedPhotos, - ArrayList recentImages, boolean onlyOnePhoto) { + public PhotoPickerActivity(int type, + MediaController.AlbumEntry selectedAlbum, + LinkedHashMap selectedPhotos, + boolean onlyOnePhoto) { super(); - this.limitPickPhoto = limitPickPhoto; + this.limitPickPhoto = getConfig().getLimitPickPhoto(); this.selectedAlbum = selectedAlbum; this.selectedPhotos = selectedPhotos; this.type = type; - this.recentImages = recentImages; this.singlePhoto = onlyOnePhoto; if (selectedAlbum != null && selectedAlbum.isVideo) { singlePhoto = true; @@ -123,10 +109,11 @@ public void onFragmentDestroy() { @SuppressWarnings("unchecked") @Override public View createView(Context context) { - actionBar.setBackgroundColor(Theme.ACTION_BAR_MEDIA_PICKER_COLOR); + actionBar.setBackgroundColor(DARK_THEME ? Theme.ACTION_BAR_MEDIA_PICKER_COLOR : 0xfff9f9f9); actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR); - actionBar.setBackButtonImage(R.drawable.ic_ab_back); - // actionBar.setBackText("返回"); + // actionBar.setBackButtonImage(DARK_THEME ? R.drawable.album_ab_back : + // R.drawable.album_ab_back_bule); + actionBar.setBackText(LocaleController.getString("Cancel", R.string.Cancel)); if (selectedAlbum != null) { actionBar.setTitle(selectedAlbum.bucketName); } @@ -135,19 +122,17 @@ public View createView(Context context) { @Override public void onItemClick(int id) { if (id == -1) { - finishFragment(); + delegate.actionButtonPressed(true); } } }); ActionBarMenu menu = actionBar.createMenu(); - TextView cancel = new TextView(context); - LinearLayout.LayoutParams cancelParams = LayoutHelper - .createLinear(LayoutHelper.WRAP_CONTENT, -1); - cancel.setTextSize(18); - cancel.setText(R.string.Cancel); - cancel.setTextColor(0xffffffff); + LinearLayout.LayoutParams cancelParams = LayoutHelper.createLinear(48, -1); + cancel.setText(LocaleController.getString("Gallery", R.string.Gallery)); + cancel.setTextColor(DARK_THEME ? 0xffffffff : 0xff007aff); + cancel.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); cancel.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL); cancelParams.setMargins(0, 0, AndroidUtilities.dp(8), 0); cancel.setLayoutParams(cancelParams); @@ -155,29 +140,18 @@ public void onItemClick(int id) { @Override public void onClick(View v) { finishFragment(); - delegate.actionButtonPressed(true); + //presentFragment(delegate.getPhotoAlbumPickerActivity(), false, true); } }); - menu.addView(cancel); - - if (selectedAlbum == null) { - if (type == 0) { - searchItem.getSearchField().setHint(R.string.SearchImagesTitle); - } else if (type == 1) { - searchItem.getSearchField().setHint( - R.string.SearchGifsTitle); - } - } + menu.addView(cancel); fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout - .setBackgroundColor(DarkTheme ? 0xff000000 : 0xffffffff); + frameLayout.setBackgroundColor(DARK_THEME ? 0xff000000 : 0xffffffff); listView = new GridView(context); - listView.setPadding(AndroidUtilities.dp(4), AndroidUtilities.dp(4), AndroidUtilities.dp(4), - AndroidUtilities.dp(4)); + listView.setPadding(AndroidUtilities.dp(4), AndroidUtilities.dp(4), AndroidUtilities.dp(4), AndroidUtilities.dp(4)); listView.setClipToPadding(false); listView.setDrawSelectorOnTop(true); listView.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); @@ -186,96 +160,62 @@ public void onClick(View v) { listView.setNumColumns(GridView.AUTO_FIT); listView.setVerticalSpacing(AndroidUtilities.dp(4)); listView.setHorizontalSpacing(AndroidUtilities.dp(4)); - listView.setSelector(R.drawable.list_selector); + listView.setSelector(R.drawable.album_list_selector); frameLayout.addView(listView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView - .getLayoutParams(); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); layoutParams.width = LayoutHelper.MATCH_PARENT; layoutParams.height = LayoutHelper.MATCH_PARENT; layoutParams.bottomMargin = singlePhoto ? 0 : AndroidUtilities.dp(48); listView.setLayoutParams(layoutParams); listView.setAdapter(listAdapter = new ListAdapter(context)); AndroidUtilities.setListViewEdgeEffectColor(listView, 0xff333333); + // 点击进去详情页 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { + public void onItemClick(AdapterView adapterView, View view, int position, long l) { if (selectedAlbum != null && selectedAlbum.isVideo) { - if (i < 0 || i >= selectedAlbum.photos.size()) { + if (position < 0 || position >= selectedAlbum.photos.size()) { return; } - if (delegate.didSelectVideo(selectedAlbum.photos.get(i).path)) { + if (delegate.didSelectVideo(selectedAlbum.photos.get(position).path)) { finishFragment(); } + } else if (selectedAlbum.photos.get(position).mimeType.contains("video") + && getConfig().isVideoEditMode() && selectedPhotos.size() > 0) { + AndroidUtilities.showToast(getParentActivity().getString(R.string.NoImageAndVideo)); } else { - ArrayList arrayList; + ArrayList arrayList = null; if (selectedAlbum != null) { arrayList = (ArrayList) selectedAlbum.photos; - } else { - if (searchResult.isEmpty() && lastSearchString == null) { - arrayList = (ArrayList) recentImages; - } else { - arrayList = (ArrayList) searchResult; - } } - if (i < 0 || i >= arrayList.size()) { + if (position < 0 || position >= arrayList.size()) { return; } - if (searchItem != null) { - AndroidUtilities.hideKeyboard(searchItem.getSearchField()); + + MediaController.PhotoEntry entry = (MediaController.PhotoEntry) arrayList.get(position); + if (getConfig().isVideoEditMode() && entry != null && entry.isVideo) { + currentVideoEditId = entry.imageId; + selectedPhotos.put(currentVideoEditId, entry); + pickerBottomLayout.updateSelectedCount(0, false); } PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhotoForSelect(arrayList, false, i, - singlePhoto ? 1 : 0, - PhotoPickerActivity.this); + PhotoViewer.getInstance().openPhotoForSelect(arrayList, false, position, PhotoPickerActivity.this); } } }); - if (selectedAlbum == null) { - listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, - long id) { - if (searchResult.isEmpty() && lastSearchString == null) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(R.string.app_name) - .setMessage( - R.string.ClearSearch) - .setPositiveButton( - R.string.ClearButton, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - recentImages.clear(); - if (listAdapter != null) { - listAdapter.notifyDataSetChanged(); - } - } - }) - .setNegativeButton( - R.string.Cancel, null); - showDialog(builder.create()); - return true; - } - return false; - } - }); - } - emptyView = new TextView(context); emptyView.setTextColor(0xff808080); - emptyView.setTextSize(20); + emptyView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); emptyView.setGravity(Gravity.CENTER); emptyView.setVisibility(View.GONE); if (selectedAlbum != null) { - emptyView.setText(R.string.NoPhotos); + emptyView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos)); } else { if (type == 0) { - emptyView.setText( - R.string.NoRecentPhotos); + emptyView.setText(LocaleController.getString("NoRecentPhotos", R.string.NoRecentPhotos)); } else if (type == 1) { - emptyView - .setText(R.string.NoRecentGIFs); + emptyView.setText(LocaleController.getString("NoRecentGIFs", R.string.NoRecentGIFs)); } } frameLayout.addView(emptyView); @@ -302,11 +242,7 @@ public void onScrollStateChanged(AbsListView absListView, int i) { @Override public void onScroll(AbsListView absListView, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - if (visibleItemCount != 0 - && firstVisibleItem + visibleItemCount > totalItemCount - 2 - && !searching) { - } + int visibleItemCount, int totalItemCount) { } }); @@ -326,29 +262,50 @@ public void onScroll(AbsListView absListView, int firstVisibleItem, layoutParams.height = LayoutHelper.WRAP_CONTENT; layoutParams.gravity = Gravity.CENTER; progressBar.setLayoutParams(layoutParams); - - updateSearchInterface(); } - pickerBottomLayout = new PickerBottomLayout(context, DarkTheme); + pickerBottomLayout = new PickerBottomLayout(context); frameLayout.addView(pickerBottomLayout); layoutParams = (FrameLayout.LayoutParams) pickerBottomLayout.getLayoutParams(); layoutParams.width = LayoutHelper.MATCH_PARENT; layoutParams.height = AndroidUtilities.dp(48); layoutParams.gravity = Gravity.BOTTOM; pickerBottomLayout.setLayoutParams(layoutParams); + pickerBottomLayout.setBackgroundColor(DARK_THEME ? 0xff1a1a1a : 0xfff9f9f9); + // 预览按钮 pickerBottomLayout.cancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { delegate.openPreview(); } }); + // 完成按钮 pickerBottomLayout.doneButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { sendSelectedPhotos(); } }); + + // 原图按钮 + pickerBottomLayout.originalView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!pickerBottomLayout.isOriginChecked()) { + for (Map.Entry entry : selectedPhotos.entrySet()) { + MediaController.PhotoEntry p = entry.getValue(); + if (!p.isVideo && p.size >= getConfig().getMaxImageSize()) { + AndroidUtilities.showToast(selectedPhotos.size() > 1 ? + getParentActivity().getString(R.string.PartSizeOutOfRange) : + getParentActivity().getString(R.string.SizeOutOfRange) + ); + break; + } + } + } + pickerBottomLayout.setChecked(!pickerBottomLayout.isOriginChecked(), false); + } + }); if (singlePhoto) { pickerBottomLayout.setVisibility(View.GONE); } @@ -363,12 +320,7 @@ public void onClick(View view) { public void onResume() { super.onResume(); if (listAdapter != null) { - listAdapter.notifyDataSetChanged(); - } - if (searchItem != null) { - searchItem.openSearch(true); - getParentActivity().getWindow() - .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + refreshList(); } fixLayout(); } @@ -384,12 +336,6 @@ public void onConfigurationChanged(android.content.res.Configuration newConfig) public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.closeChats) { removeSelfFromStack(); - } else if (id == NotificationCenter.recentImagesDidLoaded) { - if (selectedAlbum == null && type == (Integer) args[0]) { - recentImages = (ArrayList) args[1]; - loadingRecent = false; - updateSearchInterface(); - } } } @@ -405,16 +351,6 @@ private PhotoPickerPhotoCell getCellForIndex(int index) { if (num < 0 || num >= selectedAlbum.photos.size()) { continue; } - } else { - ArrayList array; - if (searchResult.isEmpty() && lastSearchString == null) { - array = recentImages; - } else { - array = searchResult; - } - if (num < 0 || num >= array.size()) { - continue; - } } if (num == index) { return cell; @@ -443,6 +379,17 @@ public PhotoViewer.PlaceProviderObject getPlaceForPhoto(FileLocation fileLocatio return null; } + PhotoViewer.PlaceProviderObject getPlaceForPhoto(int imageId) { + int size = selectedAlbum.photos.size(); + for (int i = 0; i < size; i++) { + MediaController.PhotoEntry entry = selectedAlbum.photos.get(i); + if (entry.imageId == imageId) { + return getPlaceForPhoto(null, i); + } + } + return null; + } + @Override public void updatePhotoAtIndex(int index) { PhotoPickerPhotoCell cell = getCellForIndex(index); @@ -452,40 +399,28 @@ public void updatePhotoAtIndex(int index) { MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); if (photoEntry.thumbPath != null) { cell.photoImage.setImage(photoEntry.thumbPath, null, - cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + cell.getContext().getResources().getDrawable( + DARK_THEME ? R.drawable.album_nophotos + : R.drawable.album_nophotos_new)); } else if (photoEntry.path != null) { cell.photoImage.setOrientation(photoEntry.orientation, true); if (photoEntry.isVideo) { cell.photoImage.setImage( "vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, - cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + cell.getContext().getResources().getDrawable( + DARK_THEME ? R.drawable.album_nophotos + : R.drawable.album_nophotos_new)); } else { cell.photoImage.setImage( "thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, - cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + cell.getContext().getResources().getDrawable( + DARK_THEME ? R.drawable.album_nophotos + : R.drawable.album_nophotos_new)); } } else { - cell.photoImage.setImageResource(R.drawable.nophotos); - } - } else { - ArrayList array; - if (searchResult.isEmpty() && lastSearchString == null) { - array = recentImages; - } else { - array = searchResult; - } - MediaController.SearchImage photoEntry = array.get(index); - if (photoEntry.document != null && photoEntry.document.thumb != null) { - cell.photoImage.setImage(photoEntry.document.thumb.location, null, - cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.thumbPath != null) { - cell.photoImage.setImage(photoEntry.thumbPath, null, - cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.thumbUrl != null && photoEntry.thumbUrl.length() > 0) { - cell.photoImage.setImage(photoEntry.thumbUrl, null, - cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else { - cell.photoImage.setImageResource(R.drawable.nophotos); + cell.photoImage + .setImageResource(DARK_THEME ? R.drawable.album_nophotos + : R.drawable.album_nophotos_new); } } } @@ -497,6 +432,13 @@ public int getCheckeCorner(int index) { return delegate.getCheckboxTag(imageId); } + @Override + public void removeMediaEditId() { + if (currentVideoEditId != 0) { + selectedPhotos.remove(currentVideoEditId); + } + } + @Override public boolean checkboxEnable() { return selectedPhotos.size() <= limitPickPhoto; @@ -508,8 +450,10 @@ public void openPreview() { } @Override - public boolean isSinglePhoto() { - return singlePhoto; + public void setIsOriginal(boolean isOriginal) { + if (pickerBottomLayout != null) { + pickerBottomLayout.setChecked(isOriginal, true); + } } @Override @@ -522,7 +466,7 @@ public Bitmap getThumbForPhoto(FileLocation fileLocation, int index) { } @Override - public void willSwitchFromPhoto(FileLocation fileLocation, int index) { + public void willSwitchFromPhoto(FileLocation fileLocation, int fromIndex) { int count = listView.getChildCount(); for (int a = 0; a < count; a++) { View view = listView.getChildAt(a); @@ -535,18 +479,8 @@ public void willSwitchFromPhoto(FileLocation fileLocation, int index) { if (num < 0 || num >= selectedAlbum.photos.size()) { continue; } - } else { - ArrayList array; - if (searchResult.isEmpty() && lastSearchString == null) { - array = recentImages; - } else { - array = searchResult; - } - if (num < 0 || num >= array.size()) { - continue; - } } - if (num == index) { + if (num == fromIndex) { cell.checkBox.setVisibility(View.VISIBLE); break; } @@ -556,26 +490,31 @@ public void willSwitchFromPhoto(FileLocation fileLocation, int index) { @Override public void willHidePhotoViewer() { if (listAdapter != null) { - listAdapter.notifyDataSetChanged(); + refreshList(); } } + @Override + public List getSelectedPhotos() { + ArrayList list = new ArrayList<>(selectedPhotos.size()); + list.addAll(selectedPhotos.values()); + return list; + } + @Override public boolean isPhotoChecked(int index) { - if (selectedAlbum != null) { - return !(index < 0 || index >= selectedAlbum.photos.size()) - && selectedPhotos.containsKey(selectedAlbum.photos.get(index).imageId); - } - return false; + return selectedAlbum != null && + !(index < 0 || index >= selectedAlbum.photos.size()) && + selectedPhotos.containsKey(selectedAlbum.photos.get(index).imageId); } @Override - public void setPhotoChecked(int index) { + public int setPhotoChecked(int index) { boolean add = true; int imageId = -1; if (selectedAlbum != null) { if (index < 0 || index >= selectedAlbum.photos.size()) { - return; + return Constants.STATE_FAIL; } MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); imageId = photoEntry.imageId; @@ -585,6 +524,17 @@ public void setPhotoChecked(int index) { add = false; delegate.removeCheckboxTag(photoEntry.imageId); } else if (selectedPhotos.size() < limitPickPhoto) { + boolean sizeOutOfRange = photoEntry.mimeType.contains("image") + && Gallery.sOriginChecked + && photoEntry.size >= getConfig().getMaxImageSize(); + boolean timeOutOfRange = photoEntry.mimeType.contains("video") + && photoEntry.duration >= getConfig().getMaxVideoTime(); + if (sizeOutOfRange) { + return Constants.STATE_IMAGE_SIZE_OUT; + } + if (timeOutOfRange) { + return Constants.STATE_VIDEO_TIME_OUT; + } selectedPhotos.put(photoEntry.imageId, photoEntry); int cornerIndex = delegate.generateCheckCorner(); photoEntry.sortindex = cornerIndex; @@ -598,17 +548,19 @@ public void setPhotoChecked(int index) { View view = listView.getChildAt(a); int num = (Integer) view.getTag(); if (num == index) { - ((PhotoPickerPhotoCell) view).setChecked(delegate.getCheckboxTag(imageId), add, - false); + ((PhotoPickerPhotoCell) view).setChecked(delegate.getCheckboxTag(imageId), add, false); break; } } pickerBottomLayout.updateSelectedCount(selectedPhotos.size(), true); - delegate.selectedPhotosChanged(); } + return Constants.STATE_SUCCESS; } - public void setPhotoCheckedByImageId(MediaController.PhotoEntry changedEntry) { + void setPhotoCheckedByImageId(MediaController.PhotoEntry changedEntry) { + if (changedEntry == null) { + return; + } int imageId = changedEntry.imageId; if (selectedAlbum != null) { int size = selectedAlbum.photos.size(); @@ -677,24 +629,6 @@ public int getSelectedCount() { @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { - if (isOpen && searchItem != null) { - AndroidUtilities.showKeyboard(searchItem.getSearchField()); - } - } - - private void updateSearchInterface() { - if (listAdapter != null) { - listAdapter.notifyDataSetChanged(); - } - if (searching && searchResult.isEmpty() || loadingRecent && lastSearchString == null) { - progressView.setVisibility(View.VISIBLE); - listView.setEmptyView(null); - emptyView.setVisibility(View.GONE); - } else { - progressView.setVisibility(View.GONE); - emptyView.setVisibility(View.VISIBLE); - listView.setEmptyView(emptyView); - } } public void setDelegate(PhotoPickerActivityDelegate delegate) { @@ -737,14 +671,10 @@ private void fixLayoutInternal() { int rotation = manager.getDefaultDisplay().getRotation(); int columnsCount; - if (AndroidUtilities.isTablet()) { - columnsCount = 3; + if (rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90) { + columnsCount = 5; } else { - if (rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90) { - columnsCount = 5; - } else { - columnsCount = 3; - } + columnsCount = 4; } listView.setNumColumns(columnsCount); if (AndroidUtilities.isTablet()) { @@ -756,7 +686,7 @@ private void fixLayoutInternal() { } listView.setColumnWidth(itemWidth); - listAdapter.notifyDataSetChanged(); + refreshList(); listView.setSelection(position); if (selectedAlbum == null) { @@ -766,10 +696,16 @@ private void fixLayoutInternal() { } } + private void refreshList() { + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + private class ListAdapter extends BaseFragmentAdapter { private Context mContext; - public ListAdapter(Context context) { + ListAdapter(Context context) { mContext = context; } @@ -780,27 +716,11 @@ public boolean areAllItemsEnabled() { @Override public boolean isEnabled(int i) { - if (selectedAlbum == null) { - if (searchResult.isEmpty() && lastSearchString == null) { - return i < recentImages.size(); - } else { - return i < searchResult.size(); - } - } return true; } @Override public int getCount() { - if (selectedAlbum == null) { - if (searchResult.isEmpty() && lastSearchString == null) { - return recentImages.size(); - } else if (type == 0) { - return searchResult.size() + (nextSearchBingString == null ? 0 : 1); - } else if (type == 1) { - return searchResult.size() + (giphySearchEndReached ? 0 : 1); - } - } return selectedAlbum.photos.size(); } @@ -821,145 +741,74 @@ public boolean hasStableIds() { @Override public View getView(int i, View view, ViewGroup viewGroup) { - int viewType = getItemViewType(i); - if (viewType == 0) { - PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; - if (view == null) { - view = new PhotoPickerPhotoCell(mContext); - cell = (PhotoPickerPhotoCell) view; - cell.checkFrame.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - int index = (Integer) ((View) v.getParent()).getTag(); - if (selectedAlbum != null) { - MediaController.PhotoEntry photoEntry = selectedAlbum.photos - .get(index); - if (selectedPhotos.containsKey(photoEntry.imageId)) { - photoEntry.sortindex = -1; - selectedPhotos.remove(photoEntry.imageId); - photoEntry.imagePath = null; - photoEntry.thumbPath = null; - updatePhotoAtIndex(index); - delegate.removeCheckboxTag(photoEntry.imageId); - } else if (selectedPhotos.size() < limitPickPhoto) { - selectedPhotos.put(photoEntry.imageId, photoEntry); - int cornerIndex = delegate.generateCheckCorner(); - photoEntry.sortindex = cornerIndex; - delegate.putCheckboxTag(photoEntry.imageId, cornerIndex); - } else { - String hintOfPick = sHintOfPick; - String defHint = String.format(Gallery.applicationContext - .getString(R.string.MostSelect), limitPickPhoto); - hintOfPick = TextUtils.isEmpty(hintOfPick) ? defHint - : sHintOfPick; - AndroidUtilities.showToast(hintOfPick); - } - - if (selectedPhotos.size() <= limitPickPhoto) { - ((PhotoPickerPhotoCell) v.getParent()).setChecked( - delegate.getCheckboxTag(photoEntry.imageId), - selectedPhotos.containsKey(photoEntry.imageId), true); - } - } else { - AndroidUtilities - .hideKeyboard(getParentActivity().getCurrentFocus()); - MediaController.SearchImage photoEntry; - if (searchResult.isEmpty() && lastSearchString == null) { - photoEntry = recentImages - .get((Integer) ((View) v.getParent()).getTag()); - } else { - photoEntry = searchResult - .get((Integer) ((View) v.getParent()).getTag()); - } - ((PhotoPickerPhotoCell) v.getParent()).setChecked(false, true); - } - pickerBottomLayout.updateSelectedCount( - selectedPhotos.size(), true); - delegate.selectedPhotosChanged(); - } - }); - cell.checkFrame.setVisibility(singlePhoto ? View.GONE : View.VISIBLE); - } - cell.itemWidth = itemWidth; - BackupImageView imageView = ((PhotoPickerPhotoCell) view).photoImage; - imageView.setTag(i); - view.setTag(i); - boolean showing; - imageView.setOrientation(0, true); + PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; + if (view == null) { + view = new PhotoPickerPhotoCell(mContext); + cell = (PhotoPickerPhotoCell) view; + // 选择图片 + cell.checkFrame.setOnClickListener(checkFrameListener); + cell.setActions(photoPickerPhotoCellActions); + } + cell.itemWidth = itemWidth; + BackupImageView imageView = ((PhotoPickerPhotoCell) view).photoImage; + imageView.setTag(i); + view.setTag(i); + boolean showing = false; + imageView.setOrientation(0, true); + int duration = 0; + boolean isVideo = false; - if (selectedAlbum != null) { - MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(i); - if (photoEntry.thumbPath != null) { - imageView.setImage(photoEntry.thumbPath, null, - mContext.getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.path != null) { - imageView.setOrientation(photoEntry.orientation, true); - if (photoEntry.isVideo) { - imageView.setImage( - "vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, - mContext.getResources().getDrawable(R.drawable.nophotos)); + if (selectedAlbum != null) { + MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(i); + if (photoEntry.thumbPath != null) { + imageView.setImage(photoEntry.thumbPath, null, + mContext.getResources().getDrawable(DARK_THEME ? + R.drawable.album_nophotos : R.drawable.album_nophotos_new)); + } else if (photoEntry.path != null) { + imageView.setOrientation(photoEntry.orientation, true); + if (photoEntry.isVideo) { + cell.showVideoInfo(); + duration = photoEntry.duration; + isVideo = true; + int minutes = photoEntry.duration / 60; + int seconds = photoEntry.duration - minutes * 60; + cell.videoTextView.setText(String.format(Locale.getDefault(), + "%d:%02d", minutes, seconds)); + imageView.setImage("vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, + mContext.getResources().getDrawable(DARK_THEME ? + R.drawable.album_nophotos : R.drawable.album_nophotos_new)); + } else { + if (photoEntry.path.lastIndexOf(".gif") != -1) { + cell.showGifInfo(); } else { - imageView.setImage( - "thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, - mContext.getResources().getDrawable(R.drawable.nophotos)); + cell.infoContainer.setVisibility(View.INVISIBLE); } - } else { - imageView.setImageResource(R.drawable.nophotos); + imageView.setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, + mContext.getResources().getDrawable(DARK_THEME ? + R.drawable.album_nophotos : R.drawable.album_nophotos_new)); } - cell.setChecked(delegate.getCheckboxTag(photoEntry.imageId), - selectedPhotos.containsKey(photoEntry.imageId), false); - showing = PhotoViewer.getInstance().isShowingImage(photoEntry.path); } else { - MediaController.SearchImage photoEntry; - if (searchResult.isEmpty() && lastSearchString == null) { - photoEntry = recentImages.get(i); - } else { - photoEntry = searchResult.get(i); - } - if (photoEntry.thumbPath != null) { - imageView.setImage(photoEntry.thumbPath, null, - mContext.getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.thumbUrl != null && photoEntry.thumbUrl.length() > 0) { - imageView.setImage(photoEntry.thumbUrl, null, - mContext.getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.document != null && photoEntry.document.thumb != null) { - imageView.setImage(photoEntry.document.thumb.location, null, - mContext.getResources().getDrawable(R.drawable.nophotos)); - } else { - imageView.setImageResource(R.drawable.nophotos); - } - cell.setChecked(false, false); - if (photoEntry.document != null) { - showing = PhotoViewer.getInstance().isShowingImage(FileLoader - .getPathToAttach(photoEntry.document, true).getAbsolutePath()); - } else { - showing = PhotoViewer.getInstance().isShowingImage(photoEntry.imageUrl); - } + imageView.setImageResource(DARK_THEME ? + R.drawable.album_nophotos : R.drawable.album_nophotos_new); } - imageView.getImageReceiver().setVisible(!showing, true); - cell.checkBox.setVisibility(singlePhoto || showing ? View.GONE : View.VISIBLE); - } else if (viewType == 1) { - if (view == null) { - LayoutInflater li = (LayoutInflater) mContext - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = li.inflate(R.layout.media_loading_layout, viewGroup, false); - } - ViewGroup.LayoutParams params = view.getLayoutParams(); - params.width = itemWidth; - params.height = itemWidth; - view.setLayoutParams(params); + cell.setChecked(delegate.getCheckboxTag(photoEntry.imageId), + selectedPhotos.containsKey(photoEntry.imageId), false); + showing = PhotoViewer.getInstance().isShowingImage(photoEntry.path); } + imageView.getImageReceiver().setVisible(!showing, true); + boolean goneCheckBox; + goneCheckBox = singlePhoto || showing; + cell.checkBox.setVisibility(goneCheckBox ? View.GONE : View.VISIBLE); + cell.checkFrame.setVisibility(goneCheckBox ? View.GONE : View.VISIBLE); + //clickable + /*cell.clickableView.setVisibility(sListItemClickable ? View.GONE : + cell.checkBox.isChecked() ? View.GONE : View.VISIBLE);*/ return view; } @Override public int getItemViewType(int i) { - if (selectedAlbum != null - || searchResult.isEmpty() && lastSearchString == null && i < recentImages.size() - || i < searchResult.size()) { - return 0; - } - return 1; + return 0; } @Override @@ -969,15 +818,69 @@ public int getViewTypeCount() { @Override public boolean isEmpty() { + return selectedAlbum == null || selectedAlbum.photos.isEmpty(); + } + } + + private View.OnClickListener checkFrameListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + int index = (Integer) ((View) v.getParent()).getTag(); + boolean sizeOutOfRange = false; + boolean timeOutOfRange = false; if (selectedAlbum != null) { - return selectedAlbum.photos.isEmpty(); - } else { - if (searchResult.isEmpty() && lastSearchString == null) { - return recentImages.isEmpty(); + MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(index); + if (selectedPhotos.containsKey(photoEntry.imageId)) {// 选择 -> 未选择 + photoEntry.sortindex = -1; + selectedPhotos.remove(photoEntry.imageId); + photoEntry.imagePath = null; + photoEntry.thumbPath = null; + updatePhotoAtIndex(index); + delegate.removeCheckboxTag(photoEntry.imageId); + } else if (selectedPhotos.size() < limitPickPhoto) {// 未选择 -> 选择 + sizeOutOfRange = photoEntry.mimeType.contains("image") + && Gallery.sOriginChecked + && photoEntry.size >= getConfig().getMaxImageSize(); + timeOutOfRange = photoEntry.mimeType.contains("video") + && photoEntry.duration >= getConfig().getMaxVideoTime(); + if (!sizeOutOfRange && !timeOutOfRange) { + selectedPhotos.put(photoEntry.imageId, photoEntry); + int cornerIndex = delegate.generateCheckCorner(); + photoEntry.sortindex = cornerIndex; + delegate.putCheckboxTag(photoEntry.imageId, cornerIndex); + } } else { - return searchResult.isEmpty(); + String hint = getConfig().hasVideo() ? + Gallery.applicationContext.getString(R.string.MostSelectWithVideo) : + Gallery.applicationContext.getString(R.string.MostSelectOfPhoto); + AndroidUtilities.showToast(String.format(hint, limitPickPhoto)); + } + + if (sizeOutOfRange) { + AndroidUtilities.showToast(Gallery.applicationContext.getString(R.string.SizeOutOfRange)); + } else if (timeOutOfRange) { + String format = Gallery.applicationContext.getString(R.string.LimitTimeOfVideo); + int maxVideoTime = getConfig().getMaxVideoTime(); + AndroidUtilities.showToast(String.format(Locale.getDefault(), format, maxVideoTime + "s")); + } else if (selectedPhotos.size() <= limitPickPhoto) { + ((PhotoPickerPhotoCell) v.getParent()).setChecked( + delegate.getCheckboxTag(photoEntry.imageId), + selectedPhotos.containsKey(photoEntry.imageId), true); } + + } else { + ((PhotoPickerPhotoCell) v.getParent()).setChecked(false, true); } + // 更新完成前面的数字,代表多少张图片被选中 + pickerBottomLayout.updateSelectedCount(selectedPhotos.size(), true); } - } + }; + + private PhotoPickerPhotoCell.Actions photoPickerPhotoCellActions = new PhotoPickerPhotoCell.Actions() { + @Override + public void onUnCheckedAnimationEnd() { + /*sListItemClickable = selectedPhotos.size() != getConfig().getLimitPickPhoto();*/ + listAdapter.notifyDataSetChanged(); + } + }; } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoViewer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoViewer.java index 61ba4ed..b573861 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoViewer.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/PhotoViewer.java @@ -1,53 +1,24 @@ package com.tangxiaolv.telegramgallery; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import com.tangxiaolv.telegramgallery.Actionbar.ActionBar; -import com.tangxiaolv.telegramgallery.Actionbar.ActionBarMenu; -import com.tangxiaolv.telegramgallery.Actionbar.BaseFragment; -import com.tangxiaolv.telegramgallery.Components.AspectRatioFrameLayout; -import com.tangxiaolv.telegramgallery.Components.CheckBox; -import com.tangxiaolv.telegramgallery.Components.ClippingImageView; -import com.tangxiaolv.telegramgallery.Components.PhotoCropView; -import com.tangxiaolv.telegramgallery.Components.PickerBottomLayout; -import com.tangxiaolv.telegramgallery.Components.SizeNotifierFrameLayoutPhoto; -import com.tangxiaolv.telegramgallery.TL.Document; -import com.tangxiaolv.telegramgallery.TL.FileLocation; -import com.tangxiaolv.telegramgallery.TL.Photo; -import com.tangxiaolv.telegramgallery.TL.PhotoSize; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.FileLoader; -import com.tangxiaolv.telegramgallery.Utils.ImageLoader; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; -import com.tangxiaolv.telegramgallery.Utils.MediaController; -import com.tangxiaolv.telegramgallery.Utils.NotificationCenter; -import com.tangxiaolv.telegramgallery.Utils.Utilities; - import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.graphics.RectF; +import android.graphics.SurfaceTexture; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; -import android.text.TextUtils; import android.view.ContextThemeWrapper; import android.view.GestureDetector; import android.view.Gravity; @@ -64,9 +35,42 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Scroller; +import android.widget.TextView; + +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.ui.AspectRatioFrameLayout; +import com.tangxiaolv.telegramgallery.actionbar.ActionBar; +import com.tangxiaolv.telegramgallery.actionbar.ActionBarMenu; +import com.tangxiaolv.telegramgallery.actionbar.BaseFragment; +import com.tangxiaolv.telegramgallery.components.CheckBox; +import com.tangxiaolv.telegramgallery.components.ClippingImageView; +import com.tangxiaolv.telegramgallery.components.PickerBottomLayout; +import com.tangxiaolv.telegramgallery.components.PreviewIconCellContainer; +import com.tangxiaolv.telegramgallery.components.SizeNotifierFrameLayoutPhoto; +import com.tangxiaolv.telegramgallery.components.VideoPlayer; +import com.tangxiaolv.telegramgallery.tl.BotInlineResult; +import com.tangxiaolv.telegramgallery.tl.Document; +import com.tangxiaolv.telegramgallery.tl.FileLocation; +import com.tangxiaolv.telegramgallery.tl.Photo; +import com.tangxiaolv.telegramgallery.tl.PhotoSize; +import com.tangxiaolv.telegramgallery.tl.TLObject; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.Constants; +import com.tangxiaolv.telegramgallery.utils.FileLoader; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.LocaleController; +import com.tangxiaolv.telegramgallery.utils.MediaController; +import com.tangxiaolv.telegramgallery.utils.NotificationCenter; -import static com.tangxiaolv.telegramgallery.PhotoAlbumPickerActivity.limitPickPhoto; -import static com.tangxiaolv.telegramgallery.PhotoAlbumPickerActivity.sHintOfPick; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static android.R.attr.duration; +import static com.tangxiaolv.telegramgallery.GalleryActivity.getConfig; +import static com.tangxiaolv.telegramgallery.components.CheckBox.sCheckColor; +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; @SuppressWarnings("unchecked") public class PhotoViewer implements NotificationCenter.NotificationCenterDelegate, @@ -77,7 +81,6 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private boolean isVisible; private Activity parentActivity; - private Context activityContext; private ActionBar actionBar; private boolean isActionBarVisible = true; @@ -90,22 +93,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private BackgroundDrawable backgroundDrawable = new BackgroundDrawable(0xff000000); private CheckBox checkImageView; private PickerBottomLayout pickerView; - private PickerBottomLayout editorDoneLayout; - private RadialProgressView radialProgressViews[] = new RadialProgressView[3]; - // private ActionBarMenuItem cropItem; - private View indexItem; private AnimatorSet currentActionBarAnimation; - private PhotoCropView photoCropView; private AlertDialog visibleDialog; private boolean canShowBottom = true; - private boolean isSelectPreview; - private int sendPhotoType = 0; + private boolean inPreviewMode; private AnimatedFileDrawable currentAnimation; private AspectRatioFrameLayout aspectRatioFrameLayout; private TextureView videoTextureView; - private ImageView videoPlayButton; - private boolean playerNeedsPrepare; private boolean textureUploaded; private boolean videoCrossfadeStarted; private float videoCrossfadeAlpha; @@ -135,15 +130,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private int avatarsDialogId; private long currentDialogId; private long mergeDialogId; - private int totalImagesCount; - private int totalImagesCountMerge; private boolean isFirstLoading; private boolean needSearchImageInArr; private boolean loadingMoreImages; - private boolean endReached[] = new boolean[]{ - false, true - }; - private boolean opennedFromMedia; private boolean draggingDown = false; private float dragY; @@ -154,10 +143,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private float animateToY; private float animateToScale; private float animationValue; - private int currentRotation; private long animationStartTime; private AnimatorSet imageMoveAnimation; - private AnimatorSet changeModeAnimation; private GestureDetector gestureDetector; private DecelerateInterpolator interpolator = new DecelerateInterpolator(1.5f); private float pinchStartDistance; @@ -186,18 +173,25 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private Scroller scroller = null; private ArrayList imagesArrLocations = new ArrayList<>(); - private ArrayList avatarsArr = new ArrayList<>(); private ArrayList imagesArrLocationsSizes = new ArrayList<>(); private ArrayList imagesArrLocals = new ArrayList<>(); - private FileLocation currentUserAvatarLocation = null; - private final static int gallery_menu_crop = 4; private final static int gallery_menu_index = 1; - private final static int PAGE_SPACING = AndroidUtilities.dp(30); + private static volatile PhotoViewer Instance = null; - private static DecelerateInterpolator decelerateInterpolator = null; - private static Paint progressPaint = null; + private boolean fullScreen = false; + private boolean isPlaying; + private boolean isCurrentVideo; + private boolean gonePreviewCheckBox; + + private PreviewIconCellContainer previewIconCellContainer; + private AnimatorSet animatorSet; + private VideoPlayer videoPlayer; + private ImageView videoPlayImage; + private FrameLayout playButtonButton; + private MediaController.PhotoEntry currentVideoEntry; + private FrameLayout mVideoHintContainer; private class BackgroundDrawable extends ColorDrawable { @@ -224,119 +218,6 @@ public void draw(Canvas canvas) { } } - private class RadialProgressView { - - private long lastUpdateTime = 0; - private float radOffset = 0; - private float currentProgress = 0; - private float animationProgressStart = 0; - private long currentProgressTime = 0; - private float animatedProgressValue = 0; - private RectF progressRect = new RectF(); - private int backgroundState = -1; - private View parent = null; - private int size = AndroidUtilities.dp(64); - private int previousBackgroundState = -2; - private float animatedAlphaValue = 1.0f; - private float alpha = 1.0f; - private float scale = 1.0f; - - public RadialProgressView(Context context, View parentView) { - if (decelerateInterpolator == null) { - decelerateInterpolator = new DecelerateInterpolator(1.5f); - progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - progressPaint.setStyle(Paint.Style.STROKE); - progressPaint.setStrokeCap(Paint.Cap.ROUND); - progressPaint.setStrokeWidth(AndroidUtilities.dp(3)); - progressPaint.setColor(0xffffffff); - } - parent = parentView; - } - - private void updateAnimation() { - long newTime = System.currentTimeMillis(); - long dt = newTime - lastUpdateTime; - lastUpdateTime = newTime; - - if (animatedProgressValue != 1) { - radOffset += 360 * dt / 3000.0f; - float progressDiff = currentProgress - animationProgressStart; - if (progressDiff > 0) { - currentProgressTime += dt; - if (currentProgressTime >= 300) { - animatedProgressValue = currentProgress; - animationProgressStart = currentProgress; - currentProgressTime = 0; - } else { - animatedProgressValue = animationProgressStart - + progressDiff * decelerateInterpolator - .getInterpolation(currentProgressTime / 300.0f); - } - } - parent.invalidate(); - } - if (animatedProgressValue >= 1 && previousBackgroundState != -2) { - animatedAlphaValue -= dt / 200.0f; - if (animatedAlphaValue <= 0) { - animatedAlphaValue = 0.0f; - previousBackgroundState = -2; - } - parent.invalidate(); - } - } - - public void setProgress(float value, boolean animated) { - if (!animated) { - animatedProgressValue = value; - animationProgressStart = value; - } else { - animationProgressStart = animatedProgressValue; - } - currentProgress = value; - currentProgressTime = 0; - } - - public void setBackgroundState(int state, boolean animated) { - lastUpdateTime = System.currentTimeMillis(); - if (animated && backgroundState != state) { - previousBackgroundState = backgroundState; - animatedAlphaValue = 1.0f; - } else { - previousBackgroundState = -2; - } - backgroundState = state; - parent.invalidate(); - } - - public void setAlpha(float value) { - alpha = value; - } - - public void setScale(float value) { - scale = value; - } - - public void onDraw(Canvas canvas) { - int sizeScaled = (int) (size * scale); - int x = (getContainerViewWidth() - sizeScaled) / 2; - int y = (getContainerViewHeight() - sizeScaled) / 2; - - if (backgroundState == 0 || backgroundState == 1 || previousBackgroundState == 0 - || previousBackgroundState == 1) { - int diff = AndroidUtilities.dp(4); - if (previousBackgroundState != -2) { - progressPaint.setAlpha((int) (255 * animatedAlphaValue * alpha)); - } else { - progressPaint.setAlpha((int) (255 * alpha)); - } - progressRect.set(x + diff, y + diff, x + sizeScaled - diff, y + sizeScaled - diff); - canvas.drawArc(progressRect, -90 + radOffset, - Math.max(4, 360 * animatedProgressValue), false, progressPaint); - updateAnimation(); - } - } - } - public static class PlaceProviderObject { public ImageReceiver imageReceiver; public int viewX; @@ -379,8 +260,8 @@ public boolean isPhotoChecked(int index) { } @Override - public void setPhotoChecked(int index) { - + public int setPhotoChecked(int index) { + return Constants.STATE_SUCCESS; } @Override @@ -408,6 +289,11 @@ public int getCheckeCorner(int imageId) { return -1; } + @Override + public void removeMediaEditId() { + + } + @Override public boolean checkboxEnable() { return true; @@ -419,8 +305,13 @@ public void openPreview() { } @Override - public boolean isSinglePhoto() { - return false; + public void setIsOriginal(boolean isOriginal) { + + } + + @Override + public List getSelectedPhotos() { + return null; } } @@ -430,10 +321,6 @@ public boolean isSinglePhoto() { public static class PreviewEmptyPhotoViewerProvider extends EmptyPhotoViewerProvider { public void selectChanged(int index, boolean checked) { } - - public void previewExit() { - - } } public interface PhotoViewerProvider { @@ -447,7 +334,7 @@ public interface PhotoViewerProvider { boolean isPhotoChecked(int index); - void setPhotoChecked(int index); + int setPhotoChecked(int index);//0 设置成功 1 原图过大 2 视频时间过长 boolean cancelButtonPressed(); @@ -460,11 +347,15 @@ public interface PhotoViewerProvider { // 以下为新增 int getCheckeCorner(int currentIndex); + void removeMediaEditId(); + boolean checkboxEnable(); void openPreview(); - boolean isSinglePhoto(); + void setIsOriginal(boolean isOriginal); + + List getSelectedPhotos(); } private class FrameLayoutTouchListener extends FrameLayout { @@ -506,6 +397,7 @@ protected void onDetachedFromWindow() { } private class FrameLayoutDrawer extends SizeNotifierFrameLayoutPhoto { + public FrameLayoutDrawer(Context context) { super(context); setWillNotDraw(false); @@ -515,9 +407,6 @@ public FrameLayoutDrawer(Context context) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); - if (heightSize > AndroidUtilities.displaySize.y - AndroidUtilities.statusBarHeight) { - heightSize = AndroidUtilities.displaySize.y - AndroidUtilities.statusBarHeight; - } setMeasuredDimension(widthSize, heightSize); @@ -534,7 +423,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); - int paddingBottom = 0; for (int i = 0; i < count; i++) { @@ -563,7 +451,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { childLeft = (r - l - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: - childLeft = r - width - lp.rightMargin; + childLeft = (r - l - width) - lp.rightMargin; break; case Gravity.LEFT: default: @@ -575,8 +463,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { childTop = lp.topMargin; break; case Gravity.CENTER_VERTICAL: - childTop = ((b - paddingBottom) - t - height) / 2 + lp.topMargin - - lp.bottomMargin; + childTop = ((b - paddingBottom) - t - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = ((b - paddingBottom) - t) - height - lp.bottomMargin; @@ -593,17 +480,19 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { @Override protected void onDraw(Canvas canvas) { - getInstance().onDraw(canvas); + PhotoViewer.this.onDraw(canvas); } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - return child != aspectRatioFrameLayout && super.drawChild(canvas, child, drawingTime); + try { + return child != aspectRatioFrameLayout && super.drawChild(canvas, child, drawingTime); + } catch (Throwable ignore) { + return true; + } } } - private static volatile PhotoViewer Instance = null; - public static PhotoViewer getInstance() { PhotoViewer localInstance = Instance; if (localInstance == null) { @@ -620,41 +509,10 @@ public static PhotoViewer getInstance() { @SuppressWarnings("unchecked") @Override public void didReceivedNotification(int id, Object... args) { - if (id == NotificationCenter.FileDidFailedLoad) { - String location = (String) args[0]; - for (int a = 0; a < 3; a++) { - if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { - radialProgressViews[a].setProgress(1.0f, true); - checkProgress(a, true); - break; - } - } - } else if (id == NotificationCenter.FileDidLoaded) { - String location = (String) args[0]; - for (int a = 0; a < 3; a++) { - if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { - radialProgressViews[a].setProgress(1.0f, true); - checkProgress(a, true); - if (Build.VERSION.SDK_INT >= 16 && a == 0) { - onActionClick(false); - } - break; - } - } - } else if (id == NotificationCenter.FileLoadProgressChanged) { - String location = (String) args[0]; - for (int a = 0; a < 3; a++) { - if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { - Float progress = (Float) args[1]; - radialProgressViews[a].setProgress(progress, true); - } - } - } else if (id == NotificationCenter.dialogPhotosLoaded) { + if (id == NotificationCenter.dialogPhotosLoaded) { int guid = (Integer) args[4]; int did = (Integer) args[0]; if (avatarsDialogId == did && classGuid == guid) { - boolean fromCache = (Boolean) args[3]; - int setToImage = -1; ArrayList photos = (ArrayList) args[5]; if (photos.isEmpty()) { @@ -662,7 +520,6 @@ public void didReceivedNotification(int id, Object... args) { } imagesArrLocations.clear(); imagesArrLocationsSizes.clear(); - avatarsArr.clear(); for (int a = 0; a < photos.size(); a++) { Photo photo = photos.get(a); if (photo == null || photo instanceof Photo.TL_photoEmpty @@ -683,7 +540,6 @@ public void didReceivedNotification(int id, Object... args) { } imagesArrLocations.add(sizeFull.location); imagesArrLocationsSizes.add(sizeFull.size); - avatarsArr.add(photo); } } needSearchImageInArr = false; @@ -691,30 +547,14 @@ public void didReceivedNotification(int id, Object... args) { if (setToImage != -1) { setImageIndex(setToImage, true); } else { - avatarsArr.add(0, new Photo.TL_photoEmpty()); imagesArrLocations.add(0, currentFileLocation); imagesArrLocationsSizes.add(0, 0); setImageIndex(0, true); } - if (fromCache) { - } } } else if (id == NotificationCenter.mediaCountDidLoaded) { long uid = (Long) args[0]; if (uid == currentDialogId || uid == mergeDialogId) { - if (uid == currentDialogId) { - totalImagesCount = (Integer) args[1]; - /* - * if ((Boolean) args[2]) { SharedMediaQuery.getMediaCount(currentDialogId, - * SharedMediaQuery.MEDIA_PHOTOVIDEO, classGuid, false); } - */ - } else if (uid == mergeDialogId) { - totalImagesCountMerge = (Integer) args[1]; - /* - * if ((Boolean) args[2]) { SharedMediaQuery.getMediaCount(mergeDialogId, - * SharedMediaQuery.MEDIA_PHOTOVIDEO, classGuid, false); } - */ - } if (needSearchImageInArr && isFirstLoading) { isFirstLoading = false; loadingMoreImages = true; @@ -728,7 +568,7 @@ public void setParentActivity(final Activity activity) { return; } parentActivity = activity; - activityContext = new ContextThemeWrapper(parentActivity, R.style.Theme_TMessages); + Context actvityContext = new ContextThemeWrapper(parentActivity, R.style.Theme_TMessages); scroller = new Scroller(activity); @@ -768,44 +608,72 @@ public boolean dispatchKeyEventPreIme(KeyEvent event) { windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; actionBar = new ActionBar(activity); - actionBar.setBackgroundColor(Theme.ACTION_BAR_PHOTO_VIEWER_COLOR); + actionBar.setBackgroundColor(DARK_THEME ? Theme.ACTION_BAR_PHOTO_VIEWER_COLOR : 0xfff9f9f9); actionBar.setOccupyStatusBar(false); actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR); - actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setTitle(activityContext.getString(R.string.Of, 1, 1)); + + actionBar.setBackButtonImage(DARK_THEME ? R.drawable.album_ab_back : R.drawable.album_ab_back_bule); + + actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, 1, 1)); containerView.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + /*视频时间超限提示*/ + mVideoHintContainer = new FrameLayout(activity); + mVideoHintContainer.setBackgroundColor(0xFFFFF7F0); + ImageView hintImg = new ImageView(activity); + hintImg.setBackgroundResource(R.drawable.ic_video_hint); + mVideoHintContainer.addView(hintImg, LayoutHelper.createFrame(20, 20, Gravity.CENTER_VERTICAL, 8, 0, 0, 0)); + TextView hintText = new TextView(activity); + hintText.setTextSize(14); + hintText.setTextColor(0xFF333333); + String format = Gallery.applicationContext.getString(R.string.LimitTimeOfVideo); + int maxVideoTime = getConfig().getMaxVideoTime(); + AndroidUtilities.showToast(String.format(Locale.getDefault(), format, maxVideoTime + "s")); + mVideoHintContainer.addView(hintText, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, + LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 32, 0, 0, 0)); + View topLine = new View(activity); + topLine.setBackgroundColor(0xFFDDDEE3); + mVideoHintContainer.addView(topLine, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 1)); + containerView.addView(mVideoHintContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.START, 0, 48, 0, 0)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { if (id == -1) { closePhoto(true, false); - } else if (id == gallery_menu_crop) { - switchToEditMode(1); } else if (id == gallery_menu_index && placeProvider != null) { - if (placeProvider instanceof PreviewEmptyPhotoViewerProvider) { + if (placeProvider instanceof PreviewEmptyPhotoViewerProvider) {//选择预览 PreviewEmptyPhotoViewerProvider previewProvider = (PreviewEmptyPhotoViewerProvider) placeProvider; previewProvider.selectChanged(currentIndex, !checkImageView.isChecked()); - checkImageView.setChecked(currentIndex + 1, !checkImageView.isChecked(), - true); + checkImageView.setChecked(currentIndex + 1, !checkImageView.isChecked(), true); pickerView.updateSelectedCount(previewProvider.getSelectedCount(), true); + changePreviewIconCancelStatus(!checkImageView.isChecked()); return; } - placeProvider.setPhotoChecked(currentIndex); + int state = placeProvider.setPhotoChecked(currentIndex);//全部预览 if (placeProvider.checkboxEnable()) { int checkeCorner = placeProvider.getCheckeCorner(currentIndex); - if (-1 == checkeCorner && !checkImageView.isChecked()) { - String hintOfPick = sHintOfPick; - String defHint = String.format(Gallery.applicationContext - .getString(R.string.MostSelect), limitPickPhoto); - hintOfPick = TextUtils.isEmpty(hintOfPick) ? defHint - : sHintOfPick; - AndroidUtilities.showToast(hintOfPick); + if (Constants.STATE_SUCCESS == state + && -1 == checkeCorner + && !checkImageView.isChecked()) { + String hint = getConfig().hasVideo() ? + Gallery.applicationContext.getString(R.string.MostSelectWithVideo) : + Gallery.applicationContext.getString(R.string.MostSelectOfPhoto); + AndroidUtilities.showToast(String.format(hint, getConfig().getLimitPickPhoto())); + } else if (Constants.STATE_IMAGE_SIZE_OUT == state) { + AndroidUtilities.showToast(Gallery.applicationContext.getString(R.string.SizeOutOfRange)); + return; + } else if (Constants.STATE_VIDEO_TIME_OUT == state) { + String format = Gallery.applicationContext.getString(R.string.LimitTimeOfVideo); + int maxVideoTime = getConfig().getMaxVideoTime(); + AndroidUtilities.showToast(String.format(Locale.getDefault(), format, maxVideoTime + "s")); + return; + } else { + changePreviewIconCancelStatus(checkImageView.isChecked()); } - checkImageView.setChecked(checkeCorner, - placeProvider.isPhotoChecked(currentIndex), true); + checkImageView.setChecked(checkeCorner, placeProvider.isPhotoChecked(currentIndex), true); updateSelectedCount(); } } @@ -823,53 +691,77 @@ public boolean canOpenMenu() { } }); - ActionBarMenu menu = actionBar.createMenu(); + playButtonButton = new FrameLayout(parentActivity); + playButtonButton.setBackgroundResource(R.drawable.circle_big); + videoPlayImage = new ImageView(parentActivity); + videoPlayImage.setBackgroundResource(R.drawable.ic_video_play); + playButtonButton.addView(videoPlayImage, LayoutHelper.createFrame(24, 24, Gravity.CENTER)); + containerView.addView(playButtonButton, LayoutHelper.createFrame(64, 64, Gravity.CENTER)); + playButtonButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //播放视频 + if (videoPlayer == null) { + preparePlayer(new File(currentVideoEntry.path), false); + } + if (isPlaying) { + videoPlayer.pause(); + } else { + if (videoPlayer.getCurrentPosition() == videoPlayer.getDuration()) { + videoPlayer.seekTo(0); + } + videoPlayer.play(); + } + playButtonButton.setAlpha(isPlaying ? 0 : 1.0f); - // cropItem = menu.addItemWithWidth(gallery_menu_crop, R.drawable.photo_crop, - // AndroidUtilities.dp(56)); + if (!fullScreen){ + long down = System.currentTimeMillis(); + MotionEvent downEvent = MotionEvent.obtain(down, down, MotionEvent.ACTION_DOWN, 1, 1, 0); + windowView.onTouchEvent(downEvent); + MotionEvent upEvent = MotionEvent.obtain(down, down + 10, MotionEvent.ACTION_UP, 1, 1, 0); + windowView.onTouchEvent(upEvent); + } + } + }); - checkImageView = new CheckBox(containerView.getContext(), R.drawable.selectphoto_large); + checkImageView = new CheckBox(containerView.getContext(), R.drawable.album_selectphoto_large); + checkImageView.setActionBarStyle(true); checkImageView.setDrawBackground(true); - checkImageView.setSize(32); + checkImageView.setSize(24); checkImageView.setCheckOffset(AndroidUtilities.dp(1)); - checkImageView.setColor(0xff007aff); - LinearLayout.LayoutParams params = LayoutHelper.createLinear(32, 32); + checkImageView.setColor(sCheckColor); + LinearLayout.LayoutParams params = LayoutHelper.createLinear(24, 24); params.gravity = Gravity.CENTER_VERTICAL; params.setMargins(0, 0, AndroidUtilities.dp(8), 0); checkImageView.setLayoutParams(params); - indexItem = menu.addItem(gallery_menu_index, checkImageView); + ActionBarMenu menu = actionBar.createMenu(); + menu.addItem(gallery_menu_index, checkImageView); - bottomLayout = new FrameLayout(activityContext); + bottomLayout = new FrameLayout(actvityContext); bottomLayout.setBackgroundColor(0x7f000000); containerView.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); - radialProgressViews[0] = new RadialProgressView(containerView.getContext(), containerView); - radialProgressViews[0].setBackgroundState(0, false); - radialProgressViews[1] = new RadialProgressView(containerView.getContext(), containerView); - radialProgressViews[1].setBackgroundState(0, false); - radialProgressViews[2] = new RadialProgressView(containerView.getContext(), containerView); - radialProgressViews[2].setBackgroundState(0, false); - - pickerView = new PickerBottomLayout(activityContext); - pickerView.setBackgroundColor(0x7f000000); + pickerView = new PickerBottomLayout(actvityContext); + pickerView.setBackgroundColor(DARK_THEME ? 0x7f000000 : 0xfff9f9f9); containerView.addView(pickerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); - pickerView.cancelButton.setVisibility(View.GONE); - // pickerView.cancelButton.setOnClickListener(new View.OnClickListener() { - // @Override - // public void onClick(View view) { - // if (placeProvider != null) { - // placeProvider.openPreview(); - // } - // } - // }); + pickerView.cancelButton.setText(R.string.edit); + pickerView.cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + } + }); pickerView.doneButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (placeProvider != null) { - if (placeProvider.getSelectedCount() != 0 || placeProvider.isSinglePhoto()) { + MediaController.PhotoEntry p = (MediaController.PhotoEntry) imagesArrLocals.get(currentIndex); + if (p.isVideo && p.duration > getConfig().getMaxVideoTime()) { + AndroidUtilities.showToast(String.format(Locale.getDefault(), + parentActivity.getString(R.string.VideoTimeOutOfRange), getConfig().getMaxVideoTime())); + } else if (placeProvider.getSelectedCount() != 0 || getConfig().isSinglePhoto()) { placeProvider.sendButtonPressed(currentIndex); closePhoto(false, false); } @@ -877,48 +769,26 @@ public void onClick(View view) { } }); - editorDoneLayout = new PickerBottomLayout(activityContext); - editorDoneLayout.setBackgroundColor(0x7f000000); - editorDoneLayout.updateSelectedCount(0, false); - editorDoneLayout.setVisibility(View.GONE); - containerView.addView(editorDoneLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, - 48, Gravity.LEFT | Gravity.BOTTOM)); - - editorDoneLayout.cancelButton.setOnClickListener(new View.OnClickListener() { + previewIconCellContainer = new PreviewIconCellContainer(actvityContext); + previewIconCellContainer.setBackgroundColor(0xffffffff); + containerView.addView(previewIconCellContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 88, + Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 48)); + previewIconCellContainer.setPreviewIconCellContainerActions(new PreviewIconCellContainer.PreviewIconCellContainerActions() { @Override - public void onClick(View view) { - if (currentEditMode == 1) { - photoCropView.cancelAnimationRunnable(); - } - switchToEditMode(0); - } - }); - editorDoneLayout.doneButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (currentEditMode == 1) { - photoCropView.cancelAnimationRunnable(); - if (imageMoveAnimation != null) { - return; + public void checkChanged(int imageId) { + int size = imagesArrLocals.size(); + if (size > 0) { + releasePlayer(); + for (int i = 0; i < size; i++) { + MediaController.PhotoEntry p = (MediaController.PhotoEntry) imagesArrLocals.get(i); + if (p.imageId == imageId && i != currentIndex) { + if (p.mimeType.contains("video")) { + currentVideoEntry = p; + } + setImages(i); + } } } - applyCurrentEditMode(); - switchToEditMode(0); - } - }); - - ImageView rotateButton = new ImageView(activityContext); - rotateButton.setScaleType(ImageView.ScaleType.CENTER); - rotateButton.setImageResource(R.drawable.tool_rotate); - rotateButton.setBackgroundDrawable( - Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); - editorDoneLayout.addView(rotateButton, LayoutHelper.createFrame(48, 48, Gravity.CENTER)); - rotateButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - centerImage.setOrientation(centerImage.getOrientation() - 90, false); - photoCropView.setOrientation(centerImage.getOrientation()); - containerView.invalidate(); } }); @@ -934,314 +804,13 @@ public void onClick(View v) { rightImage.setParentView(containerView); rightImage.setCrossfadeAlpha((byte) 2); rightImage.setInvalidateAll(true); - - // WindowManager manager = (WindowManager) Gallery.applicationContext - // .getSystemService(Activity.WINDOW_SERVICE); - // int rotation = manager.getDefaultDisplay().getRotation(); - // - // checkImageView = new CheckBox(containerView.getContext(), R.drawable.selectphoto_large); - // checkImageView.setDrawBackground(true); - // checkImageView.setSize(32); - // checkImageView.setCheckOffset(AndroidUtilities.dp(1)); - // checkImageView.setColor(0xff007aff); - // checkImageView.setVisibility(View.GONE); - // containerView.addView(checkImageView, - // LayoutHelper.createFrame(32, 32, Gravity.RIGHT | Gravity.TOP, 0, - // rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90 ? 58 - // : 68, - // 10, 0)); - // checkImageView.setOnClickListener(new View.OnClickListener() { - // @Override - // public void onClick(View v) { - // if (placeProvider != null) { - // placeProvider.setPhotoChecked(currentIndex); - // if (placeProvider.checkboxEnable()) { - // int checkeCorner = placeProvider.getCheckeCorner(currentIndex); - // if (-1 == checkeCorner && !checkImageView.isChecked()) { - // Toast.makeText(Gallery.applicationContext, - // String.format( - // Gallery.applicationContext - // .getString(R.string.MostSelect), - // PhotoAlbumPickerActivity.limitPickPhoto), - // Toast.LENGTH_SHORT).show(); - // } - // - // checkImageView.setChecked(checkeCorner, - // placeProvider.isPhotoChecked(currentIndex), true); - // updateSelectedCount(); - // } - // } - // } - // }); - } - - private void showAlertDialog(AlertDialog.Builder builder) { - if (parentActivity == null) { - return; - } - try { - if (visibleDialog != null) { - visibleDialog.dismiss(); - visibleDialog = null; - } - } catch (Exception e) { - e.printStackTrace(); - } - try { - visibleDialog = builder.show(); - visibleDialog.setCanceledOnTouchOutside(true); - visibleDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - visibleDialog = null; - } - }); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void applyCurrentEditMode() { - Bitmap bitmap = null; - if (currentEditMode == 1) { - bitmap = photoCropView.getBitmap(); - } - if (bitmap != null) { - PhotoSize size = ImageLoader.scaleAndSaveImage(bitmap, AndroidUtilities.getPhotoSize(), - AndroidUtilities.getPhotoSize(), 80, false, 101, 101); - if (size != null) { - Object object = imagesArrLocals.get(currentIndex); - if (object instanceof MediaController.PhotoEntry) { - MediaController.PhotoEntry entry = (MediaController.PhotoEntry) object; - entry.imagePath = FileLoader.getPathToAttach(size, true).toString(); - size = ImageLoader.scaleAndSaveImage(bitmap, AndroidUtilities.dp(120), - AndroidUtilities.dp(120), 70, false, 101, 101); - if (size != null) { - entry.thumbPath = FileLoader.getPathToAttach(size, true).toString(); - } - } else if (object instanceof MediaController.SearchImage) { - MediaController.SearchImage entry = (MediaController.SearchImage) object; - entry.imagePath = FileLoader.getPathToAttach(size, true).toString(); - size = ImageLoader.scaleAndSaveImage(bitmap, AndroidUtilities.dp(120), - AndroidUtilities.dp(120), 70, false, 101, 101); - if (size != null) { - entry.thumbPath = FileLoader.getPathToAttach(size, true).toString(); - } - } - if (sendPhotoType == 0 && placeProvider != null) { - placeProvider.updatePhotoAtIndex(currentIndex); - if (!placeProvider.isPhotoChecked(currentIndex)) { - placeProvider.setPhotoChecked(currentIndex); - checkImageView.setChecked(placeProvider.isPhotoChecked(currentIndex), true); - updateSelectedCount(); - } - } - if (currentEditMode == 1) { - float scaleX = photoCropView.getRectSizeX() / (float) getContainerViewWidth(); - float scaleY = photoCropView.getRectSizeY() / (float) getContainerViewHeight(); - scale = scaleX > scaleY ? scaleX : scaleY; - translationX = photoCropView.getRectX() + photoCropView.getRectSizeX() / 2 - - getContainerViewWidth() / 2; - translationY = photoCropView.getRectY() + photoCropView.getRectSizeY() / 2 - - getContainerViewHeight() / 2; - zoomAnimation = true; - } - centerImage.setParentView(null); - centerImage.setOrientation(0, true); - centerImage.setImageBitmap(bitmap); - centerImage.setParentView(containerView); - } - } - } - - private void switchToEditMode(final int mode) { - if (currentEditMode == mode || centerImage.getBitmap() == null - || changeModeAnimation != null || imageMoveAnimation != null - || radialProgressViews[0].backgroundState != -1) { - return; - } - if (mode == 0) { - Bitmap bitmap = centerImage.getBitmap(); - if (bitmap != null) { - int bitmapWidth = centerImage.getBitmapWidth(); - int bitmapHeight = centerImage.getBitmapHeight(); - - float scaleX = (float) getContainerViewWidth() / (float) bitmapWidth; - float scaleY = (float) getContainerViewHeight() / (float) bitmapHeight; - float newScaleX = (float) getContainerViewWidth(0) / (float) bitmapWidth; - float newScaleY = (float) getContainerViewHeight(0) / (float) bitmapHeight; - float scale = scaleX > scaleY ? scaleY : scaleX; - float newScale = newScaleX > newScaleY ? newScaleY : newScaleX; - - animateToScale = newScale / scale; - animateToX = 0; - if (currentEditMode == 1) { - animateToY = AndroidUtilities.dp(24); - } else if (currentEditMode == 2) { - animateToY = AndroidUtilities.dp(62); - } - animationStartTime = System.currentTimeMillis(); - zoomAnimation = true; - } - - imageMoveAnimation = new AnimatorSet(); - if (currentEditMode == 1) { - imageMoveAnimation.playTogether( - ObjectAnimator.ofFloat(editorDoneLayout, "translationY", - AndroidUtilities.dp(48)), - ObjectAnimator.ofFloat(PhotoViewer.this, "animationValue", 0, 1), - ObjectAnimator.ofFloat(photoCropView, "alpha", 0)); - } - imageMoveAnimation.setDuration(200); - imageMoveAnimation.addListener(new AnimatorListenerAdapterProxy() { - @Override - public void onAnimationEnd(Animator animation) { - if (currentEditMode == 1) { - editorDoneLayout.setVisibility(View.GONE); - photoCropView.setVisibility(View.GONE); - } - imageMoveAnimation = null; - currentEditMode = mode; - animateToScale = 1; - animateToX = 0; - animateToY = 0; - scale = 1; - updateMinMax(scale); - containerView.invalidate(); - - AnimatorSet animatorSet = new AnimatorSet(); - ArrayList arrayList = new ArrayList<>(); - arrayList.add(ObjectAnimator.ofFloat(pickerView, "translationY", 0)); - arrayList.add(ObjectAnimator.ofFloat(actionBar, "translationY", 0)); - if (sendPhotoType == 0) { - arrayList.add(ObjectAnimator.ofFloat(checkImageView, "alpha", 1)); - } - animatorSet.playTogether(arrayList); - animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { - @Override - public void onAnimationStart(Animator animation) { - pickerView.setVisibility(View.VISIBLE); - actionBar.setVisibility(View.VISIBLE); - if (sendPhotoType == 0) { - checkImageView.setVisibility(View.VISIBLE); - } - } - }); - animatorSet.start(); - } - }); - imageMoveAnimation.start(); - } else if (mode == 1) { - if (photoCropView == null) { - photoCropView = new PhotoCropView(activityContext); - photoCropView.setVisibility(View.GONE); - containerView.addView(photoCropView, - LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, - LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, - 48)); - photoCropView.setDelegate(new PhotoCropView.PhotoCropViewDelegate() { - @Override - public void needMoveImageTo(float x, float y, float s, boolean animated) { - if (animated) { - animateTo(s, x, y, true); - } else { - translationX = x; - translationY = y; - scale = s; - containerView.invalidate(); - } - } - - @Override - public Bitmap getBitmap() { - return centerImage.getBitmap(); - } - }); - } - - editorDoneLayout.doneButtonTextView - .setText(R.string.Crop); - changeModeAnimation = new AnimatorSet(); - ArrayList arrayList = new ArrayList<>(); - arrayList.add( - ObjectAnimator.ofFloat(pickerView, "translationY", 0, AndroidUtilities.dp(96))); - arrayList.add( - ObjectAnimator.ofFloat(actionBar, "translationY", 0, -actionBar.getHeight())); - if (sendPhotoType == 0) { - arrayList.add(ObjectAnimator.ofFloat(checkImageView, "alpha", 1, 0)); - } - changeModeAnimation.playTogether(arrayList); - changeModeAnimation.setDuration(200); - changeModeAnimation.addListener(new AnimatorListenerAdapterProxy() { - @Override - public void onAnimationEnd(Animator animation) { - changeModeAnimation = null; - pickerView.setVisibility(View.GONE); - actionBar.setVisibility(View.GONE); - if (sendPhotoType == 0) { - checkImageView.setVisibility(View.GONE); - } - - Bitmap bitmap = centerImage.getBitmap(); - if (bitmap != null) { - photoCropView.setBitmap(bitmap, centerImage.getOrientation(), - sendPhotoType != 1); - int bitmapWidth = centerImage.getBitmapWidth(); - int bitmapHeight = centerImage.getBitmapHeight(); - - float scaleX = (float) getContainerViewWidth() / (float) bitmapWidth; - float scaleY = (float) getContainerViewHeight() / (float) bitmapHeight; - float newScaleX = (float) getContainerViewWidth(1) / (float) bitmapWidth; - float newScaleY = (float) getContainerViewHeight(1) / (float) bitmapHeight; - float scale = scaleX > scaleY ? scaleY : scaleX; - float newScale = newScaleX > newScaleY ? newScaleY : newScaleX; - - animateToScale = newScale / scale; - animateToX = 0; - animateToY = -AndroidUtilities.dp(24); - animationStartTime = System.currentTimeMillis(); - zoomAnimation = true; - } - - imageMoveAnimation = new AnimatorSet(); - imageMoveAnimation.playTogether( - ObjectAnimator.ofFloat(editorDoneLayout, "translationY", - AndroidUtilities.dp(48), 0), - ObjectAnimator.ofFloat(PhotoViewer.this, "animationValue", 0, 1), - ObjectAnimator.ofFloat(photoCropView, "alpha", 0, 1)); - imageMoveAnimation.setDuration(200); - imageMoveAnimation.addListener(new AnimatorListenerAdapterProxy() { - @Override - public void onAnimationStart(Animator animation) { - editorDoneLayout.setVisibility(View.VISIBLE); - photoCropView.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animator animation) { - imageMoveAnimation = null; - currentEditMode = mode; - animateToScale = 1; - animateToX = 0; - animateToY = 0; - scale = 1; - updateMinMax(scale); - containerView.invalidate(); - } - }); - imageMoveAnimation.start(); - } - }); - changeModeAnimation.start(); - } } private void toggleCheckImageView(boolean show) { AnimatorSet animatorSet = new AnimatorSet(); ArrayList arrayList = new ArrayList<>(); arrayList.add(ObjectAnimator.ofFloat(pickerView, "alpha", show ? 1.0f : 0.0f)); - if (sendPhotoType == 0) { + if (!getConfig().isSinglePhoto()) { arrayList.add(ObjectAnimator.ofFloat(checkImageView, "alpha", show ? 1.0f : 0.0f)); } animatorSet.playTogether(arrayList); @@ -1259,11 +828,14 @@ private void toggleActionBar(boolean show, final boolean animated) { isActionBarVisible = show; actionBar.setEnabled(show); bottomLayout.setEnabled(show); + previewIconCellContainer.setEnabled(show); if (animated) { ArrayList arrayList = new ArrayList<>(); arrayList.add(ObjectAnimator.ofFloat(actionBar, "alpha", show ? 1.0f : 0.0f)); arrayList.add(ObjectAnimator.ofFloat(bottomLayout, "alpha", show ? 1.0f : 0.0f)); + arrayList.add(ObjectAnimator.ofFloat(previewIconCellContainer, "alpha", show ? 1.0f : 0.0f)); + arrayList.add(ObjectAnimator.ofFloat(mVideoHintContainer, "alpha", show ? 1.0f : 0.0f)); currentActionBarAnimation = new AnimatorSet(); currentActionBarAnimation.playTogether(arrayList); if (!show) { @@ -1287,6 +859,8 @@ public void onAnimationEnd(Animator animation) { } else { actionBar.setAlpha(show ? 1.0f : 0.0f); bottomLayout.setAlpha(show ? 1.0f : 0.0f); + previewIconCellContainer.setAlpha(show ? 1.0f : 0.0f); + mVideoHintContainer.setAlpha(show ? 1.0f : 0.0f); if (!show) { actionBar.setVisibility(View.GONE); if (canShowBottom) { @@ -1312,23 +886,6 @@ private String getFileName(int index) { if (index >= imagesArrLocals.size()) { return null; } - Object object = imagesArrLocals.get(index); - if (object instanceof MediaController.SearchImage) { - MediaController.SearchImage searchImage = ((MediaController.SearchImage) object); - if (searchImage.document != null) { - return FileLoader.getAttachFileName(searchImage.document); - } else if (searchImage.type != 1 && searchImage.localUrl != null - && searchImage.localUrl.length() > 0) { - File file = new File(searchImage.localUrl); - if (file.exists()) { - return file.getName(); - } else { - searchImage.localUrl = ""; - } - } - return Utilities.MD5(searchImage.imageUrl) + "." - + ImageLoader.getHttpUrlExtension(searchImage.imageUrl, "jpg"); - } } return null; } @@ -1352,9 +909,14 @@ private void updateSelectedCount() { return; } pickerView.updateSelectedCount(placeProvider.getSelectedCount(), false); + placeProvider.setIsOriginal(pickerView.isOriginChecked()); + if (gonePreviewCheckBox) { + pickerView.setDoneButtonText(parentActivity.getString(R.string.Send)); + } } - private void onPhotoShow(final FileLocation fileLocation, final List photos, + private void onPhotoShow(final FileLocation fileLocation, + final List photos, int index, final PlaceProviderObject object) { classGuid = BaseFragment.lastClassGuid++; @@ -1365,21 +927,15 @@ private void onPhotoShow(final FileLocation fileLocation, final List pho currentFileNames[1] = null; currentFileNames[2] = null; avatarsDialogId = 0; - totalImagesCount = 0; - totalImagesCountMerge = 0; currentEditMode = 0; isFirstLoading = true; + isCurrentVideo = false; needSearchImageInArr = false; loadingMoreImages = false; - endReached[0] = false; - endReached[1] = mergeDialogId == 0; - opennedFromMedia = false; canShowBottom = true; imagesArrLocations.clear(); imagesArrLocationsSizes.clear(); - avatarsArr.clear(); imagesArrLocals.clear(); - currentUserAvatarLocation = null; containerView.setPadding(0, 0, 0, 0); currentThumb = object != null ? object.thumb : null; bottomLayout.setVisibility(View.VISIBLE); @@ -1387,47 +943,101 @@ private void onPhotoShow(final FileLocation fileLocation, final List pho pickerView.setTranslationY(0); checkImageView.setAlpha(1.0f); pickerView.setAlpha(1.0f); - checkImageView.setVisibility(isSelectPreview ? View.VISIBLE : View.GONE); - pickerView.setVisibility(isSelectPreview ? View.VISIBLE : View.GONE); - // cropItem.setVisibility(View.GONE); - editorDoneLayout.setVisibility(View.GONE); - if (photoCropView != null) { - photoCropView.setVisibility(View.GONE); - } - - for (int a = 0; a < 3; a++) { - if (radialProgressViews[a] != null) { - radialProgressViews[a].setBackgroundState(-1, false); - } - } + playButtonButton.setAlpha(1.0f); + pickerView.setVisibility(inPreviewMode ? View.VISIBLE : View.GONE); if (fileLocation != null) { avatarsDialogId = object.dialogId; imagesArrLocations.add(fileLocation); imagesArrLocationsSizes.add(object.size); - avatarsArr.add(new Photo.TL_photoEmpty()); setImageIndex(0, true); - currentUserAvatarLocation = fileLocation; } else if (photos != null) { - if (sendPhotoType == 0) { - checkImageView.setVisibility(View.VISIBLE); - } + MediaController.PhotoEntry e = (MediaController.PhotoEntry) photos.get(index); + gonePreviewCheckBox = false; + if (getConfig().isSinglePhoto()) { + gonePreviewCheckBox = true; + } else if (e.isVideo) { + gonePreviewCheckBox = getConfig().isVideoEditMode() && getConfig().getMaxVideoTime() > duration; + } + checkImageView.setVisibility(gonePreviewCheckBox ? View.GONE : View.VISIBLE); imagesArrLocals.addAll(photos); setImageIndex(index, true); pickerView.setVisibility(View.VISIBLE); bottomLayout.setVisibility(View.GONE); canShowBottom = false; - Object obj = imagesArrLocals.get(index); - // cropItem.setVisibility(placeProvider.isSinglePhoto() ? View.VISIBLE : View.GONE); updateSelectedCount(); } } - private void setImages() { + //设置当前页,左页,右页视图,并刷新 + private void setImages(int index) { if (animationInProgress == 0) { + currentIndex = index; setIndexToImage(centerImage, currentIndex); setIndexToImage(rightImage, currentIndex + 1); setIndexToImage(leftImage, currentIndex - 1); + changePreviewIconCheckStatus(index); + changeCheckImageViewStatus(); + changeOriginPicBtn((MediaController.PhotoEntry) imagesArrLocals.get(index)); + changeActionBar(); + } + } + + private void changeActionBar() { + actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, + currentIndex + 1, imagesArrLocals.size())); + } + + private void changeOriginPicBtn(MediaController.PhotoEntry currentEntry) { + if (currentEntry == null) { + return; + } + String mimeType = currentEntry.mimeType; + if (mimeType.contains("gif") || mimeType.contains("video")) { + if (pickerView.originalView.getVisibility() != View.GONE) { + pickerView.setOriginalViewVisibility(View.GONE); + } + } else if (pickerView.originalView.getVisibility() != View.VISIBLE) { + pickerView.setOriginalViewVisibility(View.VISIBLE); + } + pickerView.setEditState(mimeType.startsWith("video"), false); + if (playButtonButton.getAlpha() == 0){ + playButtonButton.setAlpha(1.0f); + } + playButtonButton.setVisibility(mimeType.startsWith("video") ? View.VISIBLE : View.GONE); + } + + //改变预览框点击状态 + private void changePreviewIconCheckStatus(int index) { + if (!imagesArrLocals.isEmpty()) { + Object object = imagesArrLocals.get(index == -1 ? 0 : index); + if (object instanceof MediaController.PhotoEntry) { + previewIconCellContainer.requestCheckedChanged(((MediaController.PhotoEntry) object).imageId); + } + } + } + + //改变预览框取消状态 + private void changePreviewIconCancelStatus(boolean canceled) { + if (!imagesArrLocals.isEmpty()) { + Object object = imagesArrLocals.get(currentIndex == -1 ? 0 : currentIndex); + if (object instanceof MediaController.PhotoEntry) { + previewIconCellContainer.requestCanceledChanged((MediaController.PhotoEntry) object, canceled); + } + } + } + + //改变checkbox显示状态 + private void changeCheckImageViewStatus() { + MediaController.PhotoEntry p = (MediaController.PhotoEntry) imagesArrLocals.get(currentIndex); + mVideoHintContainer.setVisibility(p.duration >= getConfig().getMaxVideoTime() ? View.VISIBLE : View.GONE); + if (!inPreviewMode && getConfig().isVideoEditMode() && p.duration > getConfig().getMaxVideoTime()) { + checkImageView.setVisibility(View.GONE); + return; + } + if (!getConfig().isSinglePhoto()) { + checkImageView.setChecked(placeProvider.getCheckeCorner(currentIndex), + placeProvider.isPhotoChecked(currentIndex), false); } } @@ -1438,6 +1048,7 @@ private void setImageIndex(int index, boolean init) { if (!init) { currentThumb = null; } + currentFileNames[0] = getFileName(index); currentFileNames[1] = getFileName(index + 1); currentFileNames[2] = getFileName(index - 1); @@ -1447,6 +1058,7 @@ private void setImageIndex(int index, boolean init) { boolean isVideo = false; boolean sameImage = false; + MediaController.PhotoEntry photoEntry = null; if (!imagesArrLocations.isEmpty()) { FileLocation old = currentFileLocation; if (index < 0 || index >= imagesArrLocations.size()) { @@ -1459,39 +1071,30 @@ private void setImageIndex(int index, boolean init) { && old.volume_id == currentFileLocation.volume_id) { sameImage = true; } - actionBar.setTitle(activityContext.getString(R.string.Of, currentIndex + 1, + actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, currentIndex + 1, imagesArrLocations.size())); } else if (!imagesArrLocals.isEmpty()) { - Object object = imagesArrLocals.get(index); + Object entry = imagesArrLocals.get(index); if (index < 0 || index >= imagesArrLocals.size()) { closePhoto(false, false); return; } boolean fromCamera = false; - if (object instanceof MediaController.PhotoEntry) { - currentPathObject = ((MediaController.PhotoEntry) object).path; - fromCamera = ((MediaController.PhotoEntry) object).bucketId == 0 - && ((MediaController.PhotoEntry) object).dateTaken == 0 + if (entry instanceof MediaController.PhotoEntry) { + photoEntry = ((MediaController.PhotoEntry) entry); + currentPathObject = photoEntry.path; + fromCamera = ((MediaController.PhotoEntry) entry).bucketId == 0 + && ((MediaController.PhotoEntry) entry).dateTaken == 0 && imagesArrLocals.size() == 1; - } else if (object instanceof MediaController.SearchImage) { - MediaController.SearchImage searchImage = (MediaController.SearchImage) object; - if (searchImage.document != null) { - currentPathObject = FileLoader.getPathToAttach(searchImage.document, true) - .getAbsolutePath(); - } else { - currentPathObject = searchImage.imageUrl; - } } if (fromCamera) { - actionBar.setTitle(activityContext.getString(R.string.AttachPhoto)); + actionBar.setTitle(LocaleController.getString("AttachPhoto", R.string.AttachPhoto)); } else { - actionBar.setTitle(activityContext.getString(R.string.Of, + actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, currentIndex + 1, imagesArrLocals.size())); } - if (sendPhotoType == 0) { - checkImageView.setChecked(placeProvider.getCheckeCorner(currentIndex), - placeProvider.isPhotoChecked(currentIndex), false); - } + + changeCheckImageViewStatus(); } if (currentPlaceObject != null) { @@ -1510,6 +1113,10 @@ private void setImageIndex(int index, boolean init) { } } + if (currentPlaceObject != null) { + changeOriginPicBtn((MediaController.PhotoEntry) imagesArrLocals.get(index)); + } + if (!sameImage) { draggingDown = false; translationX = 0; @@ -1520,10 +1127,10 @@ private void setImageIndex(int index, boolean init) { animateToScale = 1; animationStartTime = 0; imageMoveAnimation = null; - changeModeAnimation = null; if (aspectRatioFrameLayout != null) { aspectRatioFrameLayout.setVisibility(View.INVISIBLE); } + releasePlayer(); pinchStartDistance = 0; pinchStartScale = 1; @@ -1540,101 +1147,39 @@ private void setImageIndex(int index, boolean init) { canDragDown = true; changingPage = false; switchImageAfterAnimation = 0; - canZoom = !imagesArrLocals.isEmpty() || (currentFileNames[0] != null && !isVideo - && radialProgressViews[0].backgroundState != 0); + canZoom = !imagesArrLocals.isEmpty() || currentFileNames[0] != null; updateMinMax(scale); } - if (prevIndex == -1) { - setImages(); + isCurrentVideo = photoEntry != null && photoEntry.mimeType.contains("video"); + if (isCurrentVideo) { + currentVideoEntry = photoEntry; + } - for (int a = 0; a < 3; a++) { - checkProgress(a, false); - } + if (prevIndex == -1) { + //setImages(-1); } else { - checkProgress(0, false); if (prevIndex > currentIndex) { ImageReceiver temp = rightImage; rightImage = centerImage; centerImage = leftImage; leftImage = temp; - RadialProgressView tempProgress = radialProgressViews[0]; - radialProgressViews[0] = radialProgressViews[2]; - radialProgressViews[2] = tempProgress; setIndexToImage(leftImage, currentIndex - 1); - - checkProgress(1, false); - checkProgress(2, false); } else if (prevIndex < currentIndex) { ImageReceiver temp = leftImage; leftImage = centerImage; centerImage = rightImage; rightImage = temp; - RadialProgressView tempProgress = radialProgressViews[0]; - radialProgressViews[0] = radialProgressViews[1]; - radialProgressViews[1] = tempProgress; setIndexToImage(rightImage, currentIndex + 1); - - checkProgress(1, false); - checkProgress(2, false); } } - } - private void checkProgress(int a, boolean animated) { - if (currentFileNames[a] != null) { - int index = currentIndex; - if (a == 1) { - index += 1; - } else if (a == 2) { - index -= 1; - } - File f = null; - boolean isVideo = false; - if (currentFileLocation != null) { - FileLocation location = imagesArrLocations.get(index); - f = FileLoader.getPathToAttach(location, avatarsDialogId != 0); - } else if (currentPathObject != null) { - f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_DOCUMENT), - currentFileNames[a]); - if (!f.exists()) { - f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), - currentFileNames[a]); - } - } - if (f != null && f.exists()) { - if (isVideo) { - radialProgressViews[a].setBackgroundState(3, animated); - } else { - radialProgressViews[a].setBackgroundState(-1, animated); - } - } else { - if (isVideo) { - if (!FileLoader.getInstance().isLoadingFile(currentFileNames[a])) { - radialProgressViews[a].setBackgroundState(2, false); - } else { - radialProgressViews[a].setBackgroundState(1, false); - } - } else { - radialProgressViews[a].setBackgroundState(0, animated); - } - Float progress = ImageLoader.getInstance().getFileProgress(currentFileNames[a]); - if (progress == null) { - progress = 0.0f; - } - radialProgressViews[a].setProgress(progress, false); - } - if (a == 0) { - canZoom = !imagesArrLocals.isEmpty() || (currentFileNames[0] != null && !isVideo - && radialProgressViews[0].backgroundState != 0); - } - } else { - radialProgressViews[a].setBackgroundState(-1, animated); - } + changePreviewIconCheckStatus(index); } + //刷新当前view private void setIndexToImage(ImageReceiver imageReceiver, int index) { imageReceiver.setOrientation(0, false); if (!imagesArrLocals.isEmpty()) { @@ -1648,51 +1193,68 @@ private void setIndexToImage(ImageReceiver imageReceiver, int index) { if (placeHolder == null) { placeHolder = placeProvider.getThumbForPhoto(null, index); } - String path = null; + String loadPath = null; Document document = null; + FileLocation photo = null; int imageSize = 0; String filter = null; + boolean isVideo = false; if (object instanceof MediaController.PhotoEntry) { MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) object; - if (photoEntry.imagePath != null) { - path = photoEntry.imagePath; + isVideo = photoEntry.mimeType.contains("video"); + if (!isVideo) { + if (photoEntry.imagePath != null) { + loadPath = photoEntry.imagePath; + } else { + imageReceiver.setOrientation(photoEntry.orientation, false); + loadPath = photoEntry.path; + } + filter = String.format(Locale.US, "%d_%d", size, size); } else { - imageReceiver.setOrientation(photoEntry.orientation, false); - path = photoEntry.path; + loadPath = "vthumb://" + photoEntry.imageId + ":" + photoEntry.path; } - filter = String.format(Locale.US, "%d_%d", size, size); - } else if (object instanceof MediaController.SearchImage) { - MediaController.SearchImage photoEntry = (MediaController.SearchImage) object; - if (photoEntry.imagePath != null) { - path = photoEntry.imagePath; - } else if (photoEntry.document != null) { - document = photoEntry.document; - imageSize = photoEntry.document.size; - } else { - path = photoEntry.imageUrl; - imageSize = photoEntry.size; + } else if (object instanceof BotInlineResult) { + BotInlineResult botInlineResult = ((BotInlineResult) object); + if (botInlineResult.type.equals("video")) { + if (botInlineResult.document != null) { + photo = botInlineResult.document.thumb.location; + } else { + loadPath = botInlineResult.thumb_url; + } + } else if (botInlineResult.type.equals("gif") && botInlineResult.document != null) { + document = botInlineResult.document; + imageSize = botInlineResult.document.size; + filter = "d"; + } else if (botInlineResult.photo != null) { + PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(botInlineResult.photo.sizes, AndroidUtilities.getPhotoSize()); + photo = sizeFull.location; + imageSize = sizeFull.size; + filter = String.format(Locale.US, "%d_%d", size, size); + } else if (botInlineResult.content_url != null) { + if (botInlineResult.type.equals("gif")) { + filter = "d"; + } else { + filter = String.format(Locale.US, "%d_%d", size, size); + } + loadPath = botInlineResult.content_url; } - filter = "d"; } if (document != null) { - imageReceiver.setImage(document, null, "d", - placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, - placeHolder == null ? document.thumb.location : null, - String.format(Locale.US, "%d_%d", size, size), imageSize, null, false); + imageReceiver.setImage(document, null, "d", placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, placeHolder == null ? document.thumb.location : null, String.format(Locale.US, "%d_%d", size, size), imageSize, null, 0); + } else if (photo != null) { + imageReceiver.setImage(photo, null, filter, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, null, String.format(Locale.US, "%d_%d", size, size), imageSize, null, 0); } else { - imageReceiver.setImage(path, filter, - placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, - null, imageSize); + imageReceiver.setImage(loadPath, filter, placeHolder != null ? new BitmapDrawable(null, placeHolder) : (isVideo && parentActivity != null ? parentActivity.getResources().getDrawable(R.drawable.photoview_placeholder) : null), null, imageSize); } } else { imageReceiver.setImageBitmap((Bitmap) null); } } else { int size[] = new int[1]; - FileLocation fileLocation = getFileLocation(index, size); + TLObject fileLocation = getFileLocation(index, size); if (fileLocation != null) { - imageReceiver.setNeedsQualityThumb(false); + imageReceiver.setNeedsQualityThumb(true); Bitmap placeHolder = null; if (currentThumb != null && imageReceiver == centerImage) { placeHolder = currentThumb; @@ -1700,17 +1262,14 @@ private void setIndexToImage(ImageReceiver imageReceiver, int index) { if (size[0] == 0) { size[0] = -1; } - imageReceiver.setImage(fileLocation, null, null, - placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, null, - "b", size[0], - null, avatarsDialogId != 0); + PhotoSize thumbLocation = null; + imageReceiver.setImage(fileLocation, null, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, thumbLocation != null ? thumbLocation.location : null, "b", size[0], null, 0); } else { - imageReceiver.setNeedsQualityThumb(false); + imageReceiver.setNeedsQualityThumb(true); if (size[0] == 0) { imageReceiver.setImageBitmap((Bitmap) null); } else { - imageReceiver.setImageBitmap(parentActivity.getResources() - .getDrawable(R.drawable.photoview_placeholder)); + imageReceiver.setImageBitmap(parentActivity.getResources().getDrawable(R.drawable.photoview_placeholder)); } } } @@ -1728,14 +1287,29 @@ public boolean isShowingImage(String object) { && object.equals(currentPathObject); } - public void openPhotoForSelect(final List photos, boolean selectPreview, - final int index, int type, + public void openPhotoForSelect(final List photos, + boolean inPreviewMode, + final int index, final PhotoViewerProvider provider) { - isSelectPreview = selectPreview; - sendPhotoType = type; + this.inPreviewMode = inPreviewMode; if (pickerView != null) { - pickerView.doneButtonTextView - .setText(R.string.Send); + pickerView.doneButtonTextView.setText(LocaleController.getString("Send", R.string.Send).toUpperCase()); + pickerView.setChecked(pickerView.isOriginChecked(), true); + pickerView.originalView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + pickerView.setChecked(!pickerView.isOriginChecked(), true); + provider.setIsOriginal(pickerView.isOriginChecked()); + } + }); + } + + List selected = provider.getSelectedPhotos(); + previewIconCellContainer.generatePreviewIconCell( + selected, provider instanceof PhotoAlbumPickerActivity.CustomProvider); + MediaController.PhotoEntry e = (MediaController.PhotoEntry) photos.get(index); + if (e != null && e.isVideo && getConfig().isVideoEditMode()) { + previewIconCellContainer.setVisibility(View.GONE); } openPhoto(null, photos, index, provider, 0, 0); } @@ -1761,8 +1335,8 @@ public void openPhoto(final FileLocation fileLocation, final List photos return; } - final PlaceProviderObject placeProviderObject = provider.getPlaceForPhoto(fileLocation, index); - if (placeProviderObject == null && photos == null) { + final PlaceProviderObject object = provider.getPlaceForPhoto(fileLocation, index); + if (object == null && photos == null) { return; } @@ -1772,11 +1346,12 @@ public void openPhoto(final FileLocation fileLocation, final List photos wm.removeView(windowView); } catch (Exception e) { // don't promt + e.getStackTrace(); } } try { - windowLayoutParams.type = WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; + windowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION; windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED; windowView.setFocusable(false); @@ -1787,7 +1362,7 @@ public void openPhoto(final FileLocation fileLocation, final List photos return; } - actionBar.setTitle(activityContext.getString(R.string.Of, 1, 1)); + actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, 1, 1)); NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidFailedLoad); NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileLoadProgressChanged); @@ -1807,28 +1382,32 @@ public void openPhoto(final FileLocation fileLocation, final List photos isVisible = true; toggleActionBar(true, false); - if (placeProviderObject != null) { + if (object != null) { disableShowCheck = true; animationInProgress = 1; - onPhotoShow(fileLocation, photos, index, placeProviderObject); + onPhotoShow(fileLocation, photos, index, object); - final Rect drawRegion = placeProviderObject.imageReceiver.getDrawRegion(); - int orientation = placeProviderObject.imageReceiver.getOrientation(); + final Rect drawRegion = object.imageReceiver.getDrawRegion(); + int orientation = object.imageReceiver.getOrientation(); + int animatedOrientation = object.imageReceiver.getAnimatedOrientation(); + if (animatedOrientation != 0) { + orientation = animatedOrientation; + } animatingImageView.setVisibility(View.VISIBLE); - animatingImageView.setRadius(placeProviderObject.radius); + animatingImageView.setRadius(object.radius); animatingImageView.setOrientation(orientation); - animatingImageView.setNeedRadius(placeProviderObject.radius != 0); - animatingImageView.setImageBitmap(placeProviderObject.thumb); + animatingImageView.setNeedRadius(object.radius != 0); + animatingImageView.setImageBitmap(object.thumb); animatingImageView.setAlpha(1.0f); animatingImageView.setPivotX(0.0f); animatingImageView.setPivotY(0.0f); - animatingImageView.setScaleX(placeProviderObject.scale); - animatingImageView.setScaleY(placeProviderObject.scale); - animatingImageView.setTranslationX(placeProviderObject.viewX + drawRegion.left * placeProviderObject.scale); - animatingImageView.setTranslationY(placeProviderObject.viewY + drawRegion.top * placeProviderObject.scale); + animatingImageView.setScaleX(object.scale); + animatingImageView.setScaleY(object.scale); + animatingImageView.setTranslationX(object.viewX + drawRegion.left * object.scale); + animatingImageView.setTranslationY(object.viewY + drawRegion.top * object.scale); final ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); layoutParams.width = (drawRegion.right - drawRegion.left); layoutParams.height = (drawRegion.bottom - drawRegion.top); @@ -1843,19 +1422,19 @@ public void openPhoto(final FileLocation fileLocation, final List photos float xPos = (AndroidUtilities.displaySize.x - width) / 2.0f; float yPos = (AndroidUtilities.displaySize.y - AndroidUtilities.statusBarHeight - height) / 2.0f; - int clipHorizontal = Math.abs(drawRegion.left - placeProviderObject.imageReceiver.getImageX()); - int clipVertical = Math.abs(drawRegion.top - placeProviderObject.imageReceiver.getImageY()); + int clipHorizontal = Math.abs(drawRegion.left - object.imageReceiver.getImageX()); + int clipVertical = Math.abs(drawRegion.top - object.imageReceiver.getImageY()); int coords2[] = new int[2]; - placeProviderObject.parentView.getLocationInWindow(coords2); + object.parentView.getLocationInWindow(coords2); int clipTop = coords2[1] - AndroidUtilities.statusBarHeight - - (placeProviderObject.viewY + drawRegion.top) + placeProviderObject.clipTopAddition; + - (object.viewY + drawRegion.top) + object.clipTopAddition; if (clipTop < 0) { clipTop = 0; } - int clipBottom = (placeProviderObject.viewY + drawRegion.top + layoutParams.height) - (coords2[1] - + placeProviderObject.parentView.getHeight() - AndroidUtilities.statusBarHeight) - + placeProviderObject.clipBottomAddition; + int clipBottom = (object.viewY + drawRegion.top + layoutParams.height) - (coords2[1] + + object.parentView.getHeight() - AndroidUtilities.statusBarHeight) + + object.clipBottomAddition; if (clipBottom < 0) { clipBottom = 0; } @@ -1866,9 +1445,9 @@ public void openPhoto(final FileLocation fileLocation, final List photos animationValues[0][1] = animatingImageView.getScaleY(); animationValues[0][2] = animatingImageView.getTranslationX(); animationValues[0][3] = animatingImageView.getTranslationY(); - animationValues[0][4] = clipHorizontal * placeProviderObject.scale; - animationValues[0][5] = clipTop * placeProviderObject.scale; - animationValues[0][6] = clipBottom * placeProviderObject.scale; + animationValues[0][4] = clipHorizontal * object.scale; + animationValues[0][5] = clipTop * object.scale; + animationValues[0][6] = clipBottom * object.scale; animationValues[0][7] = animatingImageView.getRadius(); animationValues[1][0] = scale; @@ -1901,7 +1480,7 @@ public void run() { } animationInProgress = 0; transitionAnimationStartTime = 0; - setImages(); + setImages(currentIndex); containerView.invalidate(); animatingImageView.setVisibility(View.GONE); if (showAfterAnimation != null) { @@ -1961,32 +1540,24 @@ public void run() { @Override public void run() { disableShowCheck = false; - placeProviderObject.imageReceiver.setVisible(false, true); + object.imageReceiver.setVisible(false, true); } }; } else { - if (photos != null) { - windowLayoutParams.flags = 0; - windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; - wm.updateViewLayout(windowView, windowLayoutParams); - windowView.setFocusable(true); - containerView.setFocusable(true); - } + windowLayoutParams.flags = 0; + windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; + wm.updateViewLayout(windowView, windowLayoutParams); + windowView.setFocusable(true); + containerView.setFocusable(true); backgroundDrawable.setAlpha(255); containerView.setAlpha(1.0f); - onPhotoShow(fileLocation, photos, index, placeProviderObject); + onPhotoShow(fileLocation, photos, index, object); } } public void closePhoto(boolean animated, boolean fromEditMode) { - if (!fromEditMode && currentEditMode != 0) { - if (currentEditMode == 1) { - photoCropView.cancelAnimationRunnable(); - } - switchToEditMode(0); - return; - } + inPreviewMode = false; try { if (visibleDialog != null) { visibleDialog.dismiss(); @@ -1997,10 +1568,6 @@ public void closePhoto(boolean animated, boolean fromEditMode) { } if (currentEditMode != 0) { - if (currentEditMode == 1) { - editorDoneLayout.setVisibility(View.GONE); - photoCropView.setVisibility(View.GONE); - } currentEditMode = 0; } @@ -2008,6 +1575,7 @@ public void closePhoto(boolean animated, boolean fromEditMode) { return; } + releasePlayer(); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileDidFailedLoad); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileDidLoaded); NotificationCenter.getInstance().removeObserver(this, @@ -2037,7 +1605,12 @@ public void closePhoto(boolean animated, boolean fromEditMode) { final ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); Rect drawRegion = null; - animatingImageView.setOrientation(centerImage.getOrientation()); + int orientation = centerImage.getOrientation(); + int animatedOrientation = centerImage.getAnimatedOrientation(); + if (animatedOrientation != 0) { + orientation = animatedOrientation; + } + animatingImageView.setOrientation(orientation); if (object != null) { animatingImageView.setNeedRadius(object.radius != 0); drawRegion = object.imageReceiver.getDrawRegion(); @@ -2202,10 +1775,10 @@ public void destroyPhotoViewer() { if (parentActivity == null || windowView == null) { return; } + releasePlayer(); try { if (windowView.getParent() != null) { - WindowManager wm = (WindowManager) parentActivity - .getSystemService(Context.WINDOW_SERVICE); + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); wm.removeViewImmediate(windowView); } windowView = null; @@ -2226,11 +1799,6 @@ private void onPhotoClosed(PlaceProviderObject object) { currentAnimation.setSecondParentView(null); currentAnimation = null; } - for (int a = 0; a < 3; a++) { - if (radialProgressViews[a] != null) { - radialProgressViews[a].setBackgroundState(-1, false); - } - } centerImage.setImageBitmap((Bitmap) null); leftImage.setImageBitmap((Bitmap) null); rightImage.setImageBitmap((Bitmap) null); @@ -2240,8 +1808,7 @@ public void run() { animatingImageView.setImageBitmap(null); try { if (windowView.getParent() != null) { - WindowManager wm = (WindowManager) parentActivity - .getSystemService(Context.WINDOW_SERVICE); + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); wm.removeView(windowView); } } catch (Exception e) { @@ -2252,6 +1819,7 @@ public void run() { if (placeProvider != null) { placeProvider.willHidePhotoViewer(); } + placeProvider.removeMediaEditId(); placeProvider = null; disableShowCheck = false; if (object != null) { @@ -2280,7 +1848,6 @@ public void onResume() { public void onPause() { if (currentAnimation != null) { closePhoto(false, false); - return; } } @@ -2303,12 +1870,6 @@ private void updateMinMax(float scale) { } else { minY = maxY = 0; } - if (currentEditMode == 1) { - maxX += photoCropView.getLimitX(); - maxY += photoCropView.getLimitY(); - minX -= photoCropView.getLimitWidth(); - minY -= photoCropView.getLimitHeight(); - } } private int getAdditionX() { @@ -2358,14 +1919,7 @@ private boolean onTouchEvent(MotionEvent ev) { } if (currentEditMode == 1) { - if (ev.getPointerCount() == 1) { - if (photoCropView.onTouch(ev)) { - updateMinMax(scale); - return true; - } - } else { - photoCropView.onTouch(null); - } + return true; } if (currentEditMode == 0 && ev.getPointerCount() == 1 && gestureDetector.onTouchEvent(ev)) { @@ -2378,19 +1932,14 @@ private boolean onTouchEvent(MotionEvent ev) { } } - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN - || ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { - if (currentEditMode == 1) { - photoCropView.cancelAnimationRunnable(); - } + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN || ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { discardTap = false; if (!scroller.isFinished()) { scroller.abortAnimation(); } if (!draggingDown && !changingPage) { if (canZoom && ev.getPointerCount() == 2) { - pinchStartDistance = (float) Math.hypot(ev.getX(1) - ev.getX(0), - ev.getY(1) - ev.getY(0)); + pinchStartDistance = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)); pinchStartScale = scale; pinchCenterX = (ev.getX(0) + ev.getX(1)) / 2.0f; pinchCenterY = (ev.getY(0) + ev.getY(1)) / 2.0f; @@ -2412,19 +1961,11 @@ private boolean onTouchEvent(MotionEvent ev) { } } } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { - if (currentEditMode == 1) { - photoCropView.cancelAnimationRunnable(); - } if (canZoom && ev.getPointerCount() == 2 && !draggingDown && zooming && !changingPage) { discardTap = true; - scale = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)) - / pinchStartDistance * pinchStartScale; - translationX = (pinchCenterX - getContainerViewWidth() / 2) - - ((pinchCenterX - getContainerViewWidth() / 2) - pinchStartX) - * (scale / pinchStartScale); - translationY = (pinchCenterY - getContainerViewHeight() / 2) - - ((pinchCenterY - getContainerViewHeight() / 2) - pinchStartY) - * (scale / pinchStartScale); + scale = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)) / pinchStartDistance * pinchStartScale; + translationX = (pinchCenterX - getContainerViewWidth() / 2) - ((pinchCenterX - getContainerViewWidth() / 2) - pinchStartX) * (scale / pinchStartScale); + translationY = (pinchCenterY - getContainerViewHeight() / 2) - ((pinchCenterY - getContainerViewHeight() / 2) - pinchStartY) * (scale / pinchStartScale); updateMinMax(scale); containerView.invalidate(); } else if (ev.getPointerCount() == 1) { @@ -2436,9 +1977,7 @@ private boolean onTouchEvent(MotionEvent ev) { if (dx > AndroidUtilities.dp(3) || dy > AndroidUtilities.dp(3)) { discardTap = true; } - if (!(placeProvider instanceof EmptyPhotoViewerProvider) && currentEditMode == 0 - && canDragDown && !draggingDown && scale == 1 - && dy >= AndroidUtilities.dp(30) && dy / 2 > dx) { + if (!(placeProvider instanceof EmptyPhotoViewerProvider) && currentEditMode == 0 && canDragDown && !draggingDown && scale == 1 && dy >= AndroidUtilities.dp(30) && dy / 2 > dx) { draggingDown = true; moving = false; dragY = ev.getY(); @@ -2455,10 +1994,7 @@ private boolean onTouchEvent(MotionEvent ev) { } else if (!invalidCoords && animationStartTime == 0) { float moveDx = moveStartX - ev.getX(); float moveDy = moveStartY - ev.getY(); - if (moving || currentEditMode != 0 - || scale == 1 - && Math.abs(moveDy) + AndroidUtilities.dp(12) < Math.abs(moveDx) - || scale != 1) { + if (moving || currentEditMode != 0 || scale == 1 && Math.abs(moveDy) + AndroidUtilities.dp(12) < Math.abs(moveDx) || scale != 1) { if (!moving) { moveDx = 0; moveDy = 0; @@ -2469,9 +2005,7 @@ private boolean onTouchEvent(MotionEvent ev) { moveStartX = ev.getX(); moveStartY = ev.getY(); updateMinMax(scale); - if (translationX < minX && (currentEditMode != 0 || !rightImage.hasImage()) - || translationX > maxX - && (currentEditMode != 0 || !leftImage.hasImage())) { + if (translationX < minX && (currentEditMode != 0 || !rightImage.hasImage()) || translationX > maxX && (currentEditMode != 0 || !leftImage.hasImage())) { moveDx /= 3.0f; } if (maxY == 0 && minY == 0 && currentEditMode == 0) { @@ -2501,24 +2035,15 @@ private boolean onTouchEvent(MotionEvent ev) { moveStartY = ev.getY(); } } - } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL - || ev.getActionMasked() == MotionEvent.ACTION_UP - || ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP) { - if (currentEditMode == 1) { - photoCropView.startAnimationRunnable(); - } + } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL || ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP) { if (zooming) { invalidCoords = true; if (scale < 1.0f) { updateMinMax(1.0f); animateTo(1.0f, 0, 0, true); } else if (scale > 3.0f) { - float atx = (pinchCenterX - getContainerViewWidth() / 2) - - ((pinchCenterX - getContainerViewWidth() / 2) - pinchStartX) - * (3.0f / pinchStartScale); - float aty = (pinchCenterY - getContainerViewHeight() / 2) - - ((pinchCenterY - getContainerViewHeight() / 2) - pinchStartY) - * (3.0f / pinchStartScale); + float atx = (pinchCenterX - getContainerViewWidth() / 2) - ((pinchCenterX - getContainerViewWidth() / 2) - pinchStartX) * (3.0f / pinchStartScale); + float aty = (pinchCenterY - getContainerViewHeight() / 2) - ((pinchCenterY - getContainerViewHeight() / 2) - pinchStartY) * (3.0f / pinchStartScale); updateMinMax(3.0f); if (atx < minX) { atx = minX; @@ -2559,13 +2084,11 @@ private boolean onTouchEvent(MotionEvent ev) { } if (currentEditMode == 0) { - if ((translationX < minX - getContainerViewWidth() / 3 - || velocity < -AndroidUtilities.dp(650)) && rightImage.hasImage()) { + if ((translationX < minX - getContainerViewWidth() / 3 || velocity < -AndroidUtilities.dp(650)) && rightImage.hasImage()) { goToNext(); return true; } - if ((translationX > maxX + getContainerViewWidth() / 3 - || velocity > AndroidUtilities.dp(650)) && leftImage.hasImage()) { + if ((translationX > maxX + getContainerViewWidth() / 3 || velocity > AndroidUtilities.dp(650)) && leftImage.hasImage()) { goToPrev(); return true; } @@ -2661,7 +2184,7 @@ public float getAnimationValue() { return animationValue; } - @SuppressLint("NewApi") + @SuppressLint({"NewApi", "DrawAllocation"}) private void onDraw(Canvas canvas) { if (animationInProgress == 1 || !isVisible && animationInProgress != 2) { return; @@ -2680,9 +2203,6 @@ private void onDraw(Canvas canvas) { float ts = scale + (animateToScale - scale) * animationValue; float tx = translationX + (animateToX - translationX) * animationValue; float ty = translationY + (animateToY - translationY) * animationValue; - if (currentEditMode == 1) { - photoCropView.setAnimationProgress(animationValue); - } if (animateToScale == 1 && scale == 1 && translationX == 0) { aty = ty; @@ -2697,9 +2217,6 @@ private void onDraw(Canvas canvas) { translationY = animateToY; scale = animateToScale; animationStartTime = 0; - if (currentEditMode == 1) { - photoCropView.setAnimationProgress(1); - } updateMinMax(scale); zoomAnimation = false; } @@ -2716,9 +2233,19 @@ private void onDraw(Canvas canvas) { } if (switchImageAfterAnimation != 0) { if (switchImageAfterAnimation == 1) { - setImageIndex(currentIndex + 1, false); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + setImageIndex(currentIndex + 1, false); + } + }); } else if (switchImageAfterAnimation == 2) { - setImageIndex(currentIndex - 1, false); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + setImageIndex(currentIndex - 1, false); + } + }); } switchImageAfterAnimation = 0; } @@ -2732,8 +2259,7 @@ private void onDraw(Canvas canvas) { if (currentEditMode == 0 && scale == 1 && aty != -1 && !zoomAnimation) { float maxValue = getContainerViewHeight() / 4.0f; - backgroundDrawable.setAlpha((int) Math.max(127, - 255 * (1.0f - (Math.min(Math.abs(aty), maxValue) / maxValue)))); + backgroundDrawable.setAlpha((int) Math.max(127, 255 * (1.0f - (Math.min(Math.abs(aty), maxValue) / maxValue)))); } else { backgroundDrawable.setAlpha(255); } @@ -2757,13 +2283,13 @@ private void onDraw(Canvas canvas) { if (!zoomAnimation && tranlateX < minX) { alpha = Math.min(1.0f, (minX - tranlateX) / canvas.getWidth()); scaleDiff = (1.0f - alpha) * 0.3f; - tranlateX = -canvas.getWidth() - PAGE_SPACING / 2; + tranlateX = -canvas.getWidth() - AndroidUtilities.dp(30) / 2; } if (sideImage.hasBitmapImage()) { canvas.save(); canvas.translate(getContainerViewWidth() / 2, getContainerViewHeight() / 2); - canvas.translate(canvas.getWidth() + PAGE_SPACING / 2 + tranlateX, 0); + canvas.translate(canvas.getWidth() + AndroidUtilities.dp(30) / 2 + tranlateX, 0); canvas.scale(1.0f - scaleDiff, 1.0f - scaleDiff); int bitmapWidth = sideImage.getBitmapWidth(); int bitmapHeight = sideImage.getBitmapHeight(); @@ -2782,11 +2308,7 @@ private void onDraw(Canvas canvas) { canvas.save(); canvas.translate(tranlateX, currentTranslationY / currentScale); - canvas.translate((canvas.getWidth() * (scale + 1) + PAGE_SPACING) / 2, - -currentTranslationY / currentScale); - radialProgressViews[1].setScale(1.0f - scaleDiff); - radialProgressViews[1].setAlpha(alpha); - radialProgressViews[1].onDraw(canvas); + canvas.translate((canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2, -currentTranslationY / currentScale); canvas.restore(); } @@ -2799,25 +2321,18 @@ private void onDraw(Canvas canvas) { alpha = 1.0f - alpha; translateX = maxX; } - boolean drawTextureView = Build.VERSION.SDK_INT >= 16 && aspectRatioFrameLayout != null - && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; + boolean drawTextureView = aspectRatioFrameLayout != null && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; if (centerImage.hasBitmapImage()) { canvas.save(); - canvas.translate(getContainerViewWidth() / 2 + getAdditionX(), - getContainerViewHeight() / 2 + getAdditionY()); + canvas.translate(getContainerViewWidth() / 2 + getAdditionX(), getContainerViewHeight() / 2 + getAdditionY()); canvas.translate(translateX, currentTranslationY); canvas.scale(currentScale - scaleDiff, currentScale - scaleDiff); - if (currentEditMode == 1) { - photoCropView.setBitmapParams(currentScale, translateX, currentTranslationY); - } - int bitmapWidth = centerImage.getBitmapWidth(); int bitmapHeight = centerImage.getBitmapHeight(); if (drawTextureView && textureUploaded) { float scale1 = bitmapWidth / (float) bitmapHeight; - float scale2 = videoTextureView.getMeasuredWidth() - / (float) videoTextureView.getMeasuredHeight(); + float scale2 = videoTextureView.getMeasuredWidth() / (float) videoTextureView.getMeasuredHeight(); if (Math.abs(scale1 - scale2) > 0.01f) { bitmapWidth = videoTextureView.getMeasuredWidth(); bitmapHeight = videoTextureView.getMeasuredHeight(); @@ -2830,8 +2345,7 @@ private void onDraw(Canvas canvas) { int width = (int) (bitmapWidth * scale); int height = (int) (bitmapHeight * scale); - if (!drawTextureView || !textureUploaded || !videoCrossfadeStarted - || videoCrossfadeAlpha != 1.0f) { + if (!drawTextureView || !textureUploaded || !videoCrossfadeStarted || videoCrossfadeAlpha != 1.0f) { centerImage.setAlpha(alpha); centerImage.setImageCoords(-width / 2, -height / 2, width, height); centerImage.draw(canvas); @@ -2843,13 +2357,13 @@ private void onDraw(Canvas canvas) { videoCrossfadeAlphaLastTime = System.currentTimeMillis(); } canvas.translate(-width / 2, -height / 2); - videoTextureView.setAlpha(alpha * videoCrossfadeAlpha); + videoTextureView.setAlpha(alpha); aspectRatioFrameLayout.draw(canvas); if (videoCrossfadeStarted && videoCrossfadeAlpha < 1.0f) { long newUpdateTime = System.currentTimeMillis(); long dt = newUpdateTime - videoCrossfadeAlphaLastTime; videoCrossfadeAlphaLastTime = newUpdateTime; - videoCrossfadeAlpha += dt / 300.0f; + videoCrossfadeAlpha += dt / 200.0f; containerView.invalidate(); if (videoCrossfadeAlpha > 1.0f) { videoCrossfadeAlpha = 1.0f; @@ -2858,22 +2372,12 @@ private void onDraw(Canvas canvas) { } canvas.restore(); } - if (!drawTextureView) { - canvas.save(); - canvas.translate(translateX, currentTranslationY / currentScale); - radialProgressViews[0].setScale(1.0f - scaleDiff); - radialProgressViews[0].setAlpha(alpha); - radialProgressViews[0].onDraw(canvas); - canvas.restore(); - } if (sideImage == leftImage) { if (sideImage.hasBitmapImage()) { canvas.save(); canvas.translate(getContainerViewWidth() / 2, getContainerViewHeight() / 2); - canvas.translate( - -(canvas.getWidth() * (scale + 1) + PAGE_SPACING) / 2 + currentTranslationX, - 0); + canvas.translate(-(canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2 + currentTranslationX, 0); int bitmapWidth = sideImage.getBitmapWidth(); int bitmapHeight = sideImage.getBitmapHeight(); @@ -2891,11 +2395,7 @@ private void onDraw(Canvas canvas) { canvas.save(); canvas.translate(currentTranslationX, currentTranslationY / currentScale); - canvas.translate(-(canvas.getWidth() * (scale + 1) + PAGE_SPACING) / 2, - -currentTranslationY / currentScale); - radialProgressViews[2].setScale(1.0f); - radialProgressViews[2].setAlpha(1.0f); - radialProgressViews[2].onDraw(canvas); + canvas.translate(-(canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2, -currentTranslationY / currentScale); canvas.restore(); } } @@ -2930,19 +2430,6 @@ public void run() { } } - private void onActionClick(boolean download) { - if (currentFileNames[0] == null) { - return; - } - File file = null; - if (Build.VERSION.SDK_INT >= 16) { - } else { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.fromFile(file), "video/mp4"); - parentActivity.startActivityForResult(intent, 500); - } - } - @Override public boolean onDown(MotionEvent e) { return false; @@ -2988,24 +2475,50 @@ public boolean onSingleTapConfirmed(MotionEvent e) { if (canShowBottom) { boolean drawTextureView = Build.VERSION.SDK_INT >= 16 && aspectRatioFrameLayout != null && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; - if (radialProgressViews[0] != null && containerView != null && !drawTextureView) { - int state = radialProgressViews[0].backgroundState; - if (state > 0 && state <= 3) { - float x = e.getX(); - float y = e.getY(); - if (x >= (getContainerViewWidth() - AndroidUtilities.dp(100)) / 2.0f - && x <= (getContainerViewWidth() + AndroidUtilities.dp(100)) / 2.0f && - y >= (getContainerViewHeight() - AndroidUtilities.dp(100)) / 2.0f - && y <= (getContainerViewHeight() + AndroidUtilities.dp(100)) / 2.0f) { - onActionClick(true); - checkProgress(0, true); - return true; - } + if (containerView != null && !drawTextureView) { + toggleActionBar(!isActionBarVisible, true); + } + } else { + if (fullScreen && isPlaying){ + return true; + } + + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + fullScreen = !fullScreen; + if (!fullScreen && actionBar.getVisibility() == View.GONE) { + actionBar.setVisibility(View.VISIBLE); + pickerView.setVisibility(View.VISIBLE); + if (previewIconCellContainer.isShowing()) { + previewIconCellContainer.setVisibility(View.VISIBLE); } } - toggleActionBar(!isActionBarVisible, true); - } else if (sendPhotoType == 0) { - checkImageView.performClick(); + animatorSet = new AnimatorSet(); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.setDuration(180); + animatorSet.playTogether( + ObjectAnimator.ofFloat(actionBar, "alpha", !fullScreen ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(pickerView, "alpha", !fullScreen ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(previewIconCellContainer, "alpha", !fullScreen ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(mVideoHintContainer, "alpha", !fullScreen ? 1.0f : 0.0f)); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(animatorSet)) { + animatorSet = null; + } + if (fullScreen) { + actionBar.setVisibility(View.GONE); + pickerView.setVisibility(View.GONE); + if (previewIconCellContainer.isShowing()) { + previewIconCellContainer.setVisibility(View.GONE); + } + } + } + }); + animatorSet.start(); } return true; } @@ -3046,4 +2559,139 @@ public boolean onDoubleTap(MotionEvent e) { public boolean onDoubleTapEvent(MotionEvent e) { return false; } + + public boolean isInPreviewMode() { + return inPreviewMode; + } + + public void start() { + if (currentAnimation != null) { + currentAnimation.start(); + } + } + + public void stop() { + if (currentAnimation != null) { + currentAnimation.stop(); + } + if (videoPlayer != null) { + videoPlayer.pause(); + } + } + + private void preparePlayer(File file, boolean playWhenReady) { + if (parentActivity == null) { + return; + } + releasePlayer(); + if (videoTextureView == null) { + aspectRatioFrameLayout = new AspectRatioFrameLayout(parentActivity); + aspectRatioFrameLayout.setVisibility(View.INVISIBLE); + containerView.addView(aspectRatioFrameLayout, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + videoTextureView = new TextureView(parentActivity); + videoTextureView.setOpaque(false); + aspectRatioFrameLayout.addView(videoTextureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + } + + videoTextureView.setAlpha(videoCrossfadeAlpha = 0.0f); + if (videoPlayer == null) { + videoPlayer = new VideoPlayer(); + videoPlayer.setTextureView(videoTextureView); + videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() { + @Override + public void onStateChanged(boolean playWhenReady, int playbackState) { + if (videoPlayer == null) { + return; + } + if (playbackState != ExoPlayer.STATE_ENDED && playbackState != ExoPlayer.STATE_IDLE) { + try { + parentActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (playbackState == ExoPlayer.STATE_READY && aspectRatioFrameLayout.getVisibility() != View.VISIBLE) { + aspectRatioFrameLayout.setVisibility(View.VISIBLE); + } + if (videoPlayer.isPlaying() && playbackState != ExoPlayer.STATE_ENDED) { + isPlaying = true; + videoPlayImage.setBackgroundResource(R.drawable.ic_video_pause); + playButtonButton.setAlpha(0); + } else if (isPlaying) { + isPlaying = false; + videoPlayImage.setBackgroundResource(R.drawable.ic_video_play); + playButtonButton.setAlpha(1.0f); + if (playbackState == ExoPlayer.STATE_ENDED) { + videoPlayer.pause(); + } + } + if (playbackState == ExoPlayer.STATE_ENDED) { + videoPlayer.seekTo(0); + } + } + + @Override + public void onError(Exception e) { + e.printStackTrace(); + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + if (aspectRatioFrameLayout != null) { + if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) { + int temp = width; + width = height; + height = temp; + } + aspectRatioFrameLayout.setAspectRatio(height == 0 ? 1 : (width * pixelWidthHeightRatio) / height, unappliedRotationDegrees); + } + } + + @Override + public void onRenderedFirstFrame() { + } + + @Override + public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + + } + }); + } + videoPlayer.preparePlayer(Uri.fromFile(file), "other"); + videoPlayer.setPlayWhenReady(playWhenReady); + } + + private void releasePlayer() { + if (videoPlayer != null) { + videoPlayer.releasePlayer(); + videoPlayer = null; + } + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + e.printStackTrace(); + } + if (aspectRatioFrameLayout != null) { + containerView.removeView(aspectRatioFrameLayout); + aspectRatioFrameLayout = null; + } + if (videoTextureView != null) { + videoTextureView = null; + } + + isPlaying = false; + videoPlayImage.setBackgroundResource(R.drawable.ic_video_play); + } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Theme.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Theme.java index 827f3b9..927af23 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Theme.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Theme.java @@ -11,7 +11,7 @@ import android.graphics.drawable.StateListDrawable; import android.os.Build; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; public class Theme { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBar.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBar.java similarity index 86% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBar.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBar.java index 1dd6335..68933e4 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBar.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBar.java @@ -1,13 +1,13 @@ -package com.tangxiaolv.telegramgallery.Actionbar; +package com.tangxiaolv.telegramgallery.actionbar; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.drawable.Drawable; import android.os.Build; +import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -18,11 +18,13 @@ import com.tangxiaolv.telegramgallery.AnimatorListenerAdapterProxy; import com.tangxiaolv.telegramgallery.Gallery; import com.tangxiaolv.telegramgallery.Theme; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; import java.util.ArrayList; +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; + public class ActionBar extends FrameLayout { public static class ActionBarMenuOnItemClick { @@ -38,11 +40,11 @@ public boolean canOpenMenu() { private TextView backButtonTextView; private FrameLayout backContainer; private ImageView backButtonImageView; - private SimpleTextView titleTextView; - private SimpleTextView subtitleTextView; + private com.tangxiaolv.telegramgallery.actionbar.SimpleTextView titleTextView; + private com.tangxiaolv.telegramgallery.actionbar.SimpleTextView subtitleTextView; private View actionModeTop; - private ActionBarMenu menu; - private ActionBarMenu actionMode; + private com.tangxiaolv.telegramgallery.actionbar.ActionBarMenu menu; + private com.tangxiaolv.telegramgallery.actionbar.ActionBarMenu actionMode; private boolean occupyStatusBar = Build.VERSION.SDK_INT >= 21; private boolean actionModeVisible; private boolean addToContainer = true; @@ -57,9 +59,8 @@ public boolean canOpenMenu() { protected boolean isSearchFieldVisible; protected int itemsBackgroundColor; private boolean isBackOverlayVisible; - protected BaseFragment parentFragment; + protected com.tangxiaolv.telegramgallery.actionbar.BaseFragment parentFragment; public ActionBarMenuOnItemClick actionBarMenuOnItemClick; - private Point screenSize; public ActionBar(Context context) { super(context); @@ -73,11 +74,10 @@ private void createBackButtonImage() { backContainer = new FrameLayout(getContext()); backButtonImageView = new ImageView(getContext()); backButtonImageView.setScaleType(ImageView.ScaleType.CENTER); - backButtonImageView - .setBackgroundDrawable(Theme.createBarSelectorDrawable(itemsBackgroundColor)); - LayoutParams params = LayoutHelper.createFrame(28, 30); + backButtonImageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(itemsBackgroundColor)); + LayoutParams params = LayoutHelper.createFrame(28, 28); params.gravity = Gravity.CENTER_VERTICAL; - params.setMargins(AndroidUtilities.dp(8),0,0,0); + params.setMargins(AndroidUtilities.dp(8), 0, 0, 0); backContainer.addView(backButtonImageView, params); addView(backContainer, LayoutHelper.createFrame(54, 54, Gravity.LEFT | Gravity.TOP)); @@ -96,21 +96,21 @@ public void onClick(View v) { } private void createBackButtonText(String text) { - if (backButtonTextView != null){ + if (backButtonTextView != null) { return; } backContainer = new FrameLayout(getContext()); backButtonTextView = new TextView(getContext()); backButtonTextView.setText(text); - backButtonTextView.setTextSize(18); - backButtonTextView.setTextColor(0xffffffff); + + backButtonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + backButtonTextView.setGravity(Gravity.CENTER_VERTICAL); - LayoutParams textParams = LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP,8,0,0,0); - backButtonTextView.setLayoutParams(textParams); - backContainer.addView(backButtonTextView); - backContainer.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT)); - addView(backContainer); + LayoutParams params = LayoutHelper.createFrame(36, -1); + params.setMargins(AndroidUtilities.dp(8), 0, 0, 0); + backContainer.addView(backButtonTextView, params); + addView(backContainer, LayoutHelper.createFrame(54, 54, Gravity.LEFT | Gravity.TOP)); backContainer.setOnClickListener(new OnClickListener() { @Override @@ -124,7 +124,6 @@ public void onClick(View v) { } } }); - } public void setBackButtonDrawable(Drawable drawable) { @@ -133,8 +132,8 @@ public void setBackButtonDrawable(Drawable drawable) { } backButtonImageView.setVisibility(drawable == null ? GONE : VISIBLE); backButtonImageView.setImageDrawable(drawable); - if (drawable instanceof BackDrawable) { - ((BackDrawable) drawable).setRotation(isActionModeShowed() ? 1 : 0, false); + if (drawable instanceof com.tangxiaolv.telegramgallery.actionbar.BackDrawable) { + ((com.tangxiaolv.telegramgallery.actionbar.BackDrawable) drawable).setRotation(isActionModeShowed() ? 1 : 0, false); } } @@ -156,7 +155,7 @@ private void createsubtitleTextView() { if (subtitleTextView != null) { return; } - subtitleTextView = new SimpleTextView(getContext()); + subtitleTextView = new com.tangxiaolv.telegramgallery.actionbar.SimpleTextView(getContext()); subtitleTextView.setGravity(Gravity.LEFT); subtitleTextView.setTextColor(Theme.ACTION_BAR_SUBTITLE_COLOR); addView(subtitleTextView, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, @@ -186,9 +185,9 @@ private void createTitleTextView() { if (titleTextView != null) { return; } - titleTextView = new SimpleTextView(getContext()); + titleTextView = new com.tangxiaolv.telegramgallery.actionbar.SimpleTextView(getContext()); titleTextView.setGravity(Gravity.LEFT | Gravity.CENTER); - titleTextView.setTextColor(0xffffffff); + titleTextView.setTextColor(DARK_THEME ? 0xffffffff : 0xff000000); titleTextView.setTextSize(32); // titleTextView.getPaint().setFakeBoldText(true); addView(titleTextView, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, @@ -207,11 +206,15 @@ public void setTitle(CharSequence value) { } } - public SimpleTextView getSubtitleTextView() { + public void setTitleColor(int color) { + titleTextView.setTextColor(color); + } + + public com.tangxiaolv.telegramgallery.actionbar.SimpleTextView getSubtitleTextView() { return subtitleTextView; } - public SimpleTextView getTitleTextView() { + public com.tangxiaolv.telegramgallery.actionbar.SimpleTextView getTitleTextView() { return titleTextView; } @@ -222,12 +225,11 @@ public String getTitle() { return titleTextView.getText().toString(); } - public ActionBarMenu createMenu() { + public com.tangxiaolv.telegramgallery.actionbar.ActionBarMenu createMenu() { if (menu != null) { return menu; } - screenSize = AndroidUtilities.getRealScreenSize(); - menu = new ActionBarMenu(getContext(), this); + menu = new com.tangxiaolv.telegramgallery.actionbar.ActionBarMenu(getContext(), this); addView(menu, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.RIGHT)); return menu; @@ -237,11 +239,11 @@ public void setActionBarMenuOnItemClick(ActionBarMenuOnItemClick listener) { actionBarMenuOnItemClick = listener; } - public ActionBarMenu createActionMode() { + public com.tangxiaolv.telegramgallery.actionbar.ActionBarMenu createActionMode() { if (actionMode != null) { return actionMode; } - actionMode = new ActionBarMenu(getContext(), this); + actionMode = new com.tangxiaolv.telegramgallery.actionbar.ActionBarMenu(getContext(), this); actionMode.setBackgroundColor(0xffffffff); addView(actionMode, indexOfChild(backContainer)); actionMode.setPadding(0, occupyStatusBar ? AndroidUtilities.statusBarHeight : 0, 0, 0); @@ -318,8 +320,8 @@ public void onAnimationCancel(Animator animation) { actionModeAnimation.start(); if (backButtonImageView != null) { Drawable drawable = backButtonImageView.getDrawable(); - if (drawable instanceof BackDrawable) { - ((BackDrawable) drawable).setRotation(1, true); + if (drawable instanceof com.tangxiaolv.telegramgallery.actionbar.BackDrawable) { + ((com.tangxiaolv.telegramgallery.actionbar.BackDrawable) drawable).setRotation(1, true); } backButtonImageView.setBackgroundDrawable( Theme.createBarSelectorDrawable(Theme.ACTION_BAR_MODE_SELECTOR_COLOR)); @@ -373,8 +375,8 @@ public void onAnimationCancel(Animator animation) { } if (backButtonImageView != null) { Drawable drawable = backButtonImageView.getDrawable(); - if (drawable instanceof BackDrawable) { - ((BackDrawable) drawable).setRotation(0, true); + if (drawable instanceof com.tangxiaolv.telegramgallery.actionbar.BackDrawable) { + ((com.tangxiaolv.telegramgallery.actionbar.BackDrawable) drawable).setRotation(0, true); } backButtonImageView .setBackgroundDrawable(Theme.createBarSelectorDrawable(itemsBackgroundColor)); @@ -407,8 +409,8 @@ protected void onSearchFieldVisibilityChanged(boolean visible) { subtitleTextView.setVisibility(visible ? INVISIBLE : VISIBLE); } Drawable drawable = backButtonImageView.getDrawable(); - if (drawable != null && drawable instanceof MenuDrawable) { - ((MenuDrawable) drawable).setRotation(visible ? 1 : 0, true); + if (drawable != null && drawable instanceof com.tangxiaolv.telegramgallery.actionbar.MenuDrawable) { + ((com.tangxiaolv.telegramgallery.actionbar.MenuDrawable) drawable).setRotation(visible ? 1 : 0, true); } } @@ -447,7 +449,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int textLeft; if (backContainer != null && backContainer.getVisibility() != GONE) { backContainer.measure( - MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(72), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(54), MeasureSpec.EXACTLY), actionBarHeightSpec); textLeft = AndroidUtilities.dp(AndroidUtilities.isTablet() ? 80 : 72); } else { @@ -474,7 +476,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (titleTextView != null && titleTextView.getVisibility() != GONE) { titleTextView.setTextSize(!AndroidUtilities.isTablet() && getResources() .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 18 - : 20); + : 20); titleTextView.measure( MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.AT_MOST)); @@ -483,7 +485,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (subtitleTextView != null && subtitleTextView.getVisibility() != GONE) { subtitleTextView.setTextSize(!AndroidUtilities.isTablet() && getResources() .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 14 - : 16); + : 16); subtitleTextView.measure( MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.AT_MOST)); @@ -505,7 +507,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // int additionalTop = occupyStatusBar ? AndroidUtilities.statusBarHeight : 0; - int additionalTop= 0 ; + int additionalTop = 0; int textLeft; if (backContainer != null && backContainer.getVisibility() != GONE) { @@ -529,8 +531,8 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto if (subtitleTextView != null && subtitleTextView.getVisibility() != GONE) { textTop = (getCurrentActionBarHeight() / 2 - titleTextView.getTextHeight()) / 2 + AndroidUtilities.dp(!AndroidUtilities.isTablet() && getResources() - .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE - ? 2 : 3); + .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE + ? 2 : 3); } else { textTop = (getCurrentActionBarHeight() - titleTextView.getTextHeight()) / 2; } @@ -542,8 +544,8 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto int textTop = getCurrentActionBarHeight() / 2 + (getCurrentActionBarHeight() / 2 - subtitleTextView.getTextHeight()) / 2 - AndroidUtilities.dp(!AndroidUtilities.isTablet() && getResources() - .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE - ? 1 : 1); + .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE + ? 1 : 1); subtitleTextView.layout(textLeft, additionalTop + textTop, textLeft + subtitleTextView.getMeasuredWidth(), additionalTop + textTop + subtitleTextView.getTextHeight()); @@ -597,25 +599,6 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto default: childTop = lp.topMargin; } - - //标题居中 - childLeft = screenSize.x / 2; - if (child instanceof ActionBarMenuItem){ - int realTextSize; - int count = ((ActionBarMenuItem) child).getChildCount(); - for (int j = 0; j < count; j++) { - if (((ActionBarMenuItem) child).getChildAt(j) instanceof TextView){ - TextView innertext = (TextView) ((ActionBarMenuItem) child).getChildAt(j); - realTextSize = width - innertext.getCompoundDrawablePadding(); - Drawable rightDrawable = innertext.getCompoundDrawables()[2]; - if (rightDrawable != null){ - realTextSize -= rightDrawable.getIntrinsicWidth(); - } - childLeft -= realTextSize / 2; - break; - } - } - } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarLayout.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarLayout.java similarity index 98% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarLayout.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarLayout.java index c9c4c3f..4bfbb41 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarLayout.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarLayout.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.Actionbar; +package com.tangxiaolv.telegramgallery.actionbar; import android.animation.Animator; import android.animation.AnimatorSet; @@ -26,8 +26,8 @@ import com.tangxiaolv.telegramgallery.AnimatorListenerAdapterProxy; import com.tangxiaolv.telegramgallery.R; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; import java.util.ArrayList; @@ -36,8 +36,7 @@ public class ActionBarLayout extends FrameLayout { public interface ActionBarLayoutDelegate { boolean onPreIme(); - boolean needPresentFragment(BaseFragment fragment, boolean removeLast, - boolean forceWithoutAnimation, ActionBarLayout layout); + boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, ActionBarLayout layout); boolean needAddFragmentToStack(BaseFragment fragment, ActionBarLayout layout); @@ -159,8 +158,8 @@ public ActionBarLayout(Context context) { parentActivity = (Activity) context; if (layerShadowDrawable == null) { - layerShadowDrawable = getResources().getDrawable(R.drawable.layer_shadow); - headerShadowDrawable = getResources().getDrawable(R.drawable.header_shadow); + layerShadowDrawable = getResources().getDrawable(R.drawable.album_layer_shadow); +// headerShadowDrawable = getResources().getDrawable(R.drawable.album_header_shadow); scrimPaint = new Paint(); } } @@ -432,8 +431,8 @@ public boolean onTouchEvent(MotionEvent ev) { } } else if (ev != null && ev.getPointerId(0) == startedTrackingPointerId && (ev.getAction() == MotionEvent.ACTION_CANCEL - || ev.getAction() == MotionEvent.ACTION_UP - || ev.getAction() == MotionEvent.ACTION_POINTER_UP)) { + || ev.getAction() == MotionEvent.ACTION_UP + || ev.getAction() == MotionEvent.ACTION_POINTER_UP)) { if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } @@ -654,7 +653,7 @@ public void resumeDelayedFragmentAnimation() { } public boolean presentFragment(final BaseFragment fragment, final boolean removeLast, - boolean forceWithoutAnimation, boolean check) { + boolean forceWithoutAnimation, boolean check) { if (parentActivity == null) return false; @@ -668,7 +667,7 @@ public boolean presentFragment(final BaseFragment fragment, final boolean remove } boolean needAnimation = !forceWithoutAnimation && parentActivity.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE) - .getBoolean("view_animations", true); + .getBoolean("view_animations", true); final BaseFragment currentFragment = !fragmentsStack.isEmpty() ? fragmentsStack.get(fragmentsStack.size() - 1) : null; @@ -881,7 +880,7 @@ public void closeLastFragment(boolean animated) { setInnerTranslationX(0); boolean needAnimation = animated && parentActivity.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE) - .getBoolean("view_animations", true); + .getBoolean("view_animations", true); final BaseFragment currentFragment = fragmentsStack.get(fragmentsStack.size() - 1); BaseFragment previousFragment = null; if (fragmentsStack.size() > 1) { @@ -1227,8 +1226,4 @@ public void setTitleOverlayText(String text) { public boolean hasOverlappingRendering() { return false; } - - public void clear() { - parentActivity = null; - } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarMenu.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarMenu.java similarity index 96% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarMenu.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarMenu.java index 15e1a0c..d3541a9 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarMenu.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarMenu.java @@ -1,4 +1,5 @@ -package com.tangxiaolv.telegramgallery.Actionbar; +package com.tangxiaolv.telegramgallery.actionbar; + import android.content.Context; import android.graphics.drawable.Drawable; @@ -7,8 +8,8 @@ import android.widget.LinearLayout; import com.tangxiaolv.telegramgallery.Theme; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; public class ActionBarMenu extends LinearLayout { @@ -89,7 +90,7 @@ public void onClick(View view) { return menuItem; } - public View addItem(int id,View view) { + public View addItem(int id, View view) { view.setTag(id); addView(view); view.setOnClickListener(new OnClickListener() { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarMenuItem.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarMenuItem.java similarity index 96% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarMenuItem.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarMenuItem.java index 1b4010c..e38f450 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/ActionBarMenuItem.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/ActionBarMenuItem.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Actionbar; +package com.tangxiaolv.telegramgallery.actionbar; import android.content.Context; import android.graphics.Rect; @@ -21,11 +21,12 @@ import android.widget.LinearLayout; import android.widget.TextView; -import com.tangxiaolv.telegramgallery.Components.ActionBarPopupWindow; import com.tangxiaolv.telegramgallery.R; +import com.tangxiaolv.telegramgallery.components.ActionBarPopupWindow; import com.tangxiaolv.telegramgallery.Theme; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.LocaleController; import java.lang.reflect.Field; @@ -213,8 +214,8 @@ public void onDispatchKeyEvent(KeyEvent keyEvent) { } TextView textView = new TextView(getContext()); textView.setTextColor(0xff212121); - textView.setBackgroundResource(R.drawable.list_selector); - if (!AndroidUtilities.isRTL()) { + textView.setBackgroundResource(R.drawable.album_list_selector); + if (!LocaleController.isRTL) { textView.setGravity(Gravity.CENTER_VERTICAL); } else { textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); @@ -226,7 +227,7 @@ public void onDispatchKeyEvent(KeyEvent keyEvent) { textView.setText(text); if (icon != 0) { textView.setCompoundDrawablePadding(AndroidUtilities.dp(12)); - if (!AndroidUtilities.isRTL()) { + if (!LocaleController.isRTL) { textView.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(icon), null, null, null); } else { textView.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(icon), null); @@ -235,7 +236,7 @@ public void onDispatchKeyEvent(KeyEvent keyEvent) { popupLayout.setShowedFromBotton(showFromBottom); popupLayout.addView(textView); LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) textView.getLayoutParams(); - if (AndroidUtilities.isRTL()) { + if (LocaleController.isRTL) { layoutParams.gravity = Gravity.RIGHT; } layoutParams.width = LayoutHelper.MATCH_PARENT; @@ -283,7 +284,7 @@ public void toggleSubMenu() { if (Build.VERSION.SDK_INT >= 19) { popupWindow.setAnimationStyle(0); } else { - popupWindow.setAnimationStyle(R.style.PopupAnimation); + popupWindow.setAnimationStyle(R.style.AlbumPopupAnimation); } popupWindow.setOutsideTouchable(true); popupWindow.setClippingEnabled(true); @@ -450,9 +451,10 @@ public void afterTextChanged(Editable s) { try { Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes"); mCursorDrawableRes.setAccessible(true); - mCursorDrawableRes.set(searchField, R.drawable.search_carret); + mCursorDrawableRes.set(searchField, R.drawable.album_search_carret); } catch (Exception e) { //nothing to do + e.getStackTrace(); } searchField.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_ACTION_SEARCH); searchField.setTextIsSelectable(false); @@ -465,7 +467,7 @@ public void afterTextChanged(Editable s) { searchField.setLayoutParams(layoutParams2); clearButton = new ImageView(getContext()); - clearButton.setImageResource(R.drawable.ic_close_white); + clearButton.setImageResource(R.drawable.album_close_white); clearButton.setScaleType(ImageView.ScaleType.CENTER); clearButton.setOnClickListener(new OnClickListener() { @Override @@ -558,10 +560,10 @@ private void updateOrShowPopup(boolean show, boolean update) { } } else { if (show) { - popupWindow.showAsDropDown(this, -AndroidUtilities.dp(10), offsetY); + popupWindow.showAsDropDown(this, -AndroidUtilities.dp(8), offsetY); } if (update) { - popupWindow.update(this, -AndroidUtilities.dp(10), offsetY, -1, -1); + popupWindow.update(this, -AndroidUtilities.dp(8), offsetY, -1, -1); } } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/BackDrawable.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/BackDrawable.java similarity index 97% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/BackDrawable.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/BackDrawable.java index 5d7ca0b..ef9a815 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/BackDrawable.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/BackDrawable.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Actionbar; +package com.tangxiaolv.telegramgallery.actionbar; import android.graphics.Canvas; @@ -9,7 +9,7 @@ import android.graphics.drawable.Drawable; import android.view.animation.DecelerateInterpolator; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; public class BackDrawable extends Drawable { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/BaseFragment.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/BaseFragment.java similarity index 93% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/BaseFragment.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/BaseFragment.java index 8e367ac..a307f93 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/BaseFragment.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/BaseFragment.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Actionbar; +package com.tangxiaolv.telegramgallery.actionbar; import android.animation.AnimatorSet; import android.app.Activity; @@ -7,10 +7,15 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import com.tangxiaolv.telegramgallery.R; import com.tangxiaolv.telegramgallery.Theme; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; + +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; public class BaseFragment { @@ -116,8 +121,12 @@ protected void setParentLayout(ActionBarLayout layout) { protected ActionBar createActionBar(Context context) { ActionBar actionBar = new ActionBar(context); - actionBar.setBackgroundColor(Theme.ACTION_BAR_COLOR); + actionBar.setBackgroundColor(DARK_THEME ? Theme.ACTION_BAR_COLOR : 0xfff9f9f9); actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_SELECTOR_COLOR); + View view = new View(context); + view.setBackgroundColor(context.getResources().getColor(R.color.divider)); + actionBar.addView(view, LayoutHelper.createFrame( + LayoutHelper.MATCH_PARENT, 1, Gravity.BOTTOM)); return actionBar; } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/MenuDrawable.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/MenuDrawable.java similarity index 97% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/MenuDrawable.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/MenuDrawable.java index 60de9c7..01ef89c 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/MenuDrawable.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/MenuDrawable.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Actionbar; +package com.tangxiaolv.telegramgallery.actionbar; import android.graphics.Canvas; import android.graphics.ColorFilter; @@ -7,7 +7,7 @@ import android.graphics.drawable.Drawable; import android.view.animation.DecelerateInterpolator; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; public class MenuDrawable extends Drawable { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/SimpleTextView.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/SimpleTextView.java similarity index 98% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/SimpleTextView.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/SimpleTextView.java index 63b2ac0..b2601b9 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Actionbar/SimpleTextView.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/actionbar/SimpleTextView.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.Actionbar; +package com.tangxiaolv.telegramgallery.actionbar; import android.content.Context; import android.graphics.Canvas; @@ -14,7 +14,7 @@ import android.view.Gravity; import android.view.View; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; public class SimpleTextView extends View implements Drawable.Callback { @@ -119,6 +119,7 @@ private boolean createLayout(int width) { } } catch (Exception e) { // ignore + e.getStackTrace(); } } else { layout = null; diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/ActionBarPopupWindow.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/ActionBarPopupWindow.java similarity index 97% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/ActionBarPopupWindow.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/ActionBarPopupWindow.java index 97fca30..57c2121 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/ActionBarPopupWindow.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/ActionBarPopupWindow.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; import android.animation.Animator; import android.animation.AnimatorSet; @@ -7,6 +7,7 @@ import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Build; +import android.util.ArrayMap; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -18,11 +19,10 @@ import android.widget.ScrollView; import com.tangxiaolv.telegramgallery.R; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; import java.lang.reflect.Field; -import java.util.HashMap; public class ActionBarPopupWindow extends PopupWindow { @@ -37,6 +37,7 @@ public class ActionBarPopupWindow extends PopupWindow { f.setAccessible(true); } catch (NoSuchFieldException e) { /* ignored */ + e.getStackTrace(); } superListenerField = f; } @@ -64,7 +65,7 @@ public static class ActionBarPopupWindowLayout extends FrameLayout { private int backAlpha = 255; private int lastStartedChild = 0; private boolean showedFromBotton; - private HashMap positions = new HashMap<>(); + private ArrayMap positions = new ArrayMap<>(); private ScrollView scrollView; private LinearLayout linearLayout; @@ -73,7 +74,7 @@ public ActionBarPopupWindowLayout(Context context) { super(context); if (backgroundDrawable == null) { - backgroundDrawable = getResources().getDrawable(R.drawable.popup_fixed); + backgroundDrawable = getResources().getDrawable(R.drawable.album_popup_fixed); } setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8)); @@ -399,6 +400,7 @@ public void onAnimationEnd(Animator animation) { ActionBarPopupWindow.super.dismiss(); } catch (Exception e) { //don't promt + e.getStackTrace(); } unregisterListener(); } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/AspectRatioFrameLayout.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/AspectRatioFrameLayout.java similarity index 98% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/AspectRatioFrameLayout.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/AspectRatioFrameLayout.java index c644ccd..76a8b6e 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/AspectRatioFrameLayout.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/AspectRatioFrameLayout.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; import android.content.Context; import android.util.AttributeSet; diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/BackupImageView.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/BackupImageView.java similarity index 91% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/BackupImageView.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/BackupImageView.java index a938825..50e179a 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/BackupImageView.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/BackupImageView.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; import android.content.Context; import android.graphics.Bitmap; @@ -9,8 +9,8 @@ import android.view.View; import com.tangxiaolv.telegramgallery.ImageReceiver; -import com.tangxiaolv.telegramgallery.TL.FileLocation; -import com.tangxiaolv.telegramgallery.TL.TLObject; +import com.tangxiaolv.telegramgallery.tl.FileLocation; +import com.tangxiaolv.telegramgallery.tl.TLObject; public class BackupImageView extends View { @@ -70,13 +70,12 @@ public void setOrientation(int angle, boolean center) { } public void setImage(TLObject path, String httpUrl, String filter, Drawable thumb, - Bitmap thumbBitmap, FileLocation thumbLocation, String thumbFilter, String ext, - int size) { + Bitmap thumbBitmap, FileLocation thumbLocation, String thumbFilter, String ext, + int size) { if (thumbBitmap != null) { thumb = new BitmapDrawable(null, thumbBitmap); } - imageReceiver.setImage(path, httpUrl, filter, thumb, thumbLocation, thumbFilter, size, ext, - false); + imageReceiver.setImage(path, httpUrl, filter, thumb, thumbLocation, thumbFilter, size, ext, 0); } public void setImageBitmap(Bitmap bitmap) { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/CheckBox.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/CheckBox.java similarity index 88% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/CheckBox.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/CheckBox.java index 64a0422..ff771ff 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/CheckBox.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/CheckBox.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; import android.animation.ObjectAnimator; import android.content.Context; @@ -7,9 +7,10 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.BitmapDrawable; import android.view.View; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; public class CheckBox extends View { @@ -40,7 +41,11 @@ public class CheckBox extends View { private int color = 0xff5ec245; private final static float progressBounceDiff = 0.2f; - private int number; + private int number = -1; + private boolean actionBarStyle; + private boolean bottomBarStyle; + + public static int sCheckColor = 0xff007aff; public CheckBox(Context context, int resId) { super(context); @@ -63,7 +68,7 @@ public CheckBox(Context context, int resId) { backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); backgroundPaint.setColor(0xffffffff); backgroundPaint.setStyle(Paint.Style.STROKE); - backgroundPaint.setStrokeWidth(AndroidUtilities.dp(2)); + backgroundPaint.setStrokeWidth(AndroidUtilities.dp(1)); } // checkDrawable = context.getResources().getDrawable(resId); @@ -72,7 +77,7 @@ public CheckBox(Context context, int resId) { @Override public void setVisibility(int visibility) { super.setVisibility(visibility); - if (visibility == VISIBLE && drawBitmap == null) { + if (drawBitmap == null) { drawBitmap = Bitmap.createBitmap(AndroidUtilities.dp(size), AndroidUtilities.dp(size), Bitmap.Config.ARGB_4444); bitmapCanvas = new Canvas(drawBitmap); @@ -141,7 +146,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto } public void setChecked(boolean checked, boolean animated) { - if (checked == isChecked) { + if (checked == isChecked && number == -1 && !bottomBarStyle) { return; } isChecked = checked; @@ -156,17 +161,7 @@ public void setChecked(boolean checked, boolean animated) { public void setChecked(int num, boolean checked, boolean animated) { number = num; - if (checked == isChecked && num == -1) { - return; - } - isChecked = checked; - - if (attachedToWindow && animated) { - animateToCheckedState(checked); - } else { - cancelCheckAnimator(); - setProgress(checked ? 1.0f : 0.0f); - } + setChecked(checked, animated); } public boolean isChecked() { @@ -195,7 +190,8 @@ protected void onDraw(Canvas canvas) { * (roundProgressCheckState - progressBounceDiff) / progressBounceDiff; } if (drawBackground) { - paint.setColor(0x44000000); + paint.setColor(actionBarStyle ? 0xfff9f9f9 : 0x44000000); + backgroundPaint.setColor(actionBarStyle ? 0xff9b9b9b : 0xffffffff); canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad - AndroidUtilities.dp(1), paint); canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, @@ -204,7 +200,8 @@ protected void onDraw(Canvas canvas) { paint.setColor(color); - bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad, paint); + bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, + rad - (bottomBarStyle ? AndroidUtilities.dp(4) : 1), paint); bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - roundProgress), eraser); canvas.drawBitmap(drawBitmap, 0, 0, null); @@ -239,4 +236,12 @@ protected void onDraw(Canvas canvas) { } } } + + public void setActionBarStyle(boolean actionBarStyle) { + this.actionBarStyle = actionBarStyle; + } + + public void setBottomBarStyle(boolean bottomBarStyle) { + this.bottomBarStyle = bottomBarStyle; + } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/ClippingImageView.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/ClippingImageView.java similarity index 99% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/ClippingImageView.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/ClippingImageView.java index ee6cacd..270b77c 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/ClippingImageView.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/ClippingImageView.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; import android.content.Context; import android.graphics.Bitmap; diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerAlbumsCell.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerAlbumsCell.java similarity index 87% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerAlbumsCell.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerAlbumsCell.java index 7fde6b8..ada7bf4 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerAlbumsCell.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerAlbumsCell.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; import android.content.Context; import android.os.Build; @@ -12,10 +12,14 @@ import android.widget.LinearLayout; import android.widget.TextView; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; -import com.tangxiaolv.telegramgallery.Utils.MediaController; import com.tangxiaolv.telegramgallery.R; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.MediaController; + +import java.util.Locale; + +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; public class PhotoPickerAlbumsCell extends FrameLayout { @@ -69,7 +73,7 @@ public AlbumView(Context context) { LayoutHelper.MATCH_PARENT, 4, 0, 4, 0)); selector = new View(context); - selector.setBackgroundResource(R.drawable.list_selector); + selector.setBackgroundResource(R.drawable.album_list_selector); addView(selector, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); } @@ -126,18 +130,21 @@ public void setAlbum(int a, MediaController.AlbumEntry albumEntry) { albumView.imageView.setImage( "vthumb://" + albumEntry.coverPhoto.imageId + ":" + albumEntry.coverPhoto.path, - null, getContext().getResources().getDrawable(R.drawable.nophotos)); + null, getContext().getResources().getDrawable(DARK_THEME ? + R.drawable.album_nophotos : R.drawable.album_nophotos_new)); } else { albumView.imageView.setImage( "thumb://" + albumEntry.coverPhoto.imageId + ":" + albumEntry.coverPhoto.path, - null, getContext().getResources().getDrawable(R.drawable.nophotos)); + null, getContext().getResources().getDrawable(DARK_THEME ? + R.drawable.album_nophotos : R.drawable.album_nophotos_new)); } } else { - albumView.imageView.setImageResource(R.drawable.nophotos); + albumView.imageView.setImageResource(DARK_THEME ? + R.drawable.album_nophotos : R.drawable.album_nophotos_new); } albumView.nameTextView.setText(albumEntry.bucketName); - albumView.countTextView.setText(String.format("%d", albumEntry.photos.size())); + albumView.countTextView.setText(String.format(Locale.CHINA, "%d", albumEntry.photos.size())); } else { albumViews[a].setVisibility(INVISIBLE); } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerPhotoCell.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerPhotoCell.java similarity index 54% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerPhotoCell.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerPhotoCell.java index c3c3f89..13dc645 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerPhotoCell.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerPhotoCell.java @@ -1,27 +1,39 @@ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; + import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; +import android.util.TypedValue; import android.view.Gravity; +import android.view.View; import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; import com.tangxiaolv.telegramgallery.AnimatorListenerAdapterProxy; import com.tangxiaolv.telegramgallery.R; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; -import static com.tangxiaolv.telegramgallery.PhotoAlbumPickerActivity.DarkTheme; +import static com.tangxiaolv.telegramgallery.components.CheckBox.sCheckColor; +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; public class PhotoPickerPhotoCell extends FrameLayout { public BackupImageView photoImage; + public FrameLayout infoContainer; + public TextView videoTextView; + //选中的圆圈 public FrameLayout checkFrame; public CheckBox checkBox; private AnimatorSet animator; public int itemWidth; + private Actions actions; + private ImageView imageInfo; + public View clickableView; public PhotoPickerPhotoCell(Context context) { super(context); @@ -33,13 +45,32 @@ public PhotoPickerPhotoCell(Context context) { checkFrame = new FrameLayout(context); addView(checkFrame, LayoutHelper.createFrame(42, 42, Gravity.RIGHT | Gravity.TOP)); - checkBox = new CheckBox(context, R.drawable.checkbig); + infoContainer = new FrameLayout(context); + infoContainer.setBackgroundColor(0x7f000000); + infoContainer.setPadding(AndroidUtilities.dp(3), 0, AndroidUtilities.dp(3), 0); + addView(infoContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 16, Gravity.BOTTOM | Gravity.LEFT)); + + imageInfo = new ImageView(context); + infoContainer.addView(imageInfo, LayoutHelper.createFrame(14, 9, Gravity.LEFT | Gravity.CENTER_VERTICAL)); + + videoTextView = new TextView(context); + videoTextView.setTextColor(0xffffffff); + videoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + infoContainer.addView(videoTextView, LayoutHelper.createFrame( + LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, + 18, 0, 0, 0)); + + checkBox = new CheckBox(context, R.drawable.album_checkbig); checkBox.setSize(24); checkBox.setCheckOffset(AndroidUtilities.dp(1)); checkBox.setDrawBackground(true); - checkBox.setColor(0xff007aff); - addView(checkBox, - LayoutHelper.createFrame(24, 24, Gravity.RIGHT | Gravity.TOP, 0, 4, 4, 0)); + checkBox.setColor(sCheckColor); + addView(checkBox, LayoutHelper.createFrame(24, 24, Gravity.RIGHT | Gravity.TOP, 0, 4, 4, 0)); + + /*置灰*/ + /*clickableView = new View(context); + clickableView.setBackgroundResource(R.drawable.preview_cell_canceled); + addView(clickableView,LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT,LayoutHelper.MATCH_PARENT));*/ } @Override @@ -56,12 +87,14 @@ public void setChecked(final boolean checked, final boolean animated) { } if (animated) { if (checked) { - setBackgroundColor(DarkTheme ? 0xff0A0A0A : 0xffffffff); + setBackgroundColor(DARK_THEME ? 0xff0A0A0A : 0xffffffff); } animator = new AnimatorSet(); animator.playTogether( ObjectAnimator.ofFloat(photoImage, "scaleX", checked ? 0.85f : 1.0f), ObjectAnimator.ofFloat(photoImage, "scaleY", checked ? 0.85f : 1.0f)); +// ObjectAnimator.ofFloat(infoContainer, "scaleX", checked ? 0.85f : 1.0f), +// ObjectAnimator.ofFloat(infoContainer, "scaleY", checked ? 0.85f : 1.0f)); animator.setDuration(200); animator.addListener(new AnimatorListenerAdapterProxy() { @Override @@ -83,13 +116,18 @@ public void onAnimationCancel(Animator animation) { }); animator.start(); } else { - setBackgroundColor(checked ? DarkTheme ? 0xff0A0A0A : 0xffffffff : 0); + setBackgroundColor(checked ? DARK_THEME ? 0xff0A0A0A : 0xffffffff : 0); photoImage.setScaleX(checked ? 0.85f : 1.0f); photoImage.setScaleY(checked ? 0.85f : 1.0f); + infoContainer.setScaleX(checked ? 0.85f : 1.0f); + infoContainer.setScaleY(checked ? 0.85f : 1.0f); } } public void setChecked(int num, final boolean checked, final boolean animated) { + if (!checkBox.isEnabled()) { + return; + } checkBox.setChecked(num, checked, animated); if (animator != null) { animator.cancel(); @@ -97,12 +135,14 @@ public void setChecked(int num, final boolean checked, final boolean animated) { } if (animated) { if (checked) { - setBackgroundColor(DarkTheme ? 0xff0A0A0A : 0xffffffff); + setBackgroundColor(DARK_THEME ? 0xff0A0A0A : 0xffffffff); } animator = new AnimatorSet(); animator.playTogether( ObjectAnimator.ofFloat(photoImage, "scaleX", checked ? 0.85f : 1.0f), ObjectAnimator.ofFloat(photoImage, "scaleY", checked ? 0.85f : 1.0f)); +// ObjectAnimator.ofFloat(infoContainer, "scaleX", checked ? 0.85f : 1.0f), +// ObjectAnimator.ofFloat(infoContainer, "scaleY", checked ? 0.85f : 1.0f)); animator.setDuration(200); animator.addListener(new AnimatorListenerAdapterProxy() { @Override @@ -113,6 +153,10 @@ public void onAnimationEnd(Animator animation) { setBackgroundColor(0); } } + + if (actions != null) { + actions.onUnCheckedAnimationEnd(); + } } @Override @@ -124,9 +168,31 @@ public void onAnimationCancel(Animator animation) { }); animator.start(); } else { - setBackgroundColor(checked ? DarkTheme ? 0xff0A0A0A : 0xffffffff : 0); + setBackgroundColor(checked ? DARK_THEME ? 0xff0A0A0A : 0xffffffff : 0); photoImage.setScaleX(checked ? 0.85f : 1.0f); photoImage.setScaleY(checked ? 0.85f : 1.0f); +// infoContainer.setScaleX(checked ? 0.85f : 1.0f); +// infoContainer.setScaleY(checked ? 0.85f : 1.0f); } } + + public void showVideoInfo() { + infoContainer.setVisibility(VISIBLE); + videoTextView.setVisibility(VISIBLE); + imageInfo.setImageResource(R.drawable.ic_video); + } + + public void showGifInfo() { + infoContainer.setVisibility(VISIBLE); + videoTextView.setVisibility(INVISIBLE); + imageInfo.setImageResource(R.drawable.ic_gif); + } + + public void setActions(Actions actions) { + this.actions = actions; + } + + public interface Actions { + void onUnCheckedAnimationEnd(); + } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerSearchCell.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerSearchCell.java similarity index 96% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerSearchCell.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerSearchCell.java index 48c6ffe..4fa48b8 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/PhotoPickerSearchCell.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PhotoPickerSearchCell.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; import android.content.Context; import android.os.Build; @@ -12,9 +12,9 @@ import android.widget.LinearLayout; import android.widget.TextView; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; -import com.tangxiaolv.telegramgallery.Utils.LayoutHelper; import com.tangxiaolv.telegramgallery.R; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; public class PhotoPickerSearchCell extends LinearLayout { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PickerBottomLayout.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PickerBottomLayout.java new file mode 100644 index 0000000..751cba8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PickerBottomLayout.java @@ -0,0 +1,196 @@ + +package com.tangxiaolv.telegramgallery.components; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.support.annotation.ColorInt; +import android.support.annotation.ColorRes; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.tangxiaolv.telegramgallery.R; +import com.tangxiaolv.telegramgallery.Theme; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.LocaleController; + +import java.util.Locale; + +import static com.tangxiaolv.telegramgallery.Gallery.sOriginChecked; +import static com.tangxiaolv.telegramgallery.GalleryActivity.getConfig; +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; + +public class PickerBottomLayout extends FrameLayout { + + public LinearLayout doneButton; + public CheckBox originCheckBox; + public TextView cancelButton; + public TextView doneButtonTextView; + public TextView originalTextView; + public LinearLayout originalView; + + //定制化参数 + private int previewTextColor; + private final Drawable doneNormal; + private final Drawable doneDisable; + private int doneTextColor; + + public PickerBottomLayout(Context context) { + super(context); + + //预览定制 + previewTextColor = 0xff007aff; + + //发送定制 + doneNormal = getResources().getDrawable(R.drawable.shape_send); + doneDisable = getResources().getDrawable(R.drawable.shape_unsend); + doneNormal.setColorFilter(new PorterDuffColorFilter(0xff3395ff, PorterDuff.Mode.SRC_IN)); + doneDisable.setColorFilter(new PorterDuffColorFilter(0xff92c3f9, PorterDuff.Mode.SRC_IN)); + doneTextColor = 0xffffffff; + + // + //初始化控件 + // + + cancelButton = new TextView(context); + cancelButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + cancelButton.setTextColor(DARK_THEME ? 0xffffffff : 0xff9b9b9b); + cancelButton.setGravity(Gravity.CENTER); + cancelButton.setBackgroundDrawable( + Theme.createBarSelectorDrawable(DARK_THEME ? + Theme.ACTION_BAR_PICKER_SELECTOR_COLOR + : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false) + ); + cancelButton.setPadding(AndroidUtilities.dp(9), 0, AndroidUtilities.dp(29), 0); + cancelButton.setText(LocaleController.getString("Preview", R.string.Preview).toUpperCase()); + // cancelButton.getPaint().setFakeBoldText(true); + addView(cancelButton, LayoutHelper.createFrame( + LayoutHelper.WRAP_CONTENT, + LayoutHelper.MATCH_PARENT, + Gravity.TOP | Gravity.LEFT)); + + doneButton = new LinearLayout(context); + doneButton.setOrientation(LinearLayout.HORIZONTAL); + doneButton.setBackgroundDrawable( + Theme.createBarSelectorDrawable( + DARK_THEME ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, + false)); + addView(doneButton, LayoutHelper.createFrame( + LayoutHelper.WRAP_CONTENT, + LayoutHelper.MATCH_PARENT, + Gravity.TOP | Gravity.RIGHT)); + + doneButtonTextView = new TextView(context); + doneButtonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + doneButtonTextView.setBackground(doneDisable); + //doneButtonTextView.setTextColor(DARK_THEME ? 0xffffffff : 0xff9b9b9b); + doneButtonTextView.setTextColor(doneTextColor); + doneButtonTextView.setGravity(Gravity.CENTER); + doneButtonTextView.setCompoundDrawablePadding(AndroidUtilities.dp(8)); + doneButtonTextView.setMinWidth(AndroidUtilities.dp(60)); + doneButtonTextView.setMaxHeight(AndroidUtilities.dp(30)); + doneButtonTextView.setText(LocaleController.getString("Send", R.string.Send).toUpperCase()); + // doneButtonTextView.getPaint().setFakeBoldText(true); + doneButton.addView(doneButtonTextView, + LayoutHelper.createLinear( + LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, 0, 7, 9, 7)); + + originalView = new LinearLayout(context); + originalView.setOrientation(LinearLayout.HORIZONTAL); + originalView.setBackgroundDrawable( + Theme.createBarSelectorDrawable( + DARK_THEME ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, + false)); + addView(originalView, LayoutHelper.createFrame( + LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + originCheckBox = new CheckBox(context, R.drawable.album_checkbig); + originCheckBox.setCheckOffset(AndroidUtilities.dp(1)); + originCheckBox.setDrawBackground(true); + originCheckBox.setBottomBarStyle(true); + originCheckBox.setActionBarStyle(true); + originCheckBox.setVisibility(VISIBLE); + originCheckBox.setChecked(sOriginChecked, false); + originCheckBox.setColor(0xff007aff); + originalView.addView(originCheckBox, + LayoutHelper.createLinear(20, 20, Gravity.CENTER_VERTICAL, 0, 0, 10, 0)); + originalView.setVisibility(getConfig().hasOriginalPic() ? VISIBLE : GONE); + + originalTextView = new TextView(context); + originalTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + originalTextView.setTextColor(DARK_THEME ? 0xffffffff : 0xff000000); + originalTextView.setGravity(Gravity.CENTER); + originalTextView.setCompoundDrawablePadding(AndroidUtilities.dp(8)); + originalTextView.setText(LocaleController.getString("Original", R.string.Original).toUpperCase()); + originalView.addView(originalTextView, + LayoutHelper.createLinear( + LayoutHelper.WRAP_CONTENT, + LayoutHelper.WRAP_CONTENT, + Gravity.CENTER_VERTICAL)); + } + + public void updateSelectedCount(int count, boolean disable) { + if (count == 0) { + doneButtonTextView.setText(R.string.Send); + if (disable) { + doneButtonTextView.setBackground(doneDisable); + cancelButton.setTextColor(0xff9b9b9b); + doneButton.setEnabled(false); + cancelButton.setEnabled(false); + } else { + doneButtonTextView.setBackground( + getConfig().getLimitPickPhoto() == 1 ? doneNormal : doneDisable); + } + } else { + doneButtonTextView.setBackground(doneNormal); + doneButtonTextView.setText( + String.format(Locale.getDefault(), getContext().getString(R.string.SendWithNum), count) + ); + cancelButton.setTextColor(DARK_THEME ? 0xffffffff : previewTextColor); + originalTextView.setTextColor(DARK_THEME ? 0xffffffff : 0xff000000); + if (disable) { + doneButton.setEnabled(true); + cancelButton.setEnabled(true); + } + } + } + + public void setChecked(final boolean checked, final boolean animated) { + sOriginChecked = checked; + originCheckBox.setChecked(checked, true); + } + + public boolean isOriginChecked() { + return sOriginChecked; + } + + public void setOriginalViewVisibility(int visibility) { + if (!getConfig().hasOriginalPic()) { + return; + } + originalView.setVisibility(visibility); + } + + public void setEditState(boolean show, boolean forceSend) { + cancelButton.setVisibility(getConfig().isVideoEditMode() ? show ? VISIBLE : GONE : GONE); + if (getConfig().isVideoEditMode()) { + cancelButton.setEnabled(show); + cancelButton.setTextColor(DARK_THEME ? 0xffffffff : previewTextColor); + doneButtonTextView.setBackground(forceSend ? doneNormal : doneButtonTextView.getBackground()); + } + } + + public void setDoneButtonText(String text) { + doneButtonTextView.setText(text); + } + + @ColorInt + private int getColor(@ColorRes int id, @ColorInt int def) { + return id == 0 ? def : getResources().getColor(id); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PreviewIconCell.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PreviewIconCell.java new file mode 100644 index 0000000..0277908 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PreviewIconCell.java @@ -0,0 +1,129 @@ +package com.tangxiaolv.telegramgallery.components; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.tangxiaolv.telegramgallery.R; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.MediaController; + +import static com.tangxiaolv.telegramgallery.utils.Constants.DARK_THEME; + +public class PreviewIconCell extends FrameLayout implements View.OnClickListener { + + private BackupImageView imageView; + private View frameChecked; + private View frameCanceled; + private PreviewIconCellContainer parent; + private MediaController.PhotoEntry photoEntry; + private boolean canceled = false; + + public PreviewIconCell( + Context context, + PreviewIconCellContainer parent, + MediaController.PhotoEntry photoEntry) { + this(context); + this.parent = parent; + this.photoEntry = photoEntry; + imageView = new BackupImageView(context); + addView(imageView, LayoutHelper.createFrame(60, 60, Gravity.CENTER_VERTICAL, 12, 0, 0, 0)); + + if (photoEntry.thumbPath != null) { + imageView.setImage(photoEntry.thumbPath, null, + context.getResources().getDrawable(DARK_THEME + ? R.drawable.album_nophotos + : R.drawable.album_nophotos_new)); + } else if (photoEntry.path != null) { + imageView.setOrientation(photoEntry.orientation, true); + if (photoEntry.isVideo) { + imageView.setImage( + "vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, + context.getResources().getDrawable( + DARK_THEME ? R.drawable.album_nophotos : R.drawable.album_nophotos_new)); + } else { + imageView.setImage( + "thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, + context.getResources().getDrawable( + DARK_THEME ? R.drawable.album_nophotos : R.drawable.album_nophotos_new)); + } + } else { + imageView.setImageResource( + DARK_THEME ? R.drawable.album_nophotos : R.drawable.album_nophotos_new); + } + + FrameLayout infoContainer = new FrameLayout(context); + infoContainer.setBackgroundColor(0x7f000000); + infoContainer.setPadding(AndroidUtilities.dp(3), 0, AndroidUtilities.dp(3), 0); + addView(infoContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 16, + Gravity.BOTTOM | Gravity.START, 12, 0, 0, 14)); + + ImageView imageInfo = new ImageView(context); + infoContainer.addView( + imageInfo, LayoutHelper.createFrame(14, 9, Gravity.LEFT | Gravity.CENTER_VERTICAL)); + + if (photoEntry.isVideo) { + imageInfo.setImageResource(R.drawable.ic_video); + } else if (photoEntry.path.lastIndexOf(".gif") != -1) { + imageInfo.setImageResource(R.drawable.ic_gif); + } else { + infoContainer.setVisibility(GONE); + } + + frameCanceled = new View(context); + frameCanceled.setBackgroundResource(R.drawable.preview_cell_canceled); + addView(frameCanceled, LayoutHelper.createFrame(60, 60, Gravity.CENTER_VERTICAL, 12, 0, 0, 0)); + + frameChecked = new View(context); + frameChecked.setBackgroundResource(R.drawable.preview_cell_checked); + addView(frameChecked, LayoutHelper.createFrame(60, 60, Gravity.CENTER_VERTICAL, 12, 0, 0, 0)); + + frameChecked.setVisibility(GONE); + frameCanceled.setVisibility(GONE); + + setOnClickListener(this); + } + + public PreviewIconCell(Context context) { + this(context, null); + } + + public PreviewIconCell(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PreviewIconCell(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public View getFrameChecked() { + return frameChecked; + } + + public void setChecked(boolean checked) { + frameChecked.setVisibility(checked ? VISIBLE : GONE); + PreviewIconCell child = parent.getCheckedChild(); + if (child != null && child != this) { + child.getFrameChecked().setVisibility(GONE); + } + parent.setCheckedChild(this); + } + + @Override + public void onClick(View v) { + parent.notificationCheckChanged(photoEntry.imageId); + } + + public void setCanceled(boolean canceled) { + this.canceled = canceled; + frameCanceled.setVisibility(canceled ? VISIBLE : GONE); + } + + public int getCellId() { + return photoEntry.imageId; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PreviewIconCellContainer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PreviewIconCellContainer.java new file mode 100644 index 0000000..0e61de5 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/PreviewIconCellContainer.java @@ -0,0 +1,207 @@ +package com.tangxiaolv.telegramgallery.components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; + +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.MediaController; + +import java.util.ArrayList; +import java.util.List; + +public class PreviewIconCellContainer extends HorizontalScrollView { + + private PreviewIconCell checkedChild; + private LinearLayout innerContainer; + private List datas; + private boolean fromPreview; + private final int CELL_WIDTH = 72; + private PreviewIconCellContainerActions previewIconCellContainerActions; + private AnimatorSet animatorSet; + private boolean showing; + + public PreviewIconCellContainer(Context context) { + this(context, null); + } + + public PreviewIconCellContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PreviewIconCellContainer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + innerContainer = new LinearLayout(context); + innerContainer.setOrientation(LinearLayout.HORIZONTAL); + /*LayoutTransition transition = new LayoutTransition(); + PropertyValuesHolder pvhLeft = + PropertyValuesHolder.ofInt("left", 0, CELL_WIDTH,0); + PropertyValuesHolder pvhTop = + PropertyValuesHolder.ofInt("top", 0, 0); + PropertyValuesHolder pvhRight = + PropertyValuesHolder.ofInt("right", 0, 0); + PropertyValuesHolder pvhBottom = + PropertyValuesHolder.ofInt("bottom", 0, 0); + transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, + ObjectAnimator.ofPropertyValuesHolder(innerContainer, pvhLeft, pvhBottom).setDuration(50)); + innerContainer.setLayoutTransition(transition);*/ + addView(innerContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + } + + public void generatePreviewIconCell(List photos, boolean fromPreview) { + //reset + this.datas = null; + this.fromPreview = fromPreview; + innerContainer.removeAllViews(); + if (photos == null || photos.size() == 0) { + setVisibility(View.GONE); + return; + } + datas = photos; + setVisibility(View.VISIBLE); + for (int i = 0; i < photos.size(); i++) { + PreviewIconCell cell = new PreviewIconCell(getContext(), this, + (MediaController.PhotoEntry) photos.get(i)); + innerContainer.addView(cell, LayoutHelper.createFrame(CELL_WIDTH, LayoutHelper.MATCH_PARENT)); + } + } + + public void setCheckedChild(PreviewIconCell checkedChild) { + this.checkedChild = checkedChild; + } + + public PreviewIconCell getCheckedChild() { + return checkedChild; + } + + public void requestCheckedChanged(int imageId) { + if (datas == null) { + return; + } + PreviewIconCell cell = findCellByImageId(imageId); + moveToIndex(cell); + } + + private void moveToIndex(PreviewIconCell cell) { + if (cell != null) { + cell.setChecked(true); + //scroll + int[] location = new int[2]; + cell.getLocationOnScreen(location); + int width = cell.getWidth(); + if (location[0] == getWidth()) { + smoothScrollBy(width, 0); + } else if (location[0] + width > getWidth()) { + int diff = location[0] + width - getWidth(); + smoothScrollBy(diff, 0); + } else if (location[0] < 0) { + smoothScrollBy(location[0], 0); + } + } else if (checkedChild != null) { + checkedChild.setChecked(false); + } + } + + public void requestCanceledChanged(MediaController.PhotoEntry entry, boolean canceled) { + if (fromPreview) { + PreviewIconCell cell = findCellByImageId(entry.imageId); + if (cell != null) { + cell.setCanceled(canceled); + } + } else if (canceled) { + int index = datas.indexOf(entry); + innerContainer.removeViewAt(index); + datas.remove(entry); + if (datas.size() == 0) { + //setVisibility(GONE); + startAnimation(false); + } + } else { + if (datas == null) { + datas = new ArrayList<>(); + } + if (getVisibility() != VISIBLE) { + //setVisibility(VISIBLE); + startAnimation(true); + } + datas.add(entry); + final PreviewIconCell cell = new PreviewIconCell(getContext(), this, entry); + innerContainer.addView(cell, LayoutHelper.createFrame(CELL_WIDTH, LayoutHelper.MATCH_PARENT)); + post(new Runnable() { + @Override + public void run() { + moveToIndex(cell); + } + }); + } + } + + private PreviewIconCell findCellByImageId(int imageId) { + if (datas == null || datas.size() == 0) { + return null; + } + + for (int i = 0; i < datas.size(); i++) { + PreviewIconCell cell = (PreviewIconCell) ((LinearLayout) getChildAt(0)).getChildAt(i); + if (cell.getCellId() == imageId) { + return cell; + } + } + + return null; + } + + public void setPreviewIconCellContainerActions(PreviewIconCellContainerActions actions) { + this.previewIconCellContainerActions = actions; + } + + public void notificationCheckChanged(int imageId) { + if (previewIconCellContainerActions != null) { + previewIconCellContainerActions.checkChanged(imageId); + } + + } + + public interface PreviewIconCellContainerActions { + void checkChanged(int imageId); + } + + private void startAnimation(final boolean show) { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + animatorSet = new AnimatorSet(); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.setDuration(180); + if (show && getVisibility() == View.GONE) { + setVisibility(View.VISIBLE); + } + animatorSet.playTogether( + ObjectAnimator.ofFloat(this, "alpha", show ? 1.0f : 0.0f)); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(animatorSet)) { + animatorSet = null; + } + showing = show; + if (!show) { + setVisibility(View.GONE); + } + } + }); + animatorSet.start(); + } + + public boolean isShowing() { + return showing; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/SizeNotifierFrameLayoutPhoto.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/SizeNotifierFrameLayoutPhoto.java similarity index 94% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/SizeNotifierFrameLayoutPhoto.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/SizeNotifierFrameLayoutPhoto.java index 4200e98..0feff57 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Components/SizeNotifierFrameLayoutPhoto.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/SizeNotifierFrameLayoutPhoto.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Components; +package com.tangxiaolv.telegramgallery.components; import android.content.Context; import android.graphics.Rect; @@ -6,7 +6,7 @@ import android.view.WindowManager; import android.widget.FrameLayout; -import com.tangxiaolv.telegramgallery.Utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; public class SizeNotifierFrameLayoutPhoto extends FrameLayout { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoPlayer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoPlayer.java new file mode 100644 index 0000000..ae6c683 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoPlayer.java @@ -0,0 +1,324 @@ +package com.tangxiaolv.telegramgallery.components; + +import android.annotation.SuppressLint; +import android.graphics.SurfaceTexture; +import android.net.Uri; +import android.os.Handler; +import android.view.TextureView; + +import com.tangxiaolv.telegramgallery.exoplayer2.DefaultLoadControl; +import com.tangxiaolv.telegramgallery.exoplayer2.DefaultRenderersFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayerFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.PlaybackParameters; +import com.tangxiaolv.telegramgallery.exoplayer2.SimpleExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.source.ExtractorMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.LoopingMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MergingMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.DashMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.DefaultDashChunkSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.HlsMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.SsMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.AdaptiveTrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.DefaultTrackSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.MappingTrackSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectionArray; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DefaultBandwidthMeter; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.tangxiaolv.telegramgallery.Gallery; +import com.tangxiaolv.telegramgallery.secretmedia.ExtendedDefaultDataSourceFactory; + +@SuppressLint("NewApi") +public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.VideoListener { + + public interface RendererBuilder { + void buildRenderers(VideoPlayer player); + void cancel(); + } + + public interface VideoPlayerDelegate { + void onStateChanged(boolean playWhenReady, int playbackState); + void onError(Exception e); + void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio); + void onRenderedFirstFrame(); + void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture); + boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture); + } + + private SimpleExoPlayer player; + private MappingTrackSelector trackSelector; + private Handler mainHandler; + private DataSource.Factory mediaDataSourceFactory; + private TextureView textureView; + private boolean autoplay; + + private VideoPlayerDelegate delegate; + private int lastReportedPlaybackState; + private boolean lastReportedPlayWhenReady; + + private static final int RENDERER_BUILDING_STATE_IDLE = 1; + private static final int RENDERER_BUILDING_STATE_BUILDING = 2; + private static final int RENDERER_BUILDING_STATE_BUILT = 3; + + private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); + + public VideoPlayer() { + mediaDataSourceFactory = new ExtendedDefaultDataSourceFactory(Gallery.applicationContext, BANDWIDTH_METER, new DefaultHttpDataSourceFactory("Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)", BANDWIDTH_METER)); + + mainHandler = new Handler(); + + TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); + trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); + + lastReportedPlaybackState = ExoPlayer.STATE_IDLE; + } + + private void ensurePleyaerCreated() { + if (player == null) { + player = ExoPlayerFactory.newSimpleInstance(Gallery.applicationContext, trackSelector, new DefaultLoadControl(), null, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); + player.addListener(this); + player.setVideoListener(this); + player.setVideoTextureView(textureView); + player.setPlayWhenReady(autoplay); + } + } + + public void preparePlayerLoop(Uri videoUri, String videoType, Uri audioUri, String audioType) { + ensurePleyaerCreated(); + MediaSource mediaSource1 = null, mediaSource2 = null; + for (int a = 0; a < 2; a++) { + MediaSource mediaSource; + String type; + Uri uri; + if (a == 0) { + type = videoType; + uri = videoUri; + } else { + type = audioType; + uri = audioUri; + } + switch (type) { + case "dash": + mediaSource = new DashMediaSource(uri, mediaDataSourceFactory, new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); + break; + case "hls": + mediaSource = new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null); + break; + case "ss": + mediaSource = new SsMediaSource(uri, mediaDataSourceFactory, new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); + break; + default: + mediaSource = new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), mainHandler, null); + break; + } + mediaSource = new LoopingMediaSource(mediaSource); + if (a == 0) { + mediaSource1 = mediaSource; + } else { + mediaSource2 = mediaSource; + } + } + MediaSource mediaSource = new MergingMediaSource(mediaSource1, mediaSource2); + player.prepare(mediaSource1, true, true); + } + + public void preparePlayer(Uri uri, String type) { + ensurePleyaerCreated(); + MediaSource mediaSource; + switch (type) { + case "dash": + mediaSource = new DashMediaSource(uri, mediaDataSourceFactory, new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); + break; + case "hls": + mediaSource = new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null); + break; + case "ss": + mediaSource = new SsMediaSource(uri, mediaDataSourceFactory, new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); + break; + default: + mediaSource = new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), mainHandler, null); + break; + } + player.prepare(mediaSource, true, true); + } + + public boolean isPlayerPrepared() { + return player != null; + } + + public void releasePlayer() { + if (player != null) { + player.release(); + player = null; + } + } + + public void setTextureView(TextureView texture) { + if (textureView == texture) { + return; + } + textureView = texture; + if (player == null) { + return; + } + player.setVideoTextureView(textureView); + } + + public void play() { + if (player == null) { + return; + } + player.setPlayWhenReady(true); + } + + public void pause() { + if (player == null) { + return; + } + player.setPlayWhenReady(false); + } + + public void setPlayWhenReady(boolean playWhenReady) { + autoplay = playWhenReady; + if (player == null) { + return; + } + player.setPlayWhenReady(playWhenReady); + } + + public long getDuration() { + return player != null ? player.getDuration() : 0; + } + + public long getCurrentPosition() { + return player != null ? player.getCurrentPosition() : 0; + } + + public boolean isMuted() { + return player.getVolume() == 0.0f; + } + + public void setMute(boolean value) { + if (player == null) { + return; + } + if (value) { + player.setVolume(0.0f); + } else { + player.setVolume(1.0f); + } + } + + public void setVolume(float volume) { + if (player == null) { + return; + } + player.setVolume(volume); + } + + public void seekTo(long positionMs) { + if (player == null) { + return; + } + player.seekTo(positionMs); + } + + public void setDelegate(VideoPlayerDelegate videoPlayerDelegate) { + delegate = videoPlayerDelegate; + } + + public int getBufferedPercentage() { + return player != null ? player.getBufferedPercentage() : 0; + } + + public long getBufferedPosition() { + return player != null ? player.getBufferedPosition() : 0; + } + + public boolean isPlaying() { + return player != null && player.getPlayWhenReady(); + } + + public boolean isBuffering() { + return player != null && lastReportedPlaybackState == ExoPlayer.STATE_BUFFERING; + } + + public void setStreamType(int type) { + if (player != null) { + player.setAudioStreamType(type); + } + } + + @Override + public void onLoadingChanged(boolean isLoading) { + + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + maybeReportPlayerState(); + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + delegate.onError(error); + } + + @Override + public void onPositionDiscontinuity() { + + } + + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + delegate.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); + } + + @Override + public void onRenderedFirstFrame() { + delegate.onRenderedFirstFrame(); + } + + @Override + public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { + return delegate.onSurfaceDestroyed(surfaceTexture); + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + delegate.onSurfaceTextureUpdated(surfaceTexture); + } + + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + + } + + private void maybeReportPlayerState() { + boolean playWhenReady = player.getPlayWhenReady(); + int playbackState = player.getPlaybackState(); + if (lastReportedPlayWhenReady != playWhenReady || lastReportedPlaybackState != playbackState) { + delegate.onStateChanged(playWhenReady, playbackState); + lastReportedPlayWhenReady = playWhenReady; + lastReportedPlaybackState = playbackState; + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoPlayerView.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoPlayerView.java new file mode 100644 index 0000000..e5db06d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoPlayerView.java @@ -0,0 +1,574 @@ +package com.tangxiaolv.telegramgallery.components; + +import android.app.Activity; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.net.Uri; +import android.support.annotation.AttrRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.TextureView; +import android.view.View; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.coremedia.iso.IsoFile; +import com.coremedia.iso.boxes.Box; +import com.coremedia.iso.boxes.MediaBox; +import com.coremedia.iso.boxes.MediaHeaderBox; +import com.coremedia.iso.boxes.SampleSizeBox; +import com.coremedia.iso.boxes.TrackBox; +import com.coremedia.iso.boxes.TrackHeaderBox; +import com.googlecode.mp4parser.util.Matrix; +import com.googlecode.mp4parser.util.Path; +import com.tangxiaolv.telegramgallery.R; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.ui.AspectRatioFrameLayout; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; +import com.tangxiaolv.telegramgallery.utils.FileLog; +import com.tangxiaolv.telegramgallery.utils.LayoutHelper; +import com.tangxiaolv.telegramgallery.utils.Utilities; +import com.tangxiaolv.telegramgallery.entity.VideoEditedInfo; + +import java.io.File; +import java.util.List; + +public class VideoPlayerView extends FrameLayout { + private Activity parentActivity; + private ImageView videoPlayButton; + private VideoPlayer videoPlayer; + private TextureView videoTextureView; + private AspectRatioFrameLayout aspectRatioFrameLayout; + private VideoTimelinePlayView videoTimelineView; + private Runnable currentLoadingVideoRunnable; + + private boolean isPlaying; + private boolean muteVideo; + private float videoDuration; + + /*video params*/ + private int compressionsCount = -1; + private int resultWidth; + private int resultHeight; + private int originalWidth; + private int originalHeight; + private int originalBitrate; + private int bitrate; + private int estimatedSize; + private int rotationValue; + + /** + * 0 - 240 + * 1 - 360 + * 2 - 480 + * 3 - 720 + * 4 - 1080 + */ + private int selectedCompression = 1; + + private long videoFramesSize; + private long audioFramesSize; + private long originalSize; + private long startTime; + private long endTime; + private long estimatedDuration; + private long cutDuration; + + private String videoPath; + + public VideoPlayerView(@NonNull Context context) { + this(context, null); + } + + public VideoPlayerView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public VideoPlayerView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + if (!(context instanceof Activity)) return; + parentActivity = (Activity) context; + initView(); + } + + private void initView() { + /*VideoPlayer*/ + FrameLayout playclt = new FrameLayout(getContext()); + playclt.setBackgroundResource(R.drawable.circle_big); + videoPlayButton = new ImageView(getContext()); + videoPlayButton.setBackgroundResource(R.drawable.ic_video_play); + playclt.addView(videoPlayButton, LayoutHelper.createFrame(24, 24, Gravity.CENTER)); + addView(playclt, LayoutHelper.createFrame(64, 64, Gravity.CENTER)); + playclt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (videoPlayer == null) { + return; + } + if (isPlaying) { + videoPlayer.pause(); + } else { + if (videoPlayer.getCurrentPosition() == videoPlayer.getDuration()) { + videoPlayer.seekTo(0); + } + videoPlayer.play(); + } + } + }); + + videoTimelineView = new VideoTimelinePlayView(getContext()); + videoTimelineView.setVisibility(GONE); + videoTimelineView.setDelegate(new VideoTimelinePlayView.VideoTimelineViewDelegate() { + @Override + public void onLeftProgressChanged(float progress) { + if (videoPlayer == null) { + return; + } + if (videoPlayer.isPlaying()) { + videoPlayer.pause(); + } + videoPlayer.seekTo((int) (videoPlayer.getDuration() * progress)); + videoTimelineView.setProgress(0); + updateVideoInfo(); + } + + @Override + public void onRightProgressChanged(float progress) { + if (videoPlayer == null) { + return; + } + if (videoPlayer.isPlaying()) { + videoPlayer.pause(); + } + videoPlayer.seekTo((int) (videoPlayer.getDuration() * progress)); + videoTimelineView.setProgress(0); + updateVideoInfo(); + } + + @Override + public void onPlayProgressChanged(float progress) { + videoPlayer.seekTo((int) (videoPlayer.getDuration() * progress)); + } + + @Override + public void didStartDragging() { + + } + + @Override + public void didStopDragging() { + + } + }); + + addView(videoTimelineView, LayoutHelper.createFrame( + LayoutHelper.MATCH_PARENT, 58, Gravity.BOTTOM, 0, 8, 0, 88)); + } + + private void preparePlayer(File file, boolean playWhenReady) { + releasePlayer(); + if (videoTextureView == null) { + aspectRatioFrameLayout = new AspectRatioFrameLayout(getContext()); + aspectRatioFrameLayout.setVisibility(View.INVISIBLE); + addView(aspectRatioFrameLayout, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + videoTextureView = new TextureView(getContext()); + videoTextureView.setOpaque(false); + aspectRatioFrameLayout.addView(videoTextureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + } + + if (videoPlayer == null) { + videoPlayer = new VideoPlayer(); + videoPlayer.setTextureView(videoTextureView); + videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() { + @Override + public void onStateChanged(boolean playWhenReady, int playbackState) { + if (videoPlayer == null) { + return; + } + if (playbackState != ExoPlayer.STATE_ENDED && playbackState != ExoPlayer.STATE_IDLE) { + try { + parentActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (playbackState == ExoPlayer.STATE_READY && aspectRatioFrameLayout.getVisibility() != View.VISIBLE) { + aspectRatioFrameLayout.setVisibility(View.VISIBLE); + } + if (videoPlayer.isPlaying() && playbackState != ExoPlayer.STATE_ENDED) { + isPlaying = true; + videoPlayButton.setBackgroundResource(R.drawable.ic_video_pause); + AndroidUtilities.runOnUIThread(updateProgressRunnable); + } else if (isPlaying) { + isPlaying = false; + videoPlayButton.setBackgroundResource(R.drawable.ic_video_play); + AndroidUtilities.cancelRunOnUIThread(updateProgressRunnable); + if (playbackState == ExoPlayer.STATE_ENDED) { + videoPlayer.pause(); + } + } + if (playbackState == ExoPlayer.STATE_ENDED) { + videoPlayer.seekTo(0); + } + } + + @Override + public void onError(Exception e) { + e.printStackTrace(); + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + if (aspectRatioFrameLayout != null) { + if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) { + int temp = width; + width = height; + height = temp; + } + aspectRatioFrameLayout.setAspectRatio(height == 0 ? 1 : (width * pixelWidthHeightRatio) / height, unappliedRotationDegrees); + } + } + + @Override + public void onRenderedFirstFrame() { + } + + @Override + public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + + } + }); + } + videoPlayer.preparePlayer(Uri.fromFile(file), "other"); + videoPlayer.setPlayWhenReady(playWhenReady); + } + + private void releasePlayer() { + if (videoPlayer != null) { + videoPlayer.pause(); + videoPlayer.releasePlayer(); + videoPlayer = null; + } + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + e.printStackTrace(); + } + if (aspectRatioFrameLayout != null) { + removeView(aspectRatioFrameLayout); + aspectRatioFrameLayout = null; + } + if (videoTextureView != null) { + videoTextureView = null; + } + + if (isPlaying) { + isPlaying = false; + videoPlayButton.setImageResource(R.drawable.ic_video_play); + AndroidUtilities.cancelRunOnUIThread(updateProgressRunnable); + } + } + + private void processOpenVideo(final String videoPath) { + if (currentLoadingVideoRunnable != null) { + Utilities.globalQueue.cancelRunnable(currentLoadingVideoRunnable); + currentLoadingVideoRunnable = null; + } + muteVideo = false; + compressionsCount = -1; + rotationValue = 0; + File file = new File(videoPath); + originalSize = file.length(); + + Utilities.globalQueue.postRunnable(currentLoadingVideoRunnable = new Runnable() { + @Override + public void run() { + if (currentLoadingVideoRunnable != this) { + return; + } + TrackHeaderBox trackHeaderBox = null; + boolean isAvc = true; + try { + IsoFile isoFile = new IsoFile(videoPath); + List boxes = Path.getPaths(isoFile, "/moov/trak/"); + boolean isMp4A = true; + + Box boxTest = Path.getPath(isoFile, "/moov/trak/mdia/minf/stbl/stsd/mp4a/"); + if (boxTest == null) { + isMp4A = false; + } + + if (!isMp4A) { + return; + } + + boxTest = Path.getPath(isoFile, "/moov/trak/mdia/minf/stbl/stsd/avc1/"); + if (boxTest == null) { + isAvc = false; + } + + for (int b = 0; b < boxes.size(); b++) { + if (currentLoadingVideoRunnable != this) { + return; + } + Box box = boxes.get(b); + TrackBox trackBox = (TrackBox) box; + long sampleSizes = 0; + long trackBitrate = 0; + try { + MediaBox mediaBox = trackBox.getMediaBox(); + MediaHeaderBox mediaHeaderBox = mediaBox.getMediaHeaderBox(); + SampleSizeBox sampleSizeBox = mediaBox.getMediaInformationBox().getSampleTableBox().getSampleSizeBox(); + long[] sizes = sampleSizeBox.getSampleSizes(); + for (int a = 0; a < sizes.length; a++) { + if (currentLoadingVideoRunnable != this) { + return; + } + sampleSizes += sizes[a]; + } + videoDuration = (float) mediaHeaderBox.getDuration() / (float) mediaHeaderBox.getTimescale(); + trackBitrate = (int) (sampleSizes * 8 / videoDuration); + } catch (Exception e) { + FileLog.e(e); + } + if (currentLoadingVideoRunnable != this) { + return; + } + TrackHeaderBox headerBox = trackBox.getTrackHeaderBox(); + if (headerBox.getWidth() != 0 && headerBox.getHeight() != 0) { + trackHeaderBox = headerBox; + originalBitrate = bitrate = (int) (trackBitrate / 100000 * 100000); + if (bitrate > 900000) { + bitrate = 900000; + } + videoFramesSize += sampleSizes; + } else { + audioFramesSize += sampleSizes; + } + } + } catch (Exception e) { + FileLog.e(e); + return; + } + if (trackHeaderBox == null) { + return; + } + final boolean isAvcFinal = isAvc; + final TrackHeaderBox trackHeaderBoxFinal = trackHeaderBox; + if (currentLoadingVideoRunnable != this) { + return; + } + currentLoadingVideoRunnable = null; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + Matrix matrix = trackHeaderBoxFinal.getMatrix(); + if (matrix.equals(Matrix.ROTATE_90)) { + rotationValue = 90; + } else if (matrix.equals(Matrix.ROTATE_180)) { + rotationValue = 180; + } else if (matrix.equals(Matrix.ROTATE_270)) { + rotationValue = 270; + } else { + rotationValue = 0; + } + resultWidth = originalWidth = (int) trackHeaderBoxFinal.getWidth(); + resultHeight = originalHeight = (int) trackHeaderBoxFinal.getHeight(); + + if (!isAvcFinal && (resultWidth == originalWidth || resultHeight == originalHeight)) { + return; + } + + videoDuration *= 1000; + + if (originalWidth > 1280 || originalHeight > 1280) { + compressionsCount = 5; + } else if (originalWidth > 848 || originalHeight > 848) { + compressionsCount = 4; + } else if (originalWidth > 640 || originalHeight > 640) { + compressionsCount = 3; + } else if (originalWidth > 480 || originalHeight > 480) { + compressionsCount = 2; + } else { + compressionsCount = 1; + } + updateWidthHeightBitrateForCompression(); + updateVideoInfo(); + } + }); + } + }); + } + + private void updateVideoInfo() { + estimatedDuration = (long) Math.ceil((videoTimelineView.getRightProgress() + - videoTimelineView.getLeftProgress()) * videoPlayer.getDuration()); + + if (selectedCompression == compressionsCount - 1) { + estimatedSize = (int) (originalSize * ((float) estimatedDuration / videoDuration)); + } else { + estimatedSize = (int) ((audioFramesSize + videoFramesSize) * ((float) estimatedDuration / videoDuration)); + estimatedSize += estimatedSize / (32 * 1024) * 16; + } + + if (videoTimelineView.getLeftProgress() == 0) { + startTime = -1; + } else { + startTime = (long) (videoTimelineView.getLeftProgress() * videoPlayer.getDuration()) * 1000; + } + if (videoTimelineView.getRightProgress() == 1) { + endTime = -1; + } else { + endTime = (long) (videoTimelineView.getRightProgress() * videoPlayer.getDuration()) * 1000; + } + } + + private Runnable updateProgressRunnable = new Runnable() { + @Override + public void run() { + if (videoPlayer == null) { + return; + } + if (!videoTimelineView.isDragging()) { + float progress = videoPlayer.getCurrentPosition() / (float) videoPlayer.getDuration(); + if (videoTimelineView.getVisibility() == View.VISIBLE) { + if (progress >= videoTimelineView.getRightProgress()) { + videoPlayer.pause(); + videoTimelineView.setProgress(0); + videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoPlayer.getDuration())); + } else { + progress -= videoTimelineView.getLeftProgress(); + if (progress < 0) { + progress = 0; + } + progress /= (videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress()); + if (progress > 1) { + progress = 1; + } + videoTimelineView.setProgress(progress); + } + } else { + videoTimelineView.setProgress(progress); + } + } + + if (isPlaying) { + AndroidUtilities.runOnUIThread(updateProgressRunnable); + } + } + }; + + public VideoEditedInfo getCurrentVideoEditedInfo() { + long cutStartTime = startTime == -1 ? 0 : startTime; + long cutEndTime = endTime == -1 ? videoPlayer.getDuration() * 1000 : endTime; + cutDuration = (cutEndTime - cutStartTime) / 1000; + + VideoEditedInfo videoEditedInfo = new VideoEditedInfo(); + videoEditedInfo.startTime = startTime; + videoEditedInfo.endTime = endTime; + videoEditedInfo.rotationValue = rotationValue; + videoEditedInfo.originalWidth = originalWidth; + videoEditedInfo.originalHeight = originalHeight; + videoEditedInfo.bitrate = bitrate; + videoEditedInfo.originalPath = videoPath; +// videoEditedInfo.estimatedSize = estimatedSize; +// videoEditedInfo.estimatedDuration = estimatedDuration; + videoEditedInfo.estimatedSize = 0; + videoEditedInfo.estimatedDuration = 0; + + if (!muteVideo && selectedCompression == compressionsCount - 1) { + videoEditedInfo.resultWidth = originalWidth; + videoEditedInfo.resultHeight = originalHeight; + videoEditedInfo.bitrate = muteVideo ? -1 : originalBitrate; + videoEditedInfo.muted = muteVideo; + } else { + if (muteVideo) { + selectedCompression = 1; + updateWidthHeightBitrateForCompression(); + } + videoEditedInfo.resultWidth = resultWidth; + videoEditedInfo.resultHeight = resultHeight; + videoEditedInfo.bitrate = muteVideo ? -1 : bitrate; + videoEditedInfo.muted = muteVideo; + } + return videoEditedInfo; + } + + private void updateWidthHeightBitrateForCompression() { + if (selectedCompression >= compressionsCount) { + selectedCompression = compressionsCount - 1; + } + if (selectedCompression != compressionsCount - 1) { + float maxSize; + int targetBitrate; + switch (selectedCompression) { + case 0: + maxSize = 432.0f; + targetBitrate = 400000; + break; + case 1: + maxSize = 640.0f; + targetBitrate = 900000; + break; + case 2: + maxSize = 848.0f; + targetBitrate = 1100000; + break; + case 3: + default: + targetBitrate = 1600000; + maxSize = 1280.0f; + break; + } + float scale = originalWidth > originalHeight ? maxSize / originalWidth : maxSize / originalHeight; + resultWidth = Math.round(originalWidth * scale / 2) * 2; + resultHeight = Math.round(originalHeight * scale / 2) * 2; + if (bitrate != 0) { + bitrate = Math.min(targetBitrate, (int) (originalBitrate / scale)); + videoFramesSize = (long) (bitrate / 8 * videoDuration / 1000); + } + } + } + + public void prepareVideoPlayer(String path, boolean canEdit, boolean playWhenReady) { + videoPath = path; + File videoF = new File(path); + if (!videoF.exists()) return; + preparePlayer(videoF, playWhenReady); + processOpenVideo(path); + + if (canEdit) { + showEditFrame(); + } + } + + public void showEditFrame() { + videoTimelineView.setVideoPath(videoPath); + videoTimelineView.setProgress(0); + videoTimelineView.setVisibility(VISIBLE); + } + + public void release() { + releasePlayer(); + videoTimelineView.destroy(); + } + + public long getCutDuration() { + return cutDuration; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoTimelinePlayView.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoTimelinePlayView.java new file mode 100644 index 0000000..73f8b13 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/components/VideoTimelinePlayView.java @@ -0,0 +1,436 @@ +package com.tangxiaolv.telegramgallery.components; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.media.MediaMetadataRetriever; +import android.os.AsyncTask; +import android.view.MotionEvent; +import android.view.View; + +import com.tangxiaolv.telegramgallery.R; +import com.tangxiaolv.telegramgallery.utils.AndroidUtilities; + +import java.util.ArrayList; + +@TargetApi(10) +public class VideoTimelinePlayView extends View { + + private long videoLength; + private float progressLeft; + private float progressRight = 1; + private Paint paint; + private Paint paint2; + private boolean pressedLeft; + private boolean pressedRight; + private boolean pressedPlay; + private float playProgress = 0.5f; + private float pressDx; + private MediaMetadataRetriever mediaMetadataRetriever; + private VideoTimelineViewDelegate delegate; + private ArrayList frames = new ArrayList<>(); + private AsyncTask currentTask; + private static final Object sync = new Object(); + private long frameTimeOffset; + private int frameWidth; + private int frameHeight; + private int framesToLoad; + private float maxProgressDiff = 1.0f; + private float minProgressDiff = 0.0f; + private boolean isRoundFrames; + private Rect rect1; + private Rect rect2; + private RectF rect3 = new RectF(); + private Drawable drawableLeft; + private Drawable drawableRight; + private int lastWidth; + + public interface VideoTimelineViewDelegate { + void onLeftProgressChanged(float progress); + void onRightProgressChanged(float progress); + void onPlayProgressChanged(float progress); + void didStartDragging(); + void didStopDragging(); + } + + public VideoTimelinePlayView(Context context) { + super(context); + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(0xffffffff); + paint2 = new Paint(); + paint2.setColor(0x7f000000); + drawableLeft = context.getResources().getDrawable(R.drawable.video_cropleft); + drawableLeft.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + drawableRight = context.getResources().getDrawable(R.drawable.video_cropright); + drawableRight.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + } + + public float getProgress() { + return playProgress; + } + + public float getLeftProgress() { + return progressLeft; + } + + public float getRightProgress() { + return progressRight; + } + + public void setMinProgressDiff(float value) { + minProgressDiff = value; + } + + public void setMaxProgressDiff(float value) { + maxProgressDiff = value; + if (progressRight - progressLeft > maxProgressDiff) { + progressRight = progressLeft + maxProgressDiff; + invalidate(); + } + } + + public void setRoundFrames(boolean value) { + isRoundFrames = value; + if (isRoundFrames) { + rect1 = new Rect(AndroidUtilities.dp(14), AndroidUtilities.dp(14), AndroidUtilities.dp(14 + 28), AndroidUtilities.dp(14 + 28)); + rect2 = new Rect(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event == null) { + return false; + } + float x = event.getX(); + float y = event.getY(); + + int width = getMeasuredWidth() - AndroidUtilities.dp(32); + int startX = (int) (width * progressLeft) + AndroidUtilities.dp(16); + int playX = (int) (width * (progressLeft + (progressRight - progressLeft) * playProgress)) + AndroidUtilities.dp(16); + int endX = (int) (width * progressRight) + AndroidUtilities.dp(16); + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + getParent().requestDisallowInterceptTouchEvent(true); + if (mediaMetadataRetriever == null) { + return false; + } + int additionWidth = AndroidUtilities.dp(12); + int additionWidthPlay = AndroidUtilities.dp(8); + if (playX - additionWidthPlay <= x && x <= playX + additionWidthPlay && y >= 0 && y <= getMeasuredHeight()) { + if (delegate != null) { + delegate.didStartDragging(); + } + pressedPlay = true; + pressDx = (int) (x - playX); + invalidate(); + return true; + } else if (startX - additionWidth <= x && x <= startX + additionWidth && y >= 0 && y <= getMeasuredHeight()) { + if (delegate != null) { + delegate.didStartDragging(); + } + pressedLeft = true; + pressDx = (int) (x - startX); + invalidate(); + return true; + } else if (endX - additionWidth <= x && x <= endX + additionWidth && y >= 0 && y <= getMeasuredHeight()) { + if (delegate != null) { + delegate.didStartDragging(); + } + pressedRight = true; + pressDx = (int) (x - endX); + invalidate(); + return true; + } + } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + if (pressedLeft) { + if (delegate != null) { + delegate.didStopDragging(); + } + pressedLeft = false; + return true; + } else if (pressedRight) { + if (delegate != null) { + delegate.didStopDragging(); + } + pressedRight = false; + return true; + } else if (pressedPlay) { + if (delegate != null) { + delegate.didStopDragging(); + } + pressedPlay = false; + return true; + } + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (pressedPlay) { + playX = (int) (x - pressDx); + playProgress = (float) (playX - AndroidUtilities.dp(16)) / (float) width; + if (playProgress < progressLeft) { + playProgress = progressLeft; + } else if (playProgress > progressRight) { + playProgress = progressRight; + } + playProgress = (playProgress - progressLeft) / (progressRight - progressLeft); + if (delegate != null) { + delegate.onPlayProgressChanged(progressLeft + (progressRight - progressLeft) * playProgress); + } + invalidate(); + return true; + } else if (pressedLeft) { + startX = (int) (x - pressDx); + if (startX < AndroidUtilities.dp(16)) { + startX = AndroidUtilities.dp(16); + } else if (startX > endX) { + startX = endX; + } + progressLeft = (float) (startX - AndroidUtilities.dp(16)) / (float) width; + if (progressRight - progressLeft > maxProgressDiff) { + progressRight = progressLeft + maxProgressDiff; + } else if (minProgressDiff != 0 && progressRight - progressLeft < minProgressDiff) { + progressLeft = progressRight - minProgressDiff; + if (progressLeft < 0) { + progressLeft = 0; + } + } + if (delegate != null) { + delegate.onLeftProgressChanged(progressLeft); + } + invalidate(); + return true; + } else if (pressedRight) { + endX = (int) (x - pressDx); + if (endX < startX) { + endX = startX; + } else if (endX > width + AndroidUtilities.dp(16)) { + endX = width + AndroidUtilities.dp(16); + } + progressRight = (float) (endX - AndroidUtilities.dp(16)) / (float) width; + if (progressRight - progressLeft > maxProgressDiff) { + progressLeft = progressRight - maxProgressDiff; + } else if (minProgressDiff != 0 && progressRight - progressLeft < minProgressDiff) { + progressRight = progressLeft + minProgressDiff; + if (progressRight > 1.0f) { + progressRight = 1.0f; + } + } + if (delegate != null) { + delegate.onRightProgressChanged(progressRight); + } + invalidate(); + return true; + } + } + return false; + } + + public void setColor(int color) { + paint.setColor(color); + } + + public void setVideoPath(String path) { + destroy(); + mediaMetadataRetriever = new MediaMetadataRetriever(); + progressLeft = 0.0f; + progressRight = 1.0f; + try { + mediaMetadataRetriever.setDataSource(path); + String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + videoLength = Long.parseLong(duration); + } catch (Exception e) { + e.printStackTrace(); + } + invalidate(); + } + + public void setDelegate(VideoTimelineViewDelegate delegate) { + this.delegate = delegate; + } + + private void reloadFrames(int frameNum) { + if (mediaMetadataRetriever == null) { + return; + } + if (frameNum == 0) { + if (isRoundFrames) { + frameHeight = frameWidth = AndroidUtilities.dp(56); + framesToLoad = (int) Math.ceil((getMeasuredWidth() - AndroidUtilities.dp(16)) / (frameHeight / 2.0f)); + } else { + frameHeight = AndroidUtilities.dp(40); + framesToLoad = (getMeasuredWidth() - AndroidUtilities.dp(16)) / frameHeight; + frameWidth = (int) Math.ceil((float) (getMeasuredWidth() - AndroidUtilities.dp(16)) / (float) framesToLoad); + } + frameTimeOffset = videoLength / framesToLoad; + } + currentTask = new AsyncTask() { + private int frameNum = 0; + + @Override + protected Bitmap doInBackground(Integer... objects) { + frameNum = objects[0]; + Bitmap bitmap = null; + if (isCancelled()) { + return null; + } + try { + bitmap = mediaMetadataRetriever.getFrameAtTime(frameTimeOffset * frameNum * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC); + if (isCancelled()) { + return null; + } + if (bitmap != null) { + Bitmap result = Bitmap.createBitmap(frameWidth, frameHeight, bitmap.getConfig()); + Canvas canvas = new Canvas(result); + float scaleX = (float) frameWidth / (float) bitmap.getWidth(); + float scaleY = (float) frameHeight / (float) bitmap.getHeight(); + float scale = scaleX > scaleY ? scaleX : scaleY; + int w = (int) (bitmap.getWidth() * scale); + int h = (int) (bitmap.getHeight() * scale); + Rect srcRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + Rect destRect = new Rect((frameWidth - w) / 2, (frameHeight - h) / 2, w, h); + canvas.drawBitmap(bitmap, srcRect, destRect, null); + bitmap.recycle(); + bitmap = result; + } + } catch (Exception e) { + e.printStackTrace(); + } + return bitmap; + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (!isCancelled()) { + frames.add(bitmap); + invalidate(); + if (frameNum < framesToLoad) { + reloadFrames(frameNum + 1); + } + } + } + }; + currentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, frameNum, null, null); + } + + public void destroy() { + synchronized (sync) { + try { + if (mediaMetadataRetriever != null) { + mediaMetadataRetriever.release(); + mediaMetadataRetriever = null; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + for (Bitmap bitmap : frames) { + if (bitmap != null) { + bitmap.recycle(); + } + } + frames.clear(); + if (currentTask != null) { + currentTask.cancel(true); + currentTask = null; + } + } + + public boolean isDragging() { + return pressedPlay; + } + + public void setProgress(float value) { + playProgress = value; + invalidate(); + } + + public void clearFrames() { + for (Bitmap bitmap : frames) { + if (bitmap != null) { + bitmap.recycle(); + } + } + frames.clear(); + if (currentTask != null) { + currentTask.cancel(true); + currentTask = null; + } + invalidate(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + if (lastWidth != widthSize) { + clearFrames(); + lastWidth = widthSize; + } + } + + @Override + protected void onDraw(Canvas canvas) { + int width = getMeasuredWidth() - AndroidUtilities.dp(36); + int startX = (int) (width * progressLeft) + AndroidUtilities.dp(16); + int endX = (int) (width * progressRight) + AndroidUtilities.dp(16); + + canvas.save(); + canvas.clipRect(AndroidUtilities.dp(16), AndroidUtilities.dp(4), width + AndroidUtilities.dp(20), AndroidUtilities.dp(48)); + if (frames.isEmpty() && currentTask == null) { + reloadFrames(0); + } else { + int offset = 0; + for (int a = 0; a < frames.size(); a++) { + Bitmap bitmap = frames.get(a); + if (bitmap != null) { + int x = AndroidUtilities.dp(16) + offset * (isRoundFrames ? frameWidth / 2 : frameWidth); + int y = AndroidUtilities.dp(2 + 4); + if (isRoundFrames) { + rect2.set(x, y, x + AndroidUtilities.dp(28), y + AndroidUtilities.dp(28)); + canvas.drawBitmap(bitmap, rect1, rect2, null); + } else { + canvas.drawBitmap(bitmap, x, y, null); + } + } + offset++; + } + } + + int top = AndroidUtilities.dp(2 + 4); + int end = AndroidUtilities.dp(48); + + canvas.drawRect(AndroidUtilities.dp(16), top, startX, AndroidUtilities.dp(46), paint2); + canvas.drawRect(endX + AndroidUtilities.dp(4), top, AndroidUtilities.dp(16) + width + AndroidUtilities.dp(4), AndroidUtilities.dp(46), paint2); + + canvas.drawRect(startX, AndroidUtilities.dp(4), startX + AndroidUtilities.dp(2), end, paint); + canvas.drawRect(endX + AndroidUtilities.dp(2), AndroidUtilities.dp(4), endX + AndroidUtilities.dp(4), end, paint); + canvas.drawRect(startX + AndroidUtilities.dp(2), AndroidUtilities.dp(4), endX + AndroidUtilities.dp(4), top, paint); + canvas.drawRect(startX + AndroidUtilities.dp(2), end - AndroidUtilities.dp(2), endX + AndroidUtilities.dp(4), end, paint); + canvas.restore(); + + rect3.set(startX - AndroidUtilities.dp(8), AndroidUtilities.dp(4), startX + AndroidUtilities.dp(2), end); + canvas.drawRoundRect(rect3, AndroidUtilities.dp(2), AndroidUtilities.dp(2), paint); + drawableLeft.setBounds(startX - AndroidUtilities.dp(8), AndroidUtilities.dp(4) + (AndroidUtilities.dp(44) - AndroidUtilities.dp(18)) / 2, startX + AndroidUtilities.dp(2), (AndroidUtilities.dp(44) - AndroidUtilities.dp(18)) / 2 + AndroidUtilities.dp(18 + 4)); + drawableLeft.draw(canvas); + + rect3.set(endX + AndroidUtilities.dp(2), AndroidUtilities.dp(4), endX + AndroidUtilities.dp(12), end); + canvas.drawRoundRect(rect3, AndroidUtilities.dp(2), AndroidUtilities.dp(2), paint); + drawableRight.setBounds(endX + AndroidUtilities.dp(2), AndroidUtilities.dp(4) + (AndroidUtilities.dp(44) - AndroidUtilities.dp(18)) / 2, endX + AndroidUtilities.dp(12), (AndroidUtilities.dp(44) - AndroidUtilities.dp(18)) / 2 + AndroidUtilities.dp(18 + 4)); + drawableRight.draw(canvas); + + float cx = AndroidUtilities.dp(18) + width * (progressLeft + (progressRight - progressLeft) * playProgress); + rect3.set(cx - AndroidUtilities.dp(1.5f), AndroidUtilities.dp(2), cx + AndroidUtilities.dp(1.5f), AndroidUtilities.dp(50)); + canvas.drawRoundRect(rect3, AndroidUtilities.dp(1), AndroidUtilities.dp(1), paint2); + canvas.drawCircle(cx, AndroidUtilities.dp(52), AndroidUtilities.dp(3.5f), paint2); + + rect3.set(cx - AndroidUtilities.dp(1), AndroidUtilities.dp(2), cx + AndroidUtilities.dp(1), AndroidUtilities.dp(50)); + canvas.drawRoundRect(rect3, AndroidUtilities.dp(1), AndroidUtilities.dp(1), paint); + canvas.drawCircle(cx, AndroidUtilities.dp(52), AndroidUtilities.dp(3), paint); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/entity/MediaInfo.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/entity/MediaInfo.java new file mode 100644 index 0000000..37caf6e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/entity/MediaInfo.java @@ -0,0 +1,162 @@ +package com.tangxiaolv.telegramgallery.entity; + +import android.os.Parcel; +import android.os.Parcelable; + +public class MediaInfo implements Parcelable { + //媒体类型:image/png, video/mp4 ... + private String mimeType; + //本地路径 + private String path; + //Bytes + private long size; + + // + //图片 + // + + //略缩图地址 + private String thumbPath; + + // + //视频 + // + + //名称 + private String title; + //时长:秒 + private int videoDuration; + //px + private int width; + //px + private int height; + //角度 + private int rotation; + + public String getMimeType() { + return mimeType; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public String getThumbPath() { + return thumbPath; + } + + public void setThumbPath(String thumbPath) { + this.thumbPath = thumbPath; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getVideoDuration() { + return videoDuration; + } + + public void setVideoDuration(int videoDuration) { + this.videoDuration = videoDuration; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getRotation() { + return rotation; + } + + public void setRotation(int rotation) { + this.rotation = rotation; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.mimeType); + dest.writeString(this.path); + dest.writeLong(this.size); + dest.writeString(this.thumbPath); + dest.writeString(this.title); + dest.writeInt(this.videoDuration); + dest.writeInt(this.width); + dest.writeInt(this.height); + dest.writeInt(this.rotation); + } + + public MediaInfo(String mimeType, String path, long size, String thumbPath, String title, int videoDuration, int width, int height, int rotation) { + this.mimeType = mimeType; + this.path = path; + this.size = size; + this.thumbPath = thumbPath; + this.title = title; + this.videoDuration = videoDuration; + this.width = width; + this.height = height; + this.rotation = rotation; + } + + public MediaInfo() { + } + + protected MediaInfo(Parcel in) { + this.mimeType = in.readString(); + this.path = in.readString(); + this.size = in.readLong(); + this.thumbPath = in.readString(); + this.title = in.readString(); + this.videoDuration = in.readInt(); + this.width = in.readInt(); + this.height = in.readInt(); + this.rotation = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + public MediaInfo createFromParcel(Parcel source) { + return new MediaInfo(source); + } + + public MediaInfo[] newArray(int size) { + return new MediaInfo[size]; + } + }; +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/entity/VideoEditedInfo.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/entity/VideoEditedInfo.java new file mode 100644 index 0000000..5fe667f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/entity/VideoEditedInfo.java @@ -0,0 +1,64 @@ +package com.tangxiaolv.telegramgallery.entity; + +import com.tangxiaolv.telegramgallery.tl.InputEncryptedFile; +import com.tangxiaolv.telegramgallery.tl.InputFile; + +import java.util.Locale; + +public class VideoEditedInfo { + public long startTime; + public long endTime; + public int rotationValue; + public int originalWidth; + public int originalHeight; + public int resultWidth; + public int resultHeight; + public int bitrate; + public String originalPath; + public long estimatedSize; + public long estimatedDuration; + public boolean roundVideo; + public boolean muted; + public InputFile file; + public InputEncryptedFile encryptedFile; + public byte[] key; + public byte[] iv; + + public String getString() { + return String.format(Locale.US, "-1_%d_%d_%d_%d_%d_%d_%d_%d_%s", startTime, endTime, rotationValue, originalWidth, originalHeight, bitrate, resultWidth, resultHeight, originalPath); + } + + public boolean parseString(String string) { + if (string.length() < 6) { + return false; + } + try { + String args[] = string.split("_"); + if (args.length >= 10) { + startTime = Long.parseLong(args[1]); + endTime = Long.parseLong(args[2]); + rotationValue = Integer.parseInt(args[3]); + originalWidth = Integer.parseInt(args[4]); + originalHeight = Integer.parseInt(args[5]); + bitrate = Integer.parseInt(args[6]); + resultWidth = Integer.parseInt(args[7]); + resultHeight = Integer.parseInt(args[8]); + for (int a = 9; a < args.length; a++) { + if (originalPath == null) { + originalPath = args[a]; + } else { + originalPath += "_" + args[a]; + } + } + } + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public boolean needConvert() { + return !roundVideo || roundVideo && (startTime > 0 || endTime != -1 && endTime != estimatedDuration); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/BaseRenderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/BaseRenderer.java new file mode 100644 index 0000000..05adeb4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/BaseRenderer.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MediaClock; +import java.io.IOException; + +/** + * An abstract base class suitable for most {@link Renderer} implementations. + */ +public abstract class BaseRenderer implements Renderer, com.tangxiaolv.telegramgallery.exoplayer2.RendererCapabilities { + + private final int trackType; + + private com.tangxiaolv.telegramgallery.exoplayer2.RendererConfiguration configuration; + private int index; + private int state; + private SampleStream stream; + private long streamOffsetUs; + private boolean readEndOfStream; + private boolean streamIsFinal; + + /** + * @param trackType The track type that the renderer handles. One of the {@link C} + * {@code TRACK_TYPE_*} constants. + */ + public BaseRenderer(int trackType) { + this.trackType = trackType; + readEndOfStream = true; + } + + @Override + public final int getTrackType() { + return trackType; + } + + @Override + public final com.tangxiaolv.telegramgallery.exoplayer2.RendererCapabilities getCapabilities() { + return this; + } + + @Override + public final void setIndex(int index) { + this.index = index; + } + + @Override + public MediaClock getMediaClock() { + return null; + } + + @Override + public final int getState() { + return state; + } + + @Override + public final void enable(com.tangxiaolv.telegramgallery.exoplayer2.RendererConfiguration configuration, Format[] formats, + SampleStream stream, long positionUs, boolean joining, long offsetUs) + throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + Assertions.checkState(state == STATE_DISABLED); + this.configuration = configuration; + state = STATE_ENABLED; + onEnabled(joining); + replaceStream(formats, stream, offsetUs); + onPositionReset(positionUs, joining); + } + + @Override + public final void start() throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + Assertions.checkState(state == STATE_ENABLED); + state = STATE_STARTED; + onStarted(); + } + + @Override + public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs) + throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + Assertions.checkState(!streamIsFinal); + this.stream = stream; + readEndOfStream = false; + streamOffsetUs = offsetUs; + onStreamChanged(formats); + } + + @Override + public final SampleStream getStream() { + return stream; + } + + @Override + public final boolean hasReadStreamToEnd() { + return readEndOfStream; + } + + @Override + public final void setCurrentStreamFinal() { + streamIsFinal = true; + } + + @Override + public final boolean isCurrentStreamFinal() { + return streamIsFinal; + } + + @Override + public final void maybeThrowStreamError() throws IOException { + stream.maybeThrowError(); + } + + @Override + public final void resetPosition(long positionUs) throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + streamIsFinal = false; + readEndOfStream = false; + onPositionReset(positionUs, false); + } + + @Override + public final void stop() throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + Assertions.checkState(state == STATE_STARTED); + state = STATE_ENABLED; + onStopped(); + } + + @Override + public final void disable() { + Assertions.checkState(state == STATE_ENABLED); + state = STATE_DISABLED; + onDisabled(); + stream = null; + streamIsFinal = false; + } + + // RendererCapabilities implementation. + + @Override + public int supportsMixedMimeTypeAdaptation() throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + return ADAPTIVE_NOT_SUPPORTED; + } + + // ExoPlayerComponent implementation. + + @Override + public void handleMessage(int what, Object object) throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + // Do nothing. + } + + // Methods to be overridden by subclasses. + + /** + * Called when the renderer is enabled. + *

    + * The default implementation is a no-op. + * + * @param joining Whether this renderer is being enabled to join an ongoing playback. + * @throws ExoPlaybackException If an error occurs. + */ + protected void onEnabled(boolean joining) throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the renderer's stream has changed. This occurs when the renderer is enabled after + * {@link #onEnabled(boolean)} has been called, and also when the stream has been replaced whilst + * the renderer is enabled or started. + *

    + * The default implementation is a no-op. + * + * @param formats The enabled formats. + * @throws ExoPlaybackException If an error occurs. + */ + protected void onStreamChanged(Format[] formats) throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the position is reset. This occurs when the renderer is enabled after + * {@link #onStreamChanged(Format[])} has been called, and also when a position discontinuity + * is encountered. + *

    + * After a position reset, the renderer's {@link SampleStream} is guaranteed to provide samples + * starting from a key frame. + *

    + * The default implementation is a no-op. + * + * @param positionUs The new playback position in microseconds. + * @param joining Whether this renderer is being enabled to join an ongoing playback. + * @throws ExoPlaybackException If an error occurs. + */ + protected void onPositionReset(long positionUs, boolean joining) throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the renderer is started. + *

    + * The default implementation is a no-op. + * + * @throws ExoPlaybackException If an error occurs. + */ + protected void onStarted() throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the renderer is stopped. + *

    + * The default implementation is a no-op. + * + * @throws ExoPlaybackException If an error occurs. + */ + protected void onStopped() throws com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the renderer is disabled. + *

    + * The default implementation is a no-op. + */ + protected void onDisabled() { + // Do nothing. + } + + // Methods to be called by subclasses. + + /** + * Returns the configuration set when the renderer was most recently enabled. + */ + protected final com.tangxiaolv.telegramgallery.exoplayer2.RendererConfiguration getConfiguration() { + return configuration; + } + + /** + * Returns the index of the renderer within the player. + */ + protected final int getIndex() { + return index; + } + + /** + * Reads from the enabled upstream source. If the upstream source has been read to the end then + * {@link C#RESULT_BUFFER_READ} is only returned if {@link #setCurrentStreamFinal()} has been + * called. {@link C#RESULT_NOTHING_READ} is returned otherwise. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If the end of the stream has been reached, the + * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @param formatRequired Whether the caller requires that the format of the stream be read even if + * it's not changing. A sample will never be read if set to true, however it is still possible + * for the end of stream or nothing to be read. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or + * {@link C#RESULT_BUFFER_READ}. + */ + protected final int readSource(com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired) { + int result = stream.readData(formatHolder, buffer, formatRequired); + if (result == com.tangxiaolv.telegramgallery.exoplayer2.C.RESULT_BUFFER_READ) { + if (buffer.isEndOfStream()) { + readEndOfStream = true; + return streamIsFinal ? com.tangxiaolv.telegramgallery.exoplayer2.C.RESULT_BUFFER_READ : com.tangxiaolv.telegramgallery.exoplayer2.C.RESULT_NOTHING_READ; + } + buffer.timeUs += streamOffsetUs; + } else if (result == com.tangxiaolv.telegramgallery.exoplayer2.C.RESULT_FORMAT_READ) { + Format format = formatHolder.format; + if (format.subsampleOffsetUs != com.tangxiaolv.telegramgallery.exoplayer2.Format.OFFSET_SAMPLE_RELATIVE) { + format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + streamOffsetUs); + formatHolder.format = format; + } + } + return result; + } + + /** + * Attempts to skip to the keyframe before the specified position, or to the end of the stream if + * {@code positionUs} is beyond it. + * + * @param positionUs The position in microseconds. + */ + protected void skipSource(long positionUs) { + stream.skipData(positionUs - streamOffsetUs); + } + + /** + * Returns whether the upstream source is ready. + * + * @return Whether the source is ready. + */ + protected final boolean isSourceReady() { + return readEndOfStream ? streamIsFinal : stream.isReady(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/C.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/C.java new file mode 100644 index 0000000..38335e3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/C.java @@ -0,0 +1,664 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.annotation.TargetApi; +import android.content.Context; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.support.annotation.IntDef; +import android.view.Surface; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.UUID; + +/** + * Defines constants used by the library. + */ +public final class C { + + private C() {} + + /** + * Special constant representing a time corresponding to the end of a source. Suitable for use in + * any time base. + */ + public static final long TIME_END_OF_SOURCE = Long.MIN_VALUE; + + /** + * Special constant representing an unset or unknown time or duration. Suitable for use in any + * time base. + */ + public static final long TIME_UNSET = Long.MIN_VALUE + 1; + + /** + * Represents an unset or unknown index. + */ + public static final int INDEX_UNSET = -1; + + /** + * Represents an unset or unknown position. + */ + public static final int POSITION_UNSET = -1; + + /** + * Represents an unset or unknown length. + */ + public static final int LENGTH_UNSET = -1; + + /** + * The number of microseconds in one second. + */ + public static final long MICROS_PER_SECOND = 1000000L; + + /** + * The number of nanoseconds in one second. + */ + public static final long NANOS_PER_SECOND = 1000000000L; + + /** + * The name of the UTF-8 charset. + */ + public static final String UTF8_NAME = "UTF-8"; + + /** + * The name of the UTF-16 charset. + */ + public static final String UTF16_NAME = "UTF-16"; + + /** + * * The name of the serif font family. + */ + public static final String SERIF_NAME = "serif"; + + /** + * * The name of the sans-serif font family. + */ + public static final String SANS_SERIF_NAME = "sans-serif"; + + /** + * Crypto modes for a codec. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC}) + public @interface CryptoMode {} + /** + * @see MediaCodec#CRYPTO_MODE_UNENCRYPTED + */ + @SuppressWarnings("InlinedApi") + public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED; + /** + * @see MediaCodec#CRYPTO_MODE_AES_CTR + */ + @SuppressWarnings("InlinedApi") + public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; + /** + * @see MediaCodec#CRYPTO_MODE_AES_CBC + */ + @SuppressWarnings("InlinedApi") + public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC; + + /** + * Represents an unset {@link android.media.AudioTrack} session identifier. Equal to + * {@link AudioManager#AUDIO_SESSION_ID_GENERATE}. + */ + @SuppressWarnings("InlinedApi") + public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE; + + /** + * Represents an audio encoding, or an invalid or unset value. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({com.tangxiaolv.telegramgallery.exoplayer2.Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, + ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_AC3, ENCODING_E_AC3, ENCODING_DTS, + ENCODING_DTS_HD}) + public @interface Encoding {} + + /** + * Represents a PCM audio encoding, or an invalid or unset value. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({com.tangxiaolv.telegramgallery.exoplayer2.Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, + ENCODING_PCM_24BIT, ENCODING_PCM_32BIT}) + public @interface PcmEncoding {} + /** + * @see AudioFormat#ENCODING_INVALID + */ + public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID; + /** + * @see AudioFormat#ENCODING_PCM_8BIT + */ + public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT; + /** + * @see AudioFormat#ENCODING_PCM_16BIT + */ + public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT; + /** + * PCM encoding with 24 bits per sample. + */ + public static final int ENCODING_PCM_24BIT = 0x80000000; + /** + * PCM encoding with 32 bits per sample. + */ + public static final int ENCODING_PCM_32BIT = 0x40000000; + /** + * @see AudioFormat#ENCODING_AC3 + */ + @SuppressWarnings("InlinedApi") + public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; + /** + * @see AudioFormat#ENCODING_E_AC3 + */ + @SuppressWarnings("InlinedApi") + public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; + /** + * @see AudioFormat#ENCODING_DTS + */ + @SuppressWarnings("InlinedApi") + public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; + /** + * @see AudioFormat#ENCODING_DTS_HD + */ + @SuppressWarnings("InlinedApi") + public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD; + + /** + * @see AudioFormat#CHANNEL_OUT_7POINT1_SURROUND + */ + @SuppressWarnings({"InlinedApi", "deprecation"}) + public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23 + ? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; + + /** + * Stream types for an {@link android.media.AudioTrack}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION, STREAM_TYPE_RING, + STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL}) + public @interface StreamType {} + /** + * @see AudioManager#STREAM_ALARM + */ + public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM; + /** + * @see AudioManager#STREAM_MUSIC + */ + public static final int STREAM_TYPE_MUSIC = AudioManager.STREAM_MUSIC; + /** + * @see AudioManager#STREAM_NOTIFICATION + */ + public static final int STREAM_TYPE_NOTIFICATION = AudioManager.STREAM_NOTIFICATION; + /** + * @see AudioManager#STREAM_RING + */ + public static final int STREAM_TYPE_RING = AudioManager.STREAM_RING; + /** + * @see AudioManager#STREAM_SYSTEM + */ + public static final int STREAM_TYPE_SYSTEM = AudioManager.STREAM_SYSTEM; + /** + * @see AudioManager#STREAM_VOICE_CALL + */ + public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL; + /** + * The default stream type used by audio renderers. + */ + public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; + + /** + * Flags which can apply to a buffer containing a media sample. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM, + BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY}) + public @interface BufferFlags {} + /** + * Indicates that a buffer holds a synchronization sample. + */ + @SuppressWarnings("InlinedApi") + public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; + /** + * Flag for empty buffers that signal that the end of the stream was reached. + */ + @SuppressWarnings("InlinedApi") + public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM; + /** + * Indicates that a buffer is (at least partially) encrypted. + */ + public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000; + /** + * Indicates that a buffer should be decoded but not rendered. + */ + public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000; + + /** + * Video scaling modes for {@link MediaCodec}-based {@link Renderer}s. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) + public @interface VideoScalingMode {} + /** + * @see MediaCodec#VIDEO_SCALING_MODE_SCALE_TO_FIT + */ + @SuppressWarnings("InlinedApi") + public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = + MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT; + /** + * @see MediaCodec#VIDEO_SCALING_MODE_SCALE_TO_FIT + */ + @SuppressWarnings("InlinedApi") + public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = + MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING; + /** + * A default video scaling mode for {@link MediaCodec}-based {@link Renderer}s. + */ + public static final int VIDEO_SCALING_MODE_DEFAULT = VIDEO_SCALING_MODE_SCALE_TO_FIT; + + /** + * Track selection flags. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, + SELECTION_FLAG_AUTOSELECT}) + public @interface SelectionFlags {} + /** + * Indicates that the track should be selected if user preferences do not state otherwise. + */ + public static final int SELECTION_FLAG_DEFAULT = 1; + /** + * Indicates that the track must be displayed. Only applies to text tracks. + */ + public static final int SELECTION_FLAG_FORCED = 2; + /** + * Indicates that the player may choose to play the track in absence of an explicit user + * preference. + */ + public static final int SELECTION_FLAG_AUTOSELECT = 4; + + /** + * Represents a streaming or other media type. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER}) + public @interface ContentType {} + /** + * Value returned by {@link Util#inferContentType(String)} for DASH manifests. + */ + public static final int TYPE_DASH = 0; + /** + * Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests. + */ + public static final int TYPE_SS = 1; + /** + * Value returned by {@link Util#inferContentType(String)} for HLS manifests. + */ + public static final int TYPE_HLS = 2; + /** + * Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or + * Smooth Streaming manifests. + */ + public static final int TYPE_OTHER = 3; + + /** + * A return value for methods where the end of an input was encountered. + */ + public static final int RESULT_END_OF_INPUT = -1; + /** + * A return value for methods where the length of parsed data exceeds the maximum length allowed. + */ + public static final int RESULT_MAX_LENGTH_EXCEEDED = -2; + /** + * A return value for methods where nothing was read. + */ + public static final int RESULT_NOTHING_READ = -3; + /** + * A return value for methods where a buffer was read. + */ + public static final int RESULT_BUFFER_READ = -4; + /** + * A return value for methods where a format was read. + */ + public static final int RESULT_FORMAT_READ = -5; + + /** + * A data type constant for data of unknown or unspecified type. + */ + public static final int DATA_TYPE_UNKNOWN = 0; + /** + * A data type constant for media, typically containing media samples. + */ + public static final int DATA_TYPE_MEDIA = 1; + /** + * A data type constant for media, typically containing only initialization data. + */ + public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2; + /** + * A data type constant for drm or encryption data. + */ + public static final int DATA_TYPE_DRM = 3; + /** + * A data type constant for a manifest file. + */ + public static final int DATA_TYPE_MANIFEST = 4; + /** + * A data type constant for time synchronization data. + */ + public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5; + /** + * Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or + * equal to this value. + */ + public static final int DATA_TYPE_CUSTOM_BASE = 10000; + + /** + * A type constant for tracks of unknown type. + */ + public static final int TRACK_TYPE_UNKNOWN = -1; + /** + * A type constant for tracks of some default type, where the type itself is unknown. + */ + public static final int TRACK_TYPE_DEFAULT = 0; + /** + * A type constant for audio tracks. + */ + public static final int TRACK_TYPE_AUDIO = 1; + /** + * A type constant for video tracks. + */ + public static final int TRACK_TYPE_VIDEO = 2; + /** + * A type constant for text tracks. + */ + public static final int TRACK_TYPE_TEXT = 3; + /** + * A type constant for metadata tracks. + */ + public static final int TRACK_TYPE_METADATA = 4; + /** + * Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or + * equal to this value. + */ + public static final int TRACK_TYPE_CUSTOM_BASE = 10000; + + /** + * A selection reason constant for selections whose reasons are unknown or unspecified. + */ + public static final int SELECTION_REASON_UNKNOWN = 0; + /** + * A selection reason constant for an initial track selection. + */ + public static final int SELECTION_REASON_INITIAL = 1; + /** + * A selection reason constant for an manual (i.e. user initiated) track selection. + */ + public static final int SELECTION_REASON_MANUAL = 2; + /** + * A selection reason constant for an adaptive track selection. + */ + public static final int SELECTION_REASON_ADAPTIVE = 3; + /** + * A selection reason constant for a trick play track selection. + */ + public static final int SELECTION_REASON_TRICK_PLAY = 4; + /** + * Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than + * or equal to this value. + */ + public static final int SELECTION_REASON_CUSTOM_BASE = 10000; + + /** + * A default size in bytes for an individual allocation that forms part of a larger buffer. + */ + public static final int DEFAULT_BUFFER_SEGMENT_SIZE = 64 * 1024; + + /** + * A default size in bytes for a video buffer. + */ + public static final int DEFAULT_VIDEO_BUFFER_SIZE = 200 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** + * A default size in bytes for an audio buffer. + */ + public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** + * A default size in bytes for a text buffer. + */ + public static final int DEFAULT_TEXT_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** + * A default size in bytes for a metadata buffer. + */ + public static final int DEFAULT_METADATA_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** + * A default size in bytes for a muxed buffer (e.g. containing video, audio and text). + */ + public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE + + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; + + /** + * The Nil UUID as defined by + * RFC4122. + */ + public static final UUID UUID_NIL = new UUID(0L, 0L); + + /** + * UUID for the ClearKey DRM scheme. + *

    + * ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up. + */ + public static final UUID CLEARKEY_UUID = new UUID(0x1077EFECC0B24D02L, 0xACE33C1E52E2FB4BL); + + /** + * UUID for the Widevine DRM scheme. + *

    + * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up. + */ + public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); + + /** + * UUID for the PlayReady DRM scheme. + *

    + * PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not + * provide PlayReady support. + */ + public static final UUID PLAYREADY_UUID = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L); + + /** + * The type of a message that can be passed to a video {@link Renderer} via + * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object + * should be the target {@link Surface}, or null. + */ + public static final int MSG_SET_SURFACE = 1; + + /** + * A type of a message that can be passed to an audio {@link Renderer} via + * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object + * should be a {@link Float} with 0 being silence and 1 being unity gain. + */ + public static final int MSG_SET_VOLUME = 2; + + /** + * A type of a message that can be passed to an audio {@link Renderer} via + * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object + * should be one of the integer stream types in {@link C.StreamType}, and will specify the stream + * type of the underlying {@link android.media.AudioTrack}. See also + * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}. If the stream type + * is not set, audio renderers use {@link #STREAM_TYPE_DEFAULT}. + *

    + * Note that when the stream type changes, the AudioTrack must be reinitialized, which can + * introduce a brief gap in audio output. Note also that tracks in the same audio session must + * share the same routing, so a new audio session id will be generated. + */ + public static final int MSG_SET_STREAM_TYPE = 3; + + /** + * The type of a message that can be passed to a {@link MediaCodec}-based video {@link Renderer} + * via {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message + * object should be one of the integer scaling modes in {@link C.VideoScalingMode}. + *

    + * Note that the scaling mode only applies if the {@link Surface} targeted by the renderer is + * owned by a {@link android.view.SurfaceView}. + */ + public static final int MSG_SET_SCALING_MODE = 4; + + /** + * Applications or extensions may define custom {@code MSG_*} constants greater than or equal to + * this value. + */ + public static final int MSG_CUSTOM_BASE = 10000; + + /** + * The stereo mode for 360/3D/VR videos. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + com.tangxiaolv.telegramgallery.exoplayer2.Format.NO_VALUE, + STEREO_MODE_MONO, + STEREO_MODE_TOP_BOTTOM, + STEREO_MODE_LEFT_RIGHT, + STEREO_MODE_STEREO_MESH + }) + public @interface StereoMode {} + /** + * Indicates Monoscopic stereo layout, used with 360/3D/VR videos. + */ + public static final int STEREO_MODE_MONO = 0; + /** + * Indicates Top-Bottom stereo layout, used with 360/3D/VR videos. + */ + public static final int STEREO_MODE_TOP_BOTTOM = 1; + /** + * Indicates Left-Right stereo layout, used with 360/3D/VR videos. + */ + public static final int STEREO_MODE_LEFT_RIGHT = 2; + /** + * Indicates a stereo layout where the left and right eyes have separate meshes, + * used with 360/3D/VR videos. + */ + public static final int STEREO_MODE_STEREO_MESH = 3; + + /** + * Video colorspaces. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({com.tangxiaolv.telegramgallery.exoplayer2.Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) + public @interface ColorSpace {} + /** + * @see MediaFormat#COLOR_STANDARD_BT709 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709; + /** + * @see MediaFormat#COLOR_STANDARD_BT601_PAL + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL; + /** + * @see MediaFormat#COLOR_STANDARD_BT2020 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020; + + /** + * Video color transfer characteristics. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({com.tangxiaolv.telegramgallery.exoplayer2.Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG}) + public @interface ColorTransfer {} + /** + * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO; + /** + * @see MediaFormat#COLOR_TRANSFER_ST2084 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084; + /** + * @see MediaFormat#COLOR_TRANSFER_HLG + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG; + + /** + * Video color range. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({com.tangxiaolv.telegramgallery.exoplayer2.Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL}) + public @interface ColorRange {} + /** + * @see MediaFormat#COLOR_RANGE_LIMITED + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED; + /** + * @see MediaFormat#COLOR_RANGE_FULL + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL; + + /** + * Priority for media playback. + * + *

    Larger values indicate higher priorities. + */ + public static final int PRIORITY_PLAYBACK = 0; + + /** + * Priority for media downloading. + * + *

    Larger values indicate higher priorities. + */ + public static final int PRIORITY_DOWNLOAD = PRIORITY_PLAYBACK - 1000; + + /** + * Converts a time in microseconds to the corresponding time in milliseconds, preserving + * {@link #TIME_UNSET} values. + * + * @param timeUs The time in microseconds. + * @return The corresponding time in milliseconds. + */ + public static long usToMs(long timeUs) { + return timeUs == TIME_UNSET ? TIME_UNSET : (timeUs / 1000); + } + + /** + * Converts a time in milliseconds to the corresponding time in microseconds, preserving + * {@link #TIME_UNSET} values. + * + * @param timeMs The time in milliseconds. + * @return The corresponding time in microseconds. + */ + public static long msToUs(long timeMs) { + return timeMs == TIME_UNSET ? TIME_UNSET : (timeMs * 1000); + } + + /** + * Returns a newly generated {@link android.media.AudioTrack} session identifier. + */ + @TargetApi(21) + public static int generateAudioSessionIdV21(Context context) { + return ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)) + .generateAudioSessionId(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/DefaultLoadControl.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/DefaultLoadControl.java new file mode 100644 index 0000000..9a6a336 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/DefaultLoadControl.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectionArray; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DefaultAllocator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.PriorityTaskManager; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * The default {@link LoadControl} implementation. + */ +public final class DefaultLoadControl implements LoadControl { + + /** + * The default minimum duration of media that the player will attempt to ensure is buffered at all + * times, in milliseconds. + */ + public static final int DEFAULT_MIN_BUFFER_MS = 15000; + + /** + * The default maximum duration of media that the player will attempt to buffer, in milliseconds. + */ + public static final int DEFAULT_MAX_BUFFER_MS = 30000; + + /** + * The default duration of media that must be buffered for playback to start or resume following a + * user action such as a seek, in milliseconds. + */ + public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500; + + /** + * The default duration of media that must be buffered for playback to resume after a rebuffer, + * in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user + * action. + */ + public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000; + + private static final int ABOVE_HIGH_WATERMARK = 0; + private static final int BETWEEN_WATERMARKS = 1; + private static final int BELOW_LOW_WATERMARK = 2; + + private final DefaultAllocator allocator; + + private final long minBufferUs; + private final long maxBufferUs; + private final long bufferForPlaybackUs; + private final long bufferForPlaybackAfterRebufferUs; + private final PriorityTaskManager priorityTaskManager; + + private int targetBufferSize; + private boolean isBuffering; + + /** + * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. + */ + public DefaultLoadControl() { + this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)); + } + + /** + * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. + * + * @param allocator The {@link DefaultAllocator} used by the loader. + */ + public DefaultLoadControl(DefaultAllocator allocator) { + this(allocator, DEFAULT_MIN_BUFFER_MS, DEFAULT_MAX_BUFFER_MS, DEFAULT_BUFFER_FOR_PLAYBACK_MS, + DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); + } + + /** + * Constructs a new instance. + * + * @param allocator The {@link DefaultAllocator} used by the loader. + * @param minBufferMs The minimum duration of media that the player will attempt to ensure is + * buffered at all times, in milliseconds. + * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in + * milliseconds. + * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or + * resume following a user action such as a seek, in milliseconds. + * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for + * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by + * buffer depletion rather than a user action. + */ + public DefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, + long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs) { + this(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, + null); + } + + /** + * Constructs a new instance. + * + * @param allocator The {@link DefaultAllocator} used by the loader. + * @param minBufferMs The minimum duration of media that the player will attempt to ensure is + * buffered at all times, in milliseconds. + * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in + * milliseconds. + * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or + * resume following a user action such as a seek, in milliseconds. + * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for + * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by + * buffer depletion rather than a user action. + * @param priorityTaskManager If not null, registers itself as a task with priority + * {@link C#PRIORITY_PLAYBACK} during loading periods, and unregisters itself during draining + * periods. + */ + public DefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, + long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs, + PriorityTaskManager priorityTaskManager) { + this.allocator = allocator; + minBufferUs = minBufferMs * 1000L; + maxBufferUs = maxBufferMs * 1000L; + bufferForPlaybackUs = bufferForPlaybackMs * 1000L; + bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L; + this.priorityTaskManager = priorityTaskManager; + } + + @Override + public void onPrepared() { + reset(false); + } + + @Override + public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections) { + targetBufferSize = 0; + for (int i = 0; i < renderers.length; i++) { + if (trackSelections.get(i) != null) { + targetBufferSize += Util.getDefaultBufferSize(renderers[i].getTrackType()); + } + } + allocator.setTargetBufferSize(targetBufferSize); + } + + @Override + public void onStopped() { + reset(true); + } + + @Override + public void onReleased() { + reset(true); + } + + @Override + public Allocator getAllocator() { + return allocator; + } + + @Override + public boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering) { + long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; + return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs; + } + + @Override + public boolean shouldContinueLoading(long bufferedDurationUs) { + int bufferTimeState = getBufferTimeState(bufferedDurationUs); + boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; + boolean wasBuffering = isBuffering; + isBuffering = bufferTimeState == BELOW_LOW_WATERMARK + || (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached); + if (priorityTaskManager != null && isBuffering != wasBuffering) { + if (isBuffering) { + priorityTaskManager.add(C.PRIORITY_PLAYBACK); + } else { + priorityTaskManager.remove(C.PRIORITY_PLAYBACK); + } + } + return isBuffering; + } + + private int getBufferTimeState(long bufferedDurationUs) { + return bufferedDurationUs > maxBufferUs ? ABOVE_HIGH_WATERMARK + : (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS); + } + + private void reset(boolean resetAllocator) { + targetBufferSize = 0; + if (priorityTaskManager != null && isBuffering) { + priorityTaskManager.remove(C.PRIORITY_PLAYBACK); + } + isBuffering = false; + if (resetAllocator) { + allocator.reset(); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/DefaultRenderersFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/DefaultRenderersFactory.java new file mode 100644 index 0000000..0450be3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/DefaultRenderersFactory.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.AudioCapabilities; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.AudioProcessor; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.AudioRendererEventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.MediaCodecAudioRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSessionManager; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.FrameworkMediaCrypto; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.text.TextRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.video.MediaCodecVideoRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.video.VideoRendererEventListener; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Constructor; +import java.util.ArrayList; + +/** + * Default {@link RenderersFactory} implementation. + */ +public class DefaultRenderersFactory implements RenderersFactory { + + /** + * The default maximum duration for which a video renderer can attempt to seamlessly join an + * ongoing playback. + */ + public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000; + + /** + * Modes for using extension renderers. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, + EXTENSION_RENDERER_MODE_PREFER}) + public @interface ExtensionRendererMode {} + /** + * Do not allow use of extension renderers. + */ + public static final int EXTENSION_RENDERER_MODE_OFF = 0; + /** + * Allow use of extension renderers. Extension renderers are indexed after core renderers of the + * same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore + * prefer to use a core renderer to an extension renderer in the case that both are able to play + * a given track. + */ + public static final int EXTENSION_RENDERER_MODE_ON = 1; + /** + * Allow use of extension renderers. Extension renderers are indexed before core renderers of the + * same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore + * prefer to use an extension renderer to a core renderer in the case that both are able to play + * a given track. + */ + public static final int EXTENSION_RENDERER_MODE_PREFER = 2; + + private static final String TAG = "DefaultRenderersFactory"; + + protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; + + private final Context context; + private final DrmSessionManager drmSessionManager; + private final @ExtensionRendererMode int extensionRendererMode; + private final long allowedVideoJoiningTimeMs; + + /** + * @param context A {@link Context}. + */ + public DefaultRenderersFactory(Context context) { + this(context, null); + } + + /** + * @param context A {@link Context}. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected + * playbacks are not required. + */ + public DefaultRenderersFactory(Context context, + DrmSessionManager drmSessionManager) { + this(context, drmSessionManager, EXTENSION_RENDERER_MODE_OFF); + } + + /** + * @param context A {@link Context}. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected + * playbacks are not required.. + * @param extensionRendererMode The extension renderer mode, which determines if and how + * available extension renderers are used. Note that extensions must be included in the + * application build for them to be considered available. + */ + public DefaultRenderersFactory(Context context, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode) { + this(context, drmSessionManager, extensionRendererMode, + DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); + } + + /** + * @param context A {@link Context}. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected + * playbacks are not required.. + * @param extensionRendererMode The extension renderer mode, which determines if and how + * available extension renderers are used. Note that extensions must be included in the + * application build for them to be considered available. + * @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt + * to seamlessly join an ongoing playback. + */ + public DefaultRenderersFactory(Context context, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) { + this.context = context; + this.drmSessionManager = drmSessionManager; + this.extensionRendererMode = extensionRendererMode; + this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs; + } + + @Override + public Renderer[] createRenderers(Handler eventHandler, + VideoRendererEventListener videoRendererEventListener, + AudioRendererEventListener audioRendererEventListener, + TextRenderer.Output textRendererOutput, MetadataRenderer.Output metadataRendererOutput) { + ArrayList renderersList = new ArrayList<>(); + buildVideoRenderers(context, drmSessionManager, allowedVideoJoiningTimeMs, + eventHandler, videoRendererEventListener, extensionRendererMode, renderersList); + buildAudioRenderers(context, drmSessionManager, buildAudioProcessors(), + eventHandler, audioRendererEventListener, extensionRendererMode, renderersList); + buildTextRenderers(context, textRendererOutput, eventHandler.getLooper(), + extensionRendererMode, renderersList); + buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(), + extensionRendererMode, renderersList); + buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); + return renderersList.toArray(new Renderer[renderersList.size()]); + } + + /** + * Builds video renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player + * will not be used for DRM protected playbacks. + * @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video + * renderers can attempt to seamlessly join an ongoing playback. + * @param eventHandler A handler associated with the main thread's looper. + * @param eventListener An event listener. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildVideoRenderers(Context context, + DrmSessionManager drmSessionManager, long allowedVideoJoiningTimeMs, + Handler eventHandler, VideoRendererEventListener eventListener, + @ExtensionRendererMode int extensionRendererMode, ArrayList out) { + out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, + allowedVideoJoiningTimeMs, drmSessionManager, false, eventHandler, eventListener, + MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); + + if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { + return; + } + int extensionRendererIndex = out.size(); + if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { + extensionRendererIndex--; + } + + try { + Class clazz = + Class.forName("com.tangxiaolv.telegramgallery.exoplayer2.ext.vp9.LibvpxVideoRenderer"); + Constructor constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, + VideoRendererEventListener.class, int.class); + Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, + eventHandler, eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); + out.add(extensionRendererIndex++, renderer); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + e.getStackTrace(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Builds audio renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player + * will not be used for DRM protected playbacks. + * @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio + * buffers before output. May be empty. + * @param eventHandler A handler to use when invoking event listeners and outputs. + * @param eventListener An event listener. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildAudioRenderers(Context context, + DrmSessionManager drmSessionManager, + AudioProcessor[] audioProcessors, Handler eventHandler, + AudioRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode, + ArrayList out) { + out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true, + eventHandler, eventListener, AudioCapabilities.getCapabilities(context), audioProcessors)); + + if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { + return; + } + int extensionRendererIndex = out.size(); + if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { + extensionRendererIndex--; + } + + try { + Class clazz = + Class.forName("com.tangxiaolv.telegramgallery.exoplayer2.ext.opus.LibopusAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class, AudioProcessor[].class); + Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, + audioProcessors); + out.add(extensionRendererIndex++, renderer); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + e.getStackTrace(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + Class clazz = + Class.forName("com.tangxiaolv.telegramgallery.exoplayer2.ext.flac.LibflacAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class, AudioProcessor[].class); + Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, + audioProcessors); + out.add(extensionRendererIndex++, renderer); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + e.getStackTrace(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + Class clazz = + Class.forName("com.tangxiaolv.telegramgallery.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class, AudioProcessor[].class); + Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, + audioProcessors); + out.add(extensionRendererIndex++, renderer); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + e.getStackTrace(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Builds text renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param output An output for the renderers. + * @param outputLooper The looper associated with the thread on which the output should be + * called. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildTextRenderers(Context context, TextRenderer.Output output, + Looper outputLooper, @ExtensionRendererMode int extensionRendererMode, + ArrayList out) { + out.add(new TextRenderer(output, outputLooper)); + } + + /** + * Builds metadata renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param output An output for the renderers. + * @param outputLooper The looper associated with the thread on which the output should be + * called. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildMetadataRenderers(Context context, MetadataRenderer.Output output, + Looper outputLooper, @ExtensionRendererMode int extensionRendererMode, + ArrayList out) { + out.add(new MetadataRenderer(output, outputLooper)); + } + + /** + * Builds any miscellaneous renderers used by the player. + * + * @param context The {@link Context} associated with the player. + * @param eventHandler A handler to use when invoking event listeners and outputs. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildMiscellaneousRenderers(Context context, Handler eventHandler, + @ExtensionRendererMode int extensionRendererMode, ArrayList out) { + // Do nothing. + } + + /** + * Builds an array of {@link AudioProcessor}s that will process PCM audio before output. + */ + protected AudioProcessor[] buildAudioProcessors() { + return new AudioProcessor[0]; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlaybackException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlaybackException.java new file mode 100644 index 0000000..0d5ba1c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlaybackException.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Thrown when a non-recoverable playback failure occurs. + */ +public final class ExoPlaybackException extends Exception { + + /** + * The type of source that produced the error. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED}) + public @interface Type {} + /** + * The error occurred loading data from a {@link MediaSource}. + *

    + * Call {@link #getSourceException()} to retrieve the underlying cause. + */ + public static final int TYPE_SOURCE = 0; + /** + * The error occurred in a {@link Renderer}. + *

    + * Call {@link #getRendererException()} to retrieve the underlying cause. + */ + public static final int TYPE_RENDERER = 1; + /** + * The error was an unexpected {@link RuntimeException}. + *

    + * Call {@link #getUnexpectedException()} to retrieve the underlying cause. + */ + public static final int TYPE_UNEXPECTED = 2; + + /** + * The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and + * {@link #TYPE_UNEXPECTED}. + */ + @Type public final int type; + + /** + * If {@link #type} is {@link #TYPE_RENDERER}, this is the index of the renderer. + */ + public final int rendererIndex; + + /** + * Creates an instance of type {@link #TYPE_RENDERER}. + * + * @param cause The cause of the failure. + * @param rendererIndex The index of the renderer in which the failure occurred. + * @return The created instance. + */ + public static ExoPlaybackException createForRenderer(Exception cause, int rendererIndex) { + return new ExoPlaybackException(TYPE_RENDERER, null, cause, rendererIndex); + } + + /** + * Creates an instance of type {@link #TYPE_SOURCE}. + * + * @param cause The cause of the failure. + * @return The created instance. + */ + public static ExoPlaybackException createForSource(IOException cause) { + return new ExoPlaybackException(TYPE_SOURCE, null, cause, C.INDEX_UNSET); + } + + /** + * Creates an instance of type {@link #TYPE_UNEXPECTED}. + * + * @param cause The cause of the failure. + * @return The created instance. + */ + /* package */ static ExoPlaybackException createForUnexpected(RuntimeException cause) { + return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET); + } + + private ExoPlaybackException(@Type int type, String message, Throwable cause, + int rendererIndex) { + super(message, cause); + this.type = type; + this.rendererIndex = rendererIndex; + } + + /** + * Retrieves the underlying error when {@link #type} is {@link #TYPE_SOURCE}. + * + * @throws IllegalStateException If {@link #type} is not {@link #TYPE_SOURCE}. + */ + public IOException getSourceException() { + Assertions.checkState(type == TYPE_SOURCE); + return (IOException) getCause(); + } + + /** + * Retrieves the underlying error when {@link #type} is {@link #TYPE_RENDERER}. + * + * @throws IllegalStateException If {@link #type} is not {@link #TYPE_RENDERER}. + */ + public Exception getRendererException() { + Assertions.checkState(type == TYPE_RENDERER); + return (Exception) getCause(); + } + + /** + * Retrieves the underlying error when {@link #type} is {@link #TYPE_UNEXPECTED}. + * + * @throws IllegalStateException If {@link #type} is not {@link #TYPE_UNEXPECTED}. + */ + public RuntimeException getUnexpectedException() { + Assertions.checkState(type == TYPE_UNEXPECTED); + return (RuntimeException) getCause(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayer.java new file mode 100644 index 0000000..c039b16 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayer.java @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.support.annotation.Nullable; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.MediaCodecAudioRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.source.ConcatenatingMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.ExtractorMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MergingMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SingleSampleMediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.text.TextRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.DefaultTrackSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectionArray; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.video.MediaCodecVideoRenderer; + +/** + * An extensible media player exposing traditional high-level media player functionality, such as + * the ability to buffer media, play, pause and seek. Instances can be obtained from + * {@link ExoPlayerFactory}. + * + *

    Player composition

    + *

    ExoPlayer is designed to make few assumptions about (and hence impose few restrictions on) the + * type of the media being played, how and where it is stored, and how it is rendered. Rather than + * implementing the loading and rendering of media directly, ExoPlayer implementations delegate this + * work to components that are injected when a player is created or when it's prepared for playback. + * Components common to all ExoPlayer implementations are: + *

      + *
    • A {@link MediaSource} that defines the media to be played, loads the media, and from + * which the loaded media can be read. A MediaSource is injected via {@link #prepare} at the start + * of playback. The library modules provide default implementations for regular media files + * ({@link ExtractorMediaSource}), DASH (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS + * (HlsMediaSource), implementations for merging ({@link MergingMediaSource}) and concatenating + * ({@link ConcatenatingMediaSource}) other MediaSources, and an implementation for loading single + * samples ({@link SingleSampleMediaSource}) most often used for side-loaded subtitle and closed + * caption files.
    • + *
    • {@link Renderer}s that render individual components of the media. The library + * provides default implementations for common media types ({@link MediaCodecVideoRenderer}, + * {@link MediaCodecAudioRenderer}, {@link TextRenderer} and {@link MetadataRenderer}). A Renderer + * consumes media of its corresponding type from the MediaSource being played. Renderers are + * injected when the player is created.
    • + *
    • A {@link TrackSelector} that selects tracks provided by the MediaSource to be + * consumed by each of the available Renderers. The library provides a default implementation + * ({@link DefaultTrackSelector}) suitable for most use cases. A TrackSelector is injected when + * the player is created.
    • + *
    • A {@link LoadControl} that controls when the MediaSource buffers more media, and how + * much media is buffered. The library provides a default implementation + * ({@link DefaultLoadControl}) suitable for most use cases. A LoadControl is injected when the + * player is created.
    • + *
    + *

    An ExoPlayer can be built using the default components provided by the library, but may also + * be built using custom implementations if non-standard behaviors are required. For example a + * custom LoadControl could be injected to change the player's buffering strategy, or a custom + * Renderer could be injected to use a video codec not supported natively by Android. + * + *

    The concept of injecting components that implement pieces of player functionality is present + * throughout the library. The default component implementations listed above delegate work to + * further injected components. This allows many sub-components to be individually replaced with + * custom implementations. For example the default MediaSource implementations require one or more + * {@link DataSource} factories to be injected via their constructors. By providing a custom factory + * it's possible to load data from a non-standard source or through a different network stack. + * + *

    Threading model

    + *

    The figure below shows ExoPlayer's threading model.

    + *

    + * ExoPlayer's threading model + *

    + * + *
      + *
    • It is recommended that ExoPlayer instances are created and accessed from a single application + * thread. The application's main thread is ideal. Accessing an instance from multiple threads is + * discouraged, however if an application does wish to do this then it may do so provided that it + * ensures accesses are synchronized.
    • + *
    • Registered listeners are called on the thread that created the ExoPlayer instance.
    • + *
    • An internal playback thread is responsible for playback. Injected player components such as + * Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this + * thread.
    • + *
    • When the application performs an operation on the player, for example a seek, a message is + * delivered to the internal playback thread via a message queue. The internal playback thread + * consumes messages from the queue and performs the corresponding operations. Similarly, when a + * playback event occurs on the internal playback thread, a message is delivered to the application + * thread via a second message queue. The application thread consumes messages from the queue, + * updating the application visible state and calling corresponding listener methods.
    • + *
    • Injected player components may use additional background threads. For example a MediaSource + * may use a background thread to load data. These are implementation specific.
    • + *
    + */ +public interface ExoPlayer { + + /** + * Listener of changes in player state. + */ + interface EventListener { + + /** + * Called when the timeline and/or manifest has been refreshed. + *

    + * Note that if the timeline has changed then a position discontinuity may also have occurred. + * For example the current period index may have changed as a result of periods being added or + * removed from the timeline. The will not be reported via a separate call to + * {@link #onPositionDiscontinuity()}. + * + * @param timeline The latest timeline. Never null, but may be empty. + * @param manifest The latest manifest. May be null. + */ + void onTimelineChanged(Timeline timeline, Object manifest); + + /** + * Called when the available or selected tracks change. + * + * @param trackGroups The available tracks. Never null, but may be of length zero. + * @param trackSelections The track selections for each {@link Renderer}. Never null and always + * of length {@link #getRendererCount()}, but may contain null elements. + */ + void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections); + + /** + * Called when the player starts or stops loading the source. + * + * @param isLoading Whether the source is currently being loaded. + */ + void onLoadingChanged(boolean isLoading); + + /** + * Called when the value returned from either {@link #getPlayWhenReady()} or + * {@link #getPlaybackState()} changes. + * + * @param playWhenReady Whether playback will proceed when ready. + * @param playbackState One of the {@code STATE} constants defined in the {@link ExoPlayer} + * interface. + */ + void onPlayerStateChanged(boolean playWhenReady, int playbackState); + + /** + * Called when an error occurs. The playback state will transition to {@link #STATE_IDLE} + * immediately after this method is called. The player instance can still be used, and + * {@link #release()} must still be called on the player should it no longer be required. + * + * @param error The error. + */ + void onPlayerError(ExoPlaybackException error); + + /** + * Called when a position discontinuity occurs without a change to the timeline. A position + * discontinuity occurs when the current window or period index changes (as a result of playback + * transitioning from one period in the timeline to the next), or when the playback position + * jumps within the period currently being played (as a result of a seek being performed, or + * when the source introduces a discontinuity internally). + *

    + * When a position discontinuity occurs as a result of a change to the timeline this method is + * not called. {@link #onTimelineChanged(Timeline, Object)} is called in this case. + */ + void onPositionDiscontinuity(); + + /** + * Called when the current playback parameters change. The playback parameters may change due to + * a call to {@link ExoPlayer#setPlaybackParameters(PlaybackParameters)}, or the player itself + * may change them (for example, if audio playback switches to passthrough mode, where speed + * adjustment is no longer possible). + * + * @param playbackParameters The playback parameters. + */ + void onPlaybackParametersChanged(PlaybackParameters playbackParameters); + + } + + /** + * A component of an {@link ExoPlayer} that can receive messages on the playback thread. + *

    + * Messages can be delivered to a component via {@link #sendMessages} and + * {@link #blockingSendMessages}. + */ + interface ExoPlayerComponent { + + /** + * Handles a message delivered to the component. Called on the playback thread. + * + * @param messageType The message type. + * @param message The message. + * @throws ExoPlaybackException If an error occurred whilst handling the message. + */ + void handleMessage(int messageType, Object message) throws ExoPlaybackException; + + } + + /** + * Defines a message and a target {@link ExoPlayerComponent} to receive it. + */ + final class ExoPlayerMessage { + + /** + * The target to receive the message. + */ + public final ExoPlayerComponent target; + /** + * The type of the message. + */ + public final int messageType; + /** + * The message. + */ + public final Object message; + + /** + * @param target The target of the message. + * @param messageType The message type. + * @param message The message. + */ + public ExoPlayerMessage(ExoPlayerComponent target, int messageType, Object message) { + this.target = target; + this.messageType = messageType; + this.message = message; + } + + } + + /** + * The player does not have a source to play, so it is neither buffering nor ready to play. + */ + int STATE_IDLE = 1; + /** + * The player not able to immediately play from the current position. The cause is + * {@link Renderer} specific, but this state typically occurs when more data needs to be + * loaded to be ready to play, or more data needs to be buffered for playback to resume. + */ + int STATE_BUFFERING = 2; + /** + * The player is able to immediately play from the current position. The player will be playing if + * {@link #getPlayWhenReady()} returns true, and paused otherwise. + */ + int STATE_READY = 3; + /** + * The player has finished playing the media. + */ + int STATE_ENDED = 4; + + /** + * Register a listener to receive events from the player. The listener's methods will be called on + * the thread that was used to construct the player. + * + * @param listener The listener to register. + */ + void addListener(EventListener listener); + + /** + * Unregister a listener. The listener will no longer receive events from the player. + * + * @param listener The listener to unregister. + */ + void removeListener(EventListener listener); + + /** + * Returns the current state of the player. + * + * @return One of the {@code STATE} constants defined in this interface. + */ + int getPlaybackState(); + + /** + * Prepares the player to play the provided {@link MediaSource}. Equivalent to + * {@code prepare(mediaSource, true, true)}. + */ + void prepare(MediaSource mediaSource); + + /** + * Prepares the player to play the provided {@link MediaSource}, optionally resetting the playback + * position the default position in the first {@link Timeline.Window}. + * + * @param mediaSource The {@link MediaSource} to play. + * @param resetPosition Whether the playback position should be reset to the default position in + * the first {@link Timeline.Window}. If false, playback will start from the position defined + * by {@link #getCurrentWindowIndex()} and {@link #getCurrentPosition()}. + * @param resetState Whether the timeline, manifest, tracks and track selections should be reset. + * Should be true unless the player is being prepared to play the same media as it was playing + * previously (e.g. if playback failed and is being retried). + */ + void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState); + + /** + * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. + *

    + * If the player is already in the ready state then this method can be used to pause and resume + * playback. + * + * @param playWhenReady Whether playback should proceed when ready. + */ + void setPlayWhenReady(boolean playWhenReady); + + /** + * Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. + * + * @return Whether playback will proceed when ready. + */ + boolean getPlayWhenReady(); + + /** + * Whether the player is currently loading the source. + * + * @return Whether the player is currently loading the source. + */ + boolean isLoading(); + + /** + * Seeks to the default position associated with the current window. The position can depend on + * the type of source passed to {@link #prepare(MediaSource)}. For live streams it will typically + * be the live edge of the window. For other streams it will typically be the start of the window. + */ + void seekToDefaultPosition(); + + /** + * Seeks to the default position associated with the specified window. The position can depend on + * the type of source passed to {@link #prepare(MediaSource)}. For live streams it will typically + * be the live edge of the window. For other streams it will typically be the start of the window. + * + * @param windowIndex The index of the window whose associated default position should be seeked + * to. + */ + void seekToDefaultPosition(int windowIndex); + + /** + * Seeks to a position specified in milliseconds in the current window. + * + * @param positionMs The seek position in the current window, or {@link C#TIME_UNSET} to seek to + * the window's default position. + */ + void seekTo(long positionMs); + + /** + * Seeks to a position specified in milliseconds in the specified window. + * + * @param windowIndex The index of the window. + * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek to + * the window's default position. + */ + void seekTo(int windowIndex, long positionMs); + + /** + * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the + * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment. + *

    + * Playback parameters changes may cause the player to buffer. + * {@link EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever + * the currently active playback parameters change. When that listener is called, the parameters + * passed to it may not match {@code playbackParameters}. For example, the chosen speed or pitch + * may be out of range, in which case they are constrained to a set of permitted values. If it is + * not possible to change the playback parameters, the listener will not be invoked. + * + * @param playbackParameters The playback parameters, or {@code null} to use the defaults. + */ + void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters); + + /** + * Returns the currently active playback parameters. + * + * @see EventListener#onPlaybackParametersChanged(PlaybackParameters) + */ + PlaybackParameters getPlaybackParameters(); + + /** + * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention + * is to pause playback. + *

    + * Calling this method will cause the playback state to transition to {@link #STATE_IDLE}. The + * player instance can still be used, and {@link #release()} must still be called on the player if + * it's no longer required. + *

    + * Calling this method does not reset the playback position. + */ + void stop(); + + /** + * Releases the player. This method must be called when the player is no longer required. The + * player must not be used after calling this method. + */ + void release(); + + /** + * Sends messages to their target components. The messages are delivered on the playback thread. + * If a component throws an {@link ExoPlaybackException} then it is propagated out of the player + * as an error. + * + * @param messages The messages to be sent. + */ + void sendMessages(ExoPlayerMessage... messages); + + /** + * Variant of {@link #sendMessages(ExoPlayerMessage...)} that blocks until after the messages have + * been delivered. + * + * @param messages The messages to be sent. + */ + void blockingSendMessages(ExoPlayerMessage... messages); + + /** + * Returns the number of renderers. + */ + int getRendererCount(); + + /** + * Returns the track type that the renderer at a given index handles. + * + * @see Renderer#getTrackType() + * @param index The index of the renderer. + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getRendererType(int index); + + /** + * Returns the available track groups. + */ + TrackGroupArray getCurrentTrackGroups(); + + /** + * Returns the current track selections for each renderer. + */ + TrackSelectionArray getCurrentTrackSelections(); + + /** + * Returns the current manifest. The type depends on the {@link MediaSource} passed to + * {@link #prepare}. May be null. + */ + Object getCurrentManifest(); + + /** + * Returns the current {@link Timeline}. Never null, but may be empty. + */ + Timeline getCurrentTimeline(); + + /** + * Returns the index of the period currently being played. + */ + int getCurrentPeriodIndex(); + + /** + * Returns the index of the window currently being played. + */ + int getCurrentWindowIndex(); + + /** + * Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the + * duration is not known. + */ + long getDuration(); + + /** + * Returns the playback position in the current window, in milliseconds. + */ + long getCurrentPosition(); + + /** + * Returns an estimate of the position in the current window up to which data is buffered, in + * milliseconds. + */ + long getBufferedPosition(); + + /** + * Returns an estimate of the percentage in the current window up to which data is buffered, or 0 + * if no estimate is available. + */ + int getBufferedPercentage(); + + /** + * Returns whether the current window is dynamic, or {@code false} if the {@link Timeline} is + * empty. + * + * @see Timeline.Window#isDynamic + */ + boolean isCurrentWindowDynamic(); + + /** + * Returns whether the current window is seekable, or {@code false} if the {@link Timeline} is + * empty. + * + * @see Timeline.Window#isSeekable + */ + boolean isCurrentWindowSeekable(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerFactory.java new file mode 100644 index 0000000..6cb99a4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerFactory.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.content.Context; +import android.os.Looper; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSessionManager; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.FrameworkMediaCrypto; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelector; + +/** + * A factory for {@link ExoPlayer} instances. + */ +public final class ExoPlayerFactory { + + private ExoPlayerFactory() {} + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + * @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}. + */ + @Deprecated + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + LoadControl loadControl) { + RenderersFactory renderersFactory = new DefaultRenderersFactory(context); + return newSimpleInstance(renderersFactory, trackSelector, loadControl); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. Available extension renderers are not used. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance + * will not be used for DRM protected playbacks. + * @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}. + */ + @Deprecated + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + LoadControl loadControl, DrmSessionManager drmSessionManager) { + RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager); + return newSimpleInstance(renderersFactory, trackSelector, loadControl); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance + * will not be used for DRM protected playbacks. + * @param extensionRendererMode The extension renderer mode, which determines if and how available + * extension renderers are used. Note that extensions must be included in the application + * build for them to be considered available. + * @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}. + */ + @Deprecated + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + LoadControl loadControl, DrmSessionManager drmSessionManager, + @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) { + RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager, + extensionRendererMode); + return newSimpleInstance(renderersFactory, trackSelector, loadControl); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance + * will not be used for DRM protected playbacks. + * @param extensionRendererMode The extension renderer mode, which determines if and how available + * extension renderers are used. Note that extensions must be included in the application + * build for them to be considered available. + * @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to + * seamlessly join an ongoing playback. + * @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}. + */ + @Deprecated + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + LoadControl loadControl, DrmSessionManager drmSessionManager, + @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode, + long allowedVideoJoiningTimeMs) { + RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager, + extensionRendererMode, allowedVideoJoiningTimeMs); + return newSimpleInstance(renderersFactory, trackSelector, loadControl); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + */ + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector) { + return newSimpleInstance(new DefaultRenderersFactory(context), trackSelector); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + */ + public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory, + TrackSelector trackSelector) { + return newSimpleInstance(renderersFactory, trackSelector, new DefaultLoadControl()); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + */ + public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory, + TrackSelector trackSelector, LoadControl loadControl) { + return new SimpleExoPlayer(renderersFactory, trackSelector, loadControl); + } + + /** + * Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param renderers The {@link Renderer}s that will be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + */ + public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector) { + return newInstance(renderers, trackSelector, new DefaultLoadControl()); + } + + /** + * Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param renderers The {@link Renderer}s that will be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + */ + public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector, + LoadControl loadControl) { + return new ExoPlayerImpl(renderers, trackSelector, loadControl); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerImpl.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerImpl.java new file mode 100644 index 0000000..490d670 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerImpl.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.annotation.SuppressLint; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.Nullable; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayerImplInternal.PlaybackInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayerImplInternal.SourceInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectionArray; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectorResult; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayerFactory}. + */ +/* package */ final class ExoPlayerImpl implements ExoPlayer { + + private static final String TAG = "ExoPlayerImpl"; + + private final Renderer[] renderers; + private final TrackSelector trackSelector; + private final TrackSelectionArray emptyTrackSelections; + private final Handler eventHandler; + private final ExoPlayerImplInternal internalPlayer; + private final CopyOnWriteArraySet listeners; + private final Timeline.Window window; + private final Timeline.Period period; + + private boolean tracksSelected; + private boolean playWhenReady; + private int playbackState; + private int pendingSeekAcks; + private int pendingPrepareAcks; + private boolean isLoading; + private Timeline timeline; + private Object manifest; + private TrackGroupArray trackGroups; + private TrackSelectionArray trackSelections; + private PlaybackParameters playbackParameters; + + // Playback information when there is no pending seek/set source operation. + private PlaybackInfo playbackInfo; + + // Playback information when there is a pending seek/set source operation. + private int maskingWindowIndex; + private int maskingPeriodIndex; + private long maskingWindowPositionMs; + + /** + * Constructs an instance. Must be called from a thread that has an associated {@link Looper}. + * + * @param renderers The {@link Renderer}s that will be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + */ + @SuppressLint("HandlerLeak") + public ExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { + Assertions.checkState(renderers.length > 0); + this.renderers = Assertions.checkNotNull(renderers); + this.trackSelector = Assertions.checkNotNull(trackSelector); + this.playWhenReady = false; + this.playbackState = STATE_IDLE; + this.listeners = new CopyOnWriteArraySet<>(); + emptyTrackSelections = new TrackSelectionArray(new TrackSelection[renderers.length]); + timeline = Timeline.EMPTY; + window = new Timeline.Window(); + period = new Timeline.Period(); + trackGroups = TrackGroupArray.EMPTY; + trackSelections = emptyTrackSelections; + playbackParameters = PlaybackParameters.DEFAULT; + eventHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + ExoPlayerImpl.this.handleEvent(msg); + } + }; + playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0, 0); + internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady, + eventHandler, playbackInfo, this); + } + + @Override + public void addListener(EventListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(EventListener listener) { + listeners.remove(listener); + } + + @Override + public int getPlaybackState() { + return playbackState; + } + + @Override + public void prepare(MediaSource mediaSource) { + prepare(mediaSource, true, true); + } + + @Override + public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { + if (resetState) { + if (!timeline.isEmpty() || manifest != null) { + timeline = Timeline.EMPTY; + manifest = null; + for (EventListener listener : listeners) { + listener.onTimelineChanged(timeline, manifest); + } + } + if (tracksSelected) { + tracksSelected = false; + trackGroups = TrackGroupArray.EMPTY; + trackSelections = emptyTrackSelections; + trackSelector.onSelectionActivated(null); + for (EventListener listener : listeners) { + listener.onTracksChanged(trackGroups, trackSelections); + } + } + } + pendingPrepareAcks++; + internalPlayer.prepare(mediaSource, resetPosition); + } + + @Override + public void setPlayWhenReady(boolean playWhenReady) { + if (this.playWhenReady != playWhenReady) { + this.playWhenReady = playWhenReady; + internalPlayer.setPlayWhenReady(playWhenReady); + for (EventListener listener : listeners) { + listener.onPlayerStateChanged(playWhenReady, playbackState); + } + } + } + + @Override + public boolean getPlayWhenReady() { + return playWhenReady; + } + + @Override + public boolean isLoading() { + return isLoading; + } + + @Override + public void seekToDefaultPosition() { + seekToDefaultPosition(getCurrentWindowIndex()); + } + + @Override + public void seekToDefaultPosition(int windowIndex) { + seekTo(windowIndex, C.TIME_UNSET); + } + + @Override + public void seekTo(long positionMs) { + seekTo(getCurrentWindowIndex(), positionMs); + } + + @Override + public void seekTo(int windowIndex, long positionMs) { + if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) { + throw new IllegalSeekPositionException(timeline, windowIndex, positionMs); + } + pendingSeekAcks++; + maskingWindowIndex = windowIndex; + if (timeline.isEmpty()) { + maskingPeriodIndex = 0; + } else { + timeline.getWindow(windowIndex, window); + long resolvedPositionMs = + positionMs == C.TIME_UNSET ? window.getDefaultPositionUs() : positionMs; + int periodIndex = window.firstPeriodIndex; + long periodPositionUs = window.getPositionInFirstPeriodUs() + C.msToUs(resolvedPositionMs); + long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs(); + while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs + && periodIndex < window.lastPeriodIndex) { + periodPositionUs -= periodDurationUs; + periodDurationUs = timeline.getPeriod(++periodIndex, period).getDurationUs(); + } + maskingPeriodIndex = periodIndex; + } + if (positionMs == C.TIME_UNSET) { + maskingWindowPositionMs = 0; + internalPlayer.seekTo(timeline, windowIndex, C.TIME_UNSET); + } else { + maskingWindowPositionMs = positionMs; + internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); + for (EventListener listener : listeners) { + listener.onPositionDiscontinuity(); + } + } + } + + @Override + public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { + if (playbackParameters == null) { + playbackParameters = PlaybackParameters.DEFAULT; + } + internalPlayer.setPlaybackParameters(playbackParameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return playbackParameters; + } + + @Override + public void stop() { + internalPlayer.stop(); + } + + @Override + public void release() { + internalPlayer.release(); + eventHandler.removeCallbacksAndMessages(null); + } + + @Override + public void sendMessages(ExoPlayerMessage... messages) { + internalPlayer.sendMessages(messages); + } + + @Override + public void blockingSendMessages(ExoPlayerMessage... messages) { + internalPlayer.blockingSendMessages(messages); + } + + @Override + public int getCurrentPeriodIndex() { + if (timeline.isEmpty() || pendingSeekAcks > 0) { + return maskingPeriodIndex; + } else { + return playbackInfo.periodIndex; + } + } + + @Override + public int getCurrentWindowIndex() { + if (timeline.isEmpty() || pendingSeekAcks > 0) { + return maskingWindowIndex; + } else { + return timeline.getPeriod(playbackInfo.periodIndex, period).windowIndex; + } + } + + @Override + public long getDuration() { + if (timeline.isEmpty()) { + return C.TIME_UNSET; + } + return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + } + + @Override + public long getCurrentPosition() { + if (timeline.isEmpty() || pendingSeekAcks > 0) { + return maskingWindowPositionMs; + } else { + timeline.getPeriod(playbackInfo.periodIndex, period); + return period.getPositionInWindowMs() + C.usToMs(playbackInfo.positionUs); + } + } + + @Override + public long getBufferedPosition() { + // TODO - Implement this properly. + if (timeline.isEmpty() || pendingSeekAcks > 0) { + return maskingWindowPositionMs; + } else { + timeline.getPeriod(playbackInfo.periodIndex, period); + return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs); + } + } + + @Override + public int getBufferedPercentage() { + if (timeline.isEmpty()) { + return 0; + } + long bufferedPosition = getBufferedPosition(); + long duration = getDuration(); + return (bufferedPosition == C.TIME_UNSET || duration == C.TIME_UNSET) ? 0 + : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); + } + + @Override + public boolean isCurrentWindowDynamic() { + return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; + } + + @Override + public boolean isCurrentWindowSeekable() { + return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; + } + + @Override + public int getRendererCount() { + return renderers.length; + } + + @Override + public int getRendererType(int index) { + return renderers[index].getTrackType(); + } + + @Override + public TrackGroupArray getCurrentTrackGroups() { + return trackGroups; + } + + @Override + public TrackSelectionArray getCurrentTrackSelections() { + return trackSelections; + } + + @Override + public Timeline getCurrentTimeline() { + return timeline; + } + + @Override + public Object getCurrentManifest() { + return manifest; + } + + // Not private so it can be called from an inner class without going through a thunk method. + /* package */ void handleEvent(Message msg) { + switch (msg.what) { + case ExoPlayerImplInternal.MSG_PREPARE_ACK: { + pendingPrepareAcks--; + break; + } + case ExoPlayerImplInternal.MSG_STATE_CHANGED: { + playbackState = msg.arg1; + for (EventListener listener : listeners) { + listener.onPlayerStateChanged(playWhenReady, playbackState); + } + break; + } + case ExoPlayerImplInternal.MSG_LOADING_CHANGED: { + isLoading = msg.arg1 != 0; + for (EventListener listener : listeners) { + listener.onLoadingChanged(isLoading); + } + break; + } + case ExoPlayerImplInternal.MSG_TRACKS_CHANGED: { + if (pendingPrepareAcks == 0) { + TrackSelectorResult trackSelectorResult = (TrackSelectorResult) msg.obj; + tracksSelected = true; + trackGroups = trackSelectorResult.groups; + trackSelections = trackSelectorResult.selections; + trackSelector.onSelectionActivated(trackSelectorResult.info); + for (EventListener listener : listeners) { + listener.onTracksChanged(trackGroups, trackSelections); + } + } + break; + } + case ExoPlayerImplInternal.MSG_SEEK_ACK: { + if (--pendingSeekAcks == 0) { + playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; + if (msg.arg1 != 0) { + for (EventListener listener : listeners) { + listener.onPositionDiscontinuity(); + } + } + } + break; + } + case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: { + if (pendingSeekAcks == 0) { + playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; + for (EventListener listener : listeners) { + listener.onPositionDiscontinuity(); + } + } + break; + } + case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: { + SourceInfo sourceInfo = (SourceInfo) msg.obj; + pendingSeekAcks -= sourceInfo.seekAcks; + if (pendingPrepareAcks == 0) { + timeline = sourceInfo.timeline; + manifest = sourceInfo.manifest; + playbackInfo = sourceInfo.playbackInfo; + for (EventListener listener : listeners) { + listener.onTimelineChanged(timeline, manifest); + } + } + break; + } + case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: { + PlaybackParameters playbackParameters = (PlaybackParameters) msg.obj; + if (!this.playbackParameters.equals(playbackParameters)) { + this.playbackParameters = playbackParameters; + for (EventListener listener : listeners) { + listener.onPlaybackParametersChanged(playbackParameters); + } + } + break; + } + case ExoPlayerImplInternal.MSG_ERROR: { + ExoPlaybackException exception = (ExoPlaybackException) msg.obj; + for (EventListener listener : listeners) { + listener.onPlayerError(exception); + } + break; + } + default: + throw new IllegalStateException(); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerImplInternal.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerImplInternal.java new file mode 100644 index 0000000..05794ad --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerImplInternal.java @@ -0,0 +1,1567 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.Process; +import android.os.SystemClock; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer.ExoPlayerMessage; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaPeriod; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectionArray; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectorResult; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MediaClock; +import com.tangxiaolv.telegramgallery.exoplayer2.util.StandaloneMediaClock; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TraceUtil; +import java.io.IOException; + +/** + * Implements the internal behavior of {@link ExoPlayerImpl}. + */ +/* package */ final class ExoPlayerImplInternal implements Handler.Callback, + MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener { + + /** + * Playback position information which is read on the application's thread by + * {@link ExoPlayerImpl} and read/written internally on the player's thread. + */ + public static final class PlaybackInfo { + + public final int periodIndex; + public final long startPositionUs; + + public volatile long positionUs; + public volatile long bufferedPositionUs; + + public PlaybackInfo(int periodIndex, long startPositionUs) { + this.periodIndex = periodIndex; + this.startPositionUs = startPositionUs; + positionUs = startPositionUs; + bufferedPositionUs = startPositionUs; + } + + public PlaybackInfo copyWithPeriodIndex(int periodIndex) { + PlaybackInfo playbackInfo = new PlaybackInfo(periodIndex, startPositionUs); + playbackInfo.positionUs = positionUs; + playbackInfo.bufferedPositionUs = bufferedPositionUs; + return playbackInfo; + } + + } + + public static final class SourceInfo { + + public final Timeline timeline; + public final Object manifest; + public final PlaybackInfo playbackInfo; + public final int seekAcks; + + public SourceInfo(Timeline timeline, Object manifest, PlaybackInfo playbackInfo, int seekAcks) { + this.timeline = timeline; + this.manifest = manifest; + this.playbackInfo = playbackInfo; + this.seekAcks = seekAcks; + } + + } + + private static final String TAG = "ExoPlayerImplInternal"; + + // External messages + public static final int MSG_PREPARE_ACK = 0; + public static final int MSG_STATE_CHANGED = 1; + public static final int MSG_LOADING_CHANGED = 2; + public static final int MSG_TRACKS_CHANGED = 3; + public static final int MSG_SEEK_ACK = 4; + public static final int MSG_POSITION_DISCONTINUITY = 5; + public static final int MSG_SOURCE_INFO_REFRESHED = 6; + public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 7; + public static final int MSG_ERROR = 8; + + // Internal messages + private static final int MSG_PREPARE = 0; + private static final int MSG_SET_PLAY_WHEN_READY = 1; + private static final int MSG_DO_SOME_WORK = 2; + private static final int MSG_SEEK_TO = 3; + private static final int MSG_SET_PLAYBACK_PARAMETERS = 4; + private static final int MSG_STOP = 5; + private static final int MSG_RELEASE = 6; + private static final int MSG_REFRESH_SOURCE_INFO = 7; + private static final int MSG_PERIOD_PREPARED = 8; + private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 9; + private static final int MSG_TRACK_SELECTION_INVALIDATED = 10; + private static final int MSG_CUSTOM = 11; + + private static final int PREPARING_SOURCE_INTERVAL_MS = 10; + private static final int RENDERING_INTERVAL_MS = 10; + private static final int IDLE_INTERVAL_MS = 1000; + + /** + * Limits the maximum number of periods to buffer ahead of the current playing period. The + * buffering policy normally prevents buffering too far ahead, but the policy could allow too many + * small periods to be buffered if the period count were not limited. + */ + private static final int MAXIMUM_BUFFER_AHEAD_PERIODS = 100; + + /** + * Offset added to all sample timestamps read by renderers to make them non-negative. This is + * provided for convenience of sources that may return negative timestamps due to prerolling + * samples from a keyframe before their first sample with timestamp zero, so it must be set to a + * value greater than or equal to the maximum key-frame interval in seekable periods. + */ + private static final int RENDERER_TIMESTAMP_OFFSET_US = 60000000; + + private final Renderer[] renderers; + private final RendererCapabilities[] rendererCapabilities; + private final TrackSelector trackSelector; + private final LoadControl loadControl; + private final StandaloneMediaClock standaloneMediaClock; + private final Handler handler; + private final HandlerThread internalPlaybackThread; + private final Handler eventHandler; + private final ExoPlayer player; + private final Timeline.Window window; + private final Timeline.Period period; + + private PlaybackInfo playbackInfo; + private PlaybackParameters playbackParameters; + private Renderer rendererMediaClockSource; + private MediaClock rendererMediaClock; + private MediaSource mediaSource; + private Renderer[] enabledRenderers; + private boolean released; + private boolean playWhenReady; + private boolean rebuffering; + private boolean isLoading; + private int state; + private int customMessagesSent; + private int customMessagesProcessed; + private long elapsedRealtimeUs; + + private int pendingInitialSeekCount; + private SeekPosition pendingSeekPosition; + private long rendererPositionUs; + + private MediaPeriodHolder loadingPeriodHolder; + private MediaPeriodHolder readingPeriodHolder; + private MediaPeriodHolder playingPeriodHolder; + + private Timeline timeline; + + public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, + LoadControl loadControl, boolean playWhenReady, Handler eventHandler, + PlaybackInfo playbackInfo, ExoPlayer player) { + this.renderers = renderers; + this.trackSelector = trackSelector; + this.loadControl = loadControl; + this.playWhenReady = playWhenReady; + this.eventHandler = eventHandler; + this.state = ExoPlayer.STATE_IDLE; + this.playbackInfo = playbackInfo; + this.player = player; + + rendererCapabilities = new RendererCapabilities[renderers.length]; + for (int i = 0; i < renderers.length; i++) { + renderers[i].setIndex(i); + rendererCapabilities[i] = renderers[i].getCapabilities(); + } + standaloneMediaClock = new StandaloneMediaClock(); + enabledRenderers = new Renderer[0]; + window = new Timeline.Window(); + period = new Timeline.Period(); + trackSelector.init(this); + playbackParameters = PlaybackParameters.DEFAULT; + + // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can + // not normally change to this priority" is incorrect. + internalPlaybackThread = new HandlerThread("ExoPlayerImplInternal:Handler", + Process.THREAD_PRIORITY_AUDIO); + internalPlaybackThread.start(); + handler = new Handler(internalPlaybackThread.getLooper(), this); + } + + public void prepare(MediaSource mediaSource, boolean resetPosition) { + handler.obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, 0, mediaSource) + .sendToTarget(); + } + + public void setPlayWhenReady(boolean playWhenReady) { + handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget(); + } + + public void seekTo(Timeline timeline, int windowIndex, long positionUs) { + handler.obtainMessage(MSG_SEEK_TO, new SeekPosition(timeline, windowIndex, positionUs)) + .sendToTarget(); + } + + public void setPlaybackParameters(PlaybackParameters playbackParameters) { + handler.obtainMessage(MSG_SET_PLAYBACK_PARAMETERS, playbackParameters).sendToTarget(); + } + + public void stop() { + handler.sendEmptyMessage(MSG_STOP); + } + + public void sendMessages(ExoPlayerMessage... messages) { + if (released) { + return; + } + customMessagesSent++; + handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget(); + } + + public synchronized void blockingSendMessages(ExoPlayerMessage... messages) { + if (released) { + return; + } + int messageNumber = customMessagesSent++; + handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget(); + while (customMessagesProcessed <= messageNumber) { + try { + wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + public synchronized void release() { + if (released) { + return; + } + handler.sendEmptyMessage(MSG_RELEASE); + while (!released) { + try { + wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + internalPlaybackThread.quit(); + } + + // MediaSource.Listener implementation. + + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + handler.obtainMessage(MSG_REFRESH_SOURCE_INFO, Pair.create(timeline, manifest)).sendToTarget(); + } + + // MediaPeriod.Callback implementation. + + @Override + public void onPrepared(MediaPeriod source) { + handler.obtainMessage(MSG_PERIOD_PREPARED, source).sendToTarget(); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + handler.obtainMessage(MSG_SOURCE_CONTINUE_LOADING_REQUESTED, source).sendToTarget(); + } + + // TrackSelector.InvalidationListener implementation. + + @Override + public void onTrackSelectionsInvalidated() { + handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED); + } + + // Handler.Callback implementation. + + @SuppressWarnings("unchecked") + @Override + public boolean handleMessage(Message msg) { + try { + switch (msg.what) { + case MSG_PREPARE: { + prepareInternal((MediaSource) msg.obj, msg.arg1 != 0); + return true; + } + case MSG_SET_PLAY_WHEN_READY: { + setPlayWhenReadyInternal(msg.arg1 != 0); + return true; + } + case MSG_DO_SOME_WORK: { + doSomeWork(); + return true; + } + case MSG_SEEK_TO: { + seekToInternal((SeekPosition) msg.obj); + return true; + } + case MSG_SET_PLAYBACK_PARAMETERS: { + setPlaybackParametersInternal((PlaybackParameters) msg.obj); + return true; + } + case MSG_STOP: { + stopInternal(); + return true; + } + case MSG_RELEASE: { + releaseInternal(); + return true; + } + case MSG_PERIOD_PREPARED: { + handlePeriodPrepared((MediaPeriod) msg.obj); + return true; + } + case MSG_REFRESH_SOURCE_INFO: { + handleSourceInfoRefreshed((Pair) msg.obj); + return true; + } + case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: { + handleContinueLoadingRequested((MediaPeriod) msg.obj); + return true; + } + case MSG_TRACK_SELECTION_INVALIDATED: { + reselectTracksInternal(); + return true; + } + case MSG_CUSTOM: { + sendMessagesInternal((ExoPlayerMessage[]) msg.obj); + return true; + } + default: + return false; + } + } catch (ExoPlaybackException e) { + eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); + stopInternal(); + return true; + } catch (IOException e) { + eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget(); + stopInternal(); + return true; + } catch (RuntimeException e) { + eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e)) + .sendToTarget(); + stopInternal(); + return true; + } + } + + // Private methods. + + private void setState(int state) { + if (this.state != state) { + this.state = state; + eventHandler.obtainMessage(MSG_STATE_CHANGED, state, 0).sendToTarget(); + } + } + + private void setIsLoading(boolean isLoading) { + if (this.isLoading != isLoading) { + this.isLoading = isLoading; + eventHandler.obtainMessage(MSG_LOADING_CHANGED, isLoading ? 1 : 0, 0).sendToTarget(); + } + } + + private void prepareInternal(MediaSource mediaSource, boolean resetPosition) { + eventHandler.sendEmptyMessage(MSG_PREPARE_ACK); + resetInternal(true); + loadControl.onPrepared(); + if (resetPosition) { + playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); + } + this.mediaSource = mediaSource; + mediaSource.prepareSource(player, true, this); + setState(ExoPlayer.STATE_BUFFERING); + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } + + private void setPlayWhenReadyInternal(boolean playWhenReady) throws ExoPlaybackException { + rebuffering = false; + this.playWhenReady = playWhenReady; + if (!playWhenReady) { + stopRenderers(); + updatePlaybackPositions(); + } else { + if (state == ExoPlayer.STATE_READY) { + startRenderers(); + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } else if (state == ExoPlayer.STATE_BUFFERING) { + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } + } + } + + private void startRenderers() throws ExoPlaybackException { + rebuffering = false; + standaloneMediaClock.start(); + for (Renderer renderer : enabledRenderers) { + renderer.start(); + } + } + + private void stopRenderers() throws ExoPlaybackException { + standaloneMediaClock.stop(); + for (Renderer renderer : enabledRenderers) { + ensureStopped(renderer); + } + } + + private void updatePlaybackPositions() throws ExoPlaybackException { + if (playingPeriodHolder == null) { + return; + } + + // Update the playback position. + long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity(); + if (periodPositionUs != C.TIME_UNSET) { + resetRendererPosition(periodPositionUs); + } else { + if (rendererMediaClockSource != null && !rendererMediaClockSource.isEnded()) { + rendererPositionUs = rendererMediaClock.getPositionUs(); + standaloneMediaClock.setPositionUs(rendererPositionUs); + } else { + rendererPositionUs = standaloneMediaClock.getPositionUs(); + } + periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs); + } + playbackInfo.positionUs = periodPositionUs; + elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; + + // Update the buffered position. + long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE + : playingPeriodHolder.mediaPeriod.getBufferedPositionUs(); + playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE + ? timeline.getPeriod(playingPeriodHolder.index, period).getDurationUs() + : bufferedPositionUs; + } + + private void doSomeWork() throws ExoPlaybackException, IOException { + long operationStartTimeMs = SystemClock.elapsedRealtime(); + updatePeriods(); + if (playingPeriodHolder == null) { + // We're still waiting for the first period to be prepared. + maybeThrowPeriodPrepareError(); + scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS); + return; + } + + TraceUtil.beginSection("doSomeWork"); + + updatePlaybackPositions(); + playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs); + + boolean allRenderersEnded = true; + boolean allRenderersReadyOrEnded = true; + for (Renderer renderer : enabledRenderers) { + // TODO: Each renderer should return the maximum delay before which it wishes to be called + // again. The minimum of these values should then be used as the delay before the next + // invocation of this method. + renderer.render(rendererPositionUs, elapsedRealtimeUs); + allRenderersEnded = allRenderersEnded && renderer.isEnded(); + // Determine whether the renderer is ready (or ended). If it's not, throw an error that's + // preventing the renderer from making progress, if such an error exists. + boolean rendererReadyOrEnded = renderer.isReady() || renderer.isEnded(); + if (!rendererReadyOrEnded) { + renderer.maybeThrowStreamError(); + } + allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded; + } + + if (!allRenderersReadyOrEnded) { + maybeThrowPeriodPrepareError(); + } + + // The standalone media clock never changes playback parameters, so just check the renderer. + if (rendererMediaClock != null) { + PlaybackParameters playbackParameters = rendererMediaClock.getPlaybackParameters(); + if (!playbackParameters.equals(this.playbackParameters)) { + // TODO: Make LoadControl, period transition position projection, adaptive track selection + // and potentially any time-related code in renderers take into account the playback speed. + this.playbackParameters = playbackParameters; + standaloneMediaClock.synchronize(rendererMediaClock); + eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters) + .sendToTarget(); + } + } + + long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period) + .getDurationUs(); + if (allRenderersEnded + && (playingPeriodDurationUs == C.TIME_UNSET + || playingPeriodDurationUs <= playbackInfo.positionUs) + && playingPeriodHolder.isLast) { + setState(ExoPlayer.STATE_ENDED); + stopRenderers(); + } else if (state == ExoPlayer.STATE_BUFFERING) { + boolean isNewlyReady = enabledRenderers.length > 0 + ? (allRenderersReadyOrEnded && haveSufficientBuffer(rebuffering)) + : isTimelineReady(playingPeriodDurationUs); + if (isNewlyReady) { + setState(ExoPlayer.STATE_READY); + if (playWhenReady) { + startRenderers(); + } + } + } else if (state == ExoPlayer.STATE_READY) { + boolean isStillReady = enabledRenderers.length > 0 ? allRenderersReadyOrEnded + : isTimelineReady(playingPeriodDurationUs); + if (!isStillReady) { + rebuffering = playWhenReady; + setState(ExoPlayer.STATE_BUFFERING); + stopRenderers(); + } + } + + if (state == ExoPlayer.STATE_BUFFERING) { + for (Renderer renderer : enabledRenderers) { + renderer.maybeThrowStreamError(); + } + } + + if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) { + scheduleNextWork(operationStartTimeMs, RENDERING_INTERVAL_MS); + } else if (enabledRenderers.length != 0) { + scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS); + } else { + handler.removeMessages(MSG_DO_SOME_WORK); + } + + TraceUtil.endSection(); + } + + private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) { + handler.removeMessages(MSG_DO_SOME_WORK); + long nextOperationStartTimeMs = thisOperationStartTimeMs + intervalMs; + long nextOperationDelayMs = nextOperationStartTimeMs - SystemClock.elapsedRealtime(); + if (nextOperationDelayMs <= 0) { + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } else { + handler.sendEmptyMessageDelayed(MSG_DO_SOME_WORK, nextOperationDelayMs); + } + } + + private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { + if (timeline == null) { + pendingInitialSeekCount++; + pendingSeekPosition = seekPosition; + return; + } + + Pair periodPosition = resolveSeekPosition(seekPosition); + if (periodPosition == null) { + // The seek position was valid for the timeline that it was performed into, but the + // timeline has changed and a suitable seek position could not be resolved in the new one. + playbackInfo = new PlaybackInfo(0, 0); + eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, playbackInfo).sendToTarget(); + // Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't + // ignored. + playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); + setState(ExoPlayer.STATE_ENDED); + // Reset, but retain the source so that it can still be used should a seek occur. + resetInternal(false); + return; + } + + boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET; + int periodIndex = periodPosition.first; + long periodPositionUs = periodPosition.second; + + try { + if (periodIndex == playbackInfo.periodIndex + && ((periodPositionUs / 1000) == (playbackInfo.positionUs / 1000))) { + // Seek position equals the current position. Do nothing. + return; + } + long newPeriodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs); + seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs; + periodPositionUs = newPeriodPositionUs; + } finally { + playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs); + eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo) + .sendToTarget(); + } + } + + private long seekToPeriodPosition(int periodIndex, long periodPositionUs) + throws ExoPlaybackException { + stopRenderers(); + rebuffering = false; + setState(ExoPlayer.STATE_BUFFERING); + + MediaPeriodHolder newPlayingPeriodHolder = null; + if (playingPeriodHolder == null) { + // We're still waiting for the first period to be prepared. + if (loadingPeriodHolder != null) { + loadingPeriodHolder.release(); + } + } else { + // Clear the timeline, but keep the requested period if it is already prepared. + MediaPeriodHolder periodHolder = playingPeriodHolder; + while (periodHolder != null) { + if (periodHolder.index == periodIndex && periodHolder.prepared) { + newPlayingPeriodHolder = periodHolder; + } else { + periodHolder.release(); + } + periodHolder = periodHolder.next; + } + } + + // Disable all the renderers if the period being played is changing, or if the renderers are + // reading from a period other than the one being played. + if (playingPeriodHolder != newPlayingPeriodHolder + || playingPeriodHolder != readingPeriodHolder) { + for (Renderer renderer : enabledRenderers) { + renderer.disable(); + } + enabledRenderers = new Renderer[0]; + rendererMediaClock = null; + rendererMediaClockSource = null; + playingPeriodHolder = null; + } + + // Update the holders. + if (newPlayingPeriodHolder != null) { + newPlayingPeriodHolder.next = null; + loadingPeriodHolder = newPlayingPeriodHolder; + readingPeriodHolder = newPlayingPeriodHolder; + setPlayingPeriodHolder(newPlayingPeriodHolder); + if (playingPeriodHolder.hasEnabledTracks) { + periodPositionUs = playingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs); + } + resetRendererPosition(periodPositionUs); + maybeContinueLoading(); + } else { + loadingPeriodHolder = null; + readingPeriodHolder = null; + playingPeriodHolder = null; + resetRendererPosition(periodPositionUs); + } + + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + return periodPositionUs; + } + + private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException { + rendererPositionUs = playingPeriodHolder == null + ? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US + : playingPeriodHolder.toRendererTime(periodPositionUs); + standaloneMediaClock.setPositionUs(rendererPositionUs); + for (Renderer renderer : enabledRenderers) { + renderer.resetPosition(rendererPositionUs); + } + } + + private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) { + playbackParameters = rendererMediaClock != null + ? rendererMediaClock.setPlaybackParameters(playbackParameters) + : standaloneMediaClock.setPlaybackParameters(playbackParameters); + this.playbackParameters = playbackParameters; + eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget(); + } + + private void stopInternal() { + resetInternal(true); + loadControl.onStopped(); + setState(ExoPlayer.STATE_IDLE); + } + + private void releaseInternal() { + resetInternal(true); + loadControl.onReleased(); + setState(ExoPlayer.STATE_IDLE); + synchronized (this) { + released = true; + notifyAll(); + } + } + + private void resetInternal(boolean releaseMediaSource) { + handler.removeMessages(MSG_DO_SOME_WORK); + rebuffering = false; + standaloneMediaClock.stop(); + rendererMediaClock = null; + rendererMediaClockSource = null; + rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US; + for (Renderer renderer : enabledRenderers) { + try { + ensureStopped(renderer); + renderer.disable(); + } catch (ExoPlaybackException | RuntimeException e) { + // There's nothing we can do. + e.getStackTrace(); + } + } + enabledRenderers = new Renderer[0]; + releasePeriodHoldersFrom(playingPeriodHolder != null ? playingPeriodHolder + : loadingPeriodHolder); + loadingPeriodHolder = null; + readingPeriodHolder = null; + playingPeriodHolder = null; + setIsLoading(false); + if (releaseMediaSource) { + if (mediaSource != null) { + mediaSource.releaseSource(); + mediaSource = null; + } + timeline = null; + } + } + + private void sendMessagesInternal(ExoPlayerMessage[] messages) throws ExoPlaybackException { + try { + for (ExoPlayerMessage message : messages) { + message.target.handleMessage(message.messageType, message.message); + } + if (mediaSource != null) { + // The message may have caused something to change that now requires us to do work. + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } + } finally { + synchronized (this) { + customMessagesProcessed++; + notifyAll(); + } + } + } + + private void ensureStopped(Renderer renderer) throws ExoPlaybackException { + if (renderer.getState() == Renderer.STATE_STARTED) { + renderer.stop(); + } + } + + private void reselectTracksInternal() throws ExoPlaybackException { + if (playingPeriodHolder == null) { + // We don't have tracks yet, so we don't care. + return; + } + // Reselect tracks on each period in turn, until the selection changes. + MediaPeriodHolder periodHolder = playingPeriodHolder; + boolean selectionsChangedForReadPeriod = true; + while (true) { + if (periodHolder == null || !periodHolder.prepared) { + // The reselection did not change any prepared periods. + return; + } + if (periodHolder.selectTracks()) { + // Selected tracks have changed for this period. + break; + } + if (periodHolder == readingPeriodHolder) { + // The track reselection didn't affect any period that has been read. + selectionsChangedForReadPeriod = false; + } + periodHolder = periodHolder.next; + } + + if (selectionsChangedForReadPeriod) { + // Update streams and rebuffer for the new selection, recreating all streams if reading ahead. + boolean recreateStreams = readingPeriodHolder != playingPeriodHolder; + releasePeriodHoldersFrom(playingPeriodHolder.next); + playingPeriodHolder.next = null; + loadingPeriodHolder = playingPeriodHolder; + readingPeriodHolder = playingPeriodHolder; + + boolean[] streamResetFlags = new boolean[renderers.length]; + long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection( + playbackInfo.positionUs, recreateStreams, streamResetFlags); + if (periodPositionUs != playbackInfo.positionUs) { + playbackInfo.positionUs = periodPositionUs; + resetRendererPosition(periodPositionUs); + } + + int enabledRendererCount = 0; + boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; + SampleStream sampleStream = playingPeriodHolder.sampleStreams[i]; + if (sampleStream != null) { + enabledRendererCount++; + } + if (rendererWasEnabledFlags[i]) { + if (sampleStream != renderer.getStream()) { + // We need to disable the renderer. + if (renderer == rendererMediaClockSource) { + // The renderer is providing the media clock. + if (sampleStream == null) { + // The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take + // over timing responsibilities. + standaloneMediaClock.synchronize(rendererMediaClock); + } + rendererMediaClock = null; + rendererMediaClockSource = null; + } + ensureStopped(renderer); + renderer.disable(); + } else if (streamResetFlags[i]) { + // The renderer will continue to consume from its current stream, but needs to be reset. + renderer.resetPosition(rendererPositionUs); + } + } + } + eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.trackSelectorResult) + .sendToTarget(); + enableRenderers(rendererWasEnabledFlags, enabledRendererCount); + } else { + // Release and re-prepare/buffer periods after the one whose selection changed. + loadingPeriodHolder = periodHolder; + periodHolder = loadingPeriodHolder.next; + while (periodHolder != null) { + periodHolder.release(); + periodHolder = periodHolder.next; + } + loadingPeriodHolder.next = null; + if (loadingPeriodHolder.prepared) { + long loadingPeriodPositionUs = Math.max(loadingPeriodHolder.startPositionUs, + loadingPeriodHolder.toPeriodTime(rendererPositionUs)); + loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false); + } + } + maybeContinueLoading(); + updatePlaybackPositions(); + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } + + private boolean isTimelineReady(long playingPeriodDurationUs) { + return playingPeriodDurationUs == C.TIME_UNSET + || playbackInfo.positionUs < playingPeriodDurationUs + || (playingPeriodHolder.next != null && playingPeriodHolder.next.prepared); + } + + private boolean haveSufficientBuffer(boolean rebuffering) { + long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared + ? loadingPeriodHolder.startPositionUs + : loadingPeriodHolder.mediaPeriod.getBufferedPositionUs(); + if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) { + if (loadingPeriodHolder.isLast) { + return true; + } + loadingPeriodBufferedPositionUs = timeline.getPeriod(loadingPeriodHolder.index, period) + .getDurationUs(); + } + return loadControl.shouldStartPlayback( + loadingPeriodBufferedPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs), + rebuffering); + } + + private void maybeThrowPeriodPrepareError() throws IOException { + if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared + && (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) { + for (Renderer renderer : enabledRenderers) { + if (!renderer.hasReadStreamToEnd()) { + return; + } + } + loadingPeriodHolder.mediaPeriod.maybeThrowPrepareError(); + } + } + + private void handleSourceInfoRefreshed(Pair timelineAndManifest) + throws ExoPlaybackException { + Timeline oldTimeline = timeline; + timeline = timelineAndManifest.first; + Object manifest = timelineAndManifest.second; + + int processedInitialSeekCount = 0; + if (oldTimeline == null) { + if (pendingInitialSeekCount > 0) { + Pair periodPosition = resolveSeekPosition(pendingSeekPosition); + processedInitialSeekCount = pendingInitialSeekCount; + pendingInitialSeekCount = 0; + pendingSeekPosition = null; + if (periodPosition == null) { + // The seek position was valid for the timeline that it was performed into, but the + // timeline has changed and a suitable seek position could not be resolved in the new one. + handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount); + return; + } + playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second); + } else if (playbackInfo.startPositionUs == C.TIME_UNSET) { + if (timeline.isEmpty()) { + handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount); + return; + } + Pair defaultPosition = getPeriodPosition(0, C.TIME_UNSET); + playbackInfo = new PlaybackInfo(defaultPosition.first, defaultPosition.second); + } + } + + MediaPeriodHolder periodHolder = playingPeriodHolder != null ? playingPeriodHolder + : loadingPeriodHolder; + if (periodHolder == null) { + // We don't have any period holders, so we're done. + notifySourceInfoRefresh(manifest, processedInitialSeekCount); + return; + } + + int periodIndex = timeline.getIndexOfPeriod(periodHolder.uid); + if (periodIndex == C.INDEX_UNSET) { + // We didn't find the current period in the new timeline. Attempt to resolve a subsequent + // period whose window we can restart from. + int newPeriodIndex = resolveSubsequentPeriod(periodHolder.index, oldTimeline, timeline); + if (newPeriodIndex == C.INDEX_UNSET) { + // We failed to resolve a suitable restart position. + handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount); + return; + } + // We resolved a subsequent period. Seek to the default position in the corresponding window. + Pair defaultPosition = getPeriodPosition( + timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET); + newPeriodIndex = defaultPosition.first; + long newPositionUs = defaultPosition.second; + timeline.getPeriod(newPeriodIndex, period, true); + // Clear the index of each holder that doesn't contain the default position. If a holder + // contains the default position then update its index so it can be re-used when seeking. + Object newPeriodUid = period.uid; + periodHolder.index = C.INDEX_UNSET; + while (periodHolder.next != null) { + periodHolder = periodHolder.next; + periodHolder.index = periodHolder.uid.equals(newPeriodUid) ? newPeriodIndex : C.INDEX_UNSET; + } + // Actually do the seek. + newPositionUs = seekToPeriodPosition(newPeriodIndex, newPositionUs); + playbackInfo = new PlaybackInfo(newPeriodIndex, newPositionUs); + notifySourceInfoRefresh(manifest, processedInitialSeekCount); + return; + } + + // The current period is in the new timeline. Update the holder and playbackInfo. + timeline.getPeriod(periodIndex, period); + boolean isLastPeriod = periodIndex == timeline.getPeriodCount() - 1 + && !timeline.getWindow(period.windowIndex, window).isDynamic; + periodHolder.setIndex(periodIndex, isLastPeriod); + boolean seenReadingPeriod = periodHolder == readingPeriodHolder; + if (periodIndex != playbackInfo.periodIndex) { + playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex); + } + + // If there are subsequent holders, update the index for each of them. If we find a holder + // that's inconsistent with the new timeline then take appropriate action. + while (periodHolder.next != null) { + MediaPeriodHolder previousPeriodHolder = periodHolder; + periodHolder = periodHolder.next; + periodIndex++; + timeline.getPeriod(periodIndex, period, true); + isLastPeriod = periodIndex == timeline.getPeriodCount() - 1 + && !timeline.getWindow(period.windowIndex, window).isDynamic; + if (periodHolder.uid.equals(period.uid)) { + // The holder is consistent with the new timeline. Update its index and continue. + periodHolder.setIndex(periodIndex, isLastPeriod); + seenReadingPeriod |= (periodHolder == readingPeriodHolder); + } else { + // The holder is inconsistent with the new timeline. + if (!seenReadingPeriod) { + // Renderers may have read from a period that's been removed. Seek back to the current + // position of the playing period to make sure none of the removed period is played. + periodIndex = playingPeriodHolder.index; + long newPositionUs = seekToPeriodPosition(periodIndex, playbackInfo.positionUs); + playbackInfo = new PlaybackInfo(periodIndex, newPositionUs); + } else { + // Update the loading period to be the last period that's still valid, and release all + // subsequent periods. + loadingPeriodHolder = previousPeriodHolder; + loadingPeriodHolder.next = null; + // Release the rest of the timeline. + releasePeriodHoldersFrom(periodHolder); + } + break; + } + } + + notifySourceInfoRefresh(manifest, processedInitialSeekCount); + } + + private void handleSourceInfoRefreshEndedPlayback(Object manifest, + int processedInitialSeekCount) { + // Set the playback position to (0,0) for notifying the eventHandler. + playbackInfo = new PlaybackInfo(0, 0); + notifySourceInfoRefresh(manifest, processedInitialSeekCount); + // Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't ignored. + playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); + setState(ExoPlayer.STATE_ENDED); + // Reset, but retain the source so that it can still be used should a seek occur. + resetInternal(false); + } + + private void notifySourceInfoRefresh(Object manifest, int processedInitialSeekCount) { + eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, + new SourceInfo(timeline, manifest, playbackInfo, processedInitialSeekCount)).sendToTarget(); + } + + /** + * Given a period index into an old timeline, finds the first subsequent period that also exists + * in a new timeline. The index of this period in the new timeline is returned. + * + * @param oldPeriodIndex The index of the period in the old timeline. + * @param oldTimeline The old timeline. + * @param newTimeline The new timeline. + * @return The index in the new timeline of the first subsequent period, or {@link C#INDEX_UNSET} + * if no such period was found. + */ + private int resolveSubsequentPeriod(int oldPeriodIndex, Timeline oldTimeline, + Timeline newTimeline) { + int newPeriodIndex = C.INDEX_UNSET; + while (newPeriodIndex == C.INDEX_UNSET && oldPeriodIndex < oldTimeline.getPeriodCount() - 1) { + newPeriodIndex = newTimeline.getIndexOfPeriod( + oldTimeline.getPeriod(++oldPeriodIndex, period, true).uid); + } + return newPeriodIndex; + } + + /** + * Converts a {@link SeekPosition} into the corresponding (periodIndex, periodPositionUs) for the + * internal timeline. + * + * @param seekPosition The position to resolve. + * @return The resolved position, or null if resolution was not successful. + * @throws IllegalSeekPositionException If the window index of the seek position is outside the + * bounds of the timeline. + */ + private Pair resolveSeekPosition(SeekPosition seekPosition) { + Timeline seekTimeline = seekPosition.timeline; + if (seekTimeline.isEmpty()) { + // The application performed a blind seek without a non-empty timeline (most likely based on + // knowledge of what the future timeline will be). Use the internal timeline. + seekTimeline = timeline; + } + // Map the SeekPosition to a position in the corresponding timeline. + Pair periodPosition; + try { + periodPosition = getPeriodPosition(seekTimeline, seekPosition.windowIndex, + seekPosition.windowPositionUs); + } catch (IndexOutOfBoundsException e) { + // The window index of the seek position was outside the bounds of the timeline. + throw new IllegalSeekPositionException(timeline, seekPosition.windowIndex, + seekPosition.windowPositionUs); + } + if (timeline == seekTimeline) { + // Our internal timeline is the seek timeline, so the mapped position is correct. + return periodPosition; + } + // Attempt to find the mapped period in the internal timeline. + int periodIndex = timeline.getIndexOfPeriod( + seekTimeline.getPeriod(periodPosition.first, period, true).uid); + if (periodIndex != C.INDEX_UNSET) { + // We successfully located the period in the internal timeline. + return Pair.create(periodIndex, periodPosition.second); + } + // Try and find a subsequent period from the seek timeline in the internal timeline. + periodIndex = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline); + if (periodIndex != C.INDEX_UNSET) { + // We found one. Map the SeekPosition onto the corresponding default position. + return getPeriodPosition(timeline.getPeriod(periodIndex, period).windowIndex, C.TIME_UNSET); + } + // We didn't find one. Give up. + return null; + } + + /** + * Calls {@link #getPeriodPosition(Timeline, int, long)} using the current timeline. + */ + private Pair getPeriodPosition(int windowIndex, long windowPositionUs) { + return getPeriodPosition(timeline, windowIndex, windowPositionUs); + } + + /** + * Calls {@link #getPeriodPosition(Timeline, int, long, long)} with a zero default position + * projection. + */ + private Pair getPeriodPosition(Timeline timeline, int windowIndex, + long windowPositionUs) { + return getPeriodPosition(timeline, windowIndex, windowPositionUs, 0); + } + + /** + * Converts (windowIndex, windowPositionUs) to the corresponding (periodIndex, periodPositionUs). + * + * @param timeline The timeline containing the window. + * @param windowIndex The window index. + * @param windowPositionUs The window time, or {@link C#TIME_UNSET} to use the window's default + * start position. + * @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the + * duration into the future by which the window's position should be projected. + * @return The corresponding (periodIndex, periodPositionUs), or null if {@code #windowPositionUs} + * is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's + * position could not be projected by {@code defaultPositionProjectionUs}. + */ + private Pair getPeriodPosition(Timeline timeline, int windowIndex, + long windowPositionUs, long defaultPositionProjectionUs) { + Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount()); + timeline.getWindow(windowIndex, window, false, defaultPositionProjectionUs); + if (windowPositionUs == C.TIME_UNSET) { + windowPositionUs = window.getDefaultPositionUs(); + if (windowPositionUs == C.TIME_UNSET) { + return null; + } + } + int periodIndex = window.firstPeriodIndex; + long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs; + long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs(); + while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs + && periodIndex < window.lastPeriodIndex) { + periodPositionUs -= periodDurationUs; + periodDurationUs = timeline.getPeriod(++periodIndex, period).getDurationUs(); + } + return Pair.create(periodIndex, periodPositionUs); + } + + private void updatePeriods() throws ExoPlaybackException, IOException { + if (timeline == null) { + // We're waiting to get information about periods. + mediaSource.maybeThrowSourceInfoRefreshError(); + return; + } + + // Update the loading period if required. + maybeUpdateLoadingPeriod(); + if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) { + setIsLoading(false); + } else if (loadingPeriodHolder != null && loadingPeriodHolder.needsContinueLoading) { + maybeContinueLoading(); + } + + if (playingPeriodHolder == null) { + // We're waiting for the first period to be prepared. + return; + } + + // Update the playing and reading periods. + while (playingPeriodHolder != readingPeriodHolder + && rendererPositionUs >= playingPeriodHolder.next.rendererPositionOffsetUs) { + // All enabled renderers' streams have been read to the end, and the playback position reached + // the end of the playing period, so advance playback to the next period. + playingPeriodHolder.release(); + setPlayingPeriodHolder(playingPeriodHolder.next); + playbackInfo = new PlaybackInfo(playingPeriodHolder.index, + playingPeriodHolder.startPositionUs); + updatePlaybackPositions(); + eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); + } + + if (readingPeriodHolder.isLast) { + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + SampleStream sampleStream = readingPeriodHolder.sampleStreams[i]; + // Defer setting the stream as final until the renderer has actually consumed the whole + // stream in case of playlist changes that cause the stream to be no longer final. + if (sampleStream != null && renderer.getStream() == sampleStream + && renderer.hasReadStreamToEnd()) { + renderer.setCurrentStreamFinal(); + } + } + return; + } + + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + SampleStream sampleStream = readingPeriodHolder.sampleStreams[i]; + if (renderer.getStream() != sampleStream + || (sampleStream != null && !renderer.hasReadStreamToEnd())) { + return; + } + } + + if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) { + TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult; + readingPeriodHolder = readingPeriodHolder.next; + TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult; + + boolean initialDiscontinuity = + readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET; + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + TrackSelection oldSelection = oldTrackSelectorResult.selections.get(i); + if (oldSelection == null) { + // The renderer has no current stream and will be enabled when we play the next period. + } else if (initialDiscontinuity) { + // The new period starts with a discontinuity, so the renderer will play out all data then + // be disabled and re-enabled when it starts playing the next period. + renderer.setCurrentStreamFinal(); + } else if (!renderer.isCurrentStreamFinal()) { + TrackSelection newSelection = newTrackSelectorResult.selections.get(i); + RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i]; + RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[i]; + if (newSelection != null && newConfig.equals(oldConfig)) { + // Replace the renderer's SampleStream so the transition to playing the next period can + // be seamless. + Format[] formats = new Format[newSelection.length()]; + for (int j = 0; j < formats.length; j++) { + formats[j] = newSelection.getFormat(j); + } + renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i], + readingPeriodHolder.getRendererOffset()); + } else { + // The renderer will be disabled when transitioning to playing the next period, either + // because there's no new selection or because a configuration change is required. Mark + // the SampleStream as final to play out any remaining data. + renderer.setCurrentStreamFinal(); + } + } + } + } + } + + private void maybeUpdateLoadingPeriod() throws IOException { + int newLoadingPeriodIndex; + if (loadingPeriodHolder == null) { + newLoadingPeriodIndex = playbackInfo.periodIndex; + } else { + int loadingPeriodIndex = loadingPeriodHolder.index; + if (loadingPeriodHolder.isLast || !loadingPeriodHolder.isFullyBuffered() + || timeline.getPeriod(loadingPeriodIndex, period).getDurationUs() == C.TIME_UNSET) { + // Either the existing loading period is the last period, or we are not ready to advance to + // loading the next period because it hasn't been fully buffered or its duration is unknown. + return; + } + if (playingPeriodHolder != null + && loadingPeriodIndex - playingPeriodHolder.index == MAXIMUM_BUFFER_AHEAD_PERIODS) { + // We are already buffering the maximum number of periods ahead. + return; + } + newLoadingPeriodIndex = loadingPeriodHolder.index + 1; + } + + if (newLoadingPeriodIndex >= timeline.getPeriodCount()) { + // The next period is not available yet. + mediaSource.maybeThrowSourceInfoRefreshError(); + return; + } + + long newLoadingPeriodStartPositionUs; + if (loadingPeriodHolder == null) { + newLoadingPeriodStartPositionUs = playbackInfo.positionUs; + } else { + int newLoadingWindowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex; + if (newLoadingPeriodIndex + != timeline.getWindow(newLoadingWindowIndex, window).firstPeriodIndex) { + // We're starting to buffer a new period in the current window. Always start from the + // beginning of the period. + newLoadingPeriodStartPositionUs = 0; + } else { + // We're starting to buffer a new window. When playback transitions to this window we'll + // want it to be from its default start position. The expected delay until playback + // transitions is equal the duration of media that's currently buffered (assuming no + // interruptions). Hence we project the default start position forward by the duration of + // the buffer, and start buffering from this point. + long defaultPositionProjectionUs = loadingPeriodHolder.getRendererOffset() + + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs() + - rendererPositionUs; + Pair defaultPosition = getPeriodPosition(timeline, newLoadingWindowIndex, + C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs)); + if (defaultPosition == null) { + return; + } + + newLoadingPeriodIndex = defaultPosition.first; + newLoadingPeriodStartPositionUs = defaultPosition.second; + } + } + + long rendererPositionOffsetUs = loadingPeriodHolder == null + ? newLoadingPeriodStartPositionUs + RENDERER_TIMESTAMP_OFFSET_US + : (loadingPeriodHolder.getRendererOffset() + + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()); + timeline.getPeriod(newLoadingPeriodIndex, period, true); + boolean isLastPeriod = newLoadingPeriodIndex == timeline.getPeriodCount() - 1 + && !timeline.getWindow(period.windowIndex, window).isDynamic; + MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, + rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, period.uid, + newLoadingPeriodIndex, isLastPeriod, newLoadingPeriodStartPositionUs); + if (loadingPeriodHolder != null) { + loadingPeriodHolder.next = newPeriodHolder; + } + loadingPeriodHolder = newPeriodHolder; + loadingPeriodHolder.mediaPeriod.prepare(this); + setIsLoading(true); + } + + private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException { + if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) { + // Stale event. + return; + } + loadingPeriodHolder.handlePrepared(); + if (playingPeriodHolder == null) { + // This is the first prepared period, so start playing it. + readingPeriodHolder = loadingPeriodHolder; + resetRendererPosition(readingPeriodHolder.startPositionUs); + setPlayingPeriodHolder(readingPeriodHolder); + } + maybeContinueLoading(); + } + + private void handleContinueLoadingRequested(MediaPeriod period) { + if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) { + // Stale event. + return; + } + maybeContinueLoading(); + } + + private void maybeContinueLoading() { + long nextLoadPositionUs = !loadingPeriodHolder.prepared ? 0 + : loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs(); + if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { + setIsLoading(false); + } else { + long loadingPeriodPositionUs = loadingPeriodHolder.toPeriodTime(rendererPositionUs); + long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; + boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs); + setIsLoading(continueLoading); + if (continueLoading) { + loadingPeriodHolder.needsContinueLoading = false; + loadingPeriodHolder.mediaPeriod.continueLoading(loadingPeriodPositionUs); + } else { + loadingPeriodHolder.needsContinueLoading = true; + } + } + } + + private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { + while (periodHolder != null) { + periodHolder.release(); + periodHolder = periodHolder.next; + } + } + + private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException { + if (playingPeriodHolder == periodHolder) { + return; + } + + int enabledRendererCount = 0; + boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; + TrackSelection newSelection = periodHolder.trackSelectorResult.selections.get(i); + if (newSelection != null) { + enabledRendererCount++; + } + if (rendererWasEnabledFlags[i] && (newSelection == null + || (renderer.isCurrentStreamFinal() + && renderer.getStream() == playingPeriodHolder.sampleStreams[i]))) { + // The renderer should be disabled before playing the next period, either because it's not + // needed to play the next period, or because we need to re-enable it as its current stream + // is final and it's not reading ahead. + if (renderer == rendererMediaClockSource) { + // Sync standaloneMediaClock so that it can take over timing responsibilities. + standaloneMediaClock.synchronize(rendererMediaClock); + rendererMediaClock = null; + rendererMediaClockSource = null; + } + ensureStopped(renderer); + renderer.disable(); + } + } + + playingPeriodHolder = periodHolder; + eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.trackSelectorResult).sendToTarget(); + enableRenderers(rendererWasEnabledFlags, enabledRendererCount); + } + + private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount) + throws ExoPlaybackException { + enabledRenderers = new Renderer[enabledRendererCount]; + enabledRendererCount = 0; + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + TrackSelection newSelection = playingPeriodHolder.trackSelectorResult.selections.get(i); + if (newSelection != null) { + enabledRenderers[enabledRendererCount++] = renderer; + if (renderer.getState() == Renderer.STATE_DISABLED) { + RendererConfiguration rendererConfiguration = + playingPeriodHolder.trackSelectorResult.rendererConfigurations[i]; + // The renderer needs enabling with its new track selection. + boolean playing = playWhenReady && state == ExoPlayer.STATE_READY; + // Consider as joining only if the renderer was previously disabled. + boolean joining = !rendererWasEnabledFlags[i] && playing; + // Build an array of formats contained by the selection. + Format[] formats = new Format[newSelection.length()]; + for (int j = 0; j < formats.length; j++) { + formats[j] = newSelection.getFormat(j); + } + // Enable the renderer. + renderer.enable(rendererConfiguration, formats, playingPeriodHolder.sampleStreams[i], + rendererPositionUs, joining, playingPeriodHolder.getRendererOffset()); + MediaClock mediaClock = renderer.getMediaClock(); + if (mediaClock != null) { + if (rendererMediaClock != null) { + throw ExoPlaybackException.createForUnexpected( + new IllegalStateException("Multiple renderer media clocks enabled.")); + } + rendererMediaClock = mediaClock; + rendererMediaClockSource = renderer; + rendererMediaClock.setPlaybackParameters(playbackParameters); + } + // Start the renderer if playing. + if (playing) { + renderer.start(); + } + } + } + } + } + + /** + * Holds a {@link MediaPeriod} with information required to play it as part of a timeline. + */ + private static final class MediaPeriodHolder { + + public final MediaPeriod mediaPeriod; + public final Object uid; + public final SampleStream[] sampleStreams; + public final boolean[] mayRetainStreamFlags; + public final long rendererPositionOffsetUs; + + public int index; + public long startPositionUs; + public boolean isLast; + public boolean prepared; + public boolean hasEnabledTracks; + public MediaPeriodHolder next; + public boolean needsContinueLoading; + public TrackSelectorResult trackSelectorResult; + + private final Renderer[] renderers; + private final RendererCapabilities[] rendererCapabilities; + private final TrackSelector trackSelector; + private final LoadControl loadControl; + private final MediaSource mediaSource; + + private TrackSelectorResult periodTrackSelectorResult; + + public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, + long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl, + MediaSource mediaSource, Object periodUid, int periodIndex, boolean isLastPeriod, + long startPositionUs) { + this.renderers = renderers; + this.rendererCapabilities = rendererCapabilities; + this.rendererPositionOffsetUs = rendererPositionOffsetUs; + this.trackSelector = trackSelector; + this.loadControl = loadControl; + this.mediaSource = mediaSource; + this.uid = Assertions.checkNotNull(periodUid); + this.index = periodIndex; + this.isLast = isLastPeriod; + this.startPositionUs = startPositionUs; + sampleStreams = new SampleStream[renderers.length]; + mayRetainStreamFlags = new boolean[renderers.length]; + mediaPeriod = mediaSource.createPeriod(periodIndex, loadControl.getAllocator(), + startPositionUs); + } + + public long toRendererTime(long periodTimeUs) { + return periodTimeUs + getRendererOffset(); + } + + public long toPeriodTime(long rendererTimeUs) { + return rendererTimeUs - getRendererOffset(); + } + + public long getRendererOffset() { + return rendererPositionOffsetUs - startPositionUs; + } + + public void setIndex(int index, boolean isLast) { + this.index = index; + this.isLast = isLast; + } + + public boolean isFullyBuffered() { + return prepared + && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE); + } + + public void handlePrepared() throws ExoPlaybackException { + prepared = true; + selectTracks(); + startPositionUs = updatePeriodTrackSelection(startPositionUs, false); + } + + public boolean selectTracks() throws ExoPlaybackException { + TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities, + mediaPeriod.getTrackGroups()); + if (selectorResult.isEquivalent(periodTrackSelectorResult)) { + return false; + } + trackSelectorResult = selectorResult; + return true; + } + + public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams) { + return updatePeriodTrackSelection(positionUs, forceRecreateStreams, + new boolean[renderers.length]); + } + + public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams, + boolean[] streamResetFlags) { + TrackSelectionArray trackSelections = trackSelectorResult.selections; + for (int i = 0; i < trackSelections.length; i++) { + mayRetainStreamFlags[i] = !forceRecreateStreams + && trackSelectorResult.isEquivalent(periodTrackSelectorResult, i); + } + + // Disable streams on the period and get new streams for updated/newly-enabled tracks. + positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags, + sampleStreams, streamResetFlags, positionUs); + periodTrackSelectorResult = trackSelectorResult; + + // Update whether we have enabled tracks and sanity check the expected streams are non-null. + hasEnabledTracks = false; + for (int i = 0; i < sampleStreams.length; i++) { + if (sampleStreams[i] != null) { + Assertions.checkState(trackSelections.get(i) != null); + hasEnabledTracks = true; + } else { + Assertions.checkState(trackSelections.get(i) == null); + } + } + + // The track selection has changed. + loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections); + return positionUs; + } + + public void release() { + try { + mediaSource.releasePeriod(mediaPeriod); + } catch (RuntimeException e) { + // There's nothing we can do. + e.getStackTrace(); + } + } + + } + + private static final class SeekPosition { + + public final Timeline timeline; + public final int windowIndex; + public final long windowPositionUs; + + public SeekPosition(Timeline timeline, int windowIndex, long windowPositionUs) { + this.timeline = timeline; + this.windowIndex = windowIndex; + this.windowPositionUs = windowPositionUs; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerLibraryInfo.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerLibraryInfo.java new file mode 100644 index 0000000..59b4ee9 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ExoPlayerLibraryInfo.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +/** + * Information about the ExoPlayer library. + */ +public interface ExoPlayerLibraryInfo { + + /** + * The version of the library expressed as a string, for example "1.2.3". + */ + // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. + String VERSION = "2.4.0"; + + /** + * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. + */ + // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. + String VERSION_SLASHY = "ExoPlayerLib/2.4.0"; + + /** + * The version of the library expressed as an integer, for example 1002003. + *

    + * Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the + * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding + * integer version 123045006 (123-045-006). + */ + // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. + int VERSION_INT = 2004000; + + /** + * Whether the library was compiled with {@link com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions} + * checks enabled. + */ + boolean ASSERTIONS_ENABLED = true; + + /** + * Whether the library was compiled with {@link com.tangxiaolv.telegramgallery.exoplayer2.util.TraceUtil} + * trace enabled. + */ + boolean TRACE_ENABLED = true; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Format.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Format.java new file mode 100644 index 0000000..a0d45ec --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Format.java @@ -0,0 +1,723 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.media.MediaFormat; +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import com.tangxiaolv.telegramgallery.exoplayer2.video.ColorInfo; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Representation of a media format. + */ +public final class Format implements Parcelable { + + /** + * A value for various fields to indicate that the field's value is unknown or not applicable. + */ + public static final int NO_VALUE = -1; + + /** + * A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to + * the timestamps of their parent samples. + */ + public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE; + + /** + * An identifier for the format, or null if unknown or not applicable. + */ + public final String id; + /** + * The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int bitrate; + /** + * Codecs of the format as described in RFC 6381, or null if unknown or not applicable. + */ + public final String codecs; + /** + * Metadata, or null if unknown or not applicable. + */ + public final Metadata metadata; + + // Container specific. + + /** + * The mime type of the container, or null if unknown or not applicable. + */ + public final String containerMimeType; + + // Elementary stream specific. + + /** + * The mime type of the elementary stream (i.e. the individual samples), or null if unknown or not + * applicable. + */ + public final String sampleMimeType; + /** + * The maximum size of a buffer of data (typically one sample), or {@link #NO_VALUE} if unknown or + * not applicable. + */ + public final int maxInputSize; + /** + * Initialization data that must be provided to the decoder. Will not be null, but may be empty + * if initialization data is not required. + */ + public final List initializationData; + /** + * DRM initialization data if the stream is protected, or null otherwise. + */ + public final DrmInitData drmInitData; + + // Video specific. + + /** + * The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int width; + /** + * The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int height; + /** + * The frame rate in frames per second, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final float frameRate; + /** + * The clockwise rotation that should be applied to the video for it to be rendered in the correct + * orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are + * supported. + */ + public final int rotationDegrees; + /** + * The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not + * applicable. + */ + public final float pixelWidthHeightRatio; + /** + * The stereo layout for 360/3D/VR video, or {@link #NO_VALUE} if not applicable. Valid stereo + * modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link + * C#STEREO_MODE_LEFT_RIGHT}, {@link C#STEREO_MODE_STEREO_MESH}. + */ + @C.StereoMode + public final int stereoMode; + /** + * The projection data for 360/VR video, or null if not applicable. + */ + public final byte[] projectionData; + /** + * The color metadata associated with the video, helps with accurate color reproduction. + */ + public final ColorInfo colorInfo; + + // Audio specific. + + /** + * The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int channelCount; + /** + * The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int sampleRate; + /** + * The encoding for PCM audio streams. If {@link #sampleMimeType} is {@link MimeTypes#AUDIO_RAW} + * then one of {@link C#ENCODING_PCM_8BIT}, {@link C#ENCODING_PCM_16BIT}, + * {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for + * other media types. + */ + @C.PcmEncoding + public final int pcmEncoding; + /** + * The number of samples to trim from the start of the decoded audio stream. + */ + public final int encoderDelay; + /** + * The number of samples to trim from the end of the decoded audio stream. + */ + public final int encoderPadding; + + // Text specific. + + /** + * For samples that contain subsamples, this is an offset that should be added to subsample + * timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are + * relative to the timestamps of their parent samples. + */ + public final long subsampleOffsetUs; + + // Audio and text specific. + + /** + * Track selection flags. + */ + @C.SelectionFlags + public final int selectionFlags; + + /** + * The language, or null if unknown or not applicable. + */ + public final String language; + + /** + * The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. + */ + public final int accessibilityChannel; + + // Lazily initialized hashcode. + private int hashCode; + + // Video. + + public static Format createVideoContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, int width, int height, + float frameRate, List initializationData, @C.SelectionFlags int selectionFlags) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width, + height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, selectionFlags, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + initializationData, null, null); + } + + public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int width, int height, float frameRate, + List initializationData, DrmInitData drmInitData) { + return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width, + height, frameRate, initializationData, NO_VALUE, NO_VALUE, drmInitData); + } + + public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int width, int height, float frameRate, + List initializationData, int rotationDegrees, float pixelWidthHeightRatio, + DrmInitData drmInitData) { + return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width, + height, frameRate, initializationData, rotationDegrees, pixelWidthHeightRatio, null, + NO_VALUE, null, drmInitData); + } + + public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int width, int height, float frameRate, + List initializationData, int rotationDegrees, float pixelWidthHeightRatio, + byte[] projectionData, @C.StereoMode int stereoMode, ColorInfo colorInfo, + DrmInitData drmInitData) { + return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, + frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, + colorInfo, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, + OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null); + } + + // Audio. + + public static Format createAudioContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate, + List initializationData, @C.SelectionFlags int selectionFlags, String language) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, + NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + initializationData, null, null); + } + + public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int channelCount, int sampleRate, + List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language) { + return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, + sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language); + } + + public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language) { + return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, + sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData, + selectionFlags, language, null); + } + + public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding, + List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language, Metadata metadata) { + return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, pcmEncoding, + encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + initializationData, drmInitData, metadata); + } + + // Text. + + public static Format createTextContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, + String language) { + return createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, bitrate, + selectionFlags, language, NO_VALUE); + } + + public static Format createTextContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, + String language, int accessibilityChannel) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, + OFFSET_SAMPLE_RELATIVE, null, null, null); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) { + return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.emptyList()); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel, + DrmInitData drmInitData) { + return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.emptyList()); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData, + long subsampleOffsetUs) { + return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + NO_VALUE, drmInitData, subsampleOffsetUs, Collections.emptyList()); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, + int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs, + List initializationData) { + return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, null); + } + + // Image. + + public static Format createImageSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, List initializationData, String language, DrmInitData drmInitData) { + return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, 0, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, + null); + } + + // Generic. + + public static Format createContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, + String language) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null, + null); + } + + public static Format createSampleFormat(String id, String sampleMimeType, + long subsampleOffsetUs) { + return new Format(id, null, sampleMimeType, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, 0, null, NO_VALUE, subsampleOffsetUs, null, null, null); + } + + public static Format createSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, DrmInitData drmInitData) { + return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null); + } + + /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, + float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode, + ColorInfo colorInfo, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, + int encoderDelay, int encoderPadding, @C.SelectionFlags int selectionFlags, String language, + int accessibilityChannel, long subsampleOffsetUs, List initializationData, + DrmInitData drmInitData, Metadata metadata) { + this.id = id; + this.containerMimeType = containerMimeType; + this.sampleMimeType = sampleMimeType; + this.codecs = codecs; + this.bitrate = bitrate; + this.maxInputSize = maxInputSize; + this.width = width; + this.height = height; + this.frameRate = frameRate; + this.rotationDegrees = rotationDegrees; + this.pixelWidthHeightRatio = pixelWidthHeightRatio; + this.projectionData = projectionData; + this.stereoMode = stereoMode; + this.colorInfo = colorInfo; + this.channelCount = channelCount; + this.sampleRate = sampleRate; + this.pcmEncoding = pcmEncoding; + this.encoderDelay = encoderDelay; + this.encoderPadding = encoderPadding; + this.selectionFlags = selectionFlags; + this.language = language; + this.accessibilityChannel = accessibilityChannel; + this.subsampleOffsetUs = subsampleOffsetUs; + this.initializationData = initializationData == null ? Collections.emptyList() + : initializationData; + this.drmInitData = drmInitData; + this.metadata = metadata; + } + + @SuppressWarnings("ResourceType") + /* package */ Format(Parcel in) { + id = in.readString(); + containerMimeType = in.readString(); + sampleMimeType = in.readString(); + codecs = in.readString(); + bitrate = in.readInt(); + maxInputSize = in.readInt(); + width = in.readInt(); + height = in.readInt(); + frameRate = in.readFloat(); + rotationDegrees = in.readInt(); + pixelWidthHeightRatio = in.readFloat(); + boolean hasProjectionData = in.readInt() != 0; + projectionData = hasProjectionData ? in.createByteArray() : null; + stereoMode = in.readInt(); + colorInfo = in.readParcelable(ColorInfo.class.getClassLoader()); + channelCount = in.readInt(); + sampleRate = in.readInt(); + pcmEncoding = in.readInt(); + encoderDelay = in.readInt(); + encoderPadding = in.readInt(); + selectionFlags = in.readInt(); + language = in.readString(); + accessibilityChannel = in.readInt(); + subsampleOffsetUs = in.readLong(); + int initializationDataSize = in.readInt(); + initializationData = new ArrayList<>(initializationDataSize); + for (int i = 0; i < initializationDataSize; i++) { + initializationData.add(in.createByteArray()); + } + drmInitData = in.readParcelable(DrmInitData.class.getClassLoader()); + metadata = in.readParcelable(Metadata.class.getClassLoader()); + } + + public Format copyWithMaxInputSize(int maxInputSize) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); + } + + public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); + } + + public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height, + @C.SelectionFlags int selectionFlags, String language) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); + } + + @SuppressWarnings("ReferenceEquality") + public Format copyWithManifestFormatInfo(Format manifestFormat) { + if (this == manifestFormat) { + // No need to copy from ourselves. + return this; + } + String id = manifestFormat.id; + String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs; + int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate; + float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate; + @C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags; + String language = this.language == null ? manifestFormat.language : this.language; + DrmInitData drmInitData = manifestFormat.drmInitData != null ? manifestFormat.drmInitData + : this.drmInitData; + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, + height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, + colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); + } + + public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); + } + + public Format copyWithDrmInitData(DrmInitData drmInitData) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); + } + + public Format copyWithMetadata(Metadata metadata) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); + } + + /** + * Returns the number of pixels if this is a video format whose {@link #width} and {@link #height} + * are known, or {@link #NO_VALUE} otherwise + */ + public int getPixelCount() { + return width == NO_VALUE || height == NO_VALUE ? NO_VALUE : (width * height); + } + + /** + * Returns a {@link MediaFormat} representation of this format. + */ + @SuppressLint("InlinedApi") + @TargetApi(16) + public final MediaFormat getFrameworkMediaFormatV16() { + MediaFormat format = new MediaFormat(); + format.setString(MediaFormat.KEY_MIME, sampleMimeType); + maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language); + maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); + maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width); + maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height); + maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate); + maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees); + maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount); + maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate); + maybeSetIntegerV16(format, "encoder-delay", encoderDelay); + maybeSetIntegerV16(format, "encoder-padding", encoderPadding); + for (int i = 0; i < initializationData.size(); i++) { + format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); + } + maybeSetColorInfoV24(format, colorInfo); + return format; + } + + @Override + public String toString() { + return "Format(" + id + ", " + containerMimeType + ", " + sampleMimeType + ", " + bitrate + ", " + + language + ", [" + width + ", " + height + ", " + frameRate + "]" + + ", [" + channelCount + ", " + sampleRate + "])"; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + (id == null ? 0 : id.hashCode()); + result = 31 * result + (containerMimeType == null ? 0 : containerMimeType.hashCode()); + result = 31 * result + (sampleMimeType == null ? 0 : sampleMimeType.hashCode()); + result = 31 * result + (codecs == null ? 0 : codecs.hashCode()); + result = 31 * result + bitrate; + result = 31 * result + width; + result = 31 * result + height; + result = 31 * result + channelCount; + result = 31 * result + sampleRate; + result = 31 * result + (language == null ? 0 : language.hashCode()); + result = 31 * result + accessibilityChannel; + result = 31 * result + (drmInitData == null ? 0 : drmInitData.hashCode()); + result = 31 * result + (metadata == null ? 0 : metadata.hashCode()); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Format other = (Format) obj; + if (bitrate != other.bitrate || maxInputSize != other.maxInputSize + || width != other.width || height != other.height || frameRate != other.frameRate + || rotationDegrees != other.rotationDegrees + || pixelWidthHeightRatio != other.pixelWidthHeightRatio || stereoMode != other.stereoMode + || channelCount != other.channelCount || sampleRate != other.sampleRate + || pcmEncoding != other.pcmEncoding || encoderDelay != other.encoderDelay + || encoderPadding != other.encoderPadding || subsampleOffsetUs != other.subsampleOffsetUs + || selectionFlags != other.selectionFlags || !Util.areEqual(id, other.id) + || !Util.areEqual(language, other.language) + || accessibilityChannel != other.accessibilityChannel + || !Util.areEqual(containerMimeType, other.containerMimeType) + || !Util.areEqual(sampleMimeType, other.sampleMimeType) + || !Util.areEqual(codecs, other.codecs) + || !Util.areEqual(drmInitData, other.drmInitData) + || !Util.areEqual(metadata, other.metadata) + || !Util.areEqual(colorInfo, other.colorInfo) + || !Arrays.equals(projectionData, other.projectionData) + || initializationData.size() != other.initializationData.size()) { + return false; + } + for (int i = 0; i < initializationData.size(); i++) { + if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) { + return false; + } + } + return true; + } + + @TargetApi(24) + private static void maybeSetColorInfoV24(MediaFormat format, ColorInfo colorInfo) { + if (colorInfo == null) { + return; + } + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer); + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace); + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange); + maybeSetByteBufferV16(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo); + } + + @TargetApi(16) + private static void maybeSetStringV16(MediaFormat format, String key, String value) { + if (value != null) { + format.setString(key, value); + } + } + + @TargetApi(16) + private static void maybeSetIntegerV16(MediaFormat format, String key, int value) { + if (value != NO_VALUE) { + format.setInteger(key, value); + } + } + + @TargetApi(16) + private static void maybeSetFloatV16(MediaFormat format, String key, float value) { + if (value != NO_VALUE) { + format.setFloat(key, value); + } + } + + @TargetApi(16) + private static void maybeSetByteBufferV16(MediaFormat format, String key, byte[] value) { + if (value != null) { + format.setByteBuffer(key, ByteBuffer.wrap(value)); + } + } + + // Utility methods + + /** + * Returns a prettier {@link String} than {@link #toString()}, intended for logging. + */ + public static String toLogString(Format format) { + if (format == null) { + return "null"; + } + StringBuilder builder = new StringBuilder(); + builder.append("id=").append(format.id).append(", mimeType=").append(format.sampleMimeType); + if (format.bitrate != Format.NO_VALUE) { + builder.append(", bitrate=").append(format.bitrate); + } + if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) { + builder.append(", res=").append(format.width).append("x").append(format.height); + } + if (format.frameRate != Format.NO_VALUE) { + builder.append(", fps=").append(format.frameRate); + } + if (format.channelCount != Format.NO_VALUE) { + builder.append(", channels=").append(format.channelCount); + } + if (format.sampleRate != Format.NO_VALUE) { + builder.append(", sample_rate=").append(format.sampleRate); + } + if (format.language != null) { + builder.append(", language=").append(format.language); + } + return builder.toString(); + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(containerMimeType); + dest.writeString(sampleMimeType); + dest.writeString(codecs); + dest.writeInt(bitrate); + dest.writeInt(maxInputSize); + dest.writeInt(width); + dest.writeInt(height); + dest.writeFloat(frameRate); + dest.writeInt(rotationDegrees); + dest.writeFloat(pixelWidthHeightRatio); + dest.writeInt(projectionData != null ? 1 : 0); + if (projectionData != null) { + dest.writeByteArray(projectionData); + } + dest.writeInt(stereoMode); + dest.writeParcelable(colorInfo, flags); + dest.writeInt(channelCount); + dest.writeInt(sampleRate); + dest.writeInt(pcmEncoding); + dest.writeInt(encoderDelay); + dest.writeInt(encoderPadding); + dest.writeInt(selectionFlags); + dest.writeString(language); + dest.writeInt(accessibilityChannel); + dest.writeLong(subsampleOffsetUs); + int initializationDataSize = initializationData.size(); + dest.writeInt(initializationDataSize); + for (int i = 0; i < initializationDataSize; i++) { + dest.writeByteArray(initializationData.get(i)); + } + dest.writeParcelable(drmInitData, 0); + dest.writeParcelable(metadata, 0); + } + + public static final Creator CREATOR = new Creator() { + + @Override + public Format createFromParcel(Parcel in) { + return new Format(in); + } + + @Override + public Format[] newArray(int size) { + return new Format[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/FormatHolder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/FormatHolder.java new file mode 100644 index 0000000..236452c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/FormatHolder.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +/** + * Holds a {@link Format}. + */ +public final class FormatHolder { + + /** + * The held {@link Format}. + */ + public Format format; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/IllegalSeekPositionException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/IllegalSeekPositionException.java new file mode 100644 index 0000000..59a3162 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/IllegalSeekPositionException.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +/** + * Thrown when an attempt is made to seek to a position that does not exist in the player's + * {@link Timeline}. + */ +public final class IllegalSeekPositionException extends IllegalStateException { + + /** + * The {@link Timeline} in which the seek was attempted. + */ + public final Timeline timeline; + /** + * The index of the window being seeked to. + */ + public final int windowIndex; + /** + * The seek position in the specified window. + */ + public final long positionMs; + + /** + * @param timeline The {@link Timeline} in which the seek was attempted. + * @param windowIndex The index of the window being seeked to. + * @param positionMs The seek position in the specified window. + */ + public IllegalSeekPositionException(Timeline timeline, int windowIndex, long positionMs) { + this.timeline = timeline; + this.windowIndex = windowIndex; + this.positionMs = positionMs; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/LoadControl.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/LoadControl.java new file mode 100644 index 0000000..bc57204 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/LoadControl.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectionArray; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; + +/** + * Controls buffering of media. + */ +public interface LoadControl { + + /** + * Called by the player when prepared with a new source. + */ + void onPrepared(); + + /** + * Called by the player when a track selection occurs. + * + * @param renderers The renderers. + * @param trackGroups The {@link TrackGroup}s from which the selection was made. + * @param trackSelections The track selections that were made. + */ + void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections); + + /** + * Called by the player when stopped. + */ + void onStopped(); + + /** + * Called by the player when released. + */ + void onReleased(); + + /** + * Returns the {@link Allocator} that should be used to obtain media buffer allocations. + */ + Allocator getAllocator(); + + /** + * Called by the player to determine whether sufficient media is buffered for playback to be + * started or resumed. + * + * @param bufferedDurationUs The duration of media that's currently buffered. + * @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by + * buffer depletion rather than a user action. Hence this parameter is false during initial + * buffering and when buffering as a result of a seek operation. + * @return Whether playback should be allowed to start or resume. + */ + boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering); + + /** + * Called by the player to determine whether it should continue to load the source. + * + * @param bufferedDurationUs The duration of media that's currently buffered. + * @return Whether the loading should continue. + */ + boolean shouldContinueLoading(long bufferedDurationUs); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ParserException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ParserException.java new file mode 100644 index 0000000..b1a3636 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ParserException.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import java.io.IOException; + +/** + * Thrown when an error occurs parsing media data and metadata. + */ +public class ParserException extends IOException { + + public ParserException() { + super(); + } + + /** + * @param message The detail message for the exception. + */ + public ParserException(String message) { + super(message); + } + + /** + * @param cause The cause for the exception. + */ + public ParserException(Throwable cause) { + super(cause); + } + + /** + * @param message The detail message for the exception. + * @param cause The cause for the exception. + */ + public ParserException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/PlaybackParameters.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/PlaybackParameters.java new file mode 100644 index 0000000..bfa51f8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/PlaybackParameters.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +/** + * The parameters that apply to playback. + */ +public final class PlaybackParameters { + + /** + * The default playback parameters: real-time playback with no pitch modification. + */ + public static final PlaybackParameters DEFAULT = new PlaybackParameters(1f, 1f); + + /** + * The factor by which playback will be sped up. + */ + public final float speed; + + /** + * The factor by which the audio pitch will be scaled. + */ + public final float pitch; + + private final int scaledUsPerMs; + + /** + * Creates new playback parameters. + * + * @param speed The factor by which playback will be sped up. + * @param pitch The factor by which the audio pitch will be scaled. + */ + public PlaybackParameters(float speed, float pitch) { + this.speed = speed; + this.pitch = pitch; + scaledUsPerMs = Math.round(speed * 1000f); + } + + /** + * Scales the millisecond duration {@code timeMs} by the playback speed, returning the result in + * microseconds. + * + * @param timeMs The time to scale, in milliseconds. + * @return The scaled time, in microseconds. + */ + public long getSpeedAdjustedDurationUs(long timeMs) { + return timeMs * scaledUsPerMs; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PlaybackParameters other = (PlaybackParameters) obj; + return this.speed == other.speed && this.pitch == other.pitch; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Float.floatToRawIntBits(speed); + result = 31 * result + Float.floatToRawIntBits(pitch); + return result; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Renderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Renderer.java new file mode 100644 index 0000000..fd71597 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Renderer.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer.ExoPlayerComponent; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MediaClock; +import java.io.IOException; + +/** + * Renders media read from a {@link SampleStream}. + *

    + * Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The renderer is + * transitioned through various states as the overall playback state changes. The valid state + * transitions are shown below, annotated with the methods that are called during each transition. + *

    + * Renderer state transitions + *

    + */ +public interface Renderer extends ExoPlayerComponent { + + /** + * The renderer is disabled. + */ + int STATE_DISABLED = 0; + /** + * The renderer is enabled but not started. A renderer in this state is not actively rendering + * media, but will typically hold resources that it requires for rendering (e.g. media decoders). + */ + int STATE_ENABLED = 1; + /** + * The renderer is started. Calls to {@link #render(long, long)} will cause media to be rendered. + */ + int STATE_STARTED = 2; + + /** + * Returns the track type that the {@link Renderer} handles. For example, a video renderer will + * return {@link C#TRACK_TYPE_VIDEO}, an audio renderer will return {@link C#TRACK_TYPE_AUDIO}, a + * text renderer will return {@link C#TRACK_TYPE_TEXT}, and so on. + * + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getTrackType(); + + /** + * Returns the capabilities of the renderer. + * + * @return The capabilities of the renderer. + */ + RendererCapabilities getCapabilities(); + + /** + * Sets the index of this renderer within the player. + * + * @param index The renderer index. + */ + void setIndex(int index); + + /** + * If the renderer advances its own playback position then this method returns a corresponding + * {@link MediaClock}. If provided, the player will use the returned {@link MediaClock} as its + * source of time during playback. A player may have at most one renderer that returns a + * {@link MediaClock} from this method. + * + * @return The {@link MediaClock} tracking the playback position of the renderer, or null. + */ + MediaClock getMediaClock(); + + /** + * Returns the current state of the renderer. + * + * @return The current state (one of the {@code STATE_*} constants). + */ + int getState(); + + /** + * Enables the renderer to consume from the specified {@link SampleStream}. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_DISABLED}. + * + * @param configuration The renderer configuration. + * @param formats The enabled formats. + * @param stream The {@link SampleStream} from which the renderer should consume. + * @param positionUs The player's current position. + * @param joining Whether this renderer is being enabled to join an ongoing playback. + * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} + * before they are rendered. + * @throws ExoPlaybackException If an error occurs. + */ + void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream, + long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException; + + /** + * Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be + * rendered. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}. + * + * @throws ExoPlaybackException If an error occurs. + */ + void start() throws ExoPlaybackException; + + /** + * Replaces the {@link SampleStream} from which samples will be consumed. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @param formats The enabled formats. + * @param stream The {@link SampleStream} from which the renderer should consume. + * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before + * they are rendered. + * @throws ExoPlaybackException If an error occurs. + */ + void replaceStream(Format[] formats, SampleStream stream, long offsetUs) + throws ExoPlaybackException; + + /** + * Returns the {@link SampleStream} being consumed, or null if the renderer is disabled. + */ + SampleStream getStream(); + + /** + * Returns whether the renderer has read the current {@link SampleStream} to the end. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + */ + boolean hasReadStreamToEnd(); + + /** + * Signals to the renderer that the current {@link SampleStream} will be the final one supplied + * before it is next disabled or reset. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + */ + void setCurrentStreamFinal(); + + /** + * Returns whether the current {@link SampleStream} will be the final one supplied before the + * renderer is next disabled or reset. + */ + boolean isCurrentStreamFinal(); + + /** + * Throws an error that's preventing the renderer from reading from its {@link SampleStream}. Does + * nothing if no such error exists. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @throws IOException An error that's preventing the renderer from making progress or buffering + * more data. + */ + void maybeThrowStreamError() throws IOException; + + /** + * Signals to the renderer that a position discontinuity has occurred. + *

    + * After a position discontinuity, the renderer's {@link SampleStream} is guaranteed to provide + * samples starting from a key frame. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @param positionUs The new playback position in microseconds. + * @throws ExoPlaybackException If an error occurs handling the reset. + */ + void resetPosition(long positionUs) throws ExoPlaybackException; + + /** + * Incrementally renders the {@link SampleStream}. + *

    + * If the renderer is in the {@link #STATE_ENABLED} state then each call to this method will do + * work toward being ready to render the {@link SampleStream} when the renderer is started. It may + * also render the very start of the media, for example the first frame of a video stream. If the + * renderer is in the {@link #STATE_STARTED} state then calls to this method will render the + * {@link SampleStream} in sync with the specified media positions. + *

    + * This method should return quickly, and should not block if the renderer is unable to make + * useful progress. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @param positionUs The current media time in microseconds, measured at the start of the + * current iteration of the rendering loop. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. + * @throws ExoPlaybackException If an error occurs. + */ + void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException; + + /** + * Whether the renderer is able to immediately render media from the current position. + *

    + * If the renderer is in the {@link #STATE_STARTED} state then returning true indicates that the + * renderer has everything that it needs to continue playback. Returning false indicates that + * the player should pause until the renderer is ready. + *

    + * If the renderer is in the {@link #STATE_ENABLED} state then returning true indicates that the + * renderer is ready for playback to be started. Returning false indicates that it is not. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @return Whether the renderer is ready to render media. + */ + boolean isReady(); + + /** + * Whether the renderer is ready for the {@link ExoPlayer} instance to transition to + * {@link ExoPlayer#STATE_ENDED}. The player will make this transition as soon as {@code true} is + * returned by all of its {@link Renderer}s. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @return Whether the renderer is ready for the player to transition to the ended state. + */ + boolean isEnded(); + + /** + * Stops the renderer, transitioning it to the {@link #STATE_ENABLED} state. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_STARTED}. + * + * @throws ExoPlaybackException If an error occurs. + */ + void stop() throws ExoPlaybackException; + + /** + * Disable the renderer, transitioning it to the {@link #STATE_DISABLED} state. + *

    + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}. + */ + void disable(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RendererCapabilities.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RendererCapabilities.java new file mode 100644 index 0000000..8e2bf9a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RendererCapabilities.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; + +/** + * Defines the capabilities of a {@link Renderer}. + */ +public interface RendererCapabilities { + + /** + * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of + * {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES}, + * {@link #FORMAT_UNSUPPORTED_SUBTYPE} and {@link #FORMAT_UNSUPPORTED_TYPE}. + */ + int FORMAT_SUPPORT_MASK = 0b11; + /** + * The {@link Renderer} is capable of rendering the format. + */ + int FORMAT_HANDLED = 0b11; + /** + * The {@link Renderer} is capable of rendering formats with the same mime type, but the + * properties of the format exceed the renderer's capability. + *

    + * Example: The {@link Renderer} is capable of rendering H264 and the format's mime type is + * {@link MimeTypes#VIDEO_H264}, but the format's resolution exceeds the maximum limit supported + * by the underlying H264 decoder. + */ + int FORMAT_EXCEEDS_CAPABILITIES = 0b10; + /** + * The {@link Renderer} is a general purpose renderer for formats of the same top-level type, + * but is not capable of rendering the format or any other format with the same mime type because + * the sub-type is not supported. + *

    + * Example: The {@link Renderer} is a general purpose audio renderer and the format's + * mime type matches audio/[subtype], but there does not exist a suitable decoder for [subtype]. + */ + int FORMAT_UNSUPPORTED_SUBTYPE = 0b01; + /** + * The {@link Renderer} is not capable of rendering the format, either because it does not + * support the format's top-level type, or because it's a specialized renderer for a different + * mime type. + *

    + * Example: The {@link Renderer} is a general purpose video renderer, but the format has an + * audio mime type. + */ + int FORMAT_UNSUPPORTED_TYPE = 0b00; + + /** + * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of + * {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and {@link #ADAPTIVE_NOT_SUPPORTED}. + */ + int ADAPTIVE_SUPPORT_MASK = 0b1100; + /** + * The {@link Renderer} can seamlessly adapt between formats. + */ + int ADAPTIVE_SEAMLESS = 0b1000; + /** + * The {@link Renderer} can adapt between formats, but may suffer a brief discontinuity + * (~50-100ms) when adaptation occurs. + */ + int ADAPTIVE_NOT_SEAMLESS = 0b0100; + /** + * The {@link Renderer} does not support adaptation between formats. + */ + int ADAPTIVE_NOT_SUPPORTED = 0b0000; + + /** + * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of + * {@link #TUNNELING_SUPPORTED} and {@link #TUNNELING_NOT_SUPPORTED}. + */ + int TUNNELING_SUPPORT_MASK = 0b10000; + /** + * The {@link Renderer} supports tunneled output. + */ + int TUNNELING_SUPPORTED = 0b10000; + /** + * The {@link Renderer} does not support tunneled output. + */ + int TUNNELING_NOT_SUPPORTED = 0b00000; + + /** + * Returns the track type that the {@link Renderer} handles. For example, a video renderer will + * return {@link C#TRACK_TYPE_VIDEO}, an audio renderer will return {@link C#TRACK_TYPE_AUDIO}, a + * text renderer will return {@link C#TRACK_TYPE_TEXT}, and so on. + * + * @see Renderer#getTrackType() + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getTrackType(); + + /** + * Returns the extent to which the {@link Renderer} supports a given format. The returned value is + * the bitwise OR of three properties: + *

      + *
    • The level of support for the format itself. One of {@link #FORMAT_HANDLED}, + * {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_SUBTYPE} and + * {@link #FORMAT_UNSUPPORTED_TYPE}.
    • + *
    • The level of support for adapting from the format to another format of the same mime type. + * One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and + * {@link #ADAPTIVE_NOT_SUPPORTED}.
    • + *
    • The level of support for tunneling. One of {@link #TUNNELING_SUPPORTED} and + * {@link #TUNNELING_NOT_SUPPORTED}.
    • + *
    + * The individual properties can be retrieved by performing a bitwise AND with + * {@link #FORMAT_SUPPORT_MASK}, {@link #ADAPTIVE_SUPPORT_MASK} and + * {@link #TUNNELING_SUPPORT_MASK} respectively. + * + * @param format The format. + * @return The extent to which the renderer is capable of supporting the given format. + * @throws ExoPlaybackException If an error occurs. + */ + int supportsFormat(Format format) throws ExoPlaybackException; + + /** + * Returns the extent to which the {@link Renderer} supports adapting between supported formats + * that have different mime types. + * + * @return The extent to which the renderer supports adapting between supported formats that have + * different mime types. One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and + * {@link #ADAPTIVE_NOT_SUPPORTED}. + * @throws ExoPlaybackException If an error occurs. + */ + int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RendererConfiguration.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RendererConfiguration.java new file mode 100644 index 0000000..bf370a2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RendererConfiguration.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +/** + * The configuration of a {@link Renderer}. + */ +public final class RendererConfiguration { + + /** + * The default configuration. + */ + public static final RendererConfiguration DEFAULT = + new RendererConfiguration(C.AUDIO_SESSION_ID_UNSET); + + /** + * The audio session id to use for tunneling, or {@link C#AUDIO_SESSION_ID_UNSET} if tunneling + * should not be enabled. + */ + public final int tunnelingAudioSessionId; + + /** + * @param tunnelingAudioSessionId The audio session id to use for tunneling, or + * {@link C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. + */ + public RendererConfiguration(int tunnelingAudioSessionId) { + this.tunnelingAudioSessionId = tunnelingAudioSessionId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + RendererConfiguration other = (RendererConfiguration) obj; + return tunnelingAudioSessionId == other.tunnelingAudioSessionId; + } + + @Override + public int hashCode() { + return tunnelingAudioSessionId; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RenderersFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RenderersFactory.java new file mode 100644 index 0000000..bf99ce0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/RenderersFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.os.Handler; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.AudioRendererEventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.text.TextRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.video.VideoRendererEventListener; + +/** + * Builds {@link Renderer} instances for use by a {@link SimpleExoPlayer}. + */ +public interface RenderersFactory { + + /** + * Builds the {@link Renderer} instances for a {@link SimpleExoPlayer}. + * + * @param eventHandler A handler to use when invoking event listeners and outputs. + * @param videoRendererEventListener An event listener for video renderers. + * @param videoRendererEventListener An event listener for audio renderers. + * @param textRendererOutput An output for text renderers. + * @param metadataRendererOutput An output for metadata renderers. + * @return The {@link Renderer instances}. + */ + Renderer[] createRenderers(Handler eventHandler, + VideoRendererEventListener videoRendererEventListener, + AudioRendererEventListener audioRendererEventListener, + TextRenderer.Output textRendererOutput, MetadataRenderer.Output metadataRendererOutput); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/SimpleExoPlayer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/SimpleExoPlayer.java new file mode 100644 index 0000000..f1ffe0d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/SimpleExoPlayer.java @@ -0,0 +1,885 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +import android.annotation.TargetApi; +import android.graphics.SurfaceTexture; +import android.media.MediaCodec; +import android.media.PlaybackParams; +import android.os.Handler; +import android.support.annotation.Nullable; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.AudioRendererEventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderCounters; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.TextRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectionArray; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.video.VideoRendererEventListener; +import java.util.List; + +/** + * An {@link ExoPlayer} implementation that uses default {@link Renderer} components. Instances can + * be obtained from {@link ExoPlayerFactory}. + */ +@TargetApi(16) +public class SimpleExoPlayer implements ExoPlayer { + + /** + * A listener for video rendering information from a {@link SimpleExoPlayer}. + */ + public interface VideoListener { + + /** + * Called each time there's a change in the size of the video being rendered. + * + * @param width The video width in pixels. + * @param height The video height in pixels. + * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise + * rotation in degrees that the application should apply for the video for it to be rendered + * in the correct orientation. This value will always be zero on API levels 21 and above, + * since the renderer will apply all necessary rotations internally. On earlier API levels + * this is not possible. Applications that use {@link android.view.TextureView} can apply + * the rotation by calling {@link android.view.TextureView#setTransform}. Applications that + * do not expect to encounter rotated videos can safely ignore this parameter. + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case + * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * content. + */ + void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, + float pixelWidthHeightRatio); + + /** + * Called when a frame is rendered for the first time since setting the surface, and when a + * frame is rendered for the first time since a video track was selected. + */ + void onRenderedFirstFrame(); + + boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture); + void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture); + } + + private static final String TAG = "SimpleExoPlayer"; + + protected final Renderer[] renderers; + + private final ExoPlayer player; + private final ComponentListener componentListener; + private final int videoRendererCount; + private final int audioRendererCount; + + private boolean needSetSurface = true; + + private Format videoFormat; + private Format audioFormat; + + private Surface surface; + private boolean ownsSurface; + @C.VideoScalingMode + private int videoScalingMode; + private SurfaceHolder surfaceHolder; + private TextureView textureView; + private TextRenderer.Output textOutput; + private MetadataRenderer.Output metadataOutput; + private VideoListener videoListener; + private AudioRendererEventListener audioDebugListener; + private VideoRendererEventListener videoDebugListener; + private DecoderCounters videoDecoderCounters; + private DecoderCounters audioDecoderCounters; + private int audioSessionId; + @C.StreamType + private int audioStreamType; + private float audioVolume; + + protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, + LoadControl loadControl) { + componentListener = new ComponentListener(); + renderers = renderersFactory.createRenderers(new Handler(), componentListener, + componentListener, componentListener, componentListener); + + // Obtain counts of video and audio renderers. + int videoRendererCount = 0; + int audioRendererCount = 0; + for (Renderer renderer : renderers) { + switch (renderer.getTrackType()) { + case C.TRACK_TYPE_VIDEO: + videoRendererCount++; + break; + case C.TRACK_TYPE_AUDIO: + audioRendererCount++; + break; + } + } + this.videoRendererCount = videoRendererCount; + this.audioRendererCount = audioRendererCount; + + // Set initial values. + audioVolume = 1; + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + audioStreamType = C.STREAM_TYPE_DEFAULT; + videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT; + + // Build the player and associated objects. + player = new ExoPlayerImpl(renderers, trackSelector, loadControl); + } + + /** + * Sets the video scaling mode. + *

    + * Note that the scaling mode only applies if a {@link MediaCodec}-based video {@link Renderer} is + * enabled and if the output surface is owned by a {@link android.view.SurfaceView}. + * + * @param videoScalingMode The video scaling mode. + */ + public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { + this.videoScalingMode = videoScalingMode; + ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SCALING_MODE, + videoScalingMode); + } + } + player.sendMessages(messages); + } + + /** + * Returns the video scaling mode. + */ + public @C.VideoScalingMode int getVideoScalingMode() { + return videoScalingMode; + } + + /** + * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} + * currently set on the player. + */ + public void clearVideoSurface() { + setVideoSurface(null); + } + + /** + * Sets the {@link Surface} onto which video will be rendered. The caller is responsible for + * tracking the lifecycle of the surface, and must clear the surface by calling + * {@code setVideoSurface(null)} if the surface is destroyed. + *

    + * If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link SurfaceHolder} + * then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, + * {@link #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} + * rather than this method, since passing the holder allows the player to track the lifecycle of + * the surface automatically. + * + * @param surface The {@link Surface}. + */ + public void setVideoSurface(Surface surface) { + removeSurfaceCallbacks(); + setVideoSurfaceInternal(surface, false); + } + + /** + * Clears the {@link Surface} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param surface The surface to clear. + */ + public void clearVideoSurface(Surface surface) { + if (surface != null && surface == this.surface) { + setVideoSurface(null); + } + } + + /** + * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be + * rendered. The player will track the lifecycle of the surface automatically. + * + * @param surfaceHolder The surface holder. + */ + public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) { + removeSurfaceCallbacks(); + this.surfaceHolder = surfaceHolder; + if (surfaceHolder == null) { + setVideoSurfaceInternal(null, false); + } else { + setVideoSurfaceInternal(surfaceHolder.getSurface(), false); + surfaceHolder.addCallback(componentListener); + } + } + + /** + * Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being + * rendered if it matches the one passed. Else does nothing. + * + * @param surfaceHolder The surface holder to clear. + */ + public void clearVideoSurfaceHolder(SurfaceHolder surfaceHolder) { + if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { + setVideoSurfaceHolder(null); + } + } + + /** + * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the + * lifecycle of the surface automatically. + * + * @param surfaceView The surface view. + */ + public void setVideoSurfaceView(SurfaceView surfaceView) { + setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); + } + + /** + * Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param surfaceView The texture view to clear. + */ + public void clearVideoSurfaceView(SurfaceView surfaceView) { + clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); + } + + /** + * Sets the {@link TextureView} onto which video will be rendered. The player will track the + * lifecycle of the surface automatically. + * + * @param textureView The texture view. + */ + public void setVideoTextureView(TextureView textureView) { + if (this.textureView == textureView) { + return; + } + removeSurfaceCallbacks(); + this.textureView = textureView; + needSetSurface = true; + if (textureView == null) { + setVideoSurfaceInternal(null, true); + } else { + if (textureView.getSurfaceTextureListener() != null) { + Log.w(TAG, "Replacing existing SurfaceTextureListener."); + } + SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); + setVideoSurfaceInternal(surfaceTexture == null ? null : new Surface(surfaceTexture), true); + textureView.setSurfaceTextureListener(componentListener); + } + } + + /** + * Clears the {@link TextureView} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param textureView The texture view to clear. + */ + public void clearVideoTextureView(TextureView textureView) { + if (textureView != null && textureView == this.textureView) { + setVideoTextureView(null); + } + } + + /** + * Sets the stream type for audio playback (see {@link C.StreamType} and + * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type + * is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}. + *

    + * Note that when the stream type changes, the AudioTrack must be reinitialized, which can + * introduce a brief gap in audio output. Note also that tracks in the same audio session must + * share the same routing, so a new audio session id will be generated. + * + * @param audioStreamType The stream type for audio playback. + */ + public void setAudioStreamType(@C.StreamType int audioStreamType) { + this.audioStreamType = audioStreamType; + ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_STREAM_TYPE, audioStreamType); + } + } + player.sendMessages(messages); + } + + /** + * Returns the stream type for audio playback. + */ + public @C.StreamType int getAudioStreamType() { + return audioStreamType; + } + + /** + * Sets the audio volume, with 0 being silence and 1 being unity gain. + * + * @param audioVolume The audio volume. + */ + public void setVolume(float audioVolume) { + this.audioVolume = audioVolume; + ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_VOLUME, audioVolume); + } + } + player.sendMessages(messages); + } + + /** + * Returns the audio volume, with 0 being silence and 1 being unity gain. + */ + public float getVolume() { + return audioVolume; + } + + /** + * Sets the {@link PlaybackParams} governing audio playback. + * + * @deprecated Use {@link #setPlaybackParameters(PlaybackParameters)}. + * @param params The {@link PlaybackParams}, or null to clear any previously set parameters. + */ + @Deprecated + @TargetApi(23) + public void setPlaybackParams(@Nullable PlaybackParams params) { + PlaybackParameters playbackParameters; + if (params != null) { + params.allowDefaults(); + playbackParameters = new PlaybackParameters(params.getSpeed(), params.getPitch()); + } else { + playbackParameters = null; + } + setPlaybackParameters(playbackParameters); + } + + /** + * Returns the video format currently being played, or null if no video is being played. + */ + public Format getVideoFormat() { + return videoFormat; + } + + /** + * Returns the audio format currently being played, or null if no audio is being played. + */ + public Format getAudioFormat() { + return audioFormat; + } + + /** + * Returns the audio session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} if not set. + */ + public int getAudioSessionId() { + return audioSessionId; + } + + /** + * Returns {@link DecoderCounters} for video, or null if no video is being played. + */ + public DecoderCounters getVideoDecoderCounters() { + return videoDecoderCounters; + } + + /** + * Returns {@link DecoderCounters} for audio, or null if no audio is being played. + */ + public DecoderCounters getAudioDecoderCounters() { + return audioDecoderCounters; + } + + /** + * Sets a listener to receive video events. + * + * @param listener The listener. + */ + public void setVideoListener(VideoListener listener) { + videoListener = listener; + } + + /** + * Clears the listener receiving video events if it matches the one passed. Else does nothing. + * + * @param listener The listener to clear. + */ + public void clearVideoListener(VideoListener listener) { + if (videoListener == listener) { + videoListener = null; + } + } + + /** + * Sets an output to receive text events. + * + * @param output The output. + */ + public void setTextOutput(TextRenderer.Output output) { + textOutput = output; + } + + /** + * Clears the output receiving text events if it matches the one passed. Else does nothing. + * + * @param output The output to clear. + */ + public void clearTextOutput(TextRenderer.Output output) { + if (textOutput == output) { + textOutput = null; + } + } + + /** + * Sets a listener to receive metadata events. + * + * @param output The output. + */ + public void setMetadataOutput(MetadataRenderer.Output output) { + metadataOutput = output; + } + + /** + * Clears the output receiving metadata events if it matches the one passed. Else does nothing. + * + * @param output The output to clear. + */ + public void clearMetadataOutput(MetadataRenderer.Output output) { + if (metadataOutput == output) { + metadataOutput = null; + } + } + + /** + * Sets a listener to receive debug events from the video renderer. + * + * @param listener The listener. + */ + public void setVideoDebugListener(VideoRendererEventListener listener) { + videoDebugListener = listener; + } + + /** + * Sets a listener to receive debug events from the audio renderer. + * + * @param listener The listener. + */ + public void setAudioDebugListener(AudioRendererEventListener listener) { + audioDebugListener = listener; + } + + // ExoPlayer implementation + + @Override + public void addListener(EventListener listener) { + player.addListener(listener); + } + + @Override + public void removeListener(EventListener listener) { + player.removeListener(listener); + } + + @Override + public int getPlaybackState() { + return player.getPlaybackState(); + } + + @Override + public void prepare(MediaSource mediaSource) { + player.prepare(mediaSource); + } + + @Override + public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { + player.prepare(mediaSource, resetPosition, resetState); + } + + @Override + public void setPlayWhenReady(boolean playWhenReady) { + player.setPlayWhenReady(playWhenReady); + } + + @Override + public boolean getPlayWhenReady() { + return player.getPlayWhenReady(); + } + + @Override + public boolean isLoading() { + return player.isLoading(); + } + + @Override + public void seekToDefaultPosition() { + player.seekToDefaultPosition(); + } + + @Override + public void seekToDefaultPosition(int windowIndex) { + player.seekToDefaultPosition(windowIndex); + } + + @Override + public void seekTo(long positionMs) { + player.seekTo(positionMs); + } + + @Override + public void seekTo(int windowIndex, long positionMs) { + player.seekTo(windowIndex, positionMs); + } + + @Override + public void setPlaybackParameters(PlaybackParameters playbackParameters) { + player.setPlaybackParameters(playbackParameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return player.getPlaybackParameters(); + } + + @Override + public void stop() { + player.stop(); + } + + @Override + public void release() { + player.release(); + removeSurfaceCallbacks(); + if (surface != null) { + if (ownsSurface) { + surface.release(); + } + surface = null; + } + } + + @Override + public void sendMessages(ExoPlayerMessage... messages) { + player.sendMessages(messages); + } + + @Override + public void blockingSendMessages(ExoPlayerMessage... messages) { + player.blockingSendMessages(messages); + } + + @Override + public int getRendererCount() { + return player.getRendererCount(); + } + + @Override + public int getRendererType(int index) { + return player.getRendererType(index); + } + + @Override + public TrackGroupArray getCurrentTrackGroups() { + return player.getCurrentTrackGroups(); + } + + @Override + public TrackSelectionArray getCurrentTrackSelections() { + return player.getCurrentTrackSelections(); + } + + @Override + public Timeline getCurrentTimeline() { + return player.getCurrentTimeline(); + } + + @Override + public Object getCurrentManifest() { + return player.getCurrentManifest(); + } + + @Override + public int getCurrentPeriodIndex() { + return player.getCurrentPeriodIndex(); + } + + @Override + public int getCurrentWindowIndex() { + return player.getCurrentWindowIndex(); + } + + @Override + public long getDuration() { + return player.getDuration(); + } + + @Override + public long getCurrentPosition() { + return player.getCurrentPosition(); + } + + @Override + public long getBufferedPosition() { + return player.getBufferedPosition(); + } + + @Override + public int getBufferedPercentage() { + return player.getBufferedPercentage(); + } + + @Override + public boolean isCurrentWindowDynamic() { + return player.isCurrentWindowDynamic(); + } + + @Override + public boolean isCurrentWindowSeekable() { + return player.isCurrentWindowSeekable(); + } + + // Internal methods. + + private void removeSurfaceCallbacks() { + if (textureView != null) { + if (textureView.getSurfaceTextureListener() != componentListener) { + Log.w(TAG, "SurfaceTextureListener already unset or replaced."); + } else { + textureView.setSurfaceTextureListener(null); + } + textureView = null; + } + if (surfaceHolder != null) { + surfaceHolder.removeCallback(componentListener); + surfaceHolder = null; + } + } + + private void setVideoSurfaceInternal(Surface surface, boolean ownsSurface) { + // Note: We don't turn this method into a no-op if the surface is being replaced with itself + // so as to ensure onRenderedFirstFrame callbacks are still called in this case. + ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SURFACE, surface); + } + } + if (this.surface != null && this.surface != surface) { + // If we created this surface, we are responsible for releasing it. + if (this.ownsSurface) { + this.surface.release(); + } + // We're replacing a surface. Block to ensure that it's not accessed after the method returns. + player.blockingSendMessages(messages); + } else { + player.sendMessages(messages); + } + this.surface = surface; + this.ownsSurface = ownsSurface; + } + + private final class ComponentListener implements VideoRendererEventListener, + AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output, + SurfaceHolder.Callback, TextureView.SurfaceTextureListener { + + // VideoRendererEventListener implementation + + @Override + public void onVideoEnabled(DecoderCounters counters) { + videoDecoderCounters = counters; + if (videoDebugListener != null) { + videoDebugListener.onVideoEnabled(counters); + } + } + + @Override + public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, + long initializationDurationMs) { + if (videoDebugListener != null) { + videoDebugListener.onVideoDecoderInitialized(decoderName, initializedTimestampMs, + initializationDurationMs); + } + } + + @Override + public void onVideoInputFormatChanged(Format format) { + videoFormat = format; + if (videoDebugListener != null) { + videoDebugListener.onVideoInputFormatChanged(format); + } + } + + @Override + public void onDroppedFrames(int count, long elapsed) { + if (videoDebugListener != null) { + videoDebugListener.onDroppedFrames(count, elapsed); + } + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, + float pixelWidthHeightRatio) { + if (videoListener != null) { + videoListener.onVideoSizeChanged(width, height, unappliedRotationDegrees, + pixelWidthHeightRatio); + } + if (videoDebugListener != null) { + videoDebugListener.onVideoSizeChanged(width, height, unappliedRotationDegrees, + pixelWidthHeightRatio); + } + } + + @Override + public void onRenderedFirstFrame(Surface surface) { + if (videoListener != null && SimpleExoPlayer.this.surface == surface) { + videoListener.onRenderedFirstFrame(); + } + if (videoDebugListener != null) { + videoDebugListener.onRenderedFirstFrame(surface); + } + } + + @Override + public void onVideoDisabled(DecoderCounters counters) { + if (videoDebugListener != null) { + videoDebugListener.onVideoDisabled(counters); + } + videoFormat = null; + videoDecoderCounters = null; + } + + // AudioRendererEventListener implementation + + @Override + public void onAudioEnabled(DecoderCounters counters) { + audioDecoderCounters = counters; + if (audioDebugListener != null) { + audioDebugListener.onAudioEnabled(counters); + } + } + + @Override + public void onAudioSessionId(int sessionId) { + audioSessionId = sessionId; + if (audioDebugListener != null) { + audioDebugListener.onAudioSessionId(sessionId); + } + } + + @Override + public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, + long initializationDurationMs) { + if (audioDebugListener != null) { + audioDebugListener.onAudioDecoderInitialized(decoderName, initializedTimestampMs, + initializationDurationMs); + } + } + + @Override + public void onAudioInputFormatChanged(Format format) { + audioFormat = format; + if (audioDebugListener != null) { + audioDebugListener.onAudioInputFormatChanged(format); + } + } + + @Override + public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, + long elapsedSinceLastFeedMs) { + if (audioDebugListener != null) { + audioDebugListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + } + + @Override + public void onAudioDisabled(DecoderCounters counters) { + if (audioDebugListener != null) { + audioDebugListener.onAudioDisabled(counters); + } + audioFormat = null; + audioDecoderCounters = null; + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + } + + // TextRenderer.Output implementation + + @Override + public void onCues(List cues) { + if (textOutput != null) { + textOutput.onCues(cues); + } + } + + // MetadataRenderer.Output implementation + + @Override + public void onMetadata(Metadata metadata) { + if (metadataOutput != null) { + metadataOutput.onMetadata(metadata); + } + } + + // SurfaceHolder.Callback implementation + + @Override + public void surfaceCreated(SurfaceHolder holder) { + setVideoSurfaceInternal(holder.getSurface(), false); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + // Do nothing. + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + setVideoSurfaceInternal(null, false); + } + + // TextureView.SurfaceTextureListener implementation + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { + if (needSetSurface) { + setVideoSurfaceInternal(new Surface(surfaceTexture), true); + needSetSurface = false; + } + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { + // Do nothing. + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + if (videoListener.onSurfaceDestroyed(surfaceTexture)) { + return false; + } + setVideoSurfaceInternal(null, true); + needSetSurface = true; + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + videoListener.onSurfaceTextureUpdated(surfaceTexture); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Timeline.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Timeline.java new file mode 100644 index 0000000..e83e211 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/Timeline.java @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2; + +/** + * A representation of media currently available for playback. + *

    + * Timeline instances are immutable. For cases where the available media is changing dynamically + * (e.g. live streams) a timeline provides a snapshot of the media currently available. + *

    + * A timeline consists of related {@link Period}s and {@link Window}s. A period defines a single + * logical piece of media, for example a media file. A window spans one or more periods, defining + * the region within those periods that's currently available for playback along with additional + * information such as whether seeking is supported within the window. Each window defines a default + * position, which is the position from which playback will start when the player starts playing the + * window. The following examples illustrate timelines for various use cases. + * + *

    Single media file or on-demand stream

    + *

    + * Example timeline for a single file + *

    + * A timeline for a single media file or on-demand stream consists of a single period and window. + * The window spans the whole period, indicating that all parts of the media are available for + * playback. The window's default position is typically at the start of the period (indicated by the + * black dot in the figure above). + * + *

    Playlist of media files or on-demand streams

    + *

    + * Example timeline for a playlist of files + *

    + * A timeline for a playlist of media files or on-demand streams consists of multiple periods, each + * with its own window. Each window spans the whole of the corresponding period, and typically has a + * default position at the start of the period. The properties of the periods and windows (e.g. + * their durations and whether the window is seekable) will often only become known when the player + * starts buffering the corresponding file or stream. + * + *

    Live stream with limited availability

    + *

    + * Example timeline for a live stream with
+ *       limited availability + *

    + * A timeline for a live stream consists of a period whose duration is unknown, since it's + * continually extending as more content is broadcast. If content only remains available for a + * limited period of time then the window may start at a non-zero position, defining the region of + * content that can still be played. The window will have {@link Window#isDynamic} set to true if + * the stream is still live. Its default position is typically near to the live edge (indicated by + * the black dot in the figure above). + * + *

    Live stream with indefinite availability

    + *

    + * Example timeline for a live stream with
+ *       indefinite availability + *

    + * A timeline for a live stream with indefinite availability is similar to the + * Live stream with limited availability case, except that the window + * starts at the beginning of the period to indicate that all of the previously broadcast content + * can still be played. + * + *

    Live stream with multiple periods

    + *

    + * Example timeline for a live stream
+ *       with multiple periods + *

    + * This case arises when a live stream is explicitly divided into separate periods, for example at + * content and advert boundaries. This case is similar to the Live stream + * with limited availability case, except that the window may span more than one period. + * Multiple periods are also possible in the indefinite availability case. + * + *

    On-demand pre-roll followed by live stream

    + *

    + * Example timeline for an on-demand pre-roll
+ *       followed by a live stream + *

    + * This case is the concatenation of the Single media file or on-demand + * stream and Live stream with multiple periods cases. When playback + * of the pre-roll ends, playback of the live stream will start from its default position near the + * live edge. + */ +public abstract class Timeline { + + /** + * An empty timeline. + */ + public static final Timeline EMPTY = new Timeline() { + + @Override + public int getWindowCount() { + return 0; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getPeriodCount() { + return 0; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getIndexOfPeriod(Object uid) { + return C.INDEX_UNSET; + } + + }; + + /** + * Returns whether the timeline is empty. + */ + public final boolean isEmpty() { + return getWindowCount() == 0; + } + + /** + * Returns the number of windows in the timeline. + */ + public abstract int getWindowCount(); + + /** + * Populates a {@link Window} with data for the window at the specified index. Does not populate + * {@link Window#id}. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @return The populated {@link Window}, for convenience. + */ + public final Window getWindow(int windowIndex, Window window) { + return getWindow(windowIndex, window, false); + } + + /** + * Populates a {@link Window} with data for the window at the specified index. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to + * null. The caller should pass false for efficiency reasons unless the field is required. + * @return The populated {@link Window}, for convenience. + */ + public Window getWindow(int windowIndex, Window window, boolean setIds) { + return getWindow(windowIndex, window, setIds, 0); + } + + /** + * Populates a {@link Window} with data for the window at the specified index. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to + * null. The caller should pass false for efficiency reasons unless the field is required. + * @param defaultPositionProjectionUs A duration into the future that the populated window's + * default start position should be projected. + * @return The populated {@link Window}, for convenience. + */ + public abstract Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs); + + /** + * Returns the number of periods in the timeline. + */ + public abstract int getPeriodCount(); + + /** + * Populates a {@link Period} with data for the period at the specified index. Does not populate + * {@link Period#id} and {@link Period#uid}. + * + * @param periodIndex The index of the period. + * @param period The {@link Period} to populate. Must not be null. + * @return The populated {@link Period}, for convenience. + */ + public final Period getPeriod(int periodIndex, Period period) { + return getPeriod(periodIndex, period, false); + } + + /** + * Populates a {@link Period} with data for the period at the specified index. + * + * @param periodIndex The index of the period. + * @param period The {@link Period} to populate. Must not be null. + * @param setIds Whether {@link Period#id} and {@link Period#uid} should be populated. If false, + * the fields will be set to null. The caller should pass false for efficiency reasons unless + * the fields are required. + * @return The populated {@link Period}, for convenience. + */ + public abstract Period getPeriod(int periodIndex, Period period, boolean setIds); + + /** + * Returns the index of the period identified by its unique {@code id}, or {@link C#INDEX_UNSET} + * if the period is not in the timeline. + * + * @param uid A unique identifier for a period. + * @return The index of the period, or {@link C#INDEX_UNSET} if the period was not found. + */ + public abstract int getIndexOfPeriod(Object uid); + + /** + * Holds information about a window in a {@link Timeline}. A window defines a region of media + * currently available for playback along with additional information such as whether seeking is + * supported within the window. See {@link Timeline} for more details. The figure below shows some + * of the information defined by a window, as well as how this information relates to + * corresponding {@link Period}s in the timeline. + *

    + * Information defined by a timeline window + *

    + */ + public static final class Window { + + /** + * An identifier for the window. Not necessarily unique. + */ + public Object id; + + /** + * The start time of the presentation to which this window belongs in milliseconds since the + * epoch, or {@link C#TIME_UNSET} if unknown or not applicable. For informational purposes only. + */ + public long presentationStartTimeMs; + + /** + * The window's start time in milliseconds since the epoch, or {@link C#TIME_UNSET} if unknown + * or not applicable. For informational purposes only. + */ + public long windowStartTimeMs; + + /** + * Whether it's possible to seek within this window. + */ + public boolean isSeekable; + + /** + * Whether this window may change when the timeline is updated. + */ + public boolean isDynamic; + + /** + * The index of the first period that belongs to this window. + */ + public int firstPeriodIndex; + + /** + * The index of the last period that belongs to this window. + */ + public int lastPeriodIndex; + + /** + * The default position relative to the start of the window at which to begin playback, in + * microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a + * non-zero default position projection, and if the specified projection cannot be performed + * whilst remaining within the bounds of the window. + */ + public long defaultPositionUs; + + /** + * The duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long durationUs; + + /** + * The position of the start of this window relative to the start of the first period belonging + * to it, in microseconds. + */ + public long positionInFirstPeriodUs; + + /** + * Sets the data held by this window. + */ + public Window set(Object id, long presentationStartTimeMs, long windowStartTimeMs, + boolean isSeekable, boolean isDynamic, long defaultPositionUs, long durationUs, + int firstPeriodIndex, int lastPeriodIndex, long positionInFirstPeriodUs) { + this.id = id; + this.presentationStartTimeMs = presentationStartTimeMs; + this.windowStartTimeMs = windowStartTimeMs; + this.isSeekable = isSeekable; + this.isDynamic = isDynamic; + this.defaultPositionUs = defaultPositionUs; + this.durationUs = durationUs; + this.firstPeriodIndex = firstPeriodIndex; + this.lastPeriodIndex = lastPeriodIndex; + this.positionInFirstPeriodUs = positionInFirstPeriodUs; + return this; + } + + /** + * Returns the default position relative to the start of the window at which to begin playback, + * in milliseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a + * non-zero default position projection, and if the specified projection cannot be performed + * whilst remaining within the bounds of the window. + */ + public long getDefaultPositionMs() { + return C.usToMs(defaultPositionUs); + } + + /** + * Returns the default position relative to the start of the window at which to begin playback, + * in microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a + * non-zero default position projection, and if the specified projection cannot be performed + * whilst remaining within the bounds of the window. + */ + public long getDefaultPositionUs() { + return defaultPositionUs; + } + + /** + * Returns the duration of the window in milliseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationMs() { + return C.usToMs(durationUs); + } + + /** + * Returns the duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationUs() { + return durationUs; + } + + /** + * Returns the position of the start of this window relative to the start of the first period + * belonging to it, in milliseconds. + */ + public long getPositionInFirstPeriodMs() { + return C.usToMs(positionInFirstPeriodUs); + } + + /** + * Returns the position of the start of this window relative to the start of the first period + * belonging to it, in microseconds. + */ + public long getPositionInFirstPeriodUs() { + return positionInFirstPeriodUs; + } + + } + + /** + * Holds information about a period in a {@link Timeline}. A period defines a single logical piece + * of media, for example a a media file. See {@link Timeline} for more details. The figure below + * shows some of the information defined by a period, as well as how this information relates to a + * corresponding {@link Window} in the timeline. + *

    + * Information defined by a period + *

    + */ + public static final class Period { + + /** + * An identifier for the period. Not necessarily unique. + */ + public Object id; + + /** + * A unique identifier for the period. + */ + public Object uid; + + /** + * The index of the window to which this period belongs. + */ + public int windowIndex; + + /** + * The duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long durationUs; + + /** + * Whether this period contains an ad. + */ + public boolean isAd; + + private long positionInWindowUs; + + /** + * Sets the data held by this period. + */ + public Period set(Object id, Object uid, int windowIndex, long durationUs, + long positionInWindowUs, boolean isAd) { + this.id = id; + this.uid = uid; + this.windowIndex = windowIndex; + this.durationUs = durationUs; + this.positionInWindowUs = positionInWindowUs; + this.isAd = isAd; + return this; + } + + /** + * Returns the duration of the period in milliseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationMs() { + return C.usToMs(durationUs); + } + + /** + * Returns the duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationUs() { + return durationUs; + } + + /** + * Returns the position of the start of this period relative to the start of the window to which + * it belongs, in milliseconds. May be negative if the start of the period is not within the + * window. + */ + public long getPositionInWindowMs() { + return C.usToMs(positionInWindowUs); + } + + /** + * Returns the position of the start of this period relative to the start of the window to which + * it belongs, in microseconds. May be negative if the start of the period is not within the + * window. + */ + public long getPositionInWindowUs() { + return positionInWindowUs; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/Ac3Util.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/Ac3Util.java new file mode 100644 index 0000000..b60189b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/Ac3Util.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.nio.ByteBuffer; + +/** + * Utility methods for parsing (E-)AC-3 syncframes, which are access units in (E-)AC-3 bitstreams. + */ +public final class Ac3Util { + + /** + * Holds sample format information as presented by a syncframe header. + */ + public static final class Ac3SyncFrameInfo { + + /** + * The sample mime type of the bitstream. One of {@link MimeTypes#AUDIO_AC3} and + * {@link MimeTypes#AUDIO_E_AC3}. + */ + public final String mimeType; + /** + * The audio sampling rate in Hz. + */ + public final int sampleRate; + /** + * The number of audio channels + */ + public final int channelCount; + /** + * The size of the frame. + */ + public final int frameSize; + /** + * Number of audio samples in the frame. + */ + public final int sampleCount; + + private Ac3SyncFrameInfo(String mimeType, int channelCount, int sampleRate, int frameSize, + int sampleCount) { + this.mimeType = mimeType; + this.channelCount = channelCount; + this.sampleRate = sampleRate; + this.frameSize = frameSize; + this.sampleCount = sampleCount; + } + + } + + /** + * The number of new samples per (E-)AC-3 audio block. + */ + private static final int AUDIO_SAMPLES_PER_AUDIO_BLOCK = 256; + /** + * Each syncframe has 6 blocks that provide 256 new audio samples. See ETSI TS 102 366 4.1. + */ + private static final int AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT = 6 * AUDIO_SAMPLES_PER_AUDIO_BLOCK; + /** + * Number of audio blocks per E-AC-3 syncframe, indexed by numblkscod. + */ + private static final int[] BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD = new int[] {1, 2, 3, 6}; + /** + * Sample rates, indexed by fscod. + */ + private static final int[] SAMPLE_RATE_BY_FSCOD = new int[] {48000, 44100, 32000}; + /** + * Sample rates, indexed by fscod2 (E-AC-3). + */ + private static final int[] SAMPLE_RATE_BY_FSCOD2 = new int[] {24000, 22050, 16000}; + /** + * Channel counts, indexed by acmod. + */ + private static final int[] CHANNEL_COUNT_BY_ACMOD = new int[] {2, 1, 2, 3, 3, 4, 4, 5}; + /** + * Nominal bitrates in kbps, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) + */ + private static final int[] BITRATE_BY_HALF_FRMSIZECOD = new int[] {32, 40, 48, 56, 64, 80, 96, + 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640}; + /** + * 16-bit words per syncframe, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) + */ + private static final int[] SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1 = new int[] {69, 87, 104, + 121, 139, 174, 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393}; + + /** + * Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to + * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. + * + * @param data The AC3SpecificBox to parse. + * @param trackId The track identifier to set on the format, or null. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The AC-3 format parsed from data in the header. + */ + public static Format parseAc3AnnexFFormat(ParsableByteArray data, String trackId, + String language, DrmInitData drmInitData) { + int fscod = (data.readUnsignedByte() & 0xC0) >> 6; + int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + int nextByte = data.readUnsignedByte(); + int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x38) >> 3]; + if ((nextByte & 0x04) != 0) { // lfeon + channelCount++; + } + return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, null, Format.NO_VALUE, + Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language); + } + + /** + * Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to + * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. + * + * @param data The EC3SpecificBox to parse. + * @param trackId The track identifier to set on the format, or null. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The E-AC-3 format parsed from data in the header. + */ + public static Format parseEAc3AnnexFFormat(ParsableByteArray data, String trackId, + String language, DrmInitData drmInitData) { + data.skipBytes(2); // data_rate, num_ind_sub + + // Read only the first substream. + // TODO: Read later substreams? + int fscod = (data.readUnsignedByte() & 0xC0) >> 6; + int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + int nextByte = data.readUnsignedByte(); + int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x0E) >> 1]; + if ((nextByte & 0x01) != 0) { // lfeon + channelCount++; + } + return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, null, Format.NO_VALUE, + Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language); + } + + /** + * Returns (E-)AC-3 format information given {@code data} containing a syncframe. The reading + * position of {@code data} will be modified. + * + * @param data The data to parse, positioned at the start of the syncframe. + * @return The (E-)AC-3 format data parsed from the header. + */ + public static Ac3SyncFrameInfo parseAc3SyncframeInfo(ParsableBitArray data) { + int initialPosition = data.getPosition(); + data.skipBits(40); + boolean isEac3 = data.readBits(5) == 16; + data.setPosition(initialPosition); + String mimeType; + int sampleRate; + int acmod; + int frameSize; + int sampleCount; + if (isEac3) { + mimeType = MimeTypes.AUDIO_E_AC3; + data.skipBits(16 + 2 + 3); // syncword, strmtype, substreamid + frameSize = (data.readBits(11) + 1) * 2; + int fscod = data.readBits(2); + int audioBlocks; + if (fscod == 3) { + sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)]; + audioBlocks = 6; + } else { + int numblkscod = data.readBits(2); + audioBlocks = BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[numblkscod]; + sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + } + sampleCount = AUDIO_SAMPLES_PER_AUDIO_BLOCK * audioBlocks; + acmod = data.readBits(3); + } else /* is AC-3 */ { + mimeType = MimeTypes.AUDIO_AC3; + data.skipBits(16 + 16); // syncword, crc1 + int fscod = data.readBits(2); + int frmsizecod = data.readBits(6); + frameSize = getAc3SyncframeSize(fscod, frmsizecod); + data.skipBits(5 + 3); // bsid, bsmod + acmod = data.readBits(3); + if ((acmod & 0x01) != 0 && acmod != 1) { + data.skipBits(2); // cmixlev + } + if ((acmod & 0x04) != 0) { + data.skipBits(2); // surmixlev + } + if (acmod == 2) { + data.skipBits(2); // dsurmod + } + sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + sampleCount = AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT; + } + boolean lfeon = data.readBit(); + int channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0); + return new Ac3SyncFrameInfo(mimeType, channelCount, sampleRate, frameSize, sampleCount); + } + + /** + * Returns the size in bytes of the given AC-3 syncframe. + * + * @param data The syncframe to parse. + * @return The syncframe size in bytes. {@link C#LENGTH_UNSET} if the input is invalid. + */ + public static int parseAc3SyncframeSize(byte[] data) { + if (data.length < 5) { + return C.LENGTH_UNSET; + } + int fscod = (data[4] & 0xC0) >> 6; + int frmsizecod = data[4] & 0x3F; + return getAc3SyncframeSize(fscod, frmsizecod); + } + + /** + * Returns the number of audio samples in an AC-3 syncframe. + */ + public static int getAc3SyncframeAudioSampleCount() { + return AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT; + } + + /** + * Reads the number of audio samples represented by the given E-AC-3 syncframe. The buffer's + * position is not modified. + * + * @param buffer The {@link ByteBuffer} from which to read the syncframe. + * @return The number of audio samples represented by the syncframe. + */ + public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) { + // See ETSI TS 102 366 subsection E.1.2.2. + int fscod = (buffer.get(buffer.position() + 4) & 0xC0) >> 6; + return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (fscod == 0x03 ? 6 + : BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]); + } + + private static int getAc3SyncframeSize(int fscod, int frmsizecod) { + int halfFrmsizecod = frmsizecod / 2; + if (fscod < 0 || fscod >= SAMPLE_RATE_BY_FSCOD.length || frmsizecod < 0 + || halfFrmsizecod >= SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1.length) { + // Invalid values provided. + return C.LENGTH_UNSET; + } + int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + if (sampleRate == 44100) { + return 2 * (SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1[halfFrmsizecod] + (frmsizecod % 2)); + } + int bitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod]; + if (sampleRate == 32000) { + return 6 * bitrate; + } else { // sampleRate == 48000 + return 4 * bitrate; + } + } + + private Ac3Util() {} + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioCapabilities.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioCapabilities.java new file mode 100644 index 0000000..3ba9d9c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioCapabilities.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioFormat; +import android.media.AudioManager; +import java.util.Arrays; + +/** + * Represents the set of audio formats that a device is capable of playing. + */ +@TargetApi(21) +public final class AudioCapabilities { + + /** + * The minimum audio capabilities supported by all devices. + */ + public static final AudioCapabilities DEFAULT_AUDIO_CAPABILITIES = + new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, 2); + + /** + * Returns the current audio capabilities for the device. + * + * @param context A context for obtaining the current audio capabilities. + * @return The current audio capabilities for the device. + */ + @SuppressWarnings("InlinedApi") + public static AudioCapabilities getCapabilities(Context context) { + return getCapabilities( + context.registerReceiver(null, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG))); + } + + @SuppressLint("InlinedApi") + /* package */ static AudioCapabilities getCapabilities(Intent intent) { + if (intent == null || intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) == 0) { + return DEFAULT_AUDIO_CAPABILITIES; + } + return new AudioCapabilities(intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS), + intent.getIntExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, 0)); + } + + private final int[] supportedEncodings; + private final int maxChannelCount; + + /** + * Constructs new audio capabilities based on a set of supported encodings and a maximum channel + * count. + * + * @param supportedEncodings Supported audio encodings from {@link android.media.AudioFormat}'s + * {@code ENCODING_*} constants. + * @param maxChannelCount The maximum number of audio channels that can be played simultaneously. + */ + /* package */ AudioCapabilities(int[] supportedEncodings, int maxChannelCount) { + if (supportedEncodings != null) { + this.supportedEncodings = Arrays.copyOf(supportedEncodings, supportedEncodings.length); + Arrays.sort(this.supportedEncodings); + } else { + this.supportedEncodings = new int[0]; + } + this.maxChannelCount = maxChannelCount; + } + + /** + * Returns whether this device supports playback of the specified audio {@code encoding}. + * + * @param encoding One of {@link android.media.AudioFormat}'s {@code ENCODING_*} constants. + * @return Whether this device supports playback the specified audio {@code encoding}. + */ + public boolean supportsEncoding(int encoding) { + return Arrays.binarySearch(supportedEncodings, encoding) >= 0; + } + + /** + * Returns the maximum number of channels the device can play at the same time. + */ + public int getMaxChannelCount() { + return maxChannelCount; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AudioCapabilities)) { + return false; + } + AudioCapabilities audioCapabilities = (AudioCapabilities) other; + return Arrays.equals(supportedEncodings, audioCapabilities.supportedEncodings) + && maxChannelCount == audioCapabilities.maxChannelCount; + } + + @Override + public int hashCode() { + return maxChannelCount + 31 * Arrays.hashCode(supportedEncodings); + } + + @Override + public String toString() { + return "AudioCapabilities[maxChannelCount=" + maxChannelCount + + ", supportedEncodings=" + Arrays.toString(supportedEncodings) + "]"; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioCapabilitiesReceiver.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioCapabilitiesReceiver.java new file mode 100644 index 0000000..13dfb2d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioCapabilitiesReceiver.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Receives broadcast events indicating changes to the device's audio capabilities, notifying a + * {@link Listener} when audio capability changes occur. + */ +public final class AudioCapabilitiesReceiver { + + /** + * Listener notified when audio capabilities change. + */ + public interface Listener { + + /** + * Called when the audio capabilities change. + * + * @param audioCapabilities The current audio capabilities for the device. + */ + void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities); + + } + + private final Context context; + private final Listener listener; + private final BroadcastReceiver receiver; + + /* package */ AudioCapabilities audioCapabilities; + + /** + * @param context A context for registering the receiver. + * @param listener The listener to notify when audio capabilities change. + */ + public AudioCapabilitiesReceiver(Context context, Listener listener) { + this.context = Assertions.checkNotNull(context); + this.listener = Assertions.checkNotNull(listener); + this.receiver = Util.SDK_INT >= 21 ? new HdmiAudioPlugBroadcastReceiver() : null; + } + + /** + * Registers the receiver, meaning it will notify the listener when audio capability changes + * occur. The current audio capabilities will be returned. It is important to call + * {@link #unregister} when the receiver is no longer required. + * + * @return The current audio capabilities for the device. + */ + @SuppressWarnings("InlinedApi") + public AudioCapabilities register() { + Intent stickyIntent = receiver == null ? null + : context.registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)); + audioCapabilities = AudioCapabilities.getCapabilities(stickyIntent); + return audioCapabilities; + } + + /** + * Unregisters the receiver, meaning it will no longer notify the listener when audio capability + * changes occur. + */ + public void unregister() { + if (receiver != null) { + context.unregisterReceiver(receiver); + } + } + + private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (!isInitialStickyBroadcast()) { + AudioCapabilities newAudioCapabilities = AudioCapabilities.getCapabilities(intent); + if (!newAudioCapabilities.equals(audioCapabilities)) { + audioCapabilities = newAudioCapabilities; + listener.onAudioCapabilitiesChanged(newAudioCapabilities); + } + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioDecoderException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioDecoderException.java new file mode 100644 index 0000000..c71684b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioDecoderException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +/** + * Thrown when an audio decoder error occurs. + */ +public abstract class AudioDecoderException extends Exception { + + /** + * @param detailMessage The detail message for this exception. + */ + public AudioDecoderException(String detailMessage) { + super(detailMessage); + } + + /** + * @param detailMessage The detail message for this exception. + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public AudioDecoderException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioProcessor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioProcessor.java new file mode 100644 index 0000000..c6803f0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioProcessor.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Interface for audio processors. + */ +public interface AudioProcessor { + + /** + * Exception thrown when a processor can't be configured for a given input audio format. + */ + final class UnhandledFormatException extends Exception { + + public UnhandledFormatException(int sampleRateHz, int channelCount, @C.Encoding int encoding) { + super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding " + + encoding); + } + + } + + /** + * An empty, direct {@link ByteBuffer}. + */ + ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder()); + + /** + * Configures the processor to process input audio with the specified format. After calling this + * method, {@link #isActive()} returns whether the processor needs to handle buffers; if not, the + * processor will not accept any buffers until it is reconfigured. Returns {@code true} if the + * processor must be flushed, or if the value returned by {@link #isActive()} has changed as a + * result of the call. If it's active, {@link #getOutputChannelCount()} and + * {@link #getOutputEncoding()} return the processor's output format. + * + * @param sampleRateHz The sample rate of input audio in Hz. + * @param channelCount The number of interleaved channels in input audio. + * @param encoding The encoding of input audio. + * @return {@code true} if the processor must be flushed or the value returned by + * {@link #isActive()} has changed as a result of the call. + * @throws UnhandledFormatException Thrown if the specified format can't be handled as input. + */ + boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) + throws UnhandledFormatException; + + /** + * Returns whether the processor is configured and active. + */ + boolean isActive(); + + /** + * Returns the number of audio channels in the data output by the processor. + */ + int getOutputChannelCount(); + + /** + * Returns the audio encoding used in the data output by the processor. + */ + @C.Encoding + int getOutputEncoding(); + + /** + * Queues audio data between the position and limit of the input {@code buffer} for processing. + * {@code buffer} must be a direct byte buffer with native byte order. Its contents are treated as + * read-only. Its position will be advanced by the number of bytes consumed (which may be zero). + * The caller retains ownership of the provided buffer. Calling this method invalidates any + * previous buffer returned by {@link #getOutput()}. + * + * @param buffer The input buffer to process. + */ + void queueInput(ByteBuffer buffer); + + /** + * Queues an end of stream signal. After this method has been called, + * {@link #queueInput(ByteBuffer)} may not be called until after the next call to + * {@link #flush()}. Calling {@link #getOutput()} will return any remaining output data. Multiple + * calls may be required to read all of the remaining output data. {@link #isEnded()} will return + * {@code true} once all remaining output data has been read. + */ + void queueEndOfStream(); + + /** + * Returns a buffer containing processed output data between its position and limit. The buffer + * will always be a direct byte buffer with native byte order. Calling this method invalidates any + * previously returned buffer. The buffer will be empty if no output is available. + * + * @return A buffer containing processed output data between its position and limit. + */ + ByteBuffer getOutput(); + + /** + * Returns whether this processor will return no more output from {@link #getOutput()} until it + * has been {@link #flush()}ed and more input has been queued. + */ + boolean isEnded(); + + /** + * Clears any state in preparation for receiving a new stream of input buffers. + */ + void flush(); + + /** + * Resets the processor to its initial state. + */ + void reset(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioRendererEventListener.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioRendererEventListener.java new file mode 100644 index 0000000..6709c9b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioRendererEventListener.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import android.os.Handler; +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.Renderer; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderCounters; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * Listener of audio {@link Renderer} events. + */ +public interface AudioRendererEventListener { + + /** + * Called when the renderer is enabled. + * + * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it + * remains enabled. + */ + void onAudioEnabled(DecoderCounters counters); + + /** + * Called when the audio session is set. + * + * @param audioSessionId The audio session id. + */ + void onAudioSessionId(int audioSessionId); + + /** + * Called when a decoder is created. + * + * @param decoderName The decoder that was created. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. + * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. + */ + void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, + long initializationDurationMs); + + /** + * Called when the format of the media being consumed by the renderer changes. + * + * @param format The new format. + */ + void onAudioInputFormatChanged(Format format); + + /** + * Called when an {@link AudioTrack} underrun occurs. + * + * @param bufferSize The size of the {@link AudioTrack}'s buffer, in bytes. + * @param bufferSizeMs The size of the {@link AudioTrack}'s buffer, in milliseconds, if it is + * configured for PCM output. {@link C#TIME_UNSET} if it is configured for passthrough output, + * as the buffered media can have a variable bitrate so the duration may be unknown. + * @param elapsedSinceLastFeedMs The time since the {@link AudioTrack} was last fed data. + */ + void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); + + /** + * Called when the renderer is disabled. + * + * @param counters {@link DecoderCounters} that were updated by the renderer. + */ + void onAudioDisabled(DecoderCounters counters); + + /** + * Dispatches events to a {@link AudioRendererEventListener}. + */ + final class EventDispatcher { + + private final Handler handler; + private final AudioRendererEventListener listener; + + /** + * @param handler A handler for dispatching events, or null if creating a dummy instance. + * @param listener The listener to which events should be dispatched, or null if creating a + * dummy instance. + */ + public EventDispatcher(Handler handler, AudioRendererEventListener listener) { + this.handler = listener != null ? Assertions.checkNotNull(handler) : null; + this.listener = listener; + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioEnabled(DecoderCounters)}. + */ + public void enabled(final DecoderCounters decoderCounters) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioEnabled(decoderCounters); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioDecoderInitialized(String, long, long)}. + */ + public void decoderInitialized(final String decoderName, + final long initializedTimestampMs, final long initializationDurationMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioDecoderInitialized(decoderName, initializedTimestampMs, + initializationDurationMs); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. + */ + public void inputFormatChanged(final Format format) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioInputFormatChanged(format); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioTrackUnderrun(int, long, long)}. + */ + public void audioTrackUnderrun(final int bufferSize, final long bufferSizeMs, + final long elapsedSinceLastFeedMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}. + */ + public void disabled(final DecoderCounters counters) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + counters.ensureUpdated(); + listener.onAudioDisabled(counters); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}. + */ + public void audioSessionId(final int audioSessionId) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioSessionId(audioSessionId); + } + }); + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioTrack.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioTrack.java new file mode 100644 index 0000000..1b7b309 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/AudioTrack.java @@ -0,0 +1,1734 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioTimestamp; +import android.os.ConditionVariable; +import android.os.SystemClock; +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.PlaybackParameters; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles + * playback position smoothing, non-blocking writes and reconfiguration. + *

    + * Before starting playback, specify the input format by calling + * {@link #configure(String, int, int, int, int)}. Optionally call {@link #setAudioSessionId(int)}, + * {@link #setStreamType(int)}, {@link #enableTunnelingV21(int)} and {@link #disableTunneling()} + * to configure audio playback. These methods may be called after writing data to the track, in + * which case it will be reinitialized as required. + *

    + * Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} + * when the data being fed is discontinuous. Call {@link #play()} to start playing the written data. + *

    + * Call {@link #configure(String, int, int, int, int)} whenever the input format changes. The track + * will be reinitialized on the next call to {@link #handleBuffer(ByteBuffer, long)}. + *

    + * Calling {@link #reset()} releases the underlying {@link android.media.AudioTrack} (and so does + * calling {@link #configure(String, int, int, int, int)} unless the format is unchanged). It is + * safe to call {@link #handleBuffer(ByteBuffer, long)} after {@link #reset()} without calling + * {@link #configure(String, int, int, int, int)}. + *

    + * Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers will + * be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset}. Call + * {@link #release()} when the instance is no longer required. + */ +public final class AudioTrack { + + /** + * Listener for audio track events. + */ + public interface Listener { + + /** + * Called when the audio track has been initialized with a newly generated audio session id. + * + * @param audioSessionId The newly generated audio session id. + */ + void onAudioSessionId(int audioSessionId); + + /** + * Called when the audio track handles a buffer whose timestamp is discontinuous with the last + * buffer handled since it was reset. + */ + void onPositionDiscontinuity(); + + /** + * Called when the audio track underruns. + * + * @param bufferSize The size of the track's buffer, in bytes. + * @param bufferSizeMs The size of the track's buffer, in milliseconds, if it is configured for + * PCM output. {@link C#TIME_UNSET} if it is configured for passthrough output, as the + * buffered media can have a variable bitrate so the duration may be unknown. + * @param elapsedSinceLastFeedMs The time since the track was last fed data, in milliseconds. + */ + void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); + + } + + /** + * Thrown when a failure occurs configuring the track. + */ + public static final class ConfigurationException extends Exception { + + public ConfigurationException(Throwable cause) { + super(cause); + } + + public ConfigurationException(String message) { + super(message); + } + + } + + /** + * Thrown when a failure occurs initializing an {@link android.media.AudioTrack}. + */ + public static final class InitializationException extends Exception { + + /** + * The state as reported by {@link android.media.AudioTrack#getState()}. + */ + public final int audioTrackState; + + /** + * @param audioTrackState The state as reported by {@link android.media.AudioTrack#getState()}. + * @param sampleRate The requested sample rate in Hz. + * @param channelConfig The requested channel configuration. + * @param bufferSize The requested buffer size in bytes. + */ + public InitializationException(int audioTrackState, int sampleRate, int channelConfig, + int bufferSize) { + super("AudioTrack init failed: " + audioTrackState + ", Config(" + sampleRate + ", " + + channelConfig + ", " + bufferSize + ")"); + this.audioTrackState = audioTrackState; + } + + } + + /** + * Thrown when a failure occurs writing to an {@link android.media.AudioTrack}. + */ + public static final class WriteException extends Exception { + + /** + * The error value returned from {@link android.media.AudioTrack#write(byte[], int, int)} or + * {@link android.media.AudioTrack#write(ByteBuffer, int, int)}. + */ + public final int errorCode; + + /** + * @param errorCode The error value returned from + * {@link android.media.AudioTrack#write(byte[], int, int)} or + * {@link android.media.AudioTrack#write(ByteBuffer, int, int)}. + */ + public WriteException(int errorCode) { + super("AudioTrack write failed: " + errorCode); + this.errorCode = errorCode; + } + + } + + /** + * Thrown when {@link android.media.AudioTrack#getTimestamp} returns a spurious timestamp, if + * {@code AudioTrack#failOnSpuriousAudioTimestamp} is set. + */ + public static final class InvalidAudioTrackTimestampException extends RuntimeException { + + /** + * @param detailMessage The detail message for this exception. + */ + public InvalidAudioTrackTimestampException(String detailMessage) { + super(detailMessage); + } + + } + + /** + * Returned by {@link #getCurrentPositionUs(boolean)} when the position is not set. + */ + public static final long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE; + + /** + * A minimum length for the {@link android.media.AudioTrack} buffer, in microseconds. + */ + private static final long MIN_BUFFER_DURATION_US = 250000; + /** + * A maximum length for the {@link android.media.AudioTrack} buffer, in microseconds. + */ + private static final long MAX_BUFFER_DURATION_US = 750000; + /** + * The length for passthrough {@link android.media.AudioTrack} buffers, in microseconds. + */ + private static final long PASSTHROUGH_BUFFER_DURATION_US = 250000; + /** + * A multiplication factor to apply to the minimum buffer size requested by the underlying + * {@link android.media.AudioTrack}. + */ + private static final int BUFFER_MULTIPLICATION_FACTOR = 4; + + /** + * @see android.media.AudioTrack#PLAYSTATE_STOPPED + */ + private static final int PLAYSTATE_STOPPED = android.media.AudioTrack.PLAYSTATE_STOPPED; + /** + * @see android.media.AudioTrack#PLAYSTATE_PAUSED + */ + private static final int PLAYSTATE_PAUSED = android.media.AudioTrack.PLAYSTATE_PAUSED; + /** + * @see android.media.AudioTrack#PLAYSTATE_PLAYING + */ + private static final int PLAYSTATE_PLAYING = android.media.AudioTrack.PLAYSTATE_PLAYING; + /** + * @see android.media.AudioTrack#ERROR_BAD_VALUE + */ + private static final int ERROR_BAD_VALUE = android.media.AudioTrack.ERROR_BAD_VALUE; + /** + * @see android.media.AudioTrack#MODE_STATIC + */ + private static final int MODE_STATIC = android.media.AudioTrack.MODE_STATIC; + /** + * @see android.media.AudioTrack#MODE_STREAM + */ + private static final int MODE_STREAM = android.media.AudioTrack.MODE_STREAM; + /** + * @see android.media.AudioTrack#STATE_INITIALIZED + */ + private static final int STATE_INITIALIZED = android.media.AudioTrack.STATE_INITIALIZED; + /** + * @see android.media.AudioTrack#WRITE_NON_BLOCKING + */ + @SuppressLint("InlinedApi") + private static final int WRITE_NON_BLOCKING = android.media.AudioTrack.WRITE_NON_BLOCKING; + + private static final String TAG = "AudioTrack"; + + /** + * AudioTrack timestamps are deemed spurious if they are offset from the system clock by more + * than this amount. + *

    + * This is a fail safe that should not be required on correctly functioning devices. + */ + private static final long MAX_AUDIO_TIMESTAMP_OFFSET_US = 5 * C.MICROS_PER_SECOND; + + /** + * AudioTrack latencies are deemed impossibly large if they are greater than this amount. + *

    + * This is a fail safe that should not be required on correctly functioning devices. + */ + private static final long MAX_LATENCY_US = 5 * C.MICROS_PER_SECOND; + + private static final int START_NOT_SET = 0; + private static final int START_IN_SYNC = 1; + private static final int START_NEED_SYNC = 2; + + private static final int MAX_PLAYHEAD_OFFSET_COUNT = 10; + private static final int MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US = 30000; + private static final int MIN_TIMESTAMP_SAMPLE_INTERVAL_US = 500000; + + /** + * The minimum number of output bytes from {@link #sonicAudioProcessor} at which the speedup is + * calculated using the input/output byte counts from the processor, rather than using the + * current playback parameters speed. + */ + private static final int SONIC_MIN_BYTES_FOR_SPEEDUP = 1024; + + /** + * Whether to enable a workaround for an issue where an audio effect does not keep its session + * active across releasing/initializing a new audio track, on platform builds where + * {@link Util#SDK_INT} < 21. + *

    + * The flag must be set before creating a player. + */ + public static boolean enablePreV21AudioSessionWorkaround = false; + + /** + * Whether to throw an {@link InvalidAudioTrackTimestampException} when a spurious timestamp is + * reported from {@link android.media.AudioTrack#getTimestamp}. + *

    + * The flag must be set before creating a player. Should be set to {@code true} for testing and + * debugging purposes only. + */ + public static boolean failOnSpuriousAudioTimestamp = false; + + private final AudioCapabilities audioCapabilities; + private final ChannelMappingAudioProcessor channelMappingAudioProcessor; + private final SonicAudioProcessor sonicAudioProcessor; + private final AudioProcessor[] availableAudioProcessors; + private final Listener listener; + private final ConditionVariable releasingConditionVariable; + private final long[] playheadOffsets; + private final AudioTrackUtil audioTrackUtil; + private final LinkedList playbackParametersCheckpoints; + + /** + * Used to keep the audio session active on pre-V21 builds (see {@link #initialize()}). + */ + private android.media.AudioTrack keepSessionIdAudioTrack; + + private android.media.AudioTrack audioTrack; + private int sampleRate; + private int channelConfig; + @C.Encoding + private int encoding; + @C.Encoding + private int outputEncoding; + @C.StreamType + private int streamType; + private boolean passthrough; + private int bufferSize; + private long bufferSizeUs; + + private PlaybackParameters drainingPlaybackParameters; + private PlaybackParameters playbackParameters; + private long playbackParametersOffsetUs; + private long playbackParametersPositionUs; + + private ByteBuffer avSyncHeader; + private int bytesUntilNextAvSync; + + private int nextPlayheadOffsetIndex; + private int playheadOffsetCount; + private long smoothedPlayheadOffsetUs; + private long lastPlayheadSampleTimeUs; + private boolean audioTimestampSet; + private long lastTimestampSampleTimeUs; + + private Method getLatencyMethod; + private int pcmFrameSize; + private long submittedPcmBytes; + private long submittedEncodedFrames; + private int outputPcmFrameSize; + private long writtenPcmBytes; + private long writtenEncodedFrames; + private int framesPerEncodedSample; + private int startMediaTimeState; + private long startMediaTimeUs; + private long resumeSystemTimeUs; + private long latencyUs; + private float volume; + + private AudioProcessor[] audioProcessors; + private ByteBuffer[] outputBuffers; + private ByteBuffer inputBuffer; + private ByteBuffer outputBuffer; + private byte[] preV21OutputBuffer; + private int preV21OutputBufferOffset; + private int drainingAudioProcessorIndex; + private boolean handledEndOfStream; + + private boolean playing; + private int audioSessionId; + private boolean tunneling; + private boolean hasData; + private long lastFeedElapsedRealtimeMs; + + /** + * @param audioCapabilities The audio capabilities for playback on this device. May be null if the + * default capabilities (no encoded audio passthrough support) should be assumed. + * @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio before + * output. May be empty. + * @param listener Listener for audio track events. + */ + public AudioTrack(AudioCapabilities audioCapabilities, AudioProcessor[] audioProcessors, + Listener listener) { + this.audioCapabilities = audioCapabilities; + this.listener = listener; + releasingConditionVariable = new ConditionVariable(true); + if (Util.SDK_INT >= 18) { + try { + getLatencyMethod = + android.media.AudioTrack.class.getMethod("getLatency", (Class[]) null); + } catch (NoSuchMethodException e) { + // There's no guarantee this method exists. Do nothing. + e.getStackTrace(); + } + } + if (Util.SDK_INT >= 19) { + audioTrackUtil = new AudioTrackUtilV19(); + } else { + audioTrackUtil = new AudioTrackUtil(); + } + channelMappingAudioProcessor = new ChannelMappingAudioProcessor(); + sonicAudioProcessor = new SonicAudioProcessor(); + availableAudioProcessors = new AudioProcessor[3 + audioProcessors.length]; + availableAudioProcessors[0] = new ResamplingAudioProcessor(); + availableAudioProcessors[1] = channelMappingAudioProcessor; + System.arraycopy(audioProcessors, 0, availableAudioProcessors, 2, audioProcessors.length); + availableAudioProcessors[2 + audioProcessors.length] = sonicAudioProcessor; + playheadOffsets = new long[MAX_PLAYHEAD_OFFSET_COUNT]; + volume = 1.0f; + startMediaTimeState = START_NOT_SET; + streamType = C.STREAM_TYPE_DEFAULT; + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + playbackParameters = PlaybackParameters.DEFAULT; + drainingAudioProcessorIndex = C.INDEX_UNSET; + this.audioProcessors = new AudioProcessor[0]; + outputBuffers = new ByteBuffer[0]; + playbackParametersCheckpoints = new LinkedList<>(); + } + + /** + * Returns whether it's possible to play audio in the specified format using encoded passthrough. + * + * @param mimeType The format mime type. + * @return Whether it's possible to play audio in the format using encoded passthrough. + */ + public boolean isPassthroughSupported(String mimeType) { + return audioCapabilities != null + && audioCapabilities.supportsEncoding(getEncodingForMimeType(mimeType)); + } + + /** + * Returns the playback position in the stream starting at zero, in microseconds, or + * {@link #CURRENT_POSITION_NOT_SET} if it is not yet available. + * + *

    If the device supports it, the method uses the playback timestamp from + * {@link android.media.AudioTrack#getTimestamp}. Otherwise, it derives a smoothed position by + * sampling the {@link android.media.AudioTrack}'s frame position. + * + * @param sourceEnded Specify {@code true} if no more input buffers will be provided. + * @return The playback position relative to the start of playback, in microseconds. + */ + public long getCurrentPositionUs(boolean sourceEnded) { + if (!hasCurrentPositionUs()) { + return CURRENT_POSITION_NOT_SET; + } + + if (audioTrack.getPlayState() == PLAYSTATE_PLAYING) { + maybeSampleSyncParams(); + } + + long systemClockUs = System.nanoTime() / 1000; + long positionUs; + if (audioTimestampSet) { + // Calculate the speed-adjusted position using the timestamp (which may be in the future). + long elapsedSinceTimestampUs = systemClockUs - (audioTrackUtil.getTimestampNanoTime() / 1000); + long elapsedSinceTimestampFrames = durationUsToFrames(elapsedSinceTimestampUs); + long elapsedFrames = audioTrackUtil.getTimestampFramePosition() + elapsedSinceTimestampFrames; + positionUs = framesToDurationUs(elapsedFrames); + } else { + if (playheadOffsetCount == 0) { + // The AudioTrack has started, but we don't have any samples to compute a smoothed position. + positionUs = audioTrackUtil.getPositionUs(); + } else { + // getPlayheadPositionUs() only has a granularity of ~20 ms, so we base the position off the + // system clock (and a smoothed offset between it and the playhead position) so as to + // prevent jitter in the reported positions. + positionUs = systemClockUs + smoothedPlayheadOffsetUs; + } + if (!sourceEnded) { + positionUs -= latencyUs; + } + } + + return startMediaTimeUs + applySpeedup(positionUs); + } + + /** + * Configures (or reconfigures) the audio track. + * + * @param mimeType The mime type. + * @param channelCount The number of channels. + * @param sampleRate The sample rate in Hz. + * @param pcmEncoding For PCM formats, the encoding used. One of {@link C#ENCODING_PCM_16BIT}, + * {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and + * {@link C#ENCODING_PCM_32BIT}. + * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a + * suitable buffer size automatically. + * @throws ConfigurationException If an error occurs configuring the track. + */ + public void configure(String mimeType, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, int specifiedBufferSize) throws ConfigurationException { + configure(mimeType, channelCount, sampleRate, pcmEncoding, specifiedBufferSize, null); + } + + /** + * Configures (or reconfigures) the audio track. + * + * @param mimeType The mime type. + * @param channelCount The number of channels. + * @param sampleRate The sample rate in Hz. + * @param pcmEncoding For PCM formats, the encoding used. One of {@link C#ENCODING_PCM_16BIT}, + * {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and + * {@link C#ENCODING_PCM_32BIT}. + * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a + * suitable buffer size automatically. + * @param outputChannels A mapping from input to output channels that is applied to this track's + * input as a preprocessing step, if handling PCM input. Specify {@code null} to leave the + * input unchanged. Otherwise, the element at index {@code i} specifies index of the input + * channel to map to output channel {@code i} when preprocessing input buffers. After the + * map is applied the audio data will have {@code outputChannels.length} channels. + * @throws ConfigurationException If an error occurs configuring the track. + */ + public void configure(String mimeType, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, int specifiedBufferSize, int[] outputChannels) + throws ConfigurationException { + boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType); + @C.Encoding int encoding = passthrough ? getEncodingForMimeType(mimeType) : pcmEncoding; + boolean flush = false; + if (!passthrough) { + pcmFrameSize = Util.getPcmFrameSize(pcmEncoding, channelCount); + channelMappingAudioProcessor.setChannelMap(outputChannels); + for (AudioProcessor audioProcessor : availableAudioProcessors) { + try { + flush |= audioProcessor.configure(sampleRate, channelCount, encoding); + } catch (AudioProcessor.UnhandledFormatException e) { + throw new ConfigurationException(e); + } + if (audioProcessor.isActive()) { + channelCount = audioProcessor.getOutputChannelCount(); + encoding = audioProcessor.getOutputEncoding(); + } + } + if (flush) { + resetAudioProcessors(); + } + } + + int channelConfig; + switch (channelCount) { + case 1: + channelConfig = AudioFormat.CHANNEL_OUT_MONO; + break; + case 2: + channelConfig = AudioFormat.CHANNEL_OUT_STEREO; + break; + case 3: + channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER; + break; + case 4: + channelConfig = AudioFormat.CHANNEL_OUT_QUAD; + break; + case 5: + channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER; + break; + case 6: + channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; + break; + case 7: + channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; + break; + case 8: + channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND; + break; + default: + throw new ConfigurationException("Unsupported channel count: " + channelCount); + } + + // Workaround for overly strict channel configuration checks on nVidia Shield. + if (Util.SDK_INT <= 23 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER)) { + switch (channelCount) { + case 7: + channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND; + break; + case 3: + case 5: + channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; + break; + default: + break; + } + } + + // Workaround for Nexus Player not reporting support for mono passthrough. + // (See [Internal: b/34268671].) + if (Util.SDK_INT <= 25 && "fugu".equals(Util.DEVICE) && passthrough && channelCount == 1) { + channelConfig = AudioFormat.CHANNEL_OUT_STEREO; + } + + if (!flush && isInitialized() && this.encoding == encoding && this.sampleRate == sampleRate + && this.channelConfig == channelConfig) { + // We already have an audio track with the correct sample rate, channel config and encoding. + return; + } + + reset(); + + this.encoding = encoding; + this.passthrough = passthrough; + this.sampleRate = sampleRate; + this.channelConfig = channelConfig; + outputEncoding = passthrough ? encoding : C.ENCODING_PCM_16BIT; + outputPcmFrameSize = Util.getPcmFrameSize(C.ENCODING_PCM_16BIT, channelCount); + + if (specifiedBufferSize != 0) { + bufferSize = specifiedBufferSize; + } else if (passthrough) { + // TODO: Set the minimum buffer size using getMinBufferSize when it takes the encoding into + // account. [Internal: b/25181305] + if (outputEncoding == C.ENCODING_AC3 || outputEncoding == C.ENCODING_E_AC3) { + // AC-3 allows bitrates up to 640 kbit/s. + bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 80 * 1024 / C.MICROS_PER_SECOND); + } else /* (outputEncoding == C.ENCODING_DTS || outputEncoding == C.ENCODING_DTS_HD */ { + // DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s. + bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND); + } + } else { + int minBufferSize = + android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, outputEncoding); + Assertions.checkState(minBufferSize != ERROR_BAD_VALUE); + int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR; + int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * outputPcmFrameSize; + int maxAppBufferSize = (int) Math.max(minBufferSize, + durationUsToFrames(MAX_BUFFER_DURATION_US) * outputPcmFrameSize); + bufferSize = multipliedBufferSize < minAppBufferSize ? minAppBufferSize + : multipliedBufferSize > maxAppBufferSize ? maxAppBufferSize + : multipliedBufferSize; + } + bufferSizeUs = passthrough ? C.TIME_UNSET : framesToDurationUs(bufferSize / outputPcmFrameSize); + + // The old playback parameters may no longer be applicable so try to reset them now. + setPlaybackParameters(playbackParameters); + } + + private void resetAudioProcessors() { + ArrayList newAudioProcessors = new ArrayList<>(); + for (AudioProcessor audioProcessor : availableAudioProcessors) { + if (audioProcessor.isActive()) { + newAudioProcessors.add(audioProcessor); + } else { + audioProcessor.flush(); + } + } + int count = newAudioProcessors.size(); + audioProcessors = newAudioProcessors.toArray(new AudioProcessor[count]); + outputBuffers = new ByteBuffer[count]; + for (int i = 0; i < count; i++) { + AudioProcessor audioProcessor = audioProcessors[i]; + audioProcessor.flush(); + outputBuffers[i] = audioProcessor.getOutput(); + } + } + + private void initialize() throws InitializationException { + // If we're asynchronously releasing a previous audio track then we block until it has been + // released. This guarantees that we cannot end up in a state where we have multiple audio + // track instances. Without this guarantee it would be possible, in extreme cases, to exhaust + // the shared memory that's available for audio track buffers. This would in turn cause the + // initialization of the audio track to fail. + releasingConditionVariable.block(); + + if (tunneling) { + audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, outputEncoding, + bufferSize, audioSessionId); + } else if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { + audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, + outputEncoding, bufferSize, MODE_STREAM); + } else { + // Re-attach to the same audio session. + audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, + outputEncoding, bufferSize, MODE_STREAM, audioSessionId); + } + checkAudioTrackInitialized(); + + int audioSessionId = audioTrack.getAudioSessionId(); + if (enablePreV21AudioSessionWorkaround) { + if (Util.SDK_INT < 21) { + // The workaround creates an audio track with a two byte buffer on the same session, and + // does not release it until this object is released, which keeps the session active. + if (keepSessionIdAudioTrack != null + && audioSessionId != keepSessionIdAudioTrack.getAudioSessionId()) { + releaseKeepSessionIdAudioTrack(); + } + if (keepSessionIdAudioTrack == null) { + int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. + int channelConfig = AudioFormat.CHANNEL_OUT_MONO; + @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; + int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. + keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, + channelConfig, encoding, bufferSize, MODE_STATIC, audioSessionId); + } + } + } + if (this.audioSessionId != audioSessionId) { + this.audioSessionId = audioSessionId; + listener.onAudioSessionId(audioSessionId); + } + + audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds()); + setVolumeInternal(); + hasData = false; + } + + /** + * Starts or resumes playing audio if the audio track has been initialized. + */ + public void play() { + playing = true; + if (isInitialized()) { + resumeSystemTimeUs = System.nanoTime() / 1000; + audioTrack.play(); + } + } + + /** + * Signals to the audio track that the next buffer is discontinuous with the previous buffer. + */ + public void handleDiscontinuity() { + // Force resynchronization after a skipped buffer. + if (startMediaTimeState == START_IN_SYNC) { + startMediaTimeState = START_NEED_SYNC; + } + } + + /** + * Attempts to process data from a {@link ByteBuffer}, starting from its current position and + * ending at its limit (exclusive). The position of the {@link ByteBuffer} is advanced by the + * number of bytes that were handled. {@link Listener#onPositionDiscontinuity()} will be called if + * {@code presentationTimeUs} is discontinuous with the last buffer handled since the last reset. + *

    + * Returns whether the data was handled in full. If the data was not handled in full then the same + * {@link ByteBuffer} must be provided to subsequent calls until it has been fully consumed, + * except in the case of an interleaving call to {@link #reset()} (or an interleaving call to + * {@link #configure(String, int, int, int, int)} that caused the track to be reset). + * + * @param buffer The buffer containing audio data. + * @param presentationTimeUs The presentation timestamp of the buffer in microseconds. + * @return Whether the buffer was handled fully. + * @throws InitializationException If an error occurs initializing the track. + * @throws WriteException If an error occurs writing the audio data. + */ + @SuppressWarnings("ReferenceEquality") + public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) + throws InitializationException, WriteException { + Assertions.checkArgument(inputBuffer == null || buffer == inputBuffer); + if (!isInitialized()) { + initialize(); + if (playing) { + play(); + } + } + + if (needsPassthroughWorkarounds()) { + // An AC-3 audio track continues to play data written while it is paused. Stop writing so its + // buffer empties. See [Internal: b/18899620]. + if (audioTrack.getPlayState() == PLAYSTATE_PAUSED) { + // We force an underrun to pause the track, so don't notify the listener in this case. + hasData = false; + return false; + } + + // A new AC-3 audio track's playback position continues to increase from the old track's + // position for a short time after is has been released. Avoid writing data until the playback + // head position actually returns to zero. + if (audioTrack.getPlayState() == PLAYSTATE_STOPPED + && audioTrackUtil.getPlaybackHeadPosition() != 0) { + return false; + } + } + + boolean hadData = hasData; + hasData = hasPendingData(); + if (hadData && !hasData && audioTrack.getPlayState() != PLAYSTATE_STOPPED) { + long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs; + listener.onUnderrun(bufferSize, C.usToMs(bufferSizeUs), elapsedSinceLastFeedMs); + } + + if (inputBuffer == null) { + // We are seeing this buffer for the first time. + if (!buffer.hasRemaining()) { + // The buffer is empty. + return true; + } + + if (passthrough && framesPerEncodedSample == 0) { + // If this is the first encoded sample, calculate the sample size in frames. + framesPerEncodedSample = getFramesPerEncodedSample(outputEncoding, buffer); + } + + if (drainingPlaybackParameters != null) { + if (!drainAudioProcessorsToEndOfStream()) { + // Don't process any more input until draining completes. + return false; + } + // Store the position and corresponding media time from which the parameters will apply. + playbackParametersCheckpoints.add(new PlaybackParametersCheckpoint( + drainingPlaybackParameters, Math.max(0, presentationTimeUs), + framesToDurationUs(getWrittenFrames()))); + drainingPlaybackParameters = null; + // The audio processors have drained, so flush them. This will cause any active speed + // adjustment audio processor to start producing audio with the new parameters. + resetAudioProcessors(); + } + + if (startMediaTimeState == START_NOT_SET) { + startMediaTimeUs = Math.max(0, presentationTimeUs); + startMediaTimeState = START_IN_SYNC; + } else { + // Sanity check that presentationTimeUs is consistent with the expected value. + long expectedPresentationTimeUs = startMediaTimeUs + + framesToDurationUs(getSubmittedFrames()); + if (startMediaTimeState == START_IN_SYNC + && Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) { + Log.e(TAG, "Discontinuity detected [expected " + expectedPresentationTimeUs + ", got " + + presentationTimeUs + "]"); + startMediaTimeState = START_NEED_SYNC; + } + if (startMediaTimeState == START_NEED_SYNC) { + // Adjust startMediaTimeUs to be consistent with the current buffer's start time and the + // number of bytes submitted. + startMediaTimeUs += (presentationTimeUs - expectedPresentationTimeUs); + startMediaTimeState = START_IN_SYNC; + listener.onPositionDiscontinuity(); + } + } + + if (passthrough) { + submittedEncodedFrames += framesPerEncodedSample; + } else { + submittedPcmBytes += buffer.remaining(); + } + + inputBuffer = buffer; + } + + if (passthrough) { + // Passthrough buffers are not processed. + writeBuffer(inputBuffer, presentationTimeUs); + } else { + processBuffers(presentationTimeUs); + } + + if (!inputBuffer.hasRemaining()) { + inputBuffer = null; + return true; + } + return false; + } + + private void processBuffers(long avSyncPresentationTimeUs) throws WriteException { + int count = audioProcessors.length; + int index = count; + while (index >= 0) { + ByteBuffer input = index > 0 ? outputBuffers[index - 1] + : (inputBuffer != null ? inputBuffer : AudioProcessor.EMPTY_BUFFER); + if (index == count) { + writeBuffer(input, avSyncPresentationTimeUs); + } else { + AudioProcessor audioProcessor = audioProcessors[index]; + audioProcessor.queueInput(input); + ByteBuffer output = audioProcessor.getOutput(); + outputBuffers[index] = output; + if (output.hasRemaining()) { + // Handle the output as input to the next audio processor or the AudioTrack. + index++; + continue; + } + } + + if (input.hasRemaining()) { + // The input wasn't consumed and no output was produced, so give up for now. + return; + } + + // Get more input from upstream. + index--; + } + } + + @SuppressWarnings("ReferenceEquality") + private boolean writeBuffer(ByteBuffer buffer, long avSyncPresentationTimeUs) + throws WriteException { + if (!buffer.hasRemaining()) { + return true; + } + if (outputBuffer != null) { + Assertions.checkArgument(outputBuffer == buffer); + } else { + outputBuffer = buffer; + if (Util.SDK_INT < 21) { + int bytesRemaining = buffer.remaining(); + if (preV21OutputBuffer == null || preV21OutputBuffer.length < bytesRemaining) { + preV21OutputBuffer = new byte[bytesRemaining]; + } + int originalPosition = buffer.position(); + buffer.get(preV21OutputBuffer, 0, bytesRemaining); + buffer.position(originalPosition); + preV21OutputBufferOffset = 0; + } + } + int bytesRemaining = buffer.remaining(); + int bytesWritten = 0; + if (Util.SDK_INT < 21) { // passthrough == false + // Work out how many bytes we can write without the risk of blocking. + int bytesPending = + (int) (writtenPcmBytes - (audioTrackUtil.getPlaybackHeadPosition() * outputPcmFrameSize)); + int bytesToWrite = bufferSize - bytesPending; + if (bytesToWrite > 0) { + bytesToWrite = Math.min(bytesRemaining, bytesToWrite); + bytesWritten = audioTrack.write(preV21OutputBuffer, preV21OutputBufferOffset, bytesToWrite); + if (bytesWritten > 0) { + preV21OutputBufferOffset += bytesWritten; + buffer.position(buffer.position() + bytesWritten); + } + } + } else if (tunneling) { + Assertions.checkState(avSyncPresentationTimeUs != C.TIME_UNSET); + bytesWritten = writeNonBlockingWithAvSyncV21(audioTrack, buffer, bytesRemaining, + avSyncPresentationTimeUs); + } else { + bytesWritten = writeNonBlockingV21(audioTrack, buffer, bytesRemaining); + } + + lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime(); + + if (bytesWritten < 0) { + throw new WriteException(bytesWritten); + } + + if (!passthrough) { + writtenPcmBytes += bytesWritten; + } + if (bytesWritten == bytesRemaining) { + if (passthrough) { + writtenEncodedFrames += framesPerEncodedSample; + } + outputBuffer = null; + return true; + } + return false; + } + + /** + * Plays out remaining audio. {@link #isEnded()} will return {@code true} when playback has ended. + * + * @throws WriteException If an error occurs draining data to the track. + */ + public void playToEndOfStream() throws WriteException { + if (handledEndOfStream || !isInitialized()) { + return; + } + + if (drainAudioProcessorsToEndOfStream()) { + // The audio processors have drained, so drain the underlying audio track. + audioTrackUtil.handleEndOfStream(getWrittenFrames()); + bytesUntilNextAvSync = 0; + handledEndOfStream = true; + } + } + + private boolean drainAudioProcessorsToEndOfStream() throws WriteException { + boolean audioProcessorNeedsEndOfStream = false; + if (drainingAudioProcessorIndex == C.INDEX_UNSET) { + drainingAudioProcessorIndex = passthrough ? audioProcessors.length : 0; + audioProcessorNeedsEndOfStream = true; + } + while (drainingAudioProcessorIndex < audioProcessors.length) { + AudioProcessor audioProcessor = audioProcessors[drainingAudioProcessorIndex]; + if (audioProcessorNeedsEndOfStream) { + audioProcessor.queueEndOfStream(); + } + processBuffers(C.TIME_UNSET); + if (!audioProcessor.isEnded()) { + return false; + } + audioProcessorNeedsEndOfStream = true; + drainingAudioProcessorIndex++; + } + + // Finish writing any remaining output to the track. + if (outputBuffer != null) { + writeBuffer(outputBuffer, C.TIME_UNSET); + if (outputBuffer != null) { + return false; + } + } + drainingAudioProcessorIndex = C.INDEX_UNSET; + return true; + } + + /** + * Returns whether all buffers passed to {@link #handleBuffer(ByteBuffer, long)} have been + * completely processed and played. + */ + public boolean isEnded() { + return !isInitialized() || (handledEndOfStream && !hasPendingData()); + } + + /** + * Returns whether the audio track has more data pending that will be played back. + */ + public boolean hasPendingData() { + return isInitialized() + && (getWrittenFrames() > audioTrackUtil.getPlaybackHeadPosition() + || overrideHasPendingData()); + } + + /** + * Attempts to set the playback parameters and returns the active playback parameters, which may + * differ from those passed in. + * + * @param playbackParameters The new playback parameters to attempt to set. + * @return The active playback parameters. + */ + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + if (passthrough) { + // The playback parameters are always the default in passthrough mode. + this.playbackParameters = PlaybackParameters.DEFAULT; + return this.playbackParameters; + } + playbackParameters = new PlaybackParameters( + sonicAudioProcessor.setSpeed(playbackParameters.speed), + sonicAudioProcessor.setPitch(playbackParameters.pitch)); + PlaybackParameters lastSetPlaybackParameters = + drainingPlaybackParameters != null ? drainingPlaybackParameters + : !playbackParametersCheckpoints.isEmpty() + ? playbackParametersCheckpoints.getLast().playbackParameters + : this.playbackParameters; + if (!playbackParameters.equals(lastSetPlaybackParameters)) { + if (isInitialized()) { + // Drain the audio processors so we can determine the frame position at which the new + // parameters apply. + drainingPlaybackParameters = playbackParameters; + } else { + this.playbackParameters = playbackParameters; + } + } + return this.playbackParameters; + } + + /** + * Gets the {@link PlaybackParameters}. + */ + public PlaybackParameters getPlaybackParameters() { + return playbackParameters; + } + + /** + * Sets the stream type for audio track. If the stream type has changed and if the audio track + * is not configured for use with tunneling, then the audio track is reset and the audio session + * id is cleared. + *

    + * If the audio track is configured for use with tunneling then the stream type is ignored, the + * audio track is not reset and the audio session id is not cleared. The passed stream type will + * be used if the audio track is later re-configured into non-tunneled mode. + * + * @param streamType The {@link C.StreamType} to use for audio output. + */ + public void setStreamType(@C.StreamType int streamType) { + if (this.streamType == streamType) { + return; + } + this.streamType = streamType; + if (tunneling) { + // The stream type is ignored in tunneling mode, so no need to reset. + return; + } + reset(); + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + } + + /** + * Sets the audio session id. The audio track is reset if the audio session id has changed. + */ + public void setAudioSessionId(int audioSessionId) { + if (this.audioSessionId != audioSessionId) { + this.audioSessionId = audioSessionId; + reset(); + } + } + + /** + * Enables tunneling. The audio track is reset if tunneling was previously disabled or if the + * audio session id has changed. Enabling tunneling requires platform API version 21 onwards. + *

    + * If this instance has {@link AudioProcessor}s and tunneling is enabled, care must be taken that + * audio processors do not output buffers with a different duration than their input, and buffer + * processors must produce output corresponding to their last input immediately after that input + * is queued. + * + * @param tunnelingAudioSessionId The audio session id to use. + * @throws IllegalStateException Thrown if enabling tunneling on platform API version < 21. + */ + public void enableTunnelingV21(int tunnelingAudioSessionId) { + Assertions.checkState(Util.SDK_INT >= 21); + if (!tunneling || audioSessionId != tunnelingAudioSessionId) { + tunneling = true; + audioSessionId = tunnelingAudioSessionId; + reset(); + } + } + + /** + * Disables tunneling. If tunneling was previously enabled then the audio track is reset and the + * audio session id is cleared. + */ + public void disableTunneling() { + if (tunneling) { + tunneling = false; + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + reset(); + } + } + + /** + * Sets the playback volume. + * + * @param volume A volume in the range [0.0, 1.0]. + */ + public void setVolume(float volume) { + if (this.volume != volume) { + this.volume = volume; + setVolumeInternal(); + } + } + + private void setVolumeInternal() { + if (!isInitialized()) { + // Do nothing. + } else if (Util.SDK_INT >= 21) { + setVolumeInternalV21(audioTrack, volume); + } else { + setVolumeInternalV3(audioTrack, volume); + } + } + + /** + * Pauses playback. + */ + public void pause() { + playing = false; + if (isInitialized()) { + resetSyncParams(); + audioTrackUtil.pause(); + } + } + + /** + * Releases the underlying audio track asynchronously. + *

    + * Calling {@link #handleBuffer(ByteBuffer, long)} will block until the audio track has been + * released, so it is safe to use the audio track immediately after a reset. The audio session may + * remain active until {@link #release()} is called. + */ + public void reset() { + if (isInitialized()) { + submittedPcmBytes = 0; + submittedEncodedFrames = 0; + writtenPcmBytes = 0; + writtenEncodedFrames = 0; + framesPerEncodedSample = 0; + if (drainingPlaybackParameters != null) { + playbackParameters = drainingPlaybackParameters; + drainingPlaybackParameters = null; + } else if (!playbackParametersCheckpoints.isEmpty()) { + playbackParameters = playbackParametersCheckpoints.getLast().playbackParameters; + } + playbackParametersCheckpoints.clear(); + playbackParametersOffsetUs = 0; + playbackParametersPositionUs = 0; + inputBuffer = null; + outputBuffer = null; + for (int i = 0; i < audioProcessors.length; i++) { + AudioProcessor audioProcessor = audioProcessors[i]; + audioProcessor.flush(); + outputBuffers[i] = audioProcessor.getOutput(); + } + handledEndOfStream = false; + drainingAudioProcessorIndex = C.INDEX_UNSET; + avSyncHeader = null; + bytesUntilNextAvSync = 0; + startMediaTimeState = START_NOT_SET; + latencyUs = 0; + resetSyncParams(); + int playState = audioTrack.getPlayState(); + if (playState == PLAYSTATE_PLAYING) { + audioTrack.pause(); + } + // AudioTrack.release can take some time, so we call it on a background thread. + final android.media.AudioTrack toRelease = audioTrack; + audioTrack = null; + audioTrackUtil.reconfigure(null, false); + releasingConditionVariable.close(); + new Thread() { + @Override + public void run() { + try { + toRelease.flush(); + toRelease.release(); + } finally { + releasingConditionVariable.open(); + } + } + }.start(); + } + } + + /** + * Releases all resources associated with this instance. + */ + public void release() { + reset(); + releaseKeepSessionIdAudioTrack(); + for (AudioProcessor audioProcessor : availableAudioProcessors) { + audioProcessor.reset(); + } + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + playing = false; + } + + /** + * Releases {@link #keepSessionIdAudioTrack} asynchronously, if it is non-{@code null}. + */ + private void releaseKeepSessionIdAudioTrack() { + if (keepSessionIdAudioTrack == null) { + return; + } + + // AudioTrack.release can take some time, so we call it on a background thread. + final android.media.AudioTrack toRelease = keepSessionIdAudioTrack; + keepSessionIdAudioTrack = null; + new Thread() { + @Override + public void run() { + toRelease.release(); + } + }.start(); + } + + /** + * Returns whether {@link #getCurrentPositionUs} can return the current playback position. + */ + private boolean hasCurrentPositionUs() { + return isInitialized() && startMediaTimeState != START_NOT_SET; + } + + /** + * Returns the underlying audio track {@code positionUs} with any applicable speedup applied. + */ + private long applySpeedup(long positionUs) { + while (!playbackParametersCheckpoints.isEmpty() + && positionUs >= playbackParametersCheckpoints.getFirst().positionUs) { + // We are playing (or about to play) media with the new playback parameters, so update them. + PlaybackParametersCheckpoint checkpoint = playbackParametersCheckpoints.remove(); + playbackParameters = checkpoint.playbackParameters; + playbackParametersPositionUs = checkpoint.positionUs; + playbackParametersOffsetUs = checkpoint.mediaTimeUs - startMediaTimeUs; + } + + if (playbackParameters.speed == 1f) { + return positionUs + playbackParametersOffsetUs - playbackParametersPositionUs; + } + + if (playbackParametersCheckpoints.isEmpty() + && sonicAudioProcessor.getOutputByteCount() >= SONIC_MIN_BYTES_FOR_SPEEDUP) { + return playbackParametersOffsetUs + + Util.scaleLargeTimestamp(positionUs - playbackParametersPositionUs, + sonicAudioProcessor.getInputByteCount(), sonicAudioProcessor.getOutputByteCount()); + } + + // We are playing drained data at a previous playback speed, or don't have enough bytes to + // calculate an accurate speedup, so fall back to multiplying by the speed. + return playbackParametersOffsetUs + + (long) ((double) playbackParameters.speed * (positionUs - playbackParametersPositionUs)); + } + + /** + * Updates the audio track latency and playback position parameters. + */ + private void maybeSampleSyncParams() { + long playbackPositionUs = audioTrackUtil.getPositionUs(); + if (playbackPositionUs == 0) { + // The AudioTrack hasn't output anything yet. + return; + } + long systemClockUs = System.nanoTime() / 1000; + if (systemClockUs - lastPlayheadSampleTimeUs >= MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US) { + // Take a new sample and update the smoothed offset between the system clock and the playhead. + playheadOffsets[nextPlayheadOffsetIndex] = playbackPositionUs - systemClockUs; + nextPlayheadOffsetIndex = (nextPlayheadOffsetIndex + 1) % MAX_PLAYHEAD_OFFSET_COUNT; + if (playheadOffsetCount < MAX_PLAYHEAD_OFFSET_COUNT) { + playheadOffsetCount++; + } + lastPlayheadSampleTimeUs = systemClockUs; + smoothedPlayheadOffsetUs = 0; + for (int i = 0; i < playheadOffsetCount; i++) { + smoothedPlayheadOffsetUs += playheadOffsets[i] / playheadOffsetCount; + } + } + + if (needsPassthroughWorkarounds()) { + // Don't sample the timestamp and latency if this is an AC-3 passthrough AudioTrack on + // platform API versions 21/22, as incorrect values are returned. See [Internal: b/21145353]. + return; + } + + if (systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) { + audioTimestampSet = audioTrackUtil.updateTimestamp(); + if (audioTimestampSet) { + // Perform sanity checks on the timestamp. + long audioTimestampUs = audioTrackUtil.getTimestampNanoTime() / 1000; + long audioTimestampFramePosition = audioTrackUtil.getTimestampFramePosition(); + if (audioTimestampUs < resumeSystemTimeUs) { + // The timestamp corresponds to a time before the track was most recently resumed. + audioTimestampSet = false; + } else if (Math.abs(audioTimestampUs - systemClockUs) > MAX_AUDIO_TIMESTAMP_OFFSET_US) { + // The timestamp time base is probably wrong. + String message = "Spurious audio timestamp (system clock mismatch): " + + audioTimestampFramePosition + ", " + audioTimestampUs + ", " + systemClockUs + ", " + + playbackPositionUs; + if (failOnSpuriousAudioTimestamp) { + throw new InvalidAudioTrackTimestampException(message); + } + Log.w(TAG, message); + audioTimestampSet = false; + } else if (Math.abs(framesToDurationUs(audioTimestampFramePosition) - playbackPositionUs) + > MAX_AUDIO_TIMESTAMP_OFFSET_US) { + // The timestamp frame position is probably wrong. + String message = "Spurious audio timestamp (frame position mismatch): " + + audioTimestampFramePosition + ", " + audioTimestampUs + ", " + systemClockUs + ", " + + playbackPositionUs; + if (failOnSpuriousAudioTimestamp) { + throw new InvalidAudioTrackTimestampException(message); + } + Log.w(TAG, message); + audioTimestampSet = false; + } + } + if (getLatencyMethod != null && !passthrough) { + try { + // Compute the audio track latency, excluding the latency due to the buffer (leaving + // latency due to the mixer and audio hardware driver). + latencyUs = (Integer) getLatencyMethod.invoke(audioTrack, (Object[]) null) * 1000L + - bufferSizeUs; + // Sanity check that the latency is non-negative. + latencyUs = Math.max(latencyUs, 0); + // Sanity check that the latency isn't too large. + if (latencyUs > MAX_LATENCY_US) { + Log.w(TAG, "Ignoring impossibly large audio latency: " + latencyUs); + latencyUs = 0; + } + } catch (Exception e) { + // The method existed, but doesn't work. Don't try again. + getLatencyMethod = null; + } + } + lastTimestampSampleTimeUs = systemClockUs; + } + } + + /** + * Checks that {@link #audioTrack} has been successfully initialized. If it has then calling this + * method is a no-op. If it hasn't then {@link #audioTrack} is released and set to null, and an + * exception is thrown. + * + * @throws InitializationException If {@link #audioTrack} has not been successfully initialized. + */ + private void checkAudioTrackInitialized() throws InitializationException { + int state = audioTrack.getState(); + if (state == STATE_INITIALIZED) { + return; + } + // The track is not successfully initialized. Release and null the track. + try { + audioTrack.release(); + } catch (Exception e) { + // The track has already failed to initialize, so it wouldn't be that surprising if release + // were to fail too. Swallow the exception. + e.getStackTrace(); + } finally { + audioTrack = null; + } + + throw new InitializationException(state, sampleRate, channelConfig, bufferSize); + } + + private boolean isInitialized() { + return audioTrack != null; + } + + private long framesToDurationUs(long frameCount) { + return (frameCount * C.MICROS_PER_SECOND) / sampleRate; + } + + private long durationUsToFrames(long durationUs) { + return (durationUs * sampleRate) / C.MICROS_PER_SECOND; + } + + private long getSubmittedFrames() { + return passthrough ? submittedEncodedFrames : (submittedPcmBytes / pcmFrameSize); + } + + private long getWrittenFrames() { + return passthrough ? writtenEncodedFrames : (writtenPcmBytes / outputPcmFrameSize); + } + + private void resetSyncParams() { + smoothedPlayheadOffsetUs = 0; + playheadOffsetCount = 0; + nextPlayheadOffsetIndex = 0; + lastPlayheadSampleTimeUs = 0; + audioTimestampSet = false; + lastTimestampSampleTimeUs = 0; + } + + /** + * Returns whether to work around problems with passthrough audio tracks. + * See [Internal: b/18899620, b/19187573, b/21145353]. + */ + private boolean needsPassthroughWorkarounds() { + return Util.SDK_INT < 23 + && (outputEncoding == C.ENCODING_AC3 || outputEncoding == C.ENCODING_E_AC3); + } + + /** + * Returns whether the audio track should behave as though it has pending data. This is to work + * around an issue on platform API versions 21/22 where AC-3 audio tracks can't be paused, so we + * empty their buffers when paused. In this case, they should still behave as if they have + * pending data, otherwise writing will never resume. + */ + private boolean overrideHasPendingData() { + return needsPassthroughWorkarounds() + && audioTrack.getPlayState() == PLAYSTATE_PAUSED + && audioTrack.getPlaybackHeadPosition() == 0; + } + + /** + * Instantiates an {@link android.media.AudioTrack} to be used with tunneling video playback. + */ + @TargetApi(21) + private static android.media.AudioTrack createHwAvSyncAudioTrackV21(int sampleRate, + int channelConfig, int encoding, int bufferSize, int sessionId) { + AudioAttributes attributesBuilder = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) + .setFlags(AudioAttributes.FLAG_HW_AV_SYNC) + .build(); + AudioFormat format = new AudioFormat.Builder() + .setChannelMask(channelConfig) + .setEncoding(encoding) + .setSampleRate(sampleRate) + .build(); + return new android.media.AudioTrack(attributesBuilder, format, bufferSize, MODE_STREAM, + sessionId); + } + + @C.Encoding + private static int getEncodingForMimeType(String mimeType) { + switch (mimeType) { + case MimeTypes.AUDIO_AC3: + return C.ENCODING_AC3; + case MimeTypes.AUDIO_E_AC3: + return C.ENCODING_E_AC3; + case MimeTypes.AUDIO_DTS: + return C.ENCODING_DTS; + case MimeTypes.AUDIO_DTS_HD: + return C.ENCODING_DTS_HD; + default: + return C.ENCODING_INVALID; + } + } + + private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) { + if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) { + return DtsUtil.parseDtsAudioSampleCount(buffer); + } else if (encoding == C.ENCODING_AC3) { + return Ac3Util.getAc3SyncframeAudioSampleCount(); + } else if (encoding == C.ENCODING_E_AC3) { + return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer); + } else { + throw new IllegalStateException("Unexpected audio encoding: " + encoding); + } + } + + @TargetApi(21) + private static int writeNonBlockingV21(android.media.AudioTrack audioTrack, ByteBuffer buffer, + int size) { + return audioTrack.write(buffer, size, WRITE_NON_BLOCKING); + } + + @TargetApi(21) + private int writeNonBlockingWithAvSyncV21(android.media.AudioTrack audioTrack, + ByteBuffer buffer, int size, long presentationTimeUs) { + // TODO: Uncomment this when [Internal ref b/33627517] is clarified or fixed. + // if (Util.SDK_INT >= 23) { + // // The underlying platform AudioTrack writes AV sync headers directly. + // return audioTrack.write(buffer, size, WRITE_NON_BLOCKING, presentationTimeUs * 1000); + // } + if (avSyncHeader == null) { + avSyncHeader = ByteBuffer.allocate(16); + avSyncHeader.order(ByteOrder.BIG_ENDIAN); + avSyncHeader.putInt(0x55550001); + } + if (bytesUntilNextAvSync == 0) { + avSyncHeader.putInt(4, size); + avSyncHeader.putLong(8, presentationTimeUs * 1000); + avSyncHeader.position(0); + bytesUntilNextAvSync = size; + } + int avSyncHeaderBytesRemaining = avSyncHeader.remaining(); + if (avSyncHeaderBytesRemaining > 0) { + int result = audioTrack.write(avSyncHeader, avSyncHeaderBytesRemaining, WRITE_NON_BLOCKING); + if (result < 0) { + bytesUntilNextAvSync = 0; + return result; + } + if (result < avSyncHeaderBytesRemaining) { + return 0; + } + } + int result = writeNonBlockingV21(audioTrack, buffer, size); + if (result < 0) { + bytesUntilNextAvSync = 0; + return result; + } + bytesUntilNextAvSync -= result; + return result; + } + + @TargetApi(21) + private static void setVolumeInternalV21(android.media.AudioTrack audioTrack, float volume) { + audioTrack.setVolume(volume); + } + + @SuppressWarnings("deprecation") + private static void setVolumeInternalV3(android.media.AudioTrack audioTrack, float volume) { + audioTrack.setStereoVolume(volume, volume); + } + + /** + * Wraps an {@link android.media.AudioTrack} to expose useful utility methods. + */ + private static class AudioTrackUtil { + + protected android.media.AudioTrack audioTrack; + private boolean needsPassthroughWorkaround; + private int sampleRate; + private long lastRawPlaybackHeadPosition; + private long rawPlaybackHeadWrapCount; + private long passthroughWorkaroundPauseOffset; + + private long stopTimestampUs; + private long stopPlaybackHeadPosition; + private long endPlaybackHeadPosition; + + /** + * Reconfigures the audio track utility helper to use the specified {@code audioTrack}. + * + * @param audioTrack The audio track to wrap. + * @param needsPassthroughWorkaround Whether to workaround issues with pausing AC-3 passthrough + * audio tracks on platform API version 21/22. + */ + public void reconfigure(android.media.AudioTrack audioTrack, + boolean needsPassthroughWorkaround) { + this.audioTrack = audioTrack; + this.needsPassthroughWorkaround = needsPassthroughWorkaround; + stopTimestampUs = C.TIME_UNSET; + lastRawPlaybackHeadPosition = 0; + rawPlaybackHeadWrapCount = 0; + passthroughWorkaroundPauseOffset = 0; + if (audioTrack != null) { + sampleRate = audioTrack.getSampleRate(); + } + } + + /** + * Stops the audio track in a way that ensures media written to it is played out in full, and + * that {@link #getPlaybackHeadPosition()} and {@link #getPositionUs()} continue to increment as + * the remaining media is played out. + * + * @param writtenFrames The total number of frames that have been written. + */ + public void handleEndOfStream(long writtenFrames) { + stopPlaybackHeadPosition = getPlaybackHeadPosition(); + stopTimestampUs = SystemClock.elapsedRealtime() * 1000; + endPlaybackHeadPosition = writtenFrames; + audioTrack.stop(); + } + + /** + * Pauses the audio track unless the end of the stream has been handled, in which case calling + * this method does nothing. + */ + public void pause() { + if (stopTimestampUs != C.TIME_UNSET) { + // We don't want to knock the audio track back into the paused state. + return; + } + audioTrack.pause(); + } + + /** + * {@link android.media.AudioTrack#getPlaybackHeadPosition()} returns a value intended to be + * interpreted as an unsigned 32 bit integer, which also wraps around periodically. This method + * returns the playback head position as a long that will only wrap around if the value exceeds + * {@link Long#MAX_VALUE} (which in practice will never happen). + * + * @return The playback head position, in frames. + */ + public long getPlaybackHeadPosition() { + if (stopTimestampUs != C.TIME_UNSET) { + // Simulate the playback head position up to the total number of frames submitted. + long elapsedTimeSinceStopUs = (SystemClock.elapsedRealtime() * 1000) - stopTimestampUs; + long framesSinceStop = (elapsedTimeSinceStopUs * sampleRate) / C.MICROS_PER_SECOND; + return Math.min(endPlaybackHeadPosition, stopPlaybackHeadPosition + framesSinceStop); + } + + int state = audioTrack.getPlayState(); + if (state == PLAYSTATE_STOPPED) { + // The audio track hasn't been started. + return 0; + } + + long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition(); + if (needsPassthroughWorkaround) { + // Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22 + // where the playback head position jumps back to zero on paused passthrough/direct audio + // tracks. See [Internal: b/19187573]. + if (state == PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) { + passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition; + } + rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset; + } + if (lastRawPlaybackHeadPosition > rawPlaybackHeadPosition) { + // The value must have wrapped around. + rawPlaybackHeadWrapCount++; + } + lastRawPlaybackHeadPosition = rawPlaybackHeadPosition; + return rawPlaybackHeadPosition + (rawPlaybackHeadWrapCount << 32); + } + + /** + * Returns the duration of played media since reconfiguration, in microseconds. + */ + public long getPositionUs() { + return (getPlaybackHeadPosition() * C.MICROS_PER_SECOND) / sampleRate; + } + + /** + * Updates the values returned by {@link #getTimestampNanoTime()} and + * {@link #getTimestampFramePosition()}. + * + * @return Whether the timestamp values were updated. + */ + public boolean updateTimestamp() { + return false; + } + + /** + * Returns the {@link android.media.AudioTimestamp#nanoTime} obtained during the most recent + * call to {@link #updateTimestamp()} that returned true. + * + * @return The nanoTime obtained during the most recent call to {@link #updateTimestamp()} that + * returned true. + * @throws UnsupportedOperationException If the implementation does not support audio timestamp + * queries. {@link #updateTimestamp()} will always return false in this case. + */ + public long getTimestampNanoTime() { + // Should never be called if updateTimestamp() returned false. + throw new UnsupportedOperationException(); + } + + /** + * Returns the {@link android.media.AudioTimestamp#framePosition} obtained during the most + * recent call to {@link #updateTimestamp()} that returned true. The value is adjusted so that + * wrap around only occurs if the value exceeds {@link Long#MAX_VALUE} (which in practice will + * never happen). + * + * @return The framePosition obtained during the most recent call to {@link #updateTimestamp()} + * that returned true. + * @throws UnsupportedOperationException If the implementation does not support audio timestamp + * queries. {@link #updateTimestamp()} will always return false in this case. + */ + public long getTimestampFramePosition() { + // Should never be called if updateTimestamp() returned false. + throw new UnsupportedOperationException(); + } + + } + + @TargetApi(19) + private static class AudioTrackUtilV19 extends AudioTrackUtil { + + private final AudioTimestamp audioTimestamp; + + private long rawTimestampFramePositionWrapCount; + private long lastRawTimestampFramePosition; + private long lastTimestampFramePosition; + + public AudioTrackUtilV19() { + audioTimestamp = new AudioTimestamp(); + } + + @Override + public void reconfigure(android.media.AudioTrack audioTrack, + boolean needsPassthroughWorkaround) { + super.reconfigure(audioTrack, needsPassthroughWorkaround); + rawTimestampFramePositionWrapCount = 0; + lastRawTimestampFramePosition = 0; + lastTimestampFramePosition = 0; + } + + @Override + public boolean updateTimestamp() { + boolean updated = audioTrack.getTimestamp(audioTimestamp); + if (updated) { + long rawFramePosition = audioTimestamp.framePosition; + if (lastRawTimestampFramePosition > rawFramePosition) { + // The value must have wrapped around. + rawTimestampFramePositionWrapCount++; + } + lastRawTimestampFramePosition = rawFramePosition; + lastTimestampFramePosition = rawFramePosition + (rawTimestampFramePositionWrapCount << 32); + } + return updated; + } + + @Override + public long getTimestampNanoTime() { + return audioTimestamp.nanoTime; + } + + @Override + public long getTimestampFramePosition() { + return lastTimestampFramePosition; + } + + } + + /** + * Stores playback parameters with the position and media time at which they apply. + */ + private static final class PlaybackParametersCheckpoint { + + private final PlaybackParameters playbackParameters; + private final long mediaTimeUs; + private final long positionUs; + + private PlaybackParametersCheckpoint(PlaybackParameters playbackParameters, long mediaTimeUs, + long positionUs) { + this.playbackParameters = playbackParameters; + this.mediaTimeUs = mediaTimeUs; + this.positionUs = positionUs; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/ChannelMappingAudioProcessor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/ChannelMappingAudioProcessor.java new file mode 100644 index 0000000..238e9a1 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/ChannelMappingAudioProcessor.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.C.Encoding; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * An {@link AudioProcessor} that applies a mapping from input channels onto specified output + * channels. This can be used to reorder, duplicate or discard channels. + */ +/* package */ final class ChannelMappingAudioProcessor implements AudioProcessor { + + private int channelCount; + private int sampleRateHz; + private int[] pendingOutputChannels; + + private boolean active; + private int[] outputChannels; + private ByteBuffer buffer; + private ByteBuffer outputBuffer; + private boolean inputEnded; + + /** + * Creates a new processor that applies a channel mapping. + */ + public ChannelMappingAudioProcessor() { + buffer = EMPTY_BUFFER; + outputBuffer = EMPTY_BUFFER; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; + } + + /** + * Resets the channel mapping. After calling this method, call {@link #configure(int, int, int)} + * to start using the new channel map. + * + * @see AudioTrack#configure(String, int, int, int, int, int[]) + */ + public void setChannelMap(int[] outputChannels) { + pendingOutputChannels = outputChannels; + } + + @Override + public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding) + throws UnhandledFormatException { + boolean outputChannelsChanged = !Arrays.equals(pendingOutputChannels, outputChannels); + outputChannels = pendingOutputChannels; + if (outputChannels == null) { + active = false; + return outputChannelsChanged; + } + if (encoding != C.ENCODING_PCM_16BIT) { + throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + } + if (!outputChannelsChanged && this.sampleRateHz == sampleRateHz + && this.channelCount == channelCount) { + return false; + } + this.sampleRateHz = sampleRateHz; + this.channelCount = channelCount; + + active = channelCount != outputChannels.length; + for (int i = 0; i < outputChannels.length; i++) { + int channelIndex = outputChannels[i]; + if (channelIndex >= channelCount) { + throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + } + active |= (channelIndex != i); + } + return true; + } + + @Override + public boolean isActive() { + return active; + } + + @Override + public int getOutputChannelCount() { + return outputChannels == null ? channelCount : outputChannels.length; + } + + @Override + public int getOutputEncoding() { + return C.ENCODING_PCM_16BIT; + } + + @Override + public void queueInput(ByteBuffer inputBuffer) { + int position = inputBuffer.position(); + int limit = inputBuffer.limit(); + int frameCount = (limit - position) / (2 * channelCount); + int outputSize = frameCount * outputChannels.length * 2; + if (buffer.capacity() < outputSize) { + buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); + } else { + buffer.clear(); + } + while (position < limit) { + for (int channelIndex : outputChannels) { + buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex)); + } + position += channelCount * 2; + } + inputBuffer.position(limit); + buffer.flip(); + outputBuffer = buffer; + } + + @Override + public void queueEndOfStream() { + inputEnded = true; + } + + @Override + public ByteBuffer getOutput() { + ByteBuffer outputBuffer = this.outputBuffer; + this.outputBuffer = EMPTY_BUFFER; + return outputBuffer; + } + + @SuppressWarnings("ReferenceEquality") + @Override + public boolean isEnded() { + return inputEnded && outputBuffer == EMPTY_BUFFER; + } + + @Override + public void flush() { + outputBuffer = EMPTY_BUFFER; + inputEnded = false; + } + + @Override + public void reset() { + flush(); + buffer = EMPTY_BUFFER; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; + outputChannels = null; + active = false; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/DtsUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/DtsUtil.java new file mode 100644 index 0000000..f0184f8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/DtsUtil.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import java.nio.ByteBuffer; + +/** + * Utility methods for parsing DTS frames. + */ +public final class DtsUtil { + + /** + * Maps AMODE to the number of channels. See ETSI TS 102 114 table 5.4. + */ + private static final int[] CHANNELS_BY_AMODE = new int[] {1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, + 7, 8, 8}; + + /** + * Maps SFREQ to the sampling frequency in Hz. See ETSI TS 102 144 table 5.5. + */ + private static final int[] SAMPLE_RATE_BY_SFREQ = new int[] {-1, 8000, 16000, 32000, -1, -1, + 11025, 22050, 44100, -1, -1, 12000, 24000, 48000, -1, -1}; + + /** + * Maps RATE to 2 * bitrate in kbit/s. See ETSI TS 102 144 table 5.7. + */ + private static final int[] TWICE_BITRATE_KBPS_BY_RATE = new int[] {64, 112, 128, 192, 224, 256, + 384, 448, 512, 640, 768, 896, 1024, 1152, 1280, 1536, 1920, 2048, 2304, 2560, 2688, 2816, + 2823, 2944, 3072, 3840, 4096, 6144, 7680}; + + /** + * Returns the DTS format given {@code data} containing the DTS frame according to ETSI TS 102 114 + * subsections 5.3/5.4. + * + * @param frame The DTS frame to parse. + * @param trackId The track identifier to set on the format, or null. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The DTS format parsed from data in the header. + */ + public static Format parseDtsFormat(byte[] frame, String trackId, String language, + DrmInitData drmInitData) { + ParsableBitArray frameBits = new ParsableBitArray(frame); + frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE + int amode = frameBits.readBits(6); + int channelCount = CHANNELS_BY_AMODE[amode]; + int sfreq = frameBits.readBits(4); + int sampleRate = SAMPLE_RATE_BY_SFREQ[sfreq]; + int rate = frameBits.readBits(5); + int bitrate = rate >= TWICE_BITRATE_KBPS_BY_RATE.length ? Format.NO_VALUE + : TWICE_BITRATE_KBPS_BY_RATE[rate] * 1000 / 2; + frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF + channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF + return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, null, bitrate, + Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language); + } + + /** + * Returns the number of audio samples represented by the given DTS frame. + * + * @param data The frame to parse. + * @return The number of audio samples represented by the frame. + */ + public static int parseDtsAudioSampleCount(byte[] data) { + // See ETSI TS 102 114 subsection 5.4.1. + int nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2); + return (nblks + 1) * 32; + } + + /** + * Like {@link #parseDtsAudioSampleCount(byte[])} but reads from a {@link ByteBuffer}. The + * buffer's position is not modified. + * + * @param buffer The {@link ByteBuffer} from which to read. + * @return The number of audio samples represented by the syncframe. + */ + public static int parseDtsAudioSampleCount(ByteBuffer buffer) { + // See ETSI TS 102 114 subsection 5.4.1. + int position = buffer.position(); + int nblks = ((buffer.get(position + 4) & 0x01) << 6) + | ((buffer.get(position + 5) & 0xFC) >> 2); + return (nblks + 1) * 32; + } + + /** + * Returns the size in bytes of the given DTS frame. + * + * @param data The frame to parse. + * @return The frame's size in bytes. + */ + public static int getDtsFrameSize(byte[] data) { + return (((data[5] & 0x02) << 12) + | ((data[6] & 0xFF) << 4) + | ((data[7] & 0xF0) >> 4)) + 1; + } + + private DtsUtil() {} + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/MediaCodecAudioRenderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/MediaCodecAudioRenderer.java new file mode 100644 index 0000000..543b630 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaCrypto; +import android.media.MediaFormat; +import android.media.audiofx.Virtualizer; +import android.os.Handler; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.PlaybackParameters; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSessionManager; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.FrameworkMediaCrypto; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MediaClock; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.nio.ByteBuffer; + +/** + * Decodes and renders audio using {@link MediaCodec} and {@link AudioTrack}. + */ +@TargetApi(16) +public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock { + + private final EventDispatcher eventDispatcher; + private final AudioTrack audioTrack; + + private boolean passthroughEnabled; + private boolean codecNeedsDiscardChannelsWorkaround; + private android.media.MediaFormat passthroughMediaFormat; + private int pcmEncoding; + private int channelCount; + private long currentPositionUs; + private boolean allowPositionDiscontinuity; + + /** + * @param mediaCodecSelector A decoder selector. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector) { + this(mediaCodecSelector, null, true); + } + + /** + * @param mediaCodecSelector A decoder selector. + * @param drmSessionManager For use with encrypted content. May be null if support for encrypted + * content is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys) { + this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, null, null); + } + + /** + * @param mediaCodecSelector A decoder selector. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, Handler eventHandler, + AudioRendererEventListener eventListener) { + this(mediaCodecSelector, null, true, eventHandler, eventListener); + } + + /** + * @param mediaCodecSelector A decoder selector. + * @param drmSessionManager For use with encrypted content. May be null if support for encrypted + * content is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys, Handler eventHandler, + AudioRendererEventListener eventListener) { + this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, + eventListener, null); + } + + /** + * @param mediaCodecSelector A decoder selector. + * @param drmSessionManager For use with encrypted content. May be null if support for encrypted + * content is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioCapabilities The audio capabilities for playback on this device. May be null if the + * default capabilities (no encoded audio passthrough support) should be assumed. + * @param audioProcessors Optional {@link AudioProcessor}s that will process PCM audio before + * output. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys, Handler eventHandler, + AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities, + AudioProcessor... audioProcessors) { + super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); + audioTrack = new AudioTrack(audioCapabilities, audioProcessors, new AudioTrackListener()); + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + } + + @Override + protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) + throws DecoderQueryException { + String mimeType = format.sampleMimeType; + if (!MimeTypes.isAudio(mimeType)) { + return FORMAT_UNSUPPORTED_TYPE; + } + int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; + if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null) { + return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | FORMAT_HANDLED; + } + MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, false); + if (decoderInfo == null) { + return FORMAT_UNSUPPORTED_SUBTYPE; + } + // Note: We assume support for unknown sampleRate and channelCount. + boolean decoderCapable = Util.SDK_INT < 21 + || ((format.sampleRate == Format.NO_VALUE + || decoderInfo.isAudioSampleRateSupportedV21(format.sampleRate)) + && (format.channelCount == Format.NO_VALUE + || decoderInfo.isAudioChannelCountSupportedV21(format.channelCount))); + int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; + return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | formatSupport; + } + + @Override + protected MediaCodecInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, + Format format, boolean requiresSecureDecoder) throws DecoderQueryException { + if (allowPassthrough(format.sampleMimeType)) { + MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo(); + if (passthroughDecoderInfo != null) { + passthroughEnabled = true; + return passthroughDecoderInfo; + } + } + passthroughEnabled = false; + return super.getDecoderInfo(mediaCodecSelector, format, requiresSecureDecoder); + } + + /** + * Returns whether encoded audio passthrough should be used for playing back the input format. + * This implementation returns true if the {@link AudioTrack}'s audio capabilities indicate that + * passthrough is supported. + * + * @param mimeType The type of input media. + * @return Whether passthrough playback should be used. + */ + protected boolean allowPassthrough(String mimeType) { + return audioTrack.isPassthroughSupported(mimeType); + } + + @Override + protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, + MediaCrypto crypto) { + codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); + if (passthroughEnabled) { + // Override the MIME type used to configure the codec if we are using a passthrough decoder. + passthroughMediaFormat = format.getFrameworkMediaFormatV16(); + passthroughMediaFormat.setString(MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW); + codec.configure(passthroughMediaFormat, null, crypto, 0); + passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType); + } else { + codec.configure(format.getFrameworkMediaFormatV16(), null, crypto, 0); + passthroughMediaFormat = null; + } + } + + @Override + public MediaClock getMediaClock() { + return this; + } + + @Override + protected void onCodecInitialized(String name, long initializedTimestampMs, + long initializationDurationMs) { + eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); + } + + @Override + protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + super.onInputFormatChanged(newFormat); + eventDispatcher.inputFormatChanged(newFormat); + // If the input format is anything other than PCM then we assume that the audio decoder will + // output 16-bit PCM. + pcmEncoding = MimeTypes.AUDIO_RAW.equals(newFormat.sampleMimeType) ? newFormat.pcmEncoding + : C.ENCODING_PCM_16BIT; + channelCount = newFormat.channelCount; + } + + @Override + protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) + throws ExoPlaybackException { + boolean passthrough = passthroughMediaFormat != null; + String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME) + : MimeTypes.AUDIO_RAW; + MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat; + int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); + int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); + int[] channelMap; + if (codecNeedsDiscardChannelsWorkaround && channelCount == 6 && this.channelCount < 6) { + channelMap = new int[this.channelCount]; + for (int i = 0; i < this.channelCount; i++) { + channelMap[i] = i; + } + } else { + channelMap = null; + } + + try { + audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0, channelMap); + } catch (AudioTrack.ConfigurationException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + /** + * Called when the audio session id becomes known. The default implementation is a no-op. One + * reason for overriding this method would be to instantiate and enable a {@link Virtualizer} in + * order to spatialize the audio channels. For this use case, any {@link Virtualizer} instances + * should be released in {@link #onDisabled()} (if not before). + * + * @see AudioTrack.Listener#onAudioSessionId(int) + */ + protected void onAudioSessionId(int audioSessionId) { + // Do nothing. + } + + /** + * @see AudioTrack.Listener#onPositionDiscontinuity() + */ + protected void onAudioTrackPositionDiscontinuity() { + // Do nothing. + } + + /** + * @see AudioTrack.Listener#onUnderrun(int, long, long) + */ + protected void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, + long elapsedSinceLastFeedMs) { + // Do nothing. + } + + @Override + protected void onEnabled(boolean joining) throws ExoPlaybackException { + super.onEnabled(joining); + eventDispatcher.enabled(decoderCounters); + int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; + if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { + audioTrack.enableTunnelingV21(tunnelingAudioSessionId); + } else { + audioTrack.disableTunneling(); + } + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { + super.onPositionReset(positionUs, joining); + audioTrack.reset(); + currentPositionUs = positionUs; + allowPositionDiscontinuity = true; + } + + @Override + protected void onStarted() { + super.onStarted(); + audioTrack.play(); + } + + @Override + protected void onStopped() { + audioTrack.pause(); + super.onStopped(); + } + + @Override + protected void onDisabled() { + try { + audioTrack.release(); + } finally { + try { + super.onDisabled(); + } finally { + decoderCounters.ensureUpdated(); + eventDispatcher.disabled(decoderCounters); + } + } + } + + @Override + public boolean isEnded() { + return super.isEnded() && audioTrack.isEnded(); + } + + @Override + public boolean isReady() { + return audioTrack.hasPendingData() || super.isReady(); + } + + @Override + public long getPositionUs() { + long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded()); + if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) { + currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs + : Math.max(currentPositionUs, newCurrentPositionUs); + allowPositionDiscontinuity = false; + } + return currentPositionUs; + } + + @Override + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + return audioTrack.setPlaybackParameters(playbackParameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return audioTrack.getPlaybackParameters(); + } + + @Override + protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, + ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, + boolean shouldSkip) throws ExoPlaybackException { + if (passthroughEnabled && (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + // Discard output buffers from the passthrough (raw) decoder containing codec specific data. + codec.releaseOutputBuffer(bufferIndex, false); + return true; + } + + if (shouldSkip) { + codec.releaseOutputBuffer(bufferIndex, false); + decoderCounters.skippedOutputBufferCount++; + audioTrack.handleDiscontinuity(); + return true; + } + + try { + if (audioTrack.handleBuffer(buffer, bufferPresentationTimeUs)) { + codec.releaseOutputBuffer(bufferIndex, false); + decoderCounters.renderedOutputBufferCount++; + return true; + } + } catch (AudioTrack.InitializationException | AudioTrack.WriteException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + return false; + } + + @Override + protected void renderToEndOfStream() throws ExoPlaybackException { + try { + audioTrack.playToEndOfStream(); + } catch (AudioTrack.WriteException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + switch (messageType) { + case C.MSG_SET_VOLUME: + audioTrack.setVolume((Float) message); + break; + case C.MSG_SET_STREAM_TYPE: + @C.StreamType int streamType = (Integer) message; + audioTrack.setStreamType(streamType); + break; + default: + super.handleMessage(messageType, message); + break; + } + } + + /** + * Returns whether the decoder is known to output six audio channels when provided with input with + * fewer than six channels. + *

    + * See [Internal: b/35655036]. + */ + private static boolean codecNeedsDiscardChannelsWorkaround(String codecName) { + // The workaround applies to Samsung Galaxy S6 and Samsung Galaxy S7. + return Util.SDK_INT < 24 && "OMX.SEC.aac.dec".equals(codecName) + && "samsung".equals(Util.MANUFACTURER) + && (Util.DEVICE.startsWith("zeroflte") || Util.DEVICE.startsWith("herolte") + || Util.DEVICE.startsWith("heroqlte")); + } + + private final class AudioTrackListener implements AudioTrack.Listener { + + @Override + public void onAudioSessionId(int audioSessionId) { + eventDispatcher.audioSessionId(audioSessionId); + MediaCodecAudioRenderer.this.onAudioSessionId(audioSessionId); + } + + @Override + public void onPositionDiscontinuity() { + onAudioTrackPositionDiscontinuity(); + // We are out of sync so allow currentPositionUs to jump backwards. + MediaCodecAudioRenderer.this.allowPositionDiscontinuity = true; + } + + @Override + public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { + eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/ResamplingAudioProcessor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/ResamplingAudioProcessor.java new file mode 100644 index 0000000..5ef3f76 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/ResamplingAudioProcessor.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * An {@link AudioProcessor} that converts audio data to {@link C#ENCODING_PCM_16BIT}. + */ +/* package */ final class ResamplingAudioProcessor implements AudioProcessor { + + private int sampleRateHz; + private int channelCount; + @C.PcmEncoding + private int encoding; + private ByteBuffer buffer; + private ByteBuffer outputBuffer; + private boolean inputEnded; + + /** + * Creates a new audio processor that converts audio data to {@link C#ENCODING_PCM_16BIT}. + */ + public ResamplingAudioProcessor() { + sampleRateHz = Format.NO_VALUE; + channelCount = Format.NO_VALUE; + encoding = C.ENCODING_INVALID; + buffer = EMPTY_BUFFER; + outputBuffer = EMPTY_BUFFER; + } + + @Override + public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) + throws UnhandledFormatException { + if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT + && encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) { + throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + } + if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount + && this.encoding == encoding) { + return false; + } + this.sampleRateHz = sampleRateHz; + this.channelCount = channelCount; + this.encoding = encoding; + if (encoding == C.ENCODING_PCM_16BIT) { + buffer = EMPTY_BUFFER; + } + return true; + } + + @Override + public boolean isActive() { + return encoding != C.ENCODING_INVALID && encoding != C.ENCODING_PCM_16BIT; + } + + @Override + public int getOutputChannelCount() { + return channelCount; + } + + @Override + public int getOutputEncoding() { + return C.ENCODING_PCM_16BIT; + } + + @Override + public void queueInput(ByteBuffer inputBuffer) { + // Prepare the output buffer. + int position = inputBuffer.position(); + int limit = inputBuffer.limit(); + int size = limit - position; + int resampledSize; + switch (encoding) { + case C.ENCODING_PCM_8BIT: + resampledSize = size * 2; + break; + case C.ENCODING_PCM_24BIT: + resampledSize = (size / 3) * 2; + break; + case C.ENCODING_PCM_32BIT: + resampledSize = size / 2; + break; + case C.ENCODING_PCM_16BIT: + case C.ENCODING_INVALID: + case Format.NO_VALUE: + default: + throw new IllegalStateException(); + } + if (buffer.capacity() < resampledSize) { + buffer = ByteBuffer.allocateDirect(resampledSize).order(ByteOrder.nativeOrder()); + } else { + buffer.clear(); + } + + // Resample the little endian input and update the input/output buffers. + switch (encoding) { + case C.ENCODING_PCM_8BIT: + // 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up. + for (int i = position; i < limit; i++) { + buffer.put((byte) 0); + buffer.put((byte) ((inputBuffer.get(i) & 0xFF) - 128)); + } + break; + case C.ENCODING_PCM_24BIT: + // 24->16 bit resampling. Drop the least significant byte. + for (int i = position; i < limit; i += 3) { + buffer.put(inputBuffer.get(i + 1)); + buffer.put(inputBuffer.get(i + 2)); + } + break; + case C.ENCODING_PCM_32BIT: + // 32->16 bit resampling. Drop the two least significant bytes. + for (int i = position; i < limit; i += 4) { + buffer.put(inputBuffer.get(i + 2)); + buffer.put(inputBuffer.get(i + 3)); + } + break; + case C.ENCODING_PCM_16BIT: + case C.ENCODING_INVALID: + case Format.NO_VALUE: + default: + // Never happens. + throw new IllegalStateException(); + } + inputBuffer.position(inputBuffer.limit()); + buffer.flip(); + outputBuffer = buffer; + } + + @Override + public void queueEndOfStream() { + inputEnded = true; + } + + @Override + public ByteBuffer getOutput() { + ByteBuffer outputBuffer = this.outputBuffer; + this.outputBuffer = EMPTY_BUFFER; + return outputBuffer; + } + + @SuppressWarnings("ReferenceEquality") + @Override + public boolean isEnded() { + return inputEnded && outputBuffer == EMPTY_BUFFER; + } + + @Override + public void flush() { + outputBuffer = EMPTY_BUFFER; + inputEnded = false; + } + + @Override + public void reset() { + flush(); + buffer = EMPTY_BUFFER; + sampleRateHz = Format.NO_VALUE; + channelCount = Format.NO_VALUE; + encoding = C.ENCODING_INVALID; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/SimpleDecoderAudioRenderer.java new file mode 100644 index 0000000..d8967d3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -0,0 +1,631 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import android.media.audiofx.Virtualizer; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.BaseRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.PlaybackParameters; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderCounters; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.SimpleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.SimpleOutputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSession; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSessionManager; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.ExoMediaCrypto; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MediaClock; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TraceUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Decodes and renders audio using a {@link SimpleDecoder}. + */ +public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock { + + @Retention(RetentionPolicy.SOURCE) + @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, + REINITIALIZATION_STATE_WAIT_END_OF_STREAM}) + private @interface ReinitializationState {} + /** + * The decoder does not need to be re-initialized. + */ + private static final int REINITIALIZATION_STATE_NONE = 0; + /** + * The input format has changed in a way that requires the decoder to be re-initialized, but we + * haven't yet signaled an end of stream to the existing decoder. We need to do so in order to + * ensure that it outputs any remaining buffers before we release it. + */ + private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1; + /** + * The input format has changed in a way that requires the decoder to be re-initialized, and we've + * signaled an end of stream to the existing decoder. We're waiting for the decoder to output an + * end of stream signal to indicate that it has output any remaining buffers before we release it. + */ + private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; + + private final DrmSessionManager drmSessionManager; + private final boolean playClearSamplesWithoutKeys; + private final EventDispatcher eventDispatcher; + private final AudioTrack audioTrack; + private final FormatHolder formatHolder; + private final DecoderInputBuffer flagsOnlyBuffer; + + private DecoderCounters decoderCounters; + private Format inputFormat; + private SimpleDecoder decoder; + private DecoderInputBuffer inputBuffer; + private SimpleOutputBuffer outputBuffer; + private DrmSession drmSession; + private DrmSession pendingDrmSession; + + @ReinitializationState private int decoderReinitializationState; + private boolean decoderReceivedBuffers; + private boolean audioTrackNeedsConfigure; + + private long currentPositionUs; + private boolean allowPositionDiscontinuity; + private boolean inputStreamEnded; + private boolean outputStreamEnded; + private boolean waitingForKeys; + + public SimpleDecoderAudioRenderer() { + this(null, null); + } + + /** + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output. + */ + public SimpleDecoderAudioRenderer(Handler eventHandler, + AudioRendererEventListener eventListener, AudioProcessor... audioProcessors) { + this(eventHandler, eventListener, null, null, false, audioProcessors); + } + + /** + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioCapabilities The audio capabilities for playback on this device. May be null if the + * default capabilities (no encoded audio passthrough support) should be assumed. + */ + public SimpleDecoderAudioRenderer(Handler eventHandler, + AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) { + this(eventHandler, eventListener, audioCapabilities, null, false); + } + + /** + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioCapabilities The audio capabilities for playback on this device. May be null if the + * default capabilities (no encoded audio passthrough support) should be assumed. + * @param drmSessionManager For use with encrypted media. May be null if support for encrypted + * media is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output. + */ + public SimpleDecoderAudioRenderer(Handler eventHandler, + AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities, + DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, + AudioProcessor... audioProcessors) { + super(C.TRACK_TYPE_AUDIO); + this.drmSessionManager = drmSessionManager; + this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + audioTrack = new AudioTrack(audioCapabilities, audioProcessors, new AudioTrackListener()); + formatHolder = new FormatHolder(); + flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); + decoderReinitializationState = REINITIALIZATION_STATE_NONE; + audioTrackNeedsConfigure = true; + } + + @Override + public MediaClock getMediaClock() { + return this; + } + + @Override + public final int supportsFormat(Format format) { + int formatSupport = supportsFormatInternal(format); + if (formatSupport == FORMAT_UNSUPPORTED_TYPE || formatSupport == FORMAT_UNSUPPORTED_SUBTYPE) { + return formatSupport; + } + int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; + return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | formatSupport; + } + + /** + * Returns the {@link #FORMAT_SUPPORT_MASK} component of the return value for + * {@link #supportsFormat(Format)}. + * + * @param format The format. + * @return The extent to which the renderer supports the format itself. + */ + protected abstract int supportsFormatInternal(Format format); + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (outputStreamEnded) { + try { + audioTrack.playToEndOfStream(); + } catch (AudioTrack.WriteException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + return; + } + + // Try and read a format if we don't have one already. + if (inputFormat == null) { + // We don't have a format yet, so try and read one. + flagsOnlyBuffer.clear(); + int result = readSource(formatHolder, flagsOnlyBuffer, true); + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + } else if (result == C.RESULT_BUFFER_READ) { + // End of stream read having not read a format. + Assertions.checkState(flagsOnlyBuffer.isEndOfStream()); + inputStreamEnded = true; + processEndOfStream(); + return; + } else { + // We still don't have a format and can't make progress without one. + return; + } + } + + // If we don't have a decoder yet, we need to instantiate one. + maybeInitDecoder(); + + if (decoder != null) { + try { + // Rendering loop. + TraceUtil.beginSection("drainAndFeed"); + while (drainOutputBuffer()) {} + while (feedInputBuffer()) {} + TraceUtil.endSection(); + } catch (AudioDecoderException | AudioTrack.ConfigurationException + | AudioTrack.InitializationException | AudioTrack.WriteException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + decoderCounters.ensureUpdated(); + } + } + + /** + * Called when the audio session id becomes known. The default implementation is a no-op. One + * reason for overriding this method would be to instantiate and enable a {@link Virtualizer} in + * order to spatialize the audio channels. For this use case, any {@link Virtualizer} instances + * should be released in {@link #onDisabled()} (if not before). + * + * @see AudioTrack.Listener#onAudioSessionId(int) + */ + protected void onAudioSessionId(int audioSessionId) { + // Do nothing. + } + + /** + * @see AudioTrack.Listener#onPositionDiscontinuity() + */ + protected void onAudioTrackPositionDiscontinuity() { + // Do nothing. + } + + /** + * @see AudioTrack.Listener#onUnderrun(int, long, long) + */ + protected void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, + long elapsedSinceLastFeedMs) { + // Do nothing. + } + + /** + * Creates a decoder for the given format. + * + * @param format The format for which a decoder is required. + * @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content. + * Maybe null and can be ignored if decoder does not handle encrypted content. + * @return The decoder. + * @throws AudioDecoderException If an error occurred creating a suitable decoder. + */ + protected abstract SimpleDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto) + throws AudioDecoderException; + + /** + * Returns the format of audio buffers output by the decoder. Will not be called until the first + * output buffer has been dequeued, so the decoder may use input data to determine the format. + *

    + * The default implementation returns a 16-bit PCM format with the same channel count and sample + * rate as the input. + */ + protected Format getOutputFormat() { + return Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, null, Format.NO_VALUE, + Format.NO_VALUE, inputFormat.channelCount, inputFormat.sampleRate, C.ENCODING_PCM_16BIT, + null, null, 0, null); + } + + private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException, + AudioTrack.ConfigurationException, AudioTrack.InitializationException, + AudioTrack.WriteException { + if (outputBuffer == null) { + outputBuffer = decoder.dequeueOutputBuffer(); + if (outputBuffer == null) { + return false; + } + decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount; + } + + if (outputBuffer.isEndOfStream()) { + if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { + // We're waiting to re-initialize the decoder, and have now processed all final buffers. + releaseDecoder(); + maybeInitDecoder(); + // The audio track may need to be recreated once the new output format is known. + audioTrackNeedsConfigure = true; + } else { + outputBuffer.release(); + outputBuffer = null; + processEndOfStream(); + } + return false; + } + + if (audioTrackNeedsConfigure) { + Format outputFormat = getOutputFormat(); + audioTrack.configure(outputFormat.sampleMimeType, outputFormat.channelCount, + outputFormat.sampleRate, outputFormat.pcmEncoding, 0); + audioTrackNeedsConfigure = false; + } + + if (audioTrack.handleBuffer(outputBuffer.data, outputBuffer.timeUs)) { + decoderCounters.renderedOutputBufferCount++; + outputBuffer.release(); + outputBuffer = null; + return true; + } + + return false; + } + + private boolean feedInputBuffer() throws AudioDecoderException, ExoPlaybackException { + if (decoder == null || decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM + || inputStreamEnded) { + // We need to reinitialize the decoder or the input stream has ended. + return false; + } + + if (inputBuffer == null) { + inputBuffer = decoder.dequeueInputBuffer(); + if (inputBuffer == null) { + return false; + } + } + + if (decoderReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) { + inputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + decoder.queueInputBuffer(inputBuffer); + inputBuffer = null; + decoderReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM; + return false; + } + + int result; + if (waitingForKeys) { + // We've already read an encrypted sample into buffer, and are waiting for keys. + result = C.RESULT_BUFFER_READ; + } else { + result = readSource(formatHolder, inputBuffer, false); + } + + if (result == C.RESULT_NOTHING_READ) { + return false; + } + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + return true; + } + if (inputBuffer.isEndOfStream()) { + inputStreamEnded = true; + decoder.queueInputBuffer(inputBuffer); + inputBuffer = null; + return false; + } + boolean bufferEncrypted = inputBuffer.isEncrypted(); + waitingForKeys = shouldWaitForKeys(bufferEncrypted); + if (waitingForKeys) { + return false; + } + inputBuffer.flip(); + decoder.queueInputBuffer(inputBuffer); + decoderReceivedBuffers = true; + decoderCounters.inputBufferCount++; + inputBuffer = null; + return true; + } + + private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { + if (drmSession == null) { + return false; + } + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } + return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS + && (bufferEncrypted || !playClearSamplesWithoutKeys); + } + + private void processEndOfStream() throws ExoPlaybackException { + outputStreamEnded = true; + try { + audioTrack.playToEndOfStream(); + } catch (AudioTrack.WriteException e) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } + } + + private void flushDecoder() throws ExoPlaybackException { + waitingForKeys = false; + if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) { + releaseDecoder(); + maybeInitDecoder(); + } else { + inputBuffer = null; + if (outputBuffer != null) { + outputBuffer.release(); + outputBuffer = null; + } + decoder.flush(); + decoderReceivedBuffers = false; + } + } + + @Override + public boolean isEnded() { + return outputStreamEnded && audioTrack.isEnded(); + } + + @Override + public boolean isReady() { + return audioTrack.hasPendingData() + || (inputFormat != null && !waitingForKeys && (isSourceReady() || outputBuffer != null)); + } + + @Override + public long getPositionUs() { + long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded()); + if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) { + currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs + : Math.max(currentPositionUs, newCurrentPositionUs); + allowPositionDiscontinuity = false; + } + return currentPositionUs; + } + + @Override + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + return audioTrack.setPlaybackParameters(playbackParameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return audioTrack.getPlaybackParameters(); + } + + @Override + protected void onEnabled(boolean joining) throws ExoPlaybackException { + decoderCounters = new DecoderCounters(); + eventDispatcher.enabled(decoderCounters); + int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; + if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { + audioTrack.enableTunnelingV21(tunnelingAudioSessionId); + } else { + audioTrack.disableTunneling(); + } + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { + audioTrack.reset(); + currentPositionUs = positionUs; + allowPositionDiscontinuity = true; + inputStreamEnded = false; + outputStreamEnded = false; + if (decoder != null) { + flushDecoder(); + } + } + + @Override + protected void onStarted() { + audioTrack.play(); + } + + @Override + protected void onStopped() { + audioTrack.pause(); + } + + @Override + protected void onDisabled() { + inputFormat = null; + audioTrackNeedsConfigure = true; + waitingForKeys = false; + try { + releaseDecoder(); + audioTrack.release(); + } finally { + try { + if (drmSession != null) { + drmSessionManager.releaseSession(drmSession); + } + } finally { + try { + if (pendingDrmSession != null && pendingDrmSession != drmSession) { + drmSessionManager.releaseSession(pendingDrmSession); + } + } finally { + drmSession = null; + pendingDrmSession = null; + decoderCounters.ensureUpdated(); + eventDispatcher.disabled(decoderCounters); + } + } + } + } + + private void maybeInitDecoder() throws ExoPlaybackException { + if (decoder != null) { + return; + } + + drmSession = pendingDrmSession; + ExoMediaCrypto mediaCrypto = null; + if (drmSession != null) { + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } else if (drmSessionState == DrmSession.STATE_OPENED + || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { + mediaCrypto = drmSession.getMediaCrypto(); + } else { + // The drm session isn't open yet. + return; + } + } + + try { + long codecInitializingTimestamp = SystemClock.elapsedRealtime(); + TraceUtil.beginSection("createAudioDecoder"); + decoder = createDecoder(inputFormat, mediaCrypto); + TraceUtil.endSection(); + long codecInitializedTimestamp = SystemClock.elapsedRealtime(); + eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp, + codecInitializedTimestamp - codecInitializingTimestamp); + decoderCounters.decoderInitCount++; + } catch (AudioDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + private void releaseDecoder() { + if (decoder == null) { + return; + } + + inputBuffer = null; + outputBuffer = null; + decoder.release(); + decoder = null; + decoderCounters.decoderReleaseCount++; + decoderReinitializationState = REINITIALIZATION_STATE_NONE; + decoderReceivedBuffers = false; + } + + private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + Format oldFormat = inputFormat; + inputFormat = newFormat; + + boolean drmInitDataChanged = !Util.areEqual(inputFormat.drmInitData, oldFormat == null ? null + : oldFormat.drmInitData); + if (drmInitDataChanged) { + if (inputFormat.drmInitData != null) { + if (drmSessionManager == null) { + throw ExoPlaybackException.createForRenderer( + new IllegalStateException("Media requires a DrmSessionManager"), getIndex()); + } + pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), + inputFormat.drmInitData); + if (pendingDrmSession == drmSession) { + drmSessionManager.releaseSession(pendingDrmSession); + } + } else { + pendingDrmSession = null; + } + } + + if (decoderReceivedBuffers) { + // Signal end of stream and wait for any final output buffers before re-initialization. + decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; + } else { + // There aren't any final output buffers, so release the decoder immediately. + releaseDecoder(); + maybeInitDecoder(); + audioTrackNeedsConfigure = true; + } + + eventDispatcher.inputFormatChanged(newFormat); + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + switch (messageType) { + case C.MSG_SET_VOLUME: + audioTrack.setVolume((Float) message); + break; + case C.MSG_SET_STREAM_TYPE: + @C.StreamType int streamType = (Integer) message; + audioTrack.setStreamType(streamType); + break; + default: + super.handleMessage(messageType, message); + break; + } + } + + private final class AudioTrackListener implements AudioTrack.Listener { + + @Override + public void onAudioSessionId(int audioSessionId) { + eventDispatcher.audioSessionId(audioSessionId); + SimpleDecoderAudioRenderer.this.onAudioSessionId(audioSessionId); + } + + @Override + public void onPositionDiscontinuity() { + onAudioTrackPositionDiscontinuity(); + // We are out of sync so allow currentPositionUs to jump backwards. + SimpleDecoderAudioRenderer.this.allowPositionDiscontinuity = true; + } + + @Override + public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { + eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/Sonic.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/Sonic.java new file mode 100644 index 0000000..7ea462d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/Sonic.java @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2010 Bill Cox, Sonic Library + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.nio.ShortBuffer; +import java.util.Arrays; + +/** + * Sonic audio stream processor for time/pitch stretching. + *

    + * Based on https://github.com/waywardgeek/sonic. + */ +/* package */ final class Sonic { + + private static final boolean USE_CHORD_PITCH = false; + private static final int MINIMUM_PITCH = 65; + private static final int MAXIMUM_PITCH = 400; + private static final int AMDF_FREQUENCY = 4000; + + private final int sampleRate; + private final int numChannels; + private final int minPeriod; + private final int maxPeriod; + private final int maxRequired; + private final short[] downSampleBuffer; + + private int inputBufferSize; + private short[] inputBuffer; + private int outputBufferSize; + private short[] outputBuffer; + private int pitchBufferSize; + private short[] pitchBuffer; + private int oldRatePosition; + private int newRatePosition; + private float speed; + private float pitch; + private int numInputSamples; + private int numOutputSamples; + private int numPitchSamples; + private int remainingInputToCopy; + private int prevPeriod; + private int prevMinDiff; + private int minDiff; + private int maxDiff; + + /** + * Creates a new Sonic audio stream processor. + * + * @param sampleRate The sample rate of input audio. + * @param numChannels The number of channels in the input audio. + */ + public Sonic(int sampleRate, int numChannels) { + this.sampleRate = sampleRate; + this.numChannels = numChannels; + minPeriod = sampleRate / MAXIMUM_PITCH; + maxPeriod = sampleRate / MINIMUM_PITCH; + maxRequired = 2 * maxPeriod; + downSampleBuffer = new short[maxRequired]; + inputBufferSize = maxRequired; + inputBuffer = new short[maxRequired * numChannels]; + outputBufferSize = maxRequired; + outputBuffer = new short[maxRequired * numChannels]; + pitchBufferSize = maxRequired; + pitchBuffer = new short[maxRequired * numChannels]; + oldRatePosition = 0; + newRatePosition = 0; + prevPeriod = 0; + speed = 1.0f; + pitch = 1.0f; + } + + /** + * Sets the output speed. + */ + public void setSpeed(float speed) { + this.speed = speed; + } + + /** + * Gets the output speed. + */ + public float getSpeed() { + return speed; + } + + /** + * Sets the output pitch. + */ + public void setPitch(float pitch) { + this.pitch = pitch; + } + + /** + * Gets the output pitch. + */ + public float getPitch() { + return pitch; + } + + /** + * Queues remaining data from {@code buffer}, and advances its position by the number of bytes + * consumed. + * + * @param buffer A {@link ShortBuffer} containing input data between its position and limit. + */ + public void queueInput(ShortBuffer buffer) { + int samplesToWrite = buffer.remaining() / numChannels; + int bytesToWrite = samplesToWrite * numChannels * 2; + enlargeInputBufferIfNeeded(samplesToWrite); + buffer.get(inputBuffer, numInputSamples * numChannels, bytesToWrite / 2); + numInputSamples += samplesToWrite; + processStreamInput(); + } + + /** + * Gets available output, outputting to the start of {@code buffer}. The buffer's position will be + * advanced by the number of bytes written. + * + * @param buffer A {@link ShortBuffer} into which output will be written. + */ + public void getOutput(ShortBuffer buffer) { + int samplesToRead = Math.min(buffer.remaining() / numChannels, numOutputSamples); + buffer.put(outputBuffer, 0, samplesToRead * numChannels); + numOutputSamples -= samplesToRead; + System.arraycopy(outputBuffer, samplesToRead * numChannels, outputBuffer, 0, + numOutputSamples * numChannels); + } + + /** + * Forces generating output using whatever data has been queued already. No extra delay will be + * added to the output, but flushing in the middle of words could introduce distortion. + */ + public void queueEndOfStream() { + int remainingSamples = numInputSamples; + float s = speed / pitch; + int expectedOutputSamples = + numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / pitch + 0.5f); + + // Add enough silence to flush both input and pitch buffers. + enlargeInputBufferIfNeeded(remainingSamples + 2 * maxRequired); + for (int xSample = 0; xSample < 2 * maxRequired * numChannels; xSample++) { + inputBuffer[remainingSamples * numChannels + xSample] = 0; + } + numInputSamples += 2 * maxRequired; + processStreamInput(); + // Throw away any extra samples we generated due to the silence we added. + if (numOutputSamples > expectedOutputSamples) { + numOutputSamples = expectedOutputSamples; + } + // Empty input and pitch buffers. + numInputSamples = 0; + remainingInputToCopy = 0; + numPitchSamples = 0; + } + + /** + * Returns the number of output samples that can be read with {@link #getOutput(ShortBuffer)}. + */ + public int getSamplesAvailable() { + return numOutputSamples; + } + + // Internal methods. + + private void enlargeOutputBufferIfNeeded(int numSamples) { + if (numOutputSamples + numSamples > outputBufferSize) { + outputBufferSize += (outputBufferSize / 2) + numSamples; + outputBuffer = Arrays.copyOf(outputBuffer, outputBufferSize * numChannels); + } + } + + private void enlargeInputBufferIfNeeded(int numSamples) { + if (numInputSamples + numSamples > inputBufferSize) { + inputBufferSize += (inputBufferSize / 2) + numSamples; + inputBuffer = Arrays.copyOf(inputBuffer, inputBufferSize * numChannels); + } + } + + private void removeProcessedInputSamples(int position) { + int remainingSamples = numInputSamples - position; + System.arraycopy(inputBuffer, position * numChannels, inputBuffer, 0, + remainingSamples * numChannels); + numInputSamples = remainingSamples; + } + + private void copyToOutput(short[] samples, int position, int numSamples) { + enlargeOutputBufferIfNeeded(numSamples); + System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels, + numSamples * numChannels); + numOutputSamples += numSamples; + } + + private int copyInputToOutput(int position) { + int numSamples = Math.min(maxRequired, remainingInputToCopy); + copyToOutput(inputBuffer, position, numSamples); + remainingInputToCopy -= numSamples; + return numSamples; + } + + private void downSampleInput(short[] samples, int position, int skip) { + // If skip is greater than one, average skip samples together and write them to the down-sample + // buffer. If numChannels is greater than one, mix the channels together as we down sample. + int numSamples = maxRequired / skip; + int samplesPerValue = numChannels * skip; + position *= numChannels; + for (int i = 0; i < numSamples; i++) { + int value = 0; + for (int j = 0; j < samplesPerValue; j++) { + value += samples[position + i * samplesPerValue + j]; + } + value /= samplesPerValue; + downSampleBuffer[i] = (short) value; + } + } + + private int findPitchPeriodInRange(short[] samples, int position, int minPeriod, int maxPeriod) { + // Find the best frequency match in the range, and given a sample skip multiple. For now, just + // find the pitch of the first channel. + int bestPeriod = 0; + int worstPeriod = 255; + int minDiff = 1; + int maxDiff = 0; + position *= numChannels; + for (int period = minPeriod; period <= maxPeriod; period++) { + int diff = 0; + for (int i = 0; i < period; i++) { + short sVal = samples[position + i]; + short pVal = samples[position + period + i]; + diff += sVal >= pVal ? sVal - pVal : pVal - sVal; + } + // Note that the highest number of samples we add into diff will be less than 256, since we + // skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples + // without overflow. + if (diff * bestPeriod < minDiff * period) { + minDiff = diff; + bestPeriod = period; + } + if (diff * worstPeriod > maxDiff * period) { + maxDiff = diff; + worstPeriod = period; + } + } + this.minDiff = minDiff / bestPeriod; + this.maxDiff = maxDiff / worstPeriod; + return bestPeriod; + } + + /** + * Returns whether the previous pitch period estimate is a better approximation, which can occur + * at the abrupt end of voiced words. + */ + private boolean previousPeriodBetter(int minDiff, int maxDiff, boolean preferNewPeriod) { + if (minDiff == 0 || prevPeriod == 0) { + return false; + } + if (preferNewPeriod) { + if (maxDiff > minDiff * 3) { + // Got a reasonable match this period + return false; + } + if (minDiff * 2 <= prevMinDiff * 3) { + // Mismatch is not that much greater this period + return false; + } + } else { + if (minDiff <= prevMinDiff) { + return false; + } + } + return true; + } + + private int findPitchPeriod(short[] samples, int position, boolean preferNewPeriod) { + // Find the pitch period. This is a critical step, and we may have to try multiple ways to get a + // good answer. This version uses AMDF. To improve speed, we down sample by an integer factor + // get in the 11 kHz range, and then do it again with a narrower frequency range without down + // sampling. + int period; + int retPeriod; + int skip = sampleRate > AMDF_FREQUENCY ? sampleRate / AMDF_FREQUENCY : 1; + if (numChannels == 1 && skip == 1) { + period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod); + } else { + downSampleInput(samples, position, skip); + period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod / skip, maxPeriod / skip); + if (skip != 1) { + period *= skip; + int minP = period - (skip * 4); + int maxP = period + (skip * 4); + if (minP < minPeriod) { + minP = minPeriod; + } + if (maxP > maxPeriod) { + maxP = maxPeriod; + } + if (numChannels == 1) { + period = findPitchPeriodInRange(samples, position, minP, maxP); + } else { + downSampleInput(samples, position, 1); + period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP); + } + } + } + if (previousPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { + retPeriod = prevPeriod; + } else { + retPeriod = period; + } + prevMinDiff = minDiff; + prevPeriod = period; + return retPeriod; + } + + private void moveNewSamplesToPitchBuffer(int originalNumOutputSamples) { + int numSamples = numOutputSamples - originalNumOutputSamples; + if (numPitchSamples + numSamples > pitchBufferSize) { + pitchBufferSize += (pitchBufferSize / 2) + numSamples; + pitchBuffer = Arrays.copyOf(pitchBuffer, pitchBufferSize * numChannels); + } + System.arraycopy(outputBuffer, originalNumOutputSamples * numChannels, pitchBuffer, + numPitchSamples * numChannels, numSamples * numChannels); + numOutputSamples = originalNumOutputSamples; + numPitchSamples += numSamples; + } + + private void removePitchSamples(int numSamples) { + if (numSamples == 0) { + return; + } + System.arraycopy(pitchBuffer, numSamples * numChannels, pitchBuffer, 0, + (numPitchSamples - numSamples) * numChannels); + numPitchSamples -= numSamples; + } + + private void adjustPitch(int originalNumOutputSamples) { + // Latency due to pitch changes could be reduced by looking at past samples to determine pitch, + // rather than future. + if (numOutputSamples == originalNumOutputSamples) { + return; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + int position = 0; + while (numPitchSamples - position >= maxRequired) { + int period = findPitchPeriod(pitchBuffer, position, false); + int newPeriod = (int) (period / pitch); + enlargeOutputBufferIfNeeded(newPeriod); + if (pitch >= 1.0f) { + overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer, position, + pitchBuffer, position + period - newPeriod); + } else { + int separation = newPeriod - period; + overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples, + pitchBuffer, position, pitchBuffer, position); + } + numOutputSamples += newPeriod; + position += period; + } + removePitchSamples(position); + } + + private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) { + short left = in[inPos * numChannels]; + short right = in[inPos * numChannels + numChannels]; + int position = newRatePosition * oldSampleRate; + int leftPosition = oldRatePosition * newSampleRate; + int rightPosition = (oldRatePosition + 1) * newSampleRate; + int ratio = rightPosition - position; + int width = rightPosition - leftPosition; + return (short) ((ratio * left + (width - ratio) * right) / width); + } + + private void adjustRate(float rate, int originalNumOutputSamples) { + if (numOutputSamples == originalNumOutputSamples) { + return; + } + int newSampleRate = (int) (sampleRate / rate); + int oldSampleRate = sampleRate; + // Set these values to help with the integer math. + while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { + newSampleRate /= 2; + oldSampleRate /= 2; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + // Leave at least one pitch sample in the buffer. + for (int position = 0; position < numPitchSamples - 1; position++) { + while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) { + enlargeOutputBufferIfNeeded(1); + for (int i = 0; i < numChannels; i++) { + outputBuffer[numOutputSamples * numChannels + i] = + interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate); + } + newRatePosition++; + numOutputSamples++; + } + oldRatePosition++; + if (oldRatePosition == oldSampleRate) { + oldRatePosition = 0; + Assertions.checkState(newRatePosition == newSampleRate); + newRatePosition = 0; + } + } + removePitchSamples(numPitchSamples - 1); + } + + private int skipPitchPeriod(short[] samples, int position, float speed, int period) { + // Skip over a pitch period, and copy period/speed samples to the output. + int newSamples; + if (speed >= 2.0f) { + newSamples = (int) (period / (speed - 1.0f)); + } else { + newSamples = period; + remainingInputToCopy = (int) (period * (2.0f - speed) / (speed - 1.0f)); + } + enlargeOutputBufferIfNeeded(newSamples); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, samples, + position + period); + numOutputSamples += newSamples; + return newSamples; + } + + private int insertPitchPeriod(short[] samples, int position, float speed, int period) { + // Insert a pitch period, and determine how much input to copy directly. + int newSamples; + if (speed < 0.5f) { + newSamples = (int) (period * speed / (1.0f - speed)); + } else { + newSamples = period; + remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed)); + } + enlargeOutputBufferIfNeeded(period + newSamples); + System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels, + period * numChannels); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples, + position + period, samples, position); + numOutputSamples += period + newSamples; + return newSamples; + } + + private void changeSpeed(float speed) { + if (numInputSamples < maxRequired) { + return; + } + int numSamples = numInputSamples; + int position = 0; + do { + if (remainingInputToCopy > 0) { + position += copyInputToOutput(position); + } else { + int period = findPitchPeriod(inputBuffer, position, true); + if (speed > 1.0) { + position += period + skipPitchPeriod(inputBuffer, position, speed, period); + } else { + position += insertPitchPeriod(inputBuffer, position, speed, period); + } + } + } while (position + maxRequired <= numSamples); + removeProcessedInputSamples(position); + } + + private void processStreamInput() { + // Resample as many pitch periods as we have buffered on the input. + int originalNumOutputSamples = numOutputSamples; + float s = speed / pitch; + if (s > 1.00001 || s < 0.99999) { + changeSpeed(s); + } else { + copyToOutput(inputBuffer, 0, numInputSamples); + numInputSamples = 0; + } + if (USE_CHORD_PITCH) { + if (pitch != 1.0f) { + adjustPitch(originalNumOutputSamples); + } + } else if (!USE_CHORD_PITCH && pitch != 1.0f) { + adjustRate(pitch, originalNumOutputSamples); + } + } + + private static void overlapAdd(int numSamples, int numChannels, short[] out, int outPos, + short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) { + for (int i = 0; i < numChannels; i++) { + int o = outPos * numChannels + i; + int u = rampUpPos * numChannels + i; + int d = rampDownPos * numChannels + i; + for (int t = 0; t < numSamples; t++) { + out[o] = (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * t) / numSamples); + o += numChannels; + d += numChannels; + u += numChannels; + } + } + } + + private static void overlapAddWithSeparation(int numSamples, int numChannels, int separation, + short[] out, int outPos, short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) { + for (int i = 0; i < numChannels; i++) { + int o = outPos * numChannels + i; + int u = rampUpPos * numChannels + i; + int d = rampDownPos * numChannels + i; + for (int t = 0; t < numSamples + separation; t++) { + if (t < separation) { + out[o] = (short) (rampDown[d] * (numSamples - t) / numSamples); + d += numChannels; + } else if (t < numSamples) { + out[o] = + (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * (t - separation)) + / numSamples); + d += numChannels; + u += numChannels; + } else { + out[o] = (short) (rampUp[u] * (t - separation) / numSamples); + u += numChannels; + } + o += numChannels; + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/SonicAudioProcessor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/SonicAudioProcessor.java new file mode 100644 index 0000000..51aab21 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/audio/SonicAudioProcessor.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.audio; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.C.Encoding; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; + +/** + * An {@link AudioProcessor} that uses the Sonic library to modify the speed/pitch of audio. + */ +public final class SonicAudioProcessor implements AudioProcessor { + + /** + * The maximum allowed playback speed in {@link #setSpeed(float)}. + */ + public static final float MAXIMUM_SPEED = 8.0f; + /** + * The minimum allowed playback speed in {@link #setSpeed(float)}. + */ + public static final float MINIMUM_SPEED = 0.1f; + /** + * The maximum allowed pitch in {@link #setPitch(float)}. + */ + public static final float MAXIMUM_PITCH = 8.0f; + /** + * The minimum allowed pitch in {@link #setPitch(float)}. + */ + public static final float MINIMUM_PITCH = 0.1f; + + /** + * The threshold below which the difference between two pitch/speed factors is negligible. + */ + private static final float CLOSE_THRESHOLD = 0.01f; + + private int channelCount; + private int sampleRateHz; + + private Sonic sonic; + private float speed; + private float pitch; + + private ByteBuffer buffer; + private ShortBuffer shortBuffer; + private ByteBuffer outputBuffer; + private long inputBytes; + private long outputBytes; + private boolean inputEnded; + + /** + * Creates a new Sonic audio processor. + */ + public SonicAudioProcessor() { + speed = 1f; + pitch = 1f; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; + buffer = EMPTY_BUFFER; + shortBuffer = buffer.asShortBuffer(); + outputBuffer = EMPTY_BUFFER; + } + + /** + * Sets the playback speed. The new speed will take effect after a call to {@link #flush()}. + * + * @param speed The requested new playback speed. + * @return The actual new playback speed. + */ + public float setSpeed(float speed) { + this.speed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED); + return this.speed; + } + + /** + * Sets the playback pitch. The new pitch will take effect after a call to {@link #flush()}. + * + * @param pitch The requested new pitch. + * @return The actual new pitch. + */ + public float setPitch(float pitch) { + this.pitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH); + return pitch; + } + + /** + * Returns the number of bytes of input queued since the last call to {@link #flush()}. + */ + public long getInputByteCount() { + return inputBytes; + } + + /** + * Returns the number of bytes of output dequeued since the last call to {@link #flush()}. + */ + public long getOutputByteCount() { + return outputBytes; + } + + @Override + public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding) + throws UnhandledFormatException { + if (encoding != C.ENCODING_PCM_16BIT) { + throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + } + if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount) { + return false; + } + this.sampleRateHz = sampleRateHz; + this.channelCount = channelCount; + return true; + } + + @Override + public boolean isActive() { + return Math.abs(speed - 1f) >= CLOSE_THRESHOLD || Math.abs(pitch - 1f) >= CLOSE_THRESHOLD; + } + + @Override + public int getOutputChannelCount() { + return channelCount; + } + + @Override + public int getOutputEncoding() { + return C.ENCODING_PCM_16BIT; + } + + @Override + public void queueInput(ByteBuffer inputBuffer) { + if (inputBuffer.hasRemaining()) { + ShortBuffer shortBuffer = inputBuffer.asShortBuffer(); + int inputSize = inputBuffer.remaining(); + inputBytes += inputSize; + sonic.queueInput(shortBuffer); + inputBuffer.position(inputBuffer.position() + inputSize); + } + int outputSize = sonic.getSamplesAvailable() * channelCount * 2; + if (outputSize > 0) { + if (buffer.capacity() < outputSize) { + buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); + shortBuffer = buffer.asShortBuffer(); + } else { + buffer.clear(); + shortBuffer.clear(); + } + sonic.getOutput(shortBuffer); + outputBytes += outputSize; + buffer.limit(outputSize); + outputBuffer = buffer; + } + } + + @Override + public void queueEndOfStream() { + sonic.queueEndOfStream(); + inputEnded = true; + } + + @Override + public ByteBuffer getOutput() { + ByteBuffer outputBuffer = this.outputBuffer; + this.outputBuffer = EMPTY_BUFFER; + return outputBuffer; + } + + @Override + public boolean isEnded() { + return inputEnded && (sonic == null || sonic.getSamplesAvailable() == 0); + } + + @Override + public void flush() { + sonic = new Sonic(sampleRateHz, channelCount); + sonic.setSpeed(speed); + sonic.setPitch(pitch); + outputBuffer = EMPTY_BUFFER; + inputBytes = 0; + outputBytes = 0; + inputEnded = false; + } + + @Override + public void reset() { + sonic = null; + buffer = EMPTY_BUFFER; + shortBuffer = buffer.asShortBuffer(); + outputBuffer = EMPTY_BUFFER; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; + inputBytes = 0; + outputBytes = 0; + inputEnded = false; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/Buffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/Buffer.java new file mode 100644 index 0000000..5fd2c31 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/Buffer.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.decoder; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +/** + * Base class for buffers with flags. + */ +public abstract class Buffer { + + @C.BufferFlags + private int flags; + + /** + * Clears the buffer. + */ + public void clear() { + flags = 0; + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_DECODE_ONLY} flag is set. + */ + public final boolean isDecodeOnly() { + return getFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_END_OF_STREAM} flag is set. + */ + public final boolean isEndOfStream() { + return getFlag(C.BUFFER_FLAG_END_OF_STREAM); + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_KEY_FRAME} flag is set. + */ + public final boolean isKeyFrame() { + return getFlag(C.BUFFER_FLAG_KEY_FRAME); + } + + /** + * Replaces this buffer's flags with {@code flags}. + * + * @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*} + * constants. + */ + public final void setFlags(@C.BufferFlags int flags) { + this.flags = flags; + } + + /** + * Adds the {@code flag} to this buffer's flags. + * + * @param flag The flag to add to this buffer's flags, which should be one of the + * {@code C.BUFFER_FLAG_*} constants. + */ + public final void addFlag(@C.BufferFlags int flag) { + flags |= flag; + } + + /** + * Removes the {@code flag} from this buffer's flags, if it is set. + * + * @param flag The flag to remove. + */ + public final void clearFlag(@C.BufferFlags int flag) { + flags &= ~flag; + } + + /** + * Returns whether the specified flag has been set on this buffer. + * + * @param flag The flag to check. + * @return Whether the flag is set. + */ + protected final boolean getFlag(@C.BufferFlags int flag) { + return (flags & flag) == flag; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/CryptoInfo.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/CryptoInfo.java new file mode 100644 index 0000000..28bfa04 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/CryptoInfo.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.decoder; + +import android.annotation.TargetApi; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Compatibility wrapper for {@link android.media.MediaCodec.CryptoInfo}. + */ +public final class CryptoInfo { + + /** + * @see android.media.MediaCodec.CryptoInfo#iv + */ + public byte[] iv; + /** + * @see android.media.MediaCodec.CryptoInfo#key + */ + public byte[] key; + /** + * @see android.media.MediaCodec.CryptoInfo#mode + */ + @C.CryptoMode + public int mode; + /** + * @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData + */ + public int[] numBytesOfClearData; + /** + * @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData + */ + public int[] numBytesOfEncryptedData; + /** + * @see android.media.MediaCodec.CryptoInfo#numSubSamples + */ + public int numSubSamples; + /** + * @see android.media.MediaCodec.CryptoInfo.Pattern + */ + public int patternBlocksToEncrypt; + /** + * @see android.media.MediaCodec.CryptoInfo.Pattern + */ + public int patternBlocksToSkip; + + private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; + private final PatternHolderV24 patternHolder; + + public CryptoInfo() { + frameworkCryptoInfo = Util.SDK_INT >= 16 ? newFrameworkCryptoInfoV16() : null; + patternHolder = Util.SDK_INT >= 24 ? new PatternHolderV24(frameworkCryptoInfo) : null; + } + + /** + * @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int) + */ + public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, + byte[] key, byte[] iv, @C.CryptoMode int mode) { + this.numSubSamples = numSubSamples; + this.numBytesOfClearData = numBytesOfClearData; + this.numBytesOfEncryptedData = numBytesOfEncryptedData; + this.key = key; + this.iv = iv; + this.mode = mode; + patternBlocksToEncrypt = 0; + patternBlocksToSkip = 0; + if (Util.SDK_INT >= 16) { + updateFrameworkCryptoInfoV16(); + } + } + + public void setPattern(int patternBlocksToEncrypt, int patternBlocksToSkip) { + this.patternBlocksToEncrypt = patternBlocksToEncrypt; + this.patternBlocksToSkip = patternBlocksToSkip; + if (Util.SDK_INT >= 24) { + patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip); + } + } + + /** + * Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance. + *

    + * Successive calls to this method on a single {@link CryptoInfo} will return the same instance. + * Changes to the {@link CryptoInfo} will be reflected in the returned object. The return object + * should not be modified directly. + * + * @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance. + */ + @TargetApi(16) + public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() { + return frameworkCryptoInfo; + } + + @TargetApi(16) + private android.media.MediaCodec.CryptoInfo newFrameworkCryptoInfoV16() { + return new android.media.MediaCodec.CryptoInfo(); + } + + @TargetApi(16) + private void updateFrameworkCryptoInfoV16() { + // Update fields directly because the framework's CryptoInfo.set performs an unnecessary object + // allocation on Android N. + frameworkCryptoInfo.numSubSamples = numSubSamples; + frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData; + frameworkCryptoInfo.numBytesOfEncryptedData = numBytesOfEncryptedData; + frameworkCryptoInfo.key = key; + frameworkCryptoInfo.iv = iv; + frameworkCryptoInfo.mode = mode; + if (Util.SDK_INT >= 24) { + patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip); + } + } + + @TargetApi(24) + private static final class PatternHolderV24 { + + private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; + private final android.media.MediaCodec.CryptoInfo.Pattern pattern; + + private PatternHolderV24(android.media.MediaCodec.CryptoInfo frameworkCryptoInfo) { + this.frameworkCryptoInfo = frameworkCryptoInfo; + pattern = new android.media.MediaCodec.CryptoInfo.Pattern(0, 0); + } + + private void set(int blocksToEncrypt, int blocksToSkip) { + pattern.set(blocksToEncrypt, blocksToSkip); + frameworkCryptoInfo.setPattern(pattern); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/Decoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/Decoder.java new file mode 100644 index 0000000..437c10b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/Decoder.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.decoder; + +/** + * A media decoder. + * + * @param The type of buffer input to the decoder. + * @param The type of buffer output from the decoder. + * @param The type of exception thrown from the decoder. + */ +public interface Decoder { + + /** + * Returns the name of the decoder. + * + * @return The name of the decoder. + */ + String getName(); + + /** + * Dequeues the next input buffer to be filled and queued to the decoder. + * + * @return The input buffer, which will have been cleared, or null if a buffer isn't available. + * @throws E If a decoder error has occurred. + */ + I dequeueInputBuffer() throws E; + + /** + * Queues an input buffer to the decoder. + * + * @param inputBuffer The input buffer. + * @throws E If a decoder error has occurred. + */ + void queueInputBuffer(I inputBuffer) throws E; + + /** + * Dequeues the next output buffer from the decoder. + * + * @return The output buffer, or null if an output buffer isn't available. + * @throws E If a decoder error has occurred. + */ + O dequeueOutputBuffer() throws E; + + /** + * Flushes the decoder. Ownership of dequeued input buffers is returned to the decoder. The caller + * is still responsible for releasing any dequeued output buffers. + */ + void flush(); + + /** + * Releases the decoder. Must be called when the decoder is no longer needed. + */ + void release(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/DecoderCounters.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/DecoderCounters.java new file mode 100644 index 0000000..d7ad59a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/DecoderCounters.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.decoder; + +/** + * Maintains decoder event counts, for debugging purposes only. + *

    + * Counters should be written from the playback thread only. Counters may be read from any thread. + * To ensure that the counter values are made visible across threads, users of this class should + * invoke {@link #ensureUpdated()} prior to reading and after writing. + */ +public final class DecoderCounters { + + /** + * The number of times a decoder has been initialized. + */ + public int decoderInitCount; + /** + * The number of times a decoder has been released. + */ + public int decoderReleaseCount; + /** + * The number of queued input buffers. + */ + public int inputBufferCount; + /** + * The number of rendered output buffers. + */ + public int renderedOutputBufferCount; + /** + * The number of skipped output buffers. + *

    + * A skipped output buffer is an output buffer that was deliberately not rendered. + */ + public int skippedOutputBufferCount; + /** + * The number of dropped output buffers. + *

    + * A dropped output buffer is an output buffer that was supposed to be rendered, but was instead + * dropped because it could not be rendered in time. + */ + public int droppedOutputBufferCount; + /** + * The maximum number of dropped output buffers without an interleaving rendered output buffer. + *

    + * Skipped output buffers are ignored for the purposes of calculating this value. + */ + public int maxConsecutiveDroppedOutputBufferCount; + + /** + * Should be called to ensure counter values are made visible across threads. The playback thread + * should call this method after updating the counter values. Any other thread should call this + * method before reading the counters. + */ + public synchronized void ensureUpdated() { + // Do nothing. The use of synchronized ensures a memory barrier should another thread also + // call this method. + } + + /** + * Merges the counts from {@code other} into this instance. + * + * @param other The {@link DecoderCounters} to merge into this instance. + */ + public void merge(DecoderCounters other) { + decoderInitCount += other.decoderInitCount; + decoderReleaseCount += other.decoderReleaseCount; + inputBufferCount += other.inputBufferCount; + renderedOutputBufferCount += other.renderedOutputBufferCount; + skippedOutputBufferCount += other.skippedOutputBufferCount; + droppedOutputBufferCount += other.droppedOutputBufferCount; + maxConsecutiveDroppedOutputBufferCount = Math.max(maxConsecutiveDroppedOutputBufferCount, + other.maxConsecutiveDroppedOutputBufferCount); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/DecoderInputBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/DecoderInputBuffer.java new file mode 100644 index 0000000..e594490 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/DecoderInputBuffer.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.decoder; + +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; + +/** + * Holds input for a decoder. + */ +public class DecoderInputBuffer extends Buffer { + + /** + * The buffer replacement mode, which may disable replacement. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, + BUFFER_REPLACEMENT_MODE_DIRECT}) + public @interface BufferReplacementMode {} + /** + * Disallows buffer replacement. + */ + public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0; + /** + * Allows buffer replacement using {@link ByteBuffer#allocate(int)}. + */ + public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1; + /** + * Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. + */ + public static final int BUFFER_REPLACEMENT_MODE_DIRECT = 2; + + /** + * {@link CryptoInfo} for encrypted data. + */ + public final CryptoInfo cryptoInfo; + + /** + * The buffer's data, or {@code null} if no data has been set. + */ + public ByteBuffer data; + + /** + * The time at which the sample should be presented. + */ + public long timeUs; + + @BufferReplacementMode private final int bufferReplacementMode; + + /** + * Creates a new instance for which {@link #isFlagsOnly()} will return true. + * + * @return A new flags only input buffer. + */ + public static DecoderInputBuffer newFlagsOnlyInstance() { + return new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED); + } + + /** + * @param bufferReplacementMode Determines the behavior of {@link #ensureSpaceForWrite(int)}. One + * of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and + * {@link #BUFFER_REPLACEMENT_MODE_DIRECT}. + */ + public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) { + this.cryptoInfo = new CryptoInfo(); + this.bufferReplacementMode = bufferReplacementMode; + } + + /** + * Ensures that {@link #data} is large enough to accommodate a write of a given length at its + * current position. + *

    + * If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is + * insufficient then an attempt is made to replace {@link #data} with a new {@link ByteBuffer} + * whose capacity is sufficient. Data up to the current position is copied to the new buffer. + * + * @param length The length of the write that must be accommodated, in bytes. + * @throws IllegalStateException If there is insufficient capacity to accommodate the write and + * the buffer replacement mode of the holder is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. + */ + public void ensureSpaceForWrite(int length) throws IllegalStateException { + if (data == null) { + data = createReplacementByteBuffer(length); + return; + } + // Check whether the current buffer is sufficient. + int capacity = data.capacity(); + int position = data.position(); + int requiredCapacity = position + length; + if (capacity >= requiredCapacity) { + return; + } + // Instantiate a new buffer if possible. + ByteBuffer newData = createReplacementByteBuffer(requiredCapacity); + // Copy data up to the current position from the old buffer to the new one. + if (position > 0) { + data.position(0); + data.limit(position); + newData.put(data); + } + // Set the new buffer. + data = newData; + } + + /** + * Returns whether the buffer is only able to hold flags, meaning {@link #data} is null and + * its replacement mode is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. + */ + public final boolean isFlagsOnly() { + return data == null && bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DISABLED; + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_ENCRYPTED} flag is set. + */ + public final boolean isEncrypted() { + return getFlag(C.BUFFER_FLAG_ENCRYPTED); + } + + /** + * Flips {@link #data} in preparation for being queued to a decoder. + * + * @see java.nio.Buffer#flip() + */ + public final void flip() { + data.flip(); + } + + @Override + public void clear() { + super.clear(); + if (data != null) { + data.clear(); + } + } + + private ByteBuffer createReplacementByteBuffer(int requiredCapacity) { + if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_NORMAL) { + return ByteBuffer.allocate(requiredCapacity); + } else if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DIRECT) { + return ByteBuffer.allocateDirect(requiredCapacity); + } else { + int currentCapacity = data == null ? 0 : data.capacity(); + throw new IllegalStateException("Buffer too small (" + currentCapacity + " < " + + requiredCapacity + ")"); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/OutputBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/OutputBuffer.java new file mode 100644 index 0000000..5a3399d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/OutputBuffer.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.decoder; + +/** + * Output buffer decoded by a {@link Decoder}. + */ +public abstract class OutputBuffer extends Buffer { + + /** + * The presentation timestamp for the buffer, in microseconds. + */ + public long timeUs; + + /** + * The number of buffers immediately prior to this one that were skipped in the {@link Decoder}. + */ + public int skippedOutputBufferCount; + + /** + * Releases the output buffer for reuse. Must be called when the buffer is no longer needed. + */ + public abstract void release(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/SimpleDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/SimpleDecoder.java new file mode 100644 index 0000000..fa3b05f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/SimpleDecoder.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.decoder; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.LinkedList; + +/** + * Base class for {@link Decoder}s that use their own decode thread. + */ +public abstract class SimpleDecoder implements Decoder { + + private final Thread decodeThread; + + private final Object lock; + private final LinkedList queuedInputBuffers; + private final LinkedList queuedOutputBuffers; + private final I[] availableInputBuffers; + private final O[] availableOutputBuffers; + + private int availableInputBufferCount; + private int availableOutputBufferCount; + private I dequeuedInputBuffer; + + private E exception; + private boolean flushed; + private boolean released; + private int skippedOutputBufferCount; + + /** + * @param inputBuffers An array of nulls that will be used to store references to input buffers. + * @param outputBuffers An array of nulls that will be used to store references to output buffers. + */ + protected SimpleDecoder(I[] inputBuffers, O[] outputBuffers) { + lock = new Object(); + queuedInputBuffers = new LinkedList<>(); + queuedOutputBuffers = new LinkedList<>(); + availableInputBuffers = inputBuffers; + availableInputBufferCount = inputBuffers.length; + for (int i = 0; i < availableInputBufferCount; i++) { + availableInputBuffers[i] = createInputBuffer(); + } + availableOutputBuffers = outputBuffers; + availableOutputBufferCount = outputBuffers.length; + for (int i = 0; i < availableOutputBufferCount; i++) { + availableOutputBuffers[i] = createOutputBuffer(); + } + decodeThread = new Thread() { + @Override + public void run() { + SimpleDecoder.this.run(); + } + }; + decodeThread.start(); + } + + /** + * Sets the initial size of each input buffer. + *

    + * This method should only be called before the decoder is used (i.e. before the first call to + * {@link #dequeueInputBuffer()}. + * + * @param size The required input buffer size. + */ + protected final void setInitialInputBufferSize(int size) { + Assertions.checkState(availableInputBufferCount == availableInputBuffers.length); + for (I inputBuffer : availableInputBuffers) { + inputBuffer.ensureSpaceForWrite(size); + } + } + + @Override + public final I dequeueInputBuffer() throws E { + synchronized (lock) { + maybeThrowException(); + Assertions.checkState(dequeuedInputBuffer == null); + dequeuedInputBuffer = availableInputBufferCount == 0 ? null + : availableInputBuffers[--availableInputBufferCount]; + return dequeuedInputBuffer; + } + } + + @Override + public final void queueInputBuffer(I inputBuffer) throws E { + synchronized (lock) { + maybeThrowException(); + Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); + queuedInputBuffers.addLast(inputBuffer); + maybeNotifyDecodeLoop(); + dequeuedInputBuffer = null; + } + } + + @Override + public final O dequeueOutputBuffer() throws E { + synchronized (lock) { + maybeThrowException(); + if (queuedOutputBuffers.isEmpty()) { + return null; + } + return queuedOutputBuffers.removeFirst(); + } + } + + /** + * Releases an output buffer back to the decoder. + * + * @param outputBuffer The output buffer being released. + */ + protected void releaseOutputBuffer(O outputBuffer) { + synchronized (lock) { + releaseOutputBufferInternal(outputBuffer); + maybeNotifyDecodeLoop(); + } + } + + @Override + public final void flush() { + synchronized (lock) { + flushed = true; + skippedOutputBufferCount = 0; + if (dequeuedInputBuffer != null) { + releaseInputBufferInternal(dequeuedInputBuffer); + dequeuedInputBuffer = null; + } + while (!queuedInputBuffers.isEmpty()) { + releaseInputBufferInternal(queuedInputBuffers.removeFirst()); + } + while (!queuedOutputBuffers.isEmpty()) { + releaseOutputBufferInternal(queuedOutputBuffers.removeFirst()); + } + } + } + + @Override + public void release() { + synchronized (lock) { + released = true; + lock.notify(); + } + try { + decodeThread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * Throws a decode exception, if there is one. + * + * @throws E The decode exception. + */ + private void maybeThrowException() throws E { + if (exception != null) { + throw exception; + } + } + + /** + * Notifies the decode loop if there exists a queued input buffer and an available output buffer + * to decode into. + *

    + * Should only be called whilst synchronized on the lock object. + */ + private void maybeNotifyDecodeLoop() { + if (canDecodeBuffer()) { + lock.notify(); + } + } + + private void run() { + try { + while (decode()) { + // Do nothing. + } + } catch (InterruptedException e) { + // Not expected. + throw new IllegalStateException(e); + } + } + + private boolean decode() throws InterruptedException { + I inputBuffer; + O outputBuffer; + boolean resetDecoder; + + // Wait until we have an input buffer to decode, and an output buffer to decode into. + synchronized (lock) { + while (!released && !canDecodeBuffer()) { + lock.wait(); + } + if (released) { + return false; + } + inputBuffer = queuedInputBuffers.removeFirst(); + outputBuffer = availableOutputBuffers[--availableOutputBufferCount]; + resetDecoder = flushed; + flushed = false; + } + + if (inputBuffer.isEndOfStream()) { + outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + } else { + if (inputBuffer.isDecodeOnly()) { + outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + exception = decode(inputBuffer, outputBuffer, resetDecoder); + if (exception != null) { + // Memory barrier to ensure that the decoder exception is visible from the playback thread. + synchronized (lock) {} + return false; + } + } + + synchronized (lock) { + if (flushed) { + releaseOutputBufferInternal(outputBuffer); + } else if (outputBuffer.isDecodeOnly()) { + skippedOutputBufferCount++; + releaseOutputBufferInternal(outputBuffer); + } else { + outputBuffer.skippedOutputBufferCount = skippedOutputBufferCount; + skippedOutputBufferCount = 0; + queuedOutputBuffers.addLast(outputBuffer); + } + // Make the input buffer available again. + releaseInputBufferInternal(inputBuffer); + } + + return true; + } + + private boolean canDecodeBuffer() { + return !queuedInputBuffers.isEmpty() && availableOutputBufferCount > 0; + } + + private void releaseInputBufferInternal(I inputBuffer) { + inputBuffer.clear(); + availableInputBuffers[availableInputBufferCount++] = inputBuffer; + } + + private void releaseOutputBufferInternal(O outputBuffer) { + outputBuffer.clear(); + availableOutputBuffers[availableOutputBufferCount++] = outputBuffer; + } + + /** + * Creates a new input buffer. + */ + protected abstract I createInputBuffer(); + + /** + * Creates a new output buffer. + */ + protected abstract O createOutputBuffer(); + + /** + * Decodes the {@code inputBuffer} and stores any decoded output in {@code outputBuffer}. + * + * @param inputBuffer The buffer to decode. + * @param outputBuffer The output buffer to store decoded data. The flag + * {@link C#BUFFER_FLAG_DECODE_ONLY} will be set if the same flag is set on + * {@code inputBuffer}, but may be set/unset as required. If the flag is set when the call + * returns then the output buffer will not be made available to dequeue. The output buffer + * may not have been populated in this case. + * @param reset Whether the decoder must be reset before decoding. + * @return A decoder exception if an error occurred, or null if decoding was successful. + */ + protected abstract E decode(I inputBuffer, O outputBuffer, boolean reset); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/SimpleOutputBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/SimpleOutputBuffer.java new file mode 100644 index 0000000..a98fbec --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/decoder/SimpleOutputBuffer.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.decoder; + +import java.nio.ByteBuffer; + +/** + * Buffer for {@link SimpleDecoder} output. + */ +public class SimpleOutputBuffer extends OutputBuffer { + + private final SimpleDecoder owner; + + public ByteBuffer data; + + public SimpleOutputBuffer(SimpleDecoder owner) { + this.owner = owner; + } + + /** + * Initializes the buffer. + * + * @param timeUs The presentation timestamp for the buffer, in microseconds. + * @param size An upper bound on the size of the data that will be written to the buffer. + * @return The {@link #data} buffer, for convenience. + */ + public ByteBuffer init(long timeUs, int size) { + this.timeUs = timeUs; + if (data == null || data.capacity() < size) { + data = ByteBuffer.allocateDirect(size); + } + data.position(0); + data.limit(size); + return data; + } + + @Override + public void clear() { + super.clear(); + if (data != null) { + data.clear(); + } + } + + @Override + public void release() { + owner.releaseOutputBuffer(this); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DecryptionException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DecryptionException.java new file mode 100644 index 0000000..8c2ae00 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DecryptionException.java @@ -0,0 +1,20 @@ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +/** + * An exception when doing drm decryption using the In-App Drm + */ +public class DecryptionException extends Exception { + private final int errorCode; + + public DecryptionException(int errorCode, String message) { + super(message); + this.errorCode = errorCode; + } + + /** + * Get error code + */ + public int getErrorCode() { + return errorCode; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DefaultDrmSessionManager.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DefaultDrmSessionManager.java new file mode 100644 index 0000000..ba339fb --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DefaultDrmSessionManager.java @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.media.DeniedByServerException; +import android.media.MediaDrm; +import android.media.NotProvisionedException; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.IntDef; +import android.text.TextUtils; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData.SchemeData; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.ExoMediaDrm.KeyRequest; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.ExoMediaDrm.OnEventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.PsshAtomUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * A {@link DrmSessionManager} that supports playbacks using {@link MediaDrm}. + */ +@TargetApi(18) +public class DefaultDrmSessionManager implements DrmSessionManager, + DrmSession { + + /** + * Listener of {@link DefaultDrmSessionManager} events. + */ + public interface EventListener { + + /** + * Called each time keys are loaded. + */ + void onDrmKeysLoaded(); + + /** + * Called when a drm error occurs. + * + * @param e The corresponding exception. + */ + void onDrmSessionManagerError(Exception e); + + /** + * Called each time offline keys are restored. + */ + void onDrmKeysRestored(); + + /** + * Called each time offline keys are removed. + */ + void onDrmKeysRemoved(); + + } + + /** + * The key to use when passing CustomData to a PlayReady instance in an optional parameter map. + */ + public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData"; + + /** Determines the action to be done after a session acquired. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE}) + public @interface Mode {} + /** + * Loads and refreshes (if necessary) a license for playback. Supports streaming and offline + * licenses. + */ + public static final int MODE_PLAYBACK = 0; + /** + * Restores an offline license to allow its status to be queried. If the offline license is + * expired sets state to {@link #STATE_ERROR}. + */ + public static final int MODE_QUERY = 1; + /** Downloads an offline license or renews an existing one. */ + public static final int MODE_DOWNLOAD = 2; + /** Releases an existing offline license. */ + public static final int MODE_RELEASE = 3; + + private static final String TAG = "OfflineDrmSessionMngr"; + private static final String CENC_SCHEME_MIME_TYPE = "cenc"; + + private static final int MSG_PROVISION = 0; + private static final int MSG_KEYS = 1; + + private static final int MAX_LICENSE_DURATION_TO_RENEW = 60; + + private final Handler eventHandler; + private final EventListener eventListener; + private final ExoMediaDrm mediaDrm; + private final HashMap optionalKeyRequestParameters; + + /* package */ final MediaDrmCallback callback; + /* package */ final UUID uuid; + + /* package */ MediaDrmHandler mediaDrmHandler; + /* package */ PostResponseHandler postResponseHandler; + + private Looper playbackLooper; + private HandlerThread requestHandlerThread; + private Handler postRequestHandler; + + private int mode; + private int openCount; + private boolean provisioningInProgress; + @DrmSession.State + private int state; + private T mediaCrypto; + private DrmSessionException lastException; + private byte[] schemeInitData; + private String schemeMimeType; + private byte[] sessionId; + private byte[] offlineLicenseKeySetId; + + /** + * Instantiates a new instance using the Widevine scheme. + * + * @param callback Performs key and provisioning requests. + * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument + * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @throws UnsupportedDrmException If the specified DRM scheme is not supported. + */ + public static DefaultDrmSessionManager newWidevineInstance( + MediaDrmCallback callback, HashMap optionalKeyRequestParameters, + Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException { + return newFrameworkInstance(C.WIDEVINE_UUID, callback, optionalKeyRequestParameters, + eventHandler, eventListener); + } + + /** + * Instantiates a new instance using the PlayReady scheme. + *

    + * Note that PlayReady is unsupported by most Android devices, with the exception of Android TV + * devices, which do provide support. + * + * @param callback Performs key and provisioning requests. + * @param customData Optional custom data to include in requests generated by the instance. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @throws UnsupportedDrmException If the specified DRM scheme is not supported. + */ + public static DefaultDrmSessionManager newPlayReadyInstance( + MediaDrmCallback callback, String customData, Handler eventHandler, + EventListener eventListener) throws UnsupportedDrmException { + HashMap optionalKeyRequestParameters; + if (!TextUtils.isEmpty(customData)) { + optionalKeyRequestParameters = new HashMap<>(); + optionalKeyRequestParameters.put(PLAYREADY_CUSTOM_DATA_KEY, customData); + } else { + optionalKeyRequestParameters = null; + } + return newFrameworkInstance(C.PLAYREADY_UUID, callback, optionalKeyRequestParameters, + eventHandler, eventListener); + } + + /** + * Instantiates a new instance. + * + * @param uuid The UUID of the drm scheme. + * @param callback Performs key and provisioning requests. + * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument + * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @throws UnsupportedDrmException If the specified DRM scheme is not supported. + */ + public static DefaultDrmSessionManager newFrameworkInstance( + UUID uuid, MediaDrmCallback callback, HashMap optionalKeyRequestParameters, + Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException { + return new DefaultDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), callback, + optionalKeyRequestParameters, eventHandler, eventListener); + } + + /** + * @param uuid The UUID of the drm scheme. + * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. + * @param callback Performs key and provisioning requests. + * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument + * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DefaultDrmSessionManager(UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, + HashMap optionalKeyRequestParameters, Handler eventHandler, + EventListener eventListener) { + this.uuid = uuid; + this.mediaDrm = mediaDrm; + this.callback = callback; + this.optionalKeyRequestParameters = optionalKeyRequestParameters; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + mediaDrm.setOnEventListener(new MediaDrmEventListener()); + state = STATE_CLOSED; + mode = MODE_PLAYBACK; + } + + /** + * Provides access to {@link MediaDrm#getPropertyString(String)}. + *

    + * This method may be called when the manager is in any state. + * + * @param key The key to request. + * @return The retrieved property. + */ + public final String getPropertyString(String key) { + return mediaDrm.getPropertyString(key); + } + + /** + * Provides access to {@link MediaDrm#setPropertyString(String, String)}. + *

    + * This method may be called when the manager is in any state. + * + * @param key The property to write. + * @param value The value to write. + */ + public final void setPropertyString(String key, String value) { + mediaDrm.setPropertyString(key, value); + } + + /** + * Provides access to {@link MediaDrm#getPropertyByteArray(String)}. + *

    + * This method may be called when the manager is in any state. + * + * @param key The key to request. + * @return The retrieved property. + */ + public final byte[] getPropertyByteArray(String key) { + return mediaDrm.getPropertyByteArray(key); + } + + /** + * Provides access to {@link MediaDrm#setPropertyByteArray(String, byte[])}. + *

    + * This method may be called when the manager is in any state. + * + * @param key The property to write. + * @param value The value to write. + */ + public final void setPropertyByteArray(String key, byte[] value) { + mediaDrm.setPropertyByteArray(key, value); + } + + /** + * Sets the mode, which determines the role of sessions acquired from the instance. This must be + * called before {@link #acquireSession(Looper, DrmInitData)} is called. + * + *

    By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when + * required. + * + *

    {@code mode} must be one of these: + *

      + *
    • {@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is + * requested otherwise the offline license is restored. + *
    • {@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license + * is restored. + *
    • {@link #MODE_DOWNLOAD}: If {@code offlineLicenseKeySetId} is null, an offline license is + * requested otherwise the offline license is renewed. + *
    • {@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline license + * is released. + *
    + * + * @param mode The mode to be set. + * @param offlineLicenseKeySetId The key set id of the license to be used with the given mode. + */ + public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) { + Assertions.checkState(openCount == 0); + if (mode == MODE_QUERY || mode == MODE_RELEASE) { + Assertions.checkNotNull(offlineLicenseKeySetId); + } + this.mode = mode; + this.offlineLicenseKeySetId = offlineLicenseKeySetId; + } + + // DrmSessionManager implementation. + + @Override + public DrmSession acquireSession(Looper playbackLooper, DrmInitData drmInitData) { + Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper); + if (++openCount != 1) { + return this; + } + + if (this.playbackLooper == null) { + this.playbackLooper = playbackLooper; + mediaDrmHandler = new MediaDrmHandler(playbackLooper); + postResponseHandler = new PostResponseHandler(playbackLooper); + } + + requestHandlerThread = new HandlerThread("DrmRequestHandler"); + requestHandlerThread.start(); + postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper()); + + if (offlineLicenseKeySetId == null) { + SchemeData schemeData = drmInitData.get(uuid); + if (schemeData == null) { + onError(new IllegalStateException("Media does not support uuid: " + uuid)); + return this; + } + schemeInitData = schemeData.data; + schemeMimeType = schemeData.mimeType; + if (Util.SDK_INT < 21) { + // Prior to L the Widevine CDM required data to be extracted from the PSSH atom. + byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeInitData, C.WIDEVINE_UUID); + if (psshData == null) { + // Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged. + } else { + schemeInitData = psshData; + } + } + if (Util.SDK_INT < 26 && C.CLEARKEY_UUID.equals(uuid) + && (MimeTypes.VIDEO_MP4.equals(schemeMimeType) + || MimeTypes.AUDIO_MP4.equals(schemeMimeType))) { + // Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4. + schemeMimeType = CENC_SCHEME_MIME_TYPE; + } + } + state = STATE_OPENING; + openInternal(true); + return this; + } + + @Override + public void releaseSession(DrmSession session) { + if (--openCount != 0) { + return; + } + state = STATE_CLOSED; + provisioningInProgress = false; + mediaDrmHandler.removeCallbacksAndMessages(null); + postResponseHandler.removeCallbacksAndMessages(null); + postRequestHandler.removeCallbacksAndMessages(null); + postRequestHandler = null; + requestHandlerThread.quit(); + requestHandlerThread = null; + schemeInitData = null; + schemeMimeType = null; + mediaCrypto = null; + lastException = null; + if (sessionId != null) { + mediaDrm.closeSession(sessionId); + sessionId = null; + } + } + + // DrmSession implementation. + + @Override + @DrmSession.State + public final int getState() { + return state; + } + + @Override + public final T getMediaCrypto() { + if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { + throw new IllegalStateException(); + } + return mediaCrypto; + } + + @Override + public boolean requiresSecureDecoderComponent(String mimeType) { + if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { + throw new IllegalStateException(); + } + return mediaCrypto.requiresSecureDecoderComponent(mimeType); + } + + @Override + public final DrmSessionException getError() { + return state == STATE_ERROR ? lastException : null; + } + + @Override + public Map queryKeyStatus() { + // User may call this method rightfully even if state == STATE_ERROR. So only check if there is + // a sessionId + if (sessionId == null) { + throw new IllegalStateException(); + } + return mediaDrm.queryKeyStatus(sessionId); + } + + @Override + public byte[] getOfflineLicenseKeySetId() { + return offlineLicenseKeySetId; + } + + // Internal methods. + + private void openInternal(boolean allowProvisioning) { + try { + sessionId = mediaDrm.openSession(); + mediaCrypto = mediaDrm.createMediaCrypto(uuid, sessionId); + state = STATE_OPENED; + doLicense(); + } catch (NotProvisionedException e) { + if (allowProvisioning) { + postProvisionRequest(); + } else { + onError(e); + } + } catch (Exception e) { + onError(e); + } + } + + private void postProvisionRequest() { + if (provisioningInProgress) { + return; + } + provisioningInProgress = true; + ProvisionRequest request = mediaDrm.getProvisionRequest(); + postRequestHandler.obtainMessage(MSG_PROVISION, request).sendToTarget(); + } + + private void onProvisionResponse(Object response) { + provisioningInProgress = false; + if (state != STATE_OPENING && state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { + // This event is stale. + return; + } + + if (response instanceof Exception) { + onError((Exception) response); + return; + } + + try { + mediaDrm.provideProvisionResponse((byte[]) response); + if (state == STATE_OPENING) { + openInternal(false); + } else { + doLicense(); + } + } catch (DeniedByServerException e) { + onError(e); + } + } + + private void doLicense() { + switch (mode) { + case MODE_PLAYBACK: + case MODE_QUERY: + if (offlineLicenseKeySetId == null) { + postKeyRequest(sessionId, MediaDrm.KEY_TYPE_STREAMING); + } else { + if (restoreKeys()) { + long licenseDurationRemainingSec = getLicenseDurationRemainingSec(); + if (mode == MODE_PLAYBACK + && licenseDurationRemainingSec <= MAX_LICENSE_DURATION_TO_RENEW) { + postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE); + } else if (licenseDurationRemainingSec <= 0) { + onError(new KeysExpiredException()); + } else { + state = STATE_OPENED_WITH_KEYS; + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onDrmKeysRestored(); + } + }); + } + } + } + } + break; + case MODE_DOWNLOAD: + if (offlineLicenseKeySetId == null) { + postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE); + } else { + // Renew + if (restoreKeys()) { + postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE); + } + } + break; + case MODE_RELEASE: + if (restoreKeys()) { + postKeyRequest(offlineLicenseKeySetId, MediaDrm.KEY_TYPE_RELEASE); + } + break; + } + } + + private boolean restoreKeys() { + try { + mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId); + return true; + } catch (Exception e) { + onError(e); + } + return false; + } + + private long getLicenseDurationRemainingSec() { + if (!C.WIDEVINE_UUID.equals(uuid)) { + return Long.MAX_VALUE; + } + Pair pair = WidevineUtil.getLicenseDurationRemainingSec(this); + return Math.min(pair.first, pair.second); + } + + private void postKeyRequest(byte[] scope, int keyType) { + try { + KeyRequest keyRequest = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, keyType, + optionalKeyRequestParameters); + postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); + } catch (Exception e) { + onKeysError(e); + } + } + + private void onKeyResponse(Object response) { + if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { + // This event is stale. + return; + } + + if (response instanceof Exception) { + onKeysError((Exception) response); + return; + } + + try { + if (mode == MODE_RELEASE) { + mediaDrm.provideKeyResponse(offlineLicenseKeySetId, (byte[]) response); + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onDrmKeysRemoved(); + } + }); + } + } else { + byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, (byte[]) response); + if ((mode == MODE_DOWNLOAD || (mode == MODE_PLAYBACK && offlineLicenseKeySetId != null)) + && keySetId != null && keySetId.length != 0) { + offlineLicenseKeySetId = keySetId; + } + state = STATE_OPENED_WITH_KEYS; + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onDrmKeysLoaded(); + } + }); + } + } + } catch (Exception e) { + onKeysError(e); + } + } + + private void onKeysError(Exception e) { + if (e instanceof NotProvisionedException) { + postProvisionRequest(); + } else { + onError(e); + } + } + + private void onError(final Exception e) { + lastException = new DrmSessionException(e); + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onDrmSessionManagerError(e); + } + }); + } + if (state != STATE_OPENED_WITH_KEYS) { + state = STATE_ERROR; + } + } + + @SuppressLint("HandlerLeak") + private class MediaDrmHandler extends Handler { + + public MediaDrmHandler(Looper looper) { + super(looper); + } + + @SuppressWarnings("deprecation") + @Override + public void handleMessage(Message msg) { + if (openCount == 0 || (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS)) { + return; + } + switch (msg.what) { + case MediaDrm.EVENT_KEY_REQUIRED: + doLicense(); + break; + case MediaDrm.EVENT_KEY_EXPIRED: + // When an already expired key is loaded MediaDrm sends this event immediately. Ignore + // this event if the state isn't STATE_OPENED_WITH_KEYS yet which means we're still + // waiting for key response. + if (state == STATE_OPENED_WITH_KEYS) { + state = STATE_OPENED; + onError(new KeysExpiredException()); + } + break; + case MediaDrm.EVENT_PROVISION_REQUIRED: + state = STATE_OPENED; + postProvisionRequest(); + break; + } + } + + } + + private class MediaDrmEventListener implements OnEventListener { + + @Override + public void onEvent(ExoMediaDrm md, byte[] sessionId, int event, int extra, + byte[] data) { + if (mode == MODE_PLAYBACK) { + mediaDrmHandler.sendEmptyMessage(event); + } + } + + } + + @SuppressLint("HandlerLeak") + private class PostResponseHandler extends Handler { + + public PostResponseHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PROVISION: + onProvisionResponse(msg.obj); + break; + case MSG_KEYS: + onKeyResponse(msg.obj); + break; + } + } + + } + + @SuppressLint("HandlerLeak") + private class PostRequestHandler extends Handler { + + public PostRequestHandler(Looper backgroundLooper) { + super(backgroundLooper); + } + + @Override + public void handleMessage(Message msg) { + Object response; + try { + switch (msg.what) { + case MSG_PROVISION: + response = callback.executeProvisionRequest(uuid, (ProvisionRequest) msg.obj); + break; + case MSG_KEYS: + response = callback.executeKeyRequest(uuid, (KeyRequest) msg.obj); + break; + default: + throw new RuntimeException(); + } + } catch (Exception e) { + response = e; + } + postResponseHandler.obtainMessage(msg.what, response).sendToTarget(); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmInitData.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmInitData.java new file mode 100644 index 0000000..eb21f9f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmInitData.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData.SchemeData; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +/** + * Initialization data for one or more DRM schemes. + */ +public final class DrmInitData implements Comparator, Parcelable { + + private final SchemeData[] schemeDatas; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * Number of {@link SchemeData}s. + */ + public final int schemeDataCount; + + /** + * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. + */ + public DrmInitData(List schemeDatas) { + this(false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); + } + + /** + * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. + */ + public DrmInitData(SchemeData... schemeDatas) { + this(true, schemeDatas); + } + + private DrmInitData(boolean cloneSchemeDatas, SchemeData... schemeDatas) { + if (cloneSchemeDatas) { + schemeDatas = schemeDatas.clone(); + } + // Sorting ensures that universal scheme data(i.e. data that applies to all schemes) is matched + // last. It's also required by the equals and hashcode implementations. + Arrays.sort(schemeDatas, this); + // Check for no duplicates. + for (int i = 1; i < schemeDatas.length; i++) { + if (schemeDatas[i - 1].uuid.equals(schemeDatas[i].uuid)) { + throw new IllegalArgumentException("Duplicate data for uuid: " + schemeDatas[i].uuid); + } + } + this.schemeDatas = schemeDatas; + schemeDataCount = schemeDatas.length; + } + + /* package */ DrmInitData(Parcel in) { + schemeDatas = in.createTypedArray(SchemeData.CREATOR); + schemeDataCount = schemeDatas.length; + } + + /** + * Retrieves data for a given DRM scheme, specified by its UUID. + * + * @param uuid The DRM scheme's UUID. + * @return The initialization data for the scheme, or null if the scheme is not supported. + */ + public SchemeData get(UUID uuid) { + for (SchemeData schemeData : schemeDatas) { + if (schemeData.matches(uuid)) { + return schemeData; + } + } + return null; + } + + /** + * Retrieves the {@link SchemeData} at a given index. + * + * @param index index of the scheme to return. + * @return The {@link SchemeData} at the index. + */ + public SchemeData get(int index) { + return schemeDatas[index]; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Arrays.hashCode(schemeDatas); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return Arrays.equals(schemeDatas, ((DrmInitData) obj).schemeDatas); + } + + @Override + public int compare(SchemeData first, SchemeData second) { + return C.UUID_NIL.equals(first.uuid) ? (C.UUID_NIL.equals(second.uuid) ? 0 : 1) + : first.uuid.compareTo(second.uuid); + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedArray(schemeDatas, 0); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public DrmInitData createFromParcel(Parcel in) { + return new DrmInitData(in); + } + + @Override + public DrmInitData[] newArray(int size) { + return new DrmInitData[size]; + } + + }; + + /** + * Scheme initialization data. + */ + public static final class SchemeData implements Parcelable { + + // Lazily initialized hashcode. + private int hashCode; + + /** + * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e. + * applies to all schemes). + */ + private final UUID uuid; + /** + * The mimeType of {@link #data}. + */ + public final String mimeType; + /** + * The initialization data. + */ + public final byte[] data; + /** + * Whether secure decryption is required. + */ + public final boolean requiresSecureDecryption; + + /** + * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is + * universal (i.e. applies to all schemes). + * @param mimeType The mimeType of the initialization data. + * @param data The initialization data. + */ + public SchemeData(UUID uuid, String mimeType, byte[] data) { + this(uuid, mimeType, data, false); + } + + /** + * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is + * universal (i.e. applies to all schemes). + * @param mimeType The mimeType of the initialization data. + * @param data The initialization data. + * @param requiresSecureDecryption Whether secure decryption is required. + */ + public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) { + this.uuid = Assertions.checkNotNull(uuid); + this.mimeType = Assertions.checkNotNull(mimeType); + this.data = Assertions.checkNotNull(data); + this.requiresSecureDecryption = requiresSecureDecryption; + } + + /* package */ SchemeData(Parcel in) { + uuid = new UUID(in.readLong(), in.readLong()); + mimeType = in.readString(); + data = in.createByteArray(); + requiresSecureDecryption = in.readByte() != 0; + } + + /** + * Returns whether this initialization data applies to the specified scheme. + * + * @param schemeUuid The scheme {@link UUID}. + * @return Whether this initialization data applies to the specified scheme. + */ + public boolean matches(UUID schemeUuid) { + return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SchemeData)) { + return false; + } + if (obj == this) { + return true; + } + SchemeData other = (SchemeData) obj; + return mimeType.equals(other.mimeType) && Util.areEqual(uuid, other.uuid) + && Arrays.equals(data, other.data); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = uuid.hashCode(); + result = 31 * result + mimeType.hashCode(); + result = 31 * result + Arrays.hashCode(data); + hashCode = result; + } + return hashCode; + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(uuid.getMostSignificantBits()); + dest.writeLong(uuid.getLeastSignificantBits()); + dest.writeString(mimeType); + dest.writeByteArray(data); + dest.writeByte((byte) (requiresSecureDecryption ? 1 : 0)); + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SchemeData createFromParcel(Parcel in) { + return new SchemeData(in); + } + + @Override + public SchemeData[] newArray(int size) { + return new SchemeData[size]; + } + + }; + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmSession.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmSession.java new file mode 100644 index 0000000..a871a4b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmSession.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.annotation.TargetApi; +import android.media.MediaDrm; +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; + +/** + * A DRM session. + */ +@TargetApi(16) +public interface DrmSession { + + /** Wraps the exception which is the cause of the error state. */ + class DrmSessionException extends Exception { + + public DrmSessionException(Exception e) { + super(e); + } + + } + + /** + * The state of the DRM session. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) + @interface State {} + /** + * The session has encountered an error. {@link #getError()} can be used to retrieve the cause. + */ + int STATE_ERROR = 0; + /** + * The session is closed. + */ + int STATE_CLOSED = 1; + /** + * The session is being opened. + */ + int STATE_OPENING = 2; + /** + * The session is open, but does not yet have the keys required for decryption. + */ + int STATE_OPENED = 3; + /** + * The session is open and has the keys required for decryption. + */ + int STATE_OPENED_WITH_KEYS = 4; + + /** + * Returns the current state of the session. + * + * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING}, + * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. + */ + @State int getState(); + + /** + * Returns a {@link ExoMediaCrypto} for the open session. + *

    + * This method may be called when the session is in the following states: + * {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS} + * + * @return A {@link ExoMediaCrypto} for the open session. + * @throws IllegalStateException If called when a session isn't opened. + */ + T getMediaCrypto(); + + /** + * Whether the session requires a secure decoder for the specified mime type. + *

    + * Normally this method should return + * {@link ExoMediaCrypto#requiresSecureDecoderComponent(String)}, however in some cases + * implementations may wish to modify the return value (i.e. to force a secure decoder even when + * one is not required). + *

    + * This method may be called when the session is in the following states: + * {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS} + * + * @return Whether the open session requires a secure decoder for the specified mime type. + * @throws IllegalStateException If called when a session isn't opened. + */ + boolean requiresSecureDecoderComponent(String mimeType); + + /** + * Returns the cause of the error state. + *

    + * This method may be called when the session is in any state. + * + * @return An exception if the state is {@link #STATE_ERROR}. Null otherwise. + */ + DrmSessionException getError(); + + /** + * Returns an informative description of the key status for the session. The status is in the form + * of {name, value} pairs. + * + *

    Since DRM license policies vary by vendor, the specific status field names are determined by + * each DRM vendor. Refer to your DRM provider documentation for definitions of the field names + * for a particular DRM engine plugin. + * + * @return A map of key status. + * @throws IllegalStateException If called when the session isn't opened. + * @see MediaDrm#queryKeyStatus(byte[]) + */ + Map queryKeyStatus(); + + /** + * Returns the key set id of the offline license loaded into this session, if there is one. Null + * otherwise. + */ + byte[] getOfflineLicenseKeySetId(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmSessionManager.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmSessionManager.java new file mode 100644 index 0000000..bafb404 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/DrmSessionManager.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.annotation.TargetApi; +import android.os.Looper; + +/** + * Manages a DRM session. + */ +@TargetApi(16) +public interface DrmSessionManager { + + /** + * Acquires a {@link DrmSession} for the specified {@link DrmInitData}. The {@link DrmSession} + * must be returned to {@link #releaseSession(DrmSession)} when it is no longer required. + * + * @param playbackLooper The looper associated with the media playback thread. + * @param drmInitData DRM initialization data. + * @return The DRM session. + */ + DrmSession acquireSession(Looper playbackLooper, DrmInitData drmInitData); + + /** + * Releases a {@link DrmSession}. + */ + void releaseSession(DrmSession drmSession); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/ExoMediaCrypto.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/ExoMediaCrypto.java new file mode 100644 index 0000000..143cb39 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/ExoMediaCrypto.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +/** + * An opaque {@link android.media.MediaCrypto} equivalent. + */ +public interface ExoMediaCrypto { + + /** + * @see android.media.MediaCrypto#requiresSecureDecoderComponent(String) + */ + boolean requiresSecureDecoderComponent(String mimeType); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/ExoMediaDrm.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/ExoMediaDrm.java new file mode 100644 index 0000000..8a866d3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/ExoMediaDrm.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.media.DeniedByServerException; +import android.media.MediaCryptoException; +import android.media.MediaDrm; +import android.media.NotProvisionedException; +import android.media.ResourceBusyException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Used to obtain keys for decrypting protected media streams. See {@link android.media.MediaDrm}. + */ +public interface ExoMediaDrm { + + /** + * @see android.media.MediaDrm.OnEventListener + */ + interface OnEventListener { + /** + * Called when an event occurs that requires the app to be notified + * + * @param mediaDrm the {@link ExoMediaDrm} object on which the event occurred. + * @param sessionId the DRM session ID on which the event occurred + * @param event indicates the event type + * @param extra an secondary error code + * @param data optional byte array of data that may be associated with the event + */ + void onEvent(ExoMediaDrm mediaDrm, byte[] sessionId, int event, int extra, + byte[] data); + } + + /** + * @see android.media.MediaDrm.KeyRequest + */ + interface KeyRequest { + byte[] getData(); + String getDefaultUrl(); + } + + /** + * @see android.media.MediaDrm.ProvisionRequest + */ + interface ProvisionRequest { + byte[] getData(); + String getDefaultUrl(); + } + + /** + * @see MediaDrm#setOnEventListener(MediaDrm.OnEventListener) + */ + void setOnEventListener(OnEventListener listener); + + /** + * @see MediaDrm#openSession() + */ + byte[] openSession() throws NotProvisionedException, ResourceBusyException; + + /** + * @see MediaDrm#closeSession(byte[]) + */ + void closeSession(byte[] sessionId); + + /** + * @see MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap) + */ + KeyRequest getKeyRequest(byte[] scope, byte[] init, String mimeType, int keyType, + HashMap optionalParameters) throws NotProvisionedException; + + /** + * @see MediaDrm#provideKeyResponse(byte[], byte[]) + */ + byte[] provideKeyResponse(byte[] scope, byte[] response) + throws NotProvisionedException, DeniedByServerException; + + /** + * @see MediaDrm#getProvisionRequest() + */ + ProvisionRequest getProvisionRequest(); + + /** + * @see MediaDrm#provideProvisionResponse(byte[]) + */ + void provideProvisionResponse(byte[] response) throws DeniedByServerException; + + /** + * @see MediaDrm#queryKeyStatus(byte[]) + */ + Map queryKeyStatus(byte[] sessionId); + + /** + * @see MediaDrm#release() + */ + void release(); + + /** + * @see MediaDrm#restoreKeys(byte[], byte[]) + */ + void restoreKeys(byte[] sessionId, byte[] keySetId); + + /** + * @see MediaDrm#getPropertyString(String) + */ + String getPropertyString(String propertyName); + + /** + * @see MediaDrm#getPropertyByteArray(String) + */ + byte[] getPropertyByteArray(String propertyName); + + /** + * @see MediaDrm#setPropertyString(String, String) + */ + void setPropertyString(String propertyName, String value); + + /** + * @see MediaDrm#setPropertyByteArray(String, byte[]) + */ + void setPropertyByteArray(String propertyName, byte[] value); + + /** + * @see android.media.MediaCrypto#MediaCrypto(UUID, byte[]) + * + * @param uuid The UUID of the crypto scheme. + * @param initData Opaque initialization data specific to the crypto scheme. + * @return An object extends {@link ExoMediaCrypto}, using opaque crypto scheme specific data. + * @throws MediaCryptoException + */ + T createMediaCrypto(UUID uuid, byte[] initData) throws MediaCryptoException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/FrameworkMediaCrypto.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/FrameworkMediaCrypto.java new file mode 100644 index 0000000..001cbf4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/FrameworkMediaCrypto.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.annotation.TargetApi; +import android.media.MediaCrypto; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * An {@link ExoMediaCrypto} implementation that wraps the framework {@link MediaCrypto}. + */ +@TargetApi(16) +public final class FrameworkMediaCrypto implements ExoMediaCrypto { + + private final MediaCrypto mediaCrypto; + + /* package */ FrameworkMediaCrypto(MediaCrypto mediaCrypto) { + this.mediaCrypto = Assertions.checkNotNull(mediaCrypto); + } + + public MediaCrypto getWrappedMediaCrypto() { + return mediaCrypto; + } + + @Override + public boolean requiresSecureDecoderComponent(String mimeType) { + return mediaCrypto.requiresSecureDecoderComponent(mimeType); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/FrameworkMediaDrm.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/FrameworkMediaDrm.java new file mode 100644 index 0000000..ee35ddf --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/FrameworkMediaDrm.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.annotation.TargetApi; +import android.media.DeniedByServerException; +import android.media.MediaCrypto; +import android.media.MediaCryptoException; +import android.media.MediaDrm; +import android.media.NotProvisionedException; +import android.media.ResourceBusyException; +import android.media.UnsupportedSchemeException; +import android.support.annotation.NonNull; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * An {@link ExoMediaDrm} implementation that wraps the framework {@link MediaDrm}. + */ +@TargetApi(18) +public final class FrameworkMediaDrm implements ExoMediaDrm { + + private final MediaDrm mediaDrm; + + /** + * Creates an instance for the specified scheme UUID. + * + * @param uuid The scheme uuid. + * @return The created instance. + * @throws UnsupportedDrmException If the DRM scheme is unsupported or cannot be instantiated. + */ + public static FrameworkMediaDrm newInstance(UUID uuid) throws UnsupportedDrmException { + try { + return new FrameworkMediaDrm(uuid); + } catch (UnsupportedSchemeException e) { + throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME, e); + } catch (Exception e) { + throw new UnsupportedDrmException(UnsupportedDrmException.REASON_INSTANTIATION_ERROR, e); + } + } + + private FrameworkMediaDrm(UUID uuid) throws UnsupportedSchemeException { + this.mediaDrm = new MediaDrm(Assertions.checkNotNull(uuid)); + } + + @Override + public void setOnEventListener( + final ExoMediaDrm.OnEventListener listener) { + mediaDrm.setOnEventListener(listener == null ? null : new MediaDrm.OnEventListener() { + @Override + public void onEvent(@NonNull MediaDrm md, byte[] sessionId, int event, int extra, + byte[] data) { + listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data); + } + }); + } + + @Override + public byte[] openSession() throws NotProvisionedException, ResourceBusyException { + return mediaDrm.openSession(); + } + + @Override + public void closeSession(byte[] sessionId) { + mediaDrm.closeSession(sessionId); + } + + @Override + public KeyRequest getKeyRequest(byte[] scope, byte[] init, String mimeType, int keyType, + HashMap optionalParameters) throws NotProvisionedException { + final MediaDrm.KeyRequest request = mediaDrm.getKeyRequest(scope, init, mimeType, keyType, + optionalParameters); + return new KeyRequest() { + @Override + public byte[] getData() { + return request.getData(); + } + + @Override + public String getDefaultUrl() { + return request.getDefaultUrl(); + } + }; + } + + @Override + public byte[] provideKeyResponse(byte[] scope, byte[] response) + throws NotProvisionedException, DeniedByServerException { + return mediaDrm.provideKeyResponse(scope, response); + } + + @Override + public ProvisionRequest getProvisionRequest() { + final MediaDrm.ProvisionRequest provisionRequest = mediaDrm.getProvisionRequest(); + return new ProvisionRequest() { + @Override + public byte[] getData() { + return provisionRequest.getData(); + } + + @Override + public String getDefaultUrl() { + return provisionRequest.getDefaultUrl(); + } + }; + } + + @Override + public void provideProvisionResponse(byte[] response) throws DeniedByServerException { + mediaDrm.provideProvisionResponse(response); + } + + @Override + public Map queryKeyStatus(byte[] sessionId) { + return mediaDrm.queryKeyStatus(sessionId); + } + + @Override + public void release() { + mediaDrm.release(); + } + + @Override + public void restoreKeys(byte[] sessionId, byte[] keySetId) { + mediaDrm.restoreKeys(sessionId, keySetId); + } + + @Override + public String getPropertyString(String propertyName) { + return mediaDrm.getPropertyString(propertyName); + } + + @Override + public byte[] getPropertyByteArray(String propertyName) { + return mediaDrm.getPropertyByteArray(propertyName); + } + + @Override + public void setPropertyString(String propertyName, String value) { + mediaDrm.setPropertyString(propertyName, value); + } + + @Override + public void setPropertyByteArray(String propertyName, byte[] value) { + mediaDrm.setPropertyByteArray(propertyName, value); + } + + @Override + public FrameworkMediaCrypto createMediaCrypto(UUID uuid, byte[] initData) + throws MediaCryptoException { + return new FrameworkMediaCrypto(new MediaCrypto(uuid, initData)); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/HttpMediaDrmCallback.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/HttpMediaDrmCallback.java new file mode 100644 index 0000000..4583016 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/HttpMediaDrmCallback.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.annotation.TargetApi; +import android.net.Uri; +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.ExoMediaDrm.KeyRequest; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSourceInputStream; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource.Factory; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * A {@link MediaDrmCallback} that makes requests using {@link HttpDataSource} instances. + */ +@TargetApi(18) +public final class HttpMediaDrmCallback implements MediaDrmCallback { + + private static final Map PLAYREADY_KEY_REQUEST_PROPERTIES; + static { + PLAYREADY_KEY_REQUEST_PROPERTIES = new HashMap<>(); + PLAYREADY_KEY_REQUEST_PROPERTIES.put("Content-Type", "text/xml"); + PLAYREADY_KEY_REQUEST_PROPERTIES.put("SOAPAction", + "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"); + } + + private final HttpDataSource.Factory dataSourceFactory; + private final String defaultUrl; + private final Map keyRequestProperties; + + /** + * @param defaultUrl The default license URL. + * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. + */ + public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory) { + this(defaultUrl, dataSourceFactory, null); + } + + /** + * @deprecated Use {@link HttpMediaDrmCallback#HttpMediaDrmCallback(String, Factory)}. Request + * properties can be set by calling {@link #setKeyRequestProperty(String, String)}. + * @param defaultUrl The default license URL. + * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. + * @param keyRequestProperties Request properties to set when making key requests, or null. + */ + @Deprecated + public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory, + Map keyRequestProperties) { + this.dataSourceFactory = dataSourceFactory; + this.defaultUrl = defaultUrl; + this.keyRequestProperties = new HashMap<>(); + if (keyRequestProperties != null) { + this.keyRequestProperties.putAll(keyRequestProperties); + } + } + + /** + * Sets a header for key requests made by the callback. + * + * @param name The name of the header field. + * @param value The value of the field. + */ + public void setKeyRequestProperty(String name, String value) { + Assertions.checkNotNull(name); + Assertions.checkNotNull(value); + synchronized (keyRequestProperties) { + keyRequestProperties.put(name, value); + } + } + + /** + * Clears a header for key requests made by the callback. + * + * @param name The name of the header field. + */ + public void clearKeyRequestProperty(String name) { + Assertions.checkNotNull(name); + synchronized (keyRequestProperties) { + keyRequestProperties.remove(name); + } + } + + /** + * Clears all headers for key requests made by the callback. + */ + public void clearAllKeyRequestProperties() { + synchronized (keyRequestProperties) { + keyRequestProperties.clear(); + } + } + + @Override + public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { + String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); + return executePost(dataSourceFactory, url, new byte[0], null); + } + + @Override + public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception { + String url = request.getDefaultUrl(); + if (TextUtils.isEmpty(url)) { + url = defaultUrl; + } + Map requestProperties = new HashMap<>(); + requestProperties.put("Content-Type", "application/octet-stream"); + if (C.PLAYREADY_UUID.equals(uuid)) { + requestProperties.putAll(PLAYREADY_KEY_REQUEST_PROPERTIES); + } + synchronized (keyRequestProperties) { + requestProperties.putAll(keyRequestProperties); + } + return executePost(dataSourceFactory, url, request.getData(), requestProperties); + } + + private static byte[] executePost(HttpDataSource.Factory dataSourceFactory, String url, + byte[] data, Map requestProperties) throws IOException { + HttpDataSource dataSource = dataSourceFactory.createDataSource(); + if (requestProperties != null) { + for (Map.Entry requestProperty : requestProperties.entrySet()) { + dataSource.setRequestProperty(requestProperty.getKey(), requestProperty.getValue()); + } + } + DataSpec dataSpec = new DataSpec(Uri.parse(url), data, 0, 0, C.LENGTH_UNSET, null, + DataSpec.FLAG_ALLOW_GZIP); + DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); + try { + return Util.toByteArray(inputStream); + } finally { + Util.closeQuietly(inputStream); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/KeysExpiredException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/KeysExpiredException.java new file mode 100644 index 0000000..fe2b4a9 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/KeysExpiredException.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +/** + * Thrown when the drm keys loaded into an open session expire. + */ +public final class KeysExpiredException extends Exception { +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/MediaDrmCallback.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/MediaDrmCallback.java new file mode 100644 index 0000000..25370d3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/MediaDrmCallback.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import com.tangxiaolv.telegramgallery.exoplayer2.drm.ExoMediaDrm.KeyRequest; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; +import java.util.UUID; + +/** + * Performs {@link ExoMediaDrm} key and provisioning requests. + */ +public interface MediaDrmCallback { + + /** + * Executes a provisioning request. + * + * @param uuid The UUID of the content protection scheme. + * @param request The request. + * @return The response data. + * @throws Exception If an error occurred executing the request. + */ + byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws Exception; + + /** + * Executes a key request. + * + * @param uuid The UUID of the content protection scheme. + * @param request The request. + * @return The response data. + * @throws Exception If an error occurred executing the request. + */ + byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/OfflineLicenseHelper.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/OfflineLicenseHelper.java new file mode 100644 index 0000000..0fcf93d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/OfflineLicenseHelper.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.media.MediaDrm; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DefaultDrmSessionManager.EventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DefaultDrmSessionManager.Mode; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSession.DrmSessionException; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource.Factory; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.HashMap; + +/** + * Helper class to download, renew and release offline licenses. + */ +public final class OfflineLicenseHelper { + + private final ConditionVariable conditionVariable; + private final DefaultDrmSessionManager drmSessionManager; + private final HandlerThread handlerThread; + + /** + * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance + * is no longer required. + * + * @param licenseUrl The default license URL. + * @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. + * @return A new instance which uses Widevine CDM. + * @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be + * instantiated. + */ + public static OfflineLicenseHelper newWidevineInstance( + String licenseUrl, Factory httpDataSourceFactory) throws UnsupportedDrmException { + return newWidevineInstance( + new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory), null); + } + + /** + * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance + * is no longer required. + * + * @param callback Performs key and provisioning requests. + * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument + * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. + * @return A new instance which uses Widevine CDM. + * @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be + * instantiated. + * @see DefaultDrmSessionManager#DefaultDrmSessionManager(java.util.UUID, ExoMediaDrm, + * MediaDrmCallback, HashMap, Handler, EventListener) + */ + public static OfflineLicenseHelper newWidevineInstance( + MediaDrmCallback callback, HashMap optionalKeyRequestParameters) + throws UnsupportedDrmException { + return new OfflineLicenseHelper<>(FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID), callback, + optionalKeyRequestParameters); + } + + /** + * Constructs an instance. Call {@link #release()} when the instance is no longer required. + * + * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. + * @param callback Performs key and provisioning requests. + * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument + * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. + * @see DefaultDrmSessionManager#DefaultDrmSessionManager(java.util.UUID, ExoMediaDrm, + * MediaDrmCallback, HashMap, Handler, EventListener) + */ + public OfflineLicenseHelper(ExoMediaDrm mediaDrm, MediaDrmCallback callback, + HashMap optionalKeyRequestParameters) { + handlerThread = new HandlerThread("OfflineLicenseHelper"); + handlerThread.start(); + conditionVariable = new ConditionVariable(); + EventListener eventListener = new EventListener() { + @Override + public void onDrmKeysLoaded() { + conditionVariable.open(); + } + + @Override + public void onDrmSessionManagerError(Exception e) { + conditionVariable.open(); + } + + @Override + public void onDrmKeysRestored() { + conditionVariable.open(); + } + + @Override + public void onDrmKeysRemoved() { + conditionVariable.open(); + } + }; + drmSessionManager = new DefaultDrmSessionManager<>(C.WIDEVINE_UUID, mediaDrm, callback, + optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener); + } + + /** Releases the helper. Should be called when the helper is no longer required. */ + public void release() { + handlerThread.quit(); + } + + /** + * Downloads an offline license. + * + * @param drmInitData The {@link DrmInitData} for the content whose license is to be downloaded. + * @return The key set id for the downloaded license. + * @throws IOException If an error occurs reading data from the stream. + * @throws InterruptedException If the thread has been interrupted. + * @throws DrmSessionException Thrown when a DRM session error occurs. + */ + public synchronized byte[] downloadLicense(DrmInitData drmInitData) throws IOException, + InterruptedException, DrmSessionException { + Assertions.checkArgument(drmInitData != null); + return blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData); + } + + /** + * Renews an offline license. + * + * @param offlineLicenseKeySetId The key set id of the license to be renewed. + * @return The renewed offline license key set id. + * @throws DrmSessionException Thrown when a DRM session error occurs. + */ + public synchronized byte[] renewLicense(byte[] offlineLicenseKeySetId) + throws DrmSessionException { + Assertions.checkNotNull(offlineLicenseKeySetId); + return blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null); + } + + /** + * Releases an offline license. + * + * @param offlineLicenseKeySetId The key set id of the license to be released. + * @throws DrmSessionException Thrown when a DRM session error occurs. + */ + public synchronized void releaseLicense(byte[] offlineLicenseKeySetId) + throws DrmSessionException { + Assertions.checkNotNull(offlineLicenseKeySetId); + blockingKeyRequest(DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, null); + } + + /** + * Returns the remaining license and playback durations in seconds, for an offline license. + * + * @param offlineLicenseKeySetId The key set id of the license. + * @return The remaining license and playback durations, in seconds. + * @throws DrmSessionException Thrown when a DRM session error occurs. + */ + public synchronized Pair getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId) + throws DrmSessionException { + Assertions.checkNotNull(offlineLicenseKeySetId); + DrmSession drmSession = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY, + offlineLicenseKeySetId, null); + DrmSessionException error = drmSession.getError(); + Pair licenseDurationRemainingSec = + WidevineUtil.getLicenseDurationRemainingSec(drmSession); + drmSessionManager.releaseSession(drmSession); + if (error != null) { + if (error.getCause() instanceof KeysExpiredException) { + return Pair.create(0L, 0L); + } + throw error; + } + return licenseDurationRemainingSec; + } + + private byte[] blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, + DrmInitData drmInitData) throws DrmSessionException { + DrmSession drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId, + drmInitData); + DrmSessionException error = drmSession.getError(); + byte[] keySetId = drmSession.getOfflineLicenseKeySetId(); + drmSessionManager.releaseSession(drmSession); + if (error != null) { + throw error; + } + return keySetId; + } + + private DrmSession openBlockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, + DrmInitData drmInitData) { + drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId); + conditionVariable.close(); + DrmSession drmSession = drmSessionManager.acquireSession(handlerThread.getLooper(), + drmInitData); + // Block current thread until key loading is finished + conditionVariable.block(); + return drmSession; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/UnsupportedDrmException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/UnsupportedDrmException.java new file mode 100644 index 0000000..c02af42 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/UnsupportedDrmException.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Thrown when the requested DRM scheme is not supported. + */ +public final class UnsupportedDrmException extends Exception { + + /** + * The reason for the exception. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR}) + public @interface Reason {} + /** + * The requested DRM scheme is unsupported by the device. + */ + public static final int REASON_UNSUPPORTED_SCHEME = 1; + /** + * There device advertises support for the requested DRM scheme, but there was an error + * instantiating it. The cause can be retrieved using {@link #getCause()}. + */ + public static final int REASON_INSTANTIATION_ERROR = 2; + + /** + * Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. + */ + @Reason public final int reason; + + /** + * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. + */ + public UnsupportedDrmException(@Reason int reason) { + this.reason = reason; + } + + /** + * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. + * @param cause The cause of this exception. + */ + public UnsupportedDrmException(@Reason int reason, Exception cause) { + super(cause); + this.reason = reason; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/WidevineUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/WidevineUtil.java new file mode 100644 index 0000000..3d6867a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/drm/WidevineUtil.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.drm; + +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.util.Map; + +/** + * Utility methods for Widevine. + */ +public final class WidevineUtil { + + /** Widevine specific key status field name for the remaining license duration, in seconds. */ + public static final String PROPERTY_LICENSE_DURATION_REMAINING = "LicenseDurationRemaining"; + /** Widevine specific key status field name for the remaining playback duration, in seconds. */ + public static final String PROPERTY_PLAYBACK_DURATION_REMAINING = "PlaybackDurationRemaining"; + + private WidevineUtil() {} + + /** + * Returns license and playback durations remaining in seconds. + * + * @return A {@link Pair} consisting of the remaining license and playback durations in seconds. + * @throws IllegalStateException If called when a session isn't opened. + * @param drmSession + */ + public static Pair getLicenseDurationRemainingSec(DrmSession drmSession) { + Map keyStatus = drmSession.queryKeyStatus(); + return new Pair<>( + getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING), + getDurationRemainingSec(keyStatus, PROPERTY_PLAYBACK_DURATION_REMAINING)); + } + + private static long getDurationRemainingSec(Map keyStatus, String property) { + if (keyStatus != null) { + try { + String value = keyStatus.get(property); + if (value != null) { + return Long.parseLong(value); + } + } catch (NumberFormatException e) { + // do nothing. + e.getStackTrace(); + } + } + return C.TIME_UNSET; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ChunkIndex.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ChunkIndex.java new file mode 100644 index 0000000..cd28489 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ChunkIndex.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Defines chunks of samples within a media stream. + */ +public final class ChunkIndex implements SeekMap { + + /** + * The number of chunks. + */ + public final int length; + + /** + * The chunk sizes, in bytes. + */ + public final int[] sizes; + + /** + * The chunk byte offsets. + */ + public final long[] offsets; + + /** + * The chunk durations, in microseconds. + */ + public final long[] durationsUs; + + /** + * The start time of each chunk, in microseconds. + */ + public final long[] timesUs; + + private final long durationUs; + + /** + * @param sizes The chunk sizes, in bytes. + * @param offsets The chunk byte offsets. + * @param durationsUs The chunk durations, in microseconds. + * @param timesUs The start time of each chunk, in microseconds. + */ + public ChunkIndex(int[] sizes, long[] offsets, long[] durationsUs, long[] timesUs) { + this.sizes = sizes; + this.offsets = offsets; + this.durationsUs = durationsUs; + this.timesUs = timesUs; + length = sizes.length; + if (length > 0) { + durationUs = durationsUs[length - 1] + timesUs[length - 1]; + } else { + durationUs = 0; + } + } + + /** + * Obtains the index of the chunk corresponding to a given time. + * + * @param timeUs The time, in microseconds. + * @return The index of the corresponding chunk. + */ + public int getChunkIndex(long timeUs) { + return Util.binarySearchFloor(timesUs, timeUs, true, true); + } + + // SeekMap implementation. + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getDurationUs() { + return durationUs; + } + + @Override + public long getPosition(long timeUs) { + return offsets[getChunkIndex(timeUs)]; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultExtractorInput.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultExtractorInput.java new file mode 100644 index 0000000..3f493bf --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultExtractorInput.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; +import java.util.Arrays; + +/** + * An {@link ExtractorInput} that wraps a {@link DataSource}. + */ +public final class DefaultExtractorInput implements ExtractorInput { + + private static final int PEEK_MIN_FREE_SPACE_AFTER_RESIZE = 64 * 1024; + private static final int PEEK_MAX_FREE_SPACE = 512 * 1024; + private static final byte[] SCRATCH_SPACE = new byte[4096]; + + private final DataSource dataSource; + private final long streamLength; + + private long position; + private byte[] peekBuffer; + private int peekBufferPosition; + private int peekBufferLength; + + /** + * @param dataSource The wrapped {@link DataSource}. + * @param position The initial position in the stream. + * @param length The length of the stream, or {@link C#LENGTH_UNSET} if it is unknown. + */ + public DefaultExtractorInput(DataSource dataSource, long position, long length) { + this.dataSource = dataSource; + this.position = position; + this.streamLength = length; + peekBuffer = new byte[PEEK_MIN_FREE_SPACE_AFTER_RESIZE]; + } + + @Override + public int read(byte[] target, int offset, int length) throws IOException, InterruptedException { + int bytesRead = readFromPeekBuffer(target, offset, length); + if (bytesRead == 0) { + bytesRead = readFromDataSource(target, offset, length, 0, true); + } + commitBytesRead(bytesRead); + return bytesRead; + } + + @Override + public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + int bytesRead = readFromPeekBuffer(target, offset, length); + while (bytesRead < length && bytesRead != C.RESULT_END_OF_INPUT) { + bytesRead = readFromDataSource(target, offset, length, bytesRead, allowEndOfInput); + } + commitBytesRead(bytesRead); + return bytesRead != C.RESULT_END_OF_INPUT; + } + + @Override + public void readFully(byte[] target, int offset, int length) + throws IOException, InterruptedException { + readFully(target, offset, length, false); + } + + @Override + public int skip(int length) throws IOException, InterruptedException { + int bytesSkipped = skipFromPeekBuffer(length); + if (bytesSkipped == 0) { + bytesSkipped = + readFromDataSource(SCRATCH_SPACE, 0, Math.min(length, SCRATCH_SPACE.length), 0, true); + } + commitBytesRead(bytesSkipped); + return bytesSkipped; + } + + @Override + public boolean skipFully(int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + int bytesSkipped = skipFromPeekBuffer(length); + while (bytesSkipped < length && bytesSkipped != C.RESULT_END_OF_INPUT) { + bytesSkipped = readFromDataSource(SCRATCH_SPACE, -bytesSkipped, + Math.min(length, bytesSkipped + SCRATCH_SPACE.length), bytesSkipped, allowEndOfInput); + } + commitBytesRead(bytesSkipped); + return bytesSkipped != C.RESULT_END_OF_INPUT; + } + + @Override + public void skipFully(int length) throws IOException, InterruptedException { + skipFully(length, false); + } + + @Override + public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + if (!advancePeekPosition(length, allowEndOfInput)) { + return false; + } + System.arraycopy(peekBuffer, peekBufferPosition - length, target, offset, length); + return true; + } + + @Override + public void peekFully(byte[] target, int offset, int length) + throws IOException, InterruptedException { + peekFully(target, offset, length, false); + } + + @Override + public boolean advancePeekPosition(int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + ensureSpaceForPeek(length); + int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); + while (bytesPeeked < length) { + bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, + allowEndOfInput); + if (bytesPeeked == C.RESULT_END_OF_INPUT) { + return false; + } + } + peekBufferPosition += length; + peekBufferLength = Math.max(peekBufferLength, peekBufferPosition); + return true; + } + + @Override + public void advancePeekPosition(int length) throws IOException, InterruptedException { + advancePeekPosition(length, false); + } + + @Override + public void resetPeekPosition() { + peekBufferPosition = 0; + } + + @Override + public long getPeekPosition() { + return position + peekBufferPosition; + } + + @Override + public long getPosition() { + return position; + } + + @Override + public long getLength() { + return streamLength; + } + + @Override + public void setRetryPosition(long position, E e) throws E { + Assertions.checkArgument(position >= 0); + this.position = position; + throw e; + } + + /** + * Ensures {@code peekBuffer} is large enough to store at least {@code length} bytes from the + * current peek position. + */ + private void ensureSpaceForPeek(int length) { + int requiredLength = peekBufferPosition + length; + if (requiredLength > peekBuffer.length) { + int newPeekCapacity = Util.constrainValue(peekBuffer.length * 2, + requiredLength + PEEK_MIN_FREE_SPACE_AFTER_RESIZE, requiredLength + PEEK_MAX_FREE_SPACE); + peekBuffer = Arrays.copyOf(peekBuffer, newPeekCapacity); + } + } + + /** + * Skips from the peek buffer. + * + * @param length The maximum number of bytes to skip from the peek buffer. + * @return The number of bytes skipped. + */ + private int skipFromPeekBuffer(int length) { + int bytesSkipped = Math.min(peekBufferLength, length); + updatePeekBuffer(bytesSkipped); + return bytesSkipped; + } + + /** + * Reads from the peek buffer + * + * @param target A target array into which data should be written. + * @param offset The offset into the target array at which to write. + * @param length The maximum number of bytes to read from the peek buffer. + * @return The number of bytes read. + */ + private int readFromPeekBuffer(byte[] target, int offset, int length) { + if (peekBufferLength == 0) { + return 0; + } + int peekBytes = Math.min(peekBufferLength, length); + System.arraycopy(peekBuffer, 0, target, offset, peekBytes); + updatePeekBuffer(peekBytes); + return peekBytes; + } + + /** + * Updates the peek buffer's length, position and contents after consuming data. + * + * @param bytesConsumed The number of bytes consumed from the peek buffer. + */ + private void updatePeekBuffer(int bytesConsumed) { + peekBufferLength -= bytesConsumed; + peekBufferPosition = 0; + byte[] newPeekBuffer = peekBuffer; + if (peekBufferLength < peekBuffer.length - PEEK_MAX_FREE_SPACE) { + newPeekBuffer = new byte[peekBufferLength + PEEK_MIN_FREE_SPACE_AFTER_RESIZE]; + } + System.arraycopy(peekBuffer, bytesConsumed, newPeekBuffer, 0, peekBufferLength); + peekBuffer = newPeekBuffer; + } + + /** + * Starts or continues a read from the data source. + * + * @param target A target array into which data should be written. + * @param offset The offset into the target array at which to write. + * @param length The maximum number of bytes to read from the input. + * @param bytesAlreadyRead The number of bytes already read from the input. + * @param allowEndOfInput True if encountering the end of the input having read no data is + * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it + * should be considered an error, causing an {@link EOFException} to be thrown. + * @return The total number of bytes read so far, or {@link C#RESULT_END_OF_INPUT} if + * {@code allowEndOfInput} is true and the input has ended having read no bytes. + * @throws EOFException If the end of input was encountered having partially satisfied the read + * (i.e. having read at least one byte, but fewer than {@code length}), or if no bytes were + * read and {@code allowEndOfInput} is false. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + private int readFromDataSource(byte[] target, int offset, int length, int bytesAlreadyRead, + boolean allowEndOfInput) throws InterruptedException, IOException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + int bytesRead = dataSource.read(target, offset + bytesAlreadyRead, length - bytesAlreadyRead); + if (bytesRead == C.RESULT_END_OF_INPUT) { + if (bytesAlreadyRead == 0 && allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + return bytesAlreadyRead + bytesRead; + } + + /** + * Advances the position by the specified number of bytes read. + * + * @param bytesRead The number of bytes read. + */ + private void commitBytesRead(int bytesRead) { + if (bytesRead != C.RESULT_END_OF_INPUT) { + position += bytesRead; + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultExtractorsFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultExtractorsFactory.java new file mode 100644 index 0000000..ad3cd4d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.flv.FlvExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv.MatroskaExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp3.Mp3Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.Mp4Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg.OggExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.Ac3Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.AdtsExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.PsExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.wav.WavExtractor; +import java.lang.reflect.Constructor; + +/** + * An {@link ExtractorsFactory} that provides an array of extractors for the following formats: + * + *

      + *
    • MP4, including M4A ({@link Mp4Extractor})
    • + *
    • fMP4 ({@link FragmentedMp4Extractor})
    • + *
    • Matroska and WebM ({@link MatroskaExtractor})
    • + *
    • Ogg Vorbis/FLAC ({@link OggExtractor}
    • + *
    • MP3 ({@link Mp3Extractor})
    • + *
    • AAC ({@link AdtsExtractor})
    • + *
    • MPEG TS ({@link TsExtractor})
    • + *
    • MPEG PS ({@link PsExtractor})
    • + *
    • FLV ({@link FlvExtractor})
    • + *
    • WAV ({@link WavExtractor})
    • + *
    • AC3 ({@link Ac3Extractor})
    • + *
    • FLAC (only available if the FLAC extension is built and included)
    • + *
    + */ +public final class DefaultExtractorsFactory implements ExtractorsFactory { + + private static final Constructor FLAC_EXTRACTOR_CONSTRUCTOR; + static { + Constructor flacExtractorConstructor = null; + try { + flacExtractorConstructor = + Class.forName("com.tangxiaolv.telegramgallery.exoplayer2.ext.flac.FlacExtractor") + .asSubclass(Extractor.class).getConstructor(); + } catch (ClassNotFoundException e) { + // Extractor not found. + e.getStackTrace(); + } catch (NoSuchMethodException e) { + // Constructor not found. + e.getStackTrace(); + } + FLAC_EXTRACTOR_CONSTRUCTOR = flacExtractorConstructor; + } + + private @MatroskaExtractor.Flags int matroskaFlags; + private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags; + private @Mp3Extractor.Flags int mp3Flags; + private @DefaultTsPayloadReaderFactory.Flags int tsFlags; + + /** + * Sets flags for {@link MatroskaExtractor} instances created by the factory. + * + * @see MatroskaExtractor#MatroskaExtractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setMatroskaExtractorFlags( + @MatroskaExtractor.Flags int flags) { + this.matroskaFlags = flags; + return this; + } + + /** + * Sets flags for {@link FragmentedMp4Extractor} instances created by the factory. + * + * @see FragmentedMp4Extractor#FragmentedMp4Extractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setFragmentedMp4ExtractorFlags( + @FragmentedMp4Extractor.Flags int flags) { + this.fragmentedMp4Flags = flags; + return this; + } + + /** + * Sets flags for {@link Mp3Extractor} instances created by the factory. + * + * @see Mp3Extractor#Mp3Extractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setMp3ExtractorFlags(@Mp3Extractor.Flags int flags) { + mp3Flags = flags; + return this; + } + + /** + * Sets flags for {@link DefaultTsPayloadReaderFactory}s used by {@link TsExtractor} instances + * created by the factory. + * + * @see TsExtractor#TsExtractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setTsExtractorFlags( + @DefaultTsPayloadReaderFactory.Flags int flags) { + tsFlags = flags; + return this; + } + + @Override + public synchronized Extractor[] createExtractors() { + Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12]; + extractors[0] = new MatroskaExtractor(matroskaFlags); + extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); + extractors[2] = new Mp4Extractor(); + extractors[3] = new Mp3Extractor(mp3Flags); + extractors[4] = new AdtsExtractor(); + extractors[5] = new Ac3Extractor(); + extractors[6] = new TsExtractor(tsFlags); + extractors[7] = new FlvExtractor(); + extractors[8] = new OggExtractor(); + extractors[9] = new PsExtractor(); + extractors[10] = new WavExtractor(); + if (FLAC_EXTRACTOR_CONSTRUCTOR != null) { + try { + extractors[11] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance(); + } catch (Exception e) { + // Should never happen. + throw new IllegalStateException("Unexpected error creating FLAC extractor", e); + } + } + return extractors; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultTrackOutput.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultTrackOutput.java new file mode 100644 index 0000000..80686d8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DefaultTrackOutput.java @@ -0,0 +1,997 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocation; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A {@link TrackOutput} that buffers extracted samples in a queue and allows for consumption from + * that queue. + */ +public final class DefaultTrackOutput implements TrackOutput { + + /** + * A listener for changes to the upstream format. + */ + public interface UpstreamFormatChangedListener { + + /** + * Called on the loading thread when an upstream format change occurs. + * + * @param format The new upstream format. + */ + void onUpstreamFormatChanged(Format format); + + } + + private static final int INITIAL_SCRATCH_SIZE = 32; + + private static final int STATE_ENABLED = 0; + private static final int STATE_ENABLED_WRITING = 1; + private static final int STATE_DISABLED = 2; + + private final Allocator allocator; + private final int allocationLength; + + private final InfoQueue infoQueue; + private final LinkedBlockingDeque dataQueue; + private final BufferExtrasHolder extrasHolder; + private final ParsableByteArray scratch; + private final AtomicInteger state; + + // Accessed only by the consuming thread. + private long totalBytesDropped; + private Format downstreamFormat; + + // Accessed only by the loading thread (or the consuming thread when there is no loading thread). + private boolean pendingFormatAdjustment; + private Format lastUnadjustedFormat; + private long sampleOffsetUs; + private long totalBytesWritten; + private Allocation lastAllocation; + private int lastAllocationOffset; + private boolean pendingSplice; + private UpstreamFormatChangedListener upstreamFormatChangeListener; + + /** + * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. + */ + public DefaultTrackOutput(Allocator allocator) { + this.allocator = allocator; + allocationLength = allocator.getIndividualAllocationLength(); + infoQueue = new InfoQueue(); + dataQueue = new LinkedBlockingDeque<>(); + extrasHolder = new BufferExtrasHolder(); + scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); + state = new AtomicInteger(); + lastAllocationOffset = allocationLength; + } + + // Called by the consuming thread, but only when there is no loading thread. + + /** + * Resets the output. + * + * @param enable Whether the output should be enabled. False if it should be disabled. + */ + public void reset(boolean enable) { + int previousState = state.getAndSet(enable ? STATE_ENABLED : STATE_DISABLED); + clearSampleData(); + infoQueue.resetLargestParsedTimestamps(); + if (previousState == STATE_DISABLED) { + downstreamFormat = null; + } + } + + /** + * Sets a source identifier for subsequent samples. + * + * @param sourceId The source identifier. + */ + public void sourceId(int sourceId) { + infoQueue.sourceId(sourceId); + } + + /** + * Indicates that samples subsequently queued to the buffer should be spliced into those already + * queued. + */ + public void splice() { + pendingSplice = true; + } + + /** + * Returns the current absolute write index. + */ + public int getWriteIndex() { + return infoQueue.getWriteIndex(); + } + + /** + * Discards samples from the write side of the buffer. + * + * @param discardFromIndex The absolute index of the first sample to be discarded. + */ + public void discardUpstreamSamples(int discardFromIndex) { + totalBytesWritten = infoQueue.discardUpstreamSamples(discardFromIndex); + dropUpstreamFrom(totalBytesWritten); + } + + /** + * Discards data from the write side of the buffer. Data is discarded from the specified absolute + * position. Any allocations that are fully discarded are returned to the allocator. + * + * @param absolutePosition The absolute position (inclusive) from which to discard data. + */ + private void dropUpstreamFrom(long absolutePosition) { + int relativePosition = (int) (absolutePosition - totalBytesDropped); + // Calculate the index of the allocation containing the position, and the offset within it. + int allocationIndex = relativePosition / allocationLength; + int allocationOffset = relativePosition % allocationLength; + // We want to discard any allocations after the one at allocationIdnex. + int allocationDiscardCount = dataQueue.size() - allocationIndex - 1; + if (allocationOffset == 0) { + // If the allocation at allocationIndex is empty, we should discard that one too. + allocationDiscardCount++; + } + // Discard the allocations. + for (int i = 0; i < allocationDiscardCount; i++) { + allocator.release(dataQueue.removeLast()); + } + // Update lastAllocation and lastAllocationOffset to reflect the new position. + lastAllocation = dataQueue.peekLast(); + lastAllocationOffset = allocationOffset == 0 ? allocationLength : allocationOffset; + } + + // Called by the consuming thread. + + /** + * Disables buffering of sample data and metadata. + */ + public void disable() { + if (state.getAndSet(STATE_DISABLED) == STATE_ENABLED) { + clearSampleData(); + } + } + + /** + * Returns whether the buffer is empty. + */ + public boolean isEmpty() { + return infoQueue.isEmpty(); + } + + /** + * Returns the current absolute read index. + */ + public int getReadIndex() { + return infoQueue.getReadIndex(); + } + + /** + * Peeks the source id of the next sample, or the current upstream source id if the buffer is + * empty. + * + * @return The source id. + */ + public int peekSourceId() { + return infoQueue.peekSourceId(); + } + + /** + * Returns the upstream {@link Format} in which samples are being queued. + */ + public Format getUpstreamFormat() { + return infoQueue.getUpstreamFormat(); + } + + /** + * Returns the largest sample timestamp that has been queued since the last {@link #reset}. + *

    + * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + * + * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no + * samples have been queued. + */ + public long getLargestQueuedTimestampUs() { + return infoQueue.getLargestQueuedTimestampUs(); + } + + /** + * Skips all samples currently in the buffer. + */ + public void skipAll() { + long nextOffset = infoQueue.skipAll(); + if (nextOffset != C.POSITION_UNSET) { + dropDownstreamTo(nextOffset); + } + } + + /** + * Attempts to skip to the keyframe before or at the specified time. Succeeds only if the buffer + * contains a keyframe with a timestamp of {@code timeUs} or earlier. If + * {@code allowTimeBeyondBuffer} is {@code false} then it is also required that {@code timeUs} + * falls within the buffer. + * + * @param timeUs The seek time. + * @param allowTimeBeyondBuffer Whether the skip can succeed if {@code timeUs} is beyond the end + * of the buffer. + * @return Whether the skip was successful. + */ + public boolean skipToKeyframeBefore(long timeUs, boolean allowTimeBeyondBuffer) { + long nextOffset = infoQueue.skipToKeyframeBefore(timeUs, allowTimeBeyondBuffer); + if (nextOffset == C.POSITION_UNSET) { + return false; + } + dropDownstreamTo(nextOffset); + return true; + } + + /** + * Attempts to read from the queue. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If the end of the stream has been reached, the + * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @param formatRequired Whether the caller requires that the format of the stream be read even if + * it's not changing. A sample will never be read if set to true, however it is still possible + * for the end of stream or nothing to be read. + * @param loadingFinished True if an empty queue should be considered the end of the stream. + * @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will + * be set if the buffer's timestamp is less than this value. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or + * {@link C#RESULT_BUFFER_READ}. + */ + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired, + boolean loadingFinished, long decodeOnlyUntilUs) { + int result = infoQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, + downstreamFormat, extrasHolder); + switch (result) { + case C.RESULT_FORMAT_READ: + downstreamFormat = formatHolder.format; + return C.RESULT_FORMAT_READ; + case C.RESULT_BUFFER_READ: + if (!buffer.isEndOfStream()) { + if (buffer.timeUs < decodeOnlyUntilUs) { + buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + // Read encryption data if the sample is encrypted. + if (buffer.isEncrypted()) { + readEncryptionData(buffer, extrasHolder); + } + // Write the sample data into the holder. + buffer.ensureSpaceForWrite(extrasHolder.size); + readData(extrasHolder.offset, buffer.data, extrasHolder.size); + // Advance the read head. + dropDownstreamTo(extrasHolder.nextOffset); + } + return C.RESULT_BUFFER_READ; + case C.RESULT_NOTHING_READ: + return C.RESULT_NOTHING_READ; + default: + throw new IllegalStateException(); + } + } + + /** + * Reads encryption data for the current sample. + *

    + * The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and + * {@link BufferExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The + * same value is added to {@link BufferExtrasHolder#offset}. + * + * @param buffer The buffer into which the encryption data should be written. + * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted. + */ + private void readEncryptionData(DecoderInputBuffer buffer, BufferExtrasHolder extrasHolder) { + long offset = extrasHolder.offset; + + // Read the signal byte. + scratch.reset(1); + readData(offset, scratch.data, 1); + offset++; + byte signalByte = scratch.data[0]; + boolean subsampleEncryption = (signalByte & 0x80) != 0; + int ivSize = signalByte & 0x7F; + + // Read the initialization vector. + if (buffer.cryptoInfo.iv == null) { + buffer.cryptoInfo.iv = new byte[16]; + } + readData(offset, buffer.cryptoInfo.iv, ivSize); + offset += ivSize; + + // Read the subsample count, if present. + int subsampleCount; + if (subsampleEncryption) { + scratch.reset(2); + readData(offset, scratch.data, 2); + offset += 2; + subsampleCount = scratch.readUnsignedShort(); + } else { + subsampleCount = 1; + } + + // Write the clear and encrypted subsample sizes. + int[] clearDataSizes = buffer.cryptoInfo.numBytesOfClearData; + if (clearDataSizes == null || clearDataSizes.length < subsampleCount) { + clearDataSizes = new int[subsampleCount]; + } + int[] encryptedDataSizes = buffer.cryptoInfo.numBytesOfEncryptedData; + if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) { + encryptedDataSizes = new int[subsampleCount]; + } + if (subsampleEncryption) { + int subsampleDataLength = 6 * subsampleCount; + scratch.reset(subsampleDataLength); + readData(offset, scratch.data, subsampleDataLength); + offset += subsampleDataLength; + scratch.setPosition(0); + for (int i = 0; i < subsampleCount; i++) { + clearDataSizes[i] = scratch.readUnsignedShort(); + encryptedDataSizes[i] = scratch.readUnsignedIntToInt(); + } + } else { + clearDataSizes[0] = 0; + encryptedDataSizes[0] = extrasHolder.size - (int) (offset - extrasHolder.offset); + } + + // Populate the cryptoInfo. + buffer.cryptoInfo.set(subsampleCount, clearDataSizes, encryptedDataSizes, + extrasHolder.encryptionKeyId, buffer.cryptoInfo.iv, C.CRYPTO_MODE_AES_CTR); + + // Adjust the offset and size to take into account the bytes read. + int bytesRead = (int) (offset - extrasHolder.offset); + extrasHolder.offset += bytesRead; + extrasHolder.size -= bytesRead; + } + + /** + * Reads data from the front of the rolling buffer. + * + * @param absolutePosition The absolute position from which data should be read. + * @param target The buffer into which data should be written. + * @param length The number of bytes to read. + */ + private void readData(long absolutePosition, ByteBuffer target, int length) { + int remaining = length; + while (remaining > 0) { + dropDownstreamTo(absolutePosition); + int positionInAllocation = (int) (absolutePosition - totalBytesDropped); + int toCopy = Math.min(remaining, allocationLength - positionInAllocation); + Allocation allocation = dataQueue.peek(); + target.put(allocation.data, allocation.translateOffset(positionInAllocation), toCopy); + absolutePosition += toCopy; + remaining -= toCopy; + } + } + + /** + * Reads data from the front of the rolling buffer. + * + * @param absolutePosition The absolute position from which data should be read. + * @param target The array into which data should be written. + * @param length The number of bytes to read. + */ + private void readData(long absolutePosition, byte[] target, int length) { + int bytesRead = 0; + while (bytesRead < length) { + dropDownstreamTo(absolutePosition); + int positionInAllocation = (int) (absolutePosition - totalBytesDropped); + int toCopy = Math.min(length - bytesRead, allocationLength - positionInAllocation); + Allocation allocation = dataQueue.peek(); + System.arraycopy(allocation.data, allocation.translateOffset(positionInAllocation), target, + bytesRead, toCopy); + absolutePosition += toCopy; + bytesRead += toCopy; + } + } + + /** + * Discard any allocations that hold data prior to the specified absolute position, returning + * them to the allocator. + * + * @param absolutePosition The absolute position up to which allocations can be discarded. + */ + private void dropDownstreamTo(long absolutePosition) { + int relativePosition = (int) (absolutePosition - totalBytesDropped); + int allocationIndex = relativePosition / allocationLength; + for (int i = 0; i < allocationIndex; i++) { + allocator.release(dataQueue.remove()); + totalBytesDropped += allocationLength; + } + } + + // Called by the loading thread. + + /** + * Sets a listener to be notified of changes to the upstream format. + * + * @param listener The listener. + */ + public void setUpstreamFormatChangeListener(UpstreamFormatChangedListener listener) { + upstreamFormatChangeListener = listener; + } + + /** + * Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples + * subsequently queued to the buffer. + * + * @param sampleOffsetUs The timestamp offset in microseconds. + */ + public void setSampleOffsetUs(long sampleOffsetUs) { + if (this.sampleOffsetUs != sampleOffsetUs) { + this.sampleOffsetUs = sampleOffsetUs; + pendingFormatAdjustment = true; + } + } + + @Override + public void format(Format format) { + Format adjustedFormat = getAdjustedSampleFormat(format, sampleOffsetUs); + boolean formatChanged = infoQueue.format(adjustedFormat); + lastUnadjustedFormat = format; + pendingFormatAdjustment = false; + if (upstreamFormatChangeListener != null && formatChanged) { + upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat); + } + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + if (!startWriteOperation()) { + int bytesSkipped = input.skip(length); + if (bytesSkipped == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + return bytesSkipped; + } + try { + length = prepareForAppend(length); + int bytesAppended = input.read(lastAllocation.data, + lastAllocation.translateOffset(lastAllocationOffset), length); + if (bytesAppended == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + lastAllocationOffset += bytesAppended; + totalBytesWritten += bytesAppended; + return bytesAppended; + } finally { + endWriteOperation(); + } + } + + @Override + public void sampleData(ParsableByteArray buffer, int length) { + if (!startWriteOperation()) { + buffer.skipBytes(length); + return; + } + while (length > 0) { + int thisAppendLength = prepareForAppend(length); + buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), + thisAppendLength); + lastAllocationOffset += thisAppendLength; + totalBytesWritten += thisAppendLength; + length -= thisAppendLength; + } + endWriteOperation(); + } + + @Override + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { + if (pendingFormatAdjustment) { + format(lastUnadjustedFormat); + } + if (!startWriteOperation()) { + infoQueue.commitSampleTimestamp(timeUs); + return; + } + try { + if (pendingSplice) { + if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0 || !infoQueue.attemptSplice(timeUs)) { + return; + } + pendingSplice = false; + } + timeUs += sampleOffsetUs; + long absoluteOffset = totalBytesWritten - size - offset; + infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey); + } finally { + endWriteOperation(); + } + } + + // Private methods. + + private boolean startWriteOperation() { + return state.compareAndSet(STATE_ENABLED, STATE_ENABLED_WRITING); + } + + private void endWriteOperation() { + if (!state.compareAndSet(STATE_ENABLED_WRITING, STATE_ENABLED)) { + clearSampleData(); + } + } + + private void clearSampleData() { + infoQueue.clearSampleData(); + allocator.release(dataQueue.toArray(new Allocation[dataQueue.size()])); + dataQueue.clear(); + allocator.trim(); + totalBytesDropped = 0; + totalBytesWritten = 0; + lastAllocation = null; + lastAllocationOffset = allocationLength; + } + + /** + * Prepares the rolling sample buffer for an append of up to {@code length} bytes, returning the + * number of bytes that can actually be appended. + */ + private int prepareForAppend(int length) { + if (lastAllocationOffset == allocationLength) { + lastAllocationOffset = 0; + lastAllocation = allocator.allocate(); + dataQueue.add(lastAllocation); + } + return Math.min(length, allocationLength - lastAllocationOffset); + } + + /** + * Adjusts a {@link Format} to incorporate a sample offset into {@link Format#subsampleOffsetUs}. + * + * @param format The {@link Format} to adjust. + * @param sampleOffsetUs The offset to apply. + * @return The adjusted {@link Format}. + */ + private static Format getAdjustedSampleFormat(Format format, long sampleOffsetUs) { + if (format == null) { + return null; + } + if (sampleOffsetUs != 0 && format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) { + format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs); + } + return format; + } + + /** + * Holds information about the samples in the rolling buffer. + */ + private static final class InfoQueue { + + private static final int SAMPLE_CAPACITY_INCREMENT = 1000; + + private int capacity; + + private int[] sourceIds; + private long[] offsets; + private int[] sizes; + private int[] flags; + private long[] timesUs; + private byte[][] encryptionKeys; + private Format[] formats; + + private int queueSize; + private int absoluteReadIndex; + private int relativeReadIndex; + private int relativeWriteIndex; + + private long largestDequeuedTimestampUs; + private long largestQueuedTimestampUs; + private boolean upstreamKeyframeRequired; + private boolean upstreamFormatRequired; + private Format upstreamFormat; + private int upstreamSourceId; + + public InfoQueue() { + capacity = SAMPLE_CAPACITY_INCREMENT; + sourceIds = new int[capacity]; + offsets = new long[capacity]; + timesUs = new long[capacity]; + flags = new int[capacity]; + sizes = new int[capacity]; + encryptionKeys = new byte[capacity][]; + formats = new Format[capacity]; + largestDequeuedTimestampUs = Long.MIN_VALUE; + largestQueuedTimestampUs = Long.MIN_VALUE; + upstreamFormatRequired = true; + upstreamKeyframeRequired = true; + } + + public void clearSampleData() { + absoluteReadIndex = 0; + relativeReadIndex = 0; + relativeWriteIndex = 0; + queueSize = 0; + upstreamKeyframeRequired = true; + } + + // Called by the consuming thread, but only when there is no loading thread. + + public void resetLargestParsedTimestamps() { + largestDequeuedTimestampUs = Long.MIN_VALUE; + largestQueuedTimestampUs = Long.MIN_VALUE; + } + + /** + * Returns the current absolute write index. + */ + public int getWriteIndex() { + return absoluteReadIndex + queueSize; + } + + /** + * Discards samples from the write side of the buffer. + * + * @param discardFromIndex The absolute index of the first sample to be discarded. + * @return The reduced total number of bytes written, after the samples have been discarded. + */ + public long discardUpstreamSamples(int discardFromIndex) { + int discardCount = getWriteIndex() - discardFromIndex; + Assertions.checkArgument(0 <= discardCount && discardCount <= queueSize); + + if (discardCount == 0) { + if (absoluteReadIndex == 0) { + // queueSize == absoluteReadIndex == 0, so nothing has been written to the queue. + return 0; + } + int lastWriteIndex = (relativeWriteIndex == 0 ? capacity : relativeWriteIndex) - 1; + return offsets[lastWriteIndex] + sizes[lastWriteIndex]; + } + + queueSize -= discardCount; + relativeWriteIndex = (relativeWriteIndex + capacity - discardCount) % capacity; + // Update the largest queued timestamp, assuming that the timestamps prior to a keyframe are + // always less than the timestamp of the keyframe itself, and of subsequent frames. + largestQueuedTimestampUs = Long.MIN_VALUE; + for (int i = queueSize - 1; i >= 0; i--) { + int sampleIndex = (relativeReadIndex + i) % capacity; + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timesUs[sampleIndex]); + if ((flags[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + break; + } + } + return offsets[relativeWriteIndex]; + } + + public void sourceId(int sourceId) { + upstreamSourceId = sourceId; + } + + // Called by the consuming thread. + + /** + * Returns the current absolute read index. + */ + public int getReadIndex() { + return absoluteReadIndex; + } + + /** + * Peeks the source id of the next sample, or the current upstream source id if the queue is + * empty. + */ + public int peekSourceId() { + return queueSize == 0 ? upstreamSourceId : sourceIds[relativeReadIndex]; + } + + /** + * Returns whether the queue is empty. + */ + public synchronized boolean isEmpty() { + return queueSize == 0; + } + + /** + * Returns the upstream {@link Format} in which samples are being queued. + */ + public synchronized Format getUpstreamFormat() { + return upstreamFormatRequired ? null : upstreamFormat; + } + + /** + * Returns the largest sample timestamp that has been queued since the last {@link #reset}. + *

    + * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + * + * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no + * samples have been queued. + */ + public synchronized long getLargestQueuedTimestampUs() { + return Math.max(largestDequeuedTimestampUs, largestQueuedTimestampUs); + } + + /** + * Attempts to read from the queue. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If a sample is read then the buffer is populated with information + * about the sample, but not its data. The size and absolute position of the data in the + * rolling buffer is stored in {@code extrasHolder}, along with an encryption id if present + * and the absolute position of the first byte that may still be required after the current + * sample has been read. May be null if the caller requires that the format of the stream be + * read even if it's not changing. + * @param formatRequired Whether the caller requires that the format of the stream be read even + * if it's not changing. A sample will never be read if set to true, however it is still + * possible for the end of stream or nothing to be read. + * @param loadingFinished True if an empty queue should be considered the end of the stream. + * @param downstreamFormat The current downstream {@link Format}. If the format of the next + * sample is different to the current downstream format then a format will be read. + * @param extrasHolder The holder into which extra sample information should be written. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} + * or {@link C#RESULT_BUFFER_READ}. + */ + @SuppressWarnings("ReferenceEquality") + public synchronized int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired, boolean loadingFinished, Format downstreamFormat, + BufferExtrasHolder extrasHolder) { + if (queueSize == 0) { + if (loadingFinished) { + buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + return C.RESULT_BUFFER_READ; + } else if (upstreamFormat != null + && (formatRequired || upstreamFormat != downstreamFormat)) { + formatHolder.format = upstreamFormat; + return C.RESULT_FORMAT_READ; + } else { + return C.RESULT_NOTHING_READ; + } + } + + if (formatRequired || formats[relativeReadIndex] != downstreamFormat) { + formatHolder.format = formats[relativeReadIndex]; + return C.RESULT_FORMAT_READ; + } + + if (buffer.isFlagsOnly()) { + return C.RESULT_NOTHING_READ; + } + + buffer.timeUs = timesUs[relativeReadIndex]; + buffer.setFlags(flags[relativeReadIndex]); + extrasHolder.size = sizes[relativeReadIndex]; + extrasHolder.offset = offsets[relativeReadIndex]; + extrasHolder.encryptionKeyId = encryptionKeys[relativeReadIndex]; + + largestDequeuedTimestampUs = Math.max(largestDequeuedTimestampUs, buffer.timeUs); + queueSize--; + relativeReadIndex++; + absoluteReadIndex++; + if (relativeReadIndex == capacity) { + // Wrap around. + relativeReadIndex = 0; + } + + extrasHolder.nextOffset = queueSize > 0 ? offsets[relativeReadIndex] + : extrasHolder.offset + extrasHolder.size; + return C.RESULT_BUFFER_READ; + } + + /** + * Skips all samples in the buffer. + * + * @return The offset up to which data should be dropped, or {@link C#POSITION_UNSET} if no + * dropping of data is required. + */ + public synchronized long skipAll() { + if (queueSize == 0) { + return C.POSITION_UNSET; + } + + int lastSampleIndex = (relativeReadIndex + queueSize - 1) % capacity; + relativeReadIndex = (relativeReadIndex + queueSize) % capacity; + absoluteReadIndex += queueSize; + queueSize = 0; + return offsets[lastSampleIndex] + sizes[lastSampleIndex]; + } + + /** + * Attempts to locate the keyframe before or at the specified time. If + * {@code allowTimeBeyondBuffer} is {@code false} then it is also required that {@code timeUs} + * falls within the buffer. + * + * @param timeUs The seek time. + * @param allowTimeBeyondBuffer Whether the skip can succeed if {@code timeUs} is beyond the end + * of the buffer. + * @return The offset of the keyframe's data if the keyframe was present. + * {@link C#POSITION_UNSET} otherwise. + */ + public synchronized long skipToKeyframeBefore(long timeUs, boolean allowTimeBeyondBuffer) { + if (queueSize == 0 || timeUs < timesUs[relativeReadIndex]) { + return C.POSITION_UNSET; + } + + if (timeUs > largestQueuedTimestampUs && !allowTimeBeyondBuffer) { + return C.POSITION_UNSET; + } + + // This could be optimized to use a binary search, however in practice callers to this method + // often pass times near to the start of the buffer. Hence it's unclear whether switching to + // a binary search would yield any real benefit. + int sampleCount = 0; + int sampleCountToKeyframe = -1; + int searchIndex = relativeReadIndex; + while (searchIndex != relativeWriteIndex) { + if (timesUs[searchIndex] > timeUs) { + // We've gone too far. + break; + } else if ((flags[searchIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + // We've found a keyframe, and we're still before the seek position. + sampleCountToKeyframe = sampleCount; + } + searchIndex = (searchIndex + 1) % capacity; + sampleCount++; + } + + if (sampleCountToKeyframe == -1) { + return C.POSITION_UNSET; + } + + relativeReadIndex = (relativeReadIndex + sampleCountToKeyframe) % capacity; + absoluteReadIndex += sampleCountToKeyframe; + queueSize -= sampleCountToKeyframe; + return offsets[relativeReadIndex]; + } + + // Called by the loading thread. + + public synchronized boolean format(Format format) { + if (format == null) { + upstreamFormatRequired = true; + return false; + } + upstreamFormatRequired = false; + if (Util.areEqual(format, upstreamFormat)) { + // Suppress changes between equal formats so we can use referential equality in readData. + return false; + } else { + upstreamFormat = format; + return true; + } + } + + public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset, + int size, byte[] encryptionKey) { + if (upstreamKeyframeRequired) { + if ((sampleFlags & C.BUFFER_FLAG_KEY_FRAME) == 0) { + return; + } + upstreamKeyframeRequired = false; + } + Assertions.checkState(!upstreamFormatRequired); + commitSampleTimestamp(timeUs); + timesUs[relativeWriteIndex] = timeUs; + offsets[relativeWriteIndex] = offset; + sizes[relativeWriteIndex] = size; + flags[relativeWriteIndex] = sampleFlags; + encryptionKeys[relativeWriteIndex] = encryptionKey; + formats[relativeWriteIndex] = upstreamFormat; + sourceIds[relativeWriteIndex] = upstreamSourceId; + // Increment the write index. + queueSize++; + if (queueSize == capacity) { + // Increase the capacity. + int newCapacity = capacity + SAMPLE_CAPACITY_INCREMENT; + int[] newSourceIds = new int[newCapacity]; + long[] newOffsets = new long[newCapacity]; + long[] newTimesUs = new long[newCapacity]; + int[] newFlags = new int[newCapacity]; + int[] newSizes = new int[newCapacity]; + byte[][] newEncryptionKeys = new byte[newCapacity][]; + Format[] newFormats = new Format[newCapacity]; + int beforeWrap = capacity - relativeReadIndex; + System.arraycopy(offsets, relativeReadIndex, newOffsets, 0, beforeWrap); + System.arraycopy(timesUs, relativeReadIndex, newTimesUs, 0, beforeWrap); + System.arraycopy(flags, relativeReadIndex, newFlags, 0, beforeWrap); + System.arraycopy(sizes, relativeReadIndex, newSizes, 0, beforeWrap); + System.arraycopy(encryptionKeys, relativeReadIndex, newEncryptionKeys, 0, beforeWrap); + System.arraycopy(formats, relativeReadIndex, newFormats, 0, beforeWrap); + System.arraycopy(sourceIds, relativeReadIndex, newSourceIds, 0, beforeWrap); + int afterWrap = relativeReadIndex; + System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap); + System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap); + System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap); + System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap); + System.arraycopy(encryptionKeys, 0, newEncryptionKeys, beforeWrap, afterWrap); + System.arraycopy(formats, 0, newFormats, beforeWrap, afterWrap); + System.arraycopy(sourceIds, 0, newSourceIds, beforeWrap, afterWrap); + offsets = newOffsets; + timesUs = newTimesUs; + flags = newFlags; + sizes = newSizes; + encryptionKeys = newEncryptionKeys; + formats = newFormats; + sourceIds = newSourceIds; + relativeReadIndex = 0; + relativeWriteIndex = capacity; + queueSize = capacity; + capacity = newCapacity; + } else { + relativeWriteIndex++; + if (relativeWriteIndex == capacity) { + // Wrap around. + relativeWriteIndex = 0; + } + } + } + + public synchronized void commitSampleTimestamp(long timeUs) { + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs); + } + + /** + * Attempts to discard samples from the tail of the queue to allow samples starting from the + * specified timestamp to be spliced in. + * + * @param timeUs The timestamp at which the splice occurs. + * @return Whether the splice was successful. + */ + public synchronized boolean attemptSplice(long timeUs) { + if (largestDequeuedTimestampUs >= timeUs) { + return false; + } + int retainCount = queueSize; + while (retainCount > 0 + && timesUs[(relativeReadIndex + retainCount - 1) % capacity] >= timeUs) { + retainCount--; + } + discardUpstreamSamples(absoluteReadIndex + retainCount); + return true; + } + + } + + /** + * Holds additional buffer information not held by {@link DecoderInputBuffer}. + */ + private static final class BufferExtrasHolder { + + public int size; + public long offset; + public long nextOffset; + public byte[] encryptionKeyId; + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DummyTrackOutput.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DummyTrackOutput.java new file mode 100644 index 0000000..5453fff --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/DummyTrackOutput.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.EOFException; +import java.io.IOException; + +/** + * A dummy {@link TrackOutput} implementation. + */ +public final class DummyTrackOutput implements TrackOutput { + + @Override + public void format(Format format) { + // Do nothing. + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + int bytesSkipped = input.skip(length); + if (bytesSkipped == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + return bytesSkipped; + } + + @Override + public void sampleData(ParsableByteArray data, int length) { + data.skipBytes(length); + } + + @Override + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { + // Do nothing. + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/Extractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/Extractor.java new file mode 100644 index 0000000..c8087a4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/Extractor.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.IOException; + +/** + * Extracts media data from a container format. + */ +public interface Extractor { + + /** + * Returned by {@link #read(ExtractorInput, PositionHolder)} if the {@link ExtractorInput} passed + * to the next {@link #read(ExtractorInput, PositionHolder)} is required to provide data + * continuing from the position in the stream reached by the returning call. + */ + int RESULT_CONTINUE = 0; + /** + * Returned by {@link #read(ExtractorInput, PositionHolder)} if the {@link ExtractorInput} passed + * to the next {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting + * from a specified position in the stream. + */ + int RESULT_SEEK = 1; + /** + * Returned by {@link #read(ExtractorInput, PositionHolder)} if the end of the + * {@link ExtractorInput} was reached. Equal to {@link C#RESULT_END_OF_INPUT}. + */ + int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT; + + /** + * Returns whether this extractor can extract samples from the {@link ExtractorInput}, which must + * provide data from the start of the stream. + *

    + * If {@code true} is returned, the {@code input}'s reading position may have been modified. + * Otherwise, only its peek position may have been modified. + * + * @param input The {@link ExtractorInput} from which data should be peeked/read. + * @return Whether this extractor can read the provided input. + * @throws IOException If an error occurred reading from the input. + * @throws InterruptedException If the thread was interrupted. + */ + boolean sniff(ExtractorInput input) throws IOException, InterruptedException; + + /** + * Initializes the extractor with an {@link ExtractorOutput}. Called at most once. + * + * @param output An {@link ExtractorOutput} to receive extracted data. + */ + void init(ExtractorOutput output); + + /** + * Extracts data read from a provided {@link ExtractorInput}. + *

    + * A single call to this method will block until some progress has been made, but will not block + * for longer than this. Hence each call will consume only a small amount of input data. + *

    + * In the common case, {@link #RESULT_CONTINUE} is returned to indicate that the + * {@link ExtractorInput} passed to the next read is required to provide data continuing from the + * position in the stream reached by the returning call. If the extractor requires data to be + * provided from a different position, then that position is set in {@code seekPosition} and + * {@link #RESULT_SEEK} is returned. If the extractor reached the end of the data provided by the + * {@link ExtractorInput}, then {@link #RESULT_END_OF_INPUT} is returned. + * + * @param input The {@link ExtractorInput} from which data should be read. + * @param seekPosition If {@link #RESULT_SEEK} is returned, this holder is updated to hold the + * position of the required data. + * @return One of the {@code RESULT_} values defined in this interface. + * @throws IOException If an error occurred reading from the input. + * @throws InterruptedException If the thread was interrupted. + */ + int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException; + + /** + * Notifies the extractor that a seek has occurred. + *

    + * Following a call to this method, the {@link ExtractorInput} passed to the next invocation of + * {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from {@code + * position} in the stream. Valid random access positions are the start of the stream and + * positions that can be obtained from any {@link SeekMap} passed to the {@link ExtractorOutput}. + * + * @param position The byte offset in the stream from which data will be provided. + * @param timeUs The seek time in microseconds. + */ + void seek(long position, long timeUs); + + /** + * Releases all kept resources. + */ + void release(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorInput.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorInput.java new file mode 100644 index 0000000..31cdb0f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorInput.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.EOFException; +import java.io.IOException; + +/** + * Provides data to be consumed by an {@link Extractor}. + */ +public interface ExtractorInput { + + /** + * Reads up to {@code length} bytes from the input and resets the peek position. + *

    + * This method blocks until at least one byte of data can be read, the end of the input is + * detected, or an exception is thrown. + * + * @param target A target array into which data should be written. + * @param offset The offset into the target array at which to write. + * @param length The maximum number of bytes to read from the input. + * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the input has ended. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + int read(byte[] target, int offset, int length) throws IOException, InterruptedException; + + /** + * Like {@link #read(byte[], int, int)}, but reads the requested {@code length} in full. + *

    + * If the end of the input is found having read no data, then behavior is dependent on + * {@code allowEndOfInput}. If {@code allowEndOfInput == true} then {@code false} is returned. + * Otherwise an {@link EOFException} is thrown. + *

    + * Encountering the end of input having partially satisfied the read is always considered an + * error, and will result in an {@link EOFException} being thrown. + * + * @param target A target array into which data should be written. + * @param offset The offset into the target array at which to write. + * @param length The number of bytes to read from the input. + * @param allowEndOfInput True if encountering the end of the input having read no data is + * allowed, and should result in {@code false} being returned. False if it should be + * considered an error, causing an {@link EOFException} to be thrown. + * @return True if the read was successful. False if the end of the input was encountered having + * read no data. + * @throws EOFException If the end of input was encountered having partially satisfied the read + * (i.e. having read at least one byte, but fewer than {@code length}), or if no bytes were + * read and {@code allowEndOfInput} is false. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException; + + /** + * Equivalent to {@code readFully(target, offset, length, false)}. + * + * @param target A target array into which data should be written. + * @param offset The offset into the target array at which to write. + * @param length The number of bytes to read from the input. + * @throws EOFException If the end of input was encountered. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + void readFully(byte[] target, int offset, int length) throws IOException, InterruptedException; + + /** + * Like {@link #read(byte[], int, int)}, except the data is skipped instead of read. + * + * @param length The maximum number of bytes to skip from the input. + * @return The number of bytes skipped, or {@link C#RESULT_END_OF_INPUT} if the input has ended. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + int skip(int length) throws IOException, InterruptedException; + + /** + * Like {@link #readFully(byte[], int, int, boolean)}, except the data is skipped instead of read. + * + * @param length The number of bytes to skip from the input. + * @param allowEndOfInput True if encountering the end of the input having skipped no data is + * allowed, and should result in {@code false} being returned. False if it should be + * considered an error, causing an {@link EOFException} to be thrown. + * @return True if the skip was successful. False if the end of the input was encountered having + * skipped no data. + * @throws EOFException If the end of input was encountered having partially satisfied the skip + * (i.e. having skipped at least one byte, but fewer than {@code length}), or if no bytes were + * skipped and {@code allowEndOfInput} is false. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + boolean skipFully(int length, boolean allowEndOfInput) throws IOException, InterruptedException; + + /** + * Like {@link #readFully(byte[], int, int)}, except the data is skipped instead of read. + *

    + * Encountering the end of input is always considered an error, and will result in an + * {@link EOFException} being thrown. + * + * @param length The number of bytes to skip from the input. + * @throws EOFException If the end of input was encountered. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + void skipFully(int length) throws IOException, InterruptedException; + + /** + * Peeks {@code length} bytes from the peek position, writing them into {@code target} at index + * {@code offset}. The current read position is left unchanged. + *

    + * If the end of the input is found having peeked no data, then behavior is dependent on + * {@code allowEndOfInput}. If {@code allowEndOfInput == true} then {@code false} is returned. + * Otherwise an {@link EOFException} is thrown. + *

    + * Calling {@link #resetPeekPosition()} resets the peek position to equal the current read + * position, so the caller can peek the same data again. Reading or skipping also resets the peek + * position. + * + * @param target A target array into which data should be written. + * @param offset The offset into the target array at which to write. + * @param length The number of bytes to peek from the input. + * @param allowEndOfInput True if encountering the end of the input having peeked no data is + * allowed, and should result in {@code false} being returned. False if it should be + * considered an error, causing an {@link EOFException} to be thrown. + * @return True if the peek was successful. False if the end of the input was encountered having + * peeked no data. + * @throws EOFException If the end of input was encountered having partially satisfied the peek + * (i.e. having peeked at least one byte, but fewer than {@code length}), or if no bytes were + * peeked and {@code allowEndOfInput} is false. + * @throws IOException If an error occurs peeking from the input. + * @throws InterruptedException If the thread is interrupted. + */ + boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException; + + /** + * Peeks {@code length} bytes from the peek position, writing them into {@code target} at index + * {@code offset}. The current read position is left unchanged. + *

    + * Calling {@link #resetPeekPosition()} resets the peek position to equal the current read + * position, so the caller can peek the same data again. Reading and skipping also reset the peek + * position. + * + * @param target A target array into which data should be written. + * @param offset The offset into the target array at which to write. + * @param length The number of bytes to peek from the input. + * @throws EOFException If the end of input was encountered. + * @throws IOException If an error occurs peeking from the input. + * @throws InterruptedException If the thread is interrupted. + */ + void peekFully(byte[] target, int offset, int length) throws IOException, InterruptedException; + + /** + * Advances the peek position by {@code length} bytes. + *

    + * If the end of the input is encountered before advancing the peek position, then behavior is + * dependent on {@code allowEndOfInput}. If {@code allowEndOfInput == true} then {@code false} is + * returned. Otherwise an {@link EOFException} is thrown. + * + * @param length The number of bytes by which to advance the peek position. + * @param allowEndOfInput True if encountering the end of the input before advancing is allowed, + * and should result in {@code false} being returned. False if it should be considered an + * error, causing an {@link EOFException} to be thrown. + * @return True if advancing the peek position was successful. False if the end of the input was + * encountered before the peek position could be advanced. + * @throws EOFException If the end of input was encountered having partially advanced (i.e. having + * advanced by at least one byte, but fewer than {@code length}), or if the end of input was + * encountered before advancing and {@code allowEndOfInput} is false. + * @throws IOException If an error occurs advancing the peek position. + * @throws InterruptedException If the thread is interrupted. + */ + boolean advancePeekPosition(int length, boolean allowEndOfInput) + throws IOException, InterruptedException; + + /** + * Advances the peek position by {@code length} bytes. + * + * @param length The number of bytes to peek from the input. + * @throws EOFException If the end of input was encountered. + * @throws IOException If an error occurs peeking from the input. + * @throws InterruptedException If the thread is interrupted. + */ + void advancePeekPosition(int length) throws IOException, InterruptedException; + + /** + * Resets the peek position to equal the current read position. + */ + void resetPeekPosition(); + + /** + * Returns the current peek position (byte offset) in the stream. + * + * @return The peek position (byte offset) in the stream. + */ + long getPeekPosition(); + + /** + * Returns the current read position (byte offset) in the stream. + * + * @return The read position (byte offset) in the stream. + */ + long getPosition(); + + /** + * Returns the length of the source stream, or {@link C#LENGTH_UNSET} if it is unknown. + * + * @return The length of the source stream, or {@link C#LENGTH_UNSET}. + */ + long getLength(); + + /** + * Called when reading fails and the required retry position is different from the last position. + * After setting the retry position it throws the given {@link Throwable}. + * + * @param Type of {@link Throwable} to be thrown. + * @param position The required retry position. + * @param e {@link Throwable} to be thrown. + * @throws E The given {@link Throwable} object. + */ + void setRetryPosition(long position, E e) throws E; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorOutput.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorOutput.java new file mode 100644 index 0000000..87d4d99 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorOutput.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +/** + * Receives stream level data extracted by an {@link Extractor}. + */ +public interface ExtractorOutput { + + /** + * Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track. + *

    + * The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}. + * + * @param id A track identifier. + * @param type The type of the track. Typically one of the {@link com.tangxiaolv.telegramgallery.exoplayer2.C} + * {@code TRACK_TYPE_*} constants. + * @return The {@link TrackOutput} for the given track identifier. + */ + TrackOutput track(int id, int type); + + /** + * Called when all tracks have been identified, meaning no new {@code trackId} values will be + * passed to {@link #track(int, int)}. + */ + void endTracks(); + + /** + * Called when a {@link SeekMap} has been extracted from the stream. + * + * @param seekMap The extracted {@link SeekMap}. + */ + void seekMap(SeekMap seekMap); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorsFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorsFactory.java new file mode 100644 index 0000000..da56632 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ExtractorsFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +/** + * Factory for arrays of {@link Extractor}s. + */ +public interface ExtractorsFactory { + + /** + * Returns an array of new {@link Extractor} instances. + */ + Extractor[] createExtractors(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/GaplessInfoHolder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/GaplessInfoHolder.java new file mode 100644 index 0000000..6cc6934 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/GaplessInfoHolder.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.CommentFrame; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.Id3Decoder.FramePredicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Holder for gapless playback information. + */ +public final class GaplessInfoHolder { + + /** + * A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed + * to {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback + * information are decoded. + */ + public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE = new FramePredicate() { + @Override + public boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3) { + return id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2); + } + }; + + private static final String GAPLESS_COMMENT_ID = "iTunSMPB"; + private static final Pattern GAPLESS_COMMENT_PATTERN = + Pattern.compile("^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})"); + + /** + * The number of samples to trim from the start of the decoded audio stream, or + * {@link Format#NO_VALUE} if not set. + */ + public int encoderDelay; + + /** + * The number of samples to trim from the end of the decoded audio stream, or + * {@link Format#NO_VALUE} if not set. + */ + public int encoderPadding; + + /** + * Creates a new holder for gapless playback information. + */ + public GaplessInfoHolder() { + encoderDelay = Format.NO_VALUE; + encoderPadding = Format.NO_VALUE; + } + + /** + * Populates the holder with data from an MP3 Xing header, if valid and non-zero. + * + * @param value The 24-bit value to decode. + * @return Whether the holder was populated. + */ + public boolean setFromXingHeaderValue(int value) { + int encoderDelay = value >> 12; + int encoderPadding = value & 0x0FFF; + if (encoderDelay > 0 || encoderPadding > 0) { + this.encoderDelay = encoderDelay; + this.encoderPadding = encoderPadding; + return true; + } + return false; + } + + /** + * Populates the holder with data parsed from ID3 {@link Metadata}. + * + * @param metadata The metadata from which to parse the gapless information. + * @return Whether the holder was populated. + */ + public boolean setFromMetadata(Metadata metadata) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof CommentFrame) { + CommentFrame commentFrame = (CommentFrame) entry; + if (setFromComment(commentFrame.description, commentFrame.text)) { + return true; + } + } + } + return false; + } + + /** + * Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header + * or MPEG 4 user data), if valid and non-zero. + * + * @param name The comment's identifier. + * @param data The comment's payload data. + * @return Whether the holder was populated. + */ + private boolean setFromComment(String name, String data) { + if (!GAPLESS_COMMENT_ID.equals(name)) { + return false; + } + Matcher matcher = GAPLESS_COMMENT_PATTERN.matcher(data); + if (matcher.find()) { + try { + int encoderDelay = Integer.parseInt(matcher.group(1), 16); + int encoderPadding = Integer.parseInt(matcher.group(2), 16); + if (encoderDelay > 0 || encoderPadding > 0) { + this.encoderDelay = encoderDelay; + this.encoderPadding = encoderPadding; + return true; + } + } catch (NumberFormatException e) { + // Ignore incorrectly formatted comments. + e.getStackTrace(); + } + } + return false; + } + + /** + * Returns whether {@link #encoderDelay} and {@link #encoderPadding} have been set. + */ + public boolean hasGaplessInfo() { + return encoderDelay != Format.NO_VALUE && encoderPadding != Format.NO_VALUE; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/MpegAudioHeader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/MpegAudioHeader.java new file mode 100644 index 0000000..2630a89 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/MpegAudioHeader.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; + +/** + * An MPEG audio frame header. + */ +public final class MpegAudioHeader { + + /** + * Theoretical maximum frame size for an MPEG audio stream, which occurs when playing a Layer 2 + * MPEG 2.5 audio stream at 16 kb/s (with padding). The size is 1152 sample/frame * + * 160000 bit/s / (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame. + * The next power of two size is 4 KiB. + */ + public static final int MAX_FRAME_SIZE_BYTES = 4096; + + private static final String[] MIME_TYPE_BY_LAYER = + new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG}; + private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000}; + private static final int[] BITRATE_V1_L1 = + {32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}; + private static final int[] BITRATE_V2_L1 = + {32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}; + private static final int[] BITRATE_V1_L2 = + {32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}; + private static final int[] BITRATE_V1_L3 = + {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}; + private static final int[] BITRATE_V2 = + {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}; + + /** + * Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it + * is invalid. + */ + public static int getFrameSize(int header) { + if ((header & 0xFFE00000) != 0xFFE00000) { + return C.LENGTH_UNSET; + } + + int version = (header >>> 19) & 3; + if (version == 1) { + return C.LENGTH_UNSET; + } + + int layer = (header >>> 17) & 3; + if (layer == 0) { + return C.LENGTH_UNSET; + } + + int bitrateIndex = (header >>> 12) & 15; + if (bitrateIndex == 0 || bitrateIndex == 0xF) { + // Disallow "free" bitrate. + return C.LENGTH_UNSET; + } + + int samplingRateIndex = (header >>> 10) & 3; + if (samplingRateIndex == 3) { + return C.LENGTH_UNSET; + } + + int samplingRate = SAMPLING_RATE_V1[samplingRateIndex]; + if (version == 2) { + // Version 2 + samplingRate /= 2; + } else if (version == 0) { + // Version 2.5 + samplingRate /= 4; + } + + int bitrate; + int padding = (header >>> 9) & 1; + if (layer == 3) { + // Layer I (layer == 3) + bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; + return (12000 * bitrate / samplingRate + padding) * 4; + } else { + // Layer II (layer == 2) or III (layer == 1) + if (version == 3) { + bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; + } else { + // Version 2 or 2.5. + bitrate = BITRATE_V2[bitrateIndex - 1]; + } + } + + if (version == 3) { + // Version 1 + return 144000 * bitrate / samplingRate + padding; + } else { + // Version 2 or 2.5 + return (layer == 1 ? 72000 : 144000) * bitrate / samplingRate + padding; + } + } + + /** + * Parses {@code headerData}, populating {@code header} with the parsed data. + * + * @param headerData Header data to parse. + * @param header Header to populate with data from {@code headerData}. + * @return True if the header was populated. False otherwise, indicating that {@code headerData} + * is not a valid MPEG audio header. + */ + public static boolean populateHeader(int headerData, MpegAudioHeader header) { + if ((headerData & 0xFFE00000) != 0xFFE00000) { + return false; + } + + int version = (headerData >>> 19) & 3; + if (version == 1) { + return false; + } + + int layer = (headerData >>> 17) & 3; + if (layer == 0) { + return false; + } + + int bitrateIndex = (headerData >>> 12) & 15; + if (bitrateIndex == 0 || bitrateIndex == 0xF) { + // Disallow "free" bitrate. + return false; + } + + int samplingRateIndex = (headerData >>> 10) & 3; + if (samplingRateIndex == 3) { + return false; + } + + int sampleRate = SAMPLING_RATE_V1[samplingRateIndex]; + if (version == 2) { + // Version 2 + sampleRate /= 2; + } else if (version == 0) { + // Version 2.5 + sampleRate /= 4; + } + + int padding = (headerData >>> 9) & 1; + int bitrate, frameSize, samplesPerFrame; + if (layer == 3) { + // Layer I (layer == 3) + bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; + frameSize = (12000 * bitrate / sampleRate + padding) * 4; + samplesPerFrame = 384; + } else { + // Layer II (layer == 2) or III (layer == 1) + if (version == 3) { + // Version 1 + bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; + samplesPerFrame = 1152; + frameSize = 144000 * bitrate / sampleRate + padding; + } else { + // Version 2 or 2.5. + bitrate = BITRATE_V2[bitrateIndex - 1]; + samplesPerFrame = layer == 1 ? 576 : 1152; + frameSize = (layer == 1 ? 72000 : 144000) * bitrate / sampleRate + padding; + } + } + + String mimeType = MIME_TYPE_BY_LAYER[3 - layer]; + int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2; + header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate * 1000, + samplesPerFrame); + return true; + } + + /** MPEG audio header version. */ + public int version; + /** The mime type. */ + public String mimeType; + /** Size of the frame associated with this header, in bytes. */ + public int frameSize; + /** Sample rate in samples per second. */ + public int sampleRate; + /** Number of audio channels in the frame. */ + public int channels; + /** Bitrate of the frame in bit/s. */ + public int bitrate; + /** Number of samples stored in the frame. */ + public int samplesPerFrame; + + private void setValues(int version, String mimeType, int frameSize, int sampleRate, int channels, + int bitrate, int samplesPerFrame) { + this.version = version; + this.mimeType = mimeType; + this.frameSize = frameSize; + this.sampleRate = sampleRate; + this.channels = channels; + this.bitrate = bitrate; + this.samplesPerFrame = samplesPerFrame; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/PositionHolder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/PositionHolder.java new file mode 100644 index 0000000..d366a74 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/PositionHolder.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +/** + * Holds a position in the stream. + */ +public final class PositionHolder { + + /** + * The held position. + */ + public long position; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/SeekMap.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/SeekMap.java new file mode 100644 index 0000000..9fb06fd --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/SeekMap.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +/** + * Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream. + */ +public interface SeekMap { + + /** + * A {@link SeekMap} that does not support seeking. + */ + final class Unseekable implements SeekMap { + + private final long durationUs; + + /** + * @param durationUs The duration of the stream in microseconds, or {@link C#TIME_UNSET} if + * the duration is unknown. + */ + public Unseekable(long durationUs) { + this.durationUs = durationUs; + } + + @Override + public boolean isSeekable() { + return false; + } + + @Override + public long getDurationUs() { + return durationUs; + } + + @Override + public long getPosition(long timeUs) { + return 0; + } + + } + + /** + * Returns whether seeking is supported. + *

    + * If seeking is not supported then the only valid seek position is the start of the file, and so + * {@link #getPosition(long)} will return 0 for all input values. + * + * @return Whether seeking is supported. + */ + boolean isSeekable(); + + /** + * Returns the duration of the stream in microseconds. + * + * @return The duration of the stream in microseconds, or {@link C#TIME_UNSET} if the + * duration is unknown. + */ + long getDurationUs(); + + /** + * Maps a seek position in microseconds to a corresponding position (byte offset) in the stream + * from which data can be provided to the extractor. + * + * @param timeUs A seek position in microseconds. + * @return The corresponding position (byte offset) in the stream from which data can be provided + * to the extractor, or 0 if {@code #isSeekable()} returns false. + */ + long getPosition(long timeUs); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/TrackOutput.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/TrackOutput.java new file mode 100644 index 0000000..b3670ce --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/TrackOutput.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.EOFException; +import java.io.IOException; + +/** + * Receives track level data extracted by an {@link Extractor}. + */ +public interface TrackOutput { + + /** + * Called when the {@link Format} of the track has been extracted from the stream. + * + * @param format The extracted {@link Format}. + */ + void format(Format format); + + /** + * Called to write sample data to the output. + * + * @param input An {@link ExtractorInput} from which to read the sample data. + * @param length The maximum length to read from the input. + * @param allowEndOfInput True if encountering the end of the input having read no data is + * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it + * should be considered an error, causing an {@link EOFException} to be thrown. + * @return The number of bytes appended. + * @throws IOException If an error occurred reading from the input. + * @throws InterruptedException If the thread was interrupted. + */ + int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException; + + /** + * Called to write sample data to the output. + * + * @param data A {@link ParsableByteArray} from which to read the sample data. + * @param length The number of bytes to read. + */ + void sampleData(ParsableByteArray data, int length); + + /** + * Called when metadata associated with a sample has been extracted from the stream. + *

    + * The corresponding sample data will have already been passed to the output via calls to + * {@link #sampleData(ExtractorInput, int, boolean)} or + * {@link #sampleData(ParsableByteArray, int)}. + * + * @param timeUs The media timestamp associated with the sample, in microseconds. + * @param flags Flags associated with the sample. See {@code C.BUFFER_FLAG_*}. + * @param size The size of the sample data, in bytes. + * @param offset The number of bytes that have been passed to + * {@link #sampleData(ExtractorInput, int, boolean)} or + * {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample + * whose metadata is being passed. + * @param encryptionKey The encryption key associated with the sample. May be null. + */ + void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/AudioTagPayloadReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/AudioTagPayloadReader.java new file mode 100644 index 0000000..514d2b0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/AudioTagPayloadReader.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.flv; + +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.CodecSpecificDataUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.Collections; + +/** + * Parses audio tags from an FLV stream and extracts AAC frames. + */ +/* package */ final class AudioTagPayloadReader extends TagPayloadReader { + + private static final int AUDIO_FORMAT_MP3 = 2; + private static final int AUDIO_FORMAT_ALAW = 7; + private static final int AUDIO_FORMAT_ULAW = 8; + private static final int AUDIO_FORMAT_AAC = 10; + + private static final int AAC_PACKET_TYPE_SEQUENCE_HEADER = 0; + private static final int AAC_PACKET_TYPE_AAC_RAW = 1; + + private static final int[] AUDIO_SAMPLING_RATE_TABLE = new int[] {5512, 11025, 22050, 44100}; + + // State variables + private boolean hasParsedAudioDataHeader; + private boolean hasOutputFormat; + private int audioFormat; + + public AudioTagPayloadReader(TrackOutput output) { + super(output); + } + + @Override + public void seek() { + // Do nothing. + } + + @Override + protected boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException { + if (!hasParsedAudioDataHeader) { + int header = data.readUnsignedByte(); + audioFormat = (header >> 4) & 0x0F; + if (audioFormat == AUDIO_FORMAT_MP3) { + int sampleRateIndex = (header >> 2) & 0x03; + int sampleRate = AUDIO_SAMPLING_RATE_TABLE[sampleRateIndex]; + Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_MPEG, null, + Format.NO_VALUE, Format.NO_VALUE, 1, sampleRate, null, null, 0, null); + output.format(format); + hasOutputFormat = true; + } else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) { + String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW + : MimeTypes.AUDIO_ULAW; + int pcmEncoding = (header & 0x01) == 1 ? C.ENCODING_PCM_16BIT : C.ENCODING_PCM_8BIT; + Format format = Format.createAudioSampleFormat(null, type, null, Format.NO_VALUE, + Format.NO_VALUE, 1, 8000, pcmEncoding, null, null, 0, null); + output.format(format); + hasOutputFormat = true; + } else if (audioFormat != AUDIO_FORMAT_AAC) { + throw new UnsupportedFormatException("Audio format not supported: " + audioFormat); + } + hasParsedAudioDataHeader = true; + } else { + // Skip header if it was parsed previously. + data.skipBytes(1); + } + return true; + } + + @Override + protected void parsePayload(ParsableByteArray data, long timeUs) { + if (audioFormat == AUDIO_FORMAT_MP3) { + int sampleSize = data.bytesLeft(); + output.sampleData(data, sampleSize); + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } else { + int packetType = data.readUnsignedByte(); + if (packetType == AAC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { + // Parse the sequence header. + byte[] audioSpecificConfig = new byte[data.bytesLeft()]; + data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length); + Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( + audioSpecificConfig); + Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, + Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, + Collections.singletonList(audioSpecificConfig), null, 0, null); + output.format(format); + hasOutputFormat = true; + } else if (audioFormat != AUDIO_FORMAT_AAC || packetType == AAC_PACKET_TYPE_AAC_RAW) { + int sampleSize = data.bytesLeft(); + output.sampleData(data, sampleSize); + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/FlvExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/FlvExtractor.java new file mode 100644 index 0000000..d025788 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/FlvExtractor.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.flv; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Facilitates the extraction of data from the FLV container format. + */ +public final class FlvExtractor implements Extractor, SeekMap { + + /** + * Factory for {@link FlvExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new FlvExtractor()}; + } + + }; + + // Header sizes. + private static final int FLV_HEADER_SIZE = 9; + private static final int FLV_TAG_HEADER_SIZE = 11; + + // Parser states. + private static final int STATE_READING_FLV_HEADER = 1; + private static final int STATE_SKIPPING_TO_TAG_HEADER = 2; + private static final int STATE_READING_TAG_HEADER = 3; + private static final int STATE_READING_TAG_DATA = 4; + + // Tag types. + private static final int TAG_TYPE_AUDIO = 8; + private static final int TAG_TYPE_VIDEO = 9; + private static final int TAG_TYPE_SCRIPT_DATA = 18; + + // FLV container identifier. + private static final int FLV_TAG = Util.getIntegerCodeForString("FLV"); + + // Temporary buffers. + private final ParsableByteArray scratch; + private final ParsableByteArray headerBuffer; + private final ParsableByteArray tagHeaderBuffer; + private final ParsableByteArray tagData; + + // Extractor outputs. + private ExtractorOutput extractorOutput; + + // State variables. + private int parserState; + private int bytesToNextTagHeader; + public int tagType; + public int tagDataSize; + public long tagTimestampUs; + + // Tags readers. + private AudioTagPayloadReader audioReader; + private VideoTagPayloadReader videoReader; + private ScriptTagPayloadReader metadataReader; + + public FlvExtractor() { + scratch = new ParsableByteArray(4); + headerBuffer = new ParsableByteArray(FLV_HEADER_SIZE); + tagHeaderBuffer = new ParsableByteArray(FLV_TAG_HEADER_SIZE); + tagData = new ParsableByteArray(); + parserState = STATE_READING_FLV_HEADER; + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + // Check if file starts with "FLV" tag + input.peekFully(scratch.data, 0, 3); + scratch.setPosition(0); + if (scratch.readUnsignedInt24() != FLV_TAG) { + return false; + } + + // Checking reserved flags are set to 0 + input.peekFully(scratch.data, 0, 2); + scratch.setPosition(0); + if ((scratch.readUnsignedShort() & 0xFA) != 0) { + return false; + } + + // Read data offset + input.peekFully(scratch.data, 0, 4); + scratch.setPosition(0); + int dataOffset = scratch.readInt(); + + input.resetPeekPosition(); + input.advancePeekPosition(dataOffset); + + // Checking first "previous tag size" is set to 0 + input.peekFully(scratch.data, 0, 4); + scratch.setPosition(0); + + return scratch.readInt() == 0; + } + + @Override + public void init(ExtractorOutput output) { + this.extractorOutput = output; + } + + @Override + public void seek(long position, long timeUs) { + parserState = STATE_READING_FLV_HEADER; + bytesToNextTagHeader = 0; + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, + InterruptedException { + while (true) { + switch (parserState) { + case STATE_READING_FLV_HEADER: + if (!readFlvHeader(input)) { + return RESULT_END_OF_INPUT; + } + break; + case STATE_SKIPPING_TO_TAG_HEADER: + skipToTagHeader(input); + break; + case STATE_READING_TAG_HEADER: + if (!readTagHeader(input)) { + return RESULT_END_OF_INPUT; + } + break; + case STATE_READING_TAG_DATA: + if (readTagData(input)) { + return RESULT_CONTINUE; + } + break; + } + } + } + + /** + * Reads an FLV container header from the provided {@link ExtractorInput}. + * + * @param input The {@link ExtractorInput} from which to read. + * @return True if header was read successfully. False if the end of stream was reached. + * @throws IOException If an error occurred reading or parsing data from the source. + * @throws InterruptedException If the thread was interrupted. + */ + private boolean readFlvHeader(ExtractorInput input) throws IOException, InterruptedException { + if (!input.readFully(headerBuffer.data, 0, FLV_HEADER_SIZE, true)) { + // We've reached the end of the stream. + return false; + } + + headerBuffer.setPosition(0); + headerBuffer.skipBytes(4); + int flags = headerBuffer.readUnsignedByte(); + boolean hasAudio = (flags & 0x04) != 0; + boolean hasVideo = (flags & 0x01) != 0; + if (hasAudio && audioReader == null) { + audioReader = new AudioTagPayloadReader( + extractorOutput.track(TAG_TYPE_AUDIO, C.TRACK_TYPE_AUDIO)); + } + if (hasVideo && videoReader == null) { + videoReader = new VideoTagPayloadReader( + extractorOutput.track(TAG_TYPE_VIDEO, C.TRACK_TYPE_VIDEO)); + } + if (metadataReader == null) { + metadataReader = new ScriptTagPayloadReader(null); + } + extractorOutput.endTracks(); + extractorOutput.seekMap(this); + + // We need to skip any additional content in the FLV header, plus the 4 byte previous tag size. + bytesToNextTagHeader = headerBuffer.readInt() - FLV_HEADER_SIZE + 4; + parserState = STATE_SKIPPING_TO_TAG_HEADER; + return true; + } + + /** + * Skips over data to reach the next tag header. + * + * @param input The {@link ExtractorInput} from which to read. + * @throws IOException If an error occurred skipping data from the source. + * @throws InterruptedException If the thread was interrupted. + */ + private void skipToTagHeader(ExtractorInput input) throws IOException, InterruptedException { + input.skipFully(bytesToNextTagHeader); + bytesToNextTagHeader = 0; + parserState = STATE_READING_TAG_HEADER; + } + + /** + * Reads a tag header from the provided {@link ExtractorInput}. + * + * @param input The {@link ExtractorInput} from which to read. + * @return True if tag header was read successfully. Otherwise, false. + * @throws IOException If an error occurred reading or parsing data from the source. + * @throws InterruptedException If the thread was interrupted. + */ + private boolean readTagHeader(ExtractorInput input) throws IOException, InterruptedException { + if (!input.readFully(tagHeaderBuffer.data, 0, FLV_TAG_HEADER_SIZE, true)) { + // We've reached the end of the stream. + return false; + } + + tagHeaderBuffer.setPosition(0); + tagType = tagHeaderBuffer.readUnsignedByte(); + tagDataSize = tagHeaderBuffer.readUnsignedInt24(); + tagTimestampUs = tagHeaderBuffer.readUnsignedInt24(); + tagTimestampUs = ((tagHeaderBuffer.readUnsignedByte() << 24) | tagTimestampUs) * 1000L; + tagHeaderBuffer.skipBytes(3); // streamId + parserState = STATE_READING_TAG_DATA; + return true; + } + + /** + * Reads the body of a tag from the provided {@link ExtractorInput}. + * + * @param input The {@link ExtractorInput} from which to read. + * @return True if the data was consumed by a reader. False if it was skipped. + * @throws IOException If an error occurred reading or parsing data from the source. + * @throws InterruptedException If the thread was interrupted. + */ + private boolean readTagData(ExtractorInput input) throws IOException, InterruptedException { + boolean wasConsumed = true; + if (tagType == TAG_TYPE_AUDIO && audioReader != null) { + audioReader.consume(prepareTagData(input), tagTimestampUs); + } else if (tagType == TAG_TYPE_VIDEO && videoReader != null) { + videoReader.consume(prepareTagData(input), tagTimestampUs); + } else if (tagType == TAG_TYPE_SCRIPT_DATA && metadataReader != null) { + metadataReader.consume(prepareTagData(input), tagTimestampUs); + } else { + input.skipFully(tagDataSize); + wasConsumed = false; + } + bytesToNextTagHeader = 4; // There's a 4 byte previous tag size before the next header. + parserState = STATE_SKIPPING_TO_TAG_HEADER; + return wasConsumed; + } + + private ParsableByteArray prepareTagData(ExtractorInput input) throws IOException, + InterruptedException { + if (tagDataSize > tagData.capacity()) { + tagData.reset(new byte[Math.max(tagData.capacity() * 2, tagDataSize)], 0); + } else { + tagData.setPosition(0); + } + tagData.setLimit(tagDataSize); + input.readFully(tagData.data, 0, tagDataSize); + return tagData; + } + + // SeekMap implementation. + + @Override + public boolean isSeekable() { + return false; + } + + @Override + public long getDurationUs() { + return metadataReader.getDurationUs(); + } + + @Override + public long getPosition(long timeUs) { + return 0; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/ScriptTagPayloadReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/ScriptTagPayloadReader.java new file mode 100644 index 0000000..8b9535d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/ScriptTagPayloadReader.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.flv; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Parses Script Data tags from an FLV stream and extracts metadata information. + */ +/* package */ final class ScriptTagPayloadReader extends TagPayloadReader { + + private static final String NAME_METADATA = "onMetaData"; + private static final String KEY_DURATION = "duration"; + + // AMF object types + private static final int AMF_TYPE_NUMBER = 0; + private static final int AMF_TYPE_BOOLEAN = 1; + private static final int AMF_TYPE_STRING = 2; + private static final int AMF_TYPE_OBJECT = 3; + private static final int AMF_TYPE_ECMA_ARRAY = 8; + private static final int AMF_TYPE_END_MARKER = 9; + private static final int AMF_TYPE_STRICT_ARRAY = 10; + private static final int AMF_TYPE_DATE = 11; + + private long durationUs; + + /** + * @param output A {@link TrackOutput} to which samples should be written. + */ + public ScriptTagPayloadReader(TrackOutput output) { + super(output); + durationUs = C.TIME_UNSET; + } + + public long getDurationUs() { + return durationUs; + } + + @Override + public void seek() { + // Do nothing. + } + + @Override + protected boolean parseHeader(ParsableByteArray data) { + return true; + } + + @Override + protected void parsePayload(ParsableByteArray data, long timeUs) throws ParserException { + int nameType = readAmfType(data); + if (nameType != AMF_TYPE_STRING) { + // Should never happen. + throw new ParserException(); + } + String name = readAmfString(data); + if (!NAME_METADATA.equals(name)) { + // We're only interested in metadata. + return; + } + int type = readAmfType(data); + if (type != AMF_TYPE_ECMA_ARRAY) { + // We're not interested in this metadata. + return; + } + // Set the duration to the value contained in the metadata, if present. + Map metadata = readAmfEcmaArray(data); + if (metadata.containsKey(KEY_DURATION)) { + double durationSeconds = (double) metadata.get(KEY_DURATION); + if (durationSeconds > 0.0) { + durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND); + } + } + } + + private static int readAmfType(ParsableByteArray data) { + return data.readUnsignedByte(); + } + + /** + * Read a boolean from an AMF encoded buffer. + * + * @param data The buffer from which to read. + * @return The value read from the buffer. + */ + private static Boolean readAmfBoolean(ParsableByteArray data) { + return data.readUnsignedByte() == 1; + } + + /** + * Read a double number from an AMF encoded buffer. + * + * @param data The buffer from which to read. + * @return The value read from the buffer. + */ + private static Double readAmfDouble(ParsableByteArray data) { + return Double.longBitsToDouble(data.readLong()); + } + + /** + * Read a string from an AMF encoded buffer. + * + * @param data The buffer from which to read. + * @return The value read from the buffer. + */ + private static String readAmfString(ParsableByteArray data) { + int size = data.readUnsignedShort(); + int position = data.getPosition(); + data.skipBytes(size); + return new String(data.data, position, size); + } + + /** + * Read an array from an AMF encoded buffer. + * + * @param data The buffer from which to read. + * @return The value read from the buffer. + */ + private static ArrayList readAmfStrictArray(ParsableByteArray data) { + int count = data.readUnsignedIntToInt(); + ArrayList list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + int type = readAmfType(data); + list.add(readAmfData(data, type)); + } + return list; + } + + /** + * Read an object from an AMF encoded buffer. + * + * @param data The buffer from which to read. + * @return The value read from the buffer. + */ + private static HashMap readAmfObject(ParsableByteArray data) { + HashMap array = new HashMap<>(); + while (true) { + String key = readAmfString(data); + int type = readAmfType(data); + if (type == AMF_TYPE_END_MARKER) { + break; + } + array.put(key, readAmfData(data, type)); + } + return array; + } + + /** + * Read an ECMA array from an AMF encoded buffer. + * + * @param data The buffer from which to read. + * @return The value read from the buffer. + */ + private static HashMap readAmfEcmaArray(ParsableByteArray data) { + int count = data.readUnsignedIntToInt(); + HashMap array = new HashMap<>(count); + for (int i = 0; i < count; i++) { + String key = readAmfString(data); + int type = readAmfType(data); + array.put(key, readAmfData(data, type)); + } + return array; + } + + /** + * Read a date from an AMF encoded buffer. + * + * @param data The buffer from which to read. + * @return The value read from the buffer. + */ + private static Date readAmfDate(ParsableByteArray data) { + Date date = new Date((long) readAmfDouble(data).doubleValue()); + data.skipBytes(2); // Skip reserved bytes. + return date; + } + + private static Object readAmfData(ParsableByteArray data, int type) { + switch (type) { + case AMF_TYPE_NUMBER: + return readAmfDouble(data); + case AMF_TYPE_BOOLEAN: + return readAmfBoolean(data); + case AMF_TYPE_STRING: + return readAmfString(data); + case AMF_TYPE_OBJECT: + return readAmfObject(data); + case AMF_TYPE_ECMA_ARRAY: + return readAmfEcmaArray(data); + case AMF_TYPE_STRICT_ARRAY: + return readAmfStrictArray(data); + case AMF_TYPE_DATE: + return readAmfDate(data); + default: + return null; + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/TagPayloadReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/TagPayloadReader.java new file mode 100644 index 0000000..0e2bc0c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/TagPayloadReader.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.flv; + +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; + +/** + * Extracts individual samples from FLV tags, preserving original order. + */ +/* package */ abstract class TagPayloadReader { + + /** + * Thrown when the format is not supported. + */ + public static final class UnsupportedFormatException extends ParserException { + + public UnsupportedFormatException(String msg) { + super(msg); + } + + } + + protected final TrackOutput output; + + /** + * @param output A {@link TrackOutput} to which samples should be written. + */ + protected TagPayloadReader(TrackOutput output) { + this.output = output; + } + + /** + * Notifies the reader that a seek has occurred. + *

    + * Following a call to this method, the data passed to the next invocation of + * {@link #consume(ParsableByteArray, long)} will not be a continuation of the data that + * was previously passed. Hence the reader should reset any internal state. + */ + public abstract void seek(); + + /** + * Consumes payload data. + * + * @param data The payload data to consume. + * @param timeUs The timestamp associated with the payload. + * @throws ParserException If an error occurs parsing the data. + */ + public final void consume(ParsableByteArray data, long timeUs) throws ParserException { + if (parseHeader(data)) { + parsePayload(data, timeUs); + } + } + + /** + * Parses tag header. + * + * @param data Buffer where the tag header is stored. + * @return Whether the header was parsed successfully. + * @throws ParserException If an error occurs parsing the header. + */ + protected abstract boolean parseHeader(ParsableByteArray data) throws ParserException; + + /** + * Parses tag payload. + * + * @param data Buffer where tag payload is stored + * @param timeUs Time position of the frame + * @throws ParserException If an error occurs parsing the payload. + */ + protected abstract void parsePayload(ParsableByteArray data, long timeUs) throws ParserException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/VideoTagPayloadReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/VideoTagPayloadReader.java new file mode 100644 index 0000000..63bf239 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/flv/VideoTagPayloadReader.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.flv; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.video.AvcConfig; + +/** + * Parses video tags from an FLV stream and extracts H.264 nal units. + */ +/* package */ final class VideoTagPayloadReader extends TagPayloadReader { + + // Video codec. + private static final int VIDEO_CODEC_AVC = 7; + + // Frame types. + private static final int VIDEO_FRAME_KEYFRAME = 1; + private static final int VIDEO_FRAME_VIDEO_INFO = 5; + + // Packet types. + private static final int AVC_PACKET_TYPE_SEQUENCE_HEADER = 0; + private static final int AVC_PACKET_TYPE_AVC_NALU = 1; + + // Temporary arrays. + private final ParsableByteArray nalStartCode; + private final ParsableByteArray nalLength; + private int nalUnitLengthFieldLength; + + // State variables. + private boolean hasOutputFormat; + private int frameType; + + /** + * @param output A {@link TrackOutput} to which samples should be written. + */ + public VideoTagPayloadReader(TrackOutput output) { + super(output); + nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); + nalLength = new ParsableByteArray(4); + } + + @Override + public void seek() { + // Do nothing. + } + + @Override + protected boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException { + int header = data.readUnsignedByte(); + int frameType = (header >> 4) & 0x0F; + int videoCodec = (header & 0x0F); + // Support just H.264 encoded content. + if (videoCodec != VIDEO_CODEC_AVC) { + throw new UnsupportedFormatException("Video format not supported: " + videoCodec); + } + this.frameType = frameType; + return (frameType != VIDEO_FRAME_VIDEO_INFO); + } + + @Override + protected void parsePayload(ParsableByteArray data, long timeUs) throws ParserException { + int packetType = data.readUnsignedByte(); + int compositionTimeMs = data.readUnsignedInt24(); + timeUs += compositionTimeMs * 1000L; + // Parse avc sequence header in case this was not done before. + if (packetType == AVC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { + ParsableByteArray videoSequence = new ParsableByteArray(new byte[data.bytesLeft()]); + data.readBytes(videoSequence.data, 0, data.bytesLeft()); + AvcConfig avcConfig = AvcConfig.parse(videoSequence); + nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; + // Construct and output the format. + Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null, + Format.NO_VALUE, Format.NO_VALUE, avcConfig.width, avcConfig.height, Format.NO_VALUE, + avcConfig.initializationData, Format.NO_VALUE, avcConfig.pixelWidthAspectRatio, null); + output.format(format); + hasOutputFormat = true; + } else if (packetType == AVC_PACKET_TYPE_AVC_NALU && hasOutputFormat) { + // TODO: Deduplicate with Mp4Extractor. + // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case + // they're only 1 or 2 bytes long. + byte[] nalLengthData = nalLength.data; + nalLengthData[0] = 0; + nalLengthData[1] = 0; + nalLengthData[2] = 0; + int nalUnitLengthFieldLengthDiff = 4 - nalUnitLengthFieldLength; + // NAL units are length delimited, but the decoder requires start code delimited units. + // Loop until we've written the sample to the track output, replacing length delimiters with + // start codes as we encounter them. + int bytesWritten = 0; + int bytesToWrite; + while (data.bytesLeft() > 0) { + // Read the NAL length so that we know where we find the next one. + data.readBytes(nalLength.data, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); + nalLength.setPosition(0); + bytesToWrite = nalLength.readUnsignedIntToInt(); + + // Write a start code for the current NAL unit. + nalStartCode.setPosition(0); + output.sampleData(nalStartCode, 4); + bytesWritten += 4; + + // Write the payload of the NAL unit. + output.sampleData(data, bytesToWrite); + bytesWritten += bytesToWrite; + } + output.sampleMetadata(timeUs, frameType == VIDEO_FRAME_KEYFRAME ? C.BUFFER_FLAG_KEY_FRAME : 0, + bytesWritten, 0, null); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/DefaultEbmlReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/DefaultEbmlReader.java new file mode 100644 index 0000000..7028685 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/DefaultEbmlReader.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.EOFException; +import java.io.IOException; +import java.util.Stack; + +/** + * Default implementation of {@link EbmlReader}. + */ +/* package */ final class DefaultEbmlReader implements EbmlReader { + + private static final int ELEMENT_STATE_READ_ID = 0; + private static final int ELEMENT_STATE_READ_CONTENT_SIZE = 1; + private static final int ELEMENT_STATE_READ_CONTENT = 2; + + private static final int MAX_ID_BYTES = 4; + private static final int MAX_LENGTH_BYTES = 8; + + private static final int MAX_INTEGER_ELEMENT_SIZE_BYTES = 8; + private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4; + private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8; + + private final byte[] scratch = new byte[8]; + private final Stack masterElementsStack = new Stack<>(); + private final VarintReader varintReader = new VarintReader(); + + private EbmlReaderOutput output; + private int elementState; + private int elementId; + private long elementContentSize; + + @Override + public void init(EbmlReaderOutput eventHandler) { + this.output = eventHandler; + } + + @Override + public void reset() { + elementState = ELEMENT_STATE_READ_ID; + masterElementsStack.clear(); + varintReader.reset(); + } + + @Override + public boolean read(ExtractorInput input) throws IOException, InterruptedException { + Assertions.checkState(output != null); + while (true) { + if (!masterElementsStack.isEmpty() + && input.getPosition() >= masterElementsStack.peek().elementEndPosition) { + output.endMasterElement(masterElementsStack.pop().elementId); + return true; + } + + if (elementState == ELEMENT_STATE_READ_ID) { + long result = varintReader.readUnsignedVarint(input, true, false, MAX_ID_BYTES); + if (result == C.RESULT_MAX_LENGTH_EXCEEDED) { + result = maybeResyncToNextLevel1Element(input); + } + if (result == C.RESULT_END_OF_INPUT) { + return false; + } + // Element IDs are at most 4 bytes, so we can cast to integers. + elementId = (int) result; + elementState = ELEMENT_STATE_READ_CONTENT_SIZE; + } + + if (elementState == ELEMENT_STATE_READ_CONTENT_SIZE) { + elementContentSize = varintReader.readUnsignedVarint(input, false, true, MAX_LENGTH_BYTES); + elementState = ELEMENT_STATE_READ_CONTENT; + } + + int type = output.getElementType(elementId); + switch (type) { + case TYPE_MASTER: + long elementContentPosition = input.getPosition(); + long elementEndPosition = elementContentPosition + elementContentSize; + masterElementsStack.add(new MasterElement(elementId, elementEndPosition)); + output.startMasterElement(elementId, elementContentPosition, elementContentSize); + elementState = ELEMENT_STATE_READ_ID; + return true; + case TYPE_UNSIGNED_INT: + if (elementContentSize > MAX_INTEGER_ELEMENT_SIZE_BYTES) { + throw new ParserException("Invalid integer size: " + elementContentSize); + } + output.integerElement(elementId, readInteger(input, (int) elementContentSize)); + elementState = ELEMENT_STATE_READ_ID; + return true; + case TYPE_FLOAT: + if (elementContentSize != VALID_FLOAT32_ELEMENT_SIZE_BYTES + && elementContentSize != VALID_FLOAT64_ELEMENT_SIZE_BYTES) { + throw new ParserException("Invalid float size: " + elementContentSize); + } + output.floatElement(elementId, readFloat(input, (int) elementContentSize)); + elementState = ELEMENT_STATE_READ_ID; + return true; + case TYPE_STRING: + if (elementContentSize > Integer.MAX_VALUE) { + throw new ParserException("String element size: " + elementContentSize); + } + output.stringElement(elementId, readString(input, (int) elementContentSize)); + elementState = ELEMENT_STATE_READ_ID; + return true; + case TYPE_BINARY: + output.binaryElement(elementId, (int) elementContentSize, input); + elementState = ELEMENT_STATE_READ_ID; + return true; + case TYPE_UNKNOWN: + input.skipFully((int) elementContentSize); + elementState = ELEMENT_STATE_READ_ID; + break; + default: + throw new ParserException("Invalid element type " + type); + } + } + } + + /** + * Does a byte by byte search to try and find the next level 1 element. This method is called if + * some invalid data is encountered in the parser. + * + * @param input The {@link ExtractorInput} from which data has to be read. + * @return id of the next level 1 element that has been found. + * @throws EOFException If the end of input was encountered when searching for the next level 1 + * element. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + private long maybeResyncToNextLevel1Element(ExtractorInput input) throws IOException, + InterruptedException { + input.resetPeekPosition(); + while (true) { + input.peekFully(scratch, 0, MAX_ID_BYTES); + int varintLength = VarintReader.parseUnsignedVarintLength(scratch[0]); + if (varintLength != C.LENGTH_UNSET && varintLength <= MAX_ID_BYTES) { + int potentialId = (int) VarintReader.assembleVarint(scratch, varintLength, false); + if (output.isLevel1Element(potentialId)) { + input.skipFully(varintLength); + return potentialId; + } + } + input.skipFully(1); + } + } + + /** + * Reads and returns an integer of length {@code byteLength} from the {@link ExtractorInput}. + * + * @param input The {@link ExtractorInput} from which to read. + * @param byteLength The length of the integer being read. + * @return The read integer value. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + private long readInteger(ExtractorInput input, int byteLength) + throws IOException, InterruptedException { + input.readFully(scratch, 0, byteLength); + long value = 0; + for (int i = 0; i < byteLength; i++) { + value = (value << 8) | (scratch[i] & 0xFF); + } + return value; + } + + /** + * Reads and returns a float of length {@code byteLength} from the {@link ExtractorInput}. + * + * @param input The {@link ExtractorInput} from which to read. + * @param byteLength The length of the float being read. + * @return The read float value. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + private double readFloat(ExtractorInput input, int byteLength) + throws IOException, InterruptedException { + long integerValue = readInteger(input, byteLength); + double floatValue; + if (byteLength == VALID_FLOAT32_ELEMENT_SIZE_BYTES) { + floatValue = Float.intBitsToFloat((int) integerValue); + } else { + floatValue = Double.longBitsToDouble(integerValue); + } + return floatValue; + } + + /** + * Reads and returns a string of length {@code byteLength} from the {@link ExtractorInput}. + * + * @param input The {@link ExtractorInput} from which to read. + * @param byteLength The length of the float being read. + * @return The read string value. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + private String readString(ExtractorInput input, int byteLength) + throws IOException, InterruptedException { + if (byteLength == 0) { + return ""; + } + byte[] stringBytes = new byte[byteLength]; + input.readFully(stringBytes, 0, byteLength); + return new String(stringBytes); + } + + /** + * Used in {@link #masterElementsStack} to track when the current master element ends, so that + * {@link EbmlReaderOutput#endMasterElement(int)} can be called. + */ + private static final class MasterElement { + + private final int elementId; + private final long elementEndPosition; + + private MasterElement(int elementId, long elementEndPosition) { + this.elementId = elementId; + this.elementEndPosition = elementEndPosition; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/EbmlReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/EbmlReader.java new file mode 100644 index 0000000..d65a8c3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/EbmlReader.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv; + +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import java.io.IOException; + +/** + * Event-driven EBML reader that delivers events to an {@link EbmlReaderOutput}. + *

    + * EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers. It was + * originally designed for the Matroska container format. More information about EBML and + * Matroska is available here. + */ +/* package */ interface EbmlReader { + + /** + * Type for unknown elements. + */ + int TYPE_UNKNOWN = 0; + /** + * Type for elements that contain child elements. + */ + int TYPE_MASTER = 1; + /** + * Type for integer value elements of up to 8 bytes. + */ + int TYPE_UNSIGNED_INT = 2; + /** + * Type for string elements. + */ + int TYPE_STRING = 3; + /** + * Type for binary elements. + */ + int TYPE_BINARY = 4; + /** + * Type for IEEE floating point value elements of either 4 or 8 bytes. + */ + int TYPE_FLOAT = 5; + + /** + * Initializes the extractor with an {@link EbmlReaderOutput}. + * + * @param output An {@link EbmlReaderOutput} to receive events. + */ + void init(EbmlReaderOutput output); + + /** + * Resets the state of the reader. + *

    + * Subsequent calls to {@link #read(ExtractorInput)} will start reading a new EBML structure + * from scratch. + */ + void reset(); + + /** + * Reads from an {@link ExtractorInput}, invoking an event callback if possible. + * + * @param input The {@link ExtractorInput} from which data should be read. + * @return True if data can continue to be read. False if the end of the input was encountered. + * @throws ParserException If parsing fails. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + boolean read(ExtractorInput input) throws IOException, InterruptedException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/EbmlReaderOutput.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/EbmlReaderOutput.java new file mode 100644 index 0000000..315ae6d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/EbmlReaderOutput.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv; + +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import java.io.IOException; + +/** + * Defines EBML element IDs/types and reacts to events. + */ +/* package */ interface EbmlReaderOutput { + + /** + * Maps an element ID to a corresponding type. + *

    + * If {@link EbmlReader#TYPE_UNKNOWN} is returned then the element is skipped. Note that all + * children of a skipped element are also skipped. + * + * @param id The element ID to map. + * @return One of the {@code TYPE_} constants defined in {@link EbmlReader}. + */ + int getElementType(int id); + + /** + * Checks if the given id is that of a level 1 element. + * + * @param id The element ID. + * @return Whether the given id is that of a level 1 element. + */ + boolean isLevel1Element(int id); + + /** + * Called when the start of a master element is encountered. + *

    + * Following events should be considered as taking place within this element until a matching call + * to {@link #endMasterElement(int)} is made. + *

    + * Note that it is possible for another master element of the same element ID to be nested within + * itself. + * + * @param id The element ID. + * @param contentPosition The position of the start of the element's content in the stream. + * @param contentSize The size of the element's content in bytes. + * @throws ParserException If a parsing error occurs. + */ + void startMasterElement(int id, long contentPosition, long contentSize) throws ParserException; + + /** + * Called when the end of a master element is encountered. + * + * @param id The element ID. + * @throws ParserException If a parsing error occurs. + */ + void endMasterElement(int id) throws ParserException; + + /** + * Called when an integer element is encountered. + * + * @param id The element ID. + * @param value The integer value that the element contains. + * @throws ParserException If a parsing error occurs. + */ + void integerElement(int id, long value) throws ParserException; + + /** + * Called when a float element is encountered. + * + * @param id The element ID. + * @param value The float value that the element contains + * @throws ParserException If a parsing error occurs. + */ + void floatElement(int id, double value) throws ParserException; + + /** + * Called when a string element is encountered. + * + * @param id The element ID. + * @param value The string value that the element contains. + * @throws ParserException If a parsing error occurs. + */ + void stringElement(int id, String value) throws ParserException; + + /** + * Called when a binary element is encountered. + *

    + * The element header (containing the element ID and content size) will already have been read. + * Implementations are required to consume the whole remainder of the element, which is + * {@code contentSize} bytes in length, before returning. Implementations are permitted to fail + * (by throwing an exception) having partially consumed the data, however if they do this, they + * must consume the remainder of the content when called again. + * + * @param id The element ID. + * @param contentsSize The element's content size. + * @param input The {@link ExtractorInput} from which data should be read. + * @throws ParserException If a parsing error occurs. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + void binaryElement(int id, int contentsSize, ExtractorInput input) + throws IOException, InterruptedException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/MatroskaExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/MatroskaExtractor.java new file mode 100644 index 0000000..695d884 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -0,0 +1,1846 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv; + +import android.support.annotation.IntDef; +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData.SchemeData; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ChunkIndex; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.MpegAudioHeader; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.LongArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import com.tangxiaolv.telegramgallery.exoplayer2.video.AvcConfig; +import com.tangxiaolv.telegramgallery.exoplayer2.video.ColorInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.video.HevcConfig; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +/** + * Extracts data from a Matroska or WebM file. + */ +public final class MatroskaExtractor implements Extractor { + + /** + * Factory for {@link MatroskaExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new MatroskaExtractor()}; + } + + }; + + /** + * Flags controlling the behavior of the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_DISABLE_SEEK_FOR_CUES}) + public @interface Flags {} + /** + * Flag to disable seeking for cues. + *

    + * Normally (i.e. when this flag is not set) the extractor will seek to the cues element if its + * position is specified in the seek head and if it's after the first cluster. Setting this flag + * disables seeking to the cues element. If the cues element is after the first cluster then the + * media is treated as being unseekable. + */ + public static final int FLAG_DISABLE_SEEK_FOR_CUES = 1; + + private static final int UNSET_ENTRY_ID = -1; + + private static final int BLOCK_STATE_START = 0; + private static final int BLOCK_STATE_HEADER = 1; + private static final int BLOCK_STATE_DATA = 2; + + private static final String DOC_TYPE_MATROSKA = "matroska"; + private static final String DOC_TYPE_WEBM = "webm"; + private static final String CODEC_ID_VP8 = "V_VP8"; + private static final String CODEC_ID_VP9 = "V_VP9"; + private static final String CODEC_ID_MPEG2 = "V_MPEG2"; + private static final String CODEC_ID_MPEG4_SP = "V_MPEG4/ISO/SP"; + private static final String CODEC_ID_MPEG4_ASP = "V_MPEG4/ISO/ASP"; + private static final String CODEC_ID_MPEG4_AP = "V_MPEG4/ISO/AP"; + private static final String CODEC_ID_H264 = "V_MPEG4/ISO/AVC"; + private static final String CODEC_ID_H265 = "V_MPEGH/ISO/HEVC"; + private static final String CODEC_ID_FOURCC = "V_MS/VFW/FOURCC"; + private static final String CODEC_ID_THEORA = "V_THEORA"; + private static final String CODEC_ID_VORBIS = "A_VORBIS"; + private static final String CODEC_ID_OPUS = "A_OPUS"; + private static final String CODEC_ID_AAC = "A_AAC"; + private static final String CODEC_ID_MP2 = "A_MPEG/L2"; + private static final String CODEC_ID_MP3 = "A_MPEG/L3"; + private static final String CODEC_ID_AC3 = "A_AC3"; + private static final String CODEC_ID_E_AC3 = "A_EAC3"; + private static final String CODEC_ID_TRUEHD = "A_TRUEHD"; + private static final String CODEC_ID_DTS = "A_DTS"; + private static final String CODEC_ID_DTS_EXPRESS = "A_DTS/EXPRESS"; + private static final String CODEC_ID_DTS_LOSSLESS = "A_DTS/LOSSLESS"; + private static final String CODEC_ID_FLAC = "A_FLAC"; + private static final String CODEC_ID_ACM = "A_MS/ACM"; + private static final String CODEC_ID_PCM_INT_LIT = "A_PCM/INT/LIT"; + private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8"; + private static final String CODEC_ID_VOBSUB = "S_VOBSUB"; + private static final String CODEC_ID_PGS = "S_HDMV/PGS"; + private static final String CODEC_ID_DVBSUB = "S_DVBSUB"; + + private static final int VORBIS_MAX_INPUT_SIZE = 8192; + private static final int OPUS_MAX_INPUT_SIZE = 5760; + private static final int ENCRYPTION_IV_SIZE = 8; + private static final int TRACK_TYPE_AUDIO = 2; + + private static final int ID_EBML = 0x1A45DFA3; + private static final int ID_EBML_READ_VERSION = 0x42F7; + private static final int ID_DOC_TYPE = 0x4282; + private static final int ID_DOC_TYPE_READ_VERSION = 0x4285; + private static final int ID_SEGMENT = 0x18538067; + private static final int ID_SEGMENT_INFO = 0x1549A966; + private static final int ID_SEEK_HEAD = 0x114D9B74; + private static final int ID_SEEK = 0x4DBB; + private static final int ID_SEEK_ID = 0x53AB; + private static final int ID_SEEK_POSITION = 0x53AC; + private static final int ID_INFO = 0x1549A966; + private static final int ID_TIMECODE_SCALE = 0x2AD7B1; + private static final int ID_DURATION = 0x4489; + private static final int ID_CLUSTER = 0x1F43B675; + private static final int ID_TIME_CODE = 0xE7; + private static final int ID_SIMPLE_BLOCK = 0xA3; + private static final int ID_BLOCK_GROUP = 0xA0; + private static final int ID_BLOCK = 0xA1; + private static final int ID_BLOCK_DURATION = 0x9B; + private static final int ID_REFERENCE_BLOCK = 0xFB; + private static final int ID_TRACKS = 0x1654AE6B; + private static final int ID_TRACK_ENTRY = 0xAE; + private static final int ID_TRACK_NUMBER = 0xD7; + private static final int ID_TRACK_TYPE = 0x83; + private static final int ID_FLAG_DEFAULT = 0x88; + private static final int ID_FLAG_FORCED = 0x55AA; + private static final int ID_DEFAULT_DURATION = 0x23E383; + private static final int ID_CODEC_ID = 0x86; + private static final int ID_CODEC_PRIVATE = 0x63A2; + private static final int ID_CODEC_DELAY = 0x56AA; + private static final int ID_SEEK_PRE_ROLL = 0x56BB; + private static final int ID_VIDEO = 0xE0; + private static final int ID_PIXEL_WIDTH = 0xB0; + private static final int ID_PIXEL_HEIGHT = 0xBA; + private static final int ID_DISPLAY_WIDTH = 0x54B0; + private static final int ID_DISPLAY_HEIGHT = 0x54BA; + private static final int ID_DISPLAY_UNIT = 0x54B2; + private static final int ID_AUDIO = 0xE1; + private static final int ID_CHANNELS = 0x9F; + private static final int ID_AUDIO_BIT_DEPTH = 0x6264; + private static final int ID_SAMPLING_FREQUENCY = 0xB5; + private static final int ID_CONTENT_ENCODINGS = 0x6D80; + private static final int ID_CONTENT_ENCODING = 0x6240; + private static final int ID_CONTENT_ENCODING_ORDER = 0x5031; + private static final int ID_CONTENT_ENCODING_SCOPE = 0x5032; + private static final int ID_CONTENT_COMPRESSION = 0x5034; + private static final int ID_CONTENT_COMPRESSION_ALGORITHM = 0x4254; + private static final int ID_CONTENT_COMPRESSION_SETTINGS = 0x4255; + private static final int ID_CONTENT_ENCRYPTION = 0x5035; + private static final int ID_CONTENT_ENCRYPTION_ALGORITHM = 0x47E1; + private static final int ID_CONTENT_ENCRYPTION_KEY_ID = 0x47E2; + private static final int ID_CONTENT_ENCRYPTION_AES_SETTINGS = 0x47E7; + private static final int ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE = 0x47E8; + private static final int ID_CUES = 0x1C53BB6B; + private static final int ID_CUE_POINT = 0xBB; + private static final int ID_CUE_TIME = 0xB3; + private static final int ID_CUE_TRACK_POSITIONS = 0xB7; + private static final int ID_CUE_CLUSTER_POSITION = 0xF1; + private static final int ID_LANGUAGE = 0x22B59C; + private static final int ID_PROJECTION = 0x7670; + private static final int ID_PROJECTION_PRIVATE = 0x7672; + private static final int ID_STEREO_MODE = 0x53B8; + private static final int ID_COLOUR = 0x55B0; + private static final int ID_COLOUR_RANGE = 0x55B9; + private static final int ID_COLOUR_TRANSFER = 0x55BA; + private static final int ID_COLOUR_PRIMARIES = 0x55BB; + private static final int ID_MAX_CLL = 0x55BC; + private static final int ID_MAX_FALL = 0x55BD; + private static final int ID_MASTERING_METADATA = 0x55D0; + private static final int ID_PRIMARY_R_CHROMATICITY_X = 0x55D1; + private static final int ID_PRIMARY_R_CHROMATICITY_Y = 0x55D2; + private static final int ID_PRIMARY_G_CHROMATICITY_X = 0x55D3; + private static final int ID_PRIMARY_G_CHROMATICITY_Y = 0x55D4; + private static final int ID_PRIMARY_B_CHROMATICITY_X = 0x55D5; + private static final int ID_PRIMARY_B_CHROMATICITY_Y = 0x55D6; + private static final int ID_WHITE_POINT_CHROMATICITY_X = 0x55D7; + private static final int ID_WHITE_POINT_CHROMATICITY_Y = 0x55D8; + private static final int ID_LUMNINANCE_MAX = 0x55D9; + private static final int ID_LUMNINANCE_MIN = 0x55DA; + + private static final int LACING_NONE = 0; + private static final int LACING_XIPH = 1; + private static final int LACING_FIXED_SIZE = 2; + private static final int LACING_EBML = 3; + + private static final int FOURCC_COMPRESSION_VC1 = 0x31435657; + + /** + * A template for the prefix that must be added to each subrip sample. The 12 byte end timecode + * starting at {@link #SUBRIP_PREFIX_END_TIMECODE_OFFSET} is set to a dummy value, and must be + * replaced with the duration of the subtitle. + *

    + * Equivalent to the UTF-8 string: "1\n00:00:00,000 --> 00:00:00,000\n". + */ + private static final byte[] SUBRIP_PREFIX = new byte[] {49, 10, 48, 48, 58, 48, 48, 58, 48, 48, + 44, 48, 48, 48, 32, 45, 45, 62, 32, 48, 48, 58, 48, 48, 58, 48, 48, 44, 48, 48, 48, 10}; + /** + * A special end timecode indicating that a subtitle should be displayed until the next subtitle, + * or until the end of the media in the case of the last subtitle. + *

    + * Equivalent to the UTF-8 string: " ". + */ + private static final byte[] SUBRIP_TIMECODE_EMPTY = + new byte[] {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}; + /** + * The byte offset of the end timecode in {@link #SUBRIP_PREFIX}. + */ + private static final int SUBRIP_PREFIX_END_TIMECODE_OFFSET = 19; + /** + * The length in bytes of a timecode in a subrip prefix. + */ + private static final int SUBRIP_TIMECODE_LENGTH = 12; + + /** + * The length in bytes of a WAVEFORMATEX structure. + */ + private static final int WAVE_FORMAT_SIZE = 18; + /** + * Format tag indicating a WAVEFORMATEXTENSIBLE structure. + */ + private static final int WAVE_FORMAT_EXTENSIBLE = 0xFFFE; + /** + * Format tag for PCM. + */ + private static final int WAVE_FORMAT_PCM = 1; + /** + * Sub format for PCM. + */ + private static final UUID WAVE_SUBFORMAT_PCM = new UUID(0x0100000000001000L, 0x800000AA00389B71L); + + private final EbmlReader reader; + private final VarintReader varintReader; + private final SparseArray tracks; + private final boolean seekForCuesEnabled; + + // Temporary arrays. + private final ParsableByteArray nalStartCode; + private final ParsableByteArray nalLength; + private final ParsableByteArray scratch; + private final ParsableByteArray vorbisNumPageSamples; + private final ParsableByteArray seekEntryIdBytes; + private final ParsableByteArray sampleStrippedBytes; + private final ParsableByteArray subripSample; + private final ParsableByteArray encryptionInitializationVector; + private final ParsableByteArray encryptionSubsampleData; + private ByteBuffer encryptionSubsampleDataBuffer; + + private long segmentContentSize; + private long segmentContentPosition = C.POSITION_UNSET; + private long timecodeScale = C.TIME_UNSET; + private long durationTimecode = C.TIME_UNSET; + private long durationUs = C.TIME_UNSET; + + // The track corresponding to the current TrackEntry element, or null. + private Track currentTrack; + + // Whether a seek map has been sent to the output. + private boolean sentSeekMap; + + // Master seek entry related elements. + private int seekEntryId; + private long seekEntryPosition; + + // Cue related elements. + private boolean seekForCues; + private long cuesContentPosition = C.POSITION_UNSET; + private long seekPositionAfterBuildingCues = C.POSITION_UNSET; + private long clusterTimecodeUs = C.TIME_UNSET; + private LongArray cueTimesUs; + private LongArray cueClusterPositions; + private boolean seenClusterPositionForCurrentCuePoint; + + // Block reading state. + private int blockState; + private long blockTimeUs; + private long blockDurationUs; + private int blockLacingSampleIndex; + private int blockLacingSampleCount; + private int[] blockLacingSampleSizes; + private int blockTrackNumber; + private int blockTrackNumberLength; + @C.BufferFlags + private int blockFlags; + + // Sample reading state. + private int sampleBytesRead; + private boolean sampleEncodingHandled; + private boolean sampleSignalByteRead; + private boolean sampleInitializationVectorRead; + private boolean samplePartitionCountRead; + private byte sampleSignalByte; + private int samplePartitionCount; + private int sampleCurrentNalBytesRemaining; + private int sampleBytesWritten; + private boolean sampleRead; + private boolean sampleSeenReferenceBlock; + + // Extractor outputs. + private ExtractorOutput extractorOutput; + + public MatroskaExtractor() { + this(0); + } + + public MatroskaExtractor(@Flags int flags) { + this(new DefaultEbmlReader(), flags); + } + + /* package */ MatroskaExtractor(EbmlReader reader, @Flags int flags) { + this.reader = reader; + this.reader.init(new InnerEbmlReaderOutput()); + seekForCuesEnabled = (flags & FLAG_DISABLE_SEEK_FOR_CUES) == 0; + varintReader = new VarintReader(); + tracks = new SparseArray<>(); + scratch = new ParsableByteArray(4); + vorbisNumPageSamples = new ParsableByteArray(ByteBuffer.allocate(4).putInt(-1).array()); + seekEntryIdBytes = new ParsableByteArray(4); + nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); + nalLength = new ParsableByteArray(4); + sampleStrippedBytes = new ParsableByteArray(); + subripSample = new ParsableByteArray(); + encryptionInitializationVector = new ParsableByteArray(ENCRYPTION_IV_SIZE); + encryptionSubsampleData = new ParsableByteArray(); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return new Sniffer().sniff(input); + } + + @Override + public void init(ExtractorOutput output) { + extractorOutput = output; + } + + @Override + public void seek(long position, long timeUs) { + clusterTimecodeUs = C.TIME_UNSET; + blockState = BLOCK_STATE_START; + reader.reset(); + varintReader.reset(); + resetSample(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, + InterruptedException { + sampleRead = false; + boolean continueReading = true; + while (continueReading && !sampleRead) { + continueReading = reader.read(input); + if (continueReading && maybeSeekForCues(seekPosition, input.getPosition())) { + return Extractor.RESULT_SEEK; + } + } + return continueReading ? Extractor.RESULT_CONTINUE : Extractor.RESULT_END_OF_INPUT; + } + + /* package */ int getElementType(int id) { + switch (id) { + case ID_EBML: + case ID_SEGMENT: + case ID_SEEK_HEAD: + case ID_SEEK: + case ID_INFO: + case ID_CLUSTER: + case ID_TRACKS: + case ID_TRACK_ENTRY: + case ID_AUDIO: + case ID_VIDEO: + case ID_CONTENT_ENCODINGS: + case ID_CONTENT_ENCODING: + case ID_CONTENT_COMPRESSION: + case ID_CONTENT_ENCRYPTION: + case ID_CONTENT_ENCRYPTION_AES_SETTINGS: + case ID_CUES: + case ID_CUE_POINT: + case ID_CUE_TRACK_POSITIONS: + case ID_BLOCK_GROUP: + case ID_PROJECTION: + case ID_COLOUR: + case ID_MASTERING_METADATA: + return EbmlReader.TYPE_MASTER; + case ID_EBML_READ_VERSION: + case ID_DOC_TYPE_READ_VERSION: + case ID_SEEK_POSITION: + case ID_TIMECODE_SCALE: + case ID_TIME_CODE: + case ID_BLOCK_DURATION: + case ID_PIXEL_WIDTH: + case ID_PIXEL_HEIGHT: + case ID_DISPLAY_WIDTH: + case ID_DISPLAY_HEIGHT: + case ID_DISPLAY_UNIT: + case ID_TRACK_NUMBER: + case ID_TRACK_TYPE: + case ID_FLAG_DEFAULT: + case ID_FLAG_FORCED: + case ID_DEFAULT_DURATION: + case ID_CODEC_DELAY: + case ID_SEEK_PRE_ROLL: + case ID_CHANNELS: + case ID_AUDIO_BIT_DEPTH: + case ID_CONTENT_ENCODING_ORDER: + case ID_CONTENT_ENCODING_SCOPE: + case ID_CONTENT_COMPRESSION_ALGORITHM: + case ID_CONTENT_ENCRYPTION_ALGORITHM: + case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE: + case ID_CUE_TIME: + case ID_CUE_CLUSTER_POSITION: + case ID_REFERENCE_BLOCK: + case ID_STEREO_MODE: + case ID_COLOUR_RANGE: + case ID_COLOUR_TRANSFER: + case ID_COLOUR_PRIMARIES: + case ID_MAX_CLL: + case ID_MAX_FALL: + return EbmlReader.TYPE_UNSIGNED_INT; + case ID_DOC_TYPE: + case ID_CODEC_ID: + case ID_LANGUAGE: + return EbmlReader.TYPE_STRING; + case ID_SEEK_ID: + case ID_CONTENT_COMPRESSION_SETTINGS: + case ID_CONTENT_ENCRYPTION_KEY_ID: + case ID_SIMPLE_BLOCK: + case ID_BLOCK: + case ID_CODEC_PRIVATE: + case ID_PROJECTION_PRIVATE: + return EbmlReader.TYPE_BINARY; + case ID_DURATION: + case ID_SAMPLING_FREQUENCY: + case ID_PRIMARY_R_CHROMATICITY_X: + case ID_PRIMARY_R_CHROMATICITY_Y: + case ID_PRIMARY_G_CHROMATICITY_X: + case ID_PRIMARY_G_CHROMATICITY_Y: + case ID_PRIMARY_B_CHROMATICITY_X: + case ID_PRIMARY_B_CHROMATICITY_Y: + case ID_WHITE_POINT_CHROMATICITY_X: + case ID_WHITE_POINT_CHROMATICITY_Y: + case ID_LUMNINANCE_MAX: + case ID_LUMNINANCE_MIN: + return EbmlReader.TYPE_FLOAT; + default: + return EbmlReader.TYPE_UNKNOWN; + } + } + + /* package */ boolean isLevel1Element(int id) { + return id == ID_SEGMENT_INFO || id == ID_CLUSTER || id == ID_CUES || id == ID_TRACKS; + } + + /* package */ void startMasterElement(int id, long contentPosition, long contentSize) + throws ParserException { + switch (id) { + case ID_SEGMENT: + if (segmentContentPosition != C.POSITION_UNSET + && segmentContentPosition != contentPosition) { + throw new ParserException("Multiple Segment elements not supported"); + } + segmentContentPosition = contentPosition; + segmentContentSize = contentSize; + break; + case ID_SEEK: + seekEntryId = UNSET_ENTRY_ID; + seekEntryPosition = C.POSITION_UNSET; + break; + case ID_CUES: + cueTimesUs = new LongArray(); + cueClusterPositions = new LongArray(); + break; + case ID_CUE_POINT: + seenClusterPositionForCurrentCuePoint = false; + break; + case ID_CLUSTER: + if (!sentSeekMap) { + // We need to build cues before parsing the cluster. + if (seekForCuesEnabled && cuesContentPosition != C.POSITION_UNSET) { + // We know where the Cues element is located. Seek to request it. + seekForCues = true; + } else { + // We don't know where the Cues element is located. It's most likely omitted. Allow + // playback, but disable seeking. + extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); + sentSeekMap = true; + } + } + break; + case ID_BLOCK_GROUP: + sampleSeenReferenceBlock = false; + break; + case ID_CONTENT_ENCODING: + // TODO: check and fail if more than one content encoding is present. + break; + case ID_CONTENT_ENCRYPTION: + currentTrack.hasContentEncryption = true; + break; + case ID_TRACK_ENTRY: + currentTrack = new Track(); + break; + case ID_MASTERING_METADATA: + currentTrack.hasColorInfo = true; + break; + default: + break; + } + } + + /* package */ void endMasterElement(int id) throws ParserException { + switch (id) { + case ID_SEGMENT_INFO: + if (timecodeScale == C.TIME_UNSET) { + // timecodeScale was omitted. Use the default value. + timecodeScale = 1000000; + } + if (durationTimecode != C.TIME_UNSET) { + durationUs = scaleTimecodeToUs(durationTimecode); + } + break; + case ID_SEEK: + if (seekEntryId == UNSET_ENTRY_ID || seekEntryPosition == C.POSITION_UNSET) { + throw new ParserException("Mandatory element SeekID or SeekPosition not found"); + } + if (seekEntryId == ID_CUES) { + cuesContentPosition = seekEntryPosition; + } + break; + case ID_CUES: + if (!sentSeekMap) { + extractorOutput.seekMap(buildSeekMap()); + sentSeekMap = true; + } else { + // We have already built the cues. Ignore. + } + break; + case ID_BLOCK_GROUP: + if (blockState != BLOCK_STATE_DATA) { + // We've skipped this block (due to incompatible track number). + return; + } + // If the ReferenceBlock element was not found for this sample, then it is a keyframe. + if (!sampleSeenReferenceBlock) { + blockFlags |= C.BUFFER_FLAG_KEY_FRAME; + } + commitSampleToOutput(tracks.get(blockTrackNumber), blockTimeUs); + blockState = BLOCK_STATE_START; + break; + case ID_CONTENT_ENCODING: + if (currentTrack.hasContentEncryption) { + if (currentTrack.encryptionKeyId == null) { + throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); + } + currentTrack.drmInitData = new DrmInitData( + new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId)); + } + break; + case ID_CONTENT_ENCODINGS: + if (currentTrack.hasContentEncryption && currentTrack.sampleStrippedBytes != null) { + throw new ParserException("Combining encryption and compression is not supported"); + } + break; + case ID_TRACK_ENTRY: + if (isCodecSupported(currentTrack.codecId)) { + currentTrack.initializeOutput(extractorOutput, currentTrack.number); + tracks.put(currentTrack.number, currentTrack); + } + currentTrack = null; + break; + case ID_TRACKS: + if (tracks.size() == 0) { + throw new ParserException("No valid tracks were found"); + } + extractorOutput.endTracks(); + break; + default: + break; + } + } + + /* package */ void integerElement(int id, long value) throws ParserException { + switch (id) { + case ID_EBML_READ_VERSION: + // Validate that EBMLReadVersion is supported. This extractor only supports v1. + if (value != 1) { + throw new ParserException("EBMLReadVersion " + value + " not supported"); + } + break; + case ID_DOC_TYPE_READ_VERSION: + // Validate that DocTypeReadVersion is supported. This extractor only supports up to v2. + if (value < 1 || value > 2) { + throw new ParserException("DocTypeReadVersion " + value + " not supported"); + } + break; + case ID_SEEK_POSITION: + // Seek Position is the relative offset beginning from the Segment. So to get absolute + // offset from the beginning of the file, we need to add segmentContentPosition to it. + seekEntryPosition = value + segmentContentPosition; + break; + case ID_TIMECODE_SCALE: + timecodeScale = value; + break; + case ID_PIXEL_WIDTH: + currentTrack.width = (int) value; + break; + case ID_PIXEL_HEIGHT: + currentTrack.height = (int) value; + break; + case ID_DISPLAY_WIDTH: + currentTrack.displayWidth = (int) value; + break; + case ID_DISPLAY_HEIGHT: + currentTrack.displayHeight = (int) value; + break; + case ID_DISPLAY_UNIT: + currentTrack.displayUnit = (int) value; + break; + case ID_TRACK_NUMBER: + currentTrack.number = (int) value; + break; + case ID_FLAG_DEFAULT: + currentTrack.flagForced = value == 1; + break; + case ID_FLAG_FORCED: + currentTrack.flagDefault = value == 1; + break; + case ID_TRACK_TYPE: + currentTrack.type = (int) value; + break; + case ID_DEFAULT_DURATION: + currentTrack.defaultSampleDurationNs = (int) value; + break; + case ID_CODEC_DELAY: + currentTrack.codecDelayNs = value; + break; + case ID_SEEK_PRE_ROLL: + currentTrack.seekPreRollNs = value; + break; + case ID_CHANNELS: + currentTrack.channelCount = (int) value; + break; + case ID_AUDIO_BIT_DEPTH: + currentTrack.audioBitDepth = (int) value; + break; + case ID_REFERENCE_BLOCK: + sampleSeenReferenceBlock = true; + break; + case ID_CONTENT_ENCODING_ORDER: + // This extractor only supports one ContentEncoding element and hence the order has to be 0. + if (value != 0) { + throw new ParserException("ContentEncodingOrder " + value + " not supported"); + } + break; + case ID_CONTENT_ENCODING_SCOPE: + // This extractor only supports the scope of all frames. + if (value != 1) { + throw new ParserException("ContentEncodingScope " + value + " not supported"); + } + break; + case ID_CONTENT_COMPRESSION_ALGORITHM: + // This extractor only supports header stripping. + if (value != 3) { + throw new ParserException("ContentCompAlgo " + value + " not supported"); + } + break; + case ID_CONTENT_ENCRYPTION_ALGORITHM: + // Only the value 5 (AES) is allowed according to the WebM specification. + if (value != 5) { + throw new ParserException("ContentEncAlgo " + value + " not supported"); + } + break; + case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE: + // Only the value 1 is allowed according to the WebM specification. + if (value != 1) { + throw new ParserException("AESSettingsCipherMode " + value + " not supported"); + } + break; + case ID_CUE_TIME: + cueTimesUs.add(scaleTimecodeToUs(value)); + break; + case ID_CUE_CLUSTER_POSITION: + if (!seenClusterPositionForCurrentCuePoint) { + // If there's more than one video/audio track, then there could be more than one + // CueTrackPositions within a single CuePoint. In such a case, ignore all but the first + // one (since the cluster position will be quite close for all the tracks). + cueClusterPositions.add(value); + seenClusterPositionForCurrentCuePoint = true; + } + break; + case ID_TIME_CODE: + clusterTimecodeUs = scaleTimecodeToUs(value); + break; + case ID_BLOCK_DURATION: + blockDurationUs = scaleTimecodeToUs(value); + break; + case ID_STEREO_MODE: + int layout = (int) value; + switch (layout) { + case 0: + currentTrack.stereoMode = C.STEREO_MODE_MONO; + break; + case 1: + currentTrack.stereoMode = C.STEREO_MODE_LEFT_RIGHT; + break; + case 3: + currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM; + break; + case 15: + currentTrack.stereoMode = C.STEREO_MODE_STEREO_MESH; + break; + default: + break; + } + break; + case ID_COLOUR_PRIMARIES: + currentTrack.hasColorInfo = true; + switch ((int) value) { + case 1: + currentTrack.colorSpace = C.COLOR_SPACE_BT709; + break; + case 4: // BT.470M. + case 5: // BT.470BG. + case 6: // SMPTE 170M. + case 7: // SMPTE 240M. + currentTrack.colorSpace = C.COLOR_SPACE_BT601; + break; + case 9: + currentTrack.colorSpace = C.COLOR_SPACE_BT2020; + break; + default: + break; + } + break; + case ID_COLOUR_TRANSFER: + switch ((int) value) { + case 1: // BT.709. + case 6: // SMPTE 170M. + case 7: // SMPTE 240M. + currentTrack.colorTransfer = C.COLOR_TRANSFER_SDR; + break; + case 16: + currentTrack.colorTransfer = C.COLOR_TRANSFER_ST2084; + break; + case 18: + currentTrack.colorTransfer = C.COLOR_TRANSFER_HLG; + break; + default: + break; + } + break; + case ID_COLOUR_RANGE: + switch((int) value) { + case 1: // Broadcast range. + currentTrack.colorRange = C.COLOR_RANGE_LIMITED; + break; + case 2: + currentTrack.colorRange = C.COLOR_RANGE_FULL; + break; + default: + break; + } + break; + case ID_MAX_CLL: + currentTrack.maxContentLuminance = (int) value; + break; + case ID_MAX_FALL: + currentTrack.maxFrameAverageLuminance = (int) value; + break; + default: + break; + } + } + + /* package */ void floatElement(int id, double value) { + switch (id) { + case ID_DURATION: + durationTimecode = (long) value; + break; + case ID_SAMPLING_FREQUENCY: + currentTrack.sampleRate = (int) value; + break; + case ID_PRIMARY_R_CHROMATICITY_X: + currentTrack.primaryRChromaticityX = (float) value; + break; + case ID_PRIMARY_R_CHROMATICITY_Y: + currentTrack.primaryRChromaticityY = (float) value; + break; + case ID_PRIMARY_G_CHROMATICITY_X: + currentTrack.primaryGChromaticityX = (float) value; + break; + case ID_PRIMARY_G_CHROMATICITY_Y: + currentTrack.primaryGChromaticityY = (float) value; + break; + case ID_PRIMARY_B_CHROMATICITY_X: + currentTrack.primaryBChromaticityX = (float) value; + break; + case ID_PRIMARY_B_CHROMATICITY_Y: + currentTrack.primaryBChromaticityY = (float) value; + break; + case ID_WHITE_POINT_CHROMATICITY_X: + currentTrack.whitePointChromaticityX = (float) value; + break; + case ID_WHITE_POINT_CHROMATICITY_Y: + currentTrack.whitePointChromaticityY = (float) value; + break; + case ID_LUMNINANCE_MAX: + currentTrack.maxMasteringLuminance = (float) value; + break; + case ID_LUMNINANCE_MIN: + currentTrack.minMasteringLuminance = (float) value; + break; + default: + break; + } + } + + /* package */ void stringElement(int id, String value) throws ParserException { + switch (id) { + case ID_DOC_TYPE: + // Validate that DocType is supported. + if (!DOC_TYPE_WEBM.equals(value) && !DOC_TYPE_MATROSKA.equals(value)) { + throw new ParserException("DocType " + value + " not supported"); + } + break; + case ID_CODEC_ID: + currentTrack.codecId = value; + break; + case ID_LANGUAGE: + currentTrack.language = value; + break; + default: + break; + } + } + + /* package */ void binaryElement(int id, int contentSize, ExtractorInput input) + throws IOException, InterruptedException { + switch (id) { + case ID_SEEK_ID: + Arrays.fill(seekEntryIdBytes.data, (byte) 0); + input.readFully(seekEntryIdBytes.data, 4 - contentSize, contentSize); + seekEntryIdBytes.setPosition(0); + seekEntryId = (int) seekEntryIdBytes.readUnsignedInt(); + break; + case ID_CODEC_PRIVATE: + currentTrack.codecPrivate = new byte[contentSize]; + input.readFully(currentTrack.codecPrivate, 0, contentSize); + break; + case ID_PROJECTION_PRIVATE: + currentTrack.projectionData = new byte[contentSize]; + input.readFully(currentTrack.projectionData, 0, contentSize); + break; + case ID_CONTENT_COMPRESSION_SETTINGS: + // This extractor only supports header stripping, so the payload is the stripped bytes. + currentTrack.sampleStrippedBytes = new byte[contentSize]; + input.readFully(currentTrack.sampleStrippedBytes, 0, contentSize); + break; + case ID_CONTENT_ENCRYPTION_KEY_ID: + currentTrack.encryptionKeyId = new byte[contentSize]; + input.readFully(currentTrack.encryptionKeyId, 0, contentSize); + break; + case ID_SIMPLE_BLOCK: + case ID_BLOCK: + // Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure + // and http://matroska.org/technical/specs/index.html#block_structure + // for info about how data is organized in SimpleBlock and Block elements respectively. They + // differ only in the way flags are specified. + + if (blockState == BLOCK_STATE_START) { + blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true, 8); + blockTrackNumberLength = varintReader.getLastLength(); + blockDurationUs = C.TIME_UNSET; + blockState = BLOCK_STATE_HEADER; + scratch.reset(); + } + + Track track = tracks.get(blockTrackNumber); + + // Ignore the block if we don't know about the track to which it belongs. + if (track == null) { + input.skipFully(contentSize - blockTrackNumberLength); + blockState = BLOCK_STATE_START; + return; + } + + if (blockState == BLOCK_STATE_HEADER) { + // Read the relative timecode (2 bytes) and flags (1 byte). + readScratch(input, 3); + int lacing = (scratch.data[2] & 0x06) >> 1; + if (lacing == LACING_NONE) { + blockLacingSampleCount = 1; + blockLacingSampleSizes = ensureArrayCapacity(blockLacingSampleSizes, 1); + blockLacingSampleSizes[0] = contentSize - blockTrackNumberLength - 3; + } else { + if (id != ID_SIMPLE_BLOCK) { + throw new ParserException("Lacing only supported in SimpleBlocks."); + } + + // Read the sample count (1 byte). + readScratch(input, 4); + blockLacingSampleCount = (scratch.data[3] & 0xFF) + 1; + blockLacingSampleSizes = + ensureArrayCapacity(blockLacingSampleSizes, blockLacingSampleCount); + if (lacing == LACING_FIXED_SIZE) { + int blockLacingSampleSize = + (contentSize - blockTrackNumberLength - 4) / blockLacingSampleCount; + Arrays.fill(blockLacingSampleSizes, 0, blockLacingSampleCount, blockLacingSampleSize); + } else if (lacing == LACING_XIPH) { + int totalSamplesSize = 0; + int headerSize = 4; + for (int sampleIndex = 0; sampleIndex < blockLacingSampleCount - 1; sampleIndex++) { + blockLacingSampleSizes[sampleIndex] = 0; + int byteValue; + do { + readScratch(input, ++headerSize); + byteValue = scratch.data[headerSize - 1] & 0xFF; + blockLacingSampleSizes[sampleIndex] += byteValue; + } while (byteValue == 0xFF); + totalSamplesSize += blockLacingSampleSizes[sampleIndex]; + } + blockLacingSampleSizes[blockLacingSampleCount - 1] = + contentSize - blockTrackNumberLength - headerSize - totalSamplesSize; + } else if (lacing == LACING_EBML) { + int totalSamplesSize = 0; + int headerSize = 4; + for (int sampleIndex = 0; sampleIndex < blockLacingSampleCount - 1; sampleIndex++) { + blockLacingSampleSizes[sampleIndex] = 0; + readScratch(input, ++headerSize); + if (scratch.data[headerSize - 1] == 0) { + throw new ParserException("No valid varint length mask found"); + } + long readValue = 0; + for (int i = 0; i < 8; i++) { + int lengthMask = 1 << (7 - i); + if ((scratch.data[headerSize - 1] & lengthMask) != 0) { + int readPosition = headerSize - 1; + headerSize += i; + readScratch(input, headerSize); + readValue = (scratch.data[readPosition++] & 0xFF) & ~lengthMask; + while (readPosition < headerSize) { + readValue <<= 8; + readValue |= (scratch.data[readPosition++] & 0xFF); + } + // The first read value is the first size. Later values are signed offsets. + if (sampleIndex > 0) { + readValue -= (1L << (6 + i * 7)) - 1; + } + break; + } + } + if (readValue < Integer.MIN_VALUE || readValue > Integer.MAX_VALUE) { + throw new ParserException("EBML lacing sample size out of range."); + } + int intReadValue = (int) readValue; + blockLacingSampleSizes[sampleIndex] = sampleIndex == 0 + ? intReadValue : blockLacingSampleSizes[sampleIndex - 1] + intReadValue; + totalSamplesSize += blockLacingSampleSizes[sampleIndex]; + } + blockLacingSampleSizes[blockLacingSampleCount - 1] = + contentSize - blockTrackNumberLength - headerSize - totalSamplesSize; + } else { + // Lacing is always in the range 0--3. + throw new ParserException("Unexpected lacing value: " + lacing); + } + } + + int timecode = (scratch.data[0] << 8) | (scratch.data[1] & 0xFF); + blockTimeUs = clusterTimecodeUs + scaleTimecodeToUs(timecode); + boolean isInvisible = (scratch.data[2] & 0x08) == 0x08; + boolean isKeyframe = track.type == TRACK_TYPE_AUDIO + || (id == ID_SIMPLE_BLOCK && (scratch.data[2] & 0x80) == 0x80); + blockFlags = (isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0) + | (isInvisible ? C.BUFFER_FLAG_DECODE_ONLY : 0); + blockState = BLOCK_STATE_DATA; + blockLacingSampleIndex = 0; + } + + if (id == ID_SIMPLE_BLOCK) { + // For SimpleBlock, we have metadata for each sample here. + while (blockLacingSampleIndex < blockLacingSampleCount) { + writeSampleData(input, track, blockLacingSampleSizes[blockLacingSampleIndex]); + long sampleTimeUs = this.blockTimeUs + + (blockLacingSampleIndex * track.defaultSampleDurationNs) / 1000; + commitSampleToOutput(track, sampleTimeUs); + blockLacingSampleIndex++; + } + blockState = BLOCK_STATE_START; + } else { + // For Block, we send the metadata at the end of the BlockGroup element since we'll know + // if the sample is a keyframe or not only at that point. + writeSampleData(input, track, blockLacingSampleSizes[0]); + } + + break; + default: + throw new ParserException("Unexpected id: " + id); + } + } + + private void commitSampleToOutput(Track track, long timeUs) { + if (CODEC_ID_SUBRIP.equals(track.codecId)) { + writeSubripSample(track); + } + track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.encryptionKeyId); + sampleRead = true; + resetSample(); + } + + private void resetSample() { + sampleBytesRead = 0; + sampleBytesWritten = 0; + sampleCurrentNalBytesRemaining = 0; + sampleEncodingHandled = false; + sampleSignalByteRead = false; + samplePartitionCountRead = false; + samplePartitionCount = 0; + sampleSignalByte = (byte) 0; + sampleInitializationVectorRead = false; + sampleStrippedBytes.reset(); + } + + /** + * Ensures {@link #scratch} contains at least {@code requiredLength} bytes of data, reading from + * the extractor input if necessary. + */ + private void readScratch(ExtractorInput input, int requiredLength) + throws IOException, InterruptedException { + if (scratch.limit() >= requiredLength) { + return; + } + if (scratch.capacity() < requiredLength) { + scratch.reset(Arrays.copyOf(scratch.data, Math.max(scratch.data.length * 2, requiredLength)), + scratch.limit()); + } + input.readFully(scratch.data, scratch.limit(), requiredLength - scratch.limit()); + scratch.setLimit(requiredLength); + } + + private void writeSampleData(ExtractorInput input, Track track, int size) + throws IOException, InterruptedException { + if (CODEC_ID_SUBRIP.equals(track.codecId)) { + int sizeWithPrefix = SUBRIP_PREFIX.length + size; + if (subripSample.capacity() < sizeWithPrefix) { + // Initialize subripSample to contain the required prefix and have space to hold a subtitle + // twice as long as this one. + subripSample.data = Arrays.copyOf(SUBRIP_PREFIX, sizeWithPrefix + size); + } + input.readFully(subripSample.data, SUBRIP_PREFIX.length, size); + subripSample.setPosition(0); + subripSample.setLimit(sizeWithPrefix); + // Defer writing the data to the track output. We need to modify the sample data by setting + // the correct end timecode, which we might not have yet. + return; + } + + TrackOutput output = track.output; + if (!sampleEncodingHandled) { + if (track.hasContentEncryption) { + // If the sample is encrypted, read its encryption signal byte and set the IV size. + // Clear the encrypted flag. + blockFlags &= ~C.BUFFER_FLAG_ENCRYPTED; + if (!sampleSignalByteRead) { + input.readFully(scratch.data, 0, 1); + sampleBytesRead++; + if ((scratch.data[0] & 0x80) == 0x80) { + throw new ParserException("Extension bit is set in signal byte"); + } + sampleSignalByte = scratch.data[0]; + sampleSignalByteRead = true; + } + boolean isEncrypted = (sampleSignalByte & 0x01) == 0x01; + if (isEncrypted) { + boolean hasSubsampleEncryption = (sampleSignalByte & 0x02) == 0x02; + blockFlags |= C.BUFFER_FLAG_ENCRYPTED; + if (!sampleInitializationVectorRead) { + input.readFully(encryptionInitializationVector.data, 0, ENCRYPTION_IV_SIZE); + sampleBytesRead += ENCRYPTION_IV_SIZE; + sampleInitializationVectorRead = true; + // Write the signal byte, containing the IV size and the subsample encryption flag. + scratch.data[0] = (byte) (ENCRYPTION_IV_SIZE | (hasSubsampleEncryption ? 0x80 : 0x00)); + scratch.setPosition(0); + output.sampleData(scratch, 1); + sampleBytesWritten++; + // Write the IV. + encryptionInitializationVector.setPosition(0); + output.sampleData(encryptionInitializationVector, ENCRYPTION_IV_SIZE); + sampleBytesWritten += ENCRYPTION_IV_SIZE; + } + if (hasSubsampleEncryption) { + if (!samplePartitionCountRead) { + input.readFully(scratch.data, 0, 1); + sampleBytesRead++; + scratch.setPosition(0); + samplePartitionCount = scratch.readUnsignedByte(); + samplePartitionCountRead = true; + } + int samplePartitionDataSize = samplePartitionCount * 4; + scratch.reset(samplePartitionDataSize); + input.readFully(scratch.data, 0, samplePartitionDataSize); + sampleBytesRead += samplePartitionDataSize; + short subsampleCount = (short) (1 + (samplePartitionCount / 2)); + int subsampleDataSize = 2 + 6 * subsampleCount; + if (encryptionSubsampleDataBuffer == null + || encryptionSubsampleDataBuffer.capacity() < subsampleDataSize) { + encryptionSubsampleDataBuffer = ByteBuffer.allocate(subsampleDataSize); + } + encryptionSubsampleDataBuffer.position(0); + encryptionSubsampleDataBuffer.putShort(subsampleCount); + // Loop through the partition offsets and write out the data in the way ExoPlayer + // wants it (ISO 23001-7 Part 7): + // 2 bytes - sub sample count. + // for each sub sample: + // 2 bytes - clear data size. + // 4 bytes - encrypted data size. + int partitionOffset = 0; + for (int i = 0; i < samplePartitionCount; i++) { + int previousPartitionOffset = partitionOffset; + partitionOffset = scratch.readUnsignedIntToInt(); + if ((i % 2) == 0) { + encryptionSubsampleDataBuffer.putShort( + (short) (partitionOffset - previousPartitionOffset)); + } else { + encryptionSubsampleDataBuffer.putInt(partitionOffset - previousPartitionOffset); + } + } + int finalPartitionSize = size - sampleBytesRead - partitionOffset; + if ((samplePartitionCount % 2) == 1) { + encryptionSubsampleDataBuffer.putInt(finalPartitionSize); + } else { + encryptionSubsampleDataBuffer.putShort((short) finalPartitionSize); + encryptionSubsampleDataBuffer.putInt(0); + } + encryptionSubsampleData.reset(encryptionSubsampleDataBuffer.array(), subsampleDataSize); + output.sampleData(encryptionSubsampleData, subsampleDataSize); + sampleBytesWritten += subsampleDataSize; + } + } + } else if (track.sampleStrippedBytes != null) { + // If the sample has header stripping, prepare to read/output the stripped bytes first. + sampleStrippedBytes.reset(track.sampleStrippedBytes, track.sampleStrippedBytes.length); + } + sampleEncodingHandled = true; + } + size += sampleStrippedBytes.limit(); + + if (CODEC_ID_H264.equals(track.codecId) || CODEC_ID_H265.equals(track.codecId)) { + // TODO: Deduplicate with Mp4Extractor. + + // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case + // they're only 1 or 2 bytes long. + byte[] nalLengthData = nalLength.data; + nalLengthData[0] = 0; + nalLengthData[1] = 0; + nalLengthData[2] = 0; + int nalUnitLengthFieldLength = track.nalUnitLengthFieldLength; + int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength; + // NAL units are length delimited, but the decoder requires start code delimited units. + // Loop until we've written the sample to the track output, replacing length delimiters with + // start codes as we encounter them. + while (sampleBytesRead < size) { + if (sampleCurrentNalBytesRemaining == 0) { + // Read the NAL length so that we know where we find the next one. + readToTarget(input, nalLengthData, nalUnitLengthFieldLengthDiff, + nalUnitLengthFieldLength); + nalLength.setPosition(0); + sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt(); + // Write a start code for the current NAL unit. + nalStartCode.setPosition(0); + output.sampleData(nalStartCode, 4); + sampleBytesWritten += 4; + } else { + // Write the payload of the NAL unit. + sampleCurrentNalBytesRemaining -= + readToOutput(input, output, sampleCurrentNalBytesRemaining); + } + } + } else { + while (sampleBytesRead < size) { + readToOutput(input, output, size - sampleBytesRead); + } + } + + if (CODEC_ID_VORBIS.equals(track.codecId)) { + // Vorbis decoder in android MediaCodec [1] expects the last 4 bytes of the sample to be the + // number of samples in the current page. This definition holds good only for Ogg and + // irrelevant for Matroska. So we always set this to -1 (the decoder will ignore this value if + // we set it to -1). The android platform media extractor [2] does the same. + // [1] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp#314 + // [2] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/NuMediaExtractor.cpp#474 + vorbisNumPageSamples.setPosition(0); + output.sampleData(vorbisNumPageSamples, 4); + sampleBytesWritten += 4; + } + } + + private void writeSubripSample(Track track) { + setSubripSampleEndTimecode(subripSample.data, blockDurationUs); + // Note: If we ever want to support DRM protected subtitles then we'll need to output the + // appropriate encryption data here. + track.output.sampleData(subripSample, subripSample.limit()); + sampleBytesWritten += subripSample.limit(); + } + + private static void setSubripSampleEndTimecode(byte[] subripSampleData, long timeUs) { + byte[] timeCodeData; + if (timeUs == C.TIME_UNSET) { + timeCodeData = SUBRIP_TIMECODE_EMPTY; + } else { + int hours = (int) (timeUs / 3600000000L); + timeUs -= (hours * 3600000000L); + int minutes = (int) (timeUs / 60000000); + timeUs -= (minutes * 60000000); + int seconds = (int) (timeUs / 1000000); + timeUs -= (seconds * 1000000); + int milliseconds = (int) (timeUs / 1000); + timeCodeData = Util.getUtf8Bytes(String.format(Locale.US, "%02d:%02d:%02d,%03d", hours, + minutes, seconds, milliseconds)); + } + System.arraycopy(timeCodeData, 0, subripSampleData, SUBRIP_PREFIX_END_TIMECODE_OFFSET, + SUBRIP_TIMECODE_LENGTH); + } + + /** + * Writes {@code length} bytes of sample data into {@code target} at {@code offset}, consisting of + * pending {@link #sampleStrippedBytes} and any remaining data read from {@code input}. + */ + private void readToTarget(ExtractorInput input, byte[] target, int offset, int length) + throws IOException, InterruptedException { + int pendingStrippedBytes = Math.min(length, sampleStrippedBytes.bytesLeft()); + input.readFully(target, offset + pendingStrippedBytes, length - pendingStrippedBytes); + if (pendingStrippedBytes > 0) { + sampleStrippedBytes.readBytes(target, offset, pendingStrippedBytes); + } + sampleBytesRead += length; + } + + /** + * Outputs up to {@code length} bytes of sample data to {@code output}, consisting of either + * {@link #sampleStrippedBytes} or data read from {@code input}. + */ + private int readToOutput(ExtractorInput input, TrackOutput output, int length) + throws IOException, InterruptedException { + int bytesRead; + int strippedBytesLeft = sampleStrippedBytes.bytesLeft(); + if (strippedBytesLeft > 0) { + bytesRead = Math.min(length, strippedBytesLeft); + output.sampleData(sampleStrippedBytes, bytesRead); + } else { + bytesRead = output.sampleData(input, length, false); + } + sampleBytesRead += bytesRead; + sampleBytesWritten += bytesRead; + return bytesRead; + } + + /** + * Builds a {@link SeekMap} from the recently gathered Cues information. + * + * @return The built {@link SeekMap}. The returned {@link SeekMap} may be unseekable if cues + * information was missing or incomplete. + */ + private SeekMap buildSeekMap() { + if (segmentContentPosition == C.POSITION_UNSET || durationUs == C.TIME_UNSET + || cueTimesUs == null || cueTimesUs.size() == 0 + || cueClusterPositions == null || cueClusterPositions.size() != cueTimesUs.size()) { + // Cues information is missing or incomplete. + cueTimesUs = null; + cueClusterPositions = null; + return new SeekMap.Unseekable(durationUs); + } + int cuePointsSize = cueTimesUs.size(); + int[] sizes = new int[cuePointsSize]; + long[] offsets = new long[cuePointsSize]; + long[] durationsUs = new long[cuePointsSize]; + long[] timesUs = new long[cuePointsSize]; + for (int i = 0; i < cuePointsSize; i++) { + timesUs[i] = cueTimesUs.get(i); + offsets[i] = segmentContentPosition + cueClusterPositions.get(i); + } + for (int i = 0; i < cuePointsSize - 1; i++) { + sizes[i] = (int) (offsets[i + 1] - offsets[i]); + durationsUs[i] = timesUs[i + 1] - timesUs[i]; + } + sizes[cuePointsSize - 1] = + (int) (segmentContentPosition + segmentContentSize - offsets[cuePointsSize - 1]); + durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1]; + cueTimesUs = null; + cueClusterPositions = null; + return new ChunkIndex(sizes, offsets, durationsUs, timesUs); + } + + /** + * Updates the position of the holder to Cues element's position if the extractor configuration + * permits use of master seek entry. After building Cues sets the holder's position back to where + * it was before. + * + * @param seekPosition The holder whose position will be updated. + * @param currentPosition Current position of the input. + * @return Whether the seek position was updated. + */ + private boolean maybeSeekForCues(PositionHolder seekPosition, long currentPosition) { + if (seekForCues) { + seekPositionAfterBuildingCues = currentPosition; + seekPosition.position = cuesContentPosition; + seekForCues = false; + return true; + } + // After parsing Cues, seek back to original position if available. We will not do this unless + // we seeked to get to the Cues in the first place. + if (sentSeekMap && seekPositionAfterBuildingCues != C.POSITION_UNSET) { + seekPosition.position = seekPositionAfterBuildingCues; + seekPositionAfterBuildingCues = C.POSITION_UNSET; + return true; + } + return false; + } + + private long scaleTimecodeToUs(long unscaledTimecode) throws ParserException { + if (timecodeScale == C.TIME_UNSET) { + throw new ParserException("Can't scale timecode prior to timecodeScale being set."); + } + return Util.scaleLargeTimestamp(unscaledTimecode, timecodeScale, 1000); + } + + private static boolean isCodecSupported(String codecId) { + return CODEC_ID_VP8.equals(codecId) + || CODEC_ID_VP9.equals(codecId) + || CODEC_ID_MPEG2.equals(codecId) + || CODEC_ID_MPEG4_SP.equals(codecId) + || CODEC_ID_MPEG4_ASP.equals(codecId) + || CODEC_ID_MPEG4_AP.equals(codecId) + || CODEC_ID_H264.equals(codecId) + || CODEC_ID_H265.equals(codecId) + || CODEC_ID_FOURCC.equals(codecId) + || CODEC_ID_THEORA.equals(codecId) + || CODEC_ID_OPUS.equals(codecId) + || CODEC_ID_VORBIS.equals(codecId) + || CODEC_ID_AAC.equals(codecId) + || CODEC_ID_MP2.equals(codecId) + || CODEC_ID_MP3.equals(codecId) + || CODEC_ID_AC3.equals(codecId) + || CODEC_ID_E_AC3.equals(codecId) + || CODEC_ID_TRUEHD.equals(codecId) + || CODEC_ID_DTS.equals(codecId) + || CODEC_ID_DTS_EXPRESS.equals(codecId) + || CODEC_ID_DTS_LOSSLESS.equals(codecId) + || CODEC_ID_FLAC.equals(codecId) + || CODEC_ID_ACM.equals(codecId) + || CODEC_ID_PCM_INT_LIT.equals(codecId) + || CODEC_ID_SUBRIP.equals(codecId) + || CODEC_ID_VOBSUB.equals(codecId) + || CODEC_ID_PGS.equals(codecId) + || CODEC_ID_DVBSUB.equals(codecId); + } + + /** + * Returns an array that can store (at least) {@code length} elements, which will be either a new + * array or {@code array} if it's not null and large enough. + */ + private static int[] ensureArrayCapacity(int[] array, int length) { + if (array == null) { + return new int[length]; + } else if (array.length >= length) { + return array; + } else { + // Double the size to avoid allocating constantly if the required length increases gradually. + return new int[Math.max(array.length * 2, length)]; + } + } + + /** + * Passes events through to the outer {@link MatroskaExtractor}. + */ + private final class InnerEbmlReaderOutput implements EbmlReaderOutput { + + @Override + public int getElementType(int id) { + return MatroskaExtractor.this.getElementType(id); + } + + @Override + public boolean isLevel1Element(int id) { + return MatroskaExtractor.this.isLevel1Element(id); + } + + @Override + public void startMasterElement(int id, long contentPosition, long contentSize) + throws ParserException { + MatroskaExtractor.this.startMasterElement(id, contentPosition, contentSize); + } + + @Override + public void endMasterElement(int id) throws ParserException { + MatroskaExtractor.this.endMasterElement(id); + } + + @Override + public void integerElement(int id, long value) throws ParserException { + MatroskaExtractor.this.integerElement(id, value); + } + + @Override + public void floatElement(int id, double value) throws ParserException { + MatroskaExtractor.this.floatElement(id, value); + } + + @Override + public void stringElement(int id, String value) throws ParserException { + MatroskaExtractor.this.stringElement(id, value); + } + + @Override + public void binaryElement(int id, int contentsSize, ExtractorInput input) + throws IOException, InterruptedException { + MatroskaExtractor.this.binaryElement(id, contentsSize, input); + } + + } + + private static final class Track { + + private static final int DISPLAY_UNIT_PIXELS = 0; + private static final int MAX_CHROMATICITY = 50000; // Defined in CTA-861.3. + /** + * Default max content light level (CLL) that should be encoded into hdrStaticInfo. + */ + private static final int DEFAULT_MAX_CLL = 1000; // nits. + + /** + * Default frame-average light level (FALL) that should be encoded into hdrStaticInfo. + */ + private static final int DEFAULT_MAX_FALL = 200; // nits. + + // Common elements. + public String codecId; + public int number; + public int type; + public int defaultSampleDurationNs; + public boolean hasContentEncryption; + public byte[] sampleStrippedBytes; + public byte[] encryptionKeyId; + public byte[] codecPrivate; + public DrmInitData drmInitData; + + // Video elements. + public int width = Format.NO_VALUE; + public int height = Format.NO_VALUE; + public int displayWidth = Format.NO_VALUE; + public int displayHeight = Format.NO_VALUE; + public int displayUnit = DISPLAY_UNIT_PIXELS; + public byte[] projectionData = null; + @C.StereoMode + public int stereoMode = Format.NO_VALUE; + public boolean hasColorInfo = false; + @C.ColorSpace + public int colorSpace = Format.NO_VALUE; + @C.ColorTransfer + public int colorTransfer = Format.NO_VALUE; + @C.ColorRange + public int colorRange = Format.NO_VALUE; + public int maxContentLuminance = DEFAULT_MAX_CLL; + public int maxFrameAverageLuminance = DEFAULT_MAX_FALL; + public float primaryRChromaticityX = Format.NO_VALUE; + public float primaryRChromaticityY = Format.NO_VALUE; + public float primaryGChromaticityX = Format.NO_VALUE; + public float primaryGChromaticityY = Format.NO_VALUE; + public float primaryBChromaticityX = Format.NO_VALUE; + public float primaryBChromaticityY = Format.NO_VALUE; + public float whitePointChromaticityX = Format.NO_VALUE; + public float whitePointChromaticityY = Format.NO_VALUE; + public float maxMasteringLuminance = Format.NO_VALUE; + public float minMasteringLuminance = Format.NO_VALUE; + + // Audio elements. Initially set to their default values. + public int channelCount = 1; + public int audioBitDepth = Format.NO_VALUE; + public int sampleRate = 8000; + public long codecDelayNs = 0; + public long seekPreRollNs = 0; + + // Text elements. + public boolean flagForced; + public boolean flagDefault = true; + private String language = "eng"; + + // Set when the output is initialized. nalUnitLengthFieldLength is only set for H264/H265. + public TrackOutput output; + public int nalUnitLengthFieldLength; + + /** + * Initializes the track with an output. + */ + public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException { + String mimeType; + int maxInputSize = Format.NO_VALUE; + @C.PcmEncoding int pcmEncoding = Format.NO_VALUE; + List initializationData = null; + switch (codecId) { + case CODEC_ID_VP8: + mimeType = MimeTypes.VIDEO_VP8; + break; + case CODEC_ID_VP9: + mimeType = MimeTypes.VIDEO_VP9; + break; + case CODEC_ID_MPEG2: + mimeType = MimeTypes.VIDEO_MPEG2; + break; + case CODEC_ID_MPEG4_SP: + case CODEC_ID_MPEG4_ASP: + case CODEC_ID_MPEG4_AP: + mimeType = MimeTypes.VIDEO_MP4V; + initializationData = + codecPrivate == null ? null : Collections.singletonList(codecPrivate); + break; + case CODEC_ID_H264: + mimeType = MimeTypes.VIDEO_H264; + AvcConfig avcConfig = AvcConfig.parse(new ParsableByteArray(codecPrivate)); + initializationData = avcConfig.initializationData; + nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; + break; + case CODEC_ID_H265: + mimeType = MimeTypes.VIDEO_H265; + HevcConfig hevcConfig = HevcConfig.parse(new ParsableByteArray(codecPrivate)); + initializationData = hevcConfig.initializationData; + nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; + break; + case CODEC_ID_FOURCC: + initializationData = parseFourCcVc1Private(new ParsableByteArray(codecPrivate)); + mimeType = initializationData == null ? MimeTypes.VIDEO_UNKNOWN : MimeTypes.VIDEO_VC1; + break; + case CODEC_ID_THEORA: + // TODO: This can be set to the real mimeType if/when we work out what initializationData + // should be set to for this case. + mimeType = MimeTypes.VIDEO_UNKNOWN; + break; + case CODEC_ID_VORBIS: + mimeType = MimeTypes.AUDIO_VORBIS; + maxInputSize = VORBIS_MAX_INPUT_SIZE; + initializationData = parseVorbisCodecPrivate(codecPrivate); + break; + case CODEC_ID_OPUS: + mimeType = MimeTypes.AUDIO_OPUS; + maxInputSize = OPUS_MAX_INPUT_SIZE; + initializationData = new ArrayList<>(3); + initializationData.add(codecPrivate); + initializationData.add( + ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(codecDelayNs).array()); + initializationData.add( + ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(seekPreRollNs).array()); + break; + case CODEC_ID_AAC: + mimeType = MimeTypes.AUDIO_AAC; + initializationData = Collections.singletonList(codecPrivate); + break; + case CODEC_ID_MP2: + mimeType = MimeTypes.AUDIO_MPEG_L2; + maxInputSize = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; + break; + case CODEC_ID_MP3: + mimeType = MimeTypes.AUDIO_MPEG; + maxInputSize = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; + break; + case CODEC_ID_AC3: + mimeType = MimeTypes.AUDIO_AC3; + break; + case CODEC_ID_E_AC3: + mimeType = MimeTypes.AUDIO_E_AC3; + break; + case CODEC_ID_TRUEHD: + mimeType = MimeTypes.AUDIO_TRUEHD; + break; + case CODEC_ID_DTS: + case CODEC_ID_DTS_EXPRESS: + mimeType = MimeTypes.AUDIO_DTS; + break; + case CODEC_ID_DTS_LOSSLESS: + mimeType = MimeTypes.AUDIO_DTS_HD; + break; + case CODEC_ID_FLAC: + mimeType = MimeTypes.AUDIO_FLAC; + initializationData = Collections.singletonList(codecPrivate); + break; + case CODEC_ID_ACM: + mimeType = MimeTypes.AUDIO_RAW; + if (!parseMsAcmCodecPrivate(new ParsableByteArray(codecPrivate))) { + throw new ParserException("Non-PCM MS/ACM is unsupported"); + } + pcmEncoding = Util.getPcmEncoding(audioBitDepth); + if (pcmEncoding == C.ENCODING_INVALID) { + throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); + } + break; + case CODEC_ID_PCM_INT_LIT: + mimeType = MimeTypes.AUDIO_RAW; + pcmEncoding = Util.getPcmEncoding(audioBitDepth); + if (pcmEncoding == C.ENCODING_INVALID) { + throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); + } + break; + case CODEC_ID_SUBRIP: + mimeType = MimeTypes.APPLICATION_SUBRIP; + break; + case CODEC_ID_VOBSUB: + mimeType = MimeTypes.APPLICATION_VOBSUB; + initializationData = Collections.singletonList(codecPrivate); + break; + case CODEC_ID_PGS: + mimeType = MimeTypes.APPLICATION_PGS; + break; + case CODEC_ID_DVBSUB: + mimeType = MimeTypes.APPLICATION_DVBSUBS; + // Init data: composition_page (2), ancillary_page (2) + initializationData = Collections.singletonList(new byte[] {codecPrivate[0], + codecPrivate[1], codecPrivate[2], codecPrivate[3]}); + break; + default: + throw new ParserException("Unrecognized codec identifier."); + } + + int type; + Format format; + @C.SelectionFlags int selectionFlags = 0; + selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0; + selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0; + // TODO: Consider reading the name elements of the tracks and, if present, incorporating them + // into the trackId passed when creating the formats. + if (MimeTypes.isAudio(mimeType)) { + type = C.TRACK_TYPE_AUDIO; + format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, maxInputSize, channelCount, sampleRate, pcmEncoding, + initializationData, drmInitData, selectionFlags, language); + } else if (MimeTypes.isVideo(mimeType)) { + type = C.TRACK_TYPE_VIDEO; + if (displayUnit == Track.DISPLAY_UNIT_PIXELS) { + displayWidth = displayWidth == Format.NO_VALUE ? width : displayWidth; + displayHeight = displayHeight == Format.NO_VALUE ? height : displayHeight; + } + float pixelWidthHeightRatio = Format.NO_VALUE; + if (displayWidth != Format.NO_VALUE && displayHeight != Format.NO_VALUE) { + pixelWidthHeightRatio = ((float) (height * displayWidth)) / (width * displayHeight); + } + ColorInfo colorInfo = null; + if (hasColorInfo) { + byte[] hdrStaticInfo = getHdrStaticInfo(); + colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo); + } + format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, + Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, + drmInitData); + } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { + type = C.TRACK_TYPE_TEXT; + format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, selectionFlags, language, drmInitData); + } else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType) + || MimeTypes.APPLICATION_PGS.equals(mimeType) + || MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) { + type = C.TRACK_TYPE_TEXT; + format = Format.createImageSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, initializationData, language, drmInitData); + } else { + throw new ParserException("Unexpected MIME type."); + } + + this.output = output.track(number, type); + this.output.format(format); + } + + /** + * Returns the HDR Static Info as defined in CTA-861.3. + */ + private byte[] getHdrStaticInfo() { + // Are all fields present. + if (primaryRChromaticityX == Format.NO_VALUE || primaryRChromaticityY == Format.NO_VALUE + || primaryGChromaticityX == Format.NO_VALUE || primaryGChromaticityY == Format.NO_VALUE + || primaryBChromaticityX == Format.NO_VALUE || primaryBChromaticityY == Format.NO_VALUE + || whitePointChromaticityX == Format.NO_VALUE + || whitePointChromaticityY == Format.NO_VALUE || maxMasteringLuminance == Format.NO_VALUE + || minMasteringLuminance == Format.NO_VALUE) { + return null; + } + + byte[] hdrStaticInfoData = new byte[25]; + ByteBuffer hdrStaticInfo = ByteBuffer.wrap(hdrStaticInfoData); + hdrStaticInfo.put((byte) 0); // Type. + hdrStaticInfo.putShort((short) ((primaryRChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryRChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryGChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryGChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryBChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryBChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((whitePointChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((whitePointChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) (maxMasteringLuminance + 0.5f)); + hdrStaticInfo.putShort((short) (minMasteringLuminance + 0.5f)); + hdrStaticInfo.putShort((short) maxContentLuminance); + hdrStaticInfo.putShort((short) maxFrameAverageLuminance); + return hdrStaticInfoData; + } + + /** + * Builds initialization data for a {@link Format} from FourCC codec private data. + *

    + * VC1 is the only supported compression type. + * + * @return The initialization data for the {@link Format}, or null if the compression type is + * not VC1. + * @throws ParserException If the initialization data could not be built. + */ + private static List parseFourCcVc1Private(ParsableByteArray buffer) + throws ParserException { + try { + buffer.skipBytes(16); // size(4), width(4), height(4), planes(2), bitcount(2). + long compression = buffer.readLittleEndianUnsignedInt(); + if (compression != FOURCC_COMPRESSION_VC1) { + return null; + } + + // Search for the initialization data from the end of the BITMAPINFOHEADER. The last 20 + // bytes of which are: sizeImage(4), xPel/m (4), yPel/m (4), clrUsed(4), clrImportant(4). + int startOffset = buffer.getPosition() + 20; + byte[] bufferData = buffer.data; + for (int offset = startOffset; offset < bufferData.length - 4; offset++) { + if (bufferData[offset] == 0x00 && bufferData[offset + 1] == 0x00 + && bufferData[offset + 2] == 0x01 && bufferData[offset + 3] == 0x0F) { + // We've found the initialization data. + byte[] initializationData = Arrays.copyOfRange(bufferData, offset, bufferData.length); + return Collections.singletonList(initializationData); + } + } + + throw new ParserException("Failed to find FourCC VC1 initialization data"); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing FourCC VC1 codec private"); + } + } + + /** + * Builds initialization data for a {@link Format} from Vorbis codec private data. + * + * @return The initialization data for the {@link Format}. + * @throws ParserException If the initialization data could not be built. + */ + private static List parseVorbisCodecPrivate(byte[] codecPrivate) + throws ParserException { + try { + if (codecPrivate[0] != 0x02) { + throw new ParserException("Error parsing vorbis codec private"); + } + int offset = 1; + int vorbisInfoLength = 0; + while (codecPrivate[offset] == (byte) 0xFF) { + vorbisInfoLength += 0xFF; + offset++; + } + vorbisInfoLength += codecPrivate[offset++]; + + int vorbisSkipLength = 0; + while (codecPrivate[offset] == (byte) 0xFF) { + vorbisSkipLength += 0xFF; + offset++; + } + vorbisSkipLength += codecPrivate[offset++]; + + if (codecPrivate[offset] != 0x01) { + throw new ParserException("Error parsing vorbis codec private"); + } + byte[] vorbisInfo = new byte[vorbisInfoLength]; + System.arraycopy(codecPrivate, offset, vorbisInfo, 0, vorbisInfoLength); + offset += vorbisInfoLength; + if (codecPrivate[offset] != 0x03) { + throw new ParserException("Error parsing vorbis codec private"); + } + offset += vorbisSkipLength; + if (codecPrivate[offset] != 0x05) { + throw new ParserException("Error parsing vorbis codec private"); + } + byte[] vorbisBooks = new byte[codecPrivate.length - offset]; + System.arraycopy(codecPrivate, offset, vorbisBooks, 0, codecPrivate.length - offset); + List initializationData = new ArrayList<>(2); + initializationData.add(vorbisInfo); + initializationData.add(vorbisBooks); + return initializationData; + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing vorbis codec private"); + } + } + + /** + * Parses an MS/ACM codec private, returning whether it indicates PCM audio. + * + * @return Whether the codec private indicates PCM audio. + * @throws ParserException If a parsing error occurs. + */ + private static boolean parseMsAcmCodecPrivate(ParsableByteArray buffer) throws ParserException { + try { + int formatTag = buffer.readLittleEndianUnsignedShort(); + if (formatTag == WAVE_FORMAT_PCM) { + return true; + } else if (formatTag == WAVE_FORMAT_EXTENSIBLE) { + buffer.setPosition(WAVE_FORMAT_SIZE + 6); // unionSamples(2), channelMask(4) + return buffer.readLong() == WAVE_SUBFORMAT_PCM.getMostSignificantBits() + && buffer.readLong() == WAVE_SUBFORMAT_PCM.getLeastSignificantBits(); + } else { + return false; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing MS/ACM codec private"); + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/Sniffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/Sniffer.java new file mode 100644 index 0000000..cb22eb9 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/Sniffer.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * Utility class that peeks from the input stream in order to determine whether it appears to be + * compatible input for this extractor. + */ +/* package */ final class Sniffer { + + /** + * The number of bytes to search for a valid header in {@link #sniff(ExtractorInput)}. + */ + private static final int SEARCH_LENGTH = 1024; + private static final int ID_EBML = 0x1A45DFA3; + + private final ParsableByteArray scratch; + private int peekLength; + + public Sniffer() { + scratch = new ParsableByteArray(8); + } + + /** + * @see com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor#sniff(ExtractorInput) + */ + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + long inputLength = input.getLength(); + int bytesToSearch = (int) (inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH + ? SEARCH_LENGTH : inputLength); + // Find four bytes equal to ID_EBML near the start of the input. + input.peekFully(scratch.data, 0, 4); + long tag = scratch.readUnsignedInt(); + peekLength = 4; + while (tag != ID_EBML) { + if (++peekLength == bytesToSearch) { + return false; + } + input.peekFully(scratch.data, 0, 1); + tag = (tag << 8) & 0xFFFFFF00; + tag |= scratch.data[0] & 0xFF; + } + + // Read the size of the EBML header and make sure it is within the stream. + long headerSize = readUint(input); + long headerStart = peekLength; + if (headerSize == Long.MIN_VALUE + || (inputLength != C.LENGTH_UNSET && headerStart + headerSize >= inputLength)) { + return false; + } + + // Read the payload elements in the EBML header. + while (peekLength < headerStart + headerSize) { + long id = readUint(input); + if (id == Long.MIN_VALUE) { + return false; + } + long size = readUint(input); + if (size < 0 || size > Integer.MAX_VALUE) { + return false; + } + if (size != 0) { + input.advancePeekPosition((int) size); + peekLength += size; + } + } + return peekLength == headerStart + headerSize; + } + + /** + * Peeks a variable-length unsigned EBML integer from the input. + */ + private long readUint(ExtractorInput input) throws IOException, InterruptedException { + input.peekFully(scratch.data, 0, 1); + int value = scratch.data[0] & 0xFF; + if (value == 0) { + return Long.MIN_VALUE; + } + int mask = 0x80; + int length = 0; + while ((value & mask) == 0) { + mask >>= 1; + length++; + } + value &= ~mask; + input.peekFully(scratch.data, 1, length); + for (int i = 0; i < length; i++) { + value <<= 8; + value += scratch.data[i + 1] & 0xFF; + } + peekLength += length + 1; + return value; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/VarintReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/VarintReader.java new file mode 100644 index 0000000..889ab3d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mkv/VarintReader.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import java.io.EOFException; +import java.io.IOException; + +/** + * Reads EBML variable-length integers (varints) from an {@link ExtractorInput}. + */ +/* package */ final class VarintReader { + + private static final int STATE_BEGIN_READING = 0; + private static final int STATE_READ_CONTENTS = 1; + + /** + * The first byte of a variable-length integer (varint) will have one of these bit masks + * indicating the total length in bytes. + * + *

    {@code 0x80} is a one-byte integer, {@code 0x40} is two bytes, and so on up to eight bytes. + */ + private static final long[] VARINT_LENGTH_MASKS = new long[] { + 0x80L, 0x40L, 0x20L, 0x10L, 0x08L, 0x04L, 0x02L, 0x01L + }; + + private final byte[] scratch; + + private int state; + private int length; + + public VarintReader() { + scratch = new byte[8]; + } + + /** + * Resets the reader to start reading a new variable-length integer. + */ + public void reset() { + state = STATE_BEGIN_READING; + length = 0; + } + + /** + * Reads an EBML variable-length integer (varint) from an {@link ExtractorInput} such that + * reading can be resumed later if an error occurs having read only some of it. + *

    + * If an value is successfully read, then the reader will automatically reset itself ready to + * read another value. + *

    + * If an {@link IOException} or {@link InterruptedException} is throw, the read can be resumed + * later by calling this method again, passing an {@link ExtractorInput} providing data starting + * where the previous one left off. + * + * @param input The {@link ExtractorInput} from which the integer should be read. + * @param allowEndOfInput True if encountering the end of the input having read no data is + * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it + * should be considered an error, causing an {@link EOFException} to be thrown. + * @param removeLengthMask Removes the variable-length integer length mask from the value. + * @param maximumAllowedLength Maximum allowed length of the variable integer to be read. + * @return The read value, or {@link C#RESULT_END_OF_INPUT} if {@code allowEndOfStream} is true + * and the end of the input was encountered, or {@link C#RESULT_MAX_LENGTH_EXCEEDED} if the + * length of the varint exceeded maximumAllowedLength. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + public long readUnsignedVarint(ExtractorInput input, boolean allowEndOfInput, + boolean removeLengthMask, int maximumAllowedLength) throws IOException, InterruptedException { + if (state == STATE_BEGIN_READING) { + // Read the first byte to establish the length. + if (!input.readFully(scratch, 0, 1, allowEndOfInput)) { + return C.RESULT_END_OF_INPUT; + } + int firstByte = scratch[0] & 0xFF; + length = parseUnsignedVarintLength(firstByte); + if (length == C.LENGTH_UNSET) { + throw new IllegalStateException("No valid varint length mask found"); + } + state = STATE_READ_CONTENTS; + } + + if (length > maximumAllowedLength) { + state = STATE_BEGIN_READING; + return C.RESULT_MAX_LENGTH_EXCEEDED; + } + + if (length != 1) { + // Read the remaining bytes. + input.readFully(scratch, 1, length - 1); + } + + state = STATE_BEGIN_READING; + return assembleVarint(scratch, length, removeLengthMask); + } + + /** + * Returns the number of bytes occupied by the most recently parsed varint. + */ + public int getLastLength() { + return length; + } + + /** + * Parses and the length of the varint given the first byte. + * + * @param firstByte First byte of the varint. + * @return Length of the varint beginning with the given byte if it was valid, + * {@link C#LENGTH_UNSET} otherwise. + */ + public static int parseUnsignedVarintLength(int firstByte) { + int varIntLength = C.LENGTH_UNSET; + for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) { + if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) { + varIntLength = i + 1; + break; + } + } + return varIntLength; + } + + /** + * Assemble a varint from the given byte array. + * + * @param varintBytes Bytes that make up the varint. + * @param varintLength Length of the varint to assemble. + * @param removeLengthMask Removes the variable-length integer length mask from the value. + * @return Parsed and assembled varint. + */ + public static long assembleVarint(byte[] varintBytes, int varintLength, + boolean removeLengthMask) { + long varint = varintBytes[0] & 0xFFL; + if (removeLengthMask) { + varint &= ~VARINT_LENGTH_MASKS[varintLength - 1]; + } + for (int i = 1; i < varintLength; i++) { + varint = (varint << 8) | (varintBytes[i] & 0xFFL); + } + return varint; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java new file mode 100644 index 0000000..7aa9483 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp3; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +/** + * MP3 seeker that doesn't rely on metadata and seeks assuming the source has a constant bitrate. + */ +/* package */ final class ConstantBitrateSeeker implements Mp3Extractor.Seeker { + + private static final int BITS_PER_BYTE = 8; + + private final long firstFramePosition; + private final int bitrate; + private final long durationUs; + + public ConstantBitrateSeeker(long firstFramePosition, int bitrate, long inputLength) { + this.firstFramePosition = firstFramePosition; + this.bitrate = bitrate; + durationUs = inputLength == C.LENGTH_UNSET ? C.TIME_UNSET : getTimeUs(inputLength); + } + + @Override + public boolean isSeekable() { + return durationUs != C.TIME_UNSET; + } + + @Override + public long getPosition(long timeUs) { + return durationUs == C.TIME_UNSET ? 0 + : firstFramePosition + (timeUs * bitrate) / (C.MICROS_PER_SECOND * BITS_PER_BYTE); + } + + @Override + public long getTimeUs(long position) { + return (Math.max(0, position - firstFramePosition) * C.MICROS_PER_SECOND * BITS_PER_BYTE) + / bitrate; + } + + @Override + public long getDurationUs() { + return durationUs; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/Mp3Extractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/Mp3Extractor.java new file mode 100644 index 0000000..b1795a4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp3; + +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.GaplessInfoHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.MpegAudioHeader; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.Id3Decoder; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Extracts data from an MP3 file. + */ +public final class Mp3Extractor implements Extractor { + + /** + * Factory for {@link Mp3Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new Mp3Extractor()}; + } + + }; + + /** + * Flags controlling the behavior of the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_DISABLE_ID3_METADATA}) + public @interface Flags {} + /** + * Flag to force enable seeking using a constant bitrate assumption in cases where seeking would + * otherwise not be possible. + */ + public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1; + /** + * Flag to disable parsing of ID3 metadata. Can be set to save memory if ID3 metadata is not + * required. + */ + public static final int FLAG_DISABLE_ID3_METADATA = 2; + + /** + * The maximum number of bytes to search when synchronizing, before giving up. + */ + private static final int MAX_SYNC_BYTES = 128 * 1024; + /** + * The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up. + */ + private static final int MAX_SNIFF_BYTES = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; + /** + * Maximum length of data read into {@link #scratch}. + */ + private static final int SCRATCH_LENGTH = 10; + + /** + * Mask that includes the audio header values that must match between frames. + */ + private static final int HEADER_MASK = 0xFFFE0C00; + private static final int XING_HEADER = Util.getIntegerCodeForString("Xing"); + private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); + private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); + + @Flags private final int flags; + private final long forcedFirstSampleTimestampUs; + private final ParsableByteArray scratch; + private final MpegAudioHeader synchronizedHeader; + private final GaplessInfoHolder gaplessInfoHolder; + + // Extractor outputs. + private ExtractorOutput extractorOutput; + private TrackOutput trackOutput; + + private int synchronizedHeaderData; + + private Metadata metadata; + private Seeker seeker; + private long basisTimeUs; + private long samplesRead; + private int sampleBytesRemaining; + + /** + * Constructs a new {@link Mp3Extractor}. + */ + public Mp3Extractor() { + this(0); + } + + /** + * Constructs a new {@link Mp3Extractor}. + * + * @param flags Flags that control the extractor's behavior. + */ + public Mp3Extractor(@Flags int flags) { + this(flags, C.TIME_UNSET); + } + + /** + * Constructs a new {@link Mp3Extractor}. + * + * @param flags Flags that control the extractor's behavior. + * @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or + * {@link C#TIME_UNSET} if forcing is not required. + */ + public Mp3Extractor(@Flags int flags, long forcedFirstSampleTimestampUs) { + this.flags = flags; + this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs; + scratch = new ParsableByteArray(SCRATCH_LENGTH); + synchronizedHeader = new MpegAudioHeader(); + gaplessInfoHolder = new GaplessInfoHolder(); + basisTimeUs = C.TIME_UNSET; + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return synchronize(input, true); + } + + @Override + public void init(ExtractorOutput output) { + extractorOutput = output; + trackOutput = extractorOutput.track(0, C.TRACK_TYPE_AUDIO); + extractorOutput.endTracks(); + } + + @Override + public void seek(long position, long timeUs) { + synchronizedHeaderData = 0; + basisTimeUs = C.TIME_UNSET; + samplesRead = 0; + sampleBytesRemaining = 0; + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + if (synchronizedHeaderData == 0) { + try { + synchronize(input, false); + } catch (EOFException e) { + return RESULT_END_OF_INPUT; + } + } + if (seeker == null) { + seeker = setupSeeker(input); + extractorOutput.seekMap(seeker); + trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null, + Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels, + synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay, + gaplessInfoHolder.encoderPadding, null, null, 0, null, + (flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : metadata)); + } + return readSample(input); + } + + private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { + if (sampleBytesRemaining == 0) { + extractorInput.resetPeekPosition(); + if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { + return RESULT_END_OF_INPUT; + } + scratch.setPosition(0); + int sampleHeaderData = scratch.readInt(); + if ((sampleHeaderData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK) + || MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) { + // We have lost synchronization, so attempt to resynchronize starting at the next byte. + extractorInput.skipFully(1); + synchronizedHeaderData = 0; + return RESULT_CONTINUE; + } + MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader); + if (basisTimeUs == C.TIME_UNSET) { + basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); + if (forcedFirstSampleTimestampUs != C.TIME_UNSET) { + long embeddedFirstSampleTimestampUs = seeker.getTimeUs(0); + basisTimeUs += forcedFirstSampleTimestampUs - embeddedFirstSampleTimestampUs; + } + } + sampleBytesRemaining = synchronizedHeader.frameSize; + } + int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true); + if (bytesAppended == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + sampleBytesRemaining -= bytesAppended; + if (sampleBytesRemaining > 0) { + return RESULT_CONTINUE; + } + long timeUs = basisTimeUs + (samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate); + trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, synchronizedHeader.frameSize, 0, + null); + samplesRead += synchronizedHeader.samplesPerFrame; + sampleBytesRemaining = 0; + return RESULT_CONTINUE; + } + + private boolean synchronize(ExtractorInput input, boolean sniffing) + throws IOException, InterruptedException { + int validFrameCount = 0; + int candidateSynchronizedHeaderData = 0; + int peekedId3Bytes = 0; + int searchedBytes = 0; + int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES; + input.resetPeekPosition(); + if (input.getPosition() == 0) { + peekId3Data(input); + peekedId3Bytes = (int) input.getPeekPosition(); + if (!sniffing) { + input.skipFully(peekedId3Bytes); + } + } + while (true) { + if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) { + // We reached the end of the stream but found at least one valid frame. + break; + } + scratch.setPosition(0); + int headerData = scratch.readInt(); + int frameSize; + if ((candidateSynchronizedHeaderData != 0 + && (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK)) + || (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) { + // The header doesn't match the candidate header or is invalid. Try the next byte offset. + if (searchedBytes++ == searchLimitBytes) { + if (!sniffing) { + throw new ParserException("Searched too many bytes."); + } + return false; + } + validFrameCount = 0; + candidateSynchronizedHeaderData = 0; + if (sniffing) { + input.resetPeekPosition(); + input.advancePeekPosition(peekedId3Bytes + searchedBytes); + } else { + input.skipFully(1); + } + } else { + // The header matches the candidate header and/or is valid. + validFrameCount++; + if (validFrameCount == 1) { + MpegAudioHeader.populateHeader(headerData, synchronizedHeader); + candidateSynchronizedHeaderData = headerData; + } else if (validFrameCount == 4) { + break; + } + input.advancePeekPosition(frameSize - 4); + } + } + // Prepare to read the synchronized frame. + if (sniffing) { + input.skipFully(peekedId3Bytes + searchedBytes); + } else { + input.resetPeekPosition(); + } + synchronizedHeaderData = candidateSynchronizedHeaderData; + return true; + } + + /** + * Peeks ID3 data from the input, including gapless playback information. + * + * @param input The {@link ExtractorInput} from which data should be peeked. + * @throws IOException If an error occurred peeking from the input. + * @throws InterruptedException If the thread was interrupted. + */ + private void peekId3Data(ExtractorInput input) throws IOException, InterruptedException { + int peekedId3Bytes = 0; + while (true) { + input.peekFully(scratch.data, 0, Id3Decoder.ID3_HEADER_LENGTH); + scratch.setPosition(0); + if (scratch.readUnsignedInt24() != Id3Decoder.ID3_TAG) { + // Not an ID3 tag. + break; + } + scratch.skipBytes(3); // Skip major version, minor version and flags. + int framesLength = scratch.readSynchSafeInt(); + int tagLength = Id3Decoder.ID3_HEADER_LENGTH + framesLength; + + if (metadata == null) { + byte[] id3Data = new byte[tagLength]; + System.arraycopy(scratch.data, 0, id3Data, 0, Id3Decoder.ID3_HEADER_LENGTH); + input.peekFully(id3Data, Id3Decoder.ID3_HEADER_LENGTH, framesLength); + // We need to parse enough ID3 metadata to retrieve any gapless playback information even + // if ID3 metadata parsing is disabled. + Id3Decoder.FramePredicate id3FramePredicate = (flags & FLAG_DISABLE_ID3_METADATA) != 0 + ? GaplessInfoHolder.GAPLESS_INFO_ID3_FRAME_PREDICATE : null; + metadata = new Id3Decoder(id3FramePredicate).decode(id3Data, tagLength); + if (metadata != null) { + gaplessInfoHolder.setFromMetadata(metadata); + } + } else { + input.advancePeekPosition(framesLength); + } + + peekedId3Bytes += tagLength; + } + + input.resetPeekPosition(); + input.advancePeekPosition(peekedId3Bytes); + } + + /** + * Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide + * data from the start of the first frame in the stream. On returning, the input's position will + * be set to the start of the first frame of audio. + * + * @param input The {@link ExtractorInput} from which to read. + * @throws IOException Thrown if there was an error reading from the stream. Not expected if the + * next two frames were already peeked during synchronization. + * @throws InterruptedException Thrown if reading from the stream was interrupted. Not expected if + * the next two frames were already peeked during synchronization. + * @return a {@link Seeker}. + */ + private Seeker setupSeeker(ExtractorInput input) throws IOException, InterruptedException { + // Read the first frame which may contain a Xing or VBRI header with seeking metadata. + ParsableByteArray frame = new ParsableByteArray(synchronizedHeader.frameSize); + input.peekFully(frame.data, 0, synchronizedHeader.frameSize); + + long position = input.getPosition(); + long length = input.getLength(); + int headerData = 0; + Seeker seeker = null; + + // Check if there is a Xing header. + int xingBase = (synchronizedHeader.version & 1) != 0 + ? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1 + : (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5 + if (frame.limit() >= xingBase + 4) { + frame.setPosition(xingBase); + headerData = frame.readInt(); + } + if (headerData == XING_HEADER || headerData == INFO_HEADER) { + seeker = XingSeeker.create(synchronizedHeader, frame, position, length); + if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) { + // If there is a Xing header, read gapless playback metadata at a fixed offset. + input.resetPeekPosition(); + input.advancePeekPosition(xingBase + 141); + input.peekFully(scratch.data, 0, 3); + scratch.setPosition(0); + gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24()); + } + input.skipFully(synchronizedHeader.frameSize); + } else if (frame.limit() >= 40) { + // Check if there is a VBRI header. + frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. + headerData = frame.readInt(); + if (headerData == VBRI_HEADER) { + seeker = VbriSeeker.create(synchronizedHeader, frame, position, length); + input.skipFully(synchronizedHeader.frameSize); + } + } + + if (seeker == null || (!seeker.isSeekable() + && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) { + // Repopulate the synchronized header in case we had to skip an invalid seeking header, which + // would give an invalid CBR bitrate. + input.resetPeekPosition(); + input.peekFully(scratch.data, 0, 4); + scratch.setPosition(0); + MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader); + seeker = new ConstantBitrateSeeker(input.getPosition(), synchronizedHeader.bitrate, length); + } + + return seeker; + } + + /** + * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be + * used to work out the new sample basis timestamp after seeking and resynchronization. + */ + /* package */ interface Seeker extends SeekMap { + + /** + * Maps a position (byte offset) to a corresponding sample timestamp. + * + * @param position A seek position (byte offset) relative to the start of the stream. + * @return The corresponding timestamp of the next sample to be read, in microseconds. + */ + long getTimeUs(long position); + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/VbriSeeker.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/VbriSeeker.java new file mode 100644 index 0000000..b066efc --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/VbriSeeker.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp3; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.MpegAudioHeader; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * MP3 seeker that uses metadata from a VBRI header. + */ +/* package */ final class VbriSeeker implements Mp3Extractor.Seeker { + + /** + * Returns a {@link VbriSeeker} for seeking in the stream, if required information is present. + * Returns {@code null} if not. On returning, {@code frame}'s position is not specified so the + * caller should reset it. + * + * @param mpegAudioHeader The MPEG audio header associated with the frame. + * @param frame The data in this audio frame, with its position set to immediately after the + * 'VBRI' tag. + * @param position The position (byte offset) of the start of this frame in the stream. + * @param inputLength The length of the stream in bytes. + * @return A {@link VbriSeeker} for seeking in the stream, or {@code null} if the required + * information is not present. + */ + public static VbriSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame, + long position, long inputLength) { + frame.skipBytes(10); + int numFrames = frame.readInt(); + if (numFrames <= 0) { + return null; + } + int sampleRate = mpegAudioHeader.sampleRate; + long durationUs = Util.scaleLargeTimestamp(numFrames, + C.MICROS_PER_SECOND * (sampleRate >= 32000 ? 1152 : 576), sampleRate); + int entryCount = frame.readUnsignedShort(); + int scale = frame.readUnsignedShort(); + int entrySize = frame.readUnsignedShort(); + frame.skipBytes(2); + + // Skip the frame containing the VBRI header. + position += mpegAudioHeader.frameSize; + + // Read table of contents entries. + long[] timesUs = new long[entryCount + 1]; + long[] positions = new long[entryCount + 1]; + timesUs[0] = 0L; + positions[0] = position; + for (int index = 1; index < timesUs.length; index++) { + int segmentSize; + switch (entrySize) { + case 1: + segmentSize = frame.readUnsignedByte(); + break; + case 2: + segmentSize = frame.readUnsignedShort(); + break; + case 3: + segmentSize = frame.readUnsignedInt24(); + break; + case 4: + segmentSize = frame.readUnsignedIntToInt(); + break; + default: + return null; + } + position += segmentSize * scale; + timesUs[index] = index * durationUs / entryCount; + positions[index] = + inputLength == C.LENGTH_UNSET ? position : Math.min(inputLength, position); + } + return new VbriSeeker(timesUs, positions, durationUs); + } + + private final long[] timesUs; + private final long[] positions; + private final long durationUs; + + private VbriSeeker(long[] timesUs, long[] positions, long durationUs) { + this.timesUs = timesUs; + this.positions = positions; + this.durationUs = durationUs; + } + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getPosition(long timeUs) { + return positions[Util.binarySearchFloor(timesUs, timeUs, true, true)]; + } + + @Override + public long getTimeUs(long position) { + return timesUs[Util.binarySearchFloor(positions, position, true, true)]; + } + + @Override + public long getDurationUs() { + return durationUs; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/XingSeeker.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/XingSeeker.java new file mode 100644 index 0000000..d810626 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp3/XingSeeker.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp3; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.MpegAudioHeader; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * MP3 seeker that uses metadata from a Xing header. + */ +/* package */ final class XingSeeker implements Mp3Extractor.Seeker { + + /** + * Returns a {@link XingSeeker} for seeking in the stream, if required information is present. + * Returns {@code null} if not. On returning, {@code frame}'s position is not specified so the + * caller should reset it. + * + * @param mpegAudioHeader The MPEG audio header associated with the frame. + * @param frame The data in this audio frame, with its position set to immediately after the + * 'Xing' or 'Info' tag. + * @param position The position (byte offset) of the start of this frame in the stream. + * @param inputLength The length of the stream in bytes. + * @return A {@link XingSeeker} for seeking in the stream, or {@code null} if the required + * information is not present. + */ + public static XingSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame, + long position, long inputLength) { + int samplesPerFrame = mpegAudioHeader.samplesPerFrame; + int sampleRate = mpegAudioHeader.sampleRate; + long firstFramePosition = position + mpegAudioHeader.frameSize; + + int flags = frame.readInt(); + int frameCount; + if ((flags & 0x01) != 0x01 || (frameCount = frame.readUnsignedIntToInt()) == 0) { + // If the frame count is missing/invalid, the header can't be used to determine the duration. + return null; + } + long durationUs = Util.scaleLargeTimestamp(frameCount, samplesPerFrame * C.MICROS_PER_SECOND, + sampleRate); + if ((flags & 0x06) != 0x06) { + // If the size in bytes or table of contents is missing, the stream is not seekable. + return new XingSeeker(firstFramePosition, durationUs, inputLength); + } + + long sizeBytes = frame.readUnsignedIntToInt(); + frame.skipBytes(1); + long[] tableOfContents = new long[99]; + for (int i = 0; i < 99; i++) { + tableOfContents[i] = frame.readUnsignedByte(); + } + + // TODO: Handle encoder delay and padding in 3 bytes offset by xingBase + 213 bytes: + // delay = (frame.readUnsignedByte() << 4) + (frame.readUnsignedByte() >> 4); + // padding = ((frame.readUnsignedByte() & 0x0F) << 8) + frame.readUnsignedByte(); + return new XingSeeker(firstFramePosition, durationUs, inputLength, tableOfContents, + sizeBytes, mpegAudioHeader.frameSize); + } + + private final long firstFramePosition; + private final long durationUs; + private final long inputLength; + /** + * Entries are in the range [0, 255], but are stored as long integers for convenience. + */ + private final long[] tableOfContents; + private final long sizeBytes; + private final int headerSize; + + private XingSeeker(long firstFramePosition, long durationUs, long inputLength) { + this(firstFramePosition, durationUs, inputLength, null, 0, 0); + } + + private XingSeeker(long firstFramePosition, long durationUs, long inputLength, + long[] tableOfContents, long sizeBytes, int headerSize) { + this.firstFramePosition = firstFramePosition; + this.durationUs = durationUs; + this.inputLength = inputLength; + this.tableOfContents = tableOfContents; + this.sizeBytes = sizeBytes; + this.headerSize = headerSize; + } + + @Override + public boolean isSeekable() { + return tableOfContents != null; + } + + @Override + public long getPosition(long timeUs) { + if (!isSeekable()) { + return firstFramePosition; + } + float percent = timeUs * 100f / durationUs; + float fx; + if (percent <= 0f) { + fx = 0f; + } else if (percent >= 100f) { + fx = 256f; + } else { + int a = (int) percent; + float fa, fb; + if (a == 0) { + fa = 0f; + } else { + fa = tableOfContents[a - 1]; + } + if (a < 99) { + fb = tableOfContents[a]; + } else { + fb = 256f; + } + fx = fa + (fb - fa) * (percent - a); + } + + long position = Math.round((1.0 / 256) * fx * sizeBytes) + firstFramePosition; + long maximumPosition = inputLength != C.LENGTH_UNSET ? inputLength - 1 + : firstFramePosition - headerSize + sizeBytes - 1; + return Math.min(position, maximumPosition); + } + + @Override + public long getTimeUs(long position) { + if (!isSeekable() || position < firstFramePosition) { + return 0L; + } + double offsetByte = 256.0 * (position - firstFramePosition) / sizeBytes; + int previousTocPosition = + Util.binarySearchFloor(tableOfContents, (long) offsetByte, true, false) + 1; + long previousTime = getTimeUsForTocPosition(previousTocPosition); + + // Linearly interpolate the time taking into account the next entry. + long previousByte = previousTocPosition == 0 ? 0 : tableOfContents[previousTocPosition - 1]; + long nextByte = previousTocPosition == 99 ? 256 : tableOfContents[previousTocPosition]; + long nextTime = getTimeUsForTocPosition(previousTocPosition + 1); + long timeOffset = nextByte == previousByte ? 0 : (long) ((nextTime - previousTime) + * (offsetByte - previousByte) / (nextByte - previousByte)); + return previousTime + timeOffset; + } + + @Override + public long getDurationUs() { + return durationUs; + } + + /** + * Returns the time in microseconds corresponding to a table of contents position, which is + * interpreted as a percentage of the stream's duration between 0 and 100. + */ + private long getTimeUsForTocPosition(int tocPosition) { + return durationUs * tocPosition / 100; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Atom.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Atom.java new file mode 100644 index 0000000..f94f18c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Atom.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/* package*/ abstract class Atom { + + /** + * Size of an atom header, in bytes. + */ + public static final int HEADER_SIZE = 8; + + /** + * Size of a full atom header, in bytes. + */ + public static final int FULL_HEADER_SIZE = 12; + + /** + * Size of a long atom header, in bytes. + */ + public static final int LONG_HEADER_SIZE = 16; + + /** + * Value for the first 32 bits of atomSize when the atom size is actually a long value. + */ + public static final int LONG_SIZE_PREFIX = 1; + + public static final int TYPE_ftyp = Util.getIntegerCodeForString("ftyp"); + public static final int TYPE_avc1 = Util.getIntegerCodeForString("avc1"); + public static final int TYPE_avc3 = Util.getIntegerCodeForString("avc3"); + public static final int TYPE_hvc1 = Util.getIntegerCodeForString("hvc1"); + public static final int TYPE_hev1 = Util.getIntegerCodeForString("hev1"); + public static final int TYPE_s263 = Util.getIntegerCodeForString("s263"); + public static final int TYPE_d263 = Util.getIntegerCodeForString("d263"); + public static final int TYPE_mdat = Util.getIntegerCodeForString("mdat"); + public static final int TYPE_mp4a = Util.getIntegerCodeForString("mp4a"); + public static final int TYPE__mp3 = Util.getIntegerCodeForString(".mp3"); + public static final int TYPE_wave = Util.getIntegerCodeForString("wave"); + public static final int TYPE_lpcm = Util.getIntegerCodeForString("lpcm"); + public static final int TYPE_sowt = Util.getIntegerCodeForString("sowt"); + public static final int TYPE_ac_3 = Util.getIntegerCodeForString("ac-3"); + public static final int TYPE_dac3 = Util.getIntegerCodeForString("dac3"); + public static final int TYPE_ec_3 = Util.getIntegerCodeForString("ec-3"); + public static final int TYPE_dec3 = Util.getIntegerCodeForString("dec3"); + public static final int TYPE_dtsc = Util.getIntegerCodeForString("dtsc"); + public static final int TYPE_dtsh = Util.getIntegerCodeForString("dtsh"); + public static final int TYPE_dtsl = Util.getIntegerCodeForString("dtsl"); + public static final int TYPE_dtse = Util.getIntegerCodeForString("dtse"); + public static final int TYPE_ddts = Util.getIntegerCodeForString("ddts"); + public static final int TYPE_tfdt = Util.getIntegerCodeForString("tfdt"); + public static final int TYPE_tfhd = Util.getIntegerCodeForString("tfhd"); + public static final int TYPE_trex = Util.getIntegerCodeForString("trex"); + public static final int TYPE_trun = Util.getIntegerCodeForString("trun"); + public static final int TYPE_sidx = Util.getIntegerCodeForString("sidx"); + public static final int TYPE_moov = Util.getIntegerCodeForString("moov"); + public static final int TYPE_mvhd = Util.getIntegerCodeForString("mvhd"); + public static final int TYPE_trak = Util.getIntegerCodeForString("trak"); + public static final int TYPE_mdia = Util.getIntegerCodeForString("mdia"); + public static final int TYPE_minf = Util.getIntegerCodeForString("minf"); + public static final int TYPE_stbl = Util.getIntegerCodeForString("stbl"); + public static final int TYPE_avcC = Util.getIntegerCodeForString("avcC"); + public static final int TYPE_hvcC = Util.getIntegerCodeForString("hvcC"); + public static final int TYPE_esds = Util.getIntegerCodeForString("esds"); + public static final int TYPE_moof = Util.getIntegerCodeForString("moof"); + public static final int TYPE_traf = Util.getIntegerCodeForString("traf"); + public static final int TYPE_mvex = Util.getIntegerCodeForString("mvex"); + public static final int TYPE_mehd = Util.getIntegerCodeForString("mehd"); + public static final int TYPE_tkhd = Util.getIntegerCodeForString("tkhd"); + public static final int TYPE_edts = Util.getIntegerCodeForString("edts"); + public static final int TYPE_elst = Util.getIntegerCodeForString("elst"); + public static final int TYPE_mdhd = Util.getIntegerCodeForString("mdhd"); + public static final int TYPE_hdlr = Util.getIntegerCodeForString("hdlr"); + public static final int TYPE_stsd = Util.getIntegerCodeForString("stsd"); + public static final int TYPE_pssh = Util.getIntegerCodeForString("pssh"); + public static final int TYPE_sinf = Util.getIntegerCodeForString("sinf"); + public static final int TYPE_schm = Util.getIntegerCodeForString("schm"); + public static final int TYPE_schi = Util.getIntegerCodeForString("schi"); + public static final int TYPE_tenc = Util.getIntegerCodeForString("tenc"); + public static final int TYPE_encv = Util.getIntegerCodeForString("encv"); + public static final int TYPE_enca = Util.getIntegerCodeForString("enca"); + public static final int TYPE_frma = Util.getIntegerCodeForString("frma"); + public static final int TYPE_saiz = Util.getIntegerCodeForString("saiz"); + public static final int TYPE_saio = Util.getIntegerCodeForString("saio"); + public static final int TYPE_sbgp = Util.getIntegerCodeForString("sbgp"); + public static final int TYPE_sgpd = Util.getIntegerCodeForString("sgpd"); + public static final int TYPE_uuid = Util.getIntegerCodeForString("uuid"); + public static final int TYPE_senc = Util.getIntegerCodeForString("senc"); + public static final int TYPE_pasp = Util.getIntegerCodeForString("pasp"); + public static final int TYPE_TTML = Util.getIntegerCodeForString("TTML"); + public static final int TYPE_vmhd = Util.getIntegerCodeForString("vmhd"); + public static final int TYPE_mp4v = Util.getIntegerCodeForString("mp4v"); + public static final int TYPE_stts = Util.getIntegerCodeForString("stts"); + public static final int TYPE_stss = Util.getIntegerCodeForString("stss"); + public static final int TYPE_ctts = Util.getIntegerCodeForString("ctts"); + public static final int TYPE_stsc = Util.getIntegerCodeForString("stsc"); + public static final int TYPE_stsz = Util.getIntegerCodeForString("stsz"); + public static final int TYPE_stz2 = Util.getIntegerCodeForString("stz2"); + public static final int TYPE_stco = Util.getIntegerCodeForString("stco"); + public static final int TYPE_co64 = Util.getIntegerCodeForString("co64"); + public static final int TYPE_tx3g = Util.getIntegerCodeForString("tx3g"); + public static final int TYPE_wvtt = Util.getIntegerCodeForString("wvtt"); + public static final int TYPE_stpp = Util.getIntegerCodeForString("stpp"); + public static final int TYPE_c608 = Util.getIntegerCodeForString("c608"); + public static final int TYPE_samr = Util.getIntegerCodeForString("samr"); + public static final int TYPE_sawb = Util.getIntegerCodeForString("sawb"); + public static final int TYPE_udta = Util.getIntegerCodeForString("udta"); + public static final int TYPE_meta = Util.getIntegerCodeForString("meta"); + public static final int TYPE_ilst = Util.getIntegerCodeForString("ilst"); + public static final int TYPE_mean = Util.getIntegerCodeForString("mean"); + public static final int TYPE_name = Util.getIntegerCodeForString("name"); + public static final int TYPE_data = Util.getIntegerCodeForString("data"); + public static final int TYPE_emsg = Util.getIntegerCodeForString("emsg"); + public static final int TYPE_st3d = Util.getIntegerCodeForString("st3d"); + public static final int TYPE_sv3d = Util.getIntegerCodeForString("sv3d"); + public static final int TYPE_proj = Util.getIntegerCodeForString("proj"); + public static final int TYPE_vp08 = Util.getIntegerCodeForString("vp08"); + public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09"); + public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC"); + public static final int TYPE_camm = Util.getIntegerCodeForString("camm"); + public static final int TYPE_alac = Util.getIntegerCodeForString("alac"); + + public final int type; + + public Atom(int type) { + this.type = type; + } + + @Override + public String toString() { + return getAtomTypeString(type); + } + + /** + * An MP4 atom that is a leaf. + */ + /* package */ static final class LeafAtom extends Atom { + + /** + * The atom data. + */ + public final ParsableByteArray data; + + /** + * @param type The type of the atom. + * @param data The atom data. + */ + public LeafAtom(int type, ParsableByteArray data) { + super(type); + this.data = data; + } + + } + + /** + * An MP4 atom that has child atoms. + */ + /* package */ static final class ContainerAtom extends Atom { + + public final long endPosition; + public final List leafChildren; + public final List containerChildren; + + /** + * @param type The type of the atom. + * @param endPosition The position of the first byte after the end of the atom. + */ + public ContainerAtom(int type, long endPosition) { + super(type); + this.endPosition = endPosition; + leafChildren = new ArrayList<>(); + containerChildren = new ArrayList<>(); + } + + /** + * Adds a child leaf to this container. + * + * @param atom The child to add. + */ + public void add(LeafAtom atom) { + leafChildren.add(atom); + } + + /** + * Adds a child container to this container. + * + * @param atom The child to add. + */ + public void add(ContainerAtom atom) { + containerChildren.add(atom); + } + + /** + * Returns the child leaf of the given type. + *

    + * If no child exists with the given type then null is returned. If multiple children exist with + * the given type then the first one to have been added is returned. + * + * @param type The leaf type. + * @return The child leaf of the given type, or null if no such child exists. + */ + public LeafAtom getLeafAtomOfType(int type) { + int childrenSize = leafChildren.size(); + for (int i = 0; i < childrenSize; i++) { + LeafAtom atom = leafChildren.get(i); + if (atom.type == type) { + return atom; + } + } + return null; + } + + /** + * Returns the child container of the given type. + *

    + * If no child exists with the given type then null is returned. If multiple children exist with + * the given type then the first one to have been added is returned. + * + * @param type The container type. + * @return The child container of the given type, or null if no such child exists. + */ + public ContainerAtom getContainerAtomOfType(int type) { + int childrenSize = containerChildren.size(); + for (int i = 0; i < childrenSize; i++) { + ContainerAtom atom = containerChildren.get(i); + if (atom.type == type) { + return atom; + } + } + return null; + } + + /** + * Returns the total number of leaf/container children of this atom with the given type. + * + * @param type The type of child atoms to count. + * @return The total number of leaf/container children of this atom with the given type. + */ + public int getChildAtomOfTypeCount(int type) { + int count = 0; + int size = leafChildren.size(); + for (int i = 0; i < size; i++) { + LeafAtom atom = leafChildren.get(i); + if (atom.type == type) { + count++; + } + } + size = containerChildren.size(); + for (int i = 0; i < size; i++) { + ContainerAtom atom = containerChildren.get(i); + if (atom.type == type) { + count++; + } + } + return count; + } + + @Override + public String toString() { + return getAtomTypeString(type) + + " leaves: " + Arrays.toString(leafChildren.toArray()) + + " containers: " + Arrays.toString(containerChildren.toArray()); + } + + } + + /** + * Parses the version number out of the additional integer component of a full atom. + */ + public static int parseFullAtomVersion(int fullAtomInt) { + return 0x000000FF & (fullAtomInt >> 24); + } + + /** + * Parses the atom flags out of the additional integer component of a full atom. + */ + public static int parseFullAtomFlags(int fullAtomInt) { + return 0x00FFFFFF & fullAtomInt; + } + + /** + * Converts a numeric atom type to the corresponding four character string. + * + * @param type The numeric atom type. + * @return The corresponding four character string. + */ + public static String getAtomTypeString(int type) { + return "" + (char) ((type >> 24) & 0xFF) + + (char) ((type >> 16) & 0xFF) + + (char) ((type >> 8) & 0xFF) + + (char) (type & 0xFF); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/AtomParsers.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/AtomParsers.java new file mode 100644 index 0000000..a2a8e59 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/AtomParsers.java @@ -0,0 +1,1326 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import android.util.Log; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.Ac3Util; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.GaplessInfoHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.CodecSpecificDataUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import com.tangxiaolv.telegramgallery.exoplayer2.video.AvcConfig; +import com.tangxiaolv.telegramgallery.exoplayer2.video.HevcConfig; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. + */ +/* package */ final class AtomParsers { + + private static final String TAG = "AtomParsers"; + + private static final int TYPE_vide = Util.getIntegerCodeForString("vide"); + private static final int TYPE_soun = Util.getIntegerCodeForString("soun"); + private static final int TYPE_text = Util.getIntegerCodeForString("text"); + private static final int TYPE_sbtl = Util.getIntegerCodeForString("sbtl"); + private static final int TYPE_subt = Util.getIntegerCodeForString("subt"); + private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp"); + private static final int TYPE_cenc = Util.getIntegerCodeForString("cenc"); + private static final int TYPE_meta = Util.getIntegerCodeForString("meta"); + + /** + * Parses a trak atom (defined in 14496-12). + * + * @param trak Atom to decode. + * @param mvhd Movie header atom, used to get the timescale. + * @param duration The duration in units of the timescale declared in the mvhd atom, or + * {@link C#TIME_UNSET} if the duration should be parsed from the tkhd atom. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @param isQuickTime True for QuickTime media. False otherwise. + * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. + */ + public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, + DrmInitData drmInitData, boolean isQuickTime) throws ParserException { + Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); + int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); + if (trackType == C.TRACK_TYPE_UNKNOWN) { + return null; + } + + TkhdData tkhdData = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data); + if (duration == C.TIME_UNSET) { + duration = tkhdData.duration; + } + long movieTimescale = parseMvhd(mvhd.data); + long durationUs; + if (duration == C.TIME_UNSET) { + durationUs = C.TIME_UNSET; + } else { + durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, movieTimescale); + } + Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf) + .getContainerAtomOfType(Atom.TYPE_stbl); + + Pair mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); + StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, + tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime); + Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); + return stsdData.format == null ? null + : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, + stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes, + stsdData.nalUnitLengthFieldLength, edtsData.first, edtsData.second); + } + + /** + * Parses an stbl atom (defined in 14496-12). + * + * @param track Track to which this sample table corresponds. + * @param stblAtom stbl (sample table) atom to decode. + * @param gaplessInfoHolder Holder to populate with gapless playback information. + * @return Sample table described by the stbl atom. + * @throws ParserException If the resulting sample sequence does not contain a sync sample. + */ + public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAtom, + GaplessInfoHolder gaplessInfoHolder) throws ParserException { + SampleSizeBox sampleSizeBox; + Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz); + if (stszAtom != null) { + sampleSizeBox = new StszSampleSizeBox(stszAtom); + } else { + Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2); + if (stz2Atom == null) { + throw new ParserException("Track has no sample table size information"); + } + sampleSizeBox = new Stz2SampleSizeBox(stz2Atom); + } + + int sampleCount = sampleSizeBox.getSampleCount(); + if (sampleCount == 0) { + return new TrackSampleTable(new long[0], new int[0], 0, new long[0], new int[0]); + } + + // Entries are byte offsets of chunks. + boolean chunkOffsetsAreLongs = false; + Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco); + if (chunkOffsetsAtom == null) { + chunkOffsetsAreLongs = true; + chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64); + } + ParsableByteArray chunkOffsets = chunkOffsetsAtom.data; + // Entries are (chunk number, number of samples per chunk, sample description index). + ParsableByteArray stsc = stblAtom.getLeafAtomOfType(Atom.TYPE_stsc).data; + // Entries are (number of samples, timestamp delta between those samples). + ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data; + // Entries are the indices of samples that are synchronization samples. + Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss); + ParsableByteArray stss = stssAtom != null ? stssAtom.data : null; + // Entries are (number of samples, timestamp offset). + Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts); + ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null; + + // Prepare to read chunk information. + ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs); + + // Prepare to read sample timestamps. + stts.setPosition(Atom.FULL_HEADER_SIZE); + int remainingTimestampDeltaChanges = stts.readUnsignedIntToInt() - 1; + int remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); + int timestampDeltaInTimeUnits = stts.readUnsignedIntToInt(); + + // Prepare to read sample timestamp offsets, if ctts is present. + int remainingSamplesAtTimestampOffset = 0; + int remainingTimestampOffsetChanges = 0; + int timestampOffset = 0; + if (ctts != null) { + ctts.setPosition(Atom.FULL_HEADER_SIZE); + remainingTimestampOffsetChanges = ctts.readUnsignedIntToInt(); + } + + int nextSynchronizationSampleIndex = C.INDEX_UNSET; + int remainingSynchronizationSamples = 0; + if (stss != null) { + stss.setPosition(Atom.FULL_HEADER_SIZE); + remainingSynchronizationSamples = stss.readUnsignedIntToInt(); + if (remainingSynchronizationSamples > 0) { + nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1; + } else { + // Ignore empty stss boxes, which causes all samples to be treated as sync samples. + stss = null; + } + } + + // True if we can rechunk fixed-sample-size data. Note that we only rechunk raw audio. + boolean isRechunkable = sampleSizeBox.isFixedSampleSize() + && MimeTypes.AUDIO_RAW.equals(track.format.sampleMimeType) + && remainingTimestampDeltaChanges == 0 && remainingTimestampOffsetChanges == 0 + && remainingSynchronizationSamples == 0; + + long[] offsets; + int[] sizes; + int maximumSize = 0; + long[] timestamps; + int[] flags; + long timestampTimeUnits = 0; + + if (!isRechunkable) { + offsets = new long[sampleCount]; + sizes = new int[sampleCount]; + timestamps = new long[sampleCount]; + flags = new int[sampleCount]; + long offset = 0; + int remainingSamplesInChunk = 0; + + for (int i = 0; i < sampleCount; i++) { + // Advance to the next chunk if necessary. + while (remainingSamplesInChunk == 0) { + Assertions.checkState(chunkIterator.moveNext()); + offset = chunkIterator.offset; + remainingSamplesInChunk = chunkIterator.numSamples; + } + + // Add on the timestamp offset if ctts is present. + if (ctts != null) { + while (remainingSamplesAtTimestampOffset == 0 && remainingTimestampOffsetChanges > 0) { + remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt(); + // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers + // in version 0 ctts boxes, however some streams violate the spec and use signed + // integers instead. It's safe to always decode sample offsets as signed integers here, + // because unsigned integers will still be parsed correctly (unless their top bit is + // set, which is never true in practice because sample offsets are always small). + timestampOffset = ctts.readInt(); + remainingTimestampOffsetChanges--; + } + remainingSamplesAtTimestampOffset--; + } + + offsets[i] = offset; + sizes[i] = sampleSizeBox.readNextSampleSize(); + if (sizes[i] > maximumSize) { + maximumSize = sizes[i]; + } + timestamps[i] = timestampTimeUnits + timestampOffset; + + // All samples are synchronization samples if the stss is not present. + flags[i] = stss == null ? C.BUFFER_FLAG_KEY_FRAME : 0; + if (i == nextSynchronizationSampleIndex) { + flags[i] = C.BUFFER_FLAG_KEY_FRAME; + remainingSynchronizationSamples--; + if (remainingSynchronizationSamples > 0) { + nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1; + } + } + + // Add on the duration of this sample. + timestampTimeUnits += timestampDeltaInTimeUnits; + remainingSamplesAtTimestampDelta--; + if (remainingSamplesAtTimestampDelta == 0 && remainingTimestampDeltaChanges > 0) { + remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); + timestampDeltaInTimeUnits = stts.readUnsignedIntToInt(); + remainingTimestampDeltaChanges--; + } + + offset += sizes[i]; + remainingSamplesInChunk--; + } + + Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0); + // Remove trailing ctts entries with 0-valued sample counts. + while (remainingTimestampOffsetChanges > 0) { + Assertions.checkArgument(ctts.readUnsignedIntToInt() == 0); + ctts.readInt(); // Ignore offset. + remainingTimestampOffsetChanges--; + } + + // If the stbl's child boxes are not consistent the container is malformed, but the stream may + // still be playable. + if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0 + || remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) { + Log.w(TAG, "Inconsistent stbl box for track " + track.id + + ": remainingSynchronizationSamples " + remainingSynchronizationSamples + + ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta + + ", remainingSamplesInChunk " + remainingSamplesInChunk + + ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges); + } + } else { + long[] chunkOffsetsBytes = new long[chunkIterator.length]; + int[] chunkSampleCounts = new int[chunkIterator.length]; + while (chunkIterator.moveNext()) { + chunkOffsetsBytes[chunkIterator.index] = chunkIterator.offset; + chunkSampleCounts[chunkIterator.index] = chunkIterator.numSamples; + } + int fixedSampleSize = sampleSizeBox.readNextSampleSize(); + FixedSampleSizeRechunker.Results rechunkedResults = FixedSampleSizeRechunker.rechunk( + fixedSampleSize, chunkOffsetsBytes, chunkSampleCounts, timestampDeltaInTimeUnits); + offsets = rechunkedResults.offsets; + sizes = rechunkedResults.sizes; + maximumSize = rechunkedResults.maximumSize; + timestamps = rechunkedResults.timestamps; + flags = rechunkedResults.flags; + } + + if (track.editListDurations == null || gaplessInfoHolder.hasGaplessInfo()) { + // There is no edit list, or we are ignoring it as we already have gapless metadata to apply. + // This implementation does not support applying both gapless metadata and an edit list. + Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + } + + // See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a + // sync sample after reordering are not supported. Partial audio sample truncation is only + // supported in edit lists with one edit that removes less than one sample from the start/end of + // the track, for gapless audio playback. This implementation handles simple discarding/delaying + // of samples. The extractor may place further restrictions on what edited streams are playable. + + if (track.editListDurations.length == 1 && track.type == C.TRACK_TYPE_AUDIO + && timestamps.length >= 2) { + // Handle the edit by setting gapless playback metadata, if possible. This implementation + // assumes that only one "roll" sample is needed, which is the case for AAC, so the start/end + // points of the edit must lie within the first/last samples respectively. + long editStartTime = track.editListMediaTimes[0]; + long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0], + track.timescale, track.movieTimescale); + long lastSampleEndTime = timestampTimeUnits; + if (timestamps[0] <= editStartTime && editStartTime < timestamps[1] + && timestamps[timestamps.length - 1] < editEndTime && editEndTime <= lastSampleEndTime) { + long paddingTimeUnits = lastSampleEndTime - editEndTime; + long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0], + track.format.sampleRate, track.timescale); + long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits, + track.format.sampleRate, track.timescale); + if ((encoderDelay != 0 || encoderPadding != 0) && encoderDelay <= Integer.MAX_VALUE + && encoderPadding <= Integer.MAX_VALUE) { + gaplessInfoHolder.encoderDelay = (int) encoderDelay; + gaplessInfoHolder.encoderPadding = (int) encoderPadding; + Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + } + } + } + + if (track.editListDurations.length == 1 && track.editListDurations[0] == 0) { + // The current version of the spec leaves handling of an edit with zero segment_duration in + // unfragmented files open to interpretation. We handle this as a special case and include all + // samples in the edit. + for (int i = 0; i < timestamps.length; i++) { + timestamps[i] = Util.scaleLargeTimestamp(timestamps[i] - track.editListMediaTimes[0], + C.MICROS_PER_SECOND, track.timescale); + } + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + } + + // Omit any sample at the end point of an edit for audio tracks. + boolean omitClippedSample = track.type == C.TRACK_TYPE_AUDIO; + + // Count the number of samples after applying edits. + int editedSampleCount = 0; + int nextSampleIndex = 0; + boolean copyMetadata = false; + for (int i = 0; i < track.editListDurations.length; i++) { + long mediaTime = track.editListMediaTimes[i]; + if (mediaTime != -1) { + long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale, + track.movieTimescale); + int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); + int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, omitClippedSample, + false); + editedSampleCount += endIndex - startIndex; + copyMetadata |= nextSampleIndex != startIndex; + nextSampleIndex = endIndex; + } + } + copyMetadata |= editedSampleCount != sampleCount; + + // Calculate edited sample timestamps and update the corresponding metadata arrays. + long[] editedOffsets = copyMetadata ? new long[editedSampleCount] : offsets; + int[] editedSizes = copyMetadata ? new int[editedSampleCount] : sizes; + int editedMaximumSize = copyMetadata ? 0 : maximumSize; + int[] editedFlags = copyMetadata ? new int[editedSampleCount] : flags; + long[] editedTimestamps = new long[editedSampleCount]; + long pts = 0; + int sampleIndex = 0; + for (int i = 0; i < track.editListDurations.length; i++) { + long mediaTime = track.editListMediaTimes[i]; + long duration = track.editListDurations[i]; + if (mediaTime != -1) { + long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale, + track.movieTimescale); + int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); + int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, omitClippedSample, false); + if (copyMetadata) { + int count = endIndex - startIndex; + System.arraycopy(offsets, startIndex, editedOffsets, sampleIndex, count); + System.arraycopy(sizes, startIndex, editedSizes, sampleIndex, count); + System.arraycopy(flags, startIndex, editedFlags, sampleIndex, count); + } + for (int j = startIndex; j < endIndex; j++) { + long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale); + long timeInSegmentUs = Util.scaleLargeTimestamp(timestamps[j] - mediaTime, + C.MICROS_PER_SECOND, track.timescale); + editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs; + if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) { + editedMaximumSize = sizes[j]; + } + sampleIndex++; + } + } + pts += duration; + } + + boolean hasSyncSample = false; + for (int i = 0; i < editedFlags.length && !hasSyncSample; i++) { + hasSyncSample |= (editedFlags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0; + } + if (!hasSyncSample) { + throw new ParserException("The edited sample sequence does not contain a sync sample."); + } + + return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps, + editedFlags); + } + + /** + * Parses a udta atom. + * + * @param udtaAtom The udta (user data) atom to decode. + * @param isQuickTime True for QuickTime media. False otherwise. + * @return Parsed metadata, or null. + */ + public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) { + if (isQuickTime) { + // Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and + // decode one. + return null; + } + ParsableByteArray udtaData = udtaAtom.data; + udtaData.setPosition(Atom.HEADER_SIZE); + while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) { + int atomPosition = udtaData.getPosition(); + int atomSize = udtaData.readInt(); + int atomType = udtaData.readInt(); + if (atomType == Atom.TYPE_meta) { + udtaData.setPosition(atomPosition); + return parseMetaAtom(udtaData, atomPosition + atomSize); + } + udtaData.skipBytes(atomSize - Atom.HEADER_SIZE); + } + return null; + } + + private static Metadata parseMetaAtom(ParsableByteArray meta, int limit) { + meta.skipBytes(Atom.FULL_HEADER_SIZE); + while (meta.getPosition() < limit) { + int atomPosition = meta.getPosition(); + int atomSize = meta.readInt(); + int atomType = meta.readInt(); + if (atomType == Atom.TYPE_ilst) { + meta.setPosition(atomPosition); + return parseIlst(meta, atomPosition + atomSize); + } + meta.skipBytes(atomSize - Atom.HEADER_SIZE); + } + return null; + } + + private static Metadata parseIlst(ParsableByteArray ilst, int limit) { + ilst.skipBytes(Atom.HEADER_SIZE); + ArrayList entries = new ArrayList<>(); + while (ilst.getPosition() < limit) { + Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst); + if (entry != null) { + entries.add(entry); + } + } + return entries.isEmpty() ? null : new Metadata(entries); + } + + /** + * Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie. + * + * @param mvhd Contents of the mvhd atom to be parsed. + * @return Timescale for the movie. + */ + private static long parseMvhd(ParsableByteArray mvhd) { + mvhd.setPosition(Atom.HEADER_SIZE); + int fullAtom = mvhd.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + mvhd.skipBytes(version == 0 ? 8 : 16); + return mvhd.readUnsignedInt(); + } + + /** + * Parses a tkhd atom (defined in 14496-12). + * + * @return An object containing the parsed data. + */ + private static TkhdData parseTkhd(ParsableByteArray tkhd) { + tkhd.setPosition(Atom.HEADER_SIZE); + int fullAtom = tkhd.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + + tkhd.skipBytes(version == 0 ? 8 : 16); + int trackId = tkhd.readInt(); + + tkhd.skipBytes(4); + boolean durationUnknown = true; + int durationPosition = tkhd.getPosition(); + int durationByteCount = version == 0 ? 4 : 8; + for (int i = 0; i < durationByteCount; i++) { + if (tkhd.data[durationPosition + i] != -1) { + durationUnknown = false; + break; + } + } + long duration; + if (durationUnknown) { + tkhd.skipBytes(durationByteCount); + duration = C.TIME_UNSET; + } else { + duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong(); + if (duration == 0) { + // 0 duration normally indicates that the file is fully fragmented (i.e. all of the media + // samples are in fragments). Treat as unknown. + duration = C.TIME_UNSET; + } + } + + tkhd.skipBytes(16); + int a00 = tkhd.readInt(); + int a01 = tkhd.readInt(); + tkhd.skipBytes(4); + int a10 = tkhd.readInt(); + int a11 = tkhd.readInt(); + + int rotationDegrees; + int fixedOne = 65536; + if (a00 == 0 && a01 == fixedOne && a10 == -fixedOne && a11 == 0) { + rotationDegrees = 90; + } else if (a00 == 0 && a01 == -fixedOne && a10 == fixedOne && a11 == 0) { + rotationDegrees = 270; + } else if (a00 == -fixedOne && a01 == 0 && a10 == 0 && a11 == -fixedOne) { + rotationDegrees = 180; + } else { + // Only 0, 90, 180 and 270 are supported. Treat anything else as 0. + rotationDegrees = 0; + } + + return new TkhdData(trackId, duration, rotationDegrees); + } + + /** + * Parses an hdlr atom. + * + * @param hdlr The hdlr atom to decode. + * @return The track type. + */ + private static int parseHdlr(ParsableByteArray hdlr) { + hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4); + int trackType = hdlr.readInt(); + if (trackType == TYPE_soun) { + return C.TRACK_TYPE_AUDIO; + } else if (trackType == TYPE_vide) { + return C.TRACK_TYPE_VIDEO; + } else if (trackType == TYPE_text || trackType == TYPE_sbtl || trackType == TYPE_subt + || trackType == TYPE_clcp) { + return C.TRACK_TYPE_TEXT; + } else if (trackType == TYPE_meta) { + return C.TRACK_TYPE_METADATA; + } else { + return C.TRACK_TYPE_UNKNOWN; + } + } + + /** + * Parses an mdhd atom (defined in 14496-12). + * + * @param mdhd The mdhd atom to decode. + * @return A pair consisting of the media timescale defined as the number of time units that pass + * in one second, and the language code. + */ + private static Pair parseMdhd(ParsableByteArray mdhd) { + mdhd.setPosition(Atom.HEADER_SIZE); + int fullAtom = mdhd.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + mdhd.skipBytes(version == 0 ? 8 : 16); + long timescale = mdhd.readUnsignedInt(); + mdhd.skipBytes(version == 0 ? 4 : 8); + int languageCode = mdhd.readUnsignedShort(); + String language = "" + (char) (((languageCode >> 10) & 0x1F) + 0x60) + + (char) (((languageCode >> 5) & 0x1F) + 0x60) + + (char) (((languageCode) & 0x1F) + 0x60); + return Pair.create(timescale, language); + } + + /** + * Parses a stsd atom (defined in 14496-12). + * + * @param stsd The stsd atom to decode. + * @param trackId The track's identifier in its container. + * @param rotationDegrees The rotation of the track in degrees. + * @param language The language of the track. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @param isQuickTime True for QuickTime media. False otherwise. + * @return An object containing the parsed data. + */ + private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotationDegrees, + String language, DrmInitData drmInitData, boolean isQuickTime) throws ParserException { + stsd.setPosition(Atom.FULL_HEADER_SIZE); + int numberOfEntries = stsd.readInt(); + StsdData out = new StsdData(numberOfEntries); + for (int i = 0; i < numberOfEntries; i++) { + int childStartPosition = stsd.getPosition(); + int childAtomSize = stsd.readInt(); + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childAtomType = stsd.readInt(); + if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3 + || childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v + || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1 + || childAtomType == Atom.TYPE_s263 || childAtomType == Atom.TYPE_vp08 + || childAtomType == Atom.TYPE_vp09) { + parseVideoSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, + rotationDegrees, drmInitData, out, i); + } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca + || childAtomType == Atom.TYPE_ac_3 || childAtomType == Atom.TYPE_ec_3 + || childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse + || childAtomType == Atom.TYPE_dtsh || childAtomType == Atom.TYPE_dtsl + || childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb + || childAtomType == Atom.TYPE_lpcm || childAtomType == Atom.TYPE_sowt + || childAtomType == Atom.TYPE__mp3 || childAtomType == Atom.TYPE_alac) { + parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, + language, isQuickTime, drmInitData, out, i); + } else if (childAtomType == Atom.TYPE_TTML || childAtomType == Atom.TYPE_tx3g + || childAtomType == Atom.TYPE_wvtt || childAtomType == Atom.TYPE_stpp + || childAtomType == Atom.TYPE_c608) { + parseTextSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, + language, drmInitData, out); + } else if (childAtomType == Atom.TYPE_camm) { + out.format = Format.createSampleFormat(Integer.toString(trackId), + MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, drmInitData); + } + stsd.setPosition(childStartPosition + childAtomSize); + } + return out; + } + + private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, int position, + int atomSize, int trackId, String language, DrmInitData drmInitData, StsdData out) + throws ParserException { + parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); + + // Default values. + List initializationData = null; + long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE; + + String mimeType; + if (atomType == Atom.TYPE_TTML) { + mimeType = MimeTypes.APPLICATION_TTML; + } else if (atomType == Atom.TYPE_tx3g) { + mimeType = MimeTypes.APPLICATION_TX3G; + int sampleDescriptionLength = atomSize - Atom.HEADER_SIZE - 8; + byte[] sampleDescriptionData = new byte[sampleDescriptionLength]; + parent.readBytes(sampleDescriptionData, 0, sampleDescriptionLength); + initializationData = Collections.singletonList(sampleDescriptionData); + } else if (atomType == Atom.TYPE_wvtt) { + mimeType = MimeTypes.APPLICATION_MP4VTT; + } else if (atomType == Atom.TYPE_stpp) { + mimeType = MimeTypes.APPLICATION_TTML; + subsampleOffsetUs = 0; // Subsample timing is absolute. + } else if (atomType == Atom.TYPE_c608) { + // Defined by the QuickTime File Format specification. + mimeType = MimeTypes.APPLICATION_MP4CEA608; + out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT; + } else { + // Never happens. + throw new IllegalStateException(); + } + + out.format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, 0, language, Format.NO_VALUE, drmInitData, subsampleOffsetUs, + initializationData); + } + + private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, + int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out, + int entryIndex) throws ParserException { + parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); + + parent.skipBytes(16); + int width = parent.readUnsignedShort(); + int height = parent.readUnsignedShort(); + boolean pixelWidthHeightRatioFromPasp = false; + float pixelWidthHeightRatio = 1; + parent.skipBytes(50); + + int childPosition = parent.getPosition(); + if (atomType == Atom.TYPE_encv) { + atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); + parent.setPosition(childPosition); + } + + List initializationData = null; + String mimeType = null; + byte[] projectionData = null; + @C.StereoMode + int stereoMode = Format.NO_VALUE; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childStartPosition = parent.getPosition(); + int childAtomSize = parent.readInt(); + if (childAtomSize == 0 && parent.getPosition() - position == size) { + // Handle optional terminating four zero bytes in MOV files. + break; + } + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_avcC) { + Assertions.checkState(mimeType == null); + mimeType = MimeTypes.VIDEO_H264; + parent.setPosition(childStartPosition + Atom.HEADER_SIZE); + AvcConfig avcConfig = AvcConfig.parse(parent); + initializationData = avcConfig.initializationData; + out.nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; + if (!pixelWidthHeightRatioFromPasp) { + pixelWidthHeightRatio = avcConfig.pixelWidthAspectRatio; + } + } else if (childAtomType == Atom.TYPE_hvcC) { + Assertions.checkState(mimeType == null); + mimeType = MimeTypes.VIDEO_H265; + parent.setPosition(childStartPosition + Atom.HEADER_SIZE); + HevcConfig hevcConfig = HevcConfig.parse(parent); + initializationData = hevcConfig.initializationData; + out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; + } else if (childAtomType == Atom.TYPE_vpcC) { + Assertions.checkState(mimeType == null); + mimeType = (atomType == Atom.TYPE_vp08) ? MimeTypes.VIDEO_VP8 : MimeTypes.VIDEO_VP9; + } else if (childAtomType == Atom.TYPE_d263) { + Assertions.checkState(mimeType == null); + mimeType = MimeTypes.VIDEO_H263; + } else if (childAtomType == Atom.TYPE_esds) { + Assertions.checkState(mimeType == null); + Pair mimeTypeAndInitializationData = + parseEsdsFromParent(parent, childStartPosition); + mimeType = mimeTypeAndInitializationData.first; + initializationData = Collections.singletonList(mimeTypeAndInitializationData.second); + } else if (childAtomType == Atom.TYPE_pasp) { + pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition); + pixelWidthHeightRatioFromPasp = true; + } else if (childAtomType == Atom.TYPE_sv3d) { + projectionData = parseProjFromParent(parent, childStartPosition, childAtomSize); + } else if (childAtomType == Atom.TYPE_st3d) { + int version = parent.readUnsignedByte(); + parent.skipBytes(3); // Flags. + if (version == 0) { + int layout = parent.readUnsignedByte(); + switch (layout) { + case 0: + stereoMode = C.STEREO_MODE_MONO; + break; + case 1: + stereoMode = C.STEREO_MODE_TOP_BOTTOM; + break; + case 2: + stereoMode = C.STEREO_MODE_LEFT_RIGHT; + break; + case 3: + stereoMode = C.STEREO_MODE_STEREO_MESH; + break; + default: + break; + } + } + } + childPosition += childAtomSize; + } + + // If the media type was not recognized, ignore the track. + if (mimeType == null) { + return; + } + + out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData, + rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, null, drmInitData); + } + + /** + * Parses the edts atom (defined in 14496-12 subsection 8.6.5). + * + * @param edtsAtom edts (edit box) atom to decode. + * @return Pair of edit list durations and edit list media times, or a pair of nulls if they are + * not present. + */ + private static Pair parseEdts(Atom.ContainerAtom edtsAtom) { + Atom.LeafAtom elst; + if (edtsAtom == null || (elst = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst)) == null) { + return Pair.create(null, null); + } + ParsableByteArray elstData = elst.data; + elstData.setPosition(Atom.HEADER_SIZE); + int fullAtom = elstData.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + int entryCount = elstData.readUnsignedIntToInt(); + long[] editListDurations = new long[entryCount]; + long[] editListMediaTimes = new long[entryCount]; + for (int i = 0; i < entryCount; i++) { + editListDurations[i] = + version == 1 ? elstData.readUnsignedLongToLong() : elstData.readUnsignedInt(); + editListMediaTimes[i] = version == 1 ? elstData.readLong() : elstData.readInt(); + int mediaRateInteger = elstData.readShort(); + if (mediaRateInteger != 1) { + // The extractor does not handle dwell edits (mediaRateInteger == 0). + throw new IllegalArgumentException("Unsupported media rate."); + } + elstData.skipBytes(2); + } + return Pair.create(editListDurations, editListMediaTimes); + } + + private static float parsePaspFromParent(ParsableByteArray parent, int position) { + parent.setPosition(position + Atom.HEADER_SIZE); + int hSpacing = parent.readUnsignedIntToInt(); + int vSpacing = parent.readUnsignedIntToInt(); + return (float) hSpacing / vSpacing; + } + + private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, + int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData, + StsdData out, int entryIndex) { + parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); + + int quickTimeSoundDescriptionVersion = 0; + if (isQuickTime) { + quickTimeSoundDescriptionVersion = parent.readUnsignedShort(); + parent.skipBytes(6); + } else { + parent.skipBytes(8); + } + + int channelCount; + int sampleRate; + + if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) { + channelCount = parent.readUnsignedShort(); + parent.skipBytes(6); // sampleSize, compressionId, packetSize. + sampleRate = parent.readUnsignedFixedPoint1616(); + + if (quickTimeSoundDescriptionVersion == 1) { + parent.skipBytes(16); + } + } else if (quickTimeSoundDescriptionVersion == 2) { + parent.skipBytes(16); // always[3,16,Minus2,0,65536], sizeOfStructOnly + + sampleRate = (int) Math.round(parent.readDouble()); + channelCount = parent.readUnsignedIntToInt(); + + // Skip always7F000000, sampleSize, formatSpecificFlags, constBytesPerAudioPacket, + // constLPCMFramesPerAudioPacket. + parent.skipBytes(20); + } else { + // Unsupported version. + return; + } + + int childPosition = parent.getPosition(); + if (atomType == Atom.TYPE_enca) { + atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); + parent.setPosition(childPosition); + } + + // If the atom type determines a MIME type, set it immediately. + String mimeType = null; + if (atomType == Atom.TYPE_ac_3) { + mimeType = MimeTypes.AUDIO_AC3; + } else if (atomType == Atom.TYPE_ec_3) { + mimeType = MimeTypes.AUDIO_E_AC3; + } else if (atomType == Atom.TYPE_dtsc) { + mimeType = MimeTypes.AUDIO_DTS; + } else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) { + mimeType = MimeTypes.AUDIO_DTS_HD; + } else if (atomType == Atom.TYPE_dtse) { + mimeType = MimeTypes.AUDIO_DTS_EXPRESS; + } else if (atomType == Atom.TYPE_samr) { + mimeType = MimeTypes.AUDIO_AMR_NB; + } else if (atomType == Atom.TYPE_sawb) { + mimeType = MimeTypes.AUDIO_AMR_WB; + } else if (atomType == Atom.TYPE_lpcm || atomType == Atom.TYPE_sowt) { + mimeType = MimeTypes.AUDIO_RAW; + } else if (atomType == Atom.TYPE__mp3) { + mimeType = MimeTypes.AUDIO_MPEG; + } else if (atomType == Atom.TYPE_alac) { + mimeType = MimeTypes.AUDIO_ALAC; + } + + byte[] initializationData = null; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_esds || (isQuickTime && childAtomType == Atom.TYPE_wave)) { + int esdsAtomPosition = childAtomType == Atom.TYPE_esds ? childPosition + : findEsdsPosition(parent, childPosition, childAtomSize); + if (esdsAtomPosition != C.POSITION_UNSET) { + Pair mimeTypeAndInitializationData = + parseEsdsFromParent(parent, esdsAtomPosition); + mimeType = mimeTypeAndInitializationData.first; + initializationData = mimeTypeAndInitializationData.second; + if (MimeTypes.AUDIO_AAC.equals(mimeType)) { + // TODO: Do we really need to do this? See [Internal: b/10903778] + // Update sampleRate and channelCount from the AudioSpecificConfig initialization data. + Pair audioSpecificConfig = + CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData); + sampleRate = audioSpecificConfig.first; + channelCount = audioSpecificConfig.second; + } + } + } else if (childAtomType == Atom.TYPE_dac3) { + parent.setPosition(Atom.HEADER_SIZE + childPosition); + out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language, + drmInitData); + } else if (childAtomType == Atom.TYPE_dec3) { + parent.setPosition(Atom.HEADER_SIZE + childPosition); + out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language, + drmInitData); + } else if (childAtomType == Atom.TYPE_ddts) { + out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, + language); + } else if (childAtomType == Atom.TYPE_alac) { + initializationData = new byte[childAtomSize]; + parent.setPosition(childPosition); + parent.readBytes(initializationData, 0, childAtomSize); + } + childPosition += childAtomSize; + } + + if (out.format == null && mimeType != null) { + // TODO: Determine the correct PCM encoding. + @C.PcmEncoding int pcmEncoding = + MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE; + out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding, + initializationData == null ? null : Collections.singletonList(initializationData), + drmInitData, 0, language); + } + } + + /** + * Returns the position of the esds box within a parent, or {@link C#POSITION_UNSET} if no esds + * box is found + */ + private static int findEsdsPosition(ParsableByteArray parent, int position, int size) { + int childAtomPosition = parent.getPosition(); + while (childAtomPosition - position < size) { + parent.setPosition(childAtomPosition); + int childAtomSize = parent.readInt(); + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childType = parent.readInt(); + if (childType == Atom.TYPE_esds) { + return childAtomPosition; + } + childAtomPosition += childAtomSize; + } + return C.POSITION_UNSET; + } + + /** + * Returns codec-specific initialization data contained in an esds box. + */ + private static Pair parseEsdsFromParent(ParsableByteArray parent, int position) { + parent.setPosition(position + Atom.HEADER_SIZE + 4); + // Start of the ES_Descriptor (defined in 14496-1) + parent.skipBytes(1); // ES_Descriptor tag + parseExpandableClassSize(parent); + parent.skipBytes(2); // ES_ID + + int flags = parent.readUnsignedByte(); + if ((flags & 0x80 /* streamDependenceFlag */) != 0) { + parent.skipBytes(2); + } + if ((flags & 0x40 /* URL_Flag */) != 0) { + parent.skipBytes(parent.readUnsignedShort()); + } + if ((flags & 0x20 /* OCRstreamFlag */) != 0) { + parent.skipBytes(2); + } + + // Start of the DecoderConfigDescriptor (defined in 14496-1) + parent.skipBytes(1); // DecoderConfigDescriptor tag + parseExpandableClassSize(parent); + + // Set the MIME type based on the object type indication (14496-1 table 5). + int objectTypeIndication = parent.readUnsignedByte(); + String mimeType; + switch (objectTypeIndication) { + case 0x6B: + mimeType = MimeTypes.AUDIO_MPEG; + return Pair.create(mimeType, null); + case 0x20: + mimeType = MimeTypes.VIDEO_MP4V; + break; + case 0x21: + mimeType = MimeTypes.VIDEO_H264; + break; + case 0x23: + mimeType = MimeTypes.VIDEO_H265; + break; + case 0x40: + case 0x66: + case 0x67: + case 0x68: + mimeType = MimeTypes.AUDIO_AAC; + break; + case 0xA5: + mimeType = MimeTypes.AUDIO_AC3; + break; + case 0xA6: + mimeType = MimeTypes.AUDIO_E_AC3; + break; + case 0xA9: + case 0xAC: + mimeType = MimeTypes.AUDIO_DTS; + return Pair.create(mimeType, null); + case 0xAA: + case 0xAB: + mimeType = MimeTypes.AUDIO_DTS_HD; + return Pair.create(mimeType, null); + default: + mimeType = null; + break; + } + + parent.skipBytes(12); + + // Start of the AudioSpecificConfig. + parent.skipBytes(1); // AudioSpecificConfig tag + int initializationDataSize = parseExpandableClassSize(parent); + byte[] initializationData = new byte[initializationDataSize]; + parent.readBytes(initializationData, 0, initializationDataSize); + return Pair.create(mimeType, initializationData); + } + + /** + * Parses encryption data from an audio/video sample entry, populating {@code out} and returning + * the unencrypted atom type, or 0 if no common encryption sinf atom was present. + */ + private static int parseSampleEntryEncryptionData(ParsableByteArray parent, int position, + int size, StsdData out, int entryIndex) { + int childPosition = parent.getPosition(); + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_sinf) { + Pair result = parseSinfFromParent(parent, childPosition, + childAtomSize); + if (result != null) { + out.trackEncryptionBoxes[entryIndex] = result.second; + return result.first; + } + } + childPosition += childAtomSize; + } + // This enca/encv box does not have a data format so return an invalid atom type. + return 0; + } + + private static Pair parseSinfFromParent(ParsableByteArray parent, + int position, int size) { + int childPosition = position + Atom.HEADER_SIZE; + + boolean isCencScheme = false; + TrackEncryptionBox trackEncryptionBox = null; + Integer dataFormat = null; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_frma) { + dataFormat = parent.readInt(); + } else if (childAtomType == Atom.TYPE_schm) { + parent.skipBytes(4); + isCencScheme = parent.readInt() == TYPE_cenc; + } else if (childAtomType == Atom.TYPE_schi) { + trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize); + } + childPosition += childAtomSize; + } + + if (isCencScheme) { + Assertions.checkArgument(dataFormat != null, "frma atom is mandatory"); + Assertions.checkArgument(trackEncryptionBox != null, "schi->tenc atom is mandatory"); + return Pair.create(dataFormat, trackEncryptionBox); + } else { + return null; + } + } + + private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position, + int size) { + int childPosition = position + Atom.HEADER_SIZE; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_tenc) { + parent.skipBytes(6); + boolean defaultIsEncrypted = parent.readUnsignedByte() == 1; + int defaultInitVectorSize = parent.readUnsignedByte(); + byte[] defaultKeyId = new byte[16]; + parent.readBytes(defaultKeyId, 0, defaultKeyId.length); + return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId); + } + childPosition += childAtomSize; + } + return null; + } + + /** + * Parses the proj box from sv3d box, as specified by https://github.com/google/spatial-media + */ + private static byte[] parseProjFromParent(ParsableByteArray parent, int position, int size) { + int childPosition = position + Atom.HEADER_SIZE; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_proj) { + return Arrays.copyOfRange(parent.data, childPosition, childPosition + childAtomSize); + } + childPosition += childAtomSize; + } + return null; + } + + /** + * Parses the size of an expandable class, as specified by ISO 14496-1 subsection 8.3.3. + */ + private static int parseExpandableClassSize(ParsableByteArray data) { + int currentByte = data.readUnsignedByte(); + int size = currentByte & 0x7F; + while ((currentByte & 0x80) == 0x80) { + currentByte = data.readUnsignedByte(); + size = (size << 7) | (currentByte & 0x7F); + } + return size; + } + + private AtomParsers() { + // Prevent instantiation. + } + + private static final class ChunkIterator { + + public final int length; + + public int index; + public int numSamples; + public long offset; + + private final boolean chunkOffsetsAreLongs; + private final ParsableByteArray chunkOffsets; + private final ParsableByteArray stsc; + + private int nextSamplesPerChunkChangeIndex; + private int remainingSamplesPerChunkChanges; + + public ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets, + boolean chunkOffsetsAreLongs) { + this.stsc = stsc; + this.chunkOffsets = chunkOffsets; + this.chunkOffsetsAreLongs = chunkOffsetsAreLongs; + chunkOffsets.setPosition(Atom.FULL_HEADER_SIZE); + length = chunkOffsets.readUnsignedIntToInt(); + stsc.setPosition(Atom.FULL_HEADER_SIZE); + remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt(); + Assertions.checkState(stsc.readInt() == 1, "first_chunk must be 1"); + index = C.INDEX_UNSET; + } + + public boolean moveNext() { + if (++index == length) { + return false; + } + offset = chunkOffsetsAreLongs ? chunkOffsets.readUnsignedLongToLong() + : chunkOffsets.readUnsignedInt(); + if (index == nextSamplesPerChunkChangeIndex) { + numSamples = stsc.readUnsignedIntToInt(); + stsc.skipBytes(4); // Skip sample_description_index + nextSamplesPerChunkChangeIndex = --remainingSamplesPerChunkChanges > 0 + ? (stsc.readUnsignedIntToInt() - 1) : C.INDEX_UNSET; + } + return true; + } + + } + + /** + * Holds data parsed from a tkhd atom. + */ + private static final class TkhdData { + + private final int id; + private final long duration; + private final int rotationDegrees; + + public TkhdData(int id, long duration, int rotationDegrees) { + this.id = id; + this.duration = duration; + this.rotationDegrees = rotationDegrees; + } + + } + + /** + * Holds data parsed from an stsd atom and its children. + */ + private static final class StsdData { + + public static final int STSD_HEADER_SIZE = 8; + + public final TrackEncryptionBox[] trackEncryptionBoxes; + + public Format format; + public int nalUnitLengthFieldLength; + @Track.Transformation + public int requiredSampleTransformation; + + public StsdData(int numberOfEntries) { + trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries]; + requiredSampleTransformation = Track.TRANSFORMATION_NONE; + } + + } + + /** + * A box containing sample sizes (e.g. stsz, stz2). + */ + private interface SampleSizeBox { + + /** + * Returns the number of samples. + */ + int getSampleCount(); + + /** + * Returns the size for the next sample. + */ + int readNextSampleSize(); + + /** + * Returns whether samples have a fixed size. + */ + boolean isFixedSampleSize(); + + } + + /** + * An stsz sample size box. + */ + /* package */ static final class StszSampleSizeBox implements SampleSizeBox { + + private final int fixedSampleSize; + private final int sampleCount; + private final ParsableByteArray data; + + public StszSampleSizeBox(Atom.LeafAtom stszAtom) { + data = stszAtom.data; + data.setPosition(Atom.FULL_HEADER_SIZE); + fixedSampleSize = data.readUnsignedIntToInt(); + sampleCount = data.readUnsignedIntToInt(); + } + + @Override + public int getSampleCount() { + return sampleCount; + } + + @Override + public int readNextSampleSize() { + return fixedSampleSize == 0 ? data.readUnsignedIntToInt() : fixedSampleSize; + } + + @Override + public boolean isFixedSampleSize() { + return fixedSampleSize != 0; + } + + } + + /** + * An stz2 sample size box. + */ + /* package */ static final class Stz2SampleSizeBox implements SampleSizeBox { + + private final ParsableByteArray data; + private final int sampleCount; + private final int fieldSize; // Can be 4, 8, or 16. + + // Used only if fieldSize == 4. + private int sampleIndex; + private int currentByte; + + public Stz2SampleSizeBox(Atom.LeafAtom stz2Atom) { + data = stz2Atom.data; + data.setPosition(Atom.FULL_HEADER_SIZE); + fieldSize = data.readUnsignedIntToInt() & 0x000000FF; + sampleCount = data.readUnsignedIntToInt(); + } + + @Override + public int getSampleCount() { + return sampleCount; + } + + @Override + public int readNextSampleSize() { + if (fieldSize == 8) { + return data.readUnsignedByte(); + } else if (fieldSize == 16) { + return data.readUnsignedShort(); + } else { + // fieldSize == 4. + if ((sampleIndex++ % 2) == 0) { + // Read the next byte into our cached byte when we are reading the upper bits. + currentByte = data.readUnsignedByte(); + // Read the upper bits from the byte and shift them to the lower 4 bits. + return (currentByte & 0xF0) >> 4; + } else { + // Mask out the upper 4 bits of the last byte we read. + return currentByte & 0x0F; + } + } + } + + @Override + public boolean isFixedSampleSize() { + return false; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/DefaultSampleValues.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/DefaultSampleValues.java new file mode 100644 index 0000000..98c814c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/DefaultSampleValues.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +/* package */ final class DefaultSampleValues { + + public final int sampleDescriptionIndex; + public final int duration; + public final int size; + public final int flags; + + public DefaultSampleValues(int sampleDescriptionIndex, int duration, int size, int flags) { + this.sampleDescriptionIndex = sampleDescriptionIndex; + this.duration = duration; + this.size = size; + this.flags = flags; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java new file mode 100644 index 0000000..b17496e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Rechunks fixed sample size media in which every sample is a key frame (e.g. uncompressed audio). + */ +/* package */ final class FixedSampleSizeRechunker { + + /** + * The result of a rechunking operation. + */ + public static final class Results { + + public final long[] offsets; + public final int[] sizes; + public final int maximumSize; + public final long[] timestamps; + public final int[] flags; + + private Results(long[] offsets, int[] sizes, int maximumSize, long[] timestamps, int[] flags) { + this.offsets = offsets; + this.sizes = sizes; + this.maximumSize = maximumSize; + this.timestamps = timestamps; + this.flags = flags; + } + + } + + /** + * Maximum number of bytes for each buffer in rechunked output. + */ + private static final int MAX_SAMPLE_SIZE = 8 * 1024; + + /** + * Rechunk the given fixed sample size input to produce a new sequence of samples. + * + * @param fixedSampleSize Size in bytes of each sample. + * @param chunkOffsets Chunk offsets in the MP4 stream to rechunk. + * @param chunkSampleCounts Sample counts for each of the MP4 stream's chunks. + * @param timestampDeltaInTimeUnits Timestamp delta between each sample in time units. + */ + public static Results rechunk(int fixedSampleSize, long[] chunkOffsets, int[] chunkSampleCounts, + long timestampDeltaInTimeUnits) { + int maxSampleCount = MAX_SAMPLE_SIZE / fixedSampleSize; + + // Count the number of new, rechunked buffers. + int rechunkedSampleCount = 0; + for (int chunkSampleCount : chunkSampleCounts) { + rechunkedSampleCount += Util.ceilDivide(chunkSampleCount, maxSampleCount); + } + + long[] offsets = new long[rechunkedSampleCount]; + int[] sizes = new int[rechunkedSampleCount]; + int maximumSize = 0; + long[] timestamps = new long[rechunkedSampleCount]; + int[] flags = new int[rechunkedSampleCount]; + + int originalSampleIndex = 0; + int newSampleIndex = 0; + for (int chunkIndex = 0; chunkIndex < chunkSampleCounts.length; chunkIndex++) { + int chunkSamplesRemaining = chunkSampleCounts[chunkIndex]; + long sampleOffset = chunkOffsets[chunkIndex]; + + while (chunkSamplesRemaining > 0) { + int bufferSampleCount = Math.min(maxSampleCount, chunkSamplesRemaining); + + offsets[newSampleIndex] = sampleOffset; + sizes[newSampleIndex] = fixedSampleSize * bufferSampleCount; + maximumSize = Math.max(maximumSize, sizes[newSampleIndex]); + timestamps[newSampleIndex] = (timestampDeltaInTimeUnits * originalSampleIndex); + flags[newSampleIndex] = C.BUFFER_FLAG_KEY_FRAME; + + sampleOffset += sizes[newSampleIndex]; + originalSampleIndex += bufferSampleCount; + + chunkSamplesRemaining -= bufferSampleCount; + newSampleIndex++; + } + } + + return new Results(offsets, sizes, maximumSize, timestamps, flags); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java new file mode 100644 index 0000000..f3111a9 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -0,0 +1,1315 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import android.support.annotation.IntDef; +import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData.SchemeData; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ChunkIndex; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.Atom.ContainerAtom; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.Atom.LeafAtom; +import com.tangxiaolv.telegramgallery.exoplayer2.text.cea.CeaUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; +import java.util.UUID; + +/** + * Facilitates the extraction of data from the fragmented mp4 container format. + */ +public final class FragmentedMp4Extractor implements Extractor { + + /** + * Factory for {@link FragmentedMp4Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new FragmentedMp4Extractor()}; + } + + }; + + /** + * Flags controlling the behavior of the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, + FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_ENABLE_CEA608_TRACK, + FLAG_SIDELOADED}) + public @interface Flags {} + /** + * Flag to work around an issue in some video streams where every frame is marked as a sync frame. + * The workaround overrides the sync frame flags in the stream, forcing them to false except for + * the first sample in each segment. + *

    + * This flag does nothing if the stream is not a video stream. + */ + public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1; + /** + * Flag to ignore any tfdt boxes in the stream. + */ + public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2; + /** + * Flag to indicate that the extractor should output an event message metadata track. Any event + * messages in the stream will be delivered as samples to this track. + */ + public static final int FLAG_ENABLE_EMSG_TRACK = 4; + /** + * Flag to indicate that the extractor should output a CEA-608 text track. Any CEA-608 messages + * contained within SEI NAL units in the stream will be delivered as samples to this track. + */ + public static final int FLAG_ENABLE_CEA608_TRACK = 8; + /** + * Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4 + * container. + */ + private static final int FLAG_SIDELOADED = 16; + + private static final String TAG = "FragmentedMp4Extractor"; + private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); + private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = + new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12}; + + // Parser states. + private static final int STATE_READING_ATOM_HEADER = 0; + private static final int STATE_READING_ATOM_PAYLOAD = 1; + private static final int STATE_READING_ENCRYPTION_DATA = 2; + private static final int STATE_READING_SAMPLE_START = 3; + private static final int STATE_READING_SAMPLE_CONTINUE = 4; + + // Workarounds. + @Flags private final int flags; + private final Track sideloadedTrack; + + // Track-linked data bundle, accessible as a whole through trackID. + private final SparseArray trackBundles; + + // Temporary arrays. + private final ParsableByteArray nalStartCode; + private final ParsableByteArray nalPrefix; + private final ParsableByteArray nalBuffer; + private final ParsableByteArray encryptionSignalByte; + + // Adjusts sample timestamps. + private final TimestampAdjuster timestampAdjuster; + + // Parser state. + private final ParsableByteArray atomHeader; + private final byte[] extendedTypeScratch; + private final Stack containerAtoms; + private final LinkedList pendingMetadataSampleInfos; + + private int parserState; + private int atomType; + private long atomSize; + private int atomHeaderBytesRead; + private ParsableByteArray atomData; + private long endOfMdatPosition; + private int pendingMetadataSampleBytes; + + private long durationUs; + private long segmentIndexEarliestPresentationTimeUs; + private TrackBundle currentTrackBundle; + private int sampleSize; + private int sampleBytesWritten; + private int sampleCurrentNalBytesRemaining; + private boolean processSeiNalUnitPayload; + + // Extractor output. + private ExtractorOutput extractorOutput; + private TrackOutput eventMessageTrackOutput; + private TrackOutput[] cea608TrackOutputs; + + // Whether extractorOutput.seekMap has been called. + private boolean haveOutputSeekMap; + + public FragmentedMp4Extractor() { + this(0); + } + + /** + * @param flags Flags that control the extractor's behavior. + */ + public FragmentedMp4Extractor(@Flags int flags) { + this(flags, null); + } + + /** + * @param flags Flags that control the extractor's behavior. + * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. + */ + public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster) { + this(flags, timestampAdjuster, null); + } + + /** + * @param flags Flags that control the extractor's behavior. + * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. + * @param sideloadedTrack Sideloaded track information, in the case that the extractor + * will not receive a moov box in the input data. + */ + public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster, + Track sideloadedTrack) { + this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); + this.timestampAdjuster = timestampAdjuster; + this.sideloadedTrack = sideloadedTrack; + atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); + nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); + nalPrefix = new ParsableByteArray(5); + nalBuffer = new ParsableByteArray(); + encryptionSignalByte = new ParsableByteArray(1); + extendedTypeScratch = new byte[16]; + containerAtoms = new Stack<>(); + pendingMetadataSampleInfos = new LinkedList<>(); + trackBundles = new SparseArray<>(); + durationUs = C.TIME_UNSET; + segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; + enterReadingAtomHeaderState(); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return Sniffer.sniffFragmented(input); + } + + @Override + public void init(ExtractorOutput output) { + extractorOutput = output; + if (sideloadedTrack != null) { + TrackBundle bundle = new TrackBundle(output.track(0, sideloadedTrack.type)); + bundle.init(sideloadedTrack, new DefaultSampleValues(0, 0, 0, 0)); + trackBundles.put(0, bundle); + maybeInitExtraTracks(); + extractorOutput.endTracks(); + } + } + + @Override + public void seek(long position, long timeUs) { + int trackCount = trackBundles.size(); + for (int i = 0; i < trackCount; i++) { + trackBundles.valueAt(i).reset(); + } + pendingMetadataSampleInfos.clear(); + pendingMetadataSampleBytes = 0; + containerAtoms.clear(); + enterReadingAtomHeaderState(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + while (true) { + switch (parserState) { + case STATE_READING_ATOM_HEADER: + if (!readAtomHeader(input)) { + return Extractor.RESULT_END_OF_INPUT; + } + break; + case STATE_READING_ATOM_PAYLOAD: + readAtomPayload(input); + break; + case STATE_READING_ENCRYPTION_DATA: + readEncryptionData(input); + break; + default: + if (readSample(input)) { + return RESULT_CONTINUE; + } + } + } + } + + private void enterReadingAtomHeaderState() { + parserState = STATE_READING_ATOM_HEADER; + atomHeaderBytesRead = 0; + } + + private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException { + if (atomHeaderBytesRead == 0) { + // Read the standard length atom header. + if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) { + return false; + } + atomHeaderBytesRead = Atom.HEADER_SIZE; + atomHeader.setPosition(0); + atomSize = atomHeader.readUnsignedInt(); + atomType = atomHeader.readInt(); + } + + if (atomSize == Atom.LONG_SIZE_PREFIX) { + // Read the extended atom size. + int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; + input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); + atomHeaderBytesRead += headerBytesRemaining; + atomSize = atomHeader.readUnsignedLongToLong(); + } + + if (atomSize < atomHeaderBytesRead) { + throw new ParserException("Atom size less than header length (unsupported)."); + } + + long atomPosition = input.getPosition() - atomHeaderBytesRead; + if (atomType == Atom.TYPE_moof) { + // The data positions may be updated when parsing the tfhd/trun. + int trackCount = trackBundles.size(); + for (int i = 0; i < trackCount; i++) { + TrackFragment fragment = trackBundles.valueAt(i).fragment; + fragment.atomPosition = atomPosition; + fragment.auxiliaryDataPosition = atomPosition; + fragment.dataPosition = atomPosition; + } + } + + if (atomType == Atom.TYPE_mdat) { + currentTrackBundle = null; + endOfMdatPosition = atomPosition + atomSize; + if (!haveOutputSeekMap) { + extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); + haveOutputSeekMap = true; + } + parserState = STATE_READING_ENCRYPTION_DATA; + return true; + } + + if (shouldParseContainerAtom(atomType)) { + long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; + containerAtoms.add(new ContainerAtom(atomType, endPosition)); + if (atomSize == atomHeaderBytesRead) { + processAtomEnded(endPosition); + } else { + // Start reading the first child atom. + enterReadingAtomHeaderState(); + } + } else if (shouldParseLeafAtom(atomType)) { + if (atomHeaderBytesRead != Atom.HEADER_SIZE) { + throw new ParserException("Leaf atom defines extended atom size (unsupported)."); + } + if (atomSize > Integer.MAX_VALUE) { + throw new ParserException("Leaf atom with length > 2147483647 (unsupported)."); + } + atomData = new ParsableByteArray((int) atomSize); + System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE); + parserState = STATE_READING_ATOM_PAYLOAD; + } else { + if (atomSize > Integer.MAX_VALUE) { + throw new ParserException("Skipping atom with length > 2147483647 (unsupported)."); + } + atomData = null; + parserState = STATE_READING_ATOM_PAYLOAD; + } + + return true; + } + + private void readAtomPayload(ExtractorInput input) throws IOException, InterruptedException { + int atomPayloadSize = (int) atomSize - atomHeaderBytesRead; + if (atomData != null) { + input.readFully(atomData.data, Atom.HEADER_SIZE, atomPayloadSize); + onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition()); + } else { + input.skipFully(atomPayloadSize); + } + processAtomEnded(input.getPosition()); + } + + private void processAtomEnded(long atomEndPosition) throws ParserException { + while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) { + onContainerAtomRead(containerAtoms.pop()); + } + enterReadingAtomHeaderState(); + } + + private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException { + if (!containerAtoms.isEmpty()) { + containerAtoms.peek().add(leaf); + } else if (leaf.type == Atom.TYPE_sidx) { + Pair result = parseSidx(leaf.data, inputPosition); + segmentIndexEarliestPresentationTimeUs = result.first; + extractorOutput.seekMap(result.second); + haveOutputSeekMap = true; + } else if (leaf.type == Atom.TYPE_emsg) { + onEmsgLeafAtomRead(leaf.data); + } + } + + private void onContainerAtomRead(ContainerAtom container) throws ParserException { + if (container.type == Atom.TYPE_moov) { + onMoovContainerAtomRead(container); + } else if (container.type == Atom.TYPE_moof) { + onMoofContainerAtomRead(container); + } else if (!containerAtoms.isEmpty()) { + containerAtoms.peek().add(container); + } + } + + private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException { + Assertions.checkState(sideloadedTrack == null, "Unexpected moov box."); + + DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren); + + // Read declaration of track fragments in the Moov box. + ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); + SparseArray defaultSampleValuesArray = new SparseArray<>(); + long duration = C.TIME_UNSET; + int mvexChildrenSize = mvex.leafChildren.size(); + for (int i = 0; i < mvexChildrenSize; i++) { + Atom.LeafAtom atom = mvex.leafChildren.get(i); + if (atom.type == Atom.TYPE_trex) { + Pair trexData = parseTrex(atom.data); + defaultSampleValuesArray.put(trexData.first, trexData.second); + } else if (atom.type == Atom.TYPE_mehd) { + duration = parseMehd(atom.data); + } + } + + // Construction of tracks. + SparseArray tracks = new SparseArray<>(); + int moovContainerChildrenSize = moov.containerChildren.size(); + for (int i = 0; i < moovContainerChildrenSize; i++) { + Atom.ContainerAtom atom = moov.containerChildren.get(i); + if (atom.type == Atom.TYPE_trak) { + Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), duration, + drmInitData, false); + if (track != null) { + tracks.put(track.id, track); + } + } + } + + int trackCount = tracks.size(); + if (trackBundles.size() == 0) { + // We need to create the track bundles. + for (int i = 0; i < trackCount; i++) { + Track track = tracks.valueAt(i); + TrackBundle trackBundle = new TrackBundle(extractorOutput.track(i, track.type)); + trackBundle.init(track, defaultSampleValuesArray.get(track.id)); + trackBundles.put(track.id, trackBundle); + durationUs = Math.max(durationUs, track.durationUs); + } + maybeInitExtraTracks(); + extractorOutput.endTracks(); + } else { + Assertions.checkState(trackBundles.size() == trackCount); + for (int i = 0; i < trackCount; i++) { + Track track = tracks.valueAt(i); + trackBundles.get(track.id).init(track, defaultSampleValuesArray.get(track.id)); + } + } + } + + private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { + parseMoof(moof, trackBundles, flags, extendedTypeScratch); + DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren); + if (drmInitData != null) { + int trackCount = trackBundles.size(); + for (int i = 0; i < trackCount; i++) { + trackBundles.valueAt(i).updateDrmInitData(drmInitData); + } + } + } + + private void maybeInitExtraTracks() { + if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0 && eventMessageTrackOutput == null) { + eventMessageTrackOutput = extractorOutput.track(trackBundles.size(), C.TRACK_TYPE_METADATA); + eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG, + Format.OFFSET_SAMPLE_RELATIVE)); + } + if ((flags & FLAG_ENABLE_CEA608_TRACK) != 0 && cea608TrackOutputs == null) { + TrackOutput cea608TrackOutput = extractorOutput.track(trackBundles.size() + 1, + C.TRACK_TYPE_TEXT); + cea608TrackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, + null, Format.NO_VALUE, 0, null, null)); + cea608TrackOutputs = new TrackOutput[] {cea608TrackOutput}; + } + } + + /** + * Handles an emsg atom (defined in 23009-1). + */ + private void onEmsgLeafAtomRead(ParsableByteArray atom) { + if (eventMessageTrackOutput == null) { + return; + } + // Parse the event's presentation time delta. + atom.setPosition(Atom.FULL_HEADER_SIZE); + atom.readNullTerminatedString(); // schemeIdUri + atom.readNullTerminatedString(); // value + long timescale = atom.readUnsignedInt(); + long presentationTimeDeltaUs = + Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale); + // Output the sample data. + atom.setPosition(Atom.FULL_HEADER_SIZE); + int sampleSize = atom.bytesLeft(); + eventMessageTrackOutput.sampleData(atom, sampleSize); + // Output the sample metadata. + if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) { + // We can output the sample metadata immediately. + eventMessageTrackOutput.sampleMetadata( + segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs, + C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0 /* offset */, null); + } else { + // We need the first sample timestamp in the segment before we can output the metadata. + pendingMetadataSampleInfos.addLast( + new MetadataSampleInfo(presentationTimeDeltaUs, sampleSize)); + pendingMetadataSampleBytes += sampleSize; + } + } + + /** + * Parses a trex atom (defined in 14496-12). + */ + private static Pair parseTrex(ParsableByteArray trex) { + trex.setPosition(Atom.FULL_HEADER_SIZE); + int trackId = trex.readInt(); + int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1; + int defaultSampleDuration = trex.readUnsignedIntToInt(); + int defaultSampleSize = trex.readUnsignedIntToInt(); + int defaultSampleFlags = trex.readInt(); + + return Pair.create(trackId, new DefaultSampleValues(defaultSampleDescriptionIndex, + defaultSampleDuration, defaultSampleSize, defaultSampleFlags)); + } + + /** + * Parses an mehd atom (defined in 14496-12). + */ + private static long parseMehd(ParsableByteArray mehd) { + mehd.setPosition(Atom.HEADER_SIZE); + int fullAtom = mehd.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong(); + } + + private static void parseMoof(ContainerAtom moof, SparseArray trackBundleArray, + @Flags int flags, byte[] extendedTypeScratch) throws ParserException { + int moofContainerChildrenSize = moof.containerChildren.size(); + for (int i = 0; i < moofContainerChildrenSize; i++) { + Atom.ContainerAtom child = moof.containerChildren.get(i); + // TODO: Support multiple traf boxes per track in a single moof. + if (child.type == Atom.TYPE_traf) { + parseTraf(child, trackBundleArray, flags, extendedTypeScratch); + } + } + } + + /** + * Parses a traf atom (defined in 14496-12). + */ + private static void parseTraf(ContainerAtom traf, SparseArray trackBundleArray, + @Flags int flags, byte[] extendedTypeScratch) throws ParserException { + LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); + TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags); + if (trackBundle == null) { + return; + } + + TrackFragment fragment = trackBundle.fragment; + long decodeTime = fragment.nextFragmentDecodeTime; + trackBundle.reset(); + + LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); + if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) { + decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); + } + + parseTruns(traf, trackBundle, decodeTime, flags); + + LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); + if (saiz != null) { + TrackEncryptionBox trackEncryptionBox = trackBundle.track + .sampleDescriptionEncryptionBoxes[fragment.header.sampleDescriptionIndex]; + parseSaiz(trackEncryptionBox, saiz.data, fragment); + } + + LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); + if (saio != null) { + parseSaio(saio.data, fragment); + } + + LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); + if (senc != null) { + parseSenc(senc.data, fragment); + } + + LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); + LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); + if (sbgp != null && sgpd != null) { + parseSgpd(sbgp.data, sgpd.data, fragment); + } + + int leafChildrenSize = traf.leafChildren.size(); + for (int i = 0; i < leafChildrenSize; i++) { + LeafAtom atom = traf.leafChildren.get(i); + if (atom.type == Atom.TYPE_uuid) { + parseUuid(atom.data, fragment, extendedTypeScratch); + } + } + } + + private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime, + @Flags int flags) { + int trunCount = 0; + int totalSampleCount = 0; + List leafChildren = traf.leafChildren; + int leafChildrenSize = leafChildren.size(); + for (int i = 0; i < leafChildrenSize; i++) { + LeafAtom atom = leafChildren.get(i); + if (atom.type == Atom.TYPE_trun) { + ParsableByteArray trunData = atom.data; + trunData.setPosition(Atom.FULL_HEADER_SIZE); + int trunSampleCount = trunData.readUnsignedIntToInt(); + if (trunSampleCount > 0) { + totalSampleCount += trunSampleCount; + trunCount++; + } + } + } + trackBundle.currentTrackRunIndex = 0; + trackBundle.currentSampleInTrackRun = 0; + trackBundle.currentSampleIndex = 0; + trackBundle.fragment.initTables(trunCount, totalSampleCount); + + int trunIndex = 0; + int trunStartPosition = 0; + for (int i = 0; i < leafChildrenSize; i++) { + LeafAtom trun = leafChildren.get(i); + if (trun.type == Atom.TYPE_trun) { + trunStartPosition = parseTrun(trackBundle, trunIndex++, decodeTime, flags, trun.data, + trunStartPosition); + } + } + } + + private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz, + TrackFragment out) throws ParserException { + int vectorSize = encryptionBox.initializationVectorSize; + saiz.setPosition(Atom.HEADER_SIZE); + int fullAtom = saiz.readInt(); + int flags = Atom.parseFullAtomFlags(fullAtom); + if ((flags & 0x01) == 1) { + saiz.skipBytes(8); + } + int defaultSampleInfoSize = saiz.readUnsignedByte(); + + int sampleCount = saiz.readUnsignedIntToInt(); + if (sampleCount != out.sampleCount) { + throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount); + } + + int totalSize = 0; + if (defaultSampleInfoSize == 0) { + boolean[] sampleHasSubsampleEncryptionTable = out.sampleHasSubsampleEncryptionTable; + for (int i = 0; i < sampleCount; i++) { + int sampleInfoSize = saiz.readUnsignedByte(); + totalSize += sampleInfoSize; + sampleHasSubsampleEncryptionTable[i] = sampleInfoSize > vectorSize; + } + } else { + boolean subsampleEncryption = defaultSampleInfoSize > vectorSize; + totalSize += defaultSampleInfoSize * sampleCount; + Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption); + } + out.initEncryptionData(totalSize); + } + + /** + * Parses a saio atom (defined in 14496-12). + * + * @param saio The saio atom to decode. + * @param out The {@link TrackFragment} to populate with data from the saio atom. + */ + private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException { + saio.setPosition(Atom.HEADER_SIZE); + int fullAtom = saio.readInt(); + int flags = Atom.parseFullAtomFlags(fullAtom); + if ((flags & 0x01) == 1) { + saio.skipBytes(8); + } + + int entryCount = saio.readUnsignedIntToInt(); + if (entryCount != 1) { + // We only support one trun element currently, so always expect one entry. + throw new ParserException("Unexpected saio entry count: " + entryCount); + } + + int version = Atom.parseFullAtomVersion(fullAtom); + out.auxiliaryDataPosition += + version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong(); + } + + /** + * Parses a tfhd atom (defined in 14496-12), updates the corresponding {@link TrackFragment} and + * returns the {@link TrackBundle} of the corresponding {@link Track}. If the tfhd does not refer + * to any {@link TrackBundle}, {@code null} is returned and no changes are made. + * + * @param tfhd The tfhd atom to decode. + * @param trackBundles The track bundles, one of which corresponds to the tfhd atom being parsed. + * @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd + * does not refer to any {@link TrackBundle}. + */ + private static TrackBundle parseTfhd(ParsableByteArray tfhd, + SparseArray trackBundles, int flags) { + tfhd.setPosition(Atom.HEADER_SIZE); + int fullAtom = tfhd.readInt(); + int atomFlags = Atom.parseFullAtomFlags(fullAtom); + int trackId = tfhd.readInt(); + TrackBundle trackBundle = trackBundles.get((flags & FLAG_SIDELOADED) == 0 ? trackId : 0); + if (trackBundle == null) { + return null; + } + if ((atomFlags & 0x01 /* base_data_offset_present */) != 0) { + long baseDataPosition = tfhd.readUnsignedLongToLong(); + trackBundle.fragment.dataPosition = baseDataPosition; + trackBundle.fragment.auxiliaryDataPosition = baseDataPosition; + } + + DefaultSampleValues defaultSampleValues = trackBundle.defaultSampleValues; + int defaultSampleDescriptionIndex = + ((atomFlags & 0x02 /* default_sample_description_index_present */) != 0) + ? tfhd.readUnsignedIntToInt() - 1 : defaultSampleValues.sampleDescriptionIndex; + int defaultSampleDuration = ((atomFlags & 0x08 /* default_sample_duration_present */) != 0) + ? tfhd.readUnsignedIntToInt() : defaultSampleValues.duration; + int defaultSampleSize = ((atomFlags & 0x10 /* default_sample_size_present */) != 0) + ? tfhd.readUnsignedIntToInt() : defaultSampleValues.size; + int defaultSampleFlags = ((atomFlags & 0x20 /* default_sample_flags_present */) != 0) + ? tfhd.readUnsignedIntToInt() : defaultSampleValues.flags; + trackBundle.fragment.header = new DefaultSampleValues(defaultSampleDescriptionIndex, + defaultSampleDuration, defaultSampleSize, defaultSampleFlags); + return trackBundle; + } + + /** + * Parses a tfdt atom (defined in 14496-12). + * + * @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the + * media, expressed in the media's timescale. + */ + private static long parseTfdt(ParsableByteArray tfdt) { + tfdt.setPosition(Atom.HEADER_SIZE); + int fullAtom = tfdt.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt(); + } + + /** + * Parses a trun atom (defined in 14496-12). + * + * @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into + * which parsed data should be placed. + * @param index Index of the track run in the fragment. + * @param decodeTime The decode time of the first sample in the fragment run. + * @param flags Flags to allow any required workaround to be executed. + * @param trun The trun atom to decode. + * @return The starting position of samples for the next run. + */ + private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, + @Flags int flags, ParsableByteArray trun, int trackRunStart) { + trun.setPosition(Atom.HEADER_SIZE); + int fullAtom = trun.readInt(); + int atomFlags = Atom.parseFullAtomFlags(fullAtom); + + Track track = trackBundle.track; + TrackFragment fragment = trackBundle.fragment; + DefaultSampleValues defaultSampleValues = fragment.header; + + fragment.trunLength[index] = trun.readUnsignedIntToInt(); + fragment.trunDataPosition[index] = fragment.dataPosition; + if ((atomFlags & 0x01 /* data_offset_present */) != 0) { + fragment.trunDataPosition[index] += trun.readInt(); + } + + boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0; + int firstSampleFlags = defaultSampleValues.flags; + if (firstSampleFlagsPresent) { + firstSampleFlags = trun.readUnsignedIntToInt(); + } + + boolean sampleDurationsPresent = (atomFlags & 0x100 /* sample_duration_present */) != 0; + boolean sampleSizesPresent = (atomFlags & 0x200 /* sample_size_present */) != 0; + boolean sampleFlagsPresent = (atomFlags & 0x400 /* sample_flags_present */) != 0; + boolean sampleCompositionTimeOffsetsPresent = + (atomFlags & 0x800 /* sample_composition_time_offsets_present */) != 0; + + // Offset to the entire video timeline. In the presence of B-frames this is usually used to + // ensure that the first frame's presentation timestamp is zero. + long edtsOffset = 0; + + // Currently we only support a single edit that moves the entire media timeline (indicated by + // duration == 0). Other uses of edit lists are uncommon and unsupported. + if (track.editListDurations != null && track.editListDurations.length == 1 + && track.editListDurations[0] == 0) { + edtsOffset = Util.scaleLargeTimestamp(track.editListMediaTimes[0], 1000, track.timescale); + } + + int[] sampleSizeTable = fragment.sampleSizeTable; + int[] sampleCompositionTimeOffsetTable = fragment.sampleCompositionTimeOffsetTable; + long[] sampleDecodingTimeTable = fragment.sampleDecodingTimeTable; + boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable; + + boolean workaroundEveryVideoFrameIsSyncFrame = track.type == C.TRACK_TYPE_VIDEO + && (flags & FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0; + + int trackRunEnd = trackRunStart + fragment.trunLength[index]; + long timescale = track.timescale; + long cumulativeTime = index > 0 ? fragment.nextFragmentDecodeTime : decodeTime; + for (int i = trackRunStart; i < trackRunEnd; i++) { + // Use trun values if present, otherwise tfhd, otherwise trex. + int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt() + : defaultSampleValues.duration; + int sampleSize = sampleSizesPresent ? trun.readUnsignedIntToInt() : defaultSampleValues.size; + int sampleFlags = (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags + : sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags; + if (sampleCompositionTimeOffsetsPresent) { + // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in + // version 0 trun boxes, however a significant number of streams violate the spec and use + // signed integers instead. It's safe to always decode sample offsets as signed integers + // here, because unsigned integers will still be parsed correctly (unless their top bit is + // set, which is never true in practice because sample offsets are always small). + int sampleOffset = trun.readInt(); + sampleCompositionTimeOffsetTable[i] = (int) ((sampleOffset * 1000) / timescale); + } else { + sampleCompositionTimeOffsetTable[i] = 0; + } + sampleDecodingTimeTable[i] = + Util.scaleLargeTimestamp(cumulativeTime, 1000, timescale) - edtsOffset; + sampleSizeTable[i] = sampleSize; + sampleIsSyncFrameTable[i] = ((sampleFlags >> 16) & 0x1) == 0 + && (!workaroundEveryVideoFrameIsSyncFrame || i == 0); + cumulativeTime += sampleDuration; + } + fragment.nextFragmentDecodeTime = cumulativeTime; + return trackRunEnd; + } + + private static void parseUuid(ParsableByteArray uuid, TrackFragment out, + byte[] extendedTypeScratch) throws ParserException { + uuid.setPosition(Atom.HEADER_SIZE); + uuid.readBytes(extendedTypeScratch, 0, 16); + + // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox. + if (!Arrays.equals(extendedTypeScratch, PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE)) { + return; + } + + // Except for the extended type, this box is identical to a SENC box. See "Portable encoding of + // audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al, + // Section 5.3.2.1." + parseSenc(uuid, 16, out); + } + + private static void parseSenc(ParsableByteArray senc, TrackFragment out) throws ParserException { + parseSenc(senc, 0, out); + } + + private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) + throws ParserException { + senc.setPosition(Atom.HEADER_SIZE + offset); + int fullAtom = senc.readInt(); + int flags = Atom.parseFullAtomFlags(fullAtom); + + if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) { + // TODO: Implement this. + throw new ParserException("Overriding TrackEncryptionBox parameters is unsupported."); + } + + boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0; + int sampleCount = senc.readUnsignedIntToInt(); + if (sampleCount != out.sampleCount) { + throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount); + } + + Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption); + out.initEncryptionData(senc.bytesLeft()); + out.fillEncryptionData(senc); + } + + private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, TrackFragment out) + throws ParserException { + sbgp.setPosition(Atom.HEADER_SIZE); + int sbgpFullAtom = sbgp.readInt(); + if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) { + // Only seig grouping type is supported. + return; + } + if (Atom.parseFullAtomVersion(sbgpFullAtom) == 1) { + sbgp.skipBytes(4); + } + if (sbgp.readInt() != 1) { + throw new ParserException("Entry count in sbgp != 1 (unsupported)."); + } + + sgpd.setPosition(Atom.HEADER_SIZE); + int sgpdFullAtom = sgpd.readInt(); + if (sgpd.readInt() != SAMPLE_GROUP_TYPE_seig) { + // Only seig grouping type is supported. + return; + } + int sgpdVersion = Atom.parseFullAtomVersion(sgpdFullAtom); + if (sgpdVersion == 1) { + if (sgpd.readUnsignedInt() == 0) { + throw new ParserException("Variable length decription in sgpd found (unsupported)"); + } + } else if (sgpdVersion >= 2) { + sgpd.skipBytes(4); + } + if (sgpd.readUnsignedInt() != 1) { + throw new ParserException("Entry count in sgpd != 1 (unsupported)."); + } + // CencSampleEncryptionInformationGroupEntry + sgpd.skipBytes(2); + boolean isProtected = sgpd.readUnsignedByte() == 1; + if (!isProtected) { + return; + } + int initVectorSize = sgpd.readUnsignedByte(); + byte[] keyId = new byte[16]; + sgpd.readBytes(keyId, 0, keyId.length); + out.definesEncryptionData = true; + out.trackEncryptionBox = new TrackEncryptionBox(isProtected, initVectorSize, keyId); + } + + /** + * Parses a sidx atom (defined in 14496-12). + * + * @param atom The atom data. + * @param inputPosition The input position of the first byte after the atom. + * @return A pair consisting of the earliest presentation time in microseconds, and the parsed + * {@link ChunkIndex}. + */ + private static Pair parseSidx(ParsableByteArray atom, long inputPosition) + throws ParserException { + atom.setPosition(Atom.HEADER_SIZE); + int fullAtom = atom.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + + atom.skipBytes(4); + long timescale = atom.readUnsignedInt(); + long earliestPresentationTime; + long offset = inputPosition; + if (version == 0) { + earliestPresentationTime = atom.readUnsignedInt(); + offset += atom.readUnsignedInt(); + } else { + earliestPresentationTime = atom.readUnsignedLongToLong(); + offset += atom.readUnsignedLongToLong(); + } + long earliestPresentationTimeUs = Util.scaleLargeTimestamp(earliestPresentationTime, + C.MICROS_PER_SECOND, timescale); + + atom.skipBytes(2); + + int referenceCount = atom.readUnsignedShort(); + int[] sizes = new int[referenceCount]; + long[] offsets = new long[referenceCount]; + long[] durationsUs = new long[referenceCount]; + long[] timesUs = new long[referenceCount]; + + long time = earliestPresentationTime; + long timeUs = earliestPresentationTimeUs; + for (int i = 0; i < referenceCount; i++) { + int firstInt = atom.readInt(); + + int type = 0x80000000 & firstInt; + if (type != 0) { + throw new ParserException("Unhandled indirect reference"); + } + long referenceDuration = atom.readUnsignedInt(); + + sizes[i] = 0x7FFFFFFF & firstInt; + offsets[i] = offset; + + // Calculate time and duration values such that any rounding errors are consistent. i.e. That + // timesUs[i] + durationsUs[i] == timesUs[i + 1]. + timesUs[i] = timeUs; + time += referenceDuration; + timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); + durationsUs[i] = timeUs - timesUs[i]; + + atom.skipBytes(4); + offset += sizes[i]; + } + + return Pair.create(earliestPresentationTimeUs, + new ChunkIndex(sizes, offsets, durationsUs, timesUs)); + } + + private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException { + TrackBundle nextTrackBundle = null; + long nextDataOffset = Long.MAX_VALUE; + int trackBundlesSize = trackBundles.size(); + for (int i = 0; i < trackBundlesSize; i++) { + TrackFragment trackFragment = trackBundles.valueAt(i).fragment; + if (trackFragment.sampleEncryptionDataNeedsFill + && trackFragment.auxiliaryDataPosition < nextDataOffset) { + nextDataOffset = trackFragment.auxiliaryDataPosition; + nextTrackBundle = trackBundles.valueAt(i); + } + } + if (nextTrackBundle == null) { + parserState = STATE_READING_SAMPLE_START; + return; + } + int bytesToSkip = (int) (nextDataOffset - input.getPosition()); + if (bytesToSkip < 0) { + throw new ParserException("Offset to encryption data was negative."); + } + input.skipFully(bytesToSkip); + nextTrackBundle.fragment.fillEncryptionData(input); + } + + /** + * Attempts to extract the next sample in the current mdat atom. + *

    + * If there are no more samples in the current mdat atom then the parser state is transitioned + * to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned. + *

    + * It is possible for a sample to be extracted in part in the case that an exception is thrown. In + * this case the method can be called again to extract the remainder of the sample. + * + * @param input The {@link ExtractorInput} from which to read data. + * @return Whether a sample was extracted. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + private boolean readSample(ExtractorInput input) throws IOException, InterruptedException { + if (parserState == STATE_READING_SAMPLE_START) { + if (currentTrackBundle == null) { + TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles); + if (currentTrackBundle == null) { + // We've run out of samples in the current mdat. Discard any trailing data and prepare to + // read the header of the next atom. + int bytesToSkip = (int) (endOfMdatPosition - input.getPosition()); + if (bytesToSkip < 0) { + throw new ParserException("Offset to end of mdat was negative."); + } + input.skipFully(bytesToSkip); + enterReadingAtomHeaderState(); + return false; + } + + long nextDataPosition = currentTrackBundle.fragment + .trunDataPosition[currentTrackBundle.currentTrackRunIndex]; + // We skip bytes preceding the next sample to read. + int bytesToSkip = (int) (nextDataPosition - input.getPosition()); + if (bytesToSkip < 0) { + // Assume the sample data must be contiguous in the mdat with no preceding data. + Log.w(TAG, "Ignoring negative offset to sample data."); + bytesToSkip = 0; + } + input.skipFully(bytesToSkip); + this.currentTrackBundle = currentTrackBundle; + } + sampleSize = currentTrackBundle.fragment + .sampleSizeTable[currentTrackBundle.currentSampleIndex]; + if (currentTrackBundle.fragment.definesEncryptionData) { + sampleBytesWritten = appendSampleEncryptionData(currentTrackBundle); + sampleSize += sampleBytesWritten; + } else { + sampleBytesWritten = 0; + } + if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { + sampleSize -= Atom.HEADER_SIZE; + input.skipFully(Atom.HEADER_SIZE); + } + parserState = STATE_READING_SAMPLE_CONTINUE; + sampleCurrentNalBytesRemaining = 0; + } + + TrackFragment fragment = currentTrackBundle.fragment; + Track track = currentTrackBundle.track; + TrackOutput output = currentTrackBundle.output; + int sampleIndex = currentTrackBundle.currentSampleIndex; + if (track.nalUnitLengthFieldLength != 0) { + // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case + // they're only 1 or 2 bytes long. + byte[] nalPrefixData = nalPrefix.data; + nalPrefixData[0] = 0; + nalPrefixData[1] = 0; + nalPrefixData[2] = 0; + int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1; + int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength; + // NAL units are length delimited, but the decoder requires start code delimited units. + // Loop until we've written the sample to the track output, replacing length delimiters with + // start codes as we encounter them. + while (sampleBytesWritten < sampleSize) { + if (sampleCurrentNalBytesRemaining == 0) { + // Read the NAL length so that we know where we find the next one, and its type. + input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength); + nalPrefix.setPosition(0); + sampleCurrentNalBytesRemaining = nalPrefix.readUnsignedIntToInt() - 1; + // Write a start code for the current NAL unit. + nalStartCode.setPosition(0); + output.sampleData(nalStartCode, 4); + // Write the NAL unit type byte. + output.sampleData(nalPrefix, 1); + processSeiNalUnitPayload = cea608TrackOutputs != null + && NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]); + sampleBytesWritten += 5; + sampleSize += nalUnitLengthFieldLengthDiff; + } else { + int writtenBytes; + if (processSeiNalUnitPayload) { + // Read and write the payload of the SEI NAL unit. + nalBuffer.reset(sampleCurrentNalBytesRemaining); + input.readFully(nalBuffer.data, 0, sampleCurrentNalBytesRemaining); + output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining); + writtenBytes = sampleCurrentNalBytesRemaining; + // Unescape and process the SEI NAL unit. + int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.data, nalBuffer.limit()); + // If the format is H.265/HEVC the NAL unit header has two bytes so skip one more byte. + nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0); + nalBuffer.setLimit(unescapedLength); + CeaUtil.consume(fragment.getSamplePresentationTime(sampleIndex) * 1000L, nalBuffer, + cea608TrackOutputs); + } else { + // Write the payload of the NAL unit. + writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false); + } + sampleBytesWritten += writtenBytes; + sampleCurrentNalBytesRemaining -= writtenBytes; + } + } + } else { + while (sampleBytesWritten < sampleSize) { + int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false); + sampleBytesWritten += writtenBytes; + } + } + + long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L; + @C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) + | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0); + int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; + byte[] encryptionKey = null; + if (fragment.definesEncryptionData) { + encryptionKey = fragment.trackEncryptionBox != null + ? fragment.trackEncryptionBox.keyId + : track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId; + } + if (timestampAdjuster != null) { + sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs); + } + output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey); + + while (!pendingMetadataSampleInfos.isEmpty()) { + MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst(); + pendingMetadataSampleBytes -= sampleInfo.size; + eventMessageTrackOutput.sampleMetadata( + sampleTimeUs + sampleInfo.presentationTimeDeltaUs, + C.BUFFER_FLAG_KEY_FRAME, sampleInfo.size, pendingMetadataSampleBytes, null); + } + + currentTrackBundle.currentSampleIndex++; + currentTrackBundle.currentSampleInTrackRun++; + if (currentTrackBundle.currentSampleInTrackRun + == fragment.trunLength[currentTrackBundle.currentTrackRunIndex]) { + currentTrackBundle.currentTrackRunIndex++; + currentTrackBundle.currentSampleInTrackRun = 0; + currentTrackBundle = null; + } + parserState = STATE_READING_SAMPLE_START; + return true; + } + + /** + * Returns the {@link TrackBundle} whose fragment run has the earliest file position out of those + * yet to be consumed, or null if all have been consumed. + */ + private static TrackBundle getNextFragmentRun(SparseArray trackBundles) { + TrackBundle nextTrackBundle = null; + long nextTrackRunOffset = Long.MAX_VALUE; + + int trackBundlesSize = trackBundles.size(); + for (int i = 0; i < trackBundlesSize; i++) { + TrackBundle trackBundle = trackBundles.valueAt(i); + if (trackBundle.currentTrackRunIndex == trackBundle.fragment.trunCount) { + // This track fragment contains no more runs in the next mdat box. + } else { + long trunOffset = trackBundle.fragment.trunDataPosition[trackBundle.currentTrackRunIndex]; + if (trunOffset < nextTrackRunOffset) { + nextTrackBundle = trackBundle; + nextTrackRunOffset = trunOffset; + } + } + } + return nextTrackBundle; + } + + /** + * Appends the corresponding encryption data to the {@link TrackOutput} contained in the given + * {@link TrackBundle}. + * + * @param trackBundle The {@link TrackBundle} that contains the {@link Track} for which the + * Sample encryption data must be output. + * @return The number of written bytes. + */ + private int appendSampleEncryptionData(TrackBundle trackBundle) { + TrackFragment trackFragment = trackBundle.fragment; + ParsableByteArray sampleEncryptionData = trackFragment.sampleEncryptionData; + int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex; + TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null + ? trackFragment.trackEncryptionBox + : trackBundle.track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex]; + int vectorSize = encryptionBox.initializationVectorSize; + boolean subsampleEncryption = trackFragment + .sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex]; + + // Write the signal byte, containing the vector size and the subsample encryption flag. + encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0)); + encryptionSignalByte.setPosition(0); + TrackOutput output = trackBundle.output; + output.sampleData(encryptionSignalByte, 1); + // Write the vector. + output.sampleData(sampleEncryptionData, vectorSize); + // If we don't have subsample encryption data, we're done. + if (!subsampleEncryption) { + return 1 + vectorSize; + } + // Write the subsample encryption data. + int subsampleCount = sampleEncryptionData.readUnsignedShort(); + sampleEncryptionData.skipBytes(-2); + int subsampleDataLength = 2 + 6 * subsampleCount; + output.sampleData(sampleEncryptionData, subsampleDataLength); + return 1 + vectorSize + subsampleDataLength; + } + + + /** Returns DrmInitData from leaf atoms. */ + private static DrmInitData getDrmInitDataFromAtoms(List leafChildren) { + ArrayList schemeDatas = null; + int leafChildrenSize = leafChildren.size(); + for (int i = 0; i < leafChildrenSize; i++) { + LeafAtom child = leafChildren.get(i); + if (child.type == Atom.TYPE_pssh) { + if (schemeDatas == null) { + schemeDatas = new ArrayList<>(); + } + byte[] psshData = child.data.data; + UUID uuid = PsshAtomUtil.parseUuid(psshData); + if (uuid == null) { + Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); + } else { + schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData)); + } + } + } + return schemeDatas == null ? null : new DrmInitData(schemeDatas); + } + + /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */ + private static boolean shouldParseLeafAtom(int atom) { + return atom == Atom.TYPE_hdlr || atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd + || atom == Atom.TYPE_sidx || atom == Atom.TYPE_stsd || atom == Atom.TYPE_tfdt + || atom == Atom.TYPE_tfhd || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_trex + || atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz + || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_uuid + || atom == Atom.TYPE_sbgp || atom == Atom.TYPE_sgpd || atom == Atom.TYPE_elst + || atom == Atom.TYPE_mehd || atom == Atom.TYPE_emsg; + } + + /** Returns whether the extractor should decode a container atom with type {@code atom}. */ + private static boolean shouldParseContainerAtom(int atom) { + return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia + || atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_moof + || atom == Atom.TYPE_traf || atom == Atom.TYPE_mvex || atom == Atom.TYPE_edts; + } + + /** + * Holds data corresponding to a metadata sample. + */ + private static final class MetadataSampleInfo { + + public final long presentationTimeDeltaUs; + public final int size; + + public MetadataSampleInfo(long presentationTimeDeltaUs, int size) { + this.presentationTimeDeltaUs = presentationTimeDeltaUs; + this.size = size; + } + + } + + /** + * Holds data corresponding to a single track. + */ + private static final class TrackBundle { + + public final TrackFragment fragment; + public final TrackOutput output; + + public Track track; + public DefaultSampleValues defaultSampleValues; + public int currentSampleIndex; + public int currentSampleInTrackRun; + public int currentTrackRunIndex; + + public TrackBundle(TrackOutput output) { + fragment = new TrackFragment(); + this.output = output; + } + + public void init(Track track, DefaultSampleValues defaultSampleValues) { + this.track = Assertions.checkNotNull(track); + this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues); + output.format(track.format); + reset(); + } + + public void reset() { + fragment.reset(); + currentSampleIndex = 0; + currentTrackRunIndex = 0; + currentSampleInTrackRun = 0; + } + + public void updateDrmInitData(DrmInitData drmInitData) { + output.format(track.format.copyWithDrmInitData(drmInitData)); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/MetadataUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/MetadataUtil.java new file mode 100644 index 0000000..c55d51a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/MetadataUtil.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.ApicFrame; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.CommentFrame; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.Id3Frame; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.TextInformationFrame; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Parses metadata items stored in ilst atoms. + */ +/* package */ final class MetadataUtil { + + // Codes that start with the copyright character (omitted) and have equivalent ID3 frames. + private static final int SHORT_TYPE_NAME_1 = Util.getIntegerCodeForString("nam"); + private static final int SHORT_TYPE_NAME_2 = Util.getIntegerCodeForString("trk"); + private static final int SHORT_TYPE_COMMENT = Util.getIntegerCodeForString("cmt"); + private static final int SHORT_TYPE_YEAR = Util.getIntegerCodeForString("day"); + private static final int SHORT_TYPE_ARTIST = Util.getIntegerCodeForString("ART"); + private static final int SHORT_TYPE_ENCODER = Util.getIntegerCodeForString("too"); + private static final int SHORT_TYPE_ALBUM = Util.getIntegerCodeForString("alb"); + private static final int SHORT_TYPE_COMPOSER_1 = Util.getIntegerCodeForString("com"); + private static final int SHORT_TYPE_COMPOSER_2 = Util.getIntegerCodeForString("wrt"); + private static final int SHORT_TYPE_LYRICS = Util.getIntegerCodeForString("lyr"); + private static final int SHORT_TYPE_GENRE = Util.getIntegerCodeForString("gen"); + + // Codes that have equivalent ID3 frames. + private static final int TYPE_COVER_ART = Util.getIntegerCodeForString("covr"); + private static final int TYPE_GENRE = Util.getIntegerCodeForString("gnre"); + private static final int TYPE_GROUPING = Util.getIntegerCodeForString("grp"); + private static final int TYPE_DISK_NUMBER = Util.getIntegerCodeForString("disk"); + private static final int TYPE_TRACK_NUMBER = Util.getIntegerCodeForString("trkn"); + private static final int TYPE_TEMPO = Util.getIntegerCodeForString("tmpo"); + private static final int TYPE_COMPILATION = Util.getIntegerCodeForString("cpil"); + private static final int TYPE_ALBUM_ARTIST = Util.getIntegerCodeForString("aART"); + private static final int TYPE_SORT_TRACK_NAME = Util.getIntegerCodeForString("sonm"); + private static final int TYPE_SORT_ALBUM = Util.getIntegerCodeForString("soal"); + private static final int TYPE_SORT_ARTIST = Util.getIntegerCodeForString("soar"); + private static final int TYPE_SORT_ALBUM_ARTIST = Util.getIntegerCodeForString("soaa"); + private static final int TYPE_SORT_COMPOSER = Util.getIntegerCodeForString("soco"); + + // Types that do not have equivalent ID3 frames. + private static final int TYPE_RATING = Util.getIntegerCodeForString("rtng"); + private static final int TYPE_GAPLESS_ALBUM = Util.getIntegerCodeForString("pgap"); + private static final int TYPE_TV_SORT_SHOW = Util.getIntegerCodeForString("sosn"); + private static final int TYPE_TV_SHOW = Util.getIntegerCodeForString("tvsh"); + + // Type for items that are intended for internal use by the player. + private static final int TYPE_INTERNAL = Util.getIntegerCodeForString("----"); + + // Standard genres. + private static final String[] STANDARD_GENRES = new String[] { + // These are the official ID3v1 genres. + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", + "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", + "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", + "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", + "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", "Soul", + "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", + "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", + "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", + "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", + "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", + "Hard Rock", + // These were made up by the authors of Winamp and later added to the ID3 spec. + "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", + "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", + "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", + "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", + "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", + "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", + "Euro-House", "Dance Hall", + // These were med up by the authors of Winamp but have not been added to the ID3 spec. + "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", + "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", + "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", + "Jpop", "Synthpop" + }; + + private static final String LANGUAGE_UNDEFINED = "und"; + + private MetadataUtil() {} + + /** + * Parses a single ilst element from a {@link ParsableByteArray}. The element is read starting + * from the current position of the {@link ParsableByteArray}, and the position is advanced by + * the size of the element. The position is advanced even if the element's type is unrecognized. + * + * @param ilst Holds the data to be parsed. + * @return The parsed element, or null if the element's type was not recognized. + */ + public static Metadata.Entry parseIlstElement(ParsableByteArray ilst) { + int position = ilst.getPosition(); + int endPosition = position + ilst.readInt(); + int type = ilst.readInt(); + int typeTopByte = (type >> 24) & 0xFF; + try { + if (typeTopByte == '\u00A9' /* Copyright char */ + || typeTopByte == '\uFFFD' /* Replacement char */) { + int shortType = type & 0x00FFFFFF; + if (shortType == SHORT_TYPE_COMMENT) { + return parseCommentAttribute(type, ilst); + } else if (shortType == SHORT_TYPE_NAME_1 || shortType == SHORT_TYPE_NAME_2) { + return parseTextAttribute(type, "TIT2", ilst); + } else if (shortType == SHORT_TYPE_COMPOSER_1 || shortType == SHORT_TYPE_COMPOSER_2) { + return parseTextAttribute(type, "TCOM", ilst); + } else if (shortType == SHORT_TYPE_YEAR) { + return parseTextAttribute(type, "TDRC", ilst); + } else if (shortType == SHORT_TYPE_ARTIST) { + return parseTextAttribute(type, "TPE1", ilst); + } else if (shortType == SHORT_TYPE_ENCODER) { + return parseTextAttribute(type, "TSSE", ilst); + } else if (shortType == SHORT_TYPE_ALBUM) { + return parseTextAttribute(type, "TALB", ilst); + } else if (shortType == SHORT_TYPE_LYRICS) { + return parseTextAttribute(type, "USLT", ilst); + } else if (shortType == SHORT_TYPE_GENRE) { + return parseTextAttribute(type, "TCON", ilst); + } else if (shortType == TYPE_GROUPING) { + return parseTextAttribute(type, "TIT1", ilst); + } + } else if (type == TYPE_GENRE) { + return parseStandardGenreAttribute(ilst); + } else if (type == TYPE_DISK_NUMBER) { + return parseIndexAndCountAttribute(type, "TPOS", ilst); + } else if (type == TYPE_TRACK_NUMBER) { + return parseIndexAndCountAttribute(type, "TRCK", ilst); + } else if (type == TYPE_TEMPO) { + return parseUint8Attribute(type, "TBPM", ilst, true, false); + } else if (type == TYPE_COMPILATION) { + return parseUint8Attribute(type, "TCMP", ilst, true, true); + } else if (type == TYPE_COVER_ART) { + return parseCoverArt(ilst); + } else if (type == TYPE_ALBUM_ARTIST) { + return parseTextAttribute(type, "TPE2", ilst); + } else if (type == TYPE_SORT_TRACK_NAME) { + return parseTextAttribute(type, "TSOT", ilst); + } else if (type == TYPE_SORT_ALBUM) { + return parseTextAttribute(type, "TSO2", ilst); + } else if (type == TYPE_SORT_ARTIST) { + return parseTextAttribute(type, "TSOA", ilst); + } else if (type == TYPE_SORT_ALBUM_ARTIST) { + return parseTextAttribute(type, "TSOP", ilst); + } else if (type == TYPE_SORT_COMPOSER) { + return parseTextAttribute(type, "TSOC", ilst); + } else if (type == TYPE_RATING) { + return parseUint8Attribute(type, "ITUNESADVISORY", ilst, false, false); + } else if (type == TYPE_GAPLESS_ALBUM) { + return parseUint8Attribute(type, "ITUNESGAPLESS", ilst, false, true); + } else if (type == TYPE_TV_SORT_SHOW) { + return parseTextAttribute(type, "TVSHOWSORT", ilst); + } else if (type == TYPE_TV_SHOW) { + return parseTextAttribute(type, "TVSHOW", ilst); + } else if (type == TYPE_INTERNAL) { + return parseInternalAttribute(ilst, endPosition); + } + return null; + } finally { + ilst.setPosition(endPosition); + } + } + + private static TextInformationFrame parseTextAttribute(int type, String id, + ParsableByteArray data) { + int atomSize = data.readInt(); + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data) { + data.skipBytes(8); // version (1), flags (3), empty (4) + String value = data.readNullTerminatedString(atomSize - 16); + return new TextInformationFrame(id, null, value); + } + return null; + } + + private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) { + int atomSize = data.readInt(); + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data) { + data.skipBytes(8); // version (1), flags (3), empty (4) + String value = data.readNullTerminatedString(atomSize - 16); + return new CommentFrame(LANGUAGE_UNDEFINED, value, value); + } + return null; + } + + private static Id3Frame parseUint8Attribute(int type, String id, ParsableByteArray data, + boolean isTextInformationFrame, boolean isBoolean) { + int value = parseUint8AttributeValue(data); + if (isBoolean) { + value = Math.min(1, value); + } + if (value >= 0) { + return isTextInformationFrame ? new TextInformationFrame(id, null, Integer.toString(value)) + : new CommentFrame(LANGUAGE_UNDEFINED, id, Integer.toString(value)); + } + return null; + } + + private static TextInformationFrame parseIndexAndCountAttribute(int type, String attributeName, + ParsableByteArray data) { + int atomSize = data.readInt(); + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data && atomSize >= 22) { + data.skipBytes(10); // version (1), flags (3), empty (4), empty (2) + int index = data.readUnsignedShort(); + if (index > 0) { + String value = "" + index; + int count = data.readUnsignedShort(); + if (count > 0) { + value += "/" + count; + } + return new TextInformationFrame(attributeName, null, value); + } + } + return null; + } + + private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) { + int genreCode = parseUint8AttributeValue(data); + String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length) + ? STANDARD_GENRES[genreCode - 1] : null; + if (genreString != null) { + return new TextInformationFrame("TCON", null, genreString); + } + return null; + } + + private static ApicFrame parseCoverArt(ParsableByteArray data) { + int atomSize = data.readInt(); + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data) { + int fullVersionInt = data.readInt(); + int flags = Atom.parseFullAtomFlags(fullVersionInt); + String mimeType = flags == 13 ? "image/jpeg" : flags == 14 ? "image/png" : null; + if (mimeType == null) { + return null; + } + data.skipBytes(4); // empty (4) + byte[] pictureData = new byte[atomSize - 16]; + data.readBytes(pictureData, 0, pictureData.length); + return new ApicFrame(mimeType, null, 3 /* Cover (front) */, pictureData); + } + return null; + } + + private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) { + String domain = null; + String name = null; + int dataAtomPosition = -1; + int dataAtomSize = -1; + while (data.getPosition() < endPosition) { + int atomPosition = data.getPosition(); + int atomSize = data.readInt(); + int atomType = data.readInt(); + data.skipBytes(4); // version (1), flags (3) + if (atomType == Atom.TYPE_mean) { + domain = data.readNullTerminatedString(atomSize - 12); + } else if (atomType == Atom.TYPE_name) { + name = data.readNullTerminatedString(atomSize - 12); + } else { + if (atomType == Atom.TYPE_data) { + dataAtomPosition = atomPosition; + dataAtomSize = atomSize; + } + data.skipBytes(atomSize - 12); + } + } + if (!"com.apple.iTunes".equals(domain) || !"iTunSMPB".equals(name) || dataAtomPosition == -1) { + // We're only interested in iTunSMPB. + return null; + } + data.setPosition(dataAtomPosition); + data.skipBytes(16); // size (4), type (4), version (1), flags (3), empty (4) + String value = data.readNullTerminatedString(dataAtomSize - 16); + return new CommentFrame(LANGUAGE_UNDEFINED, name, value); + } + + private static int parseUint8AttributeValue(ParsableByteArray data) { + data.skipBytes(4); // atomSize + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data) { + data.skipBytes(8); // version (1), flags (3), empty (4) + return data.readUnsignedByte(); + } + return -1; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Mp4Extractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Mp4Extractor.java new file mode 100644 index 0000000..c7131bf --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.GaplessInfoHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.Atom.ContainerAtom; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * Extracts data from an unfragmented MP4 file. + */ +public final class Mp4Extractor implements Extractor, SeekMap { + + /** + * Factory for {@link Mp4Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new Mp4Extractor()}; + } + + }; + + /** + * Parser states. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_READING_ATOM_HEADER, STATE_READING_ATOM_PAYLOAD, STATE_READING_SAMPLE}) + private @interface State {} + private static final int STATE_READING_ATOM_HEADER = 0; + private static final int STATE_READING_ATOM_PAYLOAD = 1; + private static final int STATE_READING_SAMPLE = 2; + + // Brand stored in the ftyp atom for QuickTime media. + private static final int BRAND_QUICKTIME = Util.getIntegerCodeForString("qt "); + + /** + * When seeking within the source, if the offset is greater than or equal to this value (or the + * offset is negative), the source will be reloaded. + */ + private static final long RELOAD_MINIMUM_SEEK_DISTANCE = 256 * 1024; + + // Temporary arrays. + private final ParsableByteArray nalStartCode; + private final ParsableByteArray nalLength; + + private final ParsableByteArray atomHeader; + private final Stack containerAtoms; + + @State private int parserState; + private int atomType; + private long atomSize; + private int atomHeaderBytesRead; + private ParsableByteArray atomData; + + private int sampleBytesWritten; + private int sampleCurrentNalBytesRemaining; + + // Extractor outputs. + private ExtractorOutput extractorOutput; + private Mp4Track[] tracks; + private long durationUs; + private boolean isQuickTime; + + public Mp4Extractor() { + atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); + containerAtoms = new Stack<>(); + nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); + nalLength = new ParsableByteArray(4); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return Sniffer.sniffUnfragmented(input); + } + + @Override + public void init(ExtractorOutput output) { + extractorOutput = output; + } + + @Override + public void seek(long position, long timeUs) { + containerAtoms.clear(); + atomHeaderBytesRead = 0; + sampleBytesWritten = 0; + sampleCurrentNalBytesRemaining = 0; + if (position == 0) { + enterReadingAtomHeaderState(); + } else if (tracks != null) { + updateSampleIndices(timeUs); + } + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + while (true) { + switch (parserState) { + case STATE_READING_ATOM_HEADER: + if (!readAtomHeader(input)) { + return RESULT_END_OF_INPUT; + } + break; + case STATE_READING_ATOM_PAYLOAD: + if (readAtomPayload(input, seekPosition)) { + return RESULT_SEEK; + } + break; + case STATE_READING_SAMPLE: + return readSample(input, seekPosition); + default: + throw new IllegalStateException(); + } + } + } + + // SeekMap implementation. + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getDurationUs() { + return durationUs; + } + + @Override + public long getPosition(long timeUs) { + long earliestSamplePosition = Long.MAX_VALUE; + for (Mp4Track track : tracks) { + TrackSampleTable sampleTable = track.sampleTable; + int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); + if (sampleIndex == C.INDEX_UNSET) { + // Handle the case where the requested time is before the first synchronization sample. + sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); + } + long offset = sampleTable.offsets[sampleIndex]; + if (offset < earliestSamplePosition) { + earliestSamplePosition = offset; + } + } + return earliestSamplePosition; + } + + // Private methods. + + private void enterReadingAtomHeaderState() { + parserState = STATE_READING_ATOM_HEADER; + atomHeaderBytesRead = 0; + } + + private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException { + if (atomHeaderBytesRead == 0) { + // Read the standard length atom header. + if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) { + return false; + } + atomHeaderBytesRead = Atom.HEADER_SIZE; + atomHeader.setPosition(0); + atomSize = atomHeader.readUnsignedInt(); + atomType = atomHeader.readInt(); + } + + if (atomSize == Atom.LONG_SIZE_PREFIX) { + // Read the extended atom size. + int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; + input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); + atomHeaderBytesRead += headerBytesRemaining; + atomSize = atomHeader.readUnsignedLongToLong(); + } + + if (shouldParseContainerAtom(atomType)) { + long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead; + containerAtoms.add(new ContainerAtom(atomType, endPosition)); + if (atomSize == atomHeaderBytesRead) { + processAtomEnded(endPosition); + } else { + // Start reading the first child atom. + enterReadingAtomHeaderState(); + } + } else if (shouldParseLeafAtom(atomType)) { + // We don't support parsing of leaf atoms that define extended atom sizes, or that have + // lengths greater than Integer.MAX_VALUE. + Assertions.checkState(atomHeaderBytesRead == Atom.HEADER_SIZE); + Assertions.checkState(atomSize <= Integer.MAX_VALUE); + atomData = new ParsableByteArray((int) atomSize); + System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE); + parserState = STATE_READING_ATOM_PAYLOAD; + } else { + atomData = null; + parserState = STATE_READING_ATOM_PAYLOAD; + } + + return true; + } + + /** + * Processes the atom payload. If {@link #atomData} is null and the size is at or above the + * threshold {@link #RELOAD_MINIMUM_SEEK_DISTANCE}, {@code true} is returned and the caller should + * restart loading at the position in {@code positionHolder}. Otherwise, the atom is read/skipped. + */ + private boolean readAtomPayload(ExtractorInput input, PositionHolder positionHolder) + throws IOException, InterruptedException { + long atomPayloadSize = atomSize - atomHeaderBytesRead; + long atomEndPosition = input.getPosition() + atomPayloadSize; + boolean seekRequired = false; + if (atomData != null) { + input.readFully(atomData.data, atomHeaderBytesRead, (int) atomPayloadSize); + if (atomType == Atom.TYPE_ftyp) { + isQuickTime = processFtypAtom(atomData); + } else if (!containerAtoms.isEmpty()) { + containerAtoms.peek().add(new Atom.LeafAtom(atomType, atomData)); + } + } else { + // We don't need the data. Skip or seek, depending on how large the atom is. + if (atomPayloadSize < RELOAD_MINIMUM_SEEK_DISTANCE) { + input.skipFully((int) atomPayloadSize); + } else { + positionHolder.position = input.getPosition() + atomPayloadSize; + seekRequired = true; + } + } + processAtomEnded(atomEndPosition); + return seekRequired && parserState != STATE_READING_SAMPLE; + } + + private void processAtomEnded(long atomEndPosition) throws ParserException { + while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) { + Atom.ContainerAtom containerAtom = containerAtoms.pop(); + if (containerAtom.type == Atom.TYPE_moov) { + // We've reached the end of the moov atom. Process it and prepare to read samples. + processMoovAtom(containerAtom); + containerAtoms.clear(); + parserState = STATE_READING_SAMPLE; + } else if (!containerAtoms.isEmpty()) { + containerAtoms.peek().add(containerAtom); + } + } + if (parserState != STATE_READING_SAMPLE) { + enterReadingAtomHeaderState(); + } + } + + /** + * Process an ftyp atom to determine whether the media is QuickTime. + * + * @param atomData The ftyp atom data. + * @return Whether the media is QuickTime. + */ + private static boolean processFtypAtom(ParsableByteArray atomData) { + atomData.setPosition(Atom.HEADER_SIZE); + int majorBrand = atomData.readInt(); + if (majorBrand == BRAND_QUICKTIME) { + return true; + } + atomData.skipBytes(4); // minor_version + while (atomData.bytesLeft() > 0) { + if (atomData.readInt() == BRAND_QUICKTIME) { + return true; + } + } + return false; + } + + /** + * Updates the stored track metadata to reflect the contents of the specified moov atom. + */ + private void processMoovAtom(ContainerAtom moov) throws ParserException { + long durationUs = C.TIME_UNSET; + List tracks = new ArrayList<>(); + long earliestSampleOffset = Long.MAX_VALUE; + + Metadata metadata = null; + GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); + Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); + if (udta != null) { + metadata = AtomParsers.parseUdta(udta, isQuickTime); + if (metadata != null) { + gaplessInfoHolder.setFromMetadata(metadata); + } + } + + for (int i = 0; i < moov.containerChildren.size(); i++) { + Atom.ContainerAtom atom = moov.containerChildren.get(i); + if (atom.type != Atom.TYPE_trak) { + continue; + } + + Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), + C.TIME_UNSET, null, isQuickTime); + if (track == null) { + continue; + } + + Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia) + .getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl); + TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder); + if (trackSampleTable.sampleCount == 0) { + continue; + } + + Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, + extractorOutput.track(i, track.type)); + // Each sample has up to three bytes of overhead for the start code that replaces its length. + // Allow ten source samples per output sample, like the platform extractor. + int maxInputSize = trackSampleTable.maximumSize + 3 * 10; + Format format = track.format.copyWithMaxInputSize(maxInputSize); + if (track.type == C.TRACK_TYPE_AUDIO) { + if (gaplessInfoHolder.hasGaplessInfo()) { + format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, + gaplessInfoHolder.encoderPadding); + } + if (metadata != null) { + format = format.copyWithMetadata(metadata); + } + } + mp4Track.trackOutput.format(format); + + durationUs = Math.max(durationUs, track.durationUs); + tracks.add(mp4Track); + + long firstSampleOffset = trackSampleTable.offsets[0]; + if (firstSampleOffset < earliestSampleOffset) { + earliestSampleOffset = firstSampleOffset; + } + } + this.durationUs = durationUs; + this.tracks = tracks.toArray(new Mp4Track[tracks.size()]); + extractorOutput.endTracks(); + extractorOutput.seekMap(this); + } + + /** + * Attempts to extract the next sample in the current mdat atom for the specified track. + *

    + * Returns {@link #RESULT_SEEK} if the source should be reloaded from the position in + * {@code positionHolder}. + *

    + * Returns {@link #RESULT_END_OF_INPUT} if no samples are left. Otherwise, returns + * {@link #RESULT_CONTINUE}. + * + * @param input The {@link ExtractorInput} from which to read data. + * @param positionHolder If {@link #RESULT_SEEK} is returned, this holder is updated to hold the + * position of the required data. + * @return One of the {@code RESULT_*} flags in {@link Extractor}. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + private int readSample(ExtractorInput input, PositionHolder positionHolder) + throws IOException, InterruptedException { + int trackIndex = getTrackIndexOfEarliestCurrentSample(); + if (trackIndex == C.INDEX_UNSET) { + return RESULT_END_OF_INPUT; + } + Mp4Track track = tracks[trackIndex]; + TrackOutput trackOutput = track.trackOutput; + int sampleIndex = track.sampleIndex; + long position = track.sampleTable.offsets[sampleIndex]; + int sampleSize = track.sampleTable.sizes[sampleIndex]; + if (track.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { + // The sample information is contained in a cdat atom. The header must be discarded for + // committing. + position += Atom.HEADER_SIZE; + sampleSize -= Atom.HEADER_SIZE; + } + long skipAmount = position - input.getPosition() + sampleBytesWritten; + if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) { + positionHolder.position = position; + return RESULT_SEEK; + } + input.skipFully((int) skipAmount); + if (track.track.nalUnitLengthFieldLength != 0) { + // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case + // they're only 1 or 2 bytes long. + byte[] nalLengthData = nalLength.data; + nalLengthData[0] = 0; + nalLengthData[1] = 0; + nalLengthData[2] = 0; + int nalUnitLengthFieldLength = track.track.nalUnitLengthFieldLength; + int nalUnitLengthFieldLengthDiff = 4 - track.track.nalUnitLengthFieldLength; + // NAL units are length delimited, but the decoder requires start code delimited units. + // Loop until we've written the sample to the track output, replacing length delimiters with + // start codes as we encounter them. + while (sampleBytesWritten < sampleSize) { + if (sampleCurrentNalBytesRemaining == 0) { + // Read the NAL length so that we know where we find the next one. + input.readFully(nalLength.data, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); + nalLength.setPosition(0); + sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt(); + // Write a start code for the current NAL unit. + nalStartCode.setPosition(0); + trackOutput.sampleData(nalStartCode, 4); + sampleBytesWritten += 4; + sampleSize += nalUnitLengthFieldLengthDiff; + } else { + // Write the payload of the NAL unit. + int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false); + sampleBytesWritten += writtenBytes; + sampleCurrentNalBytesRemaining -= writtenBytes; + } + } + } else { + while (sampleBytesWritten < sampleSize) { + int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); + sampleBytesWritten += writtenBytes; + sampleCurrentNalBytesRemaining -= writtenBytes; + } + } + trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex], + track.sampleTable.flags[sampleIndex], sampleSize, 0, null); + track.sampleIndex++; + sampleBytesWritten = 0; + sampleCurrentNalBytesRemaining = 0; + return RESULT_CONTINUE; + } + + /** + * Returns the index of the track that contains the earliest current sample, or + * {@link C#INDEX_UNSET} if no samples remain. + */ + private int getTrackIndexOfEarliestCurrentSample() { + int earliestSampleTrackIndex = C.INDEX_UNSET; + long earliestSampleOffset = Long.MAX_VALUE; + for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) { + Mp4Track track = tracks[trackIndex]; + int sampleIndex = track.sampleIndex; + if (sampleIndex == track.sampleTable.sampleCount) { + continue; + } + + long trackSampleOffset = track.sampleTable.offsets[sampleIndex]; + if (trackSampleOffset < earliestSampleOffset) { + earliestSampleOffset = trackSampleOffset; + earliestSampleTrackIndex = trackIndex; + } + } + + return earliestSampleTrackIndex; + } + + /** + * Updates every track's sample index to point its latest sync sample before/at {@code timeUs}. + */ + private void updateSampleIndices(long timeUs) { + for (Mp4Track track : tracks) { + TrackSampleTable sampleTable = track.sampleTable; + int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); + if (sampleIndex == C.INDEX_UNSET) { + // Handle the case where the requested time is before the first synchronization sample. + sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); + } + track.sampleIndex = sampleIndex; + } + } + + /** + * Returns whether the extractor should decode a leaf atom with type {@code atom}. + */ + private static boolean shouldParseLeafAtom(int atom) { + return atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd || atom == Atom.TYPE_hdlr + || atom == Atom.TYPE_stsd || atom == Atom.TYPE_stts || atom == Atom.TYPE_stss + || atom == Atom.TYPE_ctts || atom == Atom.TYPE_elst || atom == Atom.TYPE_stsc + || atom == Atom.TYPE_stsz || atom == Atom.TYPE_stz2 || atom == Atom.TYPE_stco + || atom == Atom.TYPE_co64 || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_ftyp + || atom == Atom.TYPE_udta; + } + + /** + * Returns whether the extractor should decode a container atom with type {@code atom}. + */ + private static boolean shouldParseContainerAtom(int atom) { + return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia + || atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_edts; + } + + private static final class Mp4Track { + + public final Track track; + public final TrackSampleTable sampleTable; + public final TrackOutput trackOutput; + + public int sampleIndex; + + public Mp4Track(Track track, TrackSampleTable sampleTable, TrackOutput trackOutput) { + this.track = track; + this.sampleTable = sampleTable; + this.trackOutput = trackOutput; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/PsshAtomUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/PsshAtomUtil.java new file mode 100644 index 0000000..572457e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/PsshAtomUtil.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import android.util.Log; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * Utility methods for handling PSSH atoms. + */ +public final class PsshAtomUtil { + + private static final String TAG = "PsshAtomUtil"; + + private PsshAtomUtil() {} + + /** + * Builds a PSSH atom for a given {@link UUID} containing the given scheme specific data. + * + * @param uuid The UUID of the scheme. + * @param data The scheme specific data. + * @return The PSSH atom. + */ + public static byte[] buildPsshAtom(UUID uuid, byte[] data) { + int psshBoxLength = Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */ + data.length; + ByteBuffer psshBox = ByteBuffer.allocate(psshBoxLength); + psshBox.putInt(psshBoxLength); + psshBox.putInt(Atom.TYPE_pssh); + psshBox.putInt(0 /* version=0, flags=0 */); + psshBox.putLong(uuid.getMostSignificantBits()); + psshBox.putLong(uuid.getLeastSignificantBits()); + psshBox.putInt(data.length); + psshBox.put(data); + return psshBox.array(); + } + + /** + * Parses the UUID from a PSSH atom. Version 0 and 1 PSSH atoms are supported. + *

    + * The UUID is only parsed if the data is a valid PSSH atom. + * + * @param atom The atom to parse. + * @return The parsed UUID. Null if the input is not a valid PSSH atom, or if the PSSH atom has + * an unsupported version. + */ + public static UUID parseUuid(byte[] atom) { + Pair parsedAtom = parsePsshAtom(atom); + if (parsedAtom == null) { + return null; + } + return parsedAtom.first; + } + + /** + * Parses the scheme specific data from a PSSH atom. Version 0 and 1 PSSH atoms are supported. + *

    + * The scheme specific data is only parsed if the data is a valid PSSH atom matching the given + * UUID, or if the data is a valid PSSH atom of any type in the case that the passed UUID is null. + * + * @param atom The atom to parse. + * @param uuid The required UUID of the PSSH atom, or null to accept any UUID. + * @return The parsed scheme specific data. Null if the input is not a valid PSSH atom, or if the + * PSSH atom has an unsupported version, or if the PSSH atom does not match the passed UUID. + */ + public static byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) { + Pair parsedAtom = parsePsshAtom(atom); + if (parsedAtom == null) { + return null; + } + if (uuid != null && !uuid.equals(parsedAtom.first)) { + Log.w(TAG, "UUID mismatch. Expected: " + uuid + ", got: " + parsedAtom.first + "."); + return null; + } + return parsedAtom.second; + } + + /** + * Parses the UUID and scheme specific data from a PSSH atom. Version 0 and 1 PSSH atoms are + * supported. + * + * @param atom The atom to parse. + * @return A pair consisting of the parsed UUID and scheme specific data. Null if the input is + * not a valid PSSH atom, or if the PSSH atom has an unsupported version. + */ + private static Pair parsePsshAtom(byte[] atom) { + ParsableByteArray atomData = new ParsableByteArray(atom); + if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) { + // Data too short. + return null; + } + atomData.setPosition(0); + int atomSize = atomData.readInt(); + if (atomSize != atomData.bytesLeft() + 4) { + // Not an atom, or incorrect atom size. + return null; + } + int atomType = atomData.readInt(); + if (atomType != Atom.TYPE_pssh) { + // Not an atom, or incorrect atom type. + return null; + } + int atomVersion = Atom.parseFullAtomVersion(atomData.readInt()); + if (atomVersion > 1) { + Log.w(TAG, "Unsupported pssh version: " + atomVersion); + return null; + } + UUID uuid = new UUID(atomData.readLong(), atomData.readLong()); + if (atomVersion == 1) { + int keyIdCount = atomData.readUnsignedIntToInt(); + atomData.skipBytes(16 * keyIdCount); + } + int dataSize = atomData.readUnsignedIntToInt(); + if (dataSize != atomData.bytesLeft()) { + // Incorrect dataSize. + return null; + } + byte[] data = new byte[dataSize]; + atomData.readBytes(data, 0, dataSize); + return Pair.create(uuid, data); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Sniffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Sniffer.java new file mode 100644 index 0000000..d555f9f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Sniffer.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Provides methods that peek data from an {@link ExtractorInput} and return whether the input + * appears to be in MP4 format. + */ +/* package */ final class Sniffer { + + /** + * The maximum number of bytes to peek when sniffing. + */ + private static final int SEARCH_LENGTH = 4 * 1024; + + private static final int[] COMPATIBLE_BRANDS = new int[] { + Util.getIntegerCodeForString("isom"), + Util.getIntegerCodeForString("iso2"), + Util.getIntegerCodeForString("iso3"), + Util.getIntegerCodeForString("iso4"), + Util.getIntegerCodeForString("iso5"), + Util.getIntegerCodeForString("iso6"), + Util.getIntegerCodeForString("avc1"), + Util.getIntegerCodeForString("hvc1"), + Util.getIntegerCodeForString("hev1"), + Util.getIntegerCodeForString("mp41"), + Util.getIntegerCodeForString("mp42"), + Util.getIntegerCodeForString("3g2a"), + Util.getIntegerCodeForString("3g2b"), + Util.getIntegerCodeForString("3gr6"), + Util.getIntegerCodeForString("3gs6"), + Util.getIntegerCodeForString("3ge6"), + Util.getIntegerCodeForString("3gg6"), + Util.getIntegerCodeForString("M4V "), + Util.getIntegerCodeForString("M4A "), + Util.getIntegerCodeForString("f4v "), + Util.getIntegerCodeForString("kddi"), + Util.getIntegerCodeForString("M4VP"), + Util.getIntegerCodeForString("qt "), // Apple QuickTime + Util.getIntegerCodeForString("MSNV"), // Sony PSP + }; + + /** + * Returns whether data peeked from the current position in {@code input} is consistent with the + * input being a fragmented MP4 file. + * + * @param input The extractor input from which to peek data. The peek position will be modified. + * @return Whether the input appears to be in the fragmented MP4 format. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + public static boolean sniffFragmented(ExtractorInput input) + throws IOException, InterruptedException { + return sniffInternal(input, true); + } + + /** + * Returns whether data peeked from the current position in {@code input} is consistent with the + * input being an unfragmented MP4 file. + * + * @param input The extractor input from which to peek data. The peek position will be modified. + * @return Whether the input appears to be in the unfragmented MP4 format. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + public static boolean sniffUnfragmented(ExtractorInput input) + throws IOException, InterruptedException { + return sniffInternal(input, false); + } + + private static boolean sniffInternal(ExtractorInput input, boolean fragmented) + throws IOException, InterruptedException { + long inputLength = input.getLength(); + int bytesToSearch = (int) (inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH + ? SEARCH_LENGTH : inputLength); + + ParsableByteArray buffer = new ParsableByteArray(64); + int bytesSearched = 0; + boolean foundGoodFileType = false; + boolean isFragmented = false; + while (bytesSearched < bytesToSearch) { + // Read an atom header. + int headerSize = Atom.HEADER_SIZE; + buffer.reset(headerSize); + input.peekFully(buffer.data, 0, headerSize); + long atomSize = buffer.readUnsignedInt(); + int atomType = buffer.readInt(); + if (atomSize == Atom.LONG_SIZE_PREFIX) { + headerSize = Atom.LONG_HEADER_SIZE; + input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); + buffer.setLimit(Atom.LONG_HEADER_SIZE); + atomSize = buffer.readUnsignedLongToLong(); + } + + if (atomSize < headerSize) { + // The file is invalid because the atom size is too small for its header. + return false; + } + bytesSearched += headerSize; + + if (atomType == Atom.TYPE_moov) { + // Check for an mvex atom inside the moov atom to identify whether the file is fragmented. + continue; + } + + if (atomType == Atom.TYPE_moof || atomType == Atom.TYPE_mvex) { + // The movie is fragmented. Stop searching as we must have read any ftyp atom already. + isFragmented = true; + break; + } + + if (bytesSearched + atomSize - headerSize >= bytesToSearch) { + // Stop searching as peeking this atom would exceed the search limit. + break; + } + + int atomDataSize = (int) (atomSize - headerSize); + bytesSearched += atomDataSize; + if (atomType == Atom.TYPE_ftyp) { + // Parse the atom and check the file type/brand is compatible with the extractors. + if (atomDataSize < 8) { + return false; + } + buffer.reset(atomDataSize); + input.peekFully(buffer.data, 0, atomDataSize); + int brandsCount = atomDataSize / 4; + for (int i = 0; i < brandsCount; i++) { + if (i == 1) { + // This index refers to the minorVersion, not a brand, so skip it. + buffer.skipBytes(4); + } else if (isCompatibleBrand(buffer.readInt())) { + foundGoodFileType = true; + break; + } + } + if (!foundGoodFileType) { + // The types were not compatible and there is only one ftyp atom, so reject the file. + return false; + } + } else if (atomDataSize != 0) { + // Skip the atom. + input.advancePeekPosition(atomDataSize); + } + } + return foundGoodFileType && fragmented == isFragmented; + } + + /** + * Returns whether {@code brand} is an ftyp atom brand that is compatible with the MP4 extractors. + */ + private static boolean isCompatibleBrand(int brand) { + // Accept all brands starting '3gp'. + if (brand >>> 8 == Util.getIntegerCodeForString("3gp")) { + return true; + } + for (int compatibleBrand : COMPATIBLE_BRANDS) { + if (compatibleBrand == brand) { + return true; + } + } + return false; + } + + private Sniffer() { + // Prevent instantiation. + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Track.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Track.java new file mode 100644 index 0000000..831bce2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/Track.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Encapsulates information describing an MP4 track. + */ +public final class Track { + + /** + * The transformation to apply to samples in the track, if any. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT}) + public @interface Transformation {} + /** + * A no-op sample transformation. + */ + public static final int TRANSFORMATION_NONE = 0; + /** + * A transformation for caption samples in cdat atoms. + */ + public static final int TRANSFORMATION_CEA608_CDAT = 1; + + /** + * The track identifier. + */ + public final int id; + + /** + * One of {@link C#TRACK_TYPE_AUDIO}, {@link C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_TEXT}. + */ + public final int type; + + /** + * The track timescale, defined as the number of time units that pass in one second. + */ + public final long timescale; + + /** + * The movie timescale. + */ + public final long movieTimescale; + + /** + * The duration of the track in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public final long durationUs; + + /** + * The format. + */ + public final Format format; + + /** + * One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each + * sample. + */ + @Transformation public final int sampleTransformation; + + /** + * Track encryption boxes for the different track sample descriptions. Entries may be null. + */ + public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes; + + /** + * Durations of edit list segments in the movie timescale. Null if there is no edit list. + */ + public final long[] editListDurations; + + /** + * Media times for edit list segments in the track timescale. Null if there is no edit list. + */ + public final long[] editListMediaTimes; + + /** + * For H264 video tracks, the length in bytes of the NALUnitLength field in each sample. 0 for + * other track types. + */ + public final int nalUnitLengthFieldLength; + + public Track(int id, int type, long timescale, long movieTimescale, long durationUs, + Format format, @Transformation int sampleTransformation, + TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, + long[] editListDurations, long[] editListMediaTimes) { + this.id = id; + this.type = type; + this.timescale = timescale; + this.movieTimescale = movieTimescale; + this.durationUs = durationUs; + this.format = format; + this.sampleTransformation = sampleTransformation; + this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes; + this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + this.editListDurations = editListDurations; + this.editListMediaTimes = editListMediaTimes; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackEncryptionBox.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackEncryptionBox.java new file mode 100644 index 0000000..4933151 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackEncryptionBox.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +/** + * Encapsulates information parsed from a track encryption (tenc) box or sample group description + * (sgpd) box in an MP4 stream. + */ +public final class TrackEncryptionBox { + + /** + * Indicates the encryption state of the samples in the sample group. + */ + public final boolean isEncrypted; + + /** + * The initialization vector size in bytes for the samples in the corresponding sample group. + */ + public final int initializationVectorSize; + + /** + * The key identifier for the samples in the corresponding sample group. + */ + public final byte[] keyId; + + /** + * @param isEncrypted Indicates the encryption state of the samples in the sample group. + * @param initializationVectorSize The initialization vector size in bytes for the samples in the + * corresponding sample group. + * @param keyId The key identifier for the samples in the corresponding sample group. + */ + public TrackEncryptionBox(boolean isEncrypted, int initializationVectorSize, byte[] keyId) { + this.isEncrypted = isEncrypted; + this.initializationVectorSize = initializationVectorSize; + this.keyId = keyId; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackFragment.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackFragment.java new file mode 100644 index 0000000..2497e95 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackFragment.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * A holder for information corresponding to a single fragment of an mp4 file. + */ +/* package */ final class TrackFragment { + + /** + * The default values for samples from the track fragment header. + */ + public DefaultSampleValues header; + /** + * The position (byte offset) of the start of fragment. + */ + public long atomPosition; + /** + * The position (byte offset) of the start of data contained in the fragment. + */ + public long dataPosition; + /** + * The position (byte offset) of the start of auxiliary data. + */ + public long auxiliaryDataPosition; + /** + * The number of track runs of the fragment. + */ + public int trunCount; + /** + * The total number of samples in the fragment. + */ + public int sampleCount; + /** + * The position (byte offset) of the start of sample data of each track run in the fragment. + */ + public long[] trunDataPosition; + /** + * The number of samples contained by each track run in the fragment. + */ + public int[] trunLength; + /** + * The size of each sample in the fragment. + */ + public int[] sampleSizeTable; + /** + * The composition time offset of each sample in the fragment. + */ + public int[] sampleCompositionTimeOffsetTable; + /** + * The decoding time of each sample in the fragment. + */ + public long[] sampleDecodingTimeTable; + /** + * Indicates which samples are sync frames. + */ + public boolean[] sampleIsSyncFrameTable; + /** + * Whether the fragment defines encryption data. + */ + public boolean definesEncryptionData; + /** + * If {@link #definesEncryptionData} is true, indicates which samples use sub-sample encryption. + * Undefined otherwise. + */ + public boolean[] sampleHasSubsampleEncryptionTable; + /** + * Fragment specific track encryption. May be null. + */ + public TrackEncryptionBox trackEncryptionBox; + /** + * If {@link #definesEncryptionData} is true, indicates the length of the sample encryption data. + * Undefined otherwise. + */ + public int sampleEncryptionDataLength; + /** + * If {@link #definesEncryptionData} is true, contains binary sample encryption data. Undefined + * otherwise. + */ + public ParsableByteArray sampleEncryptionData; + /** + * Whether {@link #sampleEncryptionData} needs populating with the actual encryption data. + */ + public boolean sampleEncryptionDataNeedsFill; + /** + * The absolute decode time of the start of the next fragment. + */ + public long nextFragmentDecodeTime; + + /** + * Resets the fragment. + *

    + * {@link #sampleCount} and {@link #nextFragmentDecodeTime} are set to 0, and both + * {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false, + * and {@link #trackEncryptionBox} is set to null. + */ + public void reset() { + trunCount = 0; + nextFragmentDecodeTime = 0; + definesEncryptionData = false; + sampleEncryptionDataNeedsFill = false; + trackEncryptionBox = null; + } + + /** + * Configures the fragment for the specified number of samples. + *

    + * The {@link #sampleCount} of the fragment is set to the specified sample count, and the + * contained tables are resized if necessary such that they are at least this length. + * + * @param sampleCount The number of samples in the new run. + */ + public void initTables(int trunCount, int sampleCount) { + this.trunCount = trunCount; + this.sampleCount = sampleCount; + if (trunLength == null || trunLength.length < trunCount) { + trunDataPosition = new long[trunCount]; + trunLength = new int[trunCount]; + } + if (sampleSizeTable == null || sampleSizeTable.length < sampleCount) { + // Size the tables 25% larger than needed, so as to make future resize operations less + // likely. The choice of 25% is relatively arbitrary. + int tableSize = (sampleCount * 125) / 100; + sampleSizeTable = new int[tableSize]; + sampleCompositionTimeOffsetTable = new int[tableSize]; + sampleDecodingTimeTable = new long[tableSize]; + sampleIsSyncFrameTable = new boolean[tableSize]; + sampleHasSubsampleEncryptionTable = new boolean[tableSize]; + } + } + + /** + * Configures the fragment to be one that defines encryption data of the specified length. + *

    + * {@link #definesEncryptionData} is set to true, {@link #sampleEncryptionDataLength} is set to + * the specified length, and {@link #sampleEncryptionData} is resized if necessary such that it + * is at least this length. + * + * @param length The length in bytes of the encryption data. + */ + public void initEncryptionData(int length) { + if (sampleEncryptionData == null || sampleEncryptionData.limit() < length) { + sampleEncryptionData = new ParsableByteArray(length); + } + sampleEncryptionDataLength = length; + definesEncryptionData = true; + sampleEncryptionDataNeedsFill = true; + } + + /** + * Fills {@link #sampleEncryptionData} from the provided input. + * + * @param input An {@link ExtractorInput} from which to read the encryption data. + */ + public void fillEncryptionData(ExtractorInput input) throws IOException, InterruptedException { + input.readFully(sampleEncryptionData.data, 0, sampleEncryptionDataLength); + sampleEncryptionData.setPosition(0); + sampleEncryptionDataNeedsFill = false; + } + + /** + * Fills {@link #sampleEncryptionData} from the provided source. + * + * @param source A source from which to read the encryption data. + */ + public void fillEncryptionData(ParsableByteArray source) { + source.readBytes(sampleEncryptionData.data, 0, sampleEncryptionDataLength); + sampleEncryptionData.setPosition(0); + sampleEncryptionDataNeedsFill = false; + } + + public long getSamplePresentationTime(int index) { + return sampleDecodingTimeTable[index] + sampleCompositionTimeOffsetTable[index]; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackSampleTable.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackSampleTable.java new file mode 100644 index 0000000..e73e014 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/mp4/TrackSampleTable.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Sample table for a track in an MP4 file. + */ +/* package */ final class TrackSampleTable { + + /** + * Number of samples. + */ + public final int sampleCount; + /** + * Sample offsets in bytes. + */ + public final long[] offsets; + /** + * Sample sizes in bytes. + */ + public final int[] sizes; + /** + * Maximum sample size in {@link #sizes}. + */ + public final int maximumSize; + /** + * Sample timestamps in microseconds. + */ + public final long[] timestampsUs; + /** + * Sample flags. + */ + public final int[] flags; + + public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, + int[] flags) { + Assertions.checkArgument(sizes.length == timestampsUs.length); + Assertions.checkArgument(offsets.length == timestampsUs.length); + Assertions.checkArgument(flags.length == timestampsUs.length); + + this.offsets = offsets; + this.sizes = sizes; + this.maximumSize = maximumSize; + this.timestampsUs = timestampsUs; + this.flags = flags; + sampleCount = offsets.length; + } + + /** + * Returns the sample index of the closest synchronization sample at or before the given + * timestamp, if one is available. + * + * @param timeUs Timestamp adjacent to which to find a synchronization sample. + * @return Index of the synchronization sample, or {@link C#INDEX_UNSET} if none. + */ + public int getIndexOfEarlierOrEqualSynchronizationSample(long timeUs) { + // Video frame timestamps may not be sorted, so the behavior of this call can be undefined. + // Frames are not reordered past synchronization samples so this works in practice. + int startIndex = Util.binarySearchFloor(timestampsUs, timeUs, true, false); + for (int i = startIndex; i >= 0; i--) { + if ((flags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + return i; + } + } + return C.INDEX_UNSET; + } + + /** + * Returns the sample index of the closest synchronization sample at or after the given timestamp, + * if one is available. + * + * @param timeUs Timestamp adjacent to which to find a synchronization sample. + * @return index Index of the synchronization sample, or {@link C#INDEX_UNSET} if none. + */ + public int getIndexOfLaterOrEqualSynchronizationSample(long timeUs) { + int startIndex = Util.binarySearchCeil(timestampsUs, timeUs, true, false); + for (int i = startIndex; i < timestampsUs.length; i++) { + if ((flags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + return i; + } + } + return C.INDEX_UNSET; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/DefaultOggSeeker.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/DefaultOggSeeker.java new file mode 100644 index 0000000..6661290 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/DefaultOggSeeker.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.EOFException; +import java.io.IOException; + +/** + * Used to seek in an Ogg stream. + */ +/* package */ final class DefaultOggSeeker implements OggSeeker { + + //@VisibleForTesting + public static final int MATCH_RANGE = 72000; + //@VisibleForTesting + public static final int MATCH_BYTE_RANGE = 100000; + private static final int DEFAULT_OFFSET = 30000; + + private static final int STATE_SEEK_TO_END = 0; + private static final int STATE_READ_LAST_PAGE = 1; + private static final int STATE_SEEK = 2; + private static final int STATE_IDLE = 3; + + private final OggPageHeader pageHeader = new OggPageHeader(); + private final long startPosition; + private final long endPosition; + private final StreamReader streamReader; + + private int state; + private long totalGranules; + private long positionBeforeSeekToEnd; + private long targetGranule; + + private long start; + private long end; + private long startGranule; + private long endGranule; + + /** + * Constructs an OggSeeker. + * @param startPosition Start position of the payload (inclusive). + * @param endPosition End position of the payload (exclusive). + * @param streamReader StreamReader instance which owns this OggSeeker + * @param firstPayloadPageSize The total size of the first payload page, in bytes. + * @param firstPayloadPageGranulePosition The granule position of the first payload page. + */ + public DefaultOggSeeker(long startPosition, long endPosition, StreamReader streamReader, + int firstPayloadPageSize, long firstPayloadPageGranulePosition) { + Assertions.checkArgument(startPosition >= 0 && endPosition > startPosition); + this.streamReader = streamReader; + this.startPosition = startPosition; + this.endPosition = endPosition; + if (firstPayloadPageSize == endPosition - startPosition) { + totalGranules = firstPayloadPageGranulePosition; + state = STATE_IDLE; + } else { + state = STATE_SEEK_TO_END; + } + } + + @Override + public long read(ExtractorInput input) throws IOException, InterruptedException { + switch (state) { + case STATE_IDLE: + return -1; + case STATE_SEEK_TO_END: + positionBeforeSeekToEnd = input.getPosition(); + state = STATE_READ_LAST_PAGE; + // Seek to the end just before the last page of stream to get the duration. + long lastPageSearchPosition = endPosition - OggPageHeader.MAX_PAGE_SIZE; + if (lastPageSearchPosition > positionBeforeSeekToEnd) { + return lastPageSearchPosition; + } + // Fall through. + case STATE_READ_LAST_PAGE: + totalGranules = readGranuleOfLastPage(input); + state = STATE_IDLE; + return positionBeforeSeekToEnd; + case STATE_SEEK: + long currentGranule; + if (targetGranule == 0) { + currentGranule = 0; + } else { + long position = getNextSeekPosition(targetGranule, input); + if (position >= 0) { + return position; + } + currentGranule = skipToPageOfGranule(input, targetGranule, -(position + 2)); + } + state = STATE_IDLE; + return -(currentGranule + 2); + default: + // Never happens. + throw new IllegalStateException(); + } + } + + @Override + public long startSeek(long timeUs) { + Assertions.checkArgument(state == STATE_IDLE || state == STATE_SEEK); + targetGranule = timeUs == 0 ? 0 : streamReader.convertTimeToGranule(timeUs); + state = STATE_SEEK; + resetSeeking(); + return targetGranule; + } + + @Override + public OggSeekMap createSeekMap() { + return totalGranules != 0 ? new OggSeekMap() : null; + } + + //@VisibleForTesting + public void resetSeeking() { + start = startPosition; + end = endPosition; + startGranule = 0; + endGranule = totalGranules; + } + + /** + * Returns a position converging to the {@code targetGranule} to which the {@link ExtractorInput} + * has to seek and then be passed for another call until a negative number is returned. If a + * negative number is returned the input is at a position which is before the target page and at + * which it is sensible to just skip pages to the target granule and pre-roll instead of doing + * another seek request. + * + * @param targetGranule the target granule position to seek to. + * @param input the {@link ExtractorInput} to read from. + * @return the position to seek the {@link ExtractorInput} to for a next call or + * -(currentGranule + 2) if it's close enough to skip to the target page. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from the input. + */ + //@VisibleForTesting + public long getNextSeekPosition(long targetGranule, ExtractorInput input) + throws IOException, InterruptedException { + if (start == end) { + return -(startGranule + 2); + } + + long initialPosition = input.getPosition(); + if (!skipToNextPage(input, end)) { + if (start == initialPosition) { + throw new IOException("No ogg page can be found."); + } + return start; + } + + pageHeader.populate(input, false); + input.resetPeekPosition(); + + long granuleDistance = targetGranule - pageHeader.granulePosition; + int pageSize = pageHeader.headerSize + pageHeader.bodySize; + if (granuleDistance < 0 || granuleDistance > MATCH_RANGE) { + if (granuleDistance < 0) { + end = initialPosition; + endGranule = pageHeader.granulePosition; + } else { + start = input.getPosition() + pageSize; + startGranule = pageHeader.granulePosition; + if (end - start + pageSize < MATCH_BYTE_RANGE) { + input.skipFully(pageSize); + return -(startGranule + 2); + } + } + + if (end - start < MATCH_BYTE_RANGE) { + end = start; + return start; + } + + long offset = pageSize * (granuleDistance <= 0 ? 2 : 1); + long nextPosition = input.getPosition() - offset + + (granuleDistance * (end - start) / (endGranule - startGranule)); + + nextPosition = Math.max(nextPosition, start); + nextPosition = Math.min(nextPosition, end - 1); + return nextPosition; + } + + // position accepted (before target granule and within MATCH_RANGE) + input.skipFully(pageSize); + return -(pageHeader.granulePosition + 2); + } + + private long getEstimatedPosition(long position, long granuleDistance, long offset) { + position += (granuleDistance * (endPosition - startPosition) / totalGranules) - offset; + if (position < startPosition) { + position = startPosition; + } + if (position >= endPosition) { + position = endPosition - 1; + } + return position; + } + + private class OggSeekMap implements SeekMap { + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getPosition(long timeUs) { + if (timeUs == 0) { + return startPosition; + } + long granule = streamReader.convertTimeToGranule(timeUs); + return getEstimatedPosition(startPosition, granule, DEFAULT_OFFSET); + } + + @Override + public long getDurationUs() { + return streamReader.convertGranuleToTime(totalGranules); + } + + } + + /** + * Skips to the next page. + * + * @param input The {@code ExtractorInput} to skip to the next page. + * @throws IOException thrown if peeking/reading from the input fails. + * @throws InterruptedException thrown if interrupted while peeking/reading from the input. + * @throws EOFException if the next page can't be found before the end of the input. + */ + //@VisibleForTesting + void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException { + if (!skipToNextPage(input, endPosition)) { + // Not found until eof. + throw new EOFException(); + } + } + + /** + * Skips to the next page. Searches for the next page header. + * + * @param input The {@code ExtractorInput} to skip to the next page. + * @param until Searches until this position. + * @return true if the next page is found. + * @throws IOException thrown if peeking/reading from the input fails. + * @throws InterruptedException thrown if interrupted while peeking/reading from the input. + */ + //@VisibleForTesting + boolean skipToNextPage(ExtractorInput input, long until) + throws IOException, InterruptedException { + until = Math.min(until + 3, endPosition); + byte[] buffer = new byte[2048]; + int peekLength = buffer.length; + while (true) { + if (input.getPosition() + peekLength > until) { + // Make sure to not peek beyond the end of the input. + peekLength = (int) (until - input.getPosition()); + if (peekLength < 4) { + // Not found until end. + return false; + } + } + input.peekFully(buffer, 0, peekLength, false); + for (int i = 0; i < peekLength - 3; i++) { + if (buffer[i] == 'O' && buffer[i + 1] == 'g' && buffer[i + 2] == 'g' + && buffer[i + 3] == 'S') { + // Match! Skip to the start of the pattern. + input.skipFully(i); + return true; + } + } + // Overlap by not skipping the entire peekLength. + input.skipFully(peekLength - 3); + } + } + + /** + * Skips to the last Ogg page in the stream and reads the header's granule field which is the + * total number of samples per channel. + * + * @param input The {@link ExtractorInput} to read from. + * @return the total number of samples of this input. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from the input. + */ + //@VisibleForTesting + long readGranuleOfLastPage(ExtractorInput input) + throws IOException, InterruptedException { + skipToNextPage(input); + pageHeader.reset(); + while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < endPosition) { + pageHeader.populate(input, false); + input.skipFully(pageHeader.headerSize + pageHeader.bodySize); + } + return pageHeader.granulePosition; + } + + /** + * Skips to the position of the start of the page containing the {@code targetGranule} and + * returns the granule of the page previous to the target page. + * + * @param input the {@link ExtractorInput} to read from. + * @param targetGranule the target granule. + * @param currentGranule the current granule or -1 if it's unknown. + * @return the granule of the prior page or the {@code currentGranule} if there isn't a prior + * page. + * @throws ParserException thrown if populating the page header fails. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from the input. + */ + //@VisibleForTesting + long skipToPageOfGranule(ExtractorInput input, long targetGranule, long currentGranule) + throws IOException, InterruptedException { + pageHeader.populate(input, false); + while (pageHeader.granulePosition < targetGranule) { + input.skipFully(pageHeader.headerSize + pageHeader.bodySize); + // Store in a member field to be able to resume after IOExceptions. + currentGranule = pageHeader.granulePosition; + // Peek next header. + pageHeader.populate(input, false); + } + input.resetPeekPosition(); + return currentGranule; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/FlacReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/FlacReader.java new file mode 100644 index 0000000..5ed8a89 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/FlacReader.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.util.FlacStreamInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * {@link StreamReader} to extract Flac data out of Ogg byte stream. + */ +/* package */ final class FlacReader extends StreamReader { + + private static final byte AUDIO_PACKET_TYPE = (byte) 0xFF; + private static final byte SEEKTABLE_PACKET_TYPE = 0x03; + + private static final int FRAME_HEADER_SAMPLE_NUMBER_OFFSET = 4; + + private FlacStreamInfo streamInfo; + private FlacOggSeeker flacOggSeeker; + + public static boolean verifyBitstreamType(ParsableByteArray data) { + return data.bytesLeft() >= 5 && data.readUnsignedByte() == 0x7F && // packet type + data.readUnsignedInt() == 0x464C4143; // ASCII signature "FLAC" + } + + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + streamInfo = null; + flacOggSeeker = null; + } + } + + private static boolean isAudioPacket(byte[] data) { + return data[0] == AUDIO_PACKET_TYPE; + } + + @Override + protected long preparePayload(ParsableByteArray packet) { + if (!isAudioPacket(packet.data)) { + return -1; + } + return getFlacFrameBlockSize(packet); + } + + @Override + protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) + throws IOException, InterruptedException { + byte[] data = packet.data; + if (streamInfo == null) { + streamInfo = new FlacStreamInfo(data, 17); + byte[] metadata = Arrays.copyOfRange(data, 9, packet.limit()); + metadata[4] = (byte) 0x80; // Set the last metadata block flag, ignore the other blocks + List initializationData = Collections.singletonList(metadata); + setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_FLAC, null, + Format.NO_VALUE, streamInfo.bitRate(), streamInfo.channels, streamInfo.sampleRate, + initializationData, null, 0, null); + } else if ((data[0] & 0x7F) == SEEKTABLE_PACKET_TYPE) { + flacOggSeeker = new FlacOggSeeker(); + flacOggSeeker.parseSeekTable(packet); + } else if (isAudioPacket(data)) { + if (flacOggSeeker != null) { + flacOggSeeker.setFirstFrameOffset(position); + setupData.oggSeeker = flacOggSeeker; + } + return false; + } + return true; + } + + private int getFlacFrameBlockSize(ParsableByteArray packet) { + int blockSizeCode = (packet.data[2] & 0xFF) >> 4; + switch (blockSizeCode) { + case 1: + return 192; + case 2: + case 3: + case 4: + case 5: + return 576 << (blockSizeCode - 2); + case 6: + case 7: + // skip the sample number + packet.skipBytes(FRAME_HEADER_SAMPLE_NUMBER_OFFSET); + packet.readUtf8EncodedLong(); + int value = blockSizeCode == 6 ? packet.readUnsignedByte() : packet.readUnsignedShort(); + packet.setPosition(0); + return value + 1; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + return 256 << (blockSizeCode - 8); + } + return -1; + } + + private class FlacOggSeeker implements OggSeeker, SeekMap { + + private static final int METADATA_LENGTH_OFFSET = 1; + private static final int SEEK_POINT_SIZE = 18; + + private long[] seekPointGranules; + private long[] seekPointOffsets; + private long firstFrameOffset; + private long pendingSeekGranule; + + public FlacOggSeeker() { + firstFrameOffset = -1; + pendingSeekGranule = -1; + } + + public void setFirstFrameOffset(long firstFrameOffset) { + this.firstFrameOffset = firstFrameOffset; + } + + /** + * Parses a FLAC file seek table metadata structure and initializes internal fields. + * + * @param data A {@link ParsableByteArray} including whole seek table metadata block. Its + * position should be set to the beginning of the block. + * @see FLAC format + * METADATA_BLOCK_SEEKTABLE + */ + public void parseSeekTable(ParsableByteArray data) { + data.skipBytes(METADATA_LENGTH_OFFSET); + int length = data.readUnsignedInt24(); + int numberOfSeekPoints = length / SEEK_POINT_SIZE; + seekPointGranules = new long[numberOfSeekPoints]; + seekPointOffsets = new long[numberOfSeekPoints]; + for (int i = 0; i < numberOfSeekPoints; i++) { + seekPointGranules[i] = data.readLong(); + seekPointOffsets[i] = data.readLong(); + data.skipBytes(2); // Skip "Number of samples in the target frame." + } + } + + @Override + public long read(ExtractorInput input) throws IOException, InterruptedException { + if (pendingSeekGranule >= 0) { + long result = -(pendingSeekGranule + 2); + pendingSeekGranule = -1; + return result; + } + return -1; + } + + @Override + public long startSeek(long timeUs) { + long granule = convertTimeToGranule(timeUs); + int index = Util.binarySearchFloor(seekPointGranules, granule, true, true); + pendingSeekGranule = seekPointGranules[index]; + return granule; + } + + @Override + public SeekMap createSeekMap() { + return this; + } + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getPosition(long timeUs) { + long granule = convertTimeToGranule(timeUs); + int index = Util.binarySearchFloor(seekPointGranules, granule, true, true); + return firstFrameOffset + seekPointOffsets[index]; + } + + @Override + public long getDurationUs() { + return streamInfo.durationUs(); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggExtractor.java new file mode 100644 index 0000000..1d0faac --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggExtractor.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * Ogg {@link Extractor}. + */ +public class OggExtractor implements Extractor { + + /** + * Factory for {@link OggExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new OggExtractor()}; + } + + }; + + private static final int MAX_VERIFICATION_BYTES = 8; + + private StreamReader streamReader; + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + try { + OggPageHeader header = new OggPageHeader(); + if (!header.populate(input, true) || (header.type & 0x02) != 0x02) { + return false; + } + + int length = Math.min(header.bodySize, MAX_VERIFICATION_BYTES); + ParsableByteArray scratch = new ParsableByteArray(length); + input.peekFully(scratch.data, 0, length); + + if (FlacReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new FlacReader(); + } else if (VorbisReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new VorbisReader(); + } else if (OpusReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new OpusReader(); + } else { + return false; + } + return true; + } catch (ParserException e) { + return false; + } + } + + @Override + public void init(ExtractorOutput output) { + TrackOutput trackOutput = output.track(0, C.TRACK_TYPE_AUDIO); + output.endTracks(); + // TODO: fix the case if sniff() isn't called + streamReader.init(output, trackOutput); + } + + @Override + public void seek(long position, long timeUs) { + streamReader.seek(position, timeUs); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + return streamReader.read(input, seekPosition); + } + + //@VisibleForTesting + /* package */ StreamReader getStreamReader() { + return streamReader; + } + + private static ParsableByteArray resetPosition(ParsableByteArray scratch) { + scratch.setPosition(0); + return scratch; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggPacket.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggPacket.java new file mode 100644 index 0000000..a4f7e5c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggPacket.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * OGG packet class. + */ +/* package */ final class OggPacket { + + private final OggPageHeader pageHeader = new OggPageHeader(); + private final ParsableByteArray packetArray = + new ParsableByteArray(new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0); + + private int currentSegmentIndex = C.INDEX_UNSET; + private int segmentCount; + private boolean populated; + + /** + * Resets this reader. + */ + public void reset() { + pageHeader.reset(); + packetArray.reset(); + currentSegmentIndex = C.INDEX_UNSET; + populated = false; + } + + /** + * Reads the next packet of the ogg stream. In case of an {@code IOException} the caller must make + * sure to pass the same instance of {@code ParsableByteArray} to this method again so this reader + * can resume properly from an error while reading a continued packet spanned across multiple + * pages. + * + * @param input the {@link ExtractorInput} to read data from. + * @return {@code true} if the read was successful. {@code false} if the end of the input was + * encountered having read no data. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from input. + */ + public boolean populate(ExtractorInput input) throws IOException, InterruptedException { + Assertions.checkState(input != null); + + if (populated) { + populated = false; + packetArray.reset(); + } + + while (!populated) { + if (currentSegmentIndex < 0) { + // We're at the start of a page. + if (!pageHeader.populate(input, true)) { + return false; + } + int segmentIndex = 0; + int bytesToSkip = pageHeader.headerSize; + if ((pageHeader.type & 0x01) == 0x01 && packetArray.limit() == 0) { + // After seeking, the first packet may be the remainder + // part of a continued packet which has to be discarded. + bytesToSkip += calculatePacketSize(segmentIndex); + segmentIndex += segmentCount; + } + input.skipFully(bytesToSkip); + currentSegmentIndex = segmentIndex; + } + + int size = calculatePacketSize(currentSegmentIndex); + int segmentIndex = currentSegmentIndex + segmentCount; + if (size > 0) { + input.readFully(packetArray.data, packetArray.limit(), size); + packetArray.setLimit(packetArray.limit() + size); + populated = pageHeader.laces[segmentIndex - 1] != 255; + } + // Advance now since we are sure reading didn't throw an exception. + currentSegmentIndex = segmentIndex == pageHeader.pageSegmentCount ? C.INDEX_UNSET + : segmentIndex; + } + return true; + } + + /** + * An OGG Packet may span multiple pages. Returns the {@link OggPageHeader} of the last page read, + * or an empty header if the packet has yet to be populated. + *

    + * Note that the returned {@link OggPageHeader} is mutable and may be updated during subsequent + * calls to {@link #populate(ExtractorInput)}. + * + * @return the {@code PageHeader} of the last page read or an empty header if the packet has yet + * to be populated. + */ + //@VisibleForTesting + public OggPageHeader getPageHeader() { + return pageHeader; + } + + /** + * Returns a {@link ParsableByteArray} containing the packet's payload. + */ + public ParsableByteArray getPayload() { + return packetArray; + } + + /** + * Calculates the size of the packet starting from {@code startSegmentIndex}. + * + * @param startSegmentIndex the index of the first segment of the packet. + * @return Size of the packet. + */ + private int calculatePacketSize(int startSegmentIndex) { + segmentCount = 0; + int size = 0; + while (startSegmentIndex + segmentCount < pageHeader.pageSegmentCount) { + int segmentLength = pageHeader.laces[startSegmentIndex + segmentCount++]; + size += segmentLength; + if (segmentLength != 255) { + // packets end at first lace < 255 + break; + } + } + return size; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggPageHeader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggPageHeader.java new file mode 100644 index 0000000..087c1a4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggPageHeader.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; + +/** + * Data object to store header information. + */ +/* package */ final class OggPageHeader { + + public static final int EMPTY_PAGE_HEADER_SIZE = 27; + public static final int MAX_SEGMENT_COUNT = 255; + public static final int MAX_PAGE_PAYLOAD = 255 * 255; + public static final int MAX_PAGE_SIZE = EMPTY_PAGE_HEADER_SIZE + MAX_SEGMENT_COUNT + + MAX_PAGE_PAYLOAD; + + private static final int TYPE_OGGS = Util.getIntegerCodeForString("OggS"); + + public int revision; + public int type; + public long granulePosition; + public long streamSerialNumber; + public long pageSequenceNumber; + public long pageChecksum; + public int pageSegmentCount; + public int headerSize; + public int bodySize; + /** + * Be aware that {@code laces.length} is always {@link #MAX_SEGMENT_COUNT}. Instead use + * {@link #pageSegmentCount} to iterate. + */ + public final int[] laces = new int[MAX_SEGMENT_COUNT]; + + private final ParsableByteArray scratch = new ParsableByteArray(MAX_SEGMENT_COUNT); + + /** + * Resets all primitive member fields to zero. + */ + public void reset() { + revision = 0; + type = 0; + granulePosition = 0; + streamSerialNumber = 0; + pageSequenceNumber = 0; + pageChecksum = 0; + pageSegmentCount = 0; + headerSize = 0; + bodySize = 0; + } + + /** + * Peeks an Ogg page header and updates this {@link OggPageHeader}. + * + * @param input the {@link ExtractorInput} to read from. + * @param quiet if {@code true} no Exceptions are thrown but {@code false} is return if something + * goes wrong. + * @return {@code true} if the read was successful. {@code false} if the end of the input was + * encountered having read no data. + * @throws IOException thrown if reading data fails or the stream is invalid. + * @throws InterruptedException thrown if thread is interrupted when reading/peeking. + */ + public boolean populate(ExtractorInput input, boolean quiet) + throws IOException, InterruptedException { + scratch.reset(); + reset(); + boolean hasEnoughBytes = input.getLength() == C.LENGTH_UNSET + || input.getLength() - input.getPeekPosition() >= EMPTY_PAGE_HEADER_SIZE; + if (!hasEnoughBytes || !input.peekFully(scratch.data, 0, EMPTY_PAGE_HEADER_SIZE, true)) { + if (quiet) { + return false; + } else { + throw new EOFException(); + } + } + if (scratch.readUnsignedInt() != TYPE_OGGS) { + if (quiet) { + return false; + } else { + throw new ParserException("expected OggS capture pattern at begin of page"); + } + } + + revision = scratch.readUnsignedByte(); + if (revision != 0x00) { + if (quiet) { + return false; + } else { + throw new ParserException("unsupported bit stream revision"); + } + } + type = scratch.readUnsignedByte(); + + granulePosition = scratch.readLittleEndianLong(); + streamSerialNumber = scratch.readLittleEndianUnsignedInt(); + pageSequenceNumber = scratch.readLittleEndianUnsignedInt(); + pageChecksum = scratch.readLittleEndianUnsignedInt(); + pageSegmentCount = scratch.readUnsignedByte(); + headerSize = EMPTY_PAGE_HEADER_SIZE + pageSegmentCount; + + // calculate total size of header including laces + scratch.reset(); + input.peekFully(scratch.data, 0, pageSegmentCount); + for (int i = 0; i < pageSegmentCount; i++) { + laces[i] = scratch.readUnsignedByte(); + bodySize += laces[i]; + } + + return true; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggSeeker.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggSeeker.java new file mode 100644 index 0000000..28d8154 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OggSeeker.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import java.io.IOException; + +/** + * Used to seek in an Ogg stream. OggSeeker implementation may do direct seeking or progressive + * seeking. OggSeeker works together with a {@link SeekMap} instance to capture the queried position + * and start the seeking with an initial estimated position. + */ +/* package */ interface OggSeeker { + + /** + * Returns a {@link SeekMap} that returns an initial estimated position for progressive seeking + * or the final position for direct seeking. Returns null if {@link #read} has yet to return -1. + */ + SeekMap createSeekMap(); + + /** + * Initializes a seek operation. + * + * @param timeUs The seek position in microseconds. + * @return The granule position targeted by the seek. + */ + long startSeek(long timeUs); + + /** + * Reads data from the {@link ExtractorInput} to build the {@link SeekMap} or to continue a + * progressive seek. + *

    + * If more data is required or if the position of the input needs to be modified then a position + * from which data should be provided is returned. Else a negative value is returned. If a seek + * has been completed then the value returned is -(currentGranule + 2). Else it is -1. + * + * @param input The {@link ExtractorInput} to read from. + * @return A non-negative position to seek the {@link ExtractorInput} to, or -(currentGranule + 2) + * if the progressive seek has completed, or -1 otherwise. + * @throws IOException If reading from the {@link ExtractorInput} fails. + * @throws InterruptedException If the thread is interrupted. + */ + long read(ExtractorInput input) throws IOException, InterruptedException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OpusReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OpusReader.java new file mode 100644 index 0000000..bf7525e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/OpusReader.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * {@link StreamReader} to extract Opus data out of Ogg byte stream. + */ +/* package */ final class OpusReader extends StreamReader { + + private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; + + /** + * Opus streams are always decoded at 48000 Hz. + */ + private static final int SAMPLE_RATE = 48000; + + private static final int OPUS_CODE = Util.getIntegerCodeForString("Opus"); + private static final byte[] OPUS_SIGNATURE = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'}; + + private boolean headerRead; + + public static boolean verifyBitstreamType(ParsableByteArray data) { + if (data.bytesLeft() < OPUS_SIGNATURE.length) { + return false; + } + byte[] header = new byte[OPUS_SIGNATURE.length]; + data.readBytes(header, 0, OPUS_SIGNATURE.length); + return Arrays.equals(header, OPUS_SIGNATURE); + } + + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + headerRead = false; + } + } + + @Override + protected long preparePayload(ParsableByteArray packet) { + return convertTimeToGranule(getPacketDurationUs(packet.data)); + } + + @Override + protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) + throws IOException, InterruptedException { + if (!headerRead) { + byte[] metadata = Arrays.copyOf(packet.data, packet.limit()); + int channelCount = metadata[9] & 0xFF; + int preskip = ((metadata[11] & 0xFF) << 8) | (metadata[10] & 0xFF); + + List initializationData = new ArrayList<>(3); + initializationData.add(metadata); + putNativeOrderLong(initializationData, preskip); + putNativeOrderLong(initializationData, DEFAULT_SEEK_PRE_ROLL_SAMPLES); + + setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_OPUS, null, + Format.NO_VALUE, Format.NO_VALUE, channelCount, SAMPLE_RATE, initializationData, null, 0, + null); + headerRead = true; + } else { + boolean headerPacket = packet.readInt() == OPUS_CODE; + packet.setPosition(0); + return headerPacket; + } + return true; + } + + private void putNativeOrderLong(List initializationData, int samples) { + long ns = (samples * C.NANOS_PER_SECOND) / SAMPLE_RATE; + byte[] array = ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(ns).array(); + initializationData.add(array); + } + + /** + * Returns the duration of the given audio packet. + * + * @param packet Contains audio data. + * @return Returns the duration of the given audio packet. + */ + private long getPacketDurationUs(byte[] packet) { + int toc = packet[0] & 0xFF; + int frames; + switch (toc & 0x3) { + case 0: + frames = 1; + break; + case 1: + case 2: + frames = 2; + break; + default: + frames = packet[1] & 0x3F; + break; + } + + int config = toc >> 3; + int length = config & 0x3; + if (config >= 16) { + length = 2500 << length; + } else if (config >= 12) { + length = 10000 << (length & 0x1); + } else if (length == 3) { + length = 60000; + } else { + length = 10000 << length; + } + return frames * length; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/StreamReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/StreamReader.java new file mode 100644 index 0000000..79c6959 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/StreamReader.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * StreamReader abstract class. + */ +/* package */ abstract class StreamReader { + + private static final int STATE_READ_HEADERS = 0; + private static final int STATE_SKIP_HEADERS = 1; + private static final int STATE_READ_PAYLOAD = 2; + private static final int STATE_END_OF_INPUT = 3; + + static class SetupData { + Format format; + OggSeeker oggSeeker; + } + + private OggPacket oggPacket; + private TrackOutput trackOutput; + private ExtractorOutput extractorOutput; + private OggSeeker oggSeeker; + private long targetGranule; + private long payloadStartPosition; + private long currentGranule; + private int state; + private int sampleRate; + private SetupData setupData; + private long lengthOfReadPacket; + private boolean seekMapSet; + private boolean formatSet; + + void init(ExtractorOutput output, TrackOutput trackOutput) { + this.extractorOutput = output; + this.trackOutput = trackOutput; + this.oggPacket = new OggPacket(); + + reset(true); + } + + /** + * Resets the state of the {@link StreamReader}. + * + * @param headerData Resets parsed header data too. + */ + protected void reset(boolean headerData) { + if (headerData) { + setupData = new SetupData(); + payloadStartPosition = 0; + state = STATE_READ_HEADERS; + } else { + state = STATE_SKIP_HEADERS; + } + targetGranule = -1; + currentGranule = 0; + } + + /** + * @see Extractor#seek(long, long) + */ + final void seek(long position, long timeUs) { + oggPacket.reset(); + if (position == 0) { + reset(!seekMapSet); + } else { + if (state != STATE_READ_HEADERS) { + targetGranule = oggSeeker.startSeek(timeUs); + state = STATE_READ_PAYLOAD; + } + } + } + + /** + * @see Extractor#read(ExtractorInput, PositionHolder) + */ + final int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + switch (state) { + case STATE_READ_HEADERS: + return readHeaders(input); + + case STATE_SKIP_HEADERS: + input.skipFully((int) payloadStartPosition); + state = STATE_READ_PAYLOAD; + return Extractor.RESULT_CONTINUE; + + case STATE_READ_PAYLOAD: + return readPayload(input, seekPosition); + + default: + // Never happens. + throw new IllegalStateException(); + } + } + + private int readHeaders(ExtractorInput input) throws IOException, InterruptedException { + boolean readingHeaders = true; + while (readingHeaders) { + if (!oggPacket.populate(input)) { + state = STATE_END_OF_INPUT; + return Extractor.RESULT_END_OF_INPUT; + } + lengthOfReadPacket = input.getPosition() - payloadStartPosition; + + readingHeaders = readHeaders(oggPacket.getPayload(), payloadStartPosition, setupData); + if (readingHeaders) { + payloadStartPosition = input.getPosition(); + } + } + + sampleRate = setupData.format.sampleRate; + if (!formatSet) { + trackOutput.format(setupData.format); + formatSet = true; + } + + if (setupData.oggSeeker != null) { + oggSeeker = setupData.oggSeeker; + } else if (input.getLength() == C.LENGTH_UNSET) { + oggSeeker = new UnseekableOggSeeker(); + } else { + OggPageHeader firstPayloadPageHeader = oggPacket.getPageHeader(); + oggSeeker = new DefaultOggSeeker(payloadStartPosition, input.getLength(), this, + firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize, + firstPayloadPageHeader.granulePosition); + } + + setupData = null; + state = STATE_READ_PAYLOAD; + return Extractor.RESULT_CONTINUE; + } + + private int readPayload(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + long position = oggSeeker.read(input); + if (position >= 0) { + seekPosition.position = position; + return Extractor.RESULT_SEEK; + } else if (position < -1) { + onSeekEnd(-(position + 2)); + } + if (!seekMapSet) { + SeekMap seekMap = oggSeeker.createSeekMap(); + extractorOutput.seekMap(seekMap); + seekMapSet = true; + } + + if (lengthOfReadPacket > 0 || oggPacket.populate(input)) { + lengthOfReadPacket = 0; + ParsableByteArray payload = oggPacket.getPayload(); + long granulesInPacket = preparePayload(payload); + if (granulesInPacket >= 0 && currentGranule + granulesInPacket >= targetGranule) { + // calculate time and send payload data to codec + long timeUs = convertGranuleToTime(currentGranule); + trackOutput.sampleData(payload, payload.limit()); + trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, payload.limit(), 0, null); + targetGranule = -1; + } + currentGranule += granulesInPacket; + } else { + state = STATE_END_OF_INPUT; + return Extractor.RESULT_END_OF_INPUT; + } + return Extractor.RESULT_CONTINUE; + } + + /** + * Converts granule value to time. + * + * @param granule The granule value. + * @return Time in milliseconds. + */ + protected long convertGranuleToTime(long granule) { + return (granule * C.MICROS_PER_SECOND) / sampleRate; + } + + /** + * Converts time value to granule. + * + * @param timeUs Time in milliseconds. + * @return The granule value. + */ + protected long convertTimeToGranule(long timeUs) { + return (sampleRate * timeUs) / C.MICROS_PER_SECOND; + } + + /** + * Prepares payload data in the packet for submitting to TrackOutput and returns number of + * granules in the packet. + * + * @param packet Ogg payload data packet. + * @return Number of granules in the packet or -1 if the packet doesn't contain payload data. + */ + protected abstract long preparePayload(ParsableByteArray packet); + + /** + * Checks if the given packet is a header packet and reads it. + * + * @param packet An ogg packet. + * @param position Position of the given header packet. + * @param setupData Setup data to be filled. + * @return Whether the packet contains header data. + */ + protected abstract boolean readHeaders(ParsableByteArray packet, long position, + SetupData setupData) throws IOException, InterruptedException; + + /** + * Called on end of seeking. + * + * @param currentGranule The granule at the current input position. + */ + protected void onSeekEnd(long currentGranule) { + this.currentGranule = currentGranule; + } + + private static final class UnseekableOggSeeker implements OggSeeker { + + @Override + public long read(ExtractorInput input) throws IOException, InterruptedException { + return -1; + } + + @Override + public long startSeek(long timeUs) { + return 0; + } + + @Override + public SeekMap createSeekMap() { + return new SeekMap.Unseekable(C.TIME_UNSET); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisBitArray.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisBitArray.java new file mode 100644 index 0000000..25db364 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisBitArray.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * Wraps a byte array, providing methods that allow it to be read as a vorbis bitstream. + * + * @see Vorbis bitpacking + * specification + */ +/* package */ final class VorbisBitArray { + + public final byte[] data; + private final int limit; + private int byteOffset; + private int bitOffset; + + /** + * Creates a new instance that wraps an existing array. + * + * @param data the array to wrap. + */ + public VorbisBitArray(byte[] data) { + this(data, data.length); + } + + /** + * Creates a new instance that wraps an existing array. + * + * @param data the array to wrap. + * @param limit the limit in bytes. + */ + public VorbisBitArray(byte[] data, int limit) { + this.data = data; + this.limit = limit * 8; + } + + /** + * Resets the reading position to zero. + */ + public void reset() { + byteOffset = 0; + bitOffset = 0; + } + + /** + * Reads a single bit. + * + * @return {@code true} if the bit is set, {@code false} otherwise. + */ + public boolean readBit() { + return readBits(1) == 1; + } + + /** + * Reads up to 32 bits. + * + * @param numBits The number of bits to read. + * @return An integer whose bottom {@code numBits} bits hold the read data. + */ + public int readBits(int numBits) { + Assertions.checkState(getPosition() + numBits <= limit); + if (numBits == 0) { + return 0; + } + int result = 0; + int bitCount = 0; + if (bitOffset != 0) { + bitCount = Math.min(numBits, 8 - bitOffset); + int mask = 0xFF >>> (8 - bitCount); + result = (data[byteOffset] >>> bitOffset) & mask; + bitOffset += bitCount; + if (bitOffset == 8) { + byteOffset++; + bitOffset = 0; + } + } + + if (numBits - bitCount > 7) { + int numBytes = (numBits - bitCount) / 8; + for (int i = 0; i < numBytes; i++) { + result |= (data[byteOffset++] & 0xFFL) << bitCount; + bitCount += 8; + } + } + + if (numBits > bitCount) { + int bitsOnNextByte = numBits - bitCount; + int mask = 0xFF >>> (8 - bitsOnNextByte); + result |= (data[byteOffset] & mask) << bitCount; + bitOffset += bitsOnNextByte; + } + return result; + } + + /** + * Skips {@code numberOfBits} bits. + * + * @param numberOfBits The number of bits to skip. + */ + public void skipBits(int numberOfBits) { + Assertions.checkState(getPosition() + numberOfBits <= limit); + byteOffset += numberOfBits / 8; + bitOffset += numberOfBits % 8; + if (bitOffset > 7) { + byteOffset++; + bitOffset -= 8; + } + } + + /** + * Returns the reading position in bits. + */ + public int getPosition() { + return byteOffset * 8 + bitOffset; + } + + /** + * Sets the reading position in bits. + * + * @param position The new reading position in bits. + */ + public void setPosition(int position) { + Assertions.checkArgument(position < limit && position >= 0); + byteOffset = position / 8; + bitOffset = position - (byteOffset * 8); + } + + /** + * Returns the number of remaining bits. + */ + public int bitsLeft() { + return limit - getPosition(); + } + + /** + * Returns the limit in bits. + **/ + public int limit() { + return limit; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisReader.java new file mode 100644 index 0000000..e5e0729 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisReader.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg.VorbisUtil.Mode; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.IOException; +import java.util.ArrayList; + +/** + * {@link StreamReader} to extract Vorbis data out of Ogg byte stream. + */ +/* package */ final class VorbisReader extends StreamReader { + + private VorbisSetup vorbisSetup; + private int previousPacketBlockSize; + private boolean seenFirstAudioPacket; + + private VorbisUtil.VorbisIdHeader vorbisIdHeader; + private VorbisUtil.CommentHeader commentHeader; + + public static boolean verifyBitstreamType(ParsableByteArray data) { + try { + return VorbisUtil.verifyVorbisHeaderCapturePattern(0x01, data, true); + } catch (ParserException e) { + return false; + } + } + + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + vorbisSetup = null; + vorbisIdHeader = null; + commentHeader = null; + } + previousPacketBlockSize = 0; + seenFirstAudioPacket = false; + } + + @Override + protected void onSeekEnd(long currentGranule) { + super.onSeekEnd(currentGranule); + seenFirstAudioPacket = currentGranule != 0; + previousPacketBlockSize = vorbisIdHeader != null ? vorbisIdHeader.blockSize0 : 0; + } + + @Override + protected long preparePayload(ParsableByteArray packet) { + // if this is not an audio packet... + if ((packet.data[0] & 0x01) == 1) { + return -1; + } + + // ... we need to decode the block size + int packetBlockSize = decodeBlockSize(packet.data[0], vorbisSetup); + // a packet contains samples produced from overlapping the previous and current frame data + // (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2) + int samplesInPacket = seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4 + : 0; + // codec expects the number of samples appended to audio data + appendNumberOfSamples(packet, samplesInPacket); + + // update state in members for next iteration + seenFirstAudioPacket = true; + previousPacketBlockSize = packetBlockSize; + return samplesInPacket; + } + + @Override + protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) + throws IOException, InterruptedException { + if (vorbisSetup != null) { + return false; + } + + vorbisSetup = readSetupHeaders(packet); + if (vorbisSetup == null) { + return true; + } + + ArrayList codecInitialisationData = new ArrayList<>(); + codecInitialisationData.add(vorbisSetup.idHeader.data); + codecInitialisationData.add(vorbisSetup.setupHeaderData); + + setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS, null, + this.vorbisSetup.idHeader.bitrateNominal, OggPageHeader.MAX_PAGE_PAYLOAD, + this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, + codecInitialisationData, null, 0, null); + return true; + } + + //@VisibleForTesting + /* package */ VorbisSetup readSetupHeaders(ParsableByteArray scratch) throws IOException { + + if (vorbisIdHeader == null) { + vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch); + return null; + } + + if (commentHeader == null) { + commentHeader = VorbisUtil.readVorbisCommentHeader(scratch); + return null; + } + + // the third packet contains the setup header + byte[] setupHeaderData = new byte[scratch.limit()]; + // raw data of vorbis setup header has to be passed to decoder as CSD buffer #2 + System.arraycopy(scratch.data, 0, setupHeaderData, 0, scratch.limit()); + // partially decode setup header to get the modes + Mode[] modes = VorbisUtil.readVorbisModes(scratch, vorbisIdHeader.channels); + // we need the ilog of modes all the time when extracting, so we compute it once + int iLogModes = VorbisUtil.iLog(modes.length - 1); + + return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes); + } + + /** + * Reads an int of {@code length} bits from {@code src} starting at + * {@code leastSignificantBitIndex}. + * + * @param src the {@code byte} to read from. + * @param length the length in bits of the int to read. + * @param leastSignificantBitIndex the index of the least significant bit of the int to read. + * @return the int value read. + */ + //@VisibleForTesting + /* package */ static int readBits(byte src, int length, int leastSignificantBitIndex) { + return (src >> leastSignificantBitIndex) & (255 >>> (8 - length)); + } + + //@VisibleForTesting + /* package */ static void appendNumberOfSamples(ParsableByteArray buffer, + long packetSampleCount) { + + buffer.setLimit(buffer.limit() + 4); + // The vorbis decoder expects the number of samples in the packet + // to be appended to the audio data as an int32 + buffer.data[buffer.limit() - 4] = (byte) ((packetSampleCount) & 0xFF); + buffer.data[buffer.limit() - 3] = (byte) ((packetSampleCount >>> 8) & 0xFF); + buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF); + buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF); + } + + private static int decodeBlockSize(byte firstByteOfAudioPacket, VorbisSetup vorbisSetup) { + // read modeNumber (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-730004.3.1) + int modeNumber = readBits(firstByteOfAudioPacket, vorbisSetup.iLogModes, 1); + int currentBlockSize; + if (!vorbisSetup.modes[modeNumber].blockFlag) { + currentBlockSize = vorbisSetup.idHeader.blockSize0; + } else { + currentBlockSize = vorbisSetup.idHeader.blockSize1; + } + return currentBlockSize; + } + + /** + * Class to hold all data read from Vorbis setup headers. + */ + /* package */ static final class VorbisSetup { + + public final VorbisUtil.VorbisIdHeader idHeader; + public final VorbisUtil.CommentHeader commentHeader; + public final byte[] setupHeaderData; + public final Mode[] modes; + public final int iLogModes; + + public VorbisSetup(VorbisUtil.VorbisIdHeader idHeader, VorbisUtil.CommentHeader + commentHeader, byte[] setupHeaderData, Mode[] modes, int iLogModes) { + this.idHeader = idHeader; + this.commentHeader = commentHeader; + this.setupHeaderData = setupHeaderData; + this.modes = modes; + this.iLogModes = iLogModes; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisUtil.java new file mode 100644 index 0000000..bf83eb6 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ogg/VorbisUtil.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ogg; + +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.Arrays; + +/** + * Utility methods for parsing vorbis streams. + */ +/* package */ final class VorbisUtil { + + private static final String TAG = "VorbisUtil"; + + /** + * Returns ilog(x), which is the index of the highest set bit in {@code x}. + * + * @see + * Vorbis spec + * @param x the value of which the ilog should be calculated. + * @return ilog(x) + */ + public static int iLog(int x) { + int val = 0; + while (x > 0) { + val++; + x >>>= 1; + } + return val; + } + + /** + * Reads a vorbis identification header from {@code headerData}. + * + * @see Vorbis + * spec/Identification header + * @param headerData a {@link ParsableByteArray} wrapping the header data. + * @return a {@link VorbisUtil.VorbisIdHeader} with meta data. + * @throws ParserException thrown if invalid capture pattern is detected. + */ + public static VorbisIdHeader readVorbisIdentificationHeader(ParsableByteArray headerData) + throws ParserException { + + verifyVorbisHeaderCapturePattern(0x01, headerData, false); + + long version = headerData.readLittleEndianUnsignedInt(); + int channels = headerData.readUnsignedByte(); + long sampleRate = headerData.readLittleEndianUnsignedInt(); + int bitrateMax = headerData.readLittleEndianInt(); + int bitrateNominal = headerData.readLittleEndianInt(); + int bitrateMin = headerData.readLittleEndianInt(); + + int blockSize = headerData.readUnsignedByte(); + int blockSize0 = (int) Math.pow(2, blockSize & 0x0F); + int blockSize1 = (int) Math.pow(2, (blockSize & 0xF0) >> 4); + + boolean framingFlag = (headerData.readUnsignedByte() & 0x01) > 0; + // raw data of vorbis setup header has to be passed to decoder as CSD buffer #1 + byte[] data = Arrays.copyOf(headerData.data, headerData.limit()); + + return new VorbisIdHeader(version, channels, sampleRate, bitrateMax, bitrateNominal, bitrateMin, + blockSize0, blockSize1, framingFlag, data); + } + + /** + * Reads a vorbis comment header. + * + * @see + * Vorbis spec/Comment header + * @param headerData a {@link ParsableByteArray} wrapping the header data. + * @return a {@link VorbisUtil.CommentHeader} with all the comments. + * @throws ParserException thrown if invalid capture pattern is detected. + */ + public static CommentHeader readVorbisCommentHeader(ParsableByteArray headerData) + throws ParserException { + + verifyVorbisHeaderCapturePattern(0x03, headerData, false); + int length = 7; + + int len = (int) headerData.readLittleEndianUnsignedInt(); + length += 4; + String vendor = headerData.readString(len); + length += vendor.length(); + + long commentListLen = headerData.readLittleEndianUnsignedInt(); + String[] comments = new String[(int) commentListLen]; + length += 4; + for (int i = 0; i < commentListLen; i++) { + len = (int) headerData.readLittleEndianUnsignedInt(); + length += 4; + comments[i] = headerData.readString(len); + length += comments[i].length(); + } + if ((headerData.readUnsignedByte() & 0x01) == 0) { + throw new ParserException("framing bit expected to be set"); + } + length += 1; + return new CommentHeader(vendor, comments, length); + } + + /** + * Verifies whether the next bytes in {@code header} are a vorbis header of the given + * {@code headerType}. + * + * @param headerType the type of the header expected. + * @param header the alleged header bytes. + * @param quiet if {@code true} no exceptions are thrown. Instead {@code false} is returned. + * @return the number of bytes read. + * @throws ParserException thrown if header type or capture pattern is not as expected. + */ + public static boolean verifyVorbisHeaderCapturePattern(int headerType, ParsableByteArray header, + boolean quiet) + throws ParserException { + if (header.bytesLeft() < 7) { + if (quiet) { + return false; + } else { + throw new ParserException("too short header: " + header.bytesLeft()); + } + } + + if (header.readUnsignedByte() != headerType) { + if (quiet) { + return false; + } else { + throw new ParserException("expected header type " + Integer.toHexString(headerType)); + } + } + + if (!(header.readUnsignedByte() == 'v' + && header.readUnsignedByte() == 'o' + && header.readUnsignedByte() == 'r' + && header.readUnsignedByte() == 'b' + && header.readUnsignedByte() == 'i' + && header.readUnsignedByte() == 's')) { + if (quiet) { + return false; + } else { + throw new ParserException("expected characters 'vorbis'"); + } + } + return true; + } + + /** + * This method reads the modes which are located at the very end of the vorbis setup header. + * That's why we need to partially decode or at least read the entire setup header to know + * where to start reading the modes. + * + * @see + * Vorbis spec/Setup header + * @param headerData a {@link ParsableByteArray} containing setup header data. + * @param channels the number of channels. + * @return an array of {@link Mode}s. + * @throws ParserException thrown if bit stream is invalid. + */ + public static Mode[] readVorbisModes(ParsableByteArray headerData, int channels) + throws ParserException { + + verifyVorbisHeaderCapturePattern(0x05, headerData, false); + + int numberOfBooks = headerData.readUnsignedByte() + 1; + + VorbisBitArray bitArray = new VorbisBitArray(headerData.data); + bitArray.skipBits(headerData.getPosition() * 8); + + for (int i = 0; i < numberOfBooks; i++) { + readBook(bitArray); + } + + int timeCount = bitArray.readBits(6) + 1; + for (int i = 0; i < timeCount; i++) { + if (bitArray.readBits(16) != 0x00) { + throw new ParserException("placeholder of time domain transforms not zeroed out"); + } + } + readFloors(bitArray); + readResidues(bitArray); + readMappings(channels, bitArray); + + Mode[] modes = readModes(bitArray); + if (!bitArray.readBit()) { + throw new ParserException("framing bit after modes not set as expected"); + } + return modes; + } + + private static Mode[] readModes(VorbisBitArray bitArray) { + int modeCount = bitArray.readBits(6) + 1; + Mode[] modes = new Mode[modeCount]; + for (int i = 0; i < modeCount; i++) { + boolean blockFlag = bitArray.readBit(); + int windowType = bitArray.readBits(16); + int transformType = bitArray.readBits(16); + int mapping = bitArray.readBits(8); + modes[i] = new Mode(blockFlag, windowType, transformType, mapping); + } + return modes; + } + + private static void readMappings(int channels, VorbisBitArray bitArray) + throws ParserException { + int mappingsCount = bitArray.readBits(6) + 1; + for (int i = 0; i < mappingsCount; i++) { + int mappingType = bitArray.readBits(16); + switch (mappingType) { + case 0: + int submaps; + if (bitArray.readBit()) { + submaps = bitArray.readBits(4) + 1; + } else { + submaps = 1; + } + int couplingSteps; + if (bitArray.readBit()) { + couplingSteps = bitArray.readBits(8) + 1; + for (int j = 0; j < couplingSteps; j++) { + bitArray.skipBits(iLog(channels - 1)); // magnitude + bitArray.skipBits(iLog(channels - 1)); // angle + } + } /*else { + couplingSteps = 0; + }*/ + if (bitArray.readBits(2) != 0x00) { + throw new ParserException("to reserved bits must be zero after mapping coupling steps"); + } + if (submaps > 1) { + for (int j = 0; j < channels; j++) { + bitArray.skipBits(4); // mappingMux + } + } + for (int j = 0; j < submaps; j++) { + bitArray.skipBits(8); // discard + bitArray.skipBits(8); // submapFloor + bitArray.skipBits(8); // submapResidue + } + break; + default: + } + } + } + + private static void readResidues(VorbisBitArray bitArray) throws ParserException { + int residueCount = bitArray.readBits(6) + 1; + for (int i = 0; i < residueCount; i++) { + int residueType = bitArray.readBits(16); + if (residueType > 2) { + throw new ParserException("residueType greater than 2 is not decodable"); + } else { + bitArray.skipBits(24); // begin + bitArray.skipBits(24); // end + bitArray.skipBits(24); // partitionSize (add one) + int classifications = bitArray.readBits(6) + 1; + bitArray.skipBits(8); // classbook + int[] cascade = new int[classifications]; + for (int j = 0; j < classifications; j++) { + int highBits = 0; + int lowBits = bitArray.readBits(3); + if (bitArray.readBit()) { + highBits = bitArray.readBits(5); + } + cascade[j] = highBits * 8 + lowBits; + } + for (int j = 0; j < classifications; j++) { + for (int k = 0; k < 8; k++) { + if ((cascade[j] & (0x01 << k)) != 0) { + bitArray.skipBits(8); // discard + } + } + } + } + } + } + + private static void readFloors(VorbisBitArray bitArray) throws ParserException { + int floorCount = bitArray.readBits(6) + 1; + for (int i = 0; i < floorCount; i++) { + int floorType = bitArray.readBits(16); + switch (floorType) { + case 0: + bitArray.skipBits(8); //order + bitArray.skipBits(16); // rate + bitArray.skipBits(16); // barkMapSize + bitArray.skipBits(6); // amplitudeBits + bitArray.skipBits(8); // amplitudeOffset + int floorNumberOfBooks = bitArray.readBits(4) + 1; + for (int j = 0; j < floorNumberOfBooks; j++) { + bitArray.skipBits(8); + } + break; + case 1: + int partitions = bitArray.readBits(5); + int maximumClass = -1; + int[] partitionClassList = new int[partitions]; + for (int j = 0; j < partitions; j++) { + partitionClassList[j] = bitArray.readBits(4); + if (partitionClassList[j] > maximumClass) { + maximumClass = partitionClassList[j]; + } + } + int[] classDimensions = new int[maximumClass + 1]; + for (int j = 0; j < classDimensions.length; j++) { + classDimensions[j] = bitArray.readBits(3) + 1; + int classSubclasses = bitArray.readBits(2); + if (classSubclasses > 0) { + bitArray.skipBits(8); // classMasterbooks + } + for (int k = 0; k < (1 << classSubclasses); k++) { + bitArray.skipBits(8); // subclassBook (subtract 1) + } + } + bitArray.skipBits(2); // multiplier (add one) + int rangeBits = bitArray.readBits(4); + int count = 0; + for (int j = 0, k = 0; j < partitions; j++) { + int idx = partitionClassList[j]; + count += classDimensions[idx]; + for (; k < count; k++) { + bitArray.skipBits(rangeBits); // floorValue + } + } + break; + default: + throw new ParserException("floor type greater than 1 not decodable: " + floorType); + } + } + } + + private static CodeBook readBook(VorbisBitArray bitArray) throws ParserException { + if (bitArray.readBits(24) != 0x564342) { + throw new ParserException("expected code book to start with [0x56, 0x43, 0x42] at " + + bitArray.getPosition()); + } + int dimensions = bitArray.readBits(16); + int entries = bitArray.readBits(24); + long[] lengthMap = new long[entries]; + + boolean isOrdered = bitArray.readBit(); + if (!isOrdered) { + boolean isSparse = bitArray.readBit(); + for (int i = 0; i < lengthMap.length; i++) { + if (isSparse) { + if (bitArray.readBit()) { + lengthMap[i] = bitArray.readBits(5) + 1; + } else { // entry unused + lengthMap[i] = 0; + } + } else { // not sparse + lengthMap[i] = bitArray.readBits(5) + 1; + } + } + } else { + int length = bitArray.readBits(5) + 1; + for (int i = 0; i < lengthMap.length;) { + int num = bitArray.readBits(iLog(entries - i)); + for (int j = 0; j < num && i < lengthMap.length; i++, j++) { + lengthMap[i] = length; + } + length++; + } + } + + int lookupType = bitArray.readBits(4); + if (lookupType > 2) { + throw new ParserException("lookup type greater than 2 not decodable: " + lookupType); + } else if (lookupType == 1 || lookupType == 2) { + bitArray.skipBits(32); // minimumValue + bitArray.skipBits(32); // deltaValue + int valueBits = bitArray.readBits(4) + 1; + bitArray.skipBits(1); // sequenceP + long lookupValuesCount; + if (lookupType == 1) { + if (dimensions != 0) { + lookupValuesCount = mapType1QuantValues(entries, dimensions); + } else { + lookupValuesCount = 0; + } + } else { + lookupValuesCount = entries * dimensions; + } + // discard (no decoding required yet) + bitArray.skipBits((int) (lookupValuesCount * valueBits)); + } + return new CodeBook(dimensions, entries, lengthMap, lookupType, isOrdered); + } + + /** + * @see _book_maptype1_quantvals + */ + private static long mapType1QuantValues(long entries, long dimension) { + return (long) Math.floor(Math.pow(entries, 1.d / dimension)); + } + + public static final class CodeBook { + + public final int dimensions; + public final int entries; + public final long[] lengthMap; + public final int lookupType; + public final boolean isOrdered; + + public CodeBook(int dimensions, int entries, long[] lengthMap, int lookupType, + boolean isOrdered) { + this.dimensions = dimensions; + this.entries = entries; + this.lengthMap = lengthMap; + this.lookupType = lookupType; + this.isOrdered = isOrdered; + } + + } + + public static final class CommentHeader { + + public final String vendor; + public final String[] comments; + public final int length; + + public CommentHeader(String vendor, String[] comments, int length) { + this.vendor = vendor; + this.comments = comments; + this.length = length; + } + + } + + public static final class VorbisIdHeader { + + public final long version; + public final int channels; + public final long sampleRate; + public final int bitrateMax; + public final int bitrateNominal; + public final int bitrateMin; + public final int blockSize0; + public final int blockSize1; + public final boolean framingFlag; + public final byte[] data; + + public VorbisIdHeader(long version, int channels, long sampleRate, int bitrateMax, + int bitrateNominal, int bitrateMin, int blockSize0, int blockSize1, boolean framingFlag, + byte[] data) { + this.version = version; + this.channels = channels; + this.sampleRate = sampleRate; + this.bitrateMax = bitrateMax; + this.bitrateNominal = bitrateNominal; + this.bitrateMin = bitrateMin; + this.blockSize0 = blockSize0; + this.blockSize1 = blockSize1; + this.framingFlag = framingFlag; + this.data = data; + } + + public int getApproximateBitrate() { + return bitrateNominal == 0 ? (bitrateMin + bitrateMax) / 2 : bitrateNominal; + } + + } + + public static final class Mode { + + public final boolean blockFlag; + public final int windowType; + public final int transformType; + public final int mapping; + + public Mode(boolean blockFlag, int windowType, int transformType, int mapping) { + this.blockFlag = blockFlag; + this.windowType = windowType; + this.transformType = transformType; + this.mapping = mapping; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/rawcc/RawCcExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/rawcc/RawCcExtractor.java new file mode 100644 index 0000000..b95eb16 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/rawcc/RawCcExtractor.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.rawcc; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Extracts CEA data from a RawCC file. + */ +public final class RawCcExtractor implements Extractor { + + private static final int SCRATCH_SIZE = 9; + private static final int HEADER_SIZE = 8; + private static final int HEADER_ID = Util.getIntegerCodeForString("RCC\u0001"); + private static final int TIMESTAMP_SIZE_V0 = 4; + private static final int TIMESTAMP_SIZE_V1 = 8; + + // Parser states. + private static final int STATE_READING_HEADER = 0; + private static final int STATE_READING_TIMESTAMP_AND_COUNT = 1; + private static final int STATE_READING_SAMPLES = 2; + + private final Format format; + + private final ParsableByteArray dataScratch; + + private TrackOutput trackOutput; + + private int parserState; + private int version; + private long timestampUs; + private int remainingSampleCount; + private int sampleBytesWritten; + + public RawCcExtractor(Format format) { + this.format = format; + dataScratch = new ParsableByteArray(SCRATCH_SIZE); + parserState = STATE_READING_HEADER; + } + + @Override + public void init(ExtractorOutput output) { + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + trackOutput = output.track(0, C.TRACK_TYPE_TEXT); + output.endTracks(); + trackOutput.format(format); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + dataScratch.reset(); + input.peekFully(dataScratch.data, 0, HEADER_SIZE); + return dataScratch.readInt() == HEADER_ID; + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + while (true) { + switch (parserState) { + case STATE_READING_HEADER: + if (parseHeader(input)) { + parserState = STATE_READING_TIMESTAMP_AND_COUNT; + } else { + return RESULT_END_OF_INPUT; + } + break; + case STATE_READING_TIMESTAMP_AND_COUNT: + if (parseTimestampAndSampleCount(input)) { + parserState = STATE_READING_SAMPLES; + } else { + parserState = STATE_READING_HEADER; + return RESULT_END_OF_INPUT; + } + break; + case STATE_READING_SAMPLES: + parseSamples(input); + parserState = STATE_READING_TIMESTAMP_AND_COUNT; + return RESULT_CONTINUE; + default: + throw new IllegalStateException(); + } + } + } + + @Override + public void seek(long position, long timeUs) { + parserState = STATE_READING_HEADER; + } + + @Override + public void release() { + // Do nothing + } + + private boolean parseHeader(ExtractorInput input) throws IOException, InterruptedException { + dataScratch.reset(); + if (input.readFully(dataScratch.data, 0, HEADER_SIZE, true)) { + if (dataScratch.readInt() != HEADER_ID) { + throw new IOException("Input not RawCC"); + } + version = dataScratch.readUnsignedByte(); + // no versions use the flag fields yet + return true; + } else { + return false; + } + } + + private boolean parseTimestampAndSampleCount(ExtractorInput input) throws IOException, + InterruptedException { + dataScratch.reset(); + if (version == 0) { + if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V0 + 1, true)) { + return false; + } + // version 0 timestamps are 45kHz, so we need to convert them into us + timestampUs = dataScratch.readUnsignedInt() * 1000 / 45; + } else if (version == 1) { + if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V1 + 1, true)) { + return false; + } + timestampUs = dataScratch.readLong(); + } else { + throw new ParserException("Unsupported version number: " + version); + } + + remainingSampleCount = dataScratch.readUnsignedByte(); + sampleBytesWritten = 0; + return true; + } + + private void parseSamples(ExtractorInput input) throws IOException, InterruptedException { + for (; remainingSampleCount > 0; remainingSampleCount--) { + dataScratch.reset(); + input.readFully(dataScratch.data, 0, 3); + + trackOutput.sampleData(dataScratch, 3); + sampleBytesWritten += 3; + } + + if (sampleBytesWritten > 0) { + trackOutput.sampleMetadata(timestampUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Ac3Extractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Ac3Extractor.java new file mode 100644 index 0000000..261b585 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Ac3Extractor.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.Ac3Util; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Facilitates the extraction of AC-3 samples from elementary audio files formatted as AC-3 + * bitstreams. + */ +public final class Ac3Extractor implements Extractor { + + /** + * Factory for {@link Ac3Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new Ac3Extractor()}; + } + + }; + + /** + * The maximum number of bytes to search when sniffing, excluding ID3 information, before giving + * up. + */ + private static final int MAX_SNIFF_BYTES = 8 * 1024; + private static final int AC3_SYNC_WORD = 0x0B77; + private static final int MAX_SYNC_FRAME_SIZE = 2786; + private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); + + private final long firstSampleTimestampUs; + private final ParsableByteArray sampleData; + + private Ac3Reader reader; + private boolean startedPacket; + + public Ac3Extractor() { + this(0); + } + + public Ac3Extractor(long firstSampleTimestampUs) { + this.firstSampleTimestampUs = firstSampleTimestampUs; + sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + // Skip any ID3 headers. + ParsableByteArray scratch = new ParsableByteArray(10); + int startPosition = 0; + while (true) { + input.peekFully(scratch.data, 0, 10); + scratch.setPosition(0); + if (scratch.readUnsignedInt24() != ID3_TAG) { + break; + } + scratch.skipBytes(3); + int length = scratch.readSynchSafeInt(); + startPosition += 10 + length; + input.advancePeekPosition(length); + } + input.resetPeekPosition(); + input.advancePeekPosition(startPosition); + + int headerPosition = startPosition; + int validFramesCount = 0; + while (true) { + input.peekFully(scratch.data, 0, 5); + scratch.setPosition(0); + int syncBytes = scratch.readUnsignedShort(); + if (syncBytes != AC3_SYNC_WORD) { + validFramesCount = 0; + input.resetPeekPosition(); + if (++headerPosition - startPosition >= MAX_SNIFF_BYTES) { + return false; + } + input.advancePeekPosition(headerPosition); + } else { + if (++validFramesCount >= 4) { + return true; + } + int frameSize = Ac3Util.parseAc3SyncframeSize(scratch.data); + if (frameSize == C.LENGTH_UNSET) { + return false; + } + input.advancePeekPosition(frameSize - 5); + } + } + } + + @Override + public void init(ExtractorOutput output) { + reader = new Ac3Reader(); // TODO: Add support for embedded ID3. + reader.createTracks(output, new TrackIdGenerator(0, 1)); + output.endTracks(); + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position, long timeUs) { + startedPacket = false; + reader.seek(); + } + + @Override + public void release() { + // Do nothing. + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, + InterruptedException { + int bytesRead = input.read(sampleData.data, 0, MAX_SYNC_FRAME_SIZE); + if (bytesRead == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + + // Feed whatever data we have to the reader, regardless of whether the read finished or not. + sampleData.setPosition(0); + sampleData.setLimit(bytesRead); + + if (!startedPacket) { + // Pass data to the reader as though it's contained within a single infinitely long packet. + reader.packetStarted(firstSampleTimestampUs, true); + startedPacket = true; + } + // TODO: Make it possible for the reader to consume the dataSource directly, so that it becomes + // unnecessary to copy the data through packetBuffer. + reader.consume(sampleData); + return RESULT_CONTINUE; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Ac3Reader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Ac3Reader.java new file mode 100644 index 0000000..c559ff5 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Ac3Reader.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.Ac3Util; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Parses a continuous (E-)AC-3 byte stream and extracts individual samples. + */ +public final class Ac3Reader implements ElementaryStreamReader { + + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_FINDING_SYNC, STATE_READING_HEADER, STATE_READING_SAMPLE}) + private @interface State {} + private static final int STATE_FINDING_SYNC = 0; + private static final int STATE_READING_HEADER = 1; + private static final int STATE_READING_SAMPLE = 2; + + private static final int HEADER_SIZE = 8; + + private final ParsableBitArray headerScratchBits; + private final ParsableByteArray headerScratchBytes; + private final String language; + + private String trackFormatId; + private TrackOutput output; + + @State private int state; + private int bytesRead; + + // Used to find the header. + private boolean lastByteWas0B; + + // Used when parsing the header. + private long sampleDurationUs; + private Format format; + private int sampleSize; + + // Used when reading the samples. + private long timeUs; + + /** + * Constructs a new reader for (E-)AC-3 elementary streams. + */ + public Ac3Reader() { + this(null); + } + + /** + * Constructs a new reader for (E-)AC-3 elementary streams. + * + * @param language Track language. + */ + public Ac3Reader(String language) { + headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); + headerScratchBytes = new ParsableByteArray(headerScratchBits.data); + state = STATE_FINDING_SYNC; + this.language = language; + } + + @Override + public void seek() { + state = STATE_FINDING_SYNC; + bytesRead = 0; + lastByteWas0B = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) { + generator.generateNewId(); + trackFormatId = generator.getFormatId(); + output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + timeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_SYNC: + if (skipToNextSync(data)) { + state = STATE_READING_HEADER; + headerScratchBytes.data[0] = 0x0B; + headerScratchBytes.data[1] = 0x77; + bytesRead = 2; + } + break; + case STATE_READING_HEADER: + if (continueRead(data, headerScratchBytes.data, HEADER_SIZE)) { + parseHeader(); + headerScratchBytes.setPosition(0); + output.sampleData(headerScratchBytes, HEADER_SIZE); + state = STATE_READING_SAMPLE; + } + break; + case STATE_READING_SAMPLE: + int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); + output.sampleData(data, bytesToRead); + bytesRead += bytesToRead; + if (bytesRead == sampleSize) { + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + timeUs += sampleDurationUs; + state = STATE_FINDING_SYNC; + } + break; + default: + break; + } + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + /** + * Continues a read from the provided {@code source} into a given {@code target}. It's assumed + * that the data should be written into {@code target} starting from an offset of zero. + * + * @param source The source from which to read. + * @param target The target into which data is to be read. + * @param targetLength The target length of the read. + * @return Whether the target length was reached. + */ + private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { + int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); + source.readBytes(target, bytesRead, bytesToRead); + bytesRead += bytesToRead; + return bytesRead == targetLength; + } + + /** + * Locates the next syncword, advancing the position to the byte that immediately follows it. If a + * syncword was not located, the position is advanced to the limit. + * + * @param pesBuffer The buffer whose position should be advanced. + * @return Whether a syncword position was found. + */ + private boolean skipToNextSync(ParsableByteArray pesBuffer) { + while (pesBuffer.bytesLeft() > 0) { + if (!lastByteWas0B) { + lastByteWas0B = pesBuffer.readUnsignedByte() == 0x0B; + continue; + } + int secondByte = pesBuffer.readUnsignedByte(); + if (secondByte == 0x77) { + lastByteWas0B = false; + return true; + } else { + lastByteWas0B = secondByte == 0x0B; + } + } + return false; + } + + /** + * Parses the sample header. + */ + @SuppressWarnings("ReferenceEquality") + private void parseHeader() { + headerScratchBits.setPosition(0); + Ac3Util.Ac3SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(headerScratchBits); + if (format == null || frameInfo.channelCount != format.channelCount + || frameInfo.sampleRate != format.sampleRate + || frameInfo.mimeType != format.sampleMimeType) { + format = Format.createAudioSampleFormat(trackFormatId, frameInfo.mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, frameInfo.channelCount, frameInfo.sampleRate, null, + null, 0, language); + output.format(format); + } + sampleSize = frameInfo.frameSize; + // In this class a sample is an access unit (syncframe in AC-3), but the MediaFormat sample rate + // specifies the number of PCM audio samples per second. + sampleDurationUs = C.MICROS_PER_SECOND * frameInfo.sampleCount / format.sampleRate; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/AdtsExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/AdtsExtractor.java new file mode 100644 index 0000000..442aa06 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/AdtsExtractor.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS + * headers. + */ +public final class AdtsExtractor implements Extractor { + + /** + * Factory for {@link AdtsExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new AdtsExtractor()}; + } + + }; + + private static final int MAX_PACKET_SIZE = 200; + private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); + /** + * The maximum number of bytes to search when sniffing, excluding the header, before giving up. + * Frame sizes are represented by 13-bit fields, so expect a valid frame in the first 8192 bytes. + */ + private static final int MAX_SNIFF_BYTES = 8 * 1024; + + private final long firstSampleTimestampUs; + private final ParsableByteArray packetBuffer; + + // Accessed only by the loading thread. + private AdtsReader reader; + private boolean startedPacket; + + public AdtsExtractor() { + this(0); + } + + public AdtsExtractor(long firstSampleTimestampUs) { + this.firstSampleTimestampUs = firstSampleTimestampUs; + packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + // Skip any ID3 headers. + ParsableByteArray scratch = new ParsableByteArray(10); + ParsableBitArray scratchBits = new ParsableBitArray(scratch.data); + int startPosition = 0; + while (true) { + input.peekFully(scratch.data, 0, 10); + scratch.setPosition(0); + if (scratch.readUnsignedInt24() != ID3_TAG) { + break; + } + scratch.skipBytes(3); + int length = scratch.readSynchSafeInt(); + startPosition += 10 + length; + input.advancePeekPosition(length); + } + input.resetPeekPosition(); + input.advancePeekPosition(startPosition); + + // Try to find four or more consecutive AAC audio frames, exceeding the MPEG TS packet size. + int headerPosition = startPosition; + int validFramesSize = 0; + int validFramesCount = 0; + while (true) { + input.peekFully(scratch.data, 0, 2); + scratch.setPosition(0); + int syncBytes = scratch.readUnsignedShort(); + if ((syncBytes & 0xFFF6) != 0xFFF0) { + validFramesCount = 0; + validFramesSize = 0; + input.resetPeekPosition(); + if (++headerPosition - startPosition >= MAX_SNIFF_BYTES) { + return false; + } + input.advancePeekPosition(headerPosition); + } else { + if (++validFramesCount >= 4 && validFramesSize > 188) { + return true; + } + + // Skip the frame. + input.peekFully(scratch.data, 0, 4); + scratchBits.setPosition(14); + int frameSize = scratchBits.readBits(13); + // Either the stream is malformed OR we're not parsing an ADTS stream. + if (frameSize <= 6) { + return false; + } + input.advancePeekPosition(frameSize - 6); + validFramesSize += frameSize; + } + } + } + + @Override + public void init(ExtractorOutput output) { + reader = new AdtsReader(true); + reader.createTracks(output, new TrackIdGenerator(0, 1)); + output.endTracks(); + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position, long timeUs) { + startedPacket = false; + reader.seek(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE); + if (bytesRead == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + + // Feed whatever data we have to the reader, regardless of whether the read finished or not. + packetBuffer.setPosition(0); + packetBuffer.setLimit(bytesRead); + + if (!startedPacket) { + // Pass data to the reader as though it's contained within a single infinitely long packet. + reader.packetStarted(firstSampleTimestampUs, true); + startedPacket = true; + } + // TODO: Make it possible for reader to consume the dataSource directly, so that it becomes + // unnecessary to copy the data through packetBuffer. + reader.consume(packetBuffer); + return RESULT_CONTINUE; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/AdtsReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/AdtsReader.java new file mode 100644 index 0000000..a2d4760 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/AdtsReader.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.util.Log; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DummyTrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.CodecSpecificDataUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.Arrays; +import java.util.Collections; + +/** + * Parses a continuous ADTS byte stream and extracts individual frames. + */ +public final class AdtsReader implements ElementaryStreamReader { + + private static final String TAG = "AdtsReader"; + + private static final int STATE_FINDING_SAMPLE = 0; + private static final int STATE_READING_ID3_HEADER = 1; + private static final int STATE_READING_ADTS_HEADER = 2; + private static final int STATE_READING_SAMPLE = 3; + + private static final int HEADER_SIZE = 5; + private static final int CRC_SIZE = 2; + + // Match states used while looking for the next sample + private static final int MATCH_STATE_VALUE_SHIFT = 8; + private static final int MATCH_STATE_START = 1 << MATCH_STATE_VALUE_SHIFT; + private static final int MATCH_STATE_FF = 2 << MATCH_STATE_VALUE_SHIFT; + private static final int MATCH_STATE_I = 3 << MATCH_STATE_VALUE_SHIFT; + private static final int MATCH_STATE_ID = 4 << MATCH_STATE_VALUE_SHIFT; + + private static final int ID3_HEADER_SIZE = 10; + private static final int ID3_SIZE_OFFSET = 6; + private static final byte[] ID3_IDENTIFIER = {'I', 'D', '3'}; + + private final boolean exposeId3; + private final ParsableBitArray adtsScratch; + private final ParsableByteArray id3HeaderBuffer; + private final String language; + + private String formatId; + private TrackOutput output; + private TrackOutput id3Output; + + private int state; + private int bytesRead; + + private int matchState; + + private boolean hasCrc; + + // Used when parsing the header. + private boolean hasOutputFormat; + private long sampleDurationUs; + private int sampleSize; + + // Used when reading the samples. + private long timeUs; + + private TrackOutput currentOutput; + private long currentSampleDuration; + + /** + * @param exposeId3 True if the reader should expose ID3 information. + */ + public AdtsReader(boolean exposeId3) { + this(exposeId3, null); + } + + /** + * @param exposeId3 True if the reader should expose ID3 information. + * @param language Track language. + */ + public AdtsReader(boolean exposeId3, String language) { + adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); + id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE)); + setFindingSampleState(); + this.exposeId3 = exposeId3; + this.language = language; + } + + @Override + public void seek() { + setFindingSampleState(); + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + formatId = idGenerator.getFormatId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO); + if (exposeId3) { + idGenerator.generateNewId(); + id3Output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA); + id3Output.format(Format.createSampleFormat(idGenerator.getFormatId(), + MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, null)); + } else { + id3Output = new DummyTrackOutput(); + } + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + timeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_SAMPLE: + findNextSample(data); + break; + case STATE_READING_ID3_HEADER: + if (continueRead(data, id3HeaderBuffer.data, ID3_HEADER_SIZE)) { + parseId3Header(); + } + break; + case STATE_READING_ADTS_HEADER: + int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; + if (continueRead(data, adtsScratch.data, targetLength)) { + parseAdtsHeader(); + } + break; + case STATE_READING_SAMPLE: + readSample(data); + break; + } + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + /** + * Continues a read from the provided {@code source} into a given {@code target}. It's assumed + * that the data should be written into {@code target} starting from an offset of zero. + * + * @param source The source from which to read. + * @param target The target into which data is to be read. + * @param targetLength The target length of the read. + * @return Whether the target length was reached. + */ + private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { + int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); + source.readBytes(target, bytesRead, bytesToRead); + bytesRead += bytesToRead; + return bytesRead == targetLength; + } + + /** + * Sets the state to STATE_FINDING_SAMPLE. + */ + private void setFindingSampleState() { + state = STATE_FINDING_SAMPLE; + bytesRead = 0; + matchState = MATCH_STATE_START; + } + + /** + * Sets the state to STATE_READING_ID3_HEADER and resets the fields required for + * {@link #parseId3Header()}. + */ + private void setReadingId3HeaderState() { + state = STATE_READING_ID3_HEADER; + bytesRead = ID3_IDENTIFIER.length; + sampleSize = 0; + id3HeaderBuffer.setPosition(0); + } + + /** + * Sets the state to STATE_READING_SAMPLE. + * + * @param outputToUse TrackOutput object to write the sample to + * @param currentSampleDuration Duration of the sample to be read + * @param priorReadBytes Size of prior read bytes + * @param sampleSize Size of the sample + */ + private void setReadingSampleState(TrackOutput outputToUse, long currentSampleDuration, + int priorReadBytes, int sampleSize) { + state = STATE_READING_SAMPLE; + bytesRead = priorReadBytes; + this.currentOutput = outputToUse; + this.currentSampleDuration = currentSampleDuration; + this.sampleSize = sampleSize; + } + + /** + * Sets the state to STATE_READING_ADTS_HEADER. + */ + private void setReadingAdtsHeaderState() { + state = STATE_READING_ADTS_HEADER; + bytesRead = 0; + } + + /** + * Locates the next sample start, advancing the position to the byte that immediately follows + * identifier. If a sample was not located, the position is advanced to the limit. + * + * @param pesBuffer The buffer whose position should be advanced. + */ + private void findNextSample(ParsableByteArray pesBuffer) { + byte[] adtsData = pesBuffer.data; + int position = pesBuffer.getPosition(); + int endOffset = pesBuffer.limit(); + while (position < endOffset) { + int data = adtsData[position++] & 0xFF; + if (matchState == MATCH_STATE_FF && data >= 0xF0 && data != 0xFF) { + hasCrc = (data & 0x1) == 0; + setReadingAdtsHeaderState(); + pesBuffer.setPosition(position); + return; + } + switch (matchState | data) { + case MATCH_STATE_START | 0xFF: + matchState = MATCH_STATE_FF; + break; + case MATCH_STATE_START | 'I': + matchState = MATCH_STATE_I; + break; + case MATCH_STATE_I | 'D': + matchState = MATCH_STATE_ID; + break; + case MATCH_STATE_ID | '3': + setReadingId3HeaderState(); + pesBuffer.setPosition(position); + return; + default: + if (matchState != MATCH_STATE_START) { + // If matching fails in a later state, revert to MATCH_STATE_START and + // check this byte again + matchState = MATCH_STATE_START; + position--; + } + break; + } + } + pesBuffer.setPosition(position); + } + + /** + * Parses the Id3 header. + */ + private void parseId3Header() { + id3Output.sampleData(id3HeaderBuffer, ID3_HEADER_SIZE); + id3HeaderBuffer.setPosition(ID3_SIZE_OFFSET); + setReadingSampleState(id3Output, 0, ID3_HEADER_SIZE, + id3HeaderBuffer.readSynchSafeInt() + ID3_HEADER_SIZE); + } + + /** + * Parses the sample header. + */ + private void parseAdtsHeader() { + adtsScratch.setPosition(0); + + if (!hasOutputFormat) { + int audioObjectType = adtsScratch.readBits(2) + 1; + if (audioObjectType != 2) { + // The stream indicates AAC-Main (1), AAC-SSR (3) or AAC-LTP (4). When the stream indicates + // AAC-Main it's more likely that the stream contains HE-AAC (5), which cannot be + // represented correctly in the 2 bit audio_object_type field in the ADTS header. In + // practice when the stream indicates AAC-SSR or AAC-LTP it more commonly contains AAC-LC or + // HE-AAC. Since most Android devices don't support AAC-Main, AAC-SSR or AAC-LTP, and since + // indicating AAC-LC works for HE-AAC streams, we pretend that we're dealing with AAC-LC and + // hope for the best. In practice this often works. + // See: https://github.com/google/ExoPlayer/issues/774 + // See: https://github.com/google/ExoPlayer/issues/1383 + Log.w(TAG, "Detected audio object type: " + audioObjectType + ", but assuming AAC LC."); + audioObjectType = 2; + } + + int sampleRateIndex = adtsScratch.readBits(4); + adtsScratch.skipBits(1); + int channelConfig = adtsScratch.readBits(3); + + byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig( + audioObjectType, sampleRateIndex, channelConfig); + Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( + audioSpecificConfig); + + Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null, + Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, + Collections.singletonList(audioSpecificConfig), null, 0, language); + // In this class a sample is an access unit, but the MediaFormat sample rate specifies the + // number of PCM audio samples per second. + sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate; + output.format(format); + hasOutputFormat = true; + } else { + adtsScratch.skipBits(10); + } + + adtsScratch.skipBits(4); + int sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE; + if (hasCrc) { + sampleSize -= CRC_SIZE; + } + + setReadingSampleState(output, sampleDurationUs, 0, sampleSize); + } + + /** + * Reads the rest of the sample + */ + private void readSample(ParsableByteArray data) { + int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); + currentOutput.sampleData(data, bytesToRead); + bytesRead += bytesToRead; + if (bytesRead == sampleSize) { + currentOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + timeUs += currentSampleDuration; + setFindingSampleState(); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java new file mode 100644 index 0000000..059c8c8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.support.annotation.IntDef; +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Default implementation for {@link TsPayloadReader.Factory}. + */ +public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory { + + /** + * Flags controlling elementary stream readers' behavior. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_ALLOW_NON_IDR_KEYFRAMES, FLAG_IGNORE_AAC_STREAM, + FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS, FLAG_IGNORE_SPLICE_INFO_STREAM, + FLAG_OVERRIDE_CAPTION_DESCRIPTORS}) + public @interface Flags {} + public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; + public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1; + public static final int FLAG_IGNORE_H264_STREAM = 1 << 2; + public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3; + public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4; + public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5; + + private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; + + @Flags private final int flags; + private final List closedCaptionFormats; + + public DefaultTsPayloadReaderFactory() { + this(0); + } + + /** + * @param flags A combination of {@code FLAG_*} values that control the behavior of the created + * readers. + */ + public DefaultTsPayloadReaderFactory(@Flags int flags) { + this(flags, Collections.emptyList()); + } + + /** + * @param flags A combination of {@code FLAG_*} values that control the behavior of the created + * readers. + * @param closedCaptionFormats {@link Format}s to be exposed by payload readers for streams with + * embedded closed captions when no caption service descriptors are provided. If + * {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, {@code closedCaptionFormats} overrides + * any descriptor information. If not set, and {@code closedCaptionFormats} is empty, a + * closed caption track with {@link Format#accessibilityChannel} {@link Format#NO_VALUE} will + * be exposed. + */ + public DefaultTsPayloadReaderFactory(@Flags int flags, List closedCaptionFormats) { + this.flags = flags; + if (!isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS) && closedCaptionFormats.isEmpty()) { + closedCaptionFormats = Collections.singletonList(Format.createTextSampleFormat(null, + MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)); + } + this.closedCaptionFormats = closedCaptionFormats; + } + + @Override + public SparseArray createInitialPayloadReaders() { + return new SparseArray<>(); + } + + @Override + public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { + switch (streamType) { + case TsExtractor.TS_STREAM_TYPE_MPA: + case TsExtractor.TS_STREAM_TYPE_MPA_LSF: + return new PesReader(new MpegAudioReader(esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_AAC: + return isSet(FLAG_IGNORE_AAC_STREAM) + ? null : new PesReader(new AdtsReader(false, esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_AC3: + case TsExtractor.TS_STREAM_TYPE_E_AC3: + return new PesReader(new Ac3Reader(esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_DTS: + case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: + return new PesReader(new DtsReader(esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_H262: + return new PesReader(new H262Reader()); + case TsExtractor.TS_STREAM_TYPE_H264: + return isSet(FLAG_IGNORE_H264_STREAM) ? null + : new PesReader(new H264Reader(buildSeiReader(esInfo), + isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES), isSet(FLAG_DETECT_ACCESS_UNITS))); + case TsExtractor.TS_STREAM_TYPE_H265: + return new PesReader(new H265Reader(buildSeiReader(esInfo))); + case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO: + return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM) + ? null : new SectionReader(new SpliceInfoSectionReader()); + case TsExtractor.TS_STREAM_TYPE_ID3: + return new PesReader(new Id3Reader()); + case TsExtractor.TS_STREAM_TYPE_DVBSUBS: + return new PesReader( + new DvbSubtitleReader(esInfo.dvbSubtitleInfos)); + default: + return null; + } + } + + /** + * If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link SeiReader} for + * {@link #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a + * {@link SeiReader} for the declared formats, or {@link #closedCaptionFormats} if the descriptor + * is not present. + * + * @param esInfo The {@link EsInfo} passed to {@link #createPayloadReader(int, EsInfo)}. + * @return A {@link SeiReader} for closed caption tracks. + */ + private SeiReader buildSeiReader(EsInfo esInfo) { + if (isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS)) { + return new SeiReader(closedCaptionFormats); + } + ParsableByteArray scratchDescriptorData = new ParsableByteArray(esInfo.descriptorBytes); + List closedCaptionFormats = this.closedCaptionFormats; + while (scratchDescriptorData.bytesLeft() > 0) { + int descriptorTag = scratchDescriptorData.readUnsignedByte(); + int descriptorLength = scratchDescriptorData.readUnsignedByte(); + int nextDescriptorPosition = scratchDescriptorData.getPosition() + descriptorLength; + if (descriptorTag == DESCRIPTOR_TAG_CAPTION_SERVICE) { + // Note: see ATSC A/65 for detailed information about the caption service descriptor. + closedCaptionFormats = new ArrayList<>(); + int numberOfServices = scratchDescriptorData.readUnsignedByte() & 0x1F; + for (int i = 0; i < numberOfServices; i++) { + String language = scratchDescriptorData.readString(3); + int captionTypeByte = scratchDescriptorData.readUnsignedByte(); + boolean isDigital = (captionTypeByte & 0x80) != 0; + String mimeType; + int accessibilityChannel; + if (isDigital) { + mimeType = MimeTypes.APPLICATION_CEA708; + accessibilityChannel = captionTypeByte & 0x3F; + } else { + mimeType = MimeTypes.APPLICATION_CEA608; + accessibilityChannel = 1; + } + closedCaptionFormats.add(Format.createTextSampleFormat(null, mimeType, null, + Format.NO_VALUE, 0, language, accessibilityChannel, null)); + // Skip easy_reader(1), wide_aspect_ratio(1), reserved(14). + scratchDescriptorData.skipBytes(2); + } + } else { + // Unknown descriptor. Ignore. + } + scratchDescriptorData.setPosition(nextDescriptorPosition); + } + return new SeiReader(closedCaptionFormats); + } + + private boolean isSet(@Flags int flag) { + return (flags & flag) != 0; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DtsReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DtsReader.java new file mode 100644 index 0000000..b36d68e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DtsReader.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.audio.DtsUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; + +/** + * Parses a continuous DTS byte stream and extracts individual samples. + */ +public final class DtsReader implements ElementaryStreamReader { + + private static final int STATE_FINDING_SYNC = 0; + private static final int STATE_READING_HEADER = 1; + private static final int STATE_READING_SAMPLE = 2; + + private static final int HEADER_SIZE = 15; + private static final int SYNC_VALUE = 0x7FFE8001; + private static final int SYNC_VALUE_SIZE = 4; + + private final ParsableByteArray headerScratchBytes; + private final String language; + + private String formatId; + private TrackOutput output; + + private int state; + private int bytesRead; + + // Used to find the header. + private int syncBytes; + + // Used when parsing the header. + private long sampleDurationUs; + private Format format; + private int sampleSize; + + // Used when reading the samples. + private long timeUs; + + /** + * Constructs a new reader for DTS elementary streams. + * + * @param language Track language. + */ + public DtsReader(String language) { + headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]); + headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF); + headerScratchBytes.data[1] = (byte) ((SYNC_VALUE >> 16) & 0xFF); + headerScratchBytes.data[2] = (byte) ((SYNC_VALUE >> 8) & 0xFF); + headerScratchBytes.data[3] = (byte) (SYNC_VALUE & 0xFF); + state = STATE_FINDING_SYNC; + this.language = language; + } + + @Override + public void seek() { + state = STATE_FINDING_SYNC; + bytesRead = 0; + syncBytes = 0; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + formatId = idGenerator.getFormatId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + timeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_SYNC: + if (skipToNextSync(data)) { + bytesRead = SYNC_VALUE_SIZE; + state = STATE_READING_HEADER; + } + break; + case STATE_READING_HEADER: + if (continueRead(data, headerScratchBytes.data, HEADER_SIZE)) { + parseHeader(); + headerScratchBytes.setPosition(0); + output.sampleData(headerScratchBytes, HEADER_SIZE); + state = STATE_READING_SAMPLE; + } + break; + case STATE_READING_SAMPLE: + int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); + output.sampleData(data, bytesToRead); + bytesRead += bytesToRead; + if (bytesRead == sampleSize) { + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + timeUs += sampleDurationUs; + state = STATE_FINDING_SYNC; + } + break; + } + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + /** + * Continues a read from the provided {@code source} into a given {@code target}. It's assumed + * that the data should be written into {@code target} starting from an offset of zero. + * + * @param source The source from which to read. + * @param target The target into which data is to be read. + * @param targetLength The target length of the read. + * @return Whether the target length was reached. + */ + private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { + int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); + source.readBytes(target, bytesRead, bytesToRead); + bytesRead += bytesToRead; + return bytesRead == targetLength; + } + + /** + * Locates the next SYNC value in the buffer, advancing the position to the byte that immediately + * follows it. If SYNC was not located, the position is advanced to the limit. + * + * @param pesBuffer The buffer whose position should be advanced. + * @return Whether SYNC was found. + */ + private boolean skipToNextSync(ParsableByteArray pesBuffer) { + while (pesBuffer.bytesLeft() > 0) { + syncBytes <<= 8; + syncBytes |= pesBuffer.readUnsignedByte(); + if (syncBytes == SYNC_VALUE) { + syncBytes = 0; + return true; + } + } + return false; + } + + /** + * Parses the sample header. + */ + private void parseHeader() { + byte[] frameData = headerScratchBytes.data; + if (format == null) { + format = DtsUtil.parseDtsFormat(frameData, formatId, language, null); + output.format(format); + } + sampleSize = DtsUtil.getDtsFrameSize(frameData); + // In this class a sample is an access unit (frame in DTS), but the format's sample rate + // specifies the number of PCM audio samples per second. + sampleDurationUs = (int) (C.MICROS_PER_SECOND + * DtsUtil.parseDtsAudioSampleCount(frameData) / format.sampleRate); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DvbSubtitleReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DvbSubtitleReader.java new file mode 100644 index 0000000..e3007bc --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/DvbSubtitleReader.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.DvbSubtitleInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.Collections; +import java.util.List; + +/** + * Parses DVB subtitle data and extracts individual frames. + */ +public final class DvbSubtitleReader implements ElementaryStreamReader { + + private final List subtitleInfos; + private final TrackOutput[] outputs; + + private boolean writingSample; + private int bytesToCheck; + private int sampleBytesWritten; + private long sampleTimeUs; + + /** + * @param subtitleInfos Information about the DVB subtitles associated to the stream. + */ + public DvbSubtitleReader(List subtitleInfos) { + this.subtitleInfos = subtitleInfos; + outputs = new TrackOutput[subtitleInfos.size()]; + } + + @Override + public void seek() { + writingSample = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + for (int i = 0; i < outputs.length; i++) { + DvbSubtitleInfo subtitleInfo = subtitleInfos.get(i); + idGenerator.generateNewId(); + TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), + MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, + Collections.singletonList(subtitleInfo.initializationData), subtitleInfo.language, null)); + outputs[i] = output; + } + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + if (!dataAlignmentIndicator) { + return; + } + writingSample = true; + sampleTimeUs = pesTimeUs; + sampleBytesWritten = 0; + bytesToCheck = 2; + } + + @Override + public void packetFinished() { + if (writingSample) { + for (TrackOutput output : outputs) { + output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + } + writingSample = false; + } + } + + @Override + public void consume(ParsableByteArray data) { + if (writingSample) { + if (bytesToCheck == 2 && !checkNextByte(data, 0x20)) { + // Failed to check data_identifier + return; + } + if (bytesToCheck == 1 && !checkNextByte(data, 0x00)) { + // Check and discard the subtitle_stream_id + return; + } + int dataPosition = data.getPosition(); + int bytesAvailable = data.bytesLeft(); + for (TrackOutput output : outputs) { + data.setPosition(dataPosition); + output.sampleData(data, bytesAvailable); + } + sampleBytesWritten += bytesAvailable; + } + } + + private boolean checkNextByte(ParsableByteArray data, int expectedValue) { + if (data.bytesLeft() == 0) { + return false; + } + if (data.readUnsignedByte() != expectedValue) { + writingSample = false; + } + bytesToCheck--; + return writingSample; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/ElementaryStreamReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/ElementaryStreamReader.java new file mode 100644 index 0000000..8241cb7 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/ElementaryStreamReader.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; + +/** + * Extracts individual samples from an elementary media stream, preserving original order. + */ +public interface ElementaryStreamReader { + + /** + * Notifies the reader that a seek has occurred. + */ + void seek(); + + /** + * Initializes the reader by providing outputs and ids for the tracks. + * + * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the + * {@link TrackOutput}s. + */ + void createTracks(ExtractorOutput extractorOutput, PesReader.TrackIdGenerator idGenerator); + + /** + * Called when a packet starts. + * + * @param pesTimeUs The timestamp associated with the packet. + * @param dataAlignmentIndicator The data alignment indicator associated with the packet. + */ + void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator); + + /** + * Consumes (possibly partial) data from the current packet. + * + * @param data The data to consume. + */ + void consume(ParsableByteArray data); + + /** + * Called when a packet ends. + */ + void packetFinished(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H262Reader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H262Reader.java new file mode 100644 index 0000000..1b04b9c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H262Reader.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.Arrays; +import java.util.Collections; + +/** + * Parses a continuous H262 byte stream and extracts individual frames. + */ +public final class H262Reader implements ElementaryStreamReader { + + private static final int START_PICTURE = 0x00; + private static final int START_SEQUENCE_HEADER = 0xB3; + private static final int START_EXTENSION = 0xB5; + private static final int START_GROUP = 0xB8; + + private String formatId; + private TrackOutput output; + + // Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4. + private static final double[] FRAME_RATE_VALUES = new double[] { + 24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60}; + + // State that should not be reset on seek. + private boolean hasOutputFormat; + private long frameDurationUs; + + // State that should be reset on seek. + private final boolean[] prefixFlags; + private final CsdBuffer csdBuffer; + private boolean foundFirstFrameInGroup; + private long totalBytesWritten; + + // Per packet state that gets reset at the start of each packet. + private long pesTimeUs; + private boolean pesPtsUsAvailable; + + // Per sample state that gets reset at the start of each frame. + private boolean isKeyframe; + private long framePosition; + private long frameTimeUs; + + public H262Reader() { + prefixFlags = new boolean[4]; + csdBuffer = new CsdBuffer(128); + } + + @Override + public void seek() { + NalUnitUtil.clearPrefixFlags(prefixFlags); + csdBuffer.reset(); + pesPtsUsAvailable = false; + foundFirstFrameInGroup = false; + totalBytesWritten = 0; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + formatId = idGenerator.getFormatId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + pesPtsUsAvailable = pesTimeUs != C.TIME_UNSET; + if (pesPtsUsAvailable) { + this.pesTimeUs = pesTimeUs; + } + } + + @Override + public void consume(ParsableByteArray data) { + int offset = data.getPosition(); + int limit = data.limit(); + byte[] dataArray = data.data; + + // Append the data to the buffer. + totalBytesWritten += data.bytesLeft(); + output.sampleData(data, data.bytesLeft()); + + int searchOffset = offset; + while (true) { + int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, searchOffset, limit, prefixFlags); + + if (startCodeOffset == limit) { + // We've scanned to the end of the data without finding another start code. + if (!hasOutputFormat) { + csdBuffer.onData(dataArray, offset, limit); + } + return; + } + + // We've found a start code with the following value. + int startCodeValue = data.data[startCodeOffset + 3] & 0xFF; + + if (!hasOutputFormat) { + // This is the number of bytes from the current offset to the start of the next start + // code. It may be negative if the start code started in the previously consumed data. + int lengthToStartCode = startCodeOffset - offset; + if (lengthToStartCode > 0) { + csdBuffer.onData(dataArray, offset, startCodeOffset); + } + // This is the number of bytes belonging to the next start code that have already been + // passed to csdDataTargetBuffer. + int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0; + if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) { + // The csd data is complete, so we can decode and output the media format. + Pair result = parseCsdBuffer(csdBuffer, formatId); + output.format(result.first); + frameDurationUs = result.second; + hasOutputFormat = true; + } + } + + if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) { + int bytesWrittenPastStartCode = limit - startCodeOffset; + if (foundFirstFrameInGroup) { + @C.BufferFlags int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode; + output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null); + isKeyframe = false; + } + if (startCodeValue == START_GROUP) { + foundFirstFrameInGroup = false; + isKeyframe = true; + } else /* startCodeValue == START_PICTURE */ { + frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs); + framePosition = totalBytesWritten - bytesWrittenPastStartCode; + pesPtsUsAvailable = false; + foundFirstFrameInGroup = true; + } + } + + offset = startCodeOffset; + searchOffset = offset + 3; + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + /** + * Parses the {@link Format} and frame duration from a csd buffer. + * + * @param csdBuffer The csd buffer. + * @param formatId The id for the generated format. May be null. + * @return A pair consisting of the {@link Format} and the frame duration in microseconds, or + * 0 if the duration could not be determined. + */ + private static Pair parseCsdBuffer(CsdBuffer csdBuffer, String formatId) { + byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length); + + int firstByte = csdData[4] & 0xFF; + int secondByte = csdData[5] & 0xFF; + int thirdByte = csdData[6] & 0xFF; + int width = (firstByte << 4) | (secondByte >> 4); + int height = (secondByte & 0x0F) << 8 | thirdByte; + + float pixelWidthHeightRatio = 1f; + int aspectRatioCode = (csdData[7] & 0xF0) >> 4; + switch(aspectRatioCode) { + case 2: + pixelWidthHeightRatio = (4 * height) / (float) (3 * width); + break; + case 3: + pixelWidthHeightRatio = (16 * height) / (float) (9 * width); + break; + case 4: + pixelWidthHeightRatio = (121 * height) / (float) (100 * width); + break; + default: + // Do nothing. + break; + } + + Format format = Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_MPEG2, null, + Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, + Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null); + + long frameDurationUs = 0; + int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1; + if (0 <= frameRateCodeMinusOne && frameRateCodeMinusOne < FRAME_RATE_VALUES.length) { + double frameRate = FRAME_RATE_VALUES[frameRateCodeMinusOne]; + int sequenceExtensionPosition = csdBuffer.sequenceExtensionPosition; + int frameRateExtensionN = (csdData[sequenceExtensionPosition + 9] & 0x60) >> 5; + int frameRateExtensionD = (csdData[sequenceExtensionPosition + 9] & 0x1F); + if (frameRateExtensionN != frameRateExtensionD) { + frameRate *= (frameRateExtensionN + 1d) / (frameRateExtensionD + 1); + } + frameDurationUs = (long) (C.MICROS_PER_SECOND / frameRate); + } + + return Pair.create(format, frameDurationUs); + } + + private static final class CsdBuffer { + + private boolean isFilling; + + public int length; + public int sequenceExtensionPosition; + public byte[] data; + + public CsdBuffer(int initialCapacity) { + data = new byte[initialCapacity]; + } + + /** + * Resets the buffer, clearing any data that it holds. + */ + public void reset() { + isFilling = false; + length = 0; + sequenceExtensionPosition = 0; + } + + /** + * Called when a start code is encountered in the stream. + * + * @param startCodeValue The start code value. + * @param bytesAlreadyPassed The number of bytes of the start code that have already been + * passed to {@link #onData(byte[], int, int)}, or 0. + * @return Whether the csd data is now complete. If true is returned, neither + * this method or {@link #onData(byte[], int, int)} should be called again without an + * interleaving call to {@link #reset()}. + */ + public boolean onStartCode(int startCodeValue, int bytesAlreadyPassed) { + if (isFilling) { + if (sequenceExtensionPosition == 0 && startCodeValue == START_EXTENSION) { + sequenceExtensionPosition = length; + } else { + length -= bytesAlreadyPassed; + isFilling = false; + return true; + } + } else if (startCodeValue == START_SEQUENCE_HEADER) { + isFilling = true; + } + return false; + } + + /** + * Called to pass stream data. + * + * @param newData Holds the data being passed. + * @param offset The offset of the data in {@code data}. + * @param limit The limit (exclusive) of the data in {@code data}. + */ + public void onData(byte[] newData, int offset, int limit) { + if (!isFilling) { + return; + } + int readLength = limit - offset; + if (data.length < length + readLength) { + data = Arrays.copyOf(data, (length + readLength) * 2); + } + System.arraycopy(newData, offset, data, length, readLength); + length += readLength; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H264Reader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H264Reader.java new file mode 100644 index 0000000..e6cab8a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H264Reader.java @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil.SpsData; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableNalUnitBitArray; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Parses a continuous H264 byte stream and extracts individual frames. + */ +public final class H264Reader implements ElementaryStreamReader { + + private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information + private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set + private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set + + private final SeiReader seiReader; + private final boolean allowNonIdrKeyframes; + private final boolean detectAccessUnits; + private final NalUnitTargetBuffer sps; + private final NalUnitTargetBuffer pps; + private final NalUnitTargetBuffer sei; + private long totalBytesWritten; + private final boolean[] prefixFlags; + + private String formatId; + private TrackOutput output; + private SampleReader sampleReader; + + // State that should not be reset on seek. + private boolean hasOutputFormat; + + // Per packet state that gets reset at the start of each packet. + private long pesTimeUs; + + // Scratch variables to avoid allocations. + private final ParsableByteArray seiWrapper; + + /** + * @param seiReader An SEI reader for consuming closed caption channels. + * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as + * synchronization samples (key-frames). + * @param detectAccessUnits Whether to split the input stream into access units (samples) based on + * slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs). + */ + public H264Reader(SeiReader seiReader, boolean allowNonIdrKeyframes, boolean detectAccessUnits) { + this.seiReader = seiReader; + this.allowNonIdrKeyframes = allowNonIdrKeyframes; + this.detectAccessUnits = detectAccessUnits; + prefixFlags = new boolean[3]; + sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); + pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); + sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); + seiWrapper = new ParsableByteArray(); + } + + @Override + public void seek() { + NalUnitUtil.clearPrefixFlags(prefixFlags); + sps.reset(); + pps.reset(); + sei.reset(); + sampleReader.reset(); + totalBytesWritten = 0; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + formatId = idGenerator.getFormatId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO); + sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits); + seiReader.createTracks(extractorOutput, idGenerator); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + this.pesTimeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + int offset = data.getPosition(); + int limit = data.limit(); + byte[] dataArray = data.data; + + // Append the data to the buffer. + totalBytesWritten += data.bytesLeft(); + output.sampleData(data, data.bytesLeft()); + + // Scan the appended data, processing NAL units as they are encountered + while (true) { + int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); + + if (nalUnitOffset == limit) { + // We've scanned to the end of the data without finding the start of another NAL unit. + nalUnitData(dataArray, offset, limit); + return; + } + + // We've seen the start of a NAL unit of the following type. + int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nalUnitOffset); + + // This is the number of bytes from the current offset to the start of the next NAL unit. + // It may be negative if the NAL unit started in the previously consumed data. + int lengthToNalUnit = nalUnitOffset - offset; + if (lengthToNalUnit > 0) { + nalUnitData(dataArray, offset, nalUnitOffset); + } + int bytesWrittenPastPosition = limit - nalUnitOffset; + long absolutePosition = totalBytesWritten - bytesWrittenPastPosition; + // Indicate the end of the previous NAL unit. If the length to the start of the next unit + // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes + // when notifying that the unit has ended. + endNalUnit(absolutePosition, bytesWrittenPastPosition, + lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs); + // Indicate the start of the next NAL unit. + startNalUnit(absolutePosition, nalUnitType, pesTimeUs); + // Continue scanning the data. + offset = nalUnitOffset + 3; + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + private void startNalUnit(long position, int nalUnitType, long pesTimeUs) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { + sps.startNalUnit(nalUnitType); + pps.startNalUnit(nalUnitType); + } + sei.startNalUnit(nalUnitType); + sampleReader.startNalUnit(position, nalUnitType, pesTimeUs); + } + + private void nalUnitData(byte[] dataArray, int offset, int limit) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { + sps.appendToNalUnit(dataArray, offset, limit); + pps.appendToNalUnit(dataArray, offset, limit); + } + sei.appendToNalUnit(dataArray, offset, limit); + sampleReader.appendToNalUnit(dataArray, offset, limit); + } + + private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { + sps.endNalUnit(discardPadding); + pps.endNalUnit(discardPadding); + if (!hasOutputFormat) { + if (sps.isCompleted() && pps.isCompleted()) { + List initializationData = new ArrayList<>(); + initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength)); + initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength)); + NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength); + NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength); + output.format(Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H264, null, + Format.NO_VALUE, Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE, + initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null)); + hasOutputFormat = true; + sampleReader.putSps(spsData); + sampleReader.putPps(ppsData); + sps.reset(); + pps.reset(); + } + } else if (sps.isCompleted()) { + NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength); + sampleReader.putSps(spsData); + sps.reset(); + } else if (pps.isCompleted()) { + NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength); + sampleReader.putPps(ppsData); + pps.reset(); + } + } + if (sei.endNalUnit(discardPadding)) { + int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength); + seiWrapper.reset(sei.nalData, unescapedLength); + seiWrapper.setPosition(4); // NAL prefix and nal_unit() header. + seiReader.consume(pesTimeUs, seiWrapper); + } + sampleReader.endNalUnit(position, offset); + } + + /** + * Consumes a stream of NAL units and outputs samples. + */ + private static final class SampleReader { + + private static final int DEFAULT_BUFFER_SIZE = 128; + + private static final int NAL_UNIT_TYPE_NON_IDR = 1; // Coded slice of a non-IDR picture + private static final int NAL_UNIT_TYPE_PARTITION_A = 2; // Coded slice data partition A + private static final int NAL_UNIT_TYPE_IDR = 5; // Coded slice of an IDR picture + private static final int NAL_UNIT_TYPE_AUD = 9; // Access unit delimiter + + private final TrackOutput output; + private final boolean allowNonIdrKeyframes; + private final boolean detectAccessUnits; + private final SparseArray sps; + private final SparseArray pps; + private final ParsableNalUnitBitArray bitArray; + + private byte[] buffer; + private int bufferLength; + + // Per NAL unit state. A sample consists of one or more NAL units. + private int nalUnitType; + private long nalUnitStartPosition; + private boolean isFilling; + private long nalUnitTimeUs; + private SliceHeaderData previousSliceHeader; + private SliceHeaderData sliceHeader; + + // Per sample state that gets reset at the start of each sample. + private boolean readingSample; + private long samplePosition; + private long sampleTimeUs; + private boolean sampleIsKeyframe; + + public SampleReader(TrackOutput output, boolean allowNonIdrKeyframes, + boolean detectAccessUnits) { + this.output = output; + this.allowNonIdrKeyframes = allowNonIdrKeyframes; + this.detectAccessUnits = detectAccessUnits; + sps = new SparseArray<>(); + pps = new SparseArray<>(); + previousSliceHeader = new SliceHeaderData(); + sliceHeader = new SliceHeaderData(); + buffer = new byte[DEFAULT_BUFFER_SIZE]; + bitArray = new ParsableNalUnitBitArray(buffer, 0, 0); + reset(); + } + + public boolean needsSpsPps() { + return detectAccessUnits; + } + + public void putSps(NalUnitUtil.SpsData spsData) { + sps.append(spsData.seqParameterSetId, spsData); + } + + public void putPps(NalUnitUtil.PpsData ppsData) { + pps.append(ppsData.picParameterSetId, ppsData); + } + + public void reset() { + isFilling = false; + readingSample = false; + sliceHeader.clear(); + } + + public void startNalUnit(long position, int type, long pesTimeUs) { + nalUnitType = type; + nalUnitTimeUs = pesTimeUs; + nalUnitStartPosition = position; + if ((allowNonIdrKeyframes && nalUnitType == NAL_UNIT_TYPE_NON_IDR) + || (detectAccessUnits && (nalUnitType == NAL_UNIT_TYPE_IDR + || nalUnitType == NAL_UNIT_TYPE_NON_IDR + || nalUnitType == NAL_UNIT_TYPE_PARTITION_A))) { + // Store the previous header and prepare to populate the new one. + SliceHeaderData newSliceHeader = previousSliceHeader; + previousSliceHeader = sliceHeader; + sliceHeader = newSliceHeader; + sliceHeader.clear(); + bufferLength = 0; + isFilling = true; + } + } + + /** + * Called to pass stream data. The data passed should not include the 3 byte start code. + * + * @param data Holds the data being passed. + * @param offset The offset of the data in {@code data}. + * @param limit The limit (exclusive) of the data in {@code data}. + */ + public void appendToNalUnit(byte[] data, int offset, int limit) { + if (!isFilling) { + return; + } + int readLength = limit - offset; + if (buffer.length < bufferLength + readLength) { + buffer = Arrays.copyOf(buffer, (bufferLength + readLength) * 2); + } + System.arraycopy(data, offset, buffer, bufferLength, readLength); + bufferLength += readLength; + + bitArray.reset(buffer, 0, bufferLength); + if (!bitArray.canReadBits(8)) { + return; + } + bitArray.skipBits(1); // forbidden_zero_bit + int nalRefIdc = bitArray.readBits(2); + bitArray.skipBits(5); // nal_unit_type + + // Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013) + // subsection 7.3.3. + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + bitArray.readUnsignedExpGolombCodedInt(); // first_mb_in_slice + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + int sliceType = bitArray.readUnsignedExpGolombCodedInt(); + if (!detectAccessUnits) { + // There are AUDs in the stream so the rest of the header can be ignored. + isFilling = false; + sliceHeader.setSliceType(sliceType); + return; + } + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + int picParameterSetId = bitArray.readUnsignedExpGolombCodedInt(); + if (pps.indexOfKey(picParameterSetId) < 0) { + // We have not seen the PPS yet, so don't try to decode the slice header. + isFilling = false; + return; + } + NalUnitUtil.PpsData ppsData = pps.get(picParameterSetId); + NalUnitUtil.SpsData spsData = sps.get(ppsData.seqParameterSetId); + if (spsData.separateColorPlaneFlag) { + if (!bitArray.canReadBits(2)) { + return; + } + bitArray.skipBits(2); // colour_plane_id + } + if (!bitArray.canReadBits(spsData.frameNumLength)) { + return; + } + boolean fieldPicFlag = false; + boolean bottomFieldFlagPresent = false; + boolean bottomFieldFlag = false; + int frameNum = bitArray.readBits(spsData.frameNumLength); + if (!spsData.frameMbsOnlyFlag) { + if (!bitArray.canReadBits(1)) { + return; + } + fieldPicFlag = bitArray.readBit(); + if (fieldPicFlag) { + if (!bitArray.canReadBits(1)) { + return; + } + bottomFieldFlag = bitArray.readBit(); + bottomFieldFlagPresent = true; + } + } + boolean idrPicFlag = nalUnitType == NAL_UNIT_TYPE_IDR; + int idrPicId = 0; + if (idrPicFlag) { + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + idrPicId = bitArray.readUnsignedExpGolombCodedInt(); + } + int picOrderCntLsb = 0; + int deltaPicOrderCntBottom = 0; + int deltaPicOrderCnt0 = 0; + int deltaPicOrderCnt1 = 0; + if (spsData.picOrderCountType == 0) { + if (!bitArray.canReadBits(spsData.picOrderCntLsbLength)) { + return; + } + picOrderCntLsb = bitArray.readBits(spsData.picOrderCntLsbLength); + if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) { + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + deltaPicOrderCntBottom = bitArray.readSignedExpGolombCodedInt(); + } + } else if (spsData.picOrderCountType == 1 + && !spsData.deltaPicOrderAlwaysZeroFlag) { + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + deltaPicOrderCnt0 = bitArray.readSignedExpGolombCodedInt(); + if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) { + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + deltaPicOrderCnt1 = bitArray.readSignedExpGolombCodedInt(); + } + } + sliceHeader.setAll(spsData, nalRefIdc, sliceType, frameNum, picParameterSetId, fieldPicFlag, + bottomFieldFlagPresent, bottomFieldFlag, idrPicFlag, idrPicId, picOrderCntLsb, + deltaPicOrderCntBottom, deltaPicOrderCnt0, deltaPicOrderCnt1); + isFilling = false; + } + + public void endNalUnit(long position, int offset) { + if (nalUnitType == NAL_UNIT_TYPE_AUD + || (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) { + // If the NAL unit ending is the start of a new sample, output the previous one. + if (readingSample) { + int nalUnitLength = (int) (position - nalUnitStartPosition); + outputSample(offset + nalUnitLength); + } + samplePosition = nalUnitStartPosition; + sampleTimeUs = nalUnitTimeUs; + sampleIsKeyframe = false; + readingSample = true; + } + sampleIsKeyframe |= nalUnitType == NAL_UNIT_TYPE_IDR || (allowNonIdrKeyframes + && nalUnitType == NAL_UNIT_TYPE_NON_IDR && sliceHeader.isISlice()); + } + + private void outputSample(int offset) { + @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + int size = (int) (nalUnitStartPosition - samplePosition); + output.sampleMetadata(sampleTimeUs, flags, size, offset, null); + } + + private static final class SliceHeaderData { + + private static final int SLICE_TYPE_I = 2; + private static final int SLICE_TYPE_ALL_I = 7; + + private boolean isComplete; + private boolean hasSliceType; + + private SpsData spsData; + private int nalRefIdc; + private int sliceType; + private int frameNum; + private int picParameterSetId; + private boolean fieldPicFlag; + private boolean bottomFieldFlagPresent; + private boolean bottomFieldFlag; + private boolean idrPicFlag; + private int idrPicId; + private int picOrderCntLsb; + private int deltaPicOrderCntBottom; + private int deltaPicOrderCnt0; + private int deltaPicOrderCnt1; + + public void clear() { + hasSliceType = false; + isComplete = false; + } + + public void setSliceType(int sliceType) { + this.sliceType = sliceType; + hasSliceType = true; + } + + public void setAll(SpsData spsData, int nalRefIdc, int sliceType, int frameNum, + int picParameterSetId, boolean fieldPicFlag, boolean bottomFieldFlagPresent, + boolean bottomFieldFlag, boolean idrPicFlag, int idrPicId, int picOrderCntLsb, + int deltaPicOrderCntBottom, int deltaPicOrderCnt0, int deltaPicOrderCnt1) { + this.spsData = spsData; + this.nalRefIdc = nalRefIdc; + this.sliceType = sliceType; + this.frameNum = frameNum; + this.picParameterSetId = picParameterSetId; + this.fieldPicFlag = fieldPicFlag; + this.bottomFieldFlagPresent = bottomFieldFlagPresent; + this.bottomFieldFlag = bottomFieldFlag; + this.idrPicFlag = idrPicFlag; + this.idrPicId = idrPicId; + this.picOrderCntLsb = picOrderCntLsb; + this.deltaPicOrderCntBottom = deltaPicOrderCntBottom; + this.deltaPicOrderCnt0 = deltaPicOrderCnt0; + this.deltaPicOrderCnt1 = deltaPicOrderCnt1; + isComplete = true; + hasSliceType = true; + } + + public boolean isISlice() { + return hasSliceType && (sliceType == SLICE_TYPE_ALL_I || sliceType == SLICE_TYPE_I); + } + + private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) { + // See ISO 14496-10 subsection 7.4.1.2.4. + return isComplete && (!other.isComplete || frameNum != other.frameNum + || picParameterSetId != other.picParameterSetId || fieldPicFlag != other.fieldPicFlag + || (bottomFieldFlagPresent && other.bottomFieldFlagPresent + && bottomFieldFlag != other.bottomFieldFlag) + || (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0)) + || (spsData.picOrderCountType == 0 && other.spsData.picOrderCountType == 0 + && (picOrderCntLsb != other.picOrderCntLsb + || deltaPicOrderCntBottom != other.deltaPicOrderCntBottom)) + || (spsData.picOrderCountType == 1 && other.spsData.picOrderCountType == 1 + && (deltaPicOrderCnt0 != other.deltaPicOrderCnt0 + || deltaPicOrderCnt1 != other.deltaPicOrderCnt1)) + || idrPicFlag != other.idrPicFlag + || (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId)); + } + + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H265Reader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H265Reader.java new file mode 100644 index 0000000..960c3f0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/H265Reader.java @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableNalUnitBitArray; +import java.util.Collections; + +/** + * Parses a continuous H.265 byte stream and extracts individual frames. + */ +public final class H265Reader implements ElementaryStreamReader { + + private static final String TAG = "H265Reader"; + + // nal_unit_type values from H.265/HEVC (2014) Table 7-1. + private static final int RASL_R = 9; + private static final int BLA_W_LP = 16; + private static final int CRA_NUT = 21; + private static final int VPS_NUT = 32; + private static final int SPS_NUT = 33; + private static final int PPS_NUT = 34; + private static final int PREFIX_SEI_NUT = 39; + private static final int SUFFIX_SEI_NUT = 40; + + private final SeiReader seiReader; + + private String formatId; + private TrackOutput output; + private SampleReader sampleReader; + + // State that should not be reset on seek. + private boolean hasOutputFormat; + + // State that should be reset on seek. + private final boolean[] prefixFlags; + private final NalUnitTargetBuffer vps; + private final NalUnitTargetBuffer sps; + private final NalUnitTargetBuffer pps; + private final NalUnitTargetBuffer prefixSei; + private final NalUnitTargetBuffer suffixSei; // TODO: Are both needed? + private long totalBytesWritten; + + // Per packet state that gets reset at the start of each packet. + private long pesTimeUs; + + // Scratch variables to avoid allocations. + private final ParsableByteArray seiWrapper; + + /** + * @param seiReader An SEI reader for consuming closed caption channels. + */ + public H265Reader(SeiReader seiReader) { + this.seiReader = seiReader; + prefixFlags = new boolean[3]; + vps = new NalUnitTargetBuffer(VPS_NUT, 128); + sps = new NalUnitTargetBuffer(SPS_NUT, 128); + pps = new NalUnitTargetBuffer(PPS_NUT, 128); + prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128); + suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128); + seiWrapper = new ParsableByteArray(); + } + + @Override + public void seek() { + NalUnitUtil.clearPrefixFlags(prefixFlags); + vps.reset(); + sps.reset(); + pps.reset(); + prefixSei.reset(); + suffixSei.reset(); + sampleReader.reset(); + totalBytesWritten = 0; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + formatId = idGenerator.getFormatId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO); + sampleReader = new SampleReader(output); + seiReader.createTracks(extractorOutput, idGenerator); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + this.pesTimeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + while (data.bytesLeft() > 0) { + int offset = data.getPosition(); + int limit = data.limit(); + byte[] dataArray = data.data; + + // Append the data to the buffer. + totalBytesWritten += data.bytesLeft(); + output.sampleData(data, data.bytesLeft()); + + // Scan the appended data, processing NAL units as they are encountered + while (offset < limit) { + int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); + + if (nalUnitOffset == limit) { + // We've scanned to the end of the data without finding the start of another NAL unit. + nalUnitData(dataArray, offset, limit); + return; + } + + // We've seen the start of a NAL unit of the following type. + int nalUnitType = NalUnitUtil.getH265NalUnitType(dataArray, nalUnitOffset); + + // This is the number of bytes from the current offset to the start of the next NAL unit. + // It may be negative if the NAL unit started in the previously consumed data. + int lengthToNalUnit = nalUnitOffset - offset; + if (lengthToNalUnit > 0) { + nalUnitData(dataArray, offset, nalUnitOffset); + } + + int bytesWrittenPastPosition = limit - nalUnitOffset; + long absolutePosition = totalBytesWritten - bytesWrittenPastPosition; + // Indicate the end of the previous NAL unit. If the length to the start of the next unit + // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes + // when notifying that the unit has ended. + endNalUnit(absolutePosition, bytesWrittenPastPosition, + lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs); + // Indicate the start of the next NAL unit. + startNalUnit(absolutePosition, bytesWrittenPastPosition, nalUnitType, pesTimeUs); + // Continue scanning the data. + offset = nalUnitOffset + 3; + } + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + private void startNalUnit(long position, int offset, int nalUnitType, long pesTimeUs) { + if (hasOutputFormat) { + sampleReader.startNalUnit(position, offset, nalUnitType, pesTimeUs); + } else { + vps.startNalUnit(nalUnitType); + sps.startNalUnit(nalUnitType); + pps.startNalUnit(nalUnitType); + } + prefixSei.startNalUnit(nalUnitType); + suffixSei.startNalUnit(nalUnitType); + } + + private void nalUnitData(byte[] dataArray, int offset, int limit) { + if (hasOutputFormat) { + sampleReader.readNalUnitData(dataArray, offset, limit); + } else { + vps.appendToNalUnit(dataArray, offset, limit); + sps.appendToNalUnit(dataArray, offset, limit); + pps.appendToNalUnit(dataArray, offset, limit); + } + prefixSei.appendToNalUnit(dataArray, offset, limit); + suffixSei.appendToNalUnit(dataArray, offset, limit); + } + + private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) { + if (hasOutputFormat) { + sampleReader.endNalUnit(position, offset); + } else { + vps.endNalUnit(discardPadding); + sps.endNalUnit(discardPadding); + pps.endNalUnit(discardPadding); + if (vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) { + output.format(parseMediaFormat(formatId, vps, sps, pps)); + hasOutputFormat = true; + } + } + if (prefixSei.endNalUnit(discardPadding)) { + int unescapedLength = NalUnitUtil.unescapeStream(prefixSei.nalData, prefixSei.nalLength); + seiWrapper.reset(prefixSei.nalData, unescapedLength); + + // Skip the NAL prefix and type. + seiWrapper.skipBytes(5); + seiReader.consume(pesTimeUs, seiWrapper); + } + if (suffixSei.endNalUnit(discardPadding)) { + int unescapedLength = NalUnitUtil.unescapeStream(suffixSei.nalData, suffixSei.nalLength); + seiWrapper.reset(suffixSei.nalData, unescapedLength); + + // Skip the NAL prefix and type. + seiWrapper.skipBytes(5); + seiReader.consume(pesTimeUs, seiWrapper); + } + } + + private static Format parseMediaFormat(String formatId, NalUnitTargetBuffer vps, + NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) { + // Build codec-specific data. + byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength]; + System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength); + System.arraycopy(sps.nalData, 0, csd, vps.nalLength, sps.nalLength); + System.arraycopy(pps.nalData, 0, csd, vps.nalLength + sps.nalLength, pps.nalLength); + + // Parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1. + ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength); + bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id + int maxSubLayersMinus1 = bitArray.readBits(3); + bitArray.skipBits(1); // sps_temporal_id_nesting_flag + + // profile_tier_level(1, sps_max_sub_layers_minus1) + bitArray.skipBits(88); // if (profilePresentFlag) {...} + bitArray.skipBits(8); // general_level_idc + int toSkip = 0; + for (int i = 0; i < maxSubLayersMinus1; i++) { + if (bitArray.readBit()) { // sub_layer_profile_present_flag[i] + toSkip += 89; + } + if (bitArray.readBit()) { // sub_layer_level_present_flag[i] + toSkip += 8; + } + } + bitArray.skipBits(toSkip); + if (maxSubLayersMinus1 > 0) { + bitArray.skipBits(2 * (8 - maxSubLayersMinus1)); + } + + bitArray.readUnsignedExpGolombCodedInt(); // sps_seq_parameter_set_id + int chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt(); + if (chromaFormatIdc == 3) { + bitArray.skipBits(1); // separate_colour_plane_flag + } + int picWidthInLumaSamples = bitArray.readUnsignedExpGolombCodedInt(); + int picHeightInLumaSamples = bitArray.readUnsignedExpGolombCodedInt(); + if (bitArray.readBit()) { // conformance_window_flag + int confWinLeftOffset = bitArray.readUnsignedExpGolombCodedInt(); + int confWinRightOffset = bitArray.readUnsignedExpGolombCodedInt(); + int confWinTopOffset = bitArray.readUnsignedExpGolombCodedInt(); + int confWinBottomOffset = bitArray.readUnsignedExpGolombCodedInt(); + // H.265/HEVC (2014) Table 6-1 + int subWidthC = chromaFormatIdc == 1 || chromaFormatIdc == 2 ? 2 : 1; + int subHeightC = chromaFormatIdc == 1 ? 2 : 1; + picWidthInLumaSamples -= subWidthC * (confWinLeftOffset + confWinRightOffset); + picHeightInLumaSamples -= subHeightC * (confWinTopOffset + confWinBottomOffset); + } + bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8 + bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8 + int log2MaxPicOrderCntLsbMinus4 = bitArray.readUnsignedExpGolombCodedInt(); + // for (i = sps_sub_layer_ordering_info_present_flag ? 0 : sps_max_sub_layers_minus1; ...) + for (int i = bitArray.readBit() ? 0 : maxSubLayersMinus1; i <= maxSubLayersMinus1; i++) { + bitArray.readUnsignedExpGolombCodedInt(); // sps_max_dec_pic_buffering_minus1[i] + bitArray.readUnsignedExpGolombCodedInt(); // sps_max_num_reorder_pics[i] + bitArray.readUnsignedExpGolombCodedInt(); // sps_max_latency_increase_plus1[i] + } + bitArray.readUnsignedExpGolombCodedInt(); // log2_min_luma_coding_block_size_minus3 + bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_luma_coding_block_size + bitArray.readUnsignedExpGolombCodedInt(); // log2_min_luma_transform_block_size_minus2 + bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_luma_transform_block_size + bitArray.readUnsignedExpGolombCodedInt(); // max_transform_hierarchy_depth_inter + bitArray.readUnsignedExpGolombCodedInt(); // max_transform_hierarchy_depth_intra + // if (scaling_list_enabled_flag) { if (sps_scaling_list_data_present_flag) {...}} + boolean scalingListEnabled = bitArray.readBit(); + if (scalingListEnabled && bitArray.readBit()) { + skipScalingList(bitArray); + } + bitArray.skipBits(2); // amp_enabled_flag (1), sample_adaptive_offset_enabled_flag (1) + if (bitArray.readBit()) { // pcm_enabled_flag + // pcm_sample_bit_depth_luma_minus1 (4), pcm_sample_bit_depth_chroma_minus1 (4) + bitArray.skipBits(8); + bitArray.readUnsignedExpGolombCodedInt(); // log2_min_pcm_luma_coding_block_size_minus3 + bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_pcm_luma_coding_block_size + bitArray.skipBits(1); // pcm_loop_filter_disabled_flag + } + // Skips all short term reference picture sets. + skipShortTermRefPicSets(bitArray); + if (bitArray.readBit()) { // long_term_ref_pics_present_flag + // num_long_term_ref_pics_sps + for (int i = 0; i < bitArray.readUnsignedExpGolombCodedInt(); i++) { + int ltRefPicPocLsbSpsLength = log2MaxPicOrderCntLsbMinus4 + 4; + // lt_ref_pic_poc_lsb_sps[i], used_by_curr_pic_lt_sps_flag[i] + bitArray.skipBits(ltRefPicPocLsbSpsLength + 1); + } + } + bitArray.skipBits(2); // sps_temporal_mvp_enabled_flag, strong_intra_smoothing_enabled_flag + float pixelWidthHeightRatio = 1; + if (bitArray.readBit()) { // vui_parameters_present_flag + if (bitArray.readBit()) { // aspect_ratio_info_present_flag + int aspectRatioIdc = bitArray.readBits(8); + if (aspectRatioIdc == NalUnitUtil.EXTENDED_SAR) { + int sarWidth = bitArray.readBits(16); + int sarHeight = bitArray.readBits(16); + if (sarWidth != 0 && sarHeight != 0) { + pixelWidthHeightRatio = (float) sarWidth / sarHeight; + } + } else if (aspectRatioIdc < NalUnitUtil.ASPECT_RATIO_IDC_VALUES.length) { + pixelWidthHeightRatio = NalUnitUtil.ASPECT_RATIO_IDC_VALUES[aspectRatioIdc]; + } else { + Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc); + } + } + } + + return Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H265, null, Format.NO_VALUE, + Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE, + Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null); + } + + /** + * Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. + */ + private static void skipScalingList(ParsableNalUnitBitArray bitArray) { + for (int sizeId = 0; sizeId < 4; sizeId++) { + for (int matrixId = 0; matrixId < 6; matrixId += sizeId == 3 ? 3 : 1) { + if (!bitArray.readBit()) { // scaling_list_pred_mode_flag[sizeId][matrixId] + // scaling_list_pred_matrix_id_delta[sizeId][matrixId] + bitArray.readUnsignedExpGolombCodedInt(); + } else { + int coefNum = Math.min(64, 1 << (4 + (sizeId << 1))); + if (sizeId > 1) { + // scaling_list_dc_coef_minus8[sizeId - 2][matrixId] + bitArray.readSignedExpGolombCodedInt(); + } + for (int i = 0; i < coefNum; i++) { + bitArray.readSignedExpGolombCodedInt(); // scaling_list_delta_coef + } + } + } + } + } + + /** + * Reads the number of short term reference picture sets in a SPS as ue(v), then skips all of + * them. See H.265/HEVC (2014) 7.3.7. + */ + private static void skipShortTermRefPicSets(ParsableNalUnitBitArray bitArray) { + int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt(); + boolean interRefPicSetPredictionFlag = false; + int numNegativePics; + int numPositivePics; + // As this method applies in a SPS, the only element of NumDeltaPocs accessed is the previous + // one, so we just keep track of that rather than storing the whole array. + // RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1) and delta_idx_minus1 is always zero in SPS. + int previousNumDeltaPocs = 0; + for (int stRpsIdx = 0; stRpsIdx < numShortTermRefPicSets; stRpsIdx++) { + if (stRpsIdx != 0) { + interRefPicSetPredictionFlag = bitArray.readBit(); + } + if (interRefPicSetPredictionFlag) { + bitArray.skipBits(1); // delta_rps_sign + bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1 + for (int j = 0; j <= previousNumDeltaPocs; j++) { + if (bitArray.readBit()) { // used_by_curr_pic_flag[j] + bitArray.skipBits(1); // use_delta_flag[j] + } + } + } else { + numNegativePics = bitArray.readUnsignedExpGolombCodedInt(); + numPositivePics = bitArray.readUnsignedExpGolombCodedInt(); + previousNumDeltaPocs = numNegativePics + numPositivePics; + for (int i = 0; i < numNegativePics; i++) { + bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i] + bitArray.skipBits(1); // used_by_curr_pic_s0_flag[i] + } + for (int i = 0; i < numPositivePics; i++) { + bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i] + bitArray.skipBits(1); // used_by_curr_pic_s1_flag[i] + } + } + } + } + + private static final class SampleReader { + + /** + * Offset in bytes of the first_slice_segment_in_pic_flag in a NAL unit containing a + * slice_segment_layer_rbsp. + */ + private static final int FIRST_SLICE_FLAG_OFFSET = 2; + + private final TrackOutput output; + + // Per NAL unit state. A sample consists of one or more NAL units. + private long nalUnitStartPosition; + private boolean nalUnitHasKeyframeData; + private int nalUnitBytesRead; + private long nalUnitTimeUs; + private boolean lookingForFirstSliceFlag; + private boolean isFirstSlice; + private boolean isFirstParameterSet; + + // Per sample state that gets reset at the start of each sample. + private boolean readingSample; + private boolean writingParameterSets; + private long samplePosition; + private long sampleTimeUs; + private boolean sampleIsKeyframe; + + public SampleReader(TrackOutput output) { + this.output = output; + } + + public void reset() { + lookingForFirstSliceFlag = false; + isFirstSlice = false; + isFirstParameterSet = false; + readingSample = false; + writingParameterSets = false; + } + + public void startNalUnit(long position, int offset, int nalUnitType, long pesTimeUs) { + isFirstSlice = false; + isFirstParameterSet = false; + nalUnitTimeUs = pesTimeUs; + nalUnitBytesRead = 0; + nalUnitStartPosition = position; + + if (nalUnitType >= VPS_NUT) { + if (!writingParameterSets && readingSample) { + // This is a non-VCL NAL unit, so flush the previous sample. + outputSample(offset); + readingSample = false; + } + if (nalUnitType <= PPS_NUT) { + // This sample will have parameter sets at the start. + isFirstParameterSet = !writingParameterSets; + writingParameterSets = true; + } + } + + // Look for the flag if this NAL unit contains a slice_segment_layer_rbsp. + nalUnitHasKeyframeData = (nalUnitType >= BLA_W_LP && nalUnitType <= CRA_NUT); + lookingForFirstSliceFlag = nalUnitHasKeyframeData || nalUnitType <= RASL_R; + } + + public void readNalUnitData(byte[] data, int offset, int limit) { + if (lookingForFirstSliceFlag) { + int headerOffset = offset + FIRST_SLICE_FLAG_OFFSET - nalUnitBytesRead; + if (headerOffset < limit) { + isFirstSlice = (data[headerOffset] & 0x80) != 0; + lookingForFirstSliceFlag = false; + } else { + nalUnitBytesRead += limit - offset; + } + } + } + + public void endNalUnit(long position, int offset) { + if (writingParameterSets && isFirstSlice) { + // This sample has parameter sets. Reset the key-frame flag based on the first slice. + sampleIsKeyframe = nalUnitHasKeyframeData; + writingParameterSets = false; + } else if (isFirstParameterSet || isFirstSlice) { + // This NAL unit is at the start of a new sample (access unit). + if (readingSample) { + // Output the sample ending before this NAL unit. + int nalUnitLength = (int) (position - nalUnitStartPosition); + outputSample(offset + nalUnitLength); + } + samplePosition = nalUnitStartPosition; + sampleTimeUs = nalUnitTimeUs; + readingSample = true; + sampleIsKeyframe = nalUnitHasKeyframeData; + } + } + + private void outputSample(int offset) { + @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + int size = (int) (nalUnitStartPosition - samplePosition); + output.sampleMetadata(sampleTimeUs, flags, size, offset, null); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Id3Reader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Id3Reader.java new file mode 100644 index 0000000..2fe6bdd --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/Id3Reader.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; + +/** + * Parses ID3 data and extracts individual text information frames. + */ +public final class Id3Reader implements ElementaryStreamReader { + + private static final String TAG = "Id3Reader"; + + private static final int ID3_HEADER_SIZE = 10; + + private final ParsableByteArray id3Header; + + private TrackOutput output; + + // State that should be reset on seek. + private boolean writingSample; + + // Per sample state that gets reset at the start of each sample. + private long sampleTimeUs; + private int sampleSize; + private int sampleBytesRead; + + public Id3Reader() { + id3Header = new ParsableByteArray(ID3_HEADER_SIZE); + } + + @Override + public void seek() { + writingSample = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA); + output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_ID3, + null, Format.NO_VALUE, null)); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + if (!dataAlignmentIndicator) { + return; + } + writingSample = true; + sampleTimeUs = pesTimeUs; + sampleSize = 0; + sampleBytesRead = 0; + } + + @Override + public void consume(ParsableByteArray data) { + if (!writingSample) { + return; + } + int bytesAvailable = data.bytesLeft(); + if (sampleBytesRead < ID3_HEADER_SIZE) { + // We're still reading the ID3 header. + int headerBytesAvailable = Math.min(bytesAvailable, ID3_HEADER_SIZE - sampleBytesRead); + System.arraycopy(data.data, data.getPosition(), id3Header.data, sampleBytesRead, + headerBytesAvailable); + if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_SIZE) { + // We've finished reading the ID3 header. Extract the sample size. + id3Header.setPosition(0); + if ('I' != id3Header.readUnsignedByte() || 'D' != id3Header.readUnsignedByte() + || '3' != id3Header.readUnsignedByte()) { + Log.w(TAG, "Discarding invalid ID3 tag"); + writingSample = false; + return; + } + id3Header.skipBytes(3); // version (2) + flags (1) + sampleSize = ID3_HEADER_SIZE + id3Header.readSynchSafeInt(); + } + } + // Write data to the output. + int bytesToWrite = Math.min(bytesAvailable, sampleSize - sampleBytesRead); + output.sampleData(data, bytesToWrite); + sampleBytesRead += bytesToWrite; + } + + @Override + public void packetFinished() { + if (!writingSample || sampleSize == 0 || sampleBytesRead != sampleSize) { + return; + } + output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + writingSample = false; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/MpegAudioReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/MpegAudioReader.java new file mode 100644 index 0000000..1ed7513 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/MpegAudioReader.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.MpegAudioHeader; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; + +/** + * Parses a continuous MPEG Audio byte stream and extracts individual frames. + */ +public final class MpegAudioReader implements ElementaryStreamReader { + + private static final int STATE_FINDING_HEADER = 0; + private static final int STATE_READING_HEADER = 1; + private static final int STATE_READING_FRAME = 2; + + private static final int HEADER_SIZE = 4; + + private final ParsableByteArray headerScratch; + private final MpegAudioHeader header; + private final String language; + + private String formatId; + private TrackOutput output; + + private int state; + private int frameBytesRead; + private boolean hasOutputFormat; + + // Used when finding the frame header. + private boolean lastByteWasFF; + + // Parsed from the frame header. + private long frameDurationUs; + private int frameSize; + + // The timestamp to attach to the next sample in the current packet. + private long timeUs; + + public MpegAudioReader() { + this(null); + } + + public MpegAudioReader(String language) { + state = STATE_FINDING_HEADER; + // The first byte of an MPEG Audio frame header is always 0xFF. + headerScratch = new ParsableByteArray(4); + headerScratch.data[0] = (byte) 0xFF; + header = new MpegAudioHeader(); + this.language = language; + } + + @Override + public void seek() { + state = STATE_FINDING_HEADER; + frameBytesRead = 0; + lastByteWasFF = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + formatId = idGenerator.getFormatId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + timeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_HEADER: + findHeader(data); + break; + case STATE_READING_HEADER: + readHeaderRemainder(data); + break; + case STATE_READING_FRAME: + readFrameRemainder(data); + break; + } + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + /** + * Attempts to locate the start of the next frame header. + *

    + * If a frame header is located then the state is changed to {@link #STATE_READING_HEADER}, the + * first two bytes of the header are written into {@link #headerScratch}, and the position of the + * source is advanced to the byte that immediately follows these two bytes. + *

    + * If a frame header is not located then the position of the source is advanced to the limit, and + * the method should be called again with the next source to continue the search. + * + * @param source The source from which to read. + */ + private void findHeader(ParsableByteArray source) { + byte[] data = source.data; + int startOffset = source.getPosition(); + int endOffset = source.limit(); + for (int i = startOffset; i < endOffset; i++) { + boolean byteIsFF = (data[i] & 0xFF) == 0xFF; + boolean found = lastByteWasFF && (data[i] & 0xE0) == 0xE0; + lastByteWasFF = byteIsFF; + if (found) { + source.setPosition(i + 1); + // Reset lastByteWasFF for next time. + lastByteWasFF = false; + headerScratch.data[1] = data[i]; + frameBytesRead = 2; + state = STATE_READING_HEADER; + return; + } + } + source.setPosition(endOffset); + } + + /** + * Attempts to read the remaining two bytes of the frame header. + *

    + * If a frame header is read in full then the state is changed to {@link #STATE_READING_FRAME}, + * the media format is output if this has not previously occurred, the four header bytes are + * output as sample data, and the position of the source is advanced to the byte that immediately + * follows the header. + *

    + * If a frame header is read in full but cannot be parsed then the state is changed to + * {@link #STATE_READING_HEADER}. + *

    + * If a frame header is not read in full then the position of the source is advanced to the limit, + * and the method should be called again with the next source to continue the read. + * + * @param source The source from which to read. + */ + private void readHeaderRemainder(ParsableByteArray source) { + int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - frameBytesRead); + source.readBytes(headerScratch.data, frameBytesRead, bytesToRead); + frameBytesRead += bytesToRead; + if (frameBytesRead < HEADER_SIZE) { + // We haven't read the whole header yet. + return; + } + + headerScratch.setPosition(0); + boolean parsedHeader = MpegAudioHeader.populateHeader(headerScratch.readInt(), header); + if (!parsedHeader) { + // We thought we'd located a frame header, but we hadn't. + frameBytesRead = 0; + state = STATE_READING_HEADER; + return; + } + + frameSize = header.frameSize; + if (!hasOutputFormat) { + frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; + Format format = Format.createAudioSampleFormat(formatId, header.mimeType, null, + Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, + null, null, 0, language); + output.format(format); + hasOutputFormat = true; + } + + headerScratch.setPosition(0); + output.sampleData(headerScratch, HEADER_SIZE); + state = STATE_READING_FRAME; + } + + /** + * Attempts to read the remainder of the frame. + *

    + * If a frame is read in full then true is returned. The frame will have been output, and the + * position of the source will have been advanced to the byte that immediately follows the end of + * the frame. + *

    + * If a frame is not read in full then the position of the source will have been advanced to the + * limit, and the method should be called again with the next source to continue the read. + * + * @param source The source from which to read. + */ + private void readFrameRemainder(ParsableByteArray source) { + int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead); + output.sampleData(source, bytesToRead); + frameBytesRead += bytesToRead; + if (frameBytesRead < frameSize) { + // We haven't read the whole of the frame yet. + return; + } + + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, frameSize, 0, null); + timeUs += frameDurationUs; + frameBytesRead = 0; + state = STATE_FINDING_HEADER; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/NalUnitTargetBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/NalUnitTargetBuffer.java new file mode 100644 index 0000000..53636e8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/NalUnitTargetBuffer.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.Arrays; + +/** + * A buffer that fills itself with data corresponding to a specific NAL unit, as it is + * encountered in the stream. + */ +/* package */ final class NalUnitTargetBuffer { + + private final int targetType; + + private boolean isFilling; + private boolean isCompleted; + + public byte[] nalData; + public int nalLength; + + public NalUnitTargetBuffer(int targetType, int initialCapacity) { + this.targetType = targetType; + + // Initialize data with a start code in the first three bytes. + nalData = new byte[3 + initialCapacity]; + nalData[2] = 1; + } + + /** + * Resets the buffer, clearing any data that it holds. + */ + public void reset() { + isFilling = false; + isCompleted = false; + } + + /** + * Returns whether the buffer currently holds a complete NAL unit of the target type. + */ + public boolean isCompleted() { + return isCompleted; + } + + /** + * Called to indicate that a NAL unit has started. + * + * @param type The type of the NAL unit. + */ + public void startNalUnit(int type) { + Assertions.checkState(!isFilling); + isFilling = type == targetType; + if (isFilling) { + // Skip the three byte start code when writing data. + nalLength = 3; + isCompleted = false; + } + } + + /** + * Called to pass stream data. The data passed should not include the 3 byte start code. + * + * @param data Holds the data being passed. + * @param offset The offset of the data in {@code data}. + * @param limit The limit (exclusive) of the data in {@code data}. + */ + public void appendToNalUnit(byte[] data, int offset, int limit) { + if (!isFilling) { + return; + } + int readLength = limit - offset; + if (nalData.length < nalLength + readLength) { + nalData = Arrays.copyOf(nalData, (nalLength + readLength) * 2); + } + System.arraycopy(data, offset, nalData, nalLength, readLength); + nalLength += readLength; + } + + /** + * Called to indicate that a NAL unit has ended. + * + * @param discardPadding The number of excess bytes that were passed to + * {@link #appendToNalUnit(byte[], int, int)}, which should be discarded. + * @return Whether the ended NAL unit is of the target type. + */ + public boolean endNalUnit(int discardPadding) { + if (!isFilling) { + return false; + } + nalLength -= discardPadding; + isFilling = false; + isCompleted = true; + return true; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/PesReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/PesReader.java new file mode 100644 index 0000000..68c6c8b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/PesReader.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; + +/** + * Parses PES packet data and extracts samples. + */ +public final class PesReader implements TsPayloadReader { + + private static final String TAG = "PesReader"; + + private static final int STATE_FINDING_HEADER = 0; + private static final int STATE_READING_HEADER = 1; + private static final int STATE_READING_HEADER_EXTENSION = 2; + private static final int STATE_READING_BODY = 3; + + private static final int HEADER_SIZE = 9; + private static final int MAX_HEADER_EXTENSION_SIZE = 10; + private static final int PES_SCRATCH_SIZE = 10; // max(HEADER_SIZE, MAX_HEADER_EXTENSION_SIZE) + + private final ElementaryStreamReader reader; + private final ParsableBitArray pesScratch; + + private int state; + private int bytesRead; + + private TimestampAdjuster timestampAdjuster; + private boolean ptsFlag; + private boolean dtsFlag; + private boolean seenFirstDts; + private int extendedHeaderLength; + private int payloadSize; + private boolean dataAlignmentIndicator; + private long timeUs; + + public PesReader(ElementaryStreamReader reader) { + this.reader = reader; + pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); + state = STATE_FINDING_HEADER; + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + this.timestampAdjuster = timestampAdjuster; + reader.createTracks(extractorOutput, idGenerator); + } + + // TsPayloadReader implementation. + + @Override + public final void seek() { + state = STATE_FINDING_HEADER; + bytesRead = 0; + seenFirstDts = false; + reader.seek(); + } + + @Override + public final void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { + if (payloadUnitStartIndicator) { + switch (state) { + case STATE_FINDING_HEADER: + case STATE_READING_HEADER: + // Expected. + break; + case STATE_READING_HEADER_EXTENSION: + Log.w(TAG, "Unexpected start indicator reading extended header"); + break; + case STATE_READING_BODY: + // If payloadSize == -1 then the length of the previous packet was unspecified, and so + // we only know that it's finished now that we've seen the start of the next one. This + // is expected. If payloadSize != -1, then the length of the previous packet was known, + // but we didn't receive that amount of data. This is not expected. + if (payloadSize != -1) { + Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes"); + } + // Either way, notify the reader that it has now finished. + reader.packetFinished(); + break; + } + setState(STATE_READING_HEADER); + } + + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_HEADER: + data.skipBytes(data.bytesLeft()); + break; + case STATE_READING_HEADER: + if (continueRead(data, pesScratch.data, HEADER_SIZE)) { + setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER); + } + break; + case STATE_READING_HEADER_EXTENSION: + int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength); + // Read as much of the extended header as we're interested in, and skip the rest. + if (continueRead(data, pesScratch.data, readLength) + && continueRead(data, null, extendedHeaderLength)) { + parseHeaderExtension(); + reader.packetStarted(timeUs, dataAlignmentIndicator); + setState(STATE_READING_BODY); + } + break; + case STATE_READING_BODY: + readLength = data.bytesLeft(); + int padding = payloadSize == -1 ? 0 : readLength - payloadSize; + if (padding > 0) { + readLength -= padding; + data.setLimit(data.getPosition() + readLength); + } + reader.consume(data); + if (payloadSize != -1) { + payloadSize -= readLength; + if (payloadSize == 0) { + reader.packetFinished(); + setState(STATE_READING_HEADER); + } + } + break; + } + } + } + + private void setState(int state) { + this.state = state; + bytesRead = 0; + } + + /** + * Continues a read from the provided {@code source} into a given {@code target}. It's assumed + * that the data should be written into {@code target} starting from an offset of zero. + * + * @param source The source from which to read. + * @param target The target into which data is to be read, or {@code null} to skip. + * @param targetLength The target length of the read. + * @return Whether the target length has been reached. + */ + private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { + int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); + if (bytesToRead <= 0) { + return true; + } else if (target == null) { + source.skipBytes(bytesToRead); + } else { + source.readBytes(target, bytesRead, bytesToRead); + } + bytesRead += bytesToRead; + return bytesRead == targetLength; + } + + private boolean parseHeader() { + // Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of + // the header. + pesScratch.setPosition(0); + int startCodePrefix = pesScratch.readBits(24); + if (startCodePrefix != 0x000001) { + Log.w(TAG, "Unexpected start code prefix: " + startCodePrefix); + payloadSize = -1; + return false; + } + + pesScratch.skipBits(8); // stream_id. + int packetLength = pesScratch.readBits(16); + pesScratch.skipBits(5); // '10' (2), PES_scrambling_control (2), PES_priority (1) + dataAlignmentIndicator = pesScratch.readBit(); + pesScratch.skipBits(2); // copyright (1), original_or_copy (1) + ptsFlag = pesScratch.readBit(); + dtsFlag = pesScratch.readBit(); + // ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1), + // additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1) + pesScratch.skipBits(6); + extendedHeaderLength = pesScratch.readBits(8); + + if (packetLength == 0) { + payloadSize = -1; + } else { + payloadSize = packetLength + 6 /* packetLength does not include the first 6 bytes */ + - HEADER_SIZE - extendedHeaderLength; + } + return true; + } + + private void parseHeaderExtension() { + pesScratch.setPosition(0); + timeUs = C.TIME_UNSET; + if (ptsFlag) { + pesScratch.skipBits(4); // '0010' or '0011' + long pts = (long) pesScratch.readBits(3) << 30; + pesScratch.skipBits(1); // marker_bit + pts |= pesScratch.readBits(15) << 15; + pesScratch.skipBits(1); // marker_bit + pts |= pesScratch.readBits(15); + pesScratch.skipBits(1); // marker_bit + if (!seenFirstDts && dtsFlag) { + pesScratch.skipBits(4); // '0011' + long dts = (long) pesScratch.readBits(3) << 30; + pesScratch.skipBits(1); // marker_bit + dts |= pesScratch.readBits(15) << 15; + pesScratch.skipBits(1); // marker_bit + dts |= pesScratch.readBits(15); + pesScratch.skipBits(1); // marker_bit + // Subsequent PES packets may have earlier presentation timestamps than this one, but they + // should all be greater than or equal to this packet's decode timestamp. We feed the + // decode timestamp to the adjuster here so that in the case that this is the first to be + // fed, the adjuster will be able to compute an offset to apply such that the adjusted + // presentation timestamps of all future packets are non-negative. + timestampAdjuster.adjustTsTimestamp(dts); + seenFirstDts = true; + } + timeUs = timestampAdjuster.adjustTsTimestamp(pts); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/PsExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/PsExtractor.java new file mode 100644 index 0000000..d58e8fe --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/PsExtractor.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import java.io.IOException; + +/** + * Facilitates the extraction of data from the MPEG-2 TS container format. + */ +public final class PsExtractor implements Extractor { + + /** + * Factory for {@link PsExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new PsExtractor()}; + } + + }; + + private static final int PACK_START_CODE = 0x000001BA; + private static final int SYSTEM_HEADER_START_CODE = 0x000001BB; + private static final int PACKET_START_CODE_PREFIX = 0x000001; + private static final int MPEG_PROGRAM_END_CODE = 0x000001B9; + private static final int MAX_STREAM_ID_PLUS_ONE = 0x100; + private static final long MAX_SEARCH_LENGTH = 1024 * 1024; + + public static final int PRIVATE_STREAM_1 = 0xBD; + public static final int AUDIO_STREAM = 0xC0; + public static final int AUDIO_STREAM_MASK = 0xE0; + public static final int VIDEO_STREAM = 0xE0; + public static final int VIDEO_STREAM_MASK = 0xF0; + + private final TimestampAdjuster timestampAdjuster; + private final SparseArray psPayloadReaders; // Indexed by pid + private final ParsableByteArray psPacketBuffer; + private boolean foundAllTracks; + private boolean foundAudioTrack; + private boolean foundVideoTrack; + + // Accessed only by the loading thread. + private ExtractorOutput output; + + public PsExtractor() { + this(new TimestampAdjuster(0)); + } + + public PsExtractor(TimestampAdjuster timestampAdjuster) { + this.timestampAdjuster = timestampAdjuster; + psPacketBuffer = new ParsableByteArray(4096); + psPayloadReaders = new SparseArray<>(); + } + + // Extractor implementation. + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + byte[] scratch = new byte[14]; + input.peekFully(scratch, 0, 14); + + // Verify the PACK_START_CODE for the first 4 bytes + if (PACK_START_CODE != (((scratch[0] & 0xFF) << 24) | ((scratch[1] & 0xFF) << 16) + | ((scratch[2] & 0xFF) << 8) | (scratch[3] & 0xFF))) { + return false; + } + // Verify the 01xxx1xx marker on the 5th byte + if ((scratch[4] & 0xC4) != 0x44) { + return false; + } + // Verify the xxxxx1xx marker on the 7th byte + if ((scratch[6] & 0x04) != 0x04) { + return false; + } + // Verify the xxxxx1xx marker on the 9th byte + if ((scratch[8] & 0x04) != 0x04) { + return false; + } + // Verify the xxxxxxx1 marker on the 10th byte + if ((scratch[9] & 0x01) != 0x01) { + return false; + } + // Verify the xxxxxx11 marker on the 13th byte + if ((scratch[12] & 0x03) != 0x03) { + return false; + } + // Read the stuffing length from the 14th byte (last 3 bits) + int packStuffingLength = scratch[13] & 0x07; + input.advancePeekPosition(packStuffingLength); + // Now check that the next 3 bytes are the beginning of an MPEG start code + input.peekFully(scratch, 0, 3); + return (PACKET_START_CODE_PREFIX == (((scratch[0] & 0xFF) << 16) | ((scratch[1] & 0xFF) << 8) + | (scratch[2] & 0xFF))); + } + + @Override + public void init(ExtractorOutput output) { + this.output = output; + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position, long timeUs) { + timestampAdjuster.reset(); + for (int i = 0; i < psPayloadReaders.size(); i++) { + psPayloadReaders.valueAt(i).seek(); + } + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + // First peek and check what type of start code is next. + if (!input.peekFully(psPacketBuffer.data, 0, 4, true)) { + return RESULT_END_OF_INPUT; + } + + psPacketBuffer.setPosition(0); + int nextStartCode = psPacketBuffer.readInt(); + if (nextStartCode == MPEG_PROGRAM_END_CODE) { + return RESULT_END_OF_INPUT; + } else if (nextStartCode == PACK_START_CODE) { + // Now peek the rest of the pack_header. + input.peekFully(psPacketBuffer.data, 0, 10); + + // We only care about the pack_stuffing_length in here, skip the first 77 bits. + psPacketBuffer.setPosition(9); + + // Last 3 bits is the length. + int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07; + + // Now skip the stuffing and the pack header. + input.skipFully(packStuffingLength + 14); + return RESULT_CONTINUE; + } else if (nextStartCode == SYSTEM_HEADER_START_CODE) { + // We just skip all this, but we need to get the length first. + input.peekFully(psPacketBuffer.data, 0, 2); + + // Length is the next 2 bytes. + psPacketBuffer.setPosition(0); + int systemHeaderLength = psPacketBuffer.readUnsignedShort(); + input.skipFully(systemHeaderLength + 6); + return RESULT_CONTINUE; + } else if (((nextStartCode & 0xFFFFFF00) >> 8) != PACKET_START_CODE_PREFIX) { + input.skipFully(1); // Skip bytes until we see a valid start code again. + return RESULT_CONTINUE; + } + + // We're at the start of a regular PES packet now. + // Get the stream ID off the last byte of the start code. + int streamId = nextStartCode & 0xFF; + + // Check to see if we have this one in our map yet, and if not, then add it. + PesReader payloadReader = psPayloadReaders.get(streamId); + if (!foundAllTracks) { + if (payloadReader == null) { + ElementaryStreamReader elementaryStreamReader = null; + if (!foundAudioTrack && streamId == PRIVATE_STREAM_1) { + // Private stream, used for AC3 audio. + // NOTE: This may need further parsing to determine if its DTS, but that's likely only + // valid for DVDs. + elementaryStreamReader = new Ac3Reader(); + foundAudioTrack = true; + } else if (!foundAudioTrack && (streamId & AUDIO_STREAM_MASK) == AUDIO_STREAM) { + elementaryStreamReader = new MpegAudioReader(); + foundAudioTrack = true; + } else if (!foundVideoTrack && (streamId & VIDEO_STREAM_MASK) == VIDEO_STREAM) { + elementaryStreamReader = new H262Reader(); + foundVideoTrack = true; + } + if (elementaryStreamReader != null) { + TrackIdGenerator idGenerator = new TrackIdGenerator(streamId, MAX_STREAM_ID_PLUS_ONE); + elementaryStreamReader.createTracks(output, idGenerator); + payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster); + psPayloadReaders.put(streamId, payloadReader); + } + } + if ((foundAudioTrack && foundVideoTrack) || input.getPosition() > MAX_SEARCH_LENGTH) { + foundAllTracks = true; + output.endTracks(); + } + } + + // The next 2 bytes are the length. Once we have that we can consume the complete packet. + input.peekFully(psPacketBuffer.data, 0, 2); + psPacketBuffer.setPosition(0); + int payloadLength = psPacketBuffer.readUnsignedShort(); + int pesLength = payloadLength + 6; + + if (payloadReader == null) { + // Just skip this data. + input.skipFully(pesLength); + } else { + psPacketBuffer.reset(pesLength); + // Read the whole packet and the header for consumption. + input.readFully(psPacketBuffer.data, 0, pesLength); + psPacketBuffer.setPosition(6); + payloadReader.consume(psPacketBuffer); + psPacketBuffer.setLimit(psPacketBuffer.capacity()); + } + + return RESULT_CONTINUE; + } + + // Internals. + + /** + * Parses PES packet data and extracts samples. + */ + private static final class PesReader { + + private static final int PES_SCRATCH_SIZE = 64; + + private final ElementaryStreamReader pesPayloadReader; + private final TimestampAdjuster timestampAdjuster; + private final ParsableBitArray pesScratch; + + private boolean ptsFlag; + private boolean dtsFlag; + private boolean seenFirstDts; + private int extendedHeaderLength; + private long timeUs; + + public PesReader(ElementaryStreamReader pesPayloadReader, TimestampAdjuster timestampAdjuster) { + this.pesPayloadReader = pesPayloadReader; + this.timestampAdjuster = timestampAdjuster; + pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); + } + + /** + * Notifies the reader that a seek has occurred. + *

    + * Following a call to this method, the data passed to the next invocation of + * {@link #consume(ParsableByteArray)} will not be a continuation of the data that was + * previously passed. Hence the reader should reset any internal state. + */ + public void seek() { + seenFirstDts = false; + pesPayloadReader.seek(); + } + + /** + * Consumes the payload of a PS packet. + * + * @param data The PES packet. The position will be set to the start of the payload. + */ + public void consume(ParsableByteArray data) { + data.readBytes(pesScratch.data, 0, 3); + pesScratch.setPosition(0); + parseHeader(); + data.readBytes(pesScratch.data, 0, extendedHeaderLength); + pesScratch.setPosition(0); + parseHeaderExtension(); + pesPayloadReader.packetStarted(timeUs, true); + pesPayloadReader.consume(data); + // We always have complete PES packets with program stream. + pesPayloadReader.packetFinished(); + } + + private void parseHeader() { + // Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of + // the header. + // First 8 bits are skipped: '10' (2), PES_scrambling_control (2), PES_priority (1), + // data_alignment_indicator (1), copyright (1), original_or_copy (1) + pesScratch.skipBits(8); + ptsFlag = pesScratch.readBit(); + dtsFlag = pesScratch.readBit(); + // ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1), + // additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1) + pesScratch.skipBits(6); + extendedHeaderLength = pesScratch.readBits(8); + } + + private void parseHeaderExtension() { + timeUs = 0; + if (ptsFlag) { + pesScratch.skipBits(4); // '0010' or '0011' + long pts = (long) pesScratch.readBits(3) << 30; + pesScratch.skipBits(1); // marker_bit + pts |= pesScratch.readBits(15) << 15; + pesScratch.skipBits(1); // marker_bit + pts |= pesScratch.readBits(15); + pesScratch.skipBits(1); // marker_bit + if (!seenFirstDts && dtsFlag) { + pesScratch.skipBits(4); // '0011' + long dts = (long) pesScratch.readBits(3) << 30; + pesScratch.skipBits(1); // marker_bit + dts |= pesScratch.readBits(15) << 15; + pesScratch.skipBits(1); // marker_bit + dts |= pesScratch.readBits(15); + pesScratch.skipBits(1); // marker_bit + // Subsequent PES packets may have earlier presentation timestamps than this one, but they + // should all be greater than or equal to this packet's decode timestamp. We feed the + // decode timestamp to the adjuster here so that in the case that this is the first to be + // fed, the adjuster will be able to compute an offset to apply such that the adjusted + // presentation timestamps of all future packets are non-negative. + timestampAdjuster.adjustTsTimestamp(dts); + seenFirstDts = true; + } + timeUs = timestampAdjuster.adjustTsTimestamp(pts); + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SectionPayloadReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SectionPayloadReader.java new file mode 100644 index 0000000..49fbf36 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SectionPayloadReader.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; + +/** + * Reads section data. + */ +public interface SectionPayloadReader { + + /** + * Initializes the section payload reader. + * + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the + * {@link TrackOutput}s. + */ + void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator); + + /** + * Called by a {@link SectionReader} when a full section is received. + * + * @param sectionData The data belonging to a section starting from the table_id. If + * section_syntax_indicator is set to '1', {@code sectionData} excludes the CRC_32 field. + * Otherwise, all bytes belonging to the table section are included. + */ + void consume(ParsableByteArray sectionData); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SectionReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SectionReader.java new file mode 100644 index 0000000..c2f5143 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SectionReader.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Reads section data packets and feeds the whole sections to a given {@link SectionPayloadReader}. + * Useful information on PSI sections can be found in ISO/IEC 13818-1, section 2.4.4. + */ +public final class SectionReader implements TsPayloadReader { + + private static final int SECTION_HEADER_LENGTH = 3; + private static final int DEFAULT_SECTION_BUFFER_LENGTH = 32; + private static final int MAX_SECTION_LENGTH = 4098; + + private final SectionPayloadReader reader; + private final ParsableByteArray sectionData; + + private int totalSectionLength; + private int bytesRead; + private boolean sectionSyntaxIndicator; + private boolean waitingForPayloadStart; + + public SectionReader(SectionPayloadReader reader) { + this.reader = reader; + sectionData = new ParsableByteArray(DEFAULT_SECTION_BUFFER_LENGTH); + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + reader.init(timestampAdjuster, extractorOutput, idGenerator); + waitingForPayloadStart = true; + } + + @Override + public void seek() { + waitingForPayloadStart = true; + } + + @Override + public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { + int payloadStartPosition = C.POSITION_UNSET; + if (payloadUnitStartIndicator) { + int payloadStartOffset = data.readUnsignedByte(); + payloadStartPosition = data.getPosition() + payloadStartOffset; + } + + if (waitingForPayloadStart) { + if (!payloadUnitStartIndicator) { + return; + } + waitingForPayloadStart = false; + data.setPosition(payloadStartPosition); + bytesRead = 0; + } + + while (data.bytesLeft() > 0) { + if (bytesRead < SECTION_HEADER_LENGTH) { + // Note: see ISO/IEC 13818-1, section 2.4.4.3 for detailed information on the format of + // the header. + if (bytesRead == 0) { + int tableId = data.readUnsignedByte(); + data.setPosition(data.getPosition() - 1); + if (tableId == 0xFF /* forbidden value */) { + // No more sections in this ts packet. + waitingForPayloadStart = true; + return; + } + } + int headerBytesToRead = Math.min(data.bytesLeft(), SECTION_HEADER_LENGTH - bytesRead); + data.readBytes(sectionData.data, bytesRead, headerBytesToRead); + bytesRead += headerBytesToRead; + if (bytesRead == SECTION_HEADER_LENGTH) { + sectionData.reset(SECTION_HEADER_LENGTH); + sectionData.skipBytes(1); // Skip table id (8). + int secondHeaderByte = sectionData.readUnsignedByte(); + int thirdHeaderByte = sectionData.readUnsignedByte(); + sectionSyntaxIndicator = (secondHeaderByte & 0x80) != 0; + totalSectionLength = + (((secondHeaderByte & 0x0F) << 8) | thirdHeaderByte) + SECTION_HEADER_LENGTH; + if (sectionData.capacity() < totalSectionLength) { + // Ensure there is enough space to keep the whole section. + byte[] bytes = sectionData.data; + sectionData.reset( + Math.min(MAX_SECTION_LENGTH, Math.max(totalSectionLength, bytes.length * 2))); + System.arraycopy(bytes, 0, sectionData.data, 0, SECTION_HEADER_LENGTH); + } + } + } else { + // Reading the body. + int bodyBytesToRead = Math.min(data.bytesLeft(), totalSectionLength - bytesRead); + data.readBytes(sectionData.data, bytesRead, bodyBytesToRead); + bytesRead += bodyBytesToRead; + if (bytesRead == totalSectionLength) { + if (sectionSyntaxIndicator) { + // This section has common syntax as defined in ISO/IEC 13818-1, section 2.4.4.11. + if (Util.crc(sectionData.data, 0, totalSectionLength, 0xFFFFFFFF) != 0) { + // The CRC is invalid so discard the section. + waitingForPayloadStart = true; + return; + } + sectionData.reset(totalSectionLength - 4); // Exclude the CRC_32 field. + } else { + // This is a private section with private defined syntax. + sectionData.reset(totalSectionLength); + } + reader.consume(sectionData); + bytesRead = 0; + } + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SeiReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SeiReader.java new file mode 100644 index 0000000..5d2ab13 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SeiReader.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.text.cea.CeaUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.List; + +/** + * Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}. + */ +/* package */ final class SeiReader { + + private final List closedCaptionFormats; + private final TrackOutput[] outputs; + + /** + * @param closedCaptionFormats A list of formats for the closed caption channels to expose. + */ + public SeiReader(List closedCaptionFormats) { + this.closedCaptionFormats = closedCaptionFormats; + outputs = new TrackOutput[closedCaptionFormats.size()]; + } + + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + for (int i = 0; i < outputs.length; i++) { + idGenerator.generateNewId(); + TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + Format channelFormat = closedCaptionFormats.get(i); + String channelMimeType = channelFormat.sampleMimeType; + Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType) + || MimeTypes.APPLICATION_CEA708.equals(channelMimeType), + "Invalid closed caption mime type provided: " + channelMimeType); + output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), channelMimeType, null, + Format.NO_VALUE, channelFormat.selectionFlags, channelFormat.language, + channelFormat.accessibilityChannel, null)); + outputs[i] = output; + } + } + + public void consume(long pesTimeUs, ParsableByteArray seiBuffer) { + CeaUtil.consume(pesTimeUs, seiBuffer, outputs); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SpliceInfoSectionReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SpliceInfoSectionReader.java new file mode 100644 index 0000000..4bf2d4f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/SpliceInfoSectionReader.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; + +/** + * Parses splice info sections as defined by SCTE35. + */ +public final class SpliceInfoSectionReader implements SectionPayloadReader { + + private TimestampAdjuster timestampAdjuster; + private TrackOutput output; + private boolean formatDeclared; + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TsPayloadReader.TrackIdGenerator idGenerator) { + this.timestampAdjuster = timestampAdjuster; + idGenerator.generateNewId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA); + output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_SCTE35, + null, Format.NO_VALUE, null)); + } + + @Override + public void consume(ParsableByteArray sectionData) { + if (!formatDeclared) { + if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) { + // There is not enough information to initialize the timestamp adjuster. + return; + } + output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, + timestampAdjuster.getTimestampOffsetUs())); + formatDeclared = true; + } + int sampleSize = sectionData.bytesLeft(); + output.sampleData(sectionData, sampleSize); + output.sampleMetadata(timestampAdjuster.getLastAdjustedTimestampUs(), C.BUFFER_FLAG_KEY_FRAME, + sampleSize, 0, null); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/TsExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/TsExtractor.java new file mode 100644 index 0000000..f3e43d2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/TsExtractor.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.support.annotation.IntDef; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.Flags; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.DvbSubtitleInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Facilitates the extraction of data from the MPEG-2 TS container format. + */ +public final class TsExtractor implements Extractor { + + /** + * Factory for {@link TsExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new TsExtractor()}; + } + + }; + + /** + * Modes for the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({MODE_NORMAL, MODE_SINGLE_PMT, MODE_HLS}) + public @interface Mode {} + + /** + * Behave as defined in ISO/IEC 13818-1. + */ + public static final int MODE_NORMAL = 0; + /** + * Assume only one PMT will be contained in the stream, even if more are declared by the PAT. + */ + public static final int MODE_SINGLE_PMT = 1; + /** + * Enable single PMT mode, map {@link TrackOutput}s by their type (instead of PID) and ignore + * continuity counters. + */ + public static final int MODE_HLS = 2; + + public static final int TS_STREAM_TYPE_MPA = 0x03; + public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; + public static final int TS_STREAM_TYPE_AAC = 0x0F; + public static final int TS_STREAM_TYPE_AC3 = 0x81; + public static final int TS_STREAM_TYPE_DTS = 0x8A; + public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; + public static final int TS_STREAM_TYPE_E_AC3 = 0x87; + public static final int TS_STREAM_TYPE_H262 = 0x02; + public static final int TS_STREAM_TYPE_H264 = 0x1B; + public static final int TS_STREAM_TYPE_H265 = 0x24; + public static final int TS_STREAM_TYPE_ID3 = 0x15; + public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86; + public static final int TS_STREAM_TYPE_DVBSUBS = 0x59; + + private static final int TS_PACKET_SIZE = 188; + private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. + private static final int TS_PAT_PID = 0; + private static final int MAX_PID_PLUS_ONE = 0x2000; + + private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3"); + private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3"); + private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC"); + + private static final int BUFFER_PACKET_COUNT = 5; // Should be at least 2 + private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT; + + @Mode private final int mode; + private final List timestampAdjusters; + private final ParsableByteArray tsPacketBuffer; + private final ParsableBitArray tsScratch; + private final SparseIntArray continuityCounters; + private final TsPayloadReader.Factory payloadReaderFactory; + private final SparseArray tsPayloadReaders; // Indexed by pid + private final SparseBooleanArray trackIds; + + // Accessed only by the loading thread. + private ExtractorOutput output; + private int remainingPmts; + private boolean tracksEnded; + private TsPayloadReader id3Reader; + + public TsExtractor() { + this(0); + } + + /** + * @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory} + * {@code FLAG_*} values that control the behavior of the payload readers. + */ + public TsExtractor(@Flags int defaultTsPayloadReaderFlags) { + this(MODE_NORMAL, new TimestampAdjuster(0), + new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); + } + + /** + * @param mode Mode for the extractor. One of {@link #MODE_NORMAL}, {@link #MODE_SINGLE_PMT} + * and {@link #MODE_HLS}. + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param payloadReaderFactory Factory for injecting a custom set of payload readers. + */ + public TsExtractor(@Mode int mode, TimestampAdjuster timestampAdjuster, + TsPayloadReader.Factory payloadReaderFactory) { + this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory); + this.mode = mode; + if (mode == MODE_SINGLE_PMT || mode == MODE_HLS) { + timestampAdjusters = Collections.singletonList(timestampAdjuster); + } else { + timestampAdjusters = new ArrayList<>(); + timestampAdjusters.add(timestampAdjuster); + } + tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); + tsScratch = new ParsableBitArray(new byte[3]); + trackIds = new SparseBooleanArray(); + tsPayloadReaders = new SparseArray<>(); + continuityCounters = new SparseIntArray(); + resetPayloadReaders(); + } + + // Extractor implementation. + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + byte[] buffer = tsPacketBuffer.data; + input.peekFully(buffer, 0, BUFFER_SIZE); + for (int j = 0; j < TS_PACKET_SIZE; j++) { + for (int i = 0; true; i++) { + if (i == BUFFER_PACKET_COUNT) { + input.skipFully(j); + return true; + } + if (buffer[j + i * TS_PACKET_SIZE] != TS_SYNC_BYTE) { + break; + } + } + } + return false; + } + + @Override + public void init(ExtractorOutput output) { + this.output = output; + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position, long timeUs) { + int timestampAdjustersCount = timestampAdjusters.size(); + for (int i = 0; i < timestampAdjustersCount; i++) { + timestampAdjusters.get(i).reset(); + } + tsPacketBuffer.reset(); + continuityCounters.clear(); + // Elementary stream readers' state should be cleared to get consistent behaviours when seeking. + resetPayloadReaders(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + byte[] data = tsPacketBuffer.data; + // Shift bytes to the start of the buffer if there isn't enough space left at the end + if (BUFFER_SIZE - tsPacketBuffer.getPosition() < TS_PACKET_SIZE) { + int bytesLeft = tsPacketBuffer.bytesLeft(); + if (bytesLeft > 0) { + System.arraycopy(data, tsPacketBuffer.getPosition(), data, 0, bytesLeft); + } + tsPacketBuffer.reset(data, bytesLeft); + } + // Read more bytes until there is at least one packet size + while (tsPacketBuffer.bytesLeft() < TS_PACKET_SIZE) { + int limit = tsPacketBuffer.limit(); + int read = input.read(data, limit, BUFFER_SIZE - limit); + if (read == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + tsPacketBuffer.setLimit(limit + read); + } + + // Note: see ISO/IEC 13818-1, section 2.4.3.2 for detailed information on the format of + // the header. + final int limit = tsPacketBuffer.limit(); + int position = tsPacketBuffer.getPosition(); + while (position < limit && data[position] != TS_SYNC_BYTE) { + position++; + } + tsPacketBuffer.setPosition(position); + + int endOfPacket = position + TS_PACKET_SIZE; + if (endOfPacket > limit) { + return RESULT_CONTINUE; + } + + tsPacketBuffer.skipBytes(1); + tsPacketBuffer.readBytes(tsScratch, 3); + if (tsScratch.readBit()) { // transport_error_indicator + // There are uncorrectable errors in this packet. + tsPacketBuffer.setPosition(endOfPacket); + return RESULT_CONTINUE; + } + boolean payloadUnitStartIndicator = tsScratch.readBit(); + tsScratch.skipBits(1); // transport_priority + int pid = tsScratch.readBits(13); + tsScratch.skipBits(2); // transport_scrambling_control + boolean adaptationFieldExists = tsScratch.readBit(); + boolean payloadExists = tsScratch.readBit(); + + // Discontinuity check. + boolean discontinuityFound = false; + int continuityCounter = tsScratch.readBits(4); + if (mode != MODE_HLS) { + int previousCounter = continuityCounters.get(pid, continuityCounter - 1); + continuityCounters.put(pid, continuityCounter); + if (previousCounter == continuityCounter) { + if (payloadExists) { + // Duplicate packet found. + tsPacketBuffer.setPosition(endOfPacket); + return RESULT_CONTINUE; + } + } else if (continuityCounter != (previousCounter + 1) % 16) { + discontinuityFound = true; + } + } + + // Skip the adaptation field. + if (adaptationFieldExists) { + int adaptationFieldLength = tsPacketBuffer.readUnsignedByte(); + tsPacketBuffer.skipBytes(adaptationFieldLength); + } + + // Read the payload. + if (payloadExists) { + TsPayloadReader payloadReader = tsPayloadReaders.get(pid); + if (payloadReader != null) { + if (discontinuityFound) { + payloadReader.seek(); + } + tsPacketBuffer.setLimit(endOfPacket); + payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); + Assertions.checkState(tsPacketBuffer.getPosition() <= endOfPacket); + tsPacketBuffer.setLimit(limit); + } + } + + tsPacketBuffer.setPosition(endOfPacket); + return RESULT_CONTINUE; + } + + // Internals. + + private void resetPayloadReaders() { + trackIds.clear(); + tsPayloadReaders.clear(); + SparseArray initialPayloadReaders = + payloadReaderFactory.createInitialPayloadReaders(); + int initialPayloadReadersSize = initialPayloadReaders.size(); + for (int i = 0; i < initialPayloadReadersSize; i++) { + tsPayloadReaders.put(initialPayloadReaders.keyAt(i), initialPayloadReaders.valueAt(i)); + } + tsPayloadReaders.put(TS_PAT_PID, new SectionReader(new PatReader())); + id3Reader = null; + } + + /** + * Parses Program Association Table data. + */ + private class PatReader implements SectionPayloadReader { + + private final ParsableBitArray patScratch; + + public PatReader() { + patScratch = new ParsableBitArray(new byte[4]); + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. + } + + @Override + public void consume(ParsableByteArray sectionData) { + int tableId = sectionData.readUnsignedByte(); + if (tableId != 0x00 /* program_association_section */) { + // See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment. + return; + } + // section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), + // transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1), + // section_number (8), last_section_number (8) + sectionData.skipBytes(7); + + int programCount = sectionData.bytesLeft() / 4; + for (int i = 0; i < programCount; i++) { + sectionData.readBytes(patScratch, 4); + int programNumber = patScratch.readBits(16); + patScratch.skipBits(3); // reserved (3) + if (programNumber == 0) { + patScratch.skipBits(13); // network_PID (13) + } else { + int pid = patScratch.readBits(13); + tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid))); + remainingPmts++; + } + } + if (mode != MODE_HLS) { + tsPayloadReaders.remove(TS_PAT_PID); + } + } + + } + + /** + * Parses Program Map Table. + */ + private class PmtReader implements SectionPayloadReader { + + private static final int TS_PMT_DESC_REGISTRATION = 0x05; + private static final int TS_PMT_DESC_ISO639_LANG = 0x0A; + private static final int TS_PMT_DESC_AC3 = 0x6A; + private static final int TS_PMT_DESC_EAC3 = 0x7A; + private static final int TS_PMT_DESC_DTS = 0x7B; + private static final int TS_PMT_DESC_DVBSUBS = 0x59; + + private final ParsableBitArray pmtScratch; + private final int pid; + + public PmtReader(int pid) { + pmtScratch = new ParsableBitArray(new byte[5]); + this.pid = pid; + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. + } + + @Override + public void consume(ParsableByteArray sectionData) { + int tableId = sectionData.readUnsignedByte(); + if (tableId != 0x02 /* TS_program_map_section */) { + // See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment. + return; + } + // TimestampAdjuster assignment. + TimestampAdjuster timestampAdjuster; + if (mode == MODE_SINGLE_PMT || mode == MODE_HLS || remainingPmts == 1) { + timestampAdjuster = timestampAdjusters.get(0); + } else { + timestampAdjuster = new TimestampAdjuster( + timestampAdjusters.get(0).getFirstSampleTimestampUs()); + timestampAdjusters.add(timestampAdjuster); + } + + // section_syntax_indicator(1), '0'(1), reserved(2), section_length(12) + sectionData.skipBytes(2); + int programNumber = sectionData.readUnsignedShort(); + // reserved (2), version_number (5), current_next_indicator (1), section_number (8), + // last_section_number (8), reserved (3), PCR_PID (13) + sectionData.skipBytes(5); + + // Read program_info_length. + sectionData.readBytes(pmtScratch, 2); + pmtScratch.skipBits(4); + int programInfoLength = pmtScratch.readBits(12); + + // Skip the descriptors. + sectionData.skipBytes(programInfoLength); + + if (mode == MODE_HLS && id3Reader == null) { + // Setup an ID3 track regardless of whether there's a corresponding entry, in case one + // appears intermittently during playback. See [Internal: b/20261500]. + EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, null, new byte[0]); + id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); + id3Reader.init(timestampAdjuster, output, + new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); + } + + int remainingEntriesLength = sectionData.bytesLeft(); + while (remainingEntriesLength > 0) { + sectionData.readBytes(pmtScratch, 5); + int streamType = pmtScratch.readBits(8); + pmtScratch.skipBits(3); // reserved + int elementaryPid = pmtScratch.readBits(13); + pmtScratch.skipBits(4); // reserved + int esInfoLength = pmtScratch.readBits(12); // ES_info_length. + EsInfo esInfo = readEsInfo(sectionData, esInfoLength); + if (streamType == 0x06) { + streamType = esInfo.streamType; + } + remainingEntriesLength -= esInfoLength + 5; + + int trackId = mode == MODE_HLS ? streamType : elementaryPid; + if (trackIds.get(trackId)) { + continue; + } + trackIds.put(trackId, true); + + TsPayloadReader reader; + if (mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3) { + reader = id3Reader; + } else { + reader = payloadReaderFactory.createPayloadReader(streamType, esInfo); + if (reader != null) { + reader.init(timestampAdjuster, output, + new TrackIdGenerator(programNumber, trackId, MAX_PID_PLUS_ONE)); + } + } + + if (reader != null) { + tsPayloadReaders.put(elementaryPid, reader); + } + } + if (mode == MODE_HLS) { + if (!tracksEnded) { + output.endTracks(); + remainingPmts = 0; + tracksEnded = true; + } + } else { + tsPayloadReaders.remove(pid); + remainingPmts = mode == MODE_SINGLE_PMT ? 0 : remainingPmts - 1; + if (remainingPmts == 0) { + output.endTracks(); + tracksEnded = true; + } + } + } + + /** + * Returns the stream info read from the available descriptors. Sets {@code data}'s position to + * the end of the descriptors. + * + * @param data A buffer with its position set to the start of the first descriptor. + * @param length The length of descriptors to read from the current position in {@code data}. + * @return The stream info read from the available descriptors. + */ + private EsInfo readEsInfo(ParsableByteArray data, int length) { + int descriptorsStartPosition = data.getPosition(); + int descriptorsEndPosition = descriptorsStartPosition + length; + int streamType = -1; + String language = null; + List dvbSubtitleInfos = null; + while (data.getPosition() < descriptorsEndPosition) { + int descriptorTag = data.readUnsignedByte(); + int descriptorLength = data.readUnsignedByte(); + int positionOfNextDescriptor = data.getPosition() + descriptorLength; + if (descriptorTag == TS_PMT_DESC_REGISTRATION) { // registration_descriptor + long formatIdentifier = data.readUnsignedInt(); + if (formatIdentifier == AC3_FORMAT_IDENTIFIER) { + streamType = TS_STREAM_TYPE_AC3; + } else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) { + streamType = TS_STREAM_TYPE_E_AC3; + } else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) { + streamType = TS_STREAM_TYPE_H265; + } + } else if (descriptorTag == TS_PMT_DESC_AC3) { // AC-3_descriptor in DVB (ETSI EN 300 468) + streamType = TS_STREAM_TYPE_AC3; + } else if (descriptorTag == TS_PMT_DESC_EAC3) { // enhanced_AC-3_descriptor + streamType = TS_STREAM_TYPE_E_AC3; + } else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor + streamType = TS_STREAM_TYPE_DTS; + } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { + language = data.readString(3).trim(); + // Audio type is ignored. + } else if (descriptorTag == TS_PMT_DESC_DVBSUBS) { + streamType = TS_STREAM_TYPE_DVBSUBS; + dvbSubtitleInfos = new ArrayList<>(); + while (data.getPosition() < positionOfNextDescriptor) { + String dvbLanguage = data.readString(3).trim(); + int dvbSubtitlingType = data.readUnsignedByte(); + byte[] initializationData = new byte[4]; + data.readBytes(initializationData, 0, 4); + dvbSubtitleInfos.add(new DvbSubtitleInfo(dvbLanguage, dvbSubtitlingType, + initializationData)); + } + } + // Skip unused bytes of current descriptor. + data.skipBytes(positionOfNextDescriptor - data.getPosition()); + } + data.setPosition(descriptorsEndPosition); + return new EsInfo(streamType, language, dvbSubtitleInfos, + Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition)); + } + + } + + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/TsPayloadReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/TsPayloadReader.java new file mode 100644 index 0000000..0e1ca6e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/ts/TsPayloadReader.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts; + +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import java.util.Collections; +import java.util.List; + +/** + * Parses TS packet payload data. + */ +public interface TsPayloadReader { + + /** + * Factory of {@link TsPayloadReader} instances. + */ + interface Factory { + + /** + * Returns the initial mapping from PIDs to payload readers. + *

    + * This method allows the injection of payload readers for reserved PIDs, excluding PID 0. + * + * @return A {@link SparseArray} that maps PIDs to payload readers. + */ + SparseArray createInitialPayloadReaders(); + + /** + * Returns a {@link TsPayloadReader} for a given stream type and elementary stream information. + * May return null if the stream type is not supported. + * + * @param streamType Stream type value as defined in the PMT entry or associated descriptors. + * @param esInfo Information associated to the elementary stream provided in the PMT. + * @return A {@link TsPayloadReader} for the packet stream carried by the provided pid. + * {@code null} if the stream is not supported. + */ + TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo); + + } + + /** + * Holds information associated with a PMT entry. + */ + final class EsInfo { + + public final int streamType; + public final String language; + public final List dvbSubtitleInfos; + public final byte[] descriptorBytes; + + /** + * @param streamType The type of the stream as defined by the + * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. + * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. + * @param dvbSubtitleInfos Information about DVB subtitles associated to the stream. + * @param descriptorBytes The descriptor bytes associated to the stream. + */ + public EsInfo(int streamType, String language, List dvbSubtitleInfos, + byte[] descriptorBytes) { + this.streamType = streamType; + this.language = language; + this.dvbSubtitleInfos = dvbSubtitleInfos == null ? Collections.emptyList() + : Collections.unmodifiableList(dvbSubtitleInfos); + this.descriptorBytes = descriptorBytes; + } + + } + + /** + * Holds information about a DVB subtitle, as defined in ETSI EN 300 468 V1.11.1 section 6.2.41. + */ + final class DvbSubtitleInfo { + + public final String language; + public final int type; + public final byte[] initializationData; + + /** + * @param language The ISO 639-2 three character language. + * @param type The subtitling type. + * @param initializationData The composition and ancillary page ids. + */ + public DvbSubtitleInfo(String language, int type, byte[] initializationData) { + this.language = language; + this.type = type; + this.initializationData = initializationData; + } + + } + + /** + * Generates track ids for initializing {@link TsPayloadReader}s' {@link TrackOutput}s. + */ + final class TrackIdGenerator { + + private static final int ID_UNSET = Integer.MIN_VALUE; + + private final String formatIdPrefix; + private final int firstTrackId; + private final int trackIdIncrement; + private int trackId; + private String formatId; + + public TrackIdGenerator(int firstTrackId, int trackIdIncrement) { + this(ID_UNSET, firstTrackId, trackIdIncrement); + } + + public TrackIdGenerator(int programNumber, int firstTrackId, int trackIdIncrement) { + this.formatIdPrefix = programNumber != ID_UNSET ? programNumber + "/" : ""; + this.firstTrackId = firstTrackId; + this.trackIdIncrement = trackIdIncrement; + trackId = ID_UNSET; + } + + /** + * Generates a new set of track and track format ids. Must be called before {@code get*} + * methods. + */ + public void generateNewId() { + trackId = trackId == ID_UNSET ? firstTrackId : trackId + trackIdIncrement; + formatId = formatIdPrefix + trackId; + } + + /** + * Returns the last generated track id. Must be called after the first {@link #generateNewId()} + * call. + * + * @return The last generated track id. + */ + public int getTrackId() { + maybeThrowUninitializedError(); + return trackId; + } + + /** + * Returns the last generated format id, with the format {@code "programNumber/trackId"}. If no + * {@code programNumber} was provided, the {@code trackId} alone is used as format id. Must be + * called after the first {@link #generateNewId()} call. + * + * @return The last generated format id, with the format {@code "programNumber/trackId"}. If no + * {@code programNumber} was provided, the {@code trackId} alone is used as + * format id. + */ + public String getFormatId() { + maybeThrowUninitializedError(); + return formatId; + } + + private void maybeThrowUninitializedError() { + if (trackId == ID_UNSET) { + throw new IllegalStateException("generateNewId() must be called before retrieving ids."); + } + } + + } + + /** + * Initializes the payload reader. + * + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the + * {@link TrackOutput}s. + */ + void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator); + + /** + * Notifies the reader that a seek has occurred. + *

    + * Following a call to this method, the data passed to the next invocation of + * {@link #consume(ParsableByteArray, boolean)} will not be a continuation of the data that was + * previously passed. Hence the reader should reset any internal state. + */ + void seek(); + + /** + * Consumes the payload of a TS packet. + * + * @param data The TS packet. The position will be set to the start of the payload. + * @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet. + */ + void consume(ParsableByteArray data, boolean payloadUnitStartIndicator); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavExtractor.java new file mode 100644 index 0000000..bc50e96 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavExtractor.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.wav; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import java.io.IOException; + +/** {@link Extractor} to extract samples from a WAV byte stream. */ +public final class WavExtractor implements Extractor, SeekMap { + + /** + * Factory for {@link WavExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new WavExtractor()}; + } + + }; + + /** Arbitrary maximum input size of 32KB, which is ~170ms of 16-bit stereo PCM audio at 48KHz. */ + private static final int MAX_INPUT_SIZE = 32 * 1024; + + private ExtractorOutput extractorOutput; + private TrackOutput trackOutput; + private WavHeader wavHeader; + private int bytesPerFrame; + private int pendingBytes; + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return WavHeaderReader.peek(input) != null; + } + + @Override + public void init(ExtractorOutput output) { + extractorOutput = output; + trackOutput = output.track(0, C.TRACK_TYPE_AUDIO); + wavHeader = null; + output.endTracks(); + } + + @Override + public void seek(long position, long timeUs) { + pendingBytes = 0; + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + if (wavHeader == null) { + wavHeader = WavHeaderReader.peek(input); + if (wavHeader == null) { + // Should only happen if the media wasn't sniffed. + throw new ParserException("Unsupported or unrecognized wav header."); + } + Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, null, + wavHeader.getBitrate(), MAX_INPUT_SIZE, wavHeader.getNumChannels(), + wavHeader.getSampleRateHz(), wavHeader.getEncoding(), null, null, 0, null); + trackOutput.format(format); + bytesPerFrame = wavHeader.getBytesPerFrame(); + } + + if (!wavHeader.hasDataBounds()) { + WavHeaderReader.skipToData(input, wavHeader); + extractorOutput.seekMap(this); + } + + int bytesAppended = trackOutput.sampleData(input, MAX_INPUT_SIZE - pendingBytes, true); + if (bytesAppended != RESULT_END_OF_INPUT) { + pendingBytes += bytesAppended; + } + + // Samples must consist of a whole number of frames. + int pendingFrames = pendingBytes / bytesPerFrame; + if (pendingFrames > 0) { + long timeUs = wavHeader.getTimeUs(input.getPosition() - pendingBytes); + int size = pendingFrames * bytesPerFrame; + pendingBytes -= size; + trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, size, pendingBytes, null); + } + + return bytesAppended == RESULT_END_OF_INPUT ? RESULT_END_OF_INPUT : RESULT_CONTINUE; + } + + // SeekMap implementation. + + @Override + public long getDurationUs() { + return wavHeader.getDurationUs(); + } + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getPosition(long timeUs) { + return wavHeader.getPosition(timeUs); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavHeader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavHeader.java new file mode 100644 index 0000000..2475fe0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavHeader.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.wav; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +/** Header for a WAV file. */ +/*package*/ final class WavHeader { + + /** Number of audio chanels. */ + private final int numChannels; + /** Sample rate in Hertz. */ + private final int sampleRateHz; + /** Average bytes per second for the sample data. */ + private final int averageBytesPerSecond; + /** Alignment for frames of audio data; should equal {@code numChannels * bitsPerSample / 8}. */ + private final int blockAlignment; + /** Bits per sample for the audio data. */ + private final int bitsPerSample; + /** The PCM encoding */ + @C.PcmEncoding + private final int encoding; + + /** Offset to the start of sample data. */ + private long dataStartPosition; + /** Total size in bytes of the sample data. */ + private long dataSize; + + public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment, + int bitsPerSample, @C.PcmEncoding int encoding) { + this.numChannels = numChannels; + this.sampleRateHz = sampleRateHz; + this.averageBytesPerSecond = averageBytesPerSecond; + this.blockAlignment = blockAlignment; + this.bitsPerSample = bitsPerSample; + this.encoding = encoding; + } + + /** Returns the duration in microseconds of this WAV. */ + public long getDurationUs() { + long numFrames = dataSize / blockAlignment; + return (numFrames * C.MICROS_PER_SECOND) / sampleRateHz; + } + + /** Returns the bytes per frame of this WAV. */ + public int getBytesPerFrame() { + return blockAlignment; + } + + /** Returns the bitrate of this WAV. */ + public int getBitrate() { + return sampleRateHz * bitsPerSample * numChannels; + } + + /** Returns the sample rate in Hertz of this WAV. */ + public int getSampleRateHz() { + return sampleRateHz; + } + + /** Returns the number of audio channels in this WAV. */ + public int getNumChannels() { + return numChannels; + } + + /** Returns the position in bytes in this WAV for the given time in microseconds. */ + public long getPosition(long timeUs) { + long unroundedPosition = (timeUs * averageBytesPerSecond) / C.MICROS_PER_SECOND; + // Round down to nearest frame. + long position = (unroundedPosition / blockAlignment) * blockAlignment; + return Math.min(position, dataSize - blockAlignment) + dataStartPosition; + } + + /** Returns the time in microseconds for the given position in bytes in this WAV. */ + public long getTimeUs(long position) { + return position * C.MICROS_PER_SECOND / averageBytesPerSecond; + } + + /** Returns true if the data start position and size have been set. */ + public boolean hasDataBounds() { + return dataStartPosition != 0 && dataSize != 0; + } + + /** Sets the start position and size in bytes of sample data in this WAV. */ + public void setDataBounds(long dataStartPosition, long dataSize) { + this.dataStartPosition = dataStartPosition; + this.dataSize = dataSize; + } + + /** Returns the PCM encoding. **/ + @C.PcmEncoding + public int getEncoding() { + return encoding; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavHeaderReader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavHeaderReader.java new file mode 100644 index 0000000..16c4268 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/extractor/wav/WavHeaderReader.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.extractor.wav; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */ +/*package*/ final class WavHeaderReader { + + private static final String TAG = "WavHeaderReader"; + + /** Integer PCM audio data. */ + private static final int TYPE_PCM = 0x0001; + /** Extended WAVE format. */ + private static final int TYPE_WAVE_FORMAT_EXTENSIBLE = 0xFFFE; + + /** + * Peeks and returns a {@code WavHeader}. + * + * @param input Input stream to peek the WAV header from. + * @throws ParserException If the input file is an incorrect RIFF WAV. + * @throws IOException If peeking from the input fails. + * @throws InterruptedException If interrupted while peeking from input. + * @return A new {@code WavHeader} peeked from {@code input}, or null if the input is not a + * supported WAV format. + */ + public static WavHeader peek(ExtractorInput input) throws IOException, InterruptedException { + Assertions.checkNotNull(input); + + // Allocate a scratch buffer large enough to store the format chunk. + ParsableByteArray scratch = new ParsableByteArray(16); + + // Attempt to read the RIFF chunk. + ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); + if (chunkHeader.id != Util.getIntegerCodeForString("RIFF")) { + return null; + } + + input.peekFully(scratch.data, 0, 4); + scratch.setPosition(0); + int riffFormat = scratch.readInt(); + if (riffFormat != Util.getIntegerCodeForString("WAVE")) { + return null; + } + + // Skip chunks until we find the format chunk. + chunkHeader = ChunkHeader.peek(input, scratch); + while (chunkHeader.id != Util.getIntegerCodeForString("fmt ")) { + input.advancePeekPosition((int) chunkHeader.size); + chunkHeader = ChunkHeader.peek(input, scratch); + } + + Assertions.checkState(chunkHeader.size >= 16); + input.peekFully(scratch.data, 0, 16); + scratch.setPosition(0); + int type = scratch.readLittleEndianUnsignedShort(); + int numChannels = scratch.readLittleEndianUnsignedShort(); + int sampleRateHz = scratch.readLittleEndianUnsignedIntToInt(); + int averageBytesPerSecond = scratch.readLittleEndianUnsignedIntToInt(); + int blockAlignment = scratch.readLittleEndianUnsignedShort(); + int bitsPerSample = scratch.readLittleEndianUnsignedShort(); + + int expectedBlockAlignment = numChannels * bitsPerSample / 8; + if (blockAlignment != expectedBlockAlignment) { + throw new ParserException("Expected block alignment: " + expectedBlockAlignment + "; got: " + + blockAlignment); + } + + @C.PcmEncoding int encoding = Util.getPcmEncoding(bitsPerSample); + if (encoding == C.ENCODING_INVALID) { + return null; + } + + if (type != TYPE_PCM && type != TYPE_WAVE_FORMAT_EXTENSIBLE) { + return null; + } + + // If present, skip extensionSize, validBitsPerSample, channelMask, subFormatGuid, ... + input.advancePeekPosition((int) chunkHeader.size - 16); + + return new WavHeader(numChannels, sampleRateHz, averageBytesPerSecond, blockAlignment, + bitsPerSample, encoding); + } + + /** + * Skips to the data in the given WAV input stream and returns its data size. After calling, the + * input stream's position will point to the start of sample data in the WAV. + *

    + * If an exception is thrown, the input position will be left pointing to a chunk header. + * + * @param input Input stream to skip to the data chunk in. Its peek position must be pointing to + * a valid chunk header. + * @param wavHeader WAV header to populate with data bounds. + * @throws ParserException If an error occurs parsing chunks. + * @throws IOException If reading from the input fails. + * @throws InterruptedException If interrupted while reading from input. + */ + public static void skipToData(ExtractorInput input, WavHeader wavHeader) + throws IOException, InterruptedException { + Assertions.checkNotNull(input); + Assertions.checkNotNull(wavHeader); + + // Make sure the peek position is set to the read position before we peek the first header. + input.resetPeekPosition(); + + ParsableByteArray scratch = new ParsableByteArray(ChunkHeader.SIZE_IN_BYTES); + // Skip all chunks until we hit the data header. + ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); + while (chunkHeader.id != Util.getIntegerCodeForString("data")) { + long bytesToSkip = ChunkHeader.SIZE_IN_BYTES + chunkHeader.size; + // Override size of RIFF chunk, since it describes its size as the entire file. + if (chunkHeader.id == Util.getIntegerCodeForString("RIFF")) { + bytesToSkip = ChunkHeader.SIZE_IN_BYTES + 4; + } + if (bytesToSkip > Integer.MAX_VALUE) { + throw new ParserException("Chunk is too large (~2GB+) to skip; id: " + chunkHeader.id); + } + input.skipFully((int) bytesToSkip); + chunkHeader = ChunkHeader.peek(input, scratch); + } + // Skip past the "data" header. + input.skipFully(ChunkHeader.SIZE_IN_BYTES); + + wavHeader.setDataBounds(input.getPosition(), chunkHeader.size); + } + + /** Container for a WAV chunk header. */ + private static final class ChunkHeader { + + /** Size in bytes of a WAV chunk header. */ + public static final int SIZE_IN_BYTES = 8; + + /** 4-character identifier, stored as an integer, for this chunk. */ + public final int id; + /** Size of this chunk in bytes. */ + public final long size; + + private ChunkHeader(int id, long size) { + this.id = id; + this.size = size; + } + + /** + * Peeks and returns a {@link ChunkHeader}. + * + * @param input Input stream to peek the chunk header from. + * @param scratch Buffer for temporary use. + * @throws IOException If peeking from the input fails. + * @throws InterruptedException If interrupted while peeking from input. + * @return A new {@code ChunkHeader} peeked from {@code input}. + */ + public static ChunkHeader peek(ExtractorInput input, ParsableByteArray scratch) + throws IOException, InterruptedException { + input.peekFully(scratch.data, 0, SIZE_IN_BYTES); + scratch.setPosition(0); + + int id = scratch.readInt(); + long size = scratch.readLittleEndianUnsignedInt(); + + return new ChunkHeader(id, size); + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecInfo.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecInfo.java new file mode 100644 index 0000000..284cc98 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecInfo.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.mediacodec; + +import android.annotation.TargetApi; +import android.graphics.Point; +import android.media.MediaCodec; +import android.media.MediaCodecInfo.AudioCapabilities; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecInfo.CodecProfileLevel; +import android.media.MediaCodecInfo.VideoCapabilities; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Information about a {@link MediaCodec} for a given mime type. + */ +@TargetApi(16) +public final class MediaCodecInfo { + + public static final String TAG = "MediaCodecInfo"; + + /** + * The name of the decoder. + *

    + * May be passed to {@link MediaCodec#createByCodecName(String)} to create an instance of the + * decoder. + */ + public final String name; + + /** + * Whether the decoder supports seamless resolution switches. + * + * @see CodecCapabilities#isFeatureSupported(String) + * @see CodecCapabilities#FEATURE_AdaptivePlayback + */ + public final boolean adaptive; + + /** + * Whether the decoder supports tunneling. + * + * @see CodecCapabilities#isFeatureSupported(String) + * @see CodecCapabilities#FEATURE_TunneledPlayback + */ + public final boolean tunneling; + + private final String mimeType; + private final CodecCapabilities capabilities; + + /** + * Creates an instance representing an audio passthrough decoder. + * + * @param name The name of the {@link MediaCodec}. + * @return The created instance. + */ + public static MediaCodecInfo newPassthroughInstance(String name) { + return new MediaCodecInfo(name, null, null); + } + + /** + * Creates an instance. + * + * @param name The name of the {@link MediaCodec}. + * @param mimeType A mime type supported by the {@link MediaCodec}. + * @param capabilities The capabilities of the {@link MediaCodec} for the specified mime type. + * @return The created instance. + */ + public static MediaCodecInfo newInstance(String name, String mimeType, + CodecCapabilities capabilities) { + return new MediaCodecInfo(name, mimeType, capabilities); + } + + /** + * @param name The name of the decoder. + * @param capabilities The capabilities of the decoder. + */ + private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities) { + this.name = Assertions.checkNotNull(name); + this.mimeType = mimeType; + this.capabilities = capabilities; + adaptive = capabilities != null && isAdaptive(capabilities); + tunneling = capabilities != null && isTunneling(capabilities); + } + + /** + * The profile levels supported by the decoder. + * + * @return The profile levels supported by the decoder. + */ + public CodecProfileLevel[] getProfileLevels() { + return capabilities == null || capabilities.profileLevels == null ? new CodecProfileLevel[0] + : capabilities.profileLevels; + } + + /** + * Whether the decoder supports the given {@code codec}. If there is insufficient information to + * decide, returns true. + * + * @param codec Codec string as defined in RFC 6381. + * @return True if the given codec is supported by the decoder. + */ + public boolean isCodecSupported(String codec) { + if (codec == null || mimeType == null) { + return true; + } + String codecMimeType = MimeTypes.getMediaMimeType(codec); + if (codecMimeType == null) { + return true; + } + if (!mimeType.equals(codecMimeType)) { + logNoSupport("codec.mime " + codec + ", " + codecMimeType); + return false; + } + Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(codec); + if (codecProfileAndLevel == null) { + // If we don't know any better, we assume that the profile and level are supported. + return true; + } + for (CodecProfileLevel capabilities : getProfileLevels()) { + if (capabilities.profile == codecProfileAndLevel.first + && capabilities.level >= codecProfileAndLevel.second) { + return true; + } + } + logNoSupport("codec.profileLevel, " + codec + ", " + codecMimeType); + return false; + } + + /** + * Whether the decoder supports video with a given width, height and frame rate. + *

    + * Must not be called if the device SDK version is less than 21. + * + * @param width Width in pixels. + * @param height Height in pixels. + * @param frameRate Optional frame rate in frames per second. Ignored if set to + * {@link Format#NO_VALUE} or any value less than or equal to 0. + * @return Whether the decoder supports video with the given width, height and frame rate. + */ + @TargetApi(21) + public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) { + if (capabilities == null) { + logNoSupport("sizeAndRate.caps"); + return false; + } + VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities(); + if (videoCapabilities == null) { + logNoSupport("sizeAndRate.vCaps"); + return false; + } + if (!areSizeAndRateSupported(videoCapabilities, width, height, frameRate)) { + // Capabilities are known to be inaccurately reported for vertical resolutions on some devices + // (b/31387661). If the video is vertical and the capabilities indicate support if the width + // and height are swapped, we assume that the vertical resolution is also supported. + if (width >= height + || !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) { + logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate); + return false; + } + logAssumedSupport("sizeAndRate.rotated, " + width + "x" + height + "x" + frameRate); + } + return true; + } + + /** + * Returns the smallest video size greater than or equal to a specified size that also satisfies + * the {@link MediaCodec}'s width and height alignment requirements. + *

    + * Must not be called if the device SDK version is less than 21. + * + * @param width Width in pixels. + * @param height Height in pixels. + * @return The smallest video size greater than or equal to the specified size that also satisfies + * the {@link MediaCodec}'s width and height alignment requirements, or null if not a video + * codec. + */ + @TargetApi(21) + public Point alignVideoSizeV21(int width, int height) { + if (capabilities == null) { + logNoSupport("align.caps"); + return null; + } + VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities(); + if (videoCapabilities == null) { + logNoSupport("align.vCaps"); + return null; + } + int widthAlignment = videoCapabilities.getWidthAlignment(); + int heightAlignment = videoCapabilities.getHeightAlignment(); + return new Point(Util.ceilDivide(width, widthAlignment) * widthAlignment, + Util.ceilDivide(height, heightAlignment) * heightAlignment); + } + + /** + * Whether the decoder supports audio with a given sample rate. + *

    + * Must not be called if the device SDK version is less than 21. + * + * @param sampleRate The sample rate in Hz. + * @return Whether the decoder supports audio with the given sample rate. + */ + @TargetApi(21) + public boolean isAudioSampleRateSupportedV21(int sampleRate) { + if (capabilities == null) { + logNoSupport("sampleRate.caps"); + return false; + } + AudioCapabilities audioCapabilities = capabilities.getAudioCapabilities(); + if (audioCapabilities == null) { + logNoSupport("sampleRate.aCaps"); + return false; + } + if (!audioCapabilities.isSampleRateSupported(sampleRate)) { + logNoSupport("sampleRate.support, " + sampleRate); + return false; + } + return true; + } + + /** + * Whether the decoder supports audio with a given channel count. + *

    + * Must not be called if the device SDK version is less than 21. + * + * @param channelCount The channel count. + * @return Whether the decoder supports audio with the given channel count. + */ + @TargetApi(21) + public boolean isAudioChannelCountSupportedV21(int channelCount) { + if (capabilities == null) { + logNoSupport("channelCount.caps"); + return false; + } + AudioCapabilities audioCapabilities = capabilities.getAudioCapabilities(); + if (audioCapabilities == null) { + logNoSupport("channelCount.aCaps"); + return false; + } + if (audioCapabilities.getMaxInputChannelCount() < channelCount) { + logNoSupport("channelCount.support, " + channelCount); + return false; + } + return true; + } + + private void logNoSupport(String message) { + } + + private void logAssumedSupport(String message) { + } + + private static boolean isAdaptive(CodecCapabilities capabilities) { + return Util.SDK_INT >= 19 && isAdaptiveV19(capabilities); + } + + @TargetApi(19) + private static boolean isAdaptiveV19(CodecCapabilities capabilities) { + return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback); + } + + @TargetApi(21) + private static boolean areSizeAndRateSupported(VideoCapabilities capabilities, int width, + int height, double frameRate) { + return frameRate == Format.NO_VALUE || frameRate <= 0 + ? capabilities.isSizeSupported(width, height) + : capabilities.areSizeAndRateSupported(width, height, frameRate); + } + + private static boolean isTunneling(CodecCapabilities capabilities) { + return Util.SDK_INT >= 21 && isTunnelingV21(capabilities); + } + + @TargetApi(21) + private static boolean isTunnelingV21(CodecCapabilities capabilities) { + return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecRenderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecRenderer.java new file mode 100644 index 0000000..2b81582 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -0,0 +1,1205 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.mediacodec; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaCodec.CodecException; +import android.media.MediaCodec.CryptoException; +import android.media.MediaCrypto; +import android.media.MediaFormat; +import android.os.Looper; +import android.os.SystemClock; +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.BaseRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderCounters; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSession; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSessionManager; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.FrameworkMediaCrypto; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaPeriod; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TraceUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * An abstract renderer that uses {@link MediaCodec} to decode samples for rendering. + */ +@TargetApi(16) +public abstract class MediaCodecRenderer extends BaseRenderer { + + /** + * Thrown when a failure occurs instantiating a decoder. + */ + public static class DecoderInitializationException extends Exception { + + private static final int CUSTOM_ERROR_CODE_BASE = -50000; + private static final int NO_SUITABLE_DECODER_ERROR = CUSTOM_ERROR_CODE_BASE + 1; + private static final int DECODER_QUERY_ERROR = CUSTOM_ERROR_CODE_BASE + 2; + + /** + * The mime type for which a decoder was being initialized. + */ + public final String mimeType; + + /** + * Whether it was required that the decoder support a secure output path. + */ + public final boolean secureDecoderRequired; + + /** + * The name of the decoder that failed to initialize. Null if no suitable decoder was found. + */ + public final String decoderName; + + /** + * An optional developer-readable diagnostic information string. May be null. + */ + public final String diagnosticInfo; + + public DecoderInitializationException(Format format, Throwable cause, + boolean secureDecoderRequired, int errorCode) { + super("Decoder init failed: [" + errorCode + "], " + format, cause); + this.mimeType = format.sampleMimeType; + this.secureDecoderRequired = secureDecoderRequired; + this.decoderName = null; + this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode); + } + + public DecoderInitializationException(Format format, Throwable cause, + boolean secureDecoderRequired, String decoderName) { + super("Decoder init failed: " + decoderName + ", " + format, cause); + this.mimeType = format.sampleMimeType; + this.secureDecoderRequired = secureDecoderRequired; + this.decoderName = decoderName; + this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null; + } + + @TargetApi(21) + private static String getDiagnosticInfoV21(Throwable cause) { + if (cause instanceof CodecException) { + return ((CodecException) cause).getDiagnosticInfo(); + } + return null; + } + + private static String buildCustomDiagnosticInfo(int errorCode) { + String sign = errorCode < 0 ? "neg_" : ""; + return "com.google.android.exoplayer.MediaCodecTrackRenderer_" + sign + Math.abs(errorCode); + } + + } + + private static final String TAG = "MediaCodecRenderer"; + + /** + * If the {@link MediaCodec} is hotswapped (i.e. replaced during playback), this is the period of + * time during which {@link #isReady()} will report true regardless of whether the new codec has + * output frames that are ready to be rendered. + *

    + * This allows codec hotswapping to be performed seamlessly, without interrupting the playback of + * other renderers, provided the new codec is able to decode some frames within this time period. + */ + private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000; + + /** + * There is no pending adaptive reconfiguration work. + */ + private static final int RECONFIGURATION_STATE_NONE = 0; + /** + * Codec configuration data needs to be written into the next buffer. + */ + private static final int RECONFIGURATION_STATE_WRITE_PENDING = 1; + /** + * Codec configuration data has been written into the next buffer, but that buffer still needs to + * be returned to the codec. + */ + private static final int RECONFIGURATION_STATE_QUEUE_PENDING = 2; + + /** + * The codec does not need to be re-initialized. + */ + private static final int REINITIALIZATION_STATE_NONE = 0; + /** + * The input format has changed in a way that requires the codec to be re-initialized, but we + * haven't yet signaled an end of stream to the existing codec. We need to do so in order to + * ensure that it outputs any remaining buffers before we release it. + */ + private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1; + /** + * The input format has changed in a way that requires the codec to be re-initialized, and we've + * signaled an end of stream to the existing codec. We're waiting for the codec to output an end + * of stream signal to indicate that it has output any remaining buffers before we release it. + */ + private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; + + /** + * H.264/AVC buffer to queue when using the adaptation workaround (see + * {@link #codecNeedsAdaptationWorkaround(String)}. Consists of three NAL units with start codes: + * Baseline sequence/picture parameter sets and a 32 * 32 pixel IDR slice. This stream can be + * queued to force a resolution change when adapting to a new format. + */ + private static final byte[] ADAPTATION_WORKAROUND_BUFFER = Util.getBytesFromHexString( + "0000016742C00BDA259000000168CE0F13200000016588840DCE7118A0002FBF1C31C3275D78"); + private static final int ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT = 32; + + private final MediaCodecSelector mediaCodecSelector; + private final DrmSessionManager drmSessionManager; + private final boolean playClearSamplesWithoutKeys; + private final DecoderInputBuffer buffer; + private final DecoderInputBuffer flagsOnlyBuffer; + private final FormatHolder formatHolder; + private final List decodeOnlyPresentationTimestamps; + private final MediaCodec.BufferInfo outputBufferInfo; + + private Format format; + private MediaCodec codec; + private DrmSession drmSession; + private DrmSession pendingDrmSession; + private boolean codecIsAdaptive; + private boolean codecNeedsDiscardToSpsWorkaround; + private boolean codecNeedsFlushWorkaround; + private boolean codecNeedsAdaptationWorkaround; + private boolean codecNeedsEosPropagationWorkaround; + private boolean codecNeedsEosFlushWorkaround; + private boolean codecNeedsEosOutputExceptionWorkaround; + private boolean codecNeedsMonoChannelCountWorkaround; + private boolean codecNeedsAdaptationWorkaroundBuffer; + private boolean shouldSkipAdaptationWorkaroundOutputBuffer; + private ByteBuffer[] inputBuffers; + private ByteBuffer[] outputBuffers; + private long codecHotswapDeadlineMs; + private int inputIndex; + private int outputIndex; + private boolean shouldSkipOutputBuffer; + private boolean codecReconfigured; + private int codecReconfigurationState; + private int codecReinitializationState; + private boolean codecReceivedBuffers; + private boolean codecReceivedEos; + + private boolean inputStreamEnded; + private boolean outputStreamEnded; + private boolean waitingForKeys; + private boolean waitingForFirstSyncFrame; + + protected DecoderCounters decoderCounters; + + /** + * @param trackType The track type that the renderer handles. One of the {@code C.TRACK_TYPE_*} + * constants defined in {@link C}. + * @param mediaCodecSelector A decoder selector. + * @param drmSessionManager For use with encrypted media. May be null if support for encrypted + * media is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + */ + public MediaCodecRenderer(int trackType, MediaCodecSelector mediaCodecSelector, + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys) { + super(trackType); + Assertions.checkState(Util.SDK_INT >= 16); + this.mediaCodecSelector = Assertions.checkNotNull(mediaCodecSelector); + this.drmSessionManager = drmSessionManager; + this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; + buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); + flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); + formatHolder = new FormatHolder(); + decodeOnlyPresentationTimestamps = new ArrayList<>(); + outputBufferInfo = new MediaCodec.BufferInfo(); + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + codecReinitializationState = REINITIALIZATION_STATE_NONE; + } + + @Override + public final int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException { + return ADAPTIVE_NOT_SEAMLESS; + } + + @Override + public final int supportsFormat(Format format) throws ExoPlaybackException { + try { + return supportsFormat(mediaCodecSelector, format); + } catch (DecoderQueryException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + /** + * Returns the extent to which the renderer is capable of supporting a given format. + * + * @param mediaCodecSelector The decoder selector. + * @param format The format. + * @return The extent to which the renderer is capable of supporting the given format. See + * {@link #supportsFormat(Format)} for more detail. + * @throws DecoderQueryException If there was an error querying decoders. + */ + protected abstract int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) + throws DecoderQueryException; + + /** + * Returns a {@link MediaCodecInfo} for a given format. + * + * @param mediaCodecSelector The decoder selector. + * @param format The format for which a decoder is required. + * @param requiresSecureDecoder Whether a secure decoder is required. + * @return A {@link MediaCodecInfo} describing the decoder to instantiate, or null if no + * suitable decoder exists. + * @throws DecoderQueryException Thrown if there was an error querying decoders. + */ + protected MediaCodecInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, + Format format, boolean requiresSecureDecoder) throws DecoderQueryException { + return mediaCodecSelector.getDecoderInfo(format.sampleMimeType, requiresSecureDecoder); + } + + /** + * Configures a newly created {@link MediaCodec}. + * + * @param codecInfo Information about the {@link MediaCodec} being configured. + * @param codec The {@link MediaCodec} to configure. + * @param format The format for which the codec is being configured. + * @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption. + * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. + */ + protected abstract void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, + MediaCrypto crypto) throws DecoderQueryException; + + @SuppressWarnings("deprecation") + protected final void maybeInitCodec() throws ExoPlaybackException { + if (!shouldInitCodec()) { + return; + } + + drmSession = pendingDrmSession; + String mimeType = format.sampleMimeType; + MediaCrypto mediaCrypto = null; + boolean drmSessionRequiresSecureDecoder = false; + if (drmSession != null) { + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } else if (drmSessionState == DrmSession.STATE_OPENED + || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { + mediaCrypto = drmSession.getMediaCrypto().getWrappedMediaCrypto(); + drmSessionRequiresSecureDecoder = drmSession.requiresSecureDecoderComponent(mimeType); + } else { + // The drm session isn't open yet. + return; + } + } + + MediaCodecInfo decoderInfo = null; + try { + decoderInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); + if (decoderInfo == null && drmSessionRequiresSecureDecoder) { + // The drm session indicates that a secure decoder is required, but the device does not have + // one. Assuming that supportsFormat indicated support for the media being played, we know + // that it does not require a secure output path. Most CDM implementations allow playback to + // proceed with a non-secure decoder in this case, so we try our luck. + decoderInfo = getDecoderInfo(mediaCodecSelector, format, false); + if (decoderInfo != null) { + Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but " + + "no secure decoder available. Trying to proceed with " + decoderInfo.name + "."); + } + } + } catch (DecoderQueryException e) { + throwDecoderInitError(new DecoderInitializationException(format, e, + drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); + } + + if (decoderInfo == null) { + throwDecoderInitError(new DecoderInitializationException(format, null, + drmSessionRequiresSecureDecoder, + DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); + } + + String codecName = decoderInfo.name; + codecIsAdaptive = decoderInfo.adaptive && !codecNeedsDisableAdaptationWorkaround(codecName); + codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); + codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); + codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); + codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName); + codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); + codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); + codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); + try { + long codecInitializingTimestamp = SystemClock.elapsedRealtime(); + TraceUtil.beginSection("createCodec:" + codecName); + codec = MediaCodec.createByCodecName(codecName); + TraceUtil.endSection(); + TraceUtil.beginSection("configureCodec"); + configureCodec(decoderInfo, codec, format, mediaCrypto); + TraceUtil.endSection(); + TraceUtil.beginSection("startCodec"); + codec.start(); + TraceUtil.endSection(); + long codecInitializedTimestamp = SystemClock.elapsedRealtime(); + onCodecInitialized(codecName, codecInitializedTimestamp, + codecInitializedTimestamp - codecInitializingTimestamp); + inputBuffers = codec.getInputBuffers(); + outputBuffers = codec.getOutputBuffers(); + } catch (Exception e) { + throwDecoderInitError(new DecoderInitializationException(format, e, + drmSessionRequiresSecureDecoder, codecName)); + } + codecHotswapDeadlineMs = getState() == STATE_STARTED + ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : C.TIME_UNSET; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + waitingForFirstSyncFrame = true; + decoderCounters.decoderInitCount++; + } + + private void throwDecoderInitError(DecoderInitializationException e) + throws ExoPlaybackException { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + + protected boolean shouldInitCodec() { + return codec == null && format != null; + } + + protected final MediaCodec getCodec() { + return codec; + } + + @Override + protected void onEnabled(boolean joining) throws ExoPlaybackException { + decoderCounters = new DecoderCounters(); + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { + inputStreamEnded = false; + outputStreamEnded = false; + if (codec != null) { + flushCodec(); + } + } + + @Override + protected void onDisabled() { + format = null; + try { + releaseCodec(); + } finally { + try { + if (drmSession != null) { + drmSessionManager.releaseSession(drmSession); + } + } finally { + try { + if (pendingDrmSession != null && pendingDrmSession != drmSession) { + drmSessionManager.releaseSession(pendingDrmSession); + } + } finally { + drmSession = null; + pendingDrmSession = null; + } + } + } + } + + protected void releaseCodec() { + if (codec != null) { + codecHotswapDeadlineMs = C.TIME_UNSET; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + waitingForKeys = false; + shouldSkipOutputBuffer = false; + decodeOnlyPresentationTimestamps.clear(); + inputBuffers = null; + outputBuffers = null; + codecReconfigured = false; + codecReceivedBuffers = false; + codecIsAdaptive = false; + codecNeedsDiscardToSpsWorkaround = false; + codecNeedsFlushWorkaround = false; + codecNeedsAdaptationWorkaround = false; + codecNeedsEosPropagationWorkaround = false; + codecNeedsEosFlushWorkaround = false; + codecNeedsMonoChannelCountWorkaround = false; + codecNeedsAdaptationWorkaroundBuffer = false; + shouldSkipAdaptationWorkaroundOutputBuffer = false; + codecReceivedEos = false; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + codecReinitializationState = REINITIALIZATION_STATE_NONE; + decoderCounters.decoderReleaseCount++; + buffer.data = null; + try { + codec.stop(); + } finally { + try { + codec.release(); + } finally { + codec = null; + if (drmSession != null && pendingDrmSession != drmSession) { + try { + drmSessionManager.releaseSession(drmSession); + } finally { + drmSession = null; + } + } + } + } + } + } + + @Override + protected void onStarted() { + // Do nothing. Overridden to remove throws clause. + } + + @Override + protected void onStopped() { + // Do nothing. Overridden to remove throws clause. + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (outputStreamEnded) { + renderToEndOfStream(); + return; + } + if (format == null) { + // We don't have a format yet, so try and read one. + buffer.clear(); + int result = readSource(formatHolder, flagsOnlyBuffer, true); + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + } else if (result == C.RESULT_BUFFER_READ) { + // End of stream read having not read a format. + Assertions.checkState(flagsOnlyBuffer.isEndOfStream()); + inputStreamEnded = true; + processEndOfStream(); + return; + } else { + // We still don't have a format and can't make progress without one. + return; + } + } + // We have a format. + maybeInitCodec(); + if (codec != null) { + TraceUtil.beginSection("drainAndFeed"); + while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {} + while (feedInputBuffer()) {} + TraceUtil.endSection(); + } else { + skipSource(positionUs); + // We need to read any format changes despite not having a codec so that drmSession can be + // updated, and so that we have the most recent format should the codec be initialized. We may + // also reach the end of the stream. Note that readSource will not read a sample into a + // flags-only buffer. + flagsOnlyBuffer.clear(); + int result = readSource(formatHolder, flagsOnlyBuffer, false); + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + } else if (result == C.RESULT_BUFFER_READ) { + Assertions.checkState(flagsOnlyBuffer.isEndOfStream()); + inputStreamEnded = true; + processEndOfStream(); + } + } + decoderCounters.ensureUpdated(); + } + + protected void flushCodec() throws ExoPlaybackException { + codecHotswapDeadlineMs = C.TIME_UNSET; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + waitingForFirstSyncFrame = true; + waitingForKeys = false; + shouldSkipOutputBuffer = false; + decodeOnlyPresentationTimestamps.clear(); + codecNeedsAdaptationWorkaroundBuffer = false; + shouldSkipAdaptationWorkaroundOutputBuffer = false; + if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { + releaseCodec(); + maybeInitCodec(); + } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { + // We're already waiting to release and re-initialize the codec. Since we're now flushing, + // there's no need to wait any longer. + releaseCodec(); + maybeInitCodec(); + } else { + // We can flush and re-use the existing decoder. + codec.flush(); + codecReceivedBuffers = false; + } + if (codecReconfigured && format != null) { + // Any reconfiguration data that we send shortly before the flush may be discarded. We + // avoid this issue by sending reconfiguration data following every flush. + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + } + } + + /** + * @return Whether it may be possible to feed more input data. + * @throws ExoPlaybackException If an error occurs feeding the input buffer. + */ + private boolean feedInputBuffer() throws ExoPlaybackException { + if (codec == null || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM + || inputStreamEnded) { + // We need to reinitialize the codec or the input stream has ended. + return false; + } + + if (inputIndex < 0) { + inputIndex = codec.dequeueInputBuffer(0); + if (inputIndex < 0) { + return false; + } + buffer.data = inputBuffers[inputIndex]; + buffer.clear(); + } + + if (codecReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) { + // We need to re-initialize the codec. Send an end of stream signal to the existing codec so + // that it outputs any remaining buffers before we release it. + if (codecNeedsEosPropagationWorkaround) { + // Do nothing. + } else { + codecReceivedEos = true; + codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + inputIndex = C.INDEX_UNSET; + } + codecReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM; + return false; + } + + if (codecNeedsAdaptationWorkaroundBuffer) { + codecNeedsAdaptationWorkaroundBuffer = false; + buffer.data.put(ADAPTATION_WORKAROUND_BUFFER); + codec.queueInputBuffer(inputIndex, 0, ADAPTATION_WORKAROUND_BUFFER.length, 0, 0); + inputIndex = C.INDEX_UNSET; + codecReceivedBuffers = true; + return true; + } + + int result; + int adaptiveReconfigurationBytes = 0; + if (waitingForKeys) { + // We've already read an encrypted sample into buffer, and are waiting for keys. + result = C.RESULT_BUFFER_READ; + } else { + // For adaptive reconfiguration OMX decoders expect all reconfiguration data to be supplied + // at the start of the buffer that also contains the first frame in the new format. + if (codecReconfigurationState == RECONFIGURATION_STATE_WRITE_PENDING) { + for (int i = 0; i < format.initializationData.size(); i++) { + byte[] data = format.initializationData.get(i); + buffer.data.put(data); + } + codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING; + } + adaptiveReconfigurationBytes = buffer.data.position(); + result = readSource(formatHolder, buffer, false); + } + + if (result == C.RESULT_NOTHING_READ) { + return false; + } + if (result == C.RESULT_FORMAT_READ) { + if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { + // We received two formats in a row. Clear the current buffer of any reconfiguration data + // associated with the first format. + buffer.clear(); + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + } + onInputFormatChanged(formatHolder.format); + return true; + } + + // We've read a buffer. + if (buffer.isEndOfStream()) { + if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { + // We received a new format immediately before the end of the stream. We need to clear + // the corresponding reconfiguration data from the current buffer, but re-write it into + // a subsequent buffer if there are any (e.g. if the user seeks backwards). + buffer.clear(); + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + } + inputStreamEnded = true; + if (!codecReceivedBuffers) { + processEndOfStream(); + return false; + } + try { + if (codecNeedsEosPropagationWorkaround) { + // Do nothing. + } else { + codecReceivedEos = true; + codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + inputIndex = C.INDEX_UNSET; + } + } catch (CryptoException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + return false; + } + if (waitingForFirstSyncFrame && !buffer.isKeyFrame()) { + buffer.clear(); + if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { + // The buffer we just cleared contained reconfiguration data. We need to re-write this + // data into a subsequent buffer (if there is one). + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + } + return true; + } + waitingForFirstSyncFrame = false; + boolean bufferEncrypted = buffer.isEncrypted(); + waitingForKeys = shouldWaitForKeys(bufferEncrypted); + if (waitingForKeys) { + return false; + } + if (codecNeedsDiscardToSpsWorkaround && !bufferEncrypted) { + NalUnitUtil.discardToSps(buffer.data); + if (buffer.data.position() == 0) { + return true; + } + codecNeedsDiscardToSpsWorkaround = false; + } + try { + long presentationTimeUs = buffer.timeUs; + if (buffer.isDecodeOnly()) { + decodeOnlyPresentationTimestamps.add(presentationTimeUs); + } + + buffer.flip(); + onQueueInputBuffer(buffer); + + if (bufferEncrypted) { + MediaCodec.CryptoInfo cryptoInfo = getFrameworkCryptoInfo(buffer, + adaptiveReconfigurationBytes); + codec.queueSecureInputBuffer(inputIndex, 0, cryptoInfo, presentationTimeUs, 0); + } else { + codec.queueInputBuffer(inputIndex, 0, buffer.data.limit(), presentationTimeUs, 0); + } + inputIndex = C.INDEX_UNSET; + codecReceivedBuffers = true; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + decoderCounters.inputBufferCount++; + } catch (CryptoException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + return true; + } + + private static MediaCodec.CryptoInfo getFrameworkCryptoInfo(DecoderInputBuffer buffer, + int adaptiveReconfigurationBytes) { + MediaCodec.CryptoInfo cryptoInfo = buffer.cryptoInfo.getFrameworkCryptoInfoV16(); + if (adaptiveReconfigurationBytes == 0) { + return cryptoInfo; + } + // There must be at least one sub-sample, although numBytesOfClearData is permitted to be + // null if it contains no clear data. Instantiate it if needed, and add the reconfiguration + // bytes to the clear byte count of the first sub-sample. + if (cryptoInfo.numBytesOfClearData == null) { + cryptoInfo.numBytesOfClearData = new int[1]; + } + cryptoInfo.numBytesOfClearData[0] += adaptiveReconfigurationBytes; + return cryptoInfo; + } + + private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { + if (drmSession == null) { + return false; + } + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } + return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS + && (bufferEncrypted || !playClearSamplesWithoutKeys); + } + + /** + * Called when a {@link MediaCodec} has been created and configured. + *

    + * The default implementation is a no-op. + * + * @param name The name of the codec that was initialized. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. + * @param initializationDurationMs The time taken to initialize the codec in milliseconds. + */ + protected void onCodecInitialized(String name, long initializedTimestampMs, + long initializationDurationMs) { + // Do nothing. + } + + /** + * Called when a new format is read from the upstream {@link MediaPeriod}. + * + * @param newFormat The new format. + * @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}. + */ + protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + Format oldFormat = format; + format = newFormat; + + boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null + : oldFormat.drmInitData); + if (drmInitDataChanged) { + if (format.drmInitData != null) { + if (drmSessionManager == null) { + throw ExoPlaybackException.createForRenderer( + new IllegalStateException("Media requires a DrmSessionManager"), getIndex()); + } + pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), format.drmInitData); + if (pendingDrmSession == drmSession) { + drmSessionManager.releaseSession(pendingDrmSession); + } + } else { + pendingDrmSession = null; + } + } + + if (pendingDrmSession == drmSession && codec != null + && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) { + codecReconfigured = true; + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround + && format.width == oldFormat.width && format.height == oldFormat.height; + } else { + if (codecReceivedBuffers) { + // Signal end of stream and wait for any final output buffers before re-initialization. + codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; + } else { + // There aren't any final output buffers, so perform re-initialization immediately. + releaseCodec(); + maybeInitCodec(); + } + } + } + + /** + * Called when the output format of the {@link MediaCodec} changes. + *

    + * The default implementation is a no-op. + * + * @param codec The {@link MediaCodec} instance. + * @param outputFormat The new output format. + * @throws ExoPlaybackException Thrown if an error occurs handling the new output format. + */ + protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) + throws ExoPlaybackException { + // Do nothing. + } + + /** + * Called immediately before an input buffer is queued into the codec. + *

    + * The default implementation is a no-op. + * + * @param buffer The buffer to be queued. + */ + protected void onQueueInputBuffer(DecoderInputBuffer buffer) { + // Do nothing. + } + + /** + * Called when an output buffer is successfully processed. + *

    + * The default implementation is a no-op. + * + * @param presentationTimeUs The timestamp associated with the output buffer. + */ + protected void onProcessedOutputBuffer(long presentationTimeUs) { + // Do nothing. + } + + /** + * Determines whether the existing {@link MediaCodec} should be reconfigured for a new format by + * sending codec specific initialization data at the start of the next input buffer. If true is + * returned then the {@link MediaCodec} instance will be reconfigured in this way. If false is + * returned then the instance will be released, and a new instance will be created for the new + * format. + *

    + * The default implementation returns false. + * + * @param codec The existing {@link MediaCodec} instance. + * @param codecIsAdaptive Whether the codec is adaptive. + * @param oldFormat The format for which the existing instance is configured. + * @param newFormat The new format. + * @return Whether the existing instance can be reconfigured. + */ + protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, Format oldFormat, + Format newFormat) { + return false; + } + + @Override + public boolean isEnded() { + return outputStreamEnded; + } + + @Override + public boolean isReady() { + return format != null && !waitingForKeys && (isSourceReady() || outputIndex >= 0 + || (codecHotswapDeadlineMs != C.TIME_UNSET + && SystemClock.elapsedRealtime() < codecHotswapDeadlineMs)); + } + + /** + * Returns the maximum time to block whilst waiting for a decoded output buffer. + * + * @return The maximum time to block, in microseconds. + */ + protected long getDequeueOutputBufferTimeoutUs() { + return 0; + } + + /** + * @return Whether it may be possible to drain more output data. + * @throws ExoPlaybackException If an error occurs draining the output buffer. + */ + @SuppressWarnings("deprecation") + private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) + throws ExoPlaybackException { + if (outputIndex < 0) { + if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) { + try { + outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, + getDequeueOutputBufferTimeoutUs()); + } catch (IllegalStateException e) { + processEndOfStream(); + if (outputStreamEnded) { + // Release the codec, as it's in an error state. + releaseCodec(); + } + return false; + } + } else { + outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, + getDequeueOutputBufferTimeoutUs()); + } + if (outputIndex >= 0) { + // We've dequeued a buffer. + if (shouldSkipAdaptationWorkaroundOutputBuffer) { + shouldSkipAdaptationWorkaroundOutputBuffer = false; + codec.releaseOutputBuffer(outputIndex, false); + outputIndex = C.INDEX_UNSET; + return true; + } + if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + // The dequeued buffer indicates the end of the stream. Process it immediately. + processEndOfStream(); + outputIndex = C.INDEX_UNSET; + return false; + } else { + // The dequeued buffer is a media buffer. Do some initial setup. The buffer will be + // processed by calling processOutputBuffer (possibly multiple times) below. + ByteBuffer outputBuffer = outputBuffers[outputIndex]; + if (outputBuffer != null) { + outputBuffer.position(outputBufferInfo.offset); + outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size); + } + shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs); + } + } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) { + processOutputFormat(); + return true; + } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) { + processOutputBuffersChanged(); + return true; + } else /* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */ { + if (codecNeedsEosPropagationWorkaround && (inputStreamEnded + || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM)) { + processEndOfStream(); + } + return false; + } + } + + boolean processedOutputBuffer; + if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) { + try { + processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs, codec, + outputBuffers[outputIndex], outputIndex, outputBufferInfo.flags, + outputBufferInfo.presentationTimeUs, shouldSkipOutputBuffer); + } catch (IllegalStateException e) { + processEndOfStream(); + if (outputStreamEnded) { + // Release the codec, as it's in an error state. + releaseCodec(); + } + return false; + } + } else { + processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs, codec, + outputBuffers[outputIndex], outputIndex, outputBufferInfo.flags, + outputBufferInfo.presentationTimeUs, shouldSkipOutputBuffer); + } + + if (processedOutputBuffer) { + onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs); + outputIndex = C.INDEX_UNSET; + return true; + } + + return false; + } + + /** + * Processes a new output format. + */ + private void processOutputFormat() throws ExoPlaybackException { + MediaFormat format = codec.getOutputFormat(); + if (codecNeedsAdaptationWorkaround + && format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT + && format.getInteger(MediaFormat.KEY_HEIGHT) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT) { + // We assume this format changed event was caused by the adaptation workaround. + shouldSkipAdaptationWorkaroundOutputBuffer = true; + return; + } + if (codecNeedsMonoChannelCountWorkaround) { + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); + } + onOutputFormatChanged(codec, format); + } + + /** + * Processes a change in the output buffers. + */ + @SuppressWarnings("deprecation") + private void processOutputBuffersChanged() { + outputBuffers = codec.getOutputBuffers(); + } + + /** + * Processes an output media buffer. + *

    + * When a new {@link ByteBuffer} is passed to this method its position and limit delineate the + * data to be processed. The return value indicates whether the buffer was processed in full. If + * true is returned then the next call to this method will receive a new buffer to be processed. + * If false is returned then the same buffer will be passed to the next call. An implementation of + * this method is free to modify the buffer and can assume that the buffer will not be externally + * modified between successive calls. Hence an implementation can, for example, modify the + * buffer's position to keep track of how much of the data it has processed. + *

    + * Note that the first call to this method following a call to + * {@link #onPositionReset(long, boolean)} will always receive a new {@link ByteBuffer} to be + * processed. + * + * @param positionUs The current media time in microseconds, measured at the start of the + * current iteration of the rendering loop. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. + * @param codec The {@link MediaCodec} instance. + * @param buffer The output buffer to process. + * @param bufferIndex The index of the output buffer. + * @param bufferFlags The flags attached to the output buffer. + * @param bufferPresentationTimeUs The presentation time of the output buffer in microseconds. + * @param shouldSkip Whether the buffer should be skipped (i.e. not rendered). + * + * @return Whether the output buffer was fully processed (e.g. rendered or skipped). + * @throws ExoPlaybackException If an error occurs processing the output buffer. + */ + protected abstract boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, + MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags, + long bufferPresentationTimeUs, boolean shouldSkip) throws ExoPlaybackException; + + /** + * Incrementally renders any remaining output. + *

    + * The default implementation is a no-op. + * + * @throws ExoPlaybackException Thrown if an error occurs rendering remaining output. + */ + protected void renderToEndOfStream() throws ExoPlaybackException { + // Do nothing. + } + + /** + * Processes an end of stream signal. + * + * @throws ExoPlaybackException If an error occurs processing the signal. + */ + private void processEndOfStream() throws ExoPlaybackException { + if (codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { + // We're waiting to re-initialize the codec, and have now processed all final buffers. + releaseCodec(); + maybeInitCodec(); + } else { + outputStreamEnded = true; + renderToEndOfStream(); + } + } + + private boolean shouldSkipOutputBuffer(long presentationTimeUs) { + // We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would + // box presentationTimeUs, creating a Long object that would need to be garbage collected. + int size = decodeOnlyPresentationTimestamps.size(); + for (int i = 0; i < size; i++) { + if (decodeOnlyPresentationTimestamps.get(i) == presentationTimeUs) { + decodeOnlyPresentationTimestamps.remove(i); + return true; + } + } + return false; + } + + /** + * Returns whether the decoder is known to fail when flushed. + *

    + * If true is returned, the renderer will work around the issue by releasing the decoder and + * instantiating a new one rather than flushing the current instance. + *

    + * See [Internal: b/8347958, b/8543366]. + * + * @param name The name of the decoder. + * @return True if the decoder is known to fail when flushed. + */ + private static boolean codecNeedsFlushWorkaround(String name) { + return Util.SDK_INT < 18 + || (Util.SDK_INT == 18 + && ("OMX.SEC.avc.dec".equals(name) || "OMX.SEC.avc.dec.secure".equals(name))) + || (Util.SDK_INT == 19 && Util.MODEL.startsWith("SM-G800") + && ("OMX.Exynos.avc.dec".equals(name) || "OMX.Exynos.avc.dec.secure".equals(name))); + } + + /** + * Returns whether the decoder is known to get stuck during some adaptations where the resolution + * does not change. + *

    + * If true is returned, the renderer will work around the issue by queueing and discarding a blank + * frame at a different resolution, which resets the codec's internal state. + *

    + * See [Internal: b/27807182]. + * + * @param name The name of the decoder. + * @return True if the decoder is known to get stuck during some adaptations. + */ + private static boolean codecNeedsAdaptationWorkaround(String name) { + return Util.SDK_INT < 24 + && ("OMX.Nvidia.h264.decode".equals(name) || "OMX.Nvidia.h264.decode.secure".equals(name)) + && ("flounder".equals(Util.DEVICE) || "flounder_lte".equals(Util.DEVICE) + || "grouper".equals(Util.DEVICE) || "tilapia".equals(Util.DEVICE)); + } + + /** + * Returns whether the decoder is an H.264/AVC decoder known to fail if NAL units are queued + * before the codec specific data. + *

    + * If true is returned, the renderer will work around the issue by discarding data up to the SPS. + * + * @param name The name of the decoder. + * @param format The format used to configure the decoder. + * @return True if the decoder is known to fail if NAL units are queued before CSD. + */ + private static boolean codecNeedsDiscardToSpsWorkaround(String name, Format format) { + return Util.SDK_INT < 21 && format.initializationData.isEmpty() + && "OMX.MTK.VIDEO.DECODER.AVC".equals(name); + } + + /** + * Returns whether the decoder is known to handle the propagation of the + * {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag incorrectly on the host device. + *

    + * If true is returned, the renderer will work around the issue by approximating end of stream + * behavior without relying on the flag being propagated through to an output buffer by the + * underlying decoder. + * + * @param name The name of the decoder. + * @return True if the decoder is known to handle {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} + * propagation incorrectly on the host device. False otherwise. + */ + private static boolean codecNeedsEosPropagationWorkaround(String name) { + return Util.SDK_INT <= 17 && ("OMX.rk.video_decoder.avc".equals(name) + || "OMX.allwinner.video.decoder.avc".equals(name)); + } + + /** + * Returns whether the decoder is known to behave incorrectly if flushed after receiving an input + * buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set. + *

    + * If true is returned, the renderer will work around the issue by instantiating a new decoder + * when this case occurs. + *

    + * See [Internal: b/8578467, b/23361053]. + * + * @param name The name of the decoder. + * @return True if the decoder is known to behave incorrectly if flushed after receiving an input + * buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set. False otherwise. + */ + private static boolean codecNeedsEosFlushWorkaround(String name) { + return (Util.SDK_INT <= 23 && "OMX.google.vorbis.decoder".equals(name)) + || (Util.SDK_INT <= 19 && "hb2000".equals(Util.DEVICE) + && ("OMX.amlogic.avc.decoder.awesome".equals(name) + || "OMX.amlogic.avc.decoder.awesome.secure".equals(name))); + } + + /** + * Returns whether the decoder may throw an {@link IllegalStateException} from + * {@link MediaCodec#dequeueOutputBuffer(MediaCodec.BufferInfo, long)} or + * {@link MediaCodec#releaseOutputBuffer(int, boolean)} after receiving an input + * buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set. + *

    + * See [Internal: b/17933838]. + * + * @param name The name of the decoder. + * @return True if the decoder may throw an exception after receiving an end-of-stream buffer. + */ + private static boolean codecNeedsEosOutputExceptionWorkaround(String name) { + return Util.SDK_INT == 21 && "OMX.google.aac.decoder".equals(name); + } + + /** + * Returns whether the decoder is known to set the number of audio channels in the output format + * to 2 for the given input format, whilst only actually outputting a single channel. + *

    + * If true is returned then we explicitly override the number of channels in the output format, + * setting it to 1. + * + * @param name The decoder name. + * @param format The input format. + * @return True if the decoder is known to set the number of audio channels in the output format + * to 2 for the given input format, whilst only actually outputting a single channel. False + * otherwise. + */ + private static boolean codecNeedsMonoChannelCountWorkaround(String name, Format format) { + return Util.SDK_INT <= 18 && format.channelCount == 1 + && "OMX.MTK.AUDIO.DECODER.MP3".equals(name); + } + + /** + * Returns whether the decoder is known to fail when adapting, despite advertising itself as an + * adaptive decoder. + *

    + * If true is returned then we explicitly disable adaptation for the decoder. + * + * @param name The decoder name. + * @return True if the decoder is known to fail when adapting. + */ + private static boolean codecNeedsDisableAdaptationWorkaround(String name) { + return Util.SDK_INT <= 19 && Util.MODEL.equals("ODROID-XU3") + && ("OMX.Exynos.AVC.Decoder".equals(name) || "OMX.Exynos.AVC.Decoder.secure".equals(name)); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecSelector.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecSelector.java new file mode 100644 index 0000000..e2453c2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecSelector.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.mediacodec; + +import android.media.MediaCodec; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; + +/** + * Selector of {@link MediaCodec} instances. + */ +public interface MediaCodecSelector { + + /** + * Default implementation of {@link MediaCodecSelector}. + */ + MediaCodecSelector DEFAULT = new MediaCodecSelector() { + + @Override + public MediaCodecInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) + throws DecoderQueryException { + return MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder); + } + + @Override + public MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException { + return MediaCodecUtil.getPassthroughDecoderInfo(); + } + + }; + + /** + * Selects a decoder to instantiate for a given mime type. + * + * @param mimeType The mime type for which a decoder is required. + * @param requiresSecureDecoder Whether a secure decoder is required. + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder exists. + * @throws DecoderQueryException Thrown if there was an error querying decoders. + */ + MediaCodecInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) + throws DecoderQueryException; + + /** + * Selects a decoder to instantiate for audio passthrough. + * + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder + * exists. + * @throws DecoderQueryException Thrown if there was an error querying decoders. + */ + MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecUtil.java new file mode 100644 index 0000000..8846677 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/mediacodec/MediaCodecUtil.java @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.mediacodec; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecInfo.CodecProfileLevel; +import android.media.MediaCodecList; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.util.SparseIntArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A utility class for querying the available codecs. + */ +@TargetApi(16) +@SuppressLint("InlinedApi") +public final class MediaCodecUtil { + + /** + * Thrown when an error occurs querying the device for its underlying media capabilities. + *

    + * Such failures are not expected in normal operation and are normally temporary (e.g. if the + * mediaserver process has crashed and is yet to restart). + */ + public static class DecoderQueryException extends Exception { + + private DecoderQueryException(Throwable cause) { + super("Failed to query underlying media codecs", cause); + } + + } + + private static final String TAG = "MediaCodecUtil"; + private static final MediaCodecInfo PASSTHROUGH_DECODER_INFO = + MediaCodecInfo.newPassthroughInstance("OMX.google.raw.decoder"); + private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); + + private static final HashMap> decoderInfosCache = new HashMap<>(); + + // Codecs to constant mappings. + // AVC. + private static final SparseIntArray AVC_PROFILE_NUMBER_TO_CONST; + private static final SparseIntArray AVC_LEVEL_NUMBER_TO_CONST; + private static final String CODEC_ID_AVC1 = "avc1"; + private static final String CODEC_ID_AVC2 = "avc2"; + // HEVC. + private static final Map HEVC_CODEC_STRING_TO_PROFILE_LEVEL; + private static final String CODEC_ID_HEV1 = "hev1"; + private static final String CODEC_ID_HVC1 = "hvc1"; + + // Lazily initialized. + private static int maxH264DecodableFrameSize = -1; + + private MediaCodecUtil() {} + + /** + * Optional call to warm the codec cache for a given mime type. + *

    + * Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean)} + * and {@link #getDecoderInfos(String, boolean)}. + * + * @param mimeType The mime type. + * @param secure Whether the decoder is required to support secure decryption. Always pass false + * unless secure decryption really is required. + */ + public static void warmDecoderInfoCache(String mimeType, boolean secure) { + try { + getDecoderInfos(mimeType, secure); + } catch (DecoderQueryException e) { + // Codec warming is best effort, so we can swallow the exception. + Log.e(TAG, "Codec warming failed", e); + } + } + + /** + * Returns information about a decoder suitable for audio passthrough. + ** + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder + * exists. + */ + public static MediaCodecInfo getPassthroughDecoderInfo() { + // TODO: Return null if the raw decoder doesn't exist. + return PASSTHROUGH_DECODER_INFO; + } + + /** + * Returns information about the preferred decoder for a given mime type. + * + * @param mimeType The mime type. + * @param secure Whether the decoder is required to support secure decryption. Always pass false + * unless secure decryption really is required. + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder + * exists. + * @throws DecoderQueryException If there was an error querying the available decoders. + */ + public static MediaCodecInfo getDecoderInfo(String mimeType, boolean secure) + throws DecoderQueryException { + List decoderInfos = getDecoderInfos(mimeType, secure); + return decoderInfos.isEmpty() ? null : decoderInfos.get(0); + } + + /** + * Returns all {@link MediaCodecInfo}s for the given mime type, in the order given by + * {@link MediaCodecList}. + * + * @param mimeType The mime type. + * @param secure Whether the decoder is required to support secure decryption. Always pass false + * unless secure decryption really is required. + * @return A list of all @{link MediaCodecInfo}s for the given mime type, in the order + * given by {@link MediaCodecList}. + * @throws DecoderQueryException If there was an error querying the available decoders. + */ + public static synchronized List getDecoderInfos(String mimeType, + boolean secure) throws DecoderQueryException { + CodecKey key = new CodecKey(mimeType, secure); + List decoderInfos = decoderInfosCache.get(key); + if (decoderInfos != null) { + return decoderInfos; + } + MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21 + ? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16(); + decoderInfos = getDecoderInfosInternal(key, mediaCodecList); + if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) { + // Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the + // legacy path. We also try this path on API levels 22 and 23 as a defensive measure. + mediaCodecList = new MediaCodecListCompatV16(); + decoderInfos = getDecoderInfosInternal(key, mediaCodecList); + if (!decoderInfos.isEmpty()) { + Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType + + ". Assuming: " + decoderInfos.get(0).name); + } + } + decoderInfos = Collections.unmodifiableList(decoderInfos); + decoderInfosCache.put(key, decoderInfos); + return decoderInfos; + } + + private static List getDecoderInfosInternal( + CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { + try { + List decoderInfos = new ArrayList<>(); + String mimeType = key.mimeType; + int numberOfCodecs = mediaCodecList.getCodecCount(); + boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); + // Note: MediaCodecList is sorted by the framework such that the best decoders come first. + for (int i = 0; i < numberOfCodecs; i++) { + android.media.MediaCodecInfo codecInfo = mediaCodecList.getCodecInfoAt(i); + String codecName = codecInfo.getName(); + if (isCodecUsableDecoder(codecInfo, codecName, secureDecodersExplicit)) { + for (String supportedType : codecInfo.getSupportedTypes()) { + if (supportedType.equalsIgnoreCase(mimeType)) { + try { + CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(supportedType); + boolean secure = mediaCodecList.isSecurePlaybackSupported(mimeType, capabilities); + if ((secureDecodersExplicit && key.secure == secure) + || (!secureDecodersExplicit && !key.secure)) { + decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities)); + } else if (!secureDecodersExplicit && secure) { + decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", mimeType, + capabilities)); + // It only makes sense to have one synthesized secure decoder, return immediately. + return decoderInfos; + } + } catch (Exception e) { + if (Util.SDK_INT <= 23 && !decoderInfos.isEmpty()) { + // Suppress error querying secondary codec capabilities up to API level 23. + Log.e(TAG, "Skipping codec " + codecName + " (failed to query capabilities)"); + } else { + // Rethrow error querying primary codec capabilities, or secondary codec + // capabilities if API level is greater than 23. + Log.e(TAG, "Failed to query codec " + codecName + " (" + supportedType + ")"); + throw e; + } + } + } + } + } + } + return decoderInfos; + } catch (Exception e) { + // If the underlying mediaserver is in a bad state, we may catch an IllegalStateException + // or an IllegalArgumentException here. + throw new DecoderQueryException(e); + } + } + + /** + * Returns whether the specified codec is usable for decoding on the current device. + */ + private static boolean isCodecUsableDecoder(android.media.MediaCodecInfo info, String name, + boolean secureDecodersExplicit) { + if (info.isEncoder() || (!secureDecodersExplicit && name.endsWith(".secure"))) { + return false; + } + + // Work around broken audio decoders. + if (Util.SDK_INT < 21 + && ("CIPAACDecoder".equals(name) + || "CIPMP3Decoder".equals(name) + || "CIPVorbisDecoder".equals(name) + || "CIPAMRNBDecoder".equals(name) + || "AACDecoder".equals(name) + || "MP3Decoder".equals(name))) { + return false; + } + + // Work around https://github.com/google/ExoPlayer/issues/398 + if (Util.SDK_INT < 18 && "OMX.SEC.MP3.Decoder".equals(name)) { + return false; + } + + // Work around https://github.com/google/ExoPlayer/issues/1528 + if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) + && "a70".equals(Util.DEVICE)) { + return false; + } + + // Work around an issue where querying/creating a particular MP3 decoder on some devices on + // platform API version 16 fails. + if (Util.SDK_INT == 16 + && "OMX.qcom.audio.decoder.mp3".equals(name) + && ("dlxu".equals(Util.DEVICE) // HTC Butterfly + || "protou".equals(Util.DEVICE) // HTC Desire X + || "ville".equals(Util.DEVICE) // HTC One S + || "villeplus".equals(Util.DEVICE) + || "villec2".equals(Util.DEVICE) + || Util.DEVICE.startsWith("gee") // LGE Optimus G + || "C6602".equals(Util.DEVICE) // Sony Xperia Z + || "C6603".equals(Util.DEVICE) + || "C6606".equals(Util.DEVICE) + || "C6616".equals(Util.DEVICE) + || "L36h".equals(Util.DEVICE) + || "SO-02E".equals(Util.DEVICE))) { + return false; + } + + // Work around an issue where large timestamps are not propagated correctly. + if (Util.SDK_INT == 16 + && "OMX.qcom.audio.decoder.aac".equals(name) + && ("C1504".equals(Util.DEVICE) // Sony Xperia E + || "C1505".equals(Util.DEVICE) + || "C1604".equals(Util.DEVICE) // Sony Xperia E dual + || "C1605".equals(Util.DEVICE))) { + return false; + } + + // Work around https://github.com/google/ExoPlayer/issues/548 + // VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3/Note 2 does not render video. + if (Util.SDK_INT <= 19 + && "OMX.SEC.vp8.dec".equals(name) && "samsung".equals(Util.MANUFACTURER) + && (Util.DEVICE.startsWith("d2") || Util.DEVICE.startsWith("serrano") + || Util.DEVICE.startsWith("jflte") || Util.DEVICE.startsWith("santos") + || Util.DEVICE.startsWith("t0"))) { + return false; + } + + // VP8 decoder on Samsung Galaxy S4 cannot be queried. + if (Util.SDK_INT <= 19 && Util.DEVICE.startsWith("jflte") + && "OMX.qcom.video.decoder.vp8".equals(name)) { + return false; + } + + return true; + } + + /** + * Returns the maximum frame size supported by the default H264 decoder. + * + * @return The maximum frame size for an H264 stream that can be decoded on the device. + */ + public static int maxH264DecodableFrameSize() throws DecoderQueryException { + if (maxH264DecodableFrameSize == -1) { + int result = 0; + MediaCodecInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false); + if (decoderInfo != null) { + for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) { + result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result); + } + // We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are + // the levels mandated by the Android CDD. + result = Math.max(result, Util.SDK_INT >= 21 ? (720 * 480) : (480 * 360)); + } + maxH264DecodableFrameSize = result; + } + return maxH264DecodableFrameSize; + } + + /** + * Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the given + * codec description string (as defined by RFC 6381). + * + * @param codec A codec description string, as defined by RFC 6381. + * @return A pair (profile constant, level constant) if {@code codec} is well-formed and + * recognized, or null otherwise + */ + public static Pair getCodecProfileAndLevel(String codec) { + if (codec == null) { + return null; + } + String[] parts = codec.split("\\."); + switch (parts[0]) { + case CODEC_ID_HEV1: + case CODEC_ID_HVC1: + return getHevcProfileAndLevel(codec, parts); + case CODEC_ID_AVC1: + case CODEC_ID_AVC2: + return getAvcProfileAndLevel(codec, parts); + default: + return null; + } + } + + private static Pair getHevcProfileAndLevel(String codec, String[] parts) { + if (parts.length < 4) { + // The codec has fewer parts than required by the HEVC codec string format. + Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); + return null; + } + // The profile_space gets ignored. + Matcher matcher = PROFILE_PATTERN.matcher(parts[1]); + if (!matcher.matches()) { + Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); + return null; + } + String profileString = matcher.group(1); + int profile; + if ("1".equals(profileString)) { + profile = CodecProfileLevel.HEVCProfileMain; + } else if ("2".equals(profileString)) { + profile = CodecProfileLevel.HEVCProfileMain10; + } else { + Log.w(TAG, "Unknown HEVC profile string: " + profileString); + return null; + } + Integer level = HEVC_CODEC_STRING_TO_PROFILE_LEVEL.get(parts[3]); + if (level == null) { + Log.w(TAG, "Unknown HEVC level string: " + matcher.group(1)); + return null; + } + return new Pair<>(profile, level); + } + + private static Pair getAvcProfileAndLevel(String codec, String[] codecsParts) { + if (codecsParts.length < 2) { + // The codec has fewer parts than required by the AVC codec string format. + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + Integer profileInteger; + Integer levelInteger; + try { + if (codecsParts[1].length() == 6) { + // Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal. + profileInteger = Integer.parseInt(codecsParts[1].substring(0, 2), 16); + levelInteger = Integer.parseInt(codecsParts[1].substring(4), 16); + } else if (codecsParts.length >= 3) { + // Format: avc1.xx.[y]yy where xx is profile and [y]yy level, both decimal. + profileInteger = Integer.parseInt(codecsParts[1]); + levelInteger = Integer.parseInt(codecsParts[2]); + } else { + // We don't recognize the format. + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + + Integer profile = AVC_PROFILE_NUMBER_TO_CONST.get(profileInteger); + if (profile == null) { + Log.w(TAG, "Unknown AVC profile: " + profileInteger); + return null; + } + Integer level = AVC_LEVEL_NUMBER_TO_CONST.get(levelInteger); + if (level == null) { + Log.w(TAG, "Unknown AVC level: " + levelInteger); + return null; + } + return new Pair<>(profile, level); + } + + /** + * Conversion values taken from ISO 14496-10 Table A-1. + * + * @param avcLevel one of CodecProfileLevel.AVCLevel* constants. + * @return maximum frame size that can be decoded by a decoder with the specified avc level + * (or {@code -1} if the level is not recognized) + */ + private static int avcLevelToMaxFrameSize(int avcLevel) { + switch (avcLevel) { + case CodecProfileLevel.AVCLevel1: return 99 * 16 * 16; + case CodecProfileLevel.AVCLevel1b: return 99 * 16 * 16; + case CodecProfileLevel.AVCLevel12: return 396 * 16 * 16; + case CodecProfileLevel.AVCLevel13: return 396 * 16 * 16; + case CodecProfileLevel.AVCLevel2: return 396 * 16 * 16; + case CodecProfileLevel.AVCLevel21: return 792 * 16 * 16; + case CodecProfileLevel.AVCLevel22: return 1620 * 16 * 16; + case CodecProfileLevel.AVCLevel3: return 1620 * 16 * 16; + case CodecProfileLevel.AVCLevel31: return 3600 * 16 * 16; + case CodecProfileLevel.AVCLevel32: return 5120 * 16 * 16; + case CodecProfileLevel.AVCLevel4: return 8192 * 16 * 16; + case CodecProfileLevel.AVCLevel41: return 8192 * 16 * 16; + case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16; + case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16; + case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16; + default: return -1; + } + } + + private interface MediaCodecListCompat { + + /** + * The number of codecs in the list. + */ + int getCodecCount(); + + /** + * The info at the specified index in the list. + * + * @param index The index. + */ + android.media.MediaCodecInfo getCodecInfoAt(int index); + + /** + * Returns whether secure decoders are explicitly listed, if present. + */ + boolean secureDecodersExplicit(); + + /** + * Whether secure playback is supported for the given {@link CodecCapabilities}, which should + * have been obtained from a {@link android.media.MediaCodecInfo} obtained from this list. + */ + boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities); + + } + + @TargetApi(21) + private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { + + private final int codecKind; + + private android.media.MediaCodecInfo[] mediaCodecInfos; + + public MediaCodecListCompatV21(boolean includeSecure) { + codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; + } + + @Override + public int getCodecCount() { + ensureMediaCodecInfosInitialized(); + return mediaCodecInfos.length; + } + + @Override + public android.media.MediaCodecInfo getCodecInfoAt(int index) { + ensureMediaCodecInfosInitialized(); + return mediaCodecInfos[index]; + } + + @Override + public boolean secureDecodersExplicit() { + return true; + } + + @Override + public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) { + return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); + } + + private void ensureMediaCodecInfosInitialized() { + if (mediaCodecInfos == null) { + mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); + } + } + + } + + @SuppressWarnings("deprecation") + private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { + + @Override + public int getCodecCount() { + return MediaCodecList.getCodecCount(); + } + + @Override + public android.media.MediaCodecInfo getCodecInfoAt(int index) { + return MediaCodecList.getCodecInfoAt(index); + } + + @Override + public boolean secureDecodersExplicit() { + return false; + } + + @Override + public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) { + // Secure decoders weren't explicitly listed prior to API level 21. We assume that a secure + // H264 decoder exists. + return MimeTypes.VIDEO_H264.equals(mimeType); + } + + } + + private static final class CodecKey { + + public final String mimeType; + public final boolean secure; + + public CodecKey(String mimeType, boolean secure) { + this.mimeType = mimeType; + this.secure = secure; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); + result = prime * result + (secure ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != CodecKey.class) { + return false; + } + CodecKey other = (CodecKey) obj; + return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; + } + + } + + static { + AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray(); + AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline); + AVC_PROFILE_NUMBER_TO_CONST.put(77, CodecProfileLevel.AVCProfileMain); + AVC_PROFILE_NUMBER_TO_CONST.put(88, CodecProfileLevel.AVCProfileExtended); + AVC_PROFILE_NUMBER_TO_CONST.put(100, CodecProfileLevel.AVCProfileHigh); + + AVC_LEVEL_NUMBER_TO_CONST = new SparseIntArray(); + AVC_LEVEL_NUMBER_TO_CONST.put(10, CodecProfileLevel.AVCLevel1); + // TODO: Find int for CodecProfileLevel.AVCLevel1b. + AVC_LEVEL_NUMBER_TO_CONST.put(11, CodecProfileLevel.AVCLevel11); + AVC_LEVEL_NUMBER_TO_CONST.put(12, CodecProfileLevel.AVCLevel12); + AVC_LEVEL_NUMBER_TO_CONST.put(13, CodecProfileLevel.AVCLevel13); + AVC_LEVEL_NUMBER_TO_CONST.put(20, CodecProfileLevel.AVCLevel2); + AVC_LEVEL_NUMBER_TO_CONST.put(21, CodecProfileLevel.AVCLevel21); + AVC_LEVEL_NUMBER_TO_CONST.put(22, CodecProfileLevel.AVCLevel22); + AVC_LEVEL_NUMBER_TO_CONST.put(30, CodecProfileLevel.AVCLevel3); + AVC_LEVEL_NUMBER_TO_CONST.put(31, CodecProfileLevel.AVCLevel31); + AVC_LEVEL_NUMBER_TO_CONST.put(32, CodecProfileLevel.AVCLevel32); + AVC_LEVEL_NUMBER_TO_CONST.put(40, CodecProfileLevel.AVCLevel4); + AVC_LEVEL_NUMBER_TO_CONST.put(41, CodecProfileLevel.AVCLevel41); + AVC_LEVEL_NUMBER_TO_CONST.put(42, CodecProfileLevel.AVCLevel42); + AVC_LEVEL_NUMBER_TO_CONST.put(50, CodecProfileLevel.AVCLevel5); + AVC_LEVEL_NUMBER_TO_CONST.put(51, CodecProfileLevel.AVCLevel51); + AVC_LEVEL_NUMBER_TO_CONST.put(52, CodecProfileLevel.AVCLevel52); + + HEVC_CODEC_STRING_TO_PROFILE_LEVEL = new HashMap<>(); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L30", CodecProfileLevel.HEVCMainTierLevel1); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L60", CodecProfileLevel.HEVCMainTierLevel2); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L63", CodecProfileLevel.HEVCMainTierLevel21); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L90", CodecProfileLevel.HEVCMainTierLevel3); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L93", CodecProfileLevel.HEVCMainTierLevel31); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L120", CodecProfileLevel.HEVCMainTierLevel4); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L123", CodecProfileLevel.HEVCMainTierLevel41); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L150", CodecProfileLevel.HEVCMainTierLevel5); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L153", CodecProfileLevel.HEVCMainTierLevel51); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L156", CodecProfileLevel.HEVCMainTierLevel52); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L180", CodecProfileLevel.HEVCMainTierLevel6); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L183", CodecProfileLevel.HEVCMainTierLevel61); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L186", CodecProfileLevel.HEVCMainTierLevel62); + + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H30", CodecProfileLevel.HEVCHighTierLevel1); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H60", CodecProfileLevel.HEVCHighTierLevel2); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H63", CodecProfileLevel.HEVCHighTierLevel21); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H90", CodecProfileLevel.HEVCHighTierLevel3); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H93", CodecProfileLevel.HEVCHighTierLevel31); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H120", CodecProfileLevel.HEVCHighTierLevel4); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H123", CodecProfileLevel.HEVCHighTierLevel41); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H150", CodecProfileLevel.HEVCHighTierLevel5); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H153", CodecProfileLevel.HEVCHighTierLevel51); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H156", CodecProfileLevel.HEVCHighTierLevel52); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H180", CodecProfileLevel.HEVCHighTierLevel6); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H183", CodecProfileLevel.HEVCHighTierLevel61); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H186", CodecProfileLevel.HEVCHighTierLevel62); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/Metadata.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/Metadata.java new file mode 100644 index 0000000..a9344d6 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/Metadata.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata; + +import android.os.Parcel; +import android.os.Parcelable; +import java.util.Arrays; +import java.util.List; + +/** + * A collection of metadata entries. + */ +public final class Metadata implements Parcelable { + + /** + * A metadata entry. + */ + public interface Entry extends Parcelable {} + + private final Entry[] entries; + + /** + * @param entries The metadata entries. + */ + public Metadata(Entry... entries) { + this.entries = entries == null ? new Entry[0] : entries; + } + + /** + * @param entries The metadata entries. + */ + public Metadata(List entries) { + if (entries != null) { + this.entries = new Entry[entries.size()]; + entries.toArray(this.entries); + } else { + this.entries = new Entry[0]; + } + } + + /* package */ Metadata(Parcel in) { + entries = new Metadata.Entry[in.readInt()]; + for (int i = 0; i < entries.length; i++) { + entries[i] = in.readParcelable(Entry.class.getClassLoader()); + } + } + + /** + * Returns the number of metadata entries. + */ + public int length() { + return entries.length; + } + + /** + * Returns the entry at the specified index. + * + * @param index The index of the entry. + * @return The entry at the specified index. + */ + public Metadata.Entry get(int index) { + return entries[index]; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Metadata other = (Metadata) obj; + return Arrays.equals(entries, other.entries); + } + + @Override + public int hashCode() { + return Arrays.hashCode(entries); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(entries.length); + for (Entry entry : entries) { + dest.writeParcelable(entry, 0); + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Metadata createFromParcel(Parcel in) { + return new Metadata(in); + } + + @Override + public Metadata[] newArray(int size) { + return new Metadata[0]; + } + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoder.java new file mode 100644 index 0000000..e9a6548 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoder.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata; + +/** + * Decodes metadata from binary data. + */ +public interface MetadataDecoder { + + /** + * Decodes a {@link Metadata} element from the provided input buffer. + * + * @param inputBuffer The input buffer to decode. + * @return The decoded metadata object. + * @throws MetadataDecoderException If a problem occurred decoding the data. + */ + Metadata decode(MetadataInputBuffer inputBuffer) throws MetadataDecoderException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoderException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoderException.java new file mode 100644 index 0000000..c235e70 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoderException.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata; + +/** + * Thrown when an error occurs decoding metadata. + */ +public class MetadataDecoderException extends Exception { + + /** + * @param message The detail message for this exception. + */ + public MetadataDecoderException(String message) { + super(message); + } + + /** + * @param message The detail message for this exception. + * @param cause The cause of this exception. + */ + public MetadataDecoderException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoderFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoderFactory.java new file mode 100644 index 0000000..1bb46fb --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataDecoderFactory.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.emsg.EventMessageDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.Id3Decoder; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.scte35.SpliceInfoDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; + +/** + * A factory for {@link MetadataDecoder} instances. + */ +public interface MetadataDecoderFactory { + + /** + * Returns whether the factory is able to instantiate a {@link MetadataDecoder} for the given + * {@link Format}. + * + * @param format The {@link Format}. + * @return Whether the factory can instantiate a suitable {@link MetadataDecoder}. + */ + boolean supportsFormat(Format format); + + /** + * Creates a {@link MetadataDecoder} for the given {@link Format}. + * + * @param format The {@link Format}. + * @return A new {@link MetadataDecoder}. + * @throws IllegalArgumentException If the {@link Format} is not supported. + */ + MetadataDecoder createDecoder(Format format); + + /** + * Default {@link MetadataDecoder} implementation. + *

    + * The formats supported by this factory are: + *

      + *
    • ID3 ({@link Id3Decoder})
    • + *
    • EMSG ({@link EventMessageDecoder})
    • + *
    • SCTE-35 ({@link SpliceInfoDecoder})
    • + *
    + */ + MetadataDecoderFactory DEFAULT = new MetadataDecoderFactory() { + + @Override + public boolean supportsFormat(Format format) { + String mimeType = format.sampleMimeType; + return MimeTypes.APPLICATION_ID3.equals(mimeType) + || MimeTypes.APPLICATION_EMSG.equals(mimeType) + || MimeTypes.APPLICATION_SCTE35.equals(mimeType); + } + + @Override + public MetadataDecoder createDecoder(Format format) { + switch (format.sampleMimeType) { + case MimeTypes.APPLICATION_ID3: + return new Id3Decoder(); + case MimeTypes.APPLICATION_EMSG: + return new EventMessageDecoder(); + case MimeTypes.APPLICATION_SCTE35: + return new SpliceInfoDecoder(); + default: + throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); + } + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataInputBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataInputBuffer.java new file mode 100644 index 0000000..699de31 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataInputBuffer.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; + +/** + * A {@link DecoderInputBuffer} for a {@link MetadataDecoder}. + */ +public final class MetadataInputBuffer extends DecoderInputBuffer { + + /** + * An offset that must be added to the metadata's timestamps after it's been decoded, or + * {@link Format#OFFSET_SAMPLE_RELATIVE} if {@link #timeUs} should be added. + */ + public long subsampleOffsetUs; + + public MetadataInputBuffer() { + super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataRenderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataRenderer.java new file mode 100644 index 0000000..0737574 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/MetadataRenderer.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata; + +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Looper; +import android.os.Message; +import com.tangxiaolv.telegramgallery.exoplayer2.BaseRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.Arrays; + +/** + * A renderer for metadata. + */ +public final class MetadataRenderer extends BaseRenderer implements Callback { + + /** + * Receives output from a {@link MetadataRenderer}. + */ + public interface Output { + + /** + * Called each time there is a metadata associated with current playback time. + * + * @param metadata The metadata. + */ + void onMetadata(Metadata metadata); + + } + + private static final int MSG_INVOKE_RENDERER = 0; + // TODO: Holding multiple pending metadata objects is temporary mitigation against + // https://github.com/google/ExoPlayer/issues/1874 + // It should be removed once this issue has been addressed. + private static final int MAX_PENDING_METADATA_COUNT = 5; + + private final MetadataDecoderFactory decoderFactory; + private final Output output; + private final Handler outputHandler; + private final FormatHolder formatHolder; + private final MetadataInputBuffer buffer; + private final Metadata[] pendingMetadata; + private final long[] pendingMetadataTimestamps; + + private int pendingMetadataIndex; + private int pendingMetadataCount; + private MetadataDecoder decoder; + private boolean inputStreamEnded; + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be called. + * If the output makes use of standard Android UI components, then this should normally be the + * looper associated with the application's main thread, which can be obtained using + * {@link android.app.Activity#getMainLooper()}. Null may be passed if the output should be + * called directly on the player's internal rendering thread. + */ + public MetadataRenderer(Output output, Looper outputLooper) { + this(output, outputLooper, MetadataDecoderFactory.DEFAULT); + } + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be called. + * If the output makes use of standard Android UI components, then this should normally be the + * looper associated with the application's main thread, which can be obtained using + * {@link android.app.Activity#getMainLooper()}. Null may be passed if the output should be + * called directly on the player's internal rendering thread. + * @param decoderFactory A factory from which to obtain {@link MetadataDecoder} instances. + */ + public MetadataRenderer(Output output, Looper outputLooper, + MetadataDecoderFactory decoderFactory) { + super(C.TRACK_TYPE_METADATA); + this.output = Assertions.checkNotNull(output); + this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this); + this.decoderFactory = Assertions.checkNotNull(decoderFactory); + formatHolder = new FormatHolder(); + buffer = new MetadataInputBuffer(); + pendingMetadata = new Metadata[MAX_PENDING_METADATA_COUNT]; + pendingMetadataTimestamps = new long[MAX_PENDING_METADATA_COUNT]; + } + + @Override + public int supportsFormat(Format format) { + return decoderFactory.supportsFormat(format) ? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE; + } + + @Override + protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + decoder = decoderFactory.createDecoder(formats[0]); + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) { + flushPendingMetadata(); + inputStreamEnded = false; + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (!inputStreamEnded && pendingMetadataCount < MAX_PENDING_METADATA_COUNT) { + buffer.clear(); + int result = readSource(formatHolder, buffer, false); + if (result == C.RESULT_BUFFER_READ) { + if (buffer.isEndOfStream()) { + inputStreamEnded = true; + } else if (buffer.isDecodeOnly()) { + // Do nothing. Note this assumes that all metadata buffers can be decoded independently. + // If we ever need to support a metadata format where this is not the case, we'll need to + // pass the buffer to the decoder and discard the output. + } else { + buffer.subsampleOffsetUs = formatHolder.format.subsampleOffsetUs; + buffer.flip(); + try { + int index = (pendingMetadataIndex + pendingMetadataCount) % MAX_PENDING_METADATA_COUNT; + pendingMetadata[index] = decoder.decode(buffer); + pendingMetadataTimestamps[index] = buffer.timeUs; + pendingMetadataCount++; + } catch (MetadataDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + } + } + + if (pendingMetadataCount > 0 && pendingMetadataTimestamps[pendingMetadataIndex] <= positionUs) { + invokeRenderer(pendingMetadata[pendingMetadataIndex]); + pendingMetadata[pendingMetadataIndex] = null; + pendingMetadataIndex = (pendingMetadataIndex + 1) % MAX_PENDING_METADATA_COUNT; + pendingMetadataCount--; + } + } + + @Override + protected void onDisabled() { + flushPendingMetadata(); + decoder = null; + super.onDisabled(); + } + + @Override + public boolean isEnded() { + return inputStreamEnded; + } + + @Override + public boolean isReady() { + return true; + } + + private void invokeRenderer(Metadata metadata) { + if (outputHandler != null) { + outputHandler.obtainMessage(MSG_INVOKE_RENDERER, metadata).sendToTarget(); + } else { + invokeRendererInternal(metadata); + } + } + + private void flushPendingMetadata() { + Arrays.fill(pendingMetadata, null); + pendingMetadataIndex = 0; + pendingMetadataCount = 0; + } + + @SuppressWarnings("unchecked") + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_INVOKE_RENDERER: + invokeRendererInternal((Metadata) msg.obj); + return true; + default: + // Should never happen. + throw new IllegalStateException(); + } + } + + private void invokeRendererInternal(Metadata metadata) { + output.onMetadata(metadata); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/emsg/EventMessage.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/emsg/EventMessage.java new file mode 100644 index 0000000..c3508b7 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/emsg/EventMessage.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.emsg; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * An Event Message (emsg) as defined in ISO 23009-1. + */ +public final class EventMessage implements Metadata.Entry { + + /** + * The message scheme. + */ + public final String schemeIdUri; + + /** + * The value for the event. + */ + public final String value; + + /** + * The duration of the event in milliseconds. + */ + public final long durationMs; + + /** + * The instance identifier. + */ + public final long id; + + /** + * The body of the message. + */ + public final byte[] messageData; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * + * @param schemeIdUri The message scheme. + * @param value The value for the event. + * @param durationMs The duration of the event in milliseconds. + * @param id The instance identifier. + * @param messageData The body of the message. + */ + public EventMessage(String schemeIdUri, String value, long durationMs, long id, + byte[] messageData) { + this.schemeIdUri = schemeIdUri; + this.value = value; + this.durationMs = durationMs; + this.id = id; + this.messageData = messageData; + } + + /* package */ EventMessage(Parcel in) { + schemeIdUri = in.readString(); + value = in.readString(); + durationMs = in.readLong(); + id = in.readLong(); + messageData = in.createByteArray(); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + (schemeIdUri != null ? schemeIdUri.hashCode() : 0); + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (int) (durationMs ^ (durationMs >>> 32)); + result = 31 * result + (int) (id ^ (id >>> 32)); + result = 31 * result + Arrays.hashCode(messageData); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + EventMessage other = (EventMessage) obj; + return durationMs == other.durationMs && id == other.id + && Util.areEqual(schemeIdUri, other.schemeIdUri) && Util.areEqual(value, other.value) + && Arrays.equals(messageData, other.messageData); + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(schemeIdUri); + dest.writeString(value); + dest.writeLong(durationMs); + dest.writeLong(id); + dest.writeByteArray(messageData); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public EventMessage createFromParcel(Parcel in) { + return new EventMessage(in); + } + + @Override + public EventMessage[] newArray(int size) { + return new EventMessage[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/emsg/EventMessageDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/emsg/EventMessageDecoder.java new file mode 100644 index 0000000..14468c3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/emsg/EventMessageDecoder.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.emsg; + +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * Decodes Event Message (emsg) atoms, as defined in ISO 23009-1. + *

    + * Atom data should be provided to the decoder without the full atom header (i.e. starting from the + * first byte of the scheme_id_uri field). + */ +public final class EventMessageDecoder implements MetadataDecoder { + + @Override + public Metadata decode(MetadataInputBuffer inputBuffer) { + ByteBuffer buffer = inputBuffer.data; + byte[] data = buffer.array(); + int size = buffer.limit(); + ParsableByteArray emsgData = new ParsableByteArray(data, size); + String schemeIdUri = emsgData.readNullTerminatedString(); + String value = emsgData.readNullTerminatedString(); + long timescale = emsgData.readUnsignedInt(); + emsgData.skipBytes(4); // presentation_time_delta + long durationMs = (emsgData.readUnsignedInt() * 1000) / timescale; + long id = emsgData.readUnsignedInt(); + byte[] messageData = Arrays.copyOfRange(data, emsgData.getPosition(), size); + return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData)); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ApicFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ApicFrame.java new file mode 100644 index 0000000..4872ce0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ApicFrame.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * APIC (Attached Picture) ID3 frame. + */ +public final class ApicFrame extends Id3Frame { + + public static final String ID = "APIC"; + + public final String mimeType; + public final String description; + public final int pictureType; + public final byte[] pictureData; + + public ApicFrame(String mimeType, String description, int pictureType, byte[] pictureData) { + super(ID); + this.mimeType = mimeType; + this.description = description; + this.pictureType = pictureType; + this.pictureData = pictureData; + } + + /* package */ ApicFrame(Parcel in) { + super(ID); + mimeType = in.readString(); + description = in.readString(); + pictureType = in.readInt(); + pictureData = in.createByteArray(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ApicFrame other = (ApicFrame) obj; + return pictureType == other.pictureType && Util.areEqual(mimeType, other.mimeType) + && Util.areEqual(description, other.description) + && Arrays.equals(pictureData, other.pictureData); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + pictureType; + result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + Arrays.hashCode(pictureData); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mimeType); + dest.writeString(description); + dest.writeInt(pictureType); + dest.writeByteArray(pictureData); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public ApicFrame createFromParcel(Parcel in) { + return new ApicFrame(in); + } + + @Override + public ApicFrame[] newArray(int size) { + return new ApicFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/BinaryFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/BinaryFrame.java new file mode 100644 index 0000000..c67a299 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/BinaryFrame.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import java.util.Arrays; + +/** + * Binary ID3 frame. + */ +public final class BinaryFrame extends Id3Frame { + + public final byte[] data; + + public BinaryFrame(String id, byte[] data) { + super(id); + this.data = data; + } + + /* package */ BinaryFrame(Parcel in) { + super(in.readString()); + data = in.createByteArray(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BinaryFrame other = (BinaryFrame) obj; + return id.equals(other.id) && Arrays.equals(data, other.data); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + id.hashCode(); + result = 31 * result + Arrays.hashCode(data); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeByteArray(data); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public BinaryFrame createFromParcel(Parcel in) { + return new BinaryFrame(in); + } + + @Override + public BinaryFrame[] newArray(int size) { + return new BinaryFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ChapterFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ChapterFrame.java new file mode 100644 index 0000000..2404f64 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ChapterFrame.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * Chapter information ID3 frame. + */ +public final class ChapterFrame extends Id3Frame { + + public static final String ID = "CHAP"; + + public final String chapterId; + public final int startTimeMs; + public final int endTimeMs; + /** + * The byte offset of the start of the chapter, or {@link C#POSITION_UNSET} if not set. + */ + public final long startOffset; + /** + * The byte offset of the end of the chapter, or {@link C#POSITION_UNSET} if not set. + */ + public final long endOffset; + private final Id3Frame[] subFrames; + + public ChapterFrame(String chapterId, int startTimeMs, int endTimeMs, long startOffset, + long endOffset, Id3Frame[] subFrames) { + super(ID); + this.chapterId = chapterId; + this.startTimeMs = startTimeMs; + this.endTimeMs = endTimeMs; + this.startOffset = startOffset; + this.endOffset = endOffset; + this.subFrames = subFrames; + } + + /* package */ ChapterFrame(Parcel in) { + super(ID); + this.chapterId = in.readString(); + this.startTimeMs = in.readInt(); + this.endTimeMs = in.readInt(); + this.startOffset = in.readLong(); + this.endOffset = in.readLong(); + int subFrameCount = in.readInt(); + subFrames = new Id3Frame[subFrameCount]; + for (int i = 0; i < subFrameCount; i++) { + subFrames[i] = in.readParcelable(Id3Frame.class.getClassLoader()); + } + } + + /** + * Returns the number of sub-frames. + */ + public int getSubFrameCount() { + return subFrames.length; + } + + /** + * Returns the sub-frame at {@code index}. + */ + public Id3Frame getSubFrame(int index) { + return subFrames[index]; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ChapterFrame other = (ChapterFrame) obj; + return startTimeMs == other.startTimeMs + && endTimeMs == other.endTimeMs + && startOffset == other.startOffset + && endOffset == other.endOffset + && Util.areEqual(chapterId, other.chapterId) + && Arrays.equals(subFrames, other.subFrames); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + startTimeMs; + result = 31 * result + endTimeMs; + result = 31 * result + (int) startOffset; + result = 31 * result + (int) endOffset; + result = 31 * result + (chapterId != null ? chapterId.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(chapterId); + dest.writeInt(startTimeMs); + dest.writeInt(endTimeMs); + dest.writeLong(startOffset); + dest.writeLong(endOffset); + dest.writeInt(subFrames.length); + for (Id3Frame subFrame : subFrames) { + dest.writeParcelable(subFrame, 0); + } + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + + @Override + public ChapterFrame createFromParcel(Parcel in) { + return new ChapterFrame(in); + } + + @Override + public ChapterFrame[] newArray(int size) { + return new ChapterFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ChapterTocFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ChapterTocFrame.java new file mode 100644 index 0000000..d4d712a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/ChapterTocFrame.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * Chapter table of contents ID3 frame. + */ +public final class ChapterTocFrame extends Id3Frame { + + public static final String ID = "CTOC"; + + public final String elementId; + public final boolean isRoot; + public final boolean isOrdered; + public final String[] children; + private final Id3Frame[] subFrames; + + public ChapterTocFrame(String elementId, boolean isRoot, boolean isOrdered, String[] children, + Id3Frame[] subFrames) { + super(ID); + this.elementId = elementId; + this.isRoot = isRoot; + this.isOrdered = isOrdered; + this.children = children; + this.subFrames = subFrames; + } + + /* package */ ChapterTocFrame(Parcel in) { + super(ID); + this.elementId = in.readString(); + this.isRoot = in.readByte() != 0; + this.isOrdered = in.readByte() != 0; + this.children = in.createStringArray(); + int subFrameCount = in.readInt(); + subFrames = new Id3Frame[subFrameCount]; + for (int i = 0; i < subFrameCount; i++) { + subFrames[i] = in.readParcelable(Id3Frame.class.getClassLoader()); + } + } + + /** + * Returns the number of sub-frames. + */ + public int getSubFrameCount() { + return subFrames.length; + } + + /** + * Returns the sub-frame at {@code index}. + */ + public Id3Frame getSubFrame(int index) { + return subFrames[index]; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ChapterTocFrame other = (ChapterTocFrame) obj; + return isRoot == other.isRoot + && isOrdered == other.isOrdered + && Util.areEqual(elementId, other.elementId) + && Arrays.equals(children, other.children) + && Arrays.equals(subFrames, other.subFrames); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (isRoot ? 1 : 0); + result = 31 * result + (isOrdered ? 1 : 0); + result = 31 * result + (elementId != null ? elementId.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(elementId); + dest.writeByte((byte) (isRoot ? 1 : 0)); + dest.writeByte((byte) (isOrdered ? 1 : 0)); + dest.writeStringArray(children); + dest.writeInt(subFrames.length); + for (int i = 0; i < subFrames.length; i++) { + dest.writeParcelable(subFrames[i], 0); + } + } + + public static final Creator CREATOR = new Creator() { + + @Override + public ChapterTocFrame createFromParcel(Parcel in) { + return new ChapterTocFrame(in); + } + + @Override + public ChapterTocFrame[] newArray(int size) { + return new ChapterTocFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/CommentFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/CommentFrame.java new file mode 100644 index 0000000..117dad2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/CommentFrame.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Comment ID3 frame. + */ +public final class CommentFrame extends Id3Frame { + + public static final String ID = "COMM"; + + public final String language; + public final String description; + public final String text; + + public CommentFrame(String language, String description, String text) { + super(ID); + this.language = language; + this.description = description; + this.text = text; + } + + /* package */ CommentFrame(Parcel in) { + super(ID); + language = in.readString(); + description = in.readString(); + text = in.readString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + CommentFrame other = (CommentFrame) obj; + return Util.areEqual(description, other.description) && Util.areEqual(language, other.language) + && Util.areEqual(text, other.text); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (language != null ? language.hashCode() : 0); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + (text != null ? text.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(language); + dest.writeString(text); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public CommentFrame createFromParcel(Parcel in) { + return new CommentFrame(in); + } + + @Override + public CommentFrame[] newArray(int size) { + return new CommentFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/GeobFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/GeobFrame.java new file mode 100644 index 0000000..d9a5e07 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/GeobFrame.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * GEOB (General Encapsulated Object) ID3 frame. + */ +public final class GeobFrame extends Id3Frame { + + public static final String ID = "GEOB"; + + public final String mimeType; + public final String filename; + public final String description; + public final byte[] data; + + public GeobFrame(String mimeType, String filename, String description, byte[] data) { + super(ID); + this.mimeType = mimeType; + this.filename = filename; + this.description = description; + this.data = data; + } + + /* package */ GeobFrame(Parcel in) { + super(ID); + mimeType = in.readString(); + filename = in.readString(); + description = in.readString(); + data = in.createByteArray(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + GeobFrame other = (GeobFrame) obj; + return Util.areEqual(mimeType, other.mimeType) && Util.areEqual(filename, other.filename) + && Util.areEqual(description, other.description) && Arrays.equals(data, other.data); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0); + result = 31 * result + (filename != null ? filename.hashCode() : 0); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + Arrays.hashCode(data); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mimeType); + dest.writeString(filename); + dest.writeString(description); + dest.writeByteArray(data); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public GeobFrame createFromParcel(Parcel in) { + return new GeobFrame(in); + } + + @Override + public GeobFrame[] newArray(int size) { + return new GeobFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/Id3Decoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/Id3Decoder.java new file mode 100644 index 0000000..39430c0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/Id3Decoder.java @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * Decodes ID3 tags. + */ +public final class Id3Decoder implements MetadataDecoder { + + /** + * A predicate for determining whether individual frames should be decoded. + */ + public interface FramePredicate { + + /** + * Returns whether a frame with the specified parameters should be decoded. + * + * @param majorVersion The major version of the ID3 tag. + * @param id0 The first byte of the frame ID. + * @param id1 The second byte of the frame ID. + * @param id2 The third byte of the frame ID. + * @param id3 The fourth byte of the frame ID. + * @return Whether the frame should be decoded. + */ + boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3); + + } + + private static final String TAG = "Id3Decoder"; + + /** + * The first three bytes of a well formed ID3 tag header. + */ + public static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); + /** + * Length of an ID3 tag header. + */ + public static final int ID3_HEADER_LENGTH = 10; + + private static final int FRAME_FLAG_V3_IS_COMPRESSED = 0x0080; + private static final int FRAME_FLAG_V3_IS_ENCRYPTED = 0x0040; + private static final int FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER = 0x0020; + private static final int FRAME_FLAG_V4_IS_COMPRESSED = 0x0008; + private static final int FRAME_FLAG_V4_IS_ENCRYPTED = 0x0004; + private static final int FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER = 0x0040; + private static final int FRAME_FLAG_V4_IS_UNSYNCHRONIZED = 0x0002; + private static final int FRAME_FLAG_V4_HAS_DATA_LENGTH = 0x0001; + + private static final int ID3_TEXT_ENCODING_ISO_8859_1 = 0; + private static final int ID3_TEXT_ENCODING_UTF_16 = 1; + private static final int ID3_TEXT_ENCODING_UTF_16BE = 2; + private static final int ID3_TEXT_ENCODING_UTF_8 = 3; + + private final FramePredicate framePredicate; + + public Id3Decoder() { + this(null); + } + + /** + * @param framePredicate Determines which frames are decoded. May be null to decode all frames. + */ + public Id3Decoder(FramePredicate framePredicate) { + this.framePredicate = framePredicate; + } + + @Override + public Metadata decode(MetadataInputBuffer inputBuffer) { + ByteBuffer buffer = inputBuffer.data; + return decode(buffer.array(), buffer.limit()); + } + + /** + * Decodes ID3 tags. + * + * @param data The bytes to decode ID3 tags from. + * @param size Amount of bytes in {@code data} to read. + * @return A {@link Metadata} object containing the decoded ID3 tags. + */ + public Metadata decode(byte[] data, int size) { + List id3Frames = new ArrayList<>(); + ParsableByteArray id3Data = new ParsableByteArray(data, size); + + Id3Header id3Header = decodeHeader(id3Data); + if (id3Header == null) { + return null; + } + + int startPosition = id3Data.getPosition(); + int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10; + int framesSize = id3Header.framesSize; + if (id3Header.isUnsynchronized) { + framesSize = removeUnsynchronization(id3Data, id3Header.framesSize); + } + id3Data.setLimit(startPosition + framesSize); + + boolean unsignedIntFrameSizeHack = false; + if (!validateFrames(id3Data, id3Header.majorVersion, frameHeaderSize, false)) { + if (id3Header.majorVersion == 4 && validateFrames(id3Data, 4, frameHeaderSize, true)) { + unsignedIntFrameSizeHack = true; + } else { + return null; + } + } + + while (id3Data.bytesLeft() >= frameHeaderSize) { + Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack, + frameHeaderSize, framePredicate); + if (frame != null) { + id3Frames.add(frame); + } + } + + return new Metadata(id3Frames); + } + + /** + * @param data A {@link ParsableByteArray} from which the header should be read. + * @return The parsed header, or null if the ID3 tag is unsupported. + */ + private static Id3Header decodeHeader(ParsableByteArray data) { + if (data.bytesLeft() < ID3_HEADER_LENGTH) { + return null; + } + + int id = data.readUnsignedInt24(); + if (id != ID3_TAG) { + return null; + } + + int majorVersion = data.readUnsignedByte(); + data.skipBytes(1); // Skip minor version. + int flags = data.readUnsignedByte(); + int framesSize = data.readSynchSafeInt(); + + if (majorVersion == 2) { + boolean isCompressed = (flags & 0x40) != 0; + if (isCompressed) { + return null; + } + } else if (majorVersion == 3) { + boolean hasExtendedHeader = (flags & 0x40) != 0; + if (hasExtendedHeader) { + int extendedHeaderSize = data.readInt(); // Size excluding size field. + data.skipBytes(extendedHeaderSize); + framesSize -= (extendedHeaderSize + 4); + } + } else if (majorVersion == 4) { + boolean hasExtendedHeader = (flags & 0x40) != 0; + if (hasExtendedHeader) { + int extendedHeaderSize = data.readSynchSafeInt(); // Size including size field. + data.skipBytes(extendedHeaderSize - 4); + framesSize -= extendedHeaderSize; + } + boolean hasFooter = (flags & 0x10) != 0; + if (hasFooter) { + framesSize -= 10; + } + } else { + return null; + } + + // isUnsynchronized is advisory only in version 4. Frame level flags are used instead. + boolean isUnsynchronized = majorVersion < 4 && (flags & 0x80) != 0; + return new Id3Header(majorVersion, isUnsynchronized, framesSize); + } + + private static boolean validateFrames(ParsableByteArray id3Data, int majorVersion, + int frameHeaderSize, boolean unsignedIntFrameSizeHack) { + int startPosition = id3Data.getPosition(); + try { + while (id3Data.bytesLeft() >= frameHeaderSize) { + // Read the next frame header. + int id; + long frameSize; + int flags; + if (majorVersion >= 3) { + id = id3Data.readInt(); + frameSize = id3Data.readUnsignedInt(); + flags = id3Data.readUnsignedShort(); + } else { + id = id3Data.readUnsignedInt24(); + frameSize = id3Data.readUnsignedInt24(); + flags = 0; + } + // Validate the frame header and skip to the next one. + if (id == 0 && frameSize == 0 && flags == 0) { + // We've reached zero padding after the end of the final frame. + return true; + } else { + if (majorVersion == 4 && !unsignedIntFrameSizeHack) { + // Parse the data size as a synchsafe integer, as per the spec. + if ((frameSize & 0x808080L) != 0) { + return false; + } + frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7) + | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21); + } + boolean hasGroupIdentifier = false; + boolean hasDataLength = false; + if (majorVersion == 4) { + hasGroupIdentifier = (flags & FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER) != 0; + hasDataLength = (flags & FRAME_FLAG_V4_HAS_DATA_LENGTH) != 0; + } else if (majorVersion == 3) { + hasGroupIdentifier = (flags & FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER) != 0; + // A V3 frame has data length if and only if it's compressed. + hasDataLength = (flags & FRAME_FLAG_V3_IS_COMPRESSED) != 0; + } + int minimumFrameSize = 0; + if (hasGroupIdentifier) { + minimumFrameSize++; + } + if (hasDataLength) { + minimumFrameSize += 4; + } + if (frameSize < minimumFrameSize) { + return false; + } + if (id3Data.bytesLeft() < frameSize) { + return false; + } + id3Data.skipBytes((int) frameSize); // flags + } + } + return true; + } finally { + id3Data.setPosition(startPosition); + } + } + + private static Id3Frame decodeFrame(int majorVersion, ParsableByteArray id3Data, + boolean unsignedIntFrameSizeHack, int frameHeaderSize, FramePredicate framePredicate) { + int frameId0 = id3Data.readUnsignedByte(); + int frameId1 = id3Data.readUnsignedByte(); + int frameId2 = id3Data.readUnsignedByte(); + int frameId3 = majorVersion >= 3 ? id3Data.readUnsignedByte() : 0; + + int frameSize; + if (majorVersion == 4) { + frameSize = id3Data.readUnsignedIntToInt(); + if (!unsignedIntFrameSizeHack) { + frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7) + | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21); + } + } else if (majorVersion == 3) { + frameSize = id3Data.readUnsignedIntToInt(); + } else /* id3Header.majorVersion == 2 */ { + frameSize = id3Data.readUnsignedInt24(); + } + + int flags = majorVersion >= 3 ? id3Data.readUnsignedShort() : 0; + if (frameId0 == 0 && frameId1 == 0 && frameId2 == 0 && frameId3 == 0 && frameSize == 0 + && flags == 0) { + // We must be reading zero padding at the end of the tag. + id3Data.setPosition(id3Data.limit()); + return null; + } + + int nextFramePosition = id3Data.getPosition() + frameSize; + if (nextFramePosition > id3Data.limit()) { + id3Data.setPosition(id3Data.limit()); + return null; + } + + if (framePredicate != null + && !framePredicate.evaluate(majorVersion, frameId0, frameId1, frameId2, frameId3)) { + // Filtered by the predicate. + id3Data.setPosition(nextFramePosition); + return null; + } + + // Frame flags. + boolean isCompressed = false; + boolean isEncrypted = false; + boolean isUnsynchronized = false; + boolean hasDataLength = false; + boolean hasGroupIdentifier = false; + if (majorVersion == 3) { + isCompressed = (flags & FRAME_FLAG_V3_IS_COMPRESSED) != 0; + isEncrypted = (flags & FRAME_FLAG_V3_IS_ENCRYPTED) != 0; + hasGroupIdentifier = (flags & FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER) != 0; + // A V3 frame has data length if and only if it's compressed. + hasDataLength = isCompressed; + } else if (majorVersion == 4) { + hasGroupIdentifier = (flags & FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER) != 0; + isCompressed = (flags & FRAME_FLAG_V4_IS_COMPRESSED) != 0; + isEncrypted = (flags & FRAME_FLAG_V4_IS_ENCRYPTED) != 0; + isUnsynchronized = (flags & FRAME_FLAG_V4_IS_UNSYNCHRONIZED) != 0; + hasDataLength = (flags & FRAME_FLAG_V4_HAS_DATA_LENGTH) != 0; + } + + if (isCompressed || isEncrypted) { + id3Data.setPosition(nextFramePosition); + return null; + } + + if (hasGroupIdentifier) { + frameSize--; + id3Data.skipBytes(1); + } + if (hasDataLength) { + frameSize -= 4; + id3Data.skipBytes(4); + } + if (isUnsynchronized) { + frameSize = removeUnsynchronization(id3Data, frameSize); + } + + try { + Id3Frame frame; + if (frameId0 == 'T' && frameId1 == 'X' && frameId2 == 'X' + && (majorVersion == 2 || frameId3 == 'X')) { + frame = decodeTxxxFrame(id3Data, frameSize); + } else if (frameId0 == 'T') { + String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); + frame = decodeTextInformationFrame(id3Data, frameSize, id); + } else if (frameId0 == 'W' && frameId1 == 'X' && frameId2 == 'X' + && (majorVersion == 2 || frameId3 == 'X')) { + frame = decodeWxxxFrame(id3Data, frameSize); + } else if (frameId0 == 'W') { + String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); + frame = decodeUrlLinkFrame(id3Data, frameSize, id); + } else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') { + frame = decodePrivFrame(id3Data, frameSize); + } else if (frameId0 == 'G' && frameId1 == 'E' && frameId2 == 'O' + && (frameId3 == 'B' || majorVersion == 2)) { + frame = decodeGeobFrame(id3Data, frameSize); + } else if (majorVersion == 2 ? (frameId0 == 'P' && frameId1 == 'I' && frameId2 == 'C') + : (frameId0 == 'A' && frameId1 == 'P' && frameId2 == 'I' && frameId3 == 'C')) { + frame = decodeApicFrame(id3Data, frameSize, majorVersion); + } else if (frameId0 == 'C' && frameId1 == 'O' && frameId2 == 'M' + && (frameId3 == 'M' || majorVersion == 2)) { + frame = decodeCommentFrame(id3Data, frameSize); + } else if (frameId0 == 'C' && frameId1 == 'H' && frameId2 == 'A' && frameId3 == 'P') { + frame = decodeChapterFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, + frameHeaderSize, framePredicate); + } else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') { + frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, + frameHeaderSize, framePredicate); + } else { + String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); + frame = decodeBinaryFrame(id3Data, frameSize, id); + } + return frame; + } catch (UnsupportedEncodingException e) { + return null; + } finally { + id3Data.setPosition(nextFramePosition); + } + } + + private static TextInformationFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + if (frameSize < 1) { + // Frame is malformed. + return null; + } + + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + int descriptionEndIndex = indexOfEos(data, 0, encoding); + String description = new String(data, 0, descriptionEndIndex, charset); + + String value; + int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); + if (valueStartIndex < data.length) { + int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + value = new String(data, valueStartIndex, valueEndIndex - valueStartIndex, charset); + } else { + value = ""; + } + + return new TextInformationFrame("TXXX", description, value); + } + + private static TextInformationFrame decodeTextInformationFrame(ParsableByteArray id3Data, + int frameSize, String id) throws UnsupportedEncodingException { + if (frameSize < 1) { + // Frame is malformed. + return null; + } + + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + int valueEndIndex = indexOfEos(data, 0, encoding); + String value = new String(data, 0, valueEndIndex, charset); + + return new TextInformationFrame(id, null, value); + } + + private static UrlLinkFrame decodeWxxxFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + if (frameSize < 1) { + // Frame is malformed. + return null; + } + + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + int descriptionEndIndex = indexOfEos(data, 0, encoding); + String description = new String(data, 0, descriptionEndIndex, charset); + + String url; + int urlStartIndex = descriptionEndIndex + delimiterLength(encoding); + if (urlStartIndex < data.length) { + int urlEndIndex = indexOfZeroByte(data, urlStartIndex); + url = new String(data, urlStartIndex, urlEndIndex - urlStartIndex, "ISO-8859-1"); + } else { + url = ""; + } + + return new UrlLinkFrame("WXXX", description, url); + } + + private static UrlLinkFrame decodeUrlLinkFrame(ParsableByteArray id3Data, int frameSize, + String id) throws UnsupportedEncodingException { + byte[] data = new byte[frameSize]; + id3Data.readBytes(data, 0, frameSize); + + int urlEndIndex = indexOfZeroByte(data, 0); + String url = new String(data, 0, urlEndIndex, "ISO-8859-1"); + + return new UrlLinkFrame(id, null, url); + } + + private static PrivFrame decodePrivFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + byte[] data = new byte[frameSize]; + id3Data.readBytes(data, 0, frameSize); + + int ownerEndIndex = indexOfZeroByte(data, 0); + String owner = new String(data, 0, ownerEndIndex, "ISO-8859-1"); + + byte[] privateData; + int privateDataStartIndex = ownerEndIndex + 1; + if (privateDataStartIndex < data.length) { + privateData = Arrays.copyOfRange(data, privateDataStartIndex, data.length); + } else { + privateData = new byte[0]; + } + + return new PrivFrame(owner, privateData); + } + + private static GeobFrame decodeGeobFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + int mimeTypeEndIndex = indexOfZeroByte(data, 0); + String mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"); + + int filenameStartIndex = mimeTypeEndIndex + 1; + int filenameEndIndex = indexOfEos(data, filenameStartIndex, encoding); + String filename = new String(data, filenameStartIndex, filenameEndIndex - filenameStartIndex, + charset); + + int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding); + int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); + String description = new String(data, descriptionStartIndex, + descriptionEndIndex - descriptionStartIndex, charset); + + int objectDataStartIndex = descriptionEndIndex + delimiterLength(encoding); + byte[] objectData = Arrays.copyOfRange(data, objectDataStartIndex, data.length); + + return new GeobFrame(mimeType, filename, description, objectData); + } + + private static ApicFrame decodeApicFrame(ParsableByteArray id3Data, int frameSize, + int majorVersion) throws UnsupportedEncodingException { + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + String mimeType; + int mimeTypeEndIndex; + if (majorVersion == 2) { + mimeTypeEndIndex = 2; + mimeType = "image/" + Util.toLowerInvariant(new String(data, 0, 3, "ISO-8859-1")); + if (mimeType.equals("image/jpg")) { + mimeType = "image/jpeg"; + } + } else { + mimeTypeEndIndex = indexOfZeroByte(data, 0); + mimeType = Util.toLowerInvariant(new String(data, 0, mimeTypeEndIndex, "ISO-8859-1")); + if (mimeType.indexOf('/') == -1) { + mimeType = "image/" + mimeType; + } + } + + int pictureType = data[mimeTypeEndIndex + 1] & 0xFF; + + int descriptionStartIndex = mimeTypeEndIndex + 2; + int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); + String description = new String(data, descriptionStartIndex, + descriptionEndIndex - descriptionStartIndex, charset); + + int pictureDataStartIndex = descriptionEndIndex + delimiterLength(encoding); + byte[] pictureData = Arrays.copyOfRange(data, pictureDataStartIndex, data.length); + + return new ApicFrame(mimeType, description, pictureType, pictureData); + } + + private static CommentFrame decodeCommentFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + if (frameSize < 4) { + // Frame is malformed. + return null; + } + + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[3]; + id3Data.readBytes(data, 0, 3); + String language = new String(data, 0, 3); + + data = new byte[frameSize - 4]; + id3Data.readBytes(data, 0, frameSize - 4); + + int descriptionEndIndex = indexOfEos(data, 0, encoding); + String description = new String(data, 0, descriptionEndIndex, charset); + + String text; + int textStartIndex = descriptionEndIndex + delimiterLength(encoding); + if (textStartIndex < data.length) { + int textEndIndex = indexOfEos(data, textStartIndex, encoding); + text = new String(data, textStartIndex, textEndIndex - textStartIndex, charset); + } else { + text = ""; + } + + return new CommentFrame(language, description, text); + } + + private static ChapterFrame decodeChapterFrame(ParsableByteArray id3Data, int frameSize, + int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize, + FramePredicate framePredicate) throws UnsupportedEncodingException { + int framePosition = id3Data.getPosition(); + int chapterIdEndIndex = indexOfZeroByte(id3Data.data, framePosition); + String chapterId = new String(id3Data.data, framePosition, chapterIdEndIndex - framePosition, + "ISO-8859-1"); + id3Data.setPosition(chapterIdEndIndex + 1); + + int startTime = id3Data.readInt(); + int endTime = id3Data.readInt(); + long startOffset = id3Data.readUnsignedInt(); + if (startOffset == 0xFFFFFFFFL) { + startOffset = C.POSITION_UNSET; + } + long endOffset = id3Data.readUnsignedInt(); + if (endOffset == 0xFFFFFFFFL) { + endOffset = C.POSITION_UNSET; + } + + ArrayList subFrames = new ArrayList<>(); + int limit = framePosition + frameSize; + while (id3Data.getPosition() < limit) { + Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack, + frameHeaderSize, framePredicate); + if (frame != null) { + subFrames.add(frame); + } + } + + Id3Frame[] subFrameArray = new Id3Frame[subFrames.size()]; + subFrames.toArray(subFrameArray); + return new ChapterFrame(chapterId, startTime, endTime, startOffset, endOffset, subFrameArray); + } + + private static ChapterTocFrame decodeChapterTOCFrame(ParsableByteArray id3Data, int frameSize, + int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize, + FramePredicate framePredicate) throws UnsupportedEncodingException { + int framePosition = id3Data.getPosition(); + int elementIdEndIndex = indexOfZeroByte(id3Data.data, framePosition); + String elementId = new String(id3Data.data, framePosition, elementIdEndIndex - framePosition, + "ISO-8859-1"); + id3Data.setPosition(elementIdEndIndex + 1); + + int ctocFlags = id3Data.readUnsignedByte(); + boolean isRoot = (ctocFlags & 0x0002) != 0; + boolean isOrdered = (ctocFlags & 0x0001) != 0; + + int childCount = id3Data.readUnsignedByte(); + String[] children = new String[childCount]; + for (int i = 0; i < childCount; i++) { + int startIndex = id3Data.getPosition(); + int endIndex = indexOfZeroByte(id3Data.data, startIndex); + children[i] = new String(id3Data.data, startIndex, endIndex - startIndex, "ISO-8859-1"); + id3Data.setPosition(endIndex + 1); + } + + ArrayList subFrames = new ArrayList<>(); + int limit = framePosition + frameSize; + while (id3Data.getPosition() < limit) { + Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack, + frameHeaderSize, framePredicate); + if (frame != null) { + subFrames.add(frame); + } + } + + Id3Frame[] subFrameArray = new Id3Frame[subFrames.size()]; + subFrames.toArray(subFrameArray); + return new ChapterTocFrame(elementId, isRoot, isOrdered, children, subFrameArray); + } + + private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize, + String id) { + byte[] frame = new byte[frameSize]; + id3Data.readBytes(frame, 0, frameSize); + + return new BinaryFrame(id, frame); + } + + /** + * Performs in-place removal of unsynchronization for {@code length} bytes starting from + * {@link ParsableByteArray#getPosition()} + * + * @param data Contains the data to be processed. + * @param length The length of the data to be processed. + * @return The length of the data after processing. + */ + private static int removeUnsynchronization(ParsableByteArray data, int length) { + byte[] bytes = data.data; + for (int i = data.getPosition(); i + 1 < length; i++) { + if ((bytes[i] & 0xFF) == 0xFF && bytes[i + 1] == 0x00) { + System.arraycopy(bytes, i + 2, bytes, i + 1, length - i - 2); + length--; + } + } + return length; + } + + /** + * Maps encoding byte from ID3v2 frame to a Charset. + * + * @param encodingByte The value of encoding byte from ID3v2 frame. + * @return Charset name. + */ + private static String getCharsetName(int encodingByte) { + switch (encodingByte) { + case ID3_TEXT_ENCODING_ISO_8859_1: + return "ISO-8859-1"; + case ID3_TEXT_ENCODING_UTF_16: + return "UTF-16"; + case ID3_TEXT_ENCODING_UTF_16BE: + return "UTF-16BE"; + case ID3_TEXT_ENCODING_UTF_8: + return "UTF-8"; + default: + return "ISO-8859-1"; + } + } + + private static String getFrameId(int majorVersion, int frameId0, int frameId1, int frameId2, + int frameId3) { + return majorVersion == 2 ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) + : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + } + + private static int indexOfEos(byte[] data, int fromIndex, int encoding) { + int terminationPos = indexOfZeroByte(data, fromIndex); + + // For single byte encoding charsets, we're done. + if (encoding == ID3_TEXT_ENCODING_ISO_8859_1 || encoding == ID3_TEXT_ENCODING_UTF_8) { + return terminationPos; + } + + // Otherwise ensure an even index and look for a second zero byte. + while (terminationPos < data.length - 1) { + if (terminationPos % 2 == 0 && data[terminationPos + 1] == (byte) 0) { + return terminationPos; + } + terminationPos = indexOfZeroByte(data, terminationPos + 1); + } + + return data.length; + } + + private static int indexOfZeroByte(byte[] data, int fromIndex) { + for (int i = fromIndex; i < data.length; i++) { + if (data[i] == (byte) 0) { + return i; + } + } + return data.length; + } + + private static int delimiterLength(int encodingByte) { + return (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1 || encodingByte == ID3_TEXT_ENCODING_UTF_8) + ? 1 : 2; + } + + private static final class Id3Header { + + private final int majorVersion; + private final boolean isUnsynchronized; + private final int framesSize; + + public Id3Header(int majorVersion, boolean isUnsynchronized, int framesSize) { + this.majorVersion = majorVersion; + this.isUnsynchronized = isUnsynchronized; + this.framesSize = framesSize; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/Id3Frame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/Id3Frame.java new file mode 100644 index 0000000..66644ff --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/Id3Frame.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * Base class for ID3 frames. + */ +public abstract class Id3Frame implements Metadata.Entry { + + /** + * The frame ID. + */ + public final String id; + + public Id3Frame(String id) { + this.id = Assertions.checkNotNull(id); + } + + @Override + public int describeContents() { + return 0; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/PrivFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/PrivFrame.java new file mode 100644 index 0000000..8aed9e2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/PrivFrame.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * PRIV (Private) ID3 frame. + */ +public final class PrivFrame extends Id3Frame { + + public static final String ID = "PRIV"; + + public final String owner; + public final byte[] privateData; + + public PrivFrame(String owner, byte[] privateData) { + super(ID); + this.owner = owner; + this.privateData = privateData; + } + + /* package */ PrivFrame(Parcel in) { + super(ID); + owner = in.readString(); + privateData = in.createByteArray(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PrivFrame other = (PrivFrame) obj; + return Util.areEqual(owner, other.owner) && Arrays.equals(privateData, other.privateData); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (owner != null ? owner.hashCode() : 0); + result = 31 * result + Arrays.hashCode(privateData); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(owner); + dest.writeByteArray(privateData); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public PrivFrame createFromParcel(Parcel in) { + return new PrivFrame(in); + } + + @Override + public PrivFrame[] newArray(int size) { + return new PrivFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/TextInformationFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/TextInformationFrame.java new file mode 100644 index 0000000..a38b9a2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/TextInformationFrame.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Text information ID3 frame. + */ +public final class TextInformationFrame extends Id3Frame { + + public final String description; + public final String value; + + public TextInformationFrame(String id, String description, String value) { + super(id); + this.description = description; + this.value = value; + } + + /* package */ TextInformationFrame(Parcel in) { + super(in.readString()); + description = in.readString(); + value = in.readString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TextInformationFrame other = (TextInformationFrame) obj; + return id.equals(other.id) && Util.areEqual(description, other.description) + && Util.areEqual(value, other.value); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + id.hashCode(); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(description); + dest.writeString(value); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public TextInformationFrame createFromParcel(Parcel in) { + return new TextInformationFrame(in); + } + + @Override + public TextInformationFrame[] newArray(int size) { + return new TextInformationFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/UrlLinkFrame.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/UrlLinkFrame.java new file mode 100644 index 0000000..127e202 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/id3/UrlLinkFrame.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Url link ID3 frame. + */ +public final class UrlLinkFrame extends Id3Frame { + + public final String description; + public final String url; + + public UrlLinkFrame(String id, String description, String url) { + super(id); + this.description = description; + this.url = url; + } + + /* package */ UrlLinkFrame(Parcel in) { + super(in.readString()); + description = in.readString(); + url = in.readString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + UrlLinkFrame other = (UrlLinkFrame) obj; + return id.equals(other.id) && Util.areEqual(description, other.description) + && Util.areEqual(url, other.url); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + id.hashCode(); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + (url != null ? url.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(description); + dest.writeString(url); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public UrlLinkFrame createFromParcel(Parcel in) { + return new UrlLinkFrame(in); + } + + @Override + public UrlLinkFrame[] newArray(int size) { + return new UrlLinkFrame[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/PrivateCommand.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/PrivateCommand.java new file mode 100644 index 0000000..cf41dd1 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/PrivateCommand.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; + +/** + * Represents a private command as defined in SCTE35, Section 9.3.6. + */ +public final class PrivateCommand extends SpliceCommand { + + public final long ptsAdjustment; + public final long identifier; + public final byte[] commandBytes; + + private PrivateCommand(long identifier, byte[] commandBytes, long ptsAdjustment) { + this.ptsAdjustment = ptsAdjustment; + this.identifier = identifier; + this.commandBytes = commandBytes; + } + + private PrivateCommand(Parcel in) { + ptsAdjustment = in.readLong(); + identifier = in.readLong(); + commandBytes = new byte[in.readInt()]; + in.readByteArray(commandBytes); + } + + /* package */ static PrivateCommand parseFromSection(ParsableByteArray sectionData, + int commandLength, long ptsAdjustment) { + long identifier = sectionData.readUnsignedInt(); + byte[] privateBytes = new byte[commandLength - 4 /* identifier size */]; + sectionData.readBytes(privateBytes, 0, privateBytes.length); + return new PrivateCommand(identifier, privateBytes, ptsAdjustment); + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(ptsAdjustment); + dest.writeLong(identifier); + dest.writeInt(commandBytes.length); + dest.writeByteArray(commandBytes); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public PrivateCommand createFromParcel(Parcel in) { + return new PrivateCommand(in); + } + + @Override + public PrivateCommand[] newArray(int size) { + return new PrivateCommand[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceCommand.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceCommand.java new file mode 100644 index 0000000..78f2bde --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceCommand.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.scte35; + +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; + +/** + * Superclass for SCTE35 splice commands. + */ +public abstract class SpliceCommand implements Metadata.Entry { + + @Override + public int describeContents() { + return 0; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceInfoDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceInfoDecoder.java new file mode 100644 index 0000000..032dc50 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceInfoDecoder.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.scte35; + +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataDecoderException; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.MetadataInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import java.nio.ByteBuffer; + +/** + * Decodes splice info sections and produces splice commands. + */ +public final class SpliceInfoDecoder implements MetadataDecoder { + + private static final int TYPE_SPLICE_NULL = 0x00; + private static final int TYPE_SPLICE_SCHEDULE = 0x04; + private static final int TYPE_SPLICE_INSERT = 0x05; + private static final int TYPE_TIME_SIGNAL = 0x06; + private static final int TYPE_PRIVATE_COMMAND = 0xFF; + + private final ParsableByteArray sectionData; + private final ParsableBitArray sectionHeader; + + private TimestampAdjuster timestampAdjuster; + + public SpliceInfoDecoder() { + sectionData = new ParsableByteArray(); + sectionHeader = new ParsableBitArray(); + } + + @Override + public Metadata decode(MetadataInputBuffer inputBuffer) throws MetadataDecoderException { + // Internal timestamps adjustment. + if (timestampAdjuster == null + || inputBuffer.subsampleOffsetUs != timestampAdjuster.getTimestampOffsetUs()) { + timestampAdjuster = new TimestampAdjuster(inputBuffer.timeUs); + timestampAdjuster.adjustSampleTimestamp(inputBuffer.timeUs - inputBuffer.subsampleOffsetUs); + } + + ByteBuffer buffer = inputBuffer.data; + byte[] data = buffer.array(); + int size = buffer.limit(); + sectionData.reset(data, size); + sectionHeader.reset(data, size); + // table_id(8), section_syntax_indicator(1), private_indicator(1), reserved(2), + // section_length(12), protocol_version(8), encrypted_packet(1), encryption_algorithm(6). + sectionHeader.skipBits(39); + long ptsAdjustment = sectionHeader.readBits(1); + ptsAdjustment = (ptsAdjustment << 32) | sectionHeader.readBits(32); + // cw_index(8), tier(12). + sectionHeader.skipBits(20); + int spliceCommandLength = sectionHeader.readBits(12); + int spliceCommandType = sectionHeader.readBits(8); + SpliceCommand command = null; + // Go to the start of the command by skipping all fields up to command_type. + sectionData.skipBytes(14); + switch (spliceCommandType) { + case TYPE_SPLICE_NULL: + command = new SpliceNullCommand(); + break; + case TYPE_SPLICE_SCHEDULE: + command = SpliceScheduleCommand.parseFromSection(sectionData); + break; + case TYPE_SPLICE_INSERT: + command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment, + timestampAdjuster); + break; + case TYPE_TIME_SIGNAL: + command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment, timestampAdjuster); + break; + case TYPE_PRIVATE_COMMAND: + command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment); + break; + default: + // Do nothing. + break; + } + return command == null ? new Metadata() : new Metadata(command); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceInsertCommand.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceInsertCommand.java new file mode 100644 index 0000000..02e26fd --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceInsertCommand.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a splice insert command defined in SCTE35, Section 9.3.3. + */ +public final class SpliceInsertCommand extends SpliceCommand { + + public final long spliceEventId; + public final boolean spliceEventCancelIndicator; + public final boolean outOfNetworkIndicator; + public final boolean programSpliceFlag; + public final boolean spliceImmediateFlag; + public final long programSplicePts; + public final long programSplicePlaybackPositionUs; + public final List componentSpliceList; + public final boolean autoReturn; + public final long breakDuration; + public final int uniqueProgramId; + public final int availNum; + public final int availsExpected; + + private SpliceInsertCommand(long spliceEventId, boolean spliceEventCancelIndicator, + boolean outOfNetworkIndicator, boolean programSpliceFlag, boolean spliceImmediateFlag, + long programSplicePts, long programSplicePlaybackPositionUs, + List componentSpliceList, boolean autoReturn, long breakDuration, + int uniqueProgramId, int availNum, int availsExpected) { + this.spliceEventId = spliceEventId; + this.spliceEventCancelIndicator = spliceEventCancelIndicator; + this.outOfNetworkIndicator = outOfNetworkIndicator; + this.programSpliceFlag = programSpliceFlag; + this.spliceImmediateFlag = spliceImmediateFlag; + this.programSplicePts = programSplicePts; + this.programSplicePlaybackPositionUs = programSplicePlaybackPositionUs; + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.autoReturn = autoReturn; + this.breakDuration = breakDuration; + this.uniqueProgramId = uniqueProgramId; + this.availNum = availNum; + this.availsExpected = availsExpected; + } + + private SpliceInsertCommand(Parcel in) { + spliceEventId = in.readLong(); + spliceEventCancelIndicator = in.readByte() == 1; + outOfNetworkIndicator = in.readByte() == 1; + programSpliceFlag = in.readByte() == 1; + spliceImmediateFlag = in.readByte() == 1; + programSplicePts = in.readLong(); + programSplicePlaybackPositionUs = in.readLong(); + int componentSpliceListSize = in.readInt(); + List componentSpliceList = new ArrayList<>(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.add(ComponentSplice.createFromParcel(in)); + } + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + autoReturn = in.readByte() == 1; + breakDuration = in.readLong(); + uniqueProgramId = in.readInt(); + availNum = in.readInt(); + availsExpected = in.readInt(); + } + + /* package */ static SpliceInsertCommand parseFromSection(ParsableByteArray sectionData, + long ptsAdjustment, TimestampAdjuster timestampAdjuster) { + long spliceEventId = sectionData.readUnsignedInt(); + // splice_event_cancel_indicator(1), reserved(7). + boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0; + boolean outOfNetworkIndicator = false; + boolean programSpliceFlag = false; + boolean spliceImmediateFlag = false; + long programSplicePts = C.TIME_UNSET; + List componentSplices = Collections.emptyList(); + int uniqueProgramId = 0; + int availNum = 0; + int availsExpected = 0; + boolean autoReturn = false; + long duration = C.TIME_UNSET; + if (!spliceEventCancelIndicator) { + int headerByte = sectionData.readUnsignedByte(); + outOfNetworkIndicator = (headerByte & 0x80) != 0; + programSpliceFlag = (headerByte & 0x40) != 0; + boolean durationFlag = (headerByte & 0x20) != 0; + spliceImmediateFlag = (headerByte & 0x10) != 0; + if (programSpliceFlag && !spliceImmediateFlag) { + programSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment); + } + if (!programSpliceFlag) { + int componentCount = sectionData.readUnsignedByte(); + componentSplices = new ArrayList<>(componentCount); + for (int i = 0; i < componentCount; i++) { + int componentTag = sectionData.readUnsignedByte(); + long componentSplicePts = C.TIME_UNSET; + if (!spliceImmediateFlag) { + componentSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment); + } + componentSplices.add(new ComponentSplice(componentTag, componentSplicePts, + timestampAdjuster.adjustTsTimestamp(componentSplicePts))); + } + } + if (durationFlag) { + long firstByte = sectionData.readUnsignedByte(); + autoReturn = (firstByte & 0x80) != 0; + duration = ((firstByte & 0x01) << 32) | sectionData.readUnsignedInt(); + } + uniqueProgramId = sectionData.readUnsignedShort(); + availNum = sectionData.readUnsignedByte(); + availsExpected = sectionData.readUnsignedByte(); + } + return new SpliceInsertCommand(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator, + programSpliceFlag, spliceImmediateFlag, programSplicePts, + timestampAdjuster.adjustTsTimestamp(programSplicePts), componentSplices, autoReturn, + duration, uniqueProgramId, availNum, availsExpected); + } + + /** + * Holds splicing information for specific splice insert command components. + */ + public static final class ComponentSplice { + + public final int componentTag; + public final long componentSplicePts; + public final long componentSplicePlaybackPositionUs; + + private ComponentSplice(int componentTag, long componentSplicePts, + long componentSplicePlaybackPositionUs) { + this.componentTag = componentTag; + this.componentSplicePts = componentSplicePts; + this.componentSplicePlaybackPositionUs = componentSplicePlaybackPositionUs; + } + + public void writeToParcel(Parcel dest) { + dest.writeInt(componentTag); + dest.writeLong(componentSplicePts); + dest.writeLong(componentSplicePlaybackPositionUs); + } + + public static ComponentSplice createFromParcel(Parcel in) { + return new ComponentSplice(in.readInt(), in.readLong(), in.readLong()); + } + + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(spliceEventId); + dest.writeByte((byte) (spliceEventCancelIndicator ? 1 : 0)); + dest.writeByte((byte) (outOfNetworkIndicator ? 1 : 0)); + dest.writeByte((byte) (programSpliceFlag ? 1 : 0)); + dest.writeByte((byte) (spliceImmediateFlag ? 1 : 0)); + dest.writeLong(programSplicePts); + dest.writeLong(programSplicePlaybackPositionUs); + int componentSpliceListSize = componentSpliceList.size(); + dest.writeInt(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.get(i).writeToParcel(dest); + } + dest.writeByte((byte) (autoReturn ? 1 : 0)); + dest.writeLong(breakDuration); + dest.writeInt(uniqueProgramId); + dest.writeInt(availNum); + dest.writeInt(availsExpected); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SpliceInsertCommand createFromParcel(Parcel in) { + return new SpliceInsertCommand(in); + } + + @Override + public SpliceInsertCommand[] newArray(int size) { + return new SpliceInsertCommand[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceNullCommand.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceNullCommand.java new file mode 100644 index 0000000..117fb18 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceNullCommand.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.scte35; + +import android.os.Parcel; + +/** + * Represents a splice null command as defined in SCTE35, Section 9.3.1. + */ +public final class SpliceNullCommand extends SpliceCommand { + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + // Do nothing. + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public SpliceNullCommand createFromParcel(Parcel in) { + return new SpliceNullCommand(); + } + + @Override + public SpliceNullCommand[] newArray(int size) { + return new SpliceNullCommand[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceScheduleCommand.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceScheduleCommand.java new file mode 100644 index 0000000..039a189 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/SpliceScheduleCommand.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a splice schedule command as defined in SCTE35, Section 9.3.2. + */ +public final class SpliceScheduleCommand extends SpliceCommand { + + /** + * Represents a splice event as contained in a {@link SpliceScheduleCommand}. + */ + public static final class Event { + + public final long spliceEventId; + public final boolean spliceEventCancelIndicator; + public final boolean outOfNetworkIndicator; + public final boolean programSpliceFlag; + public final long utcSpliceTime; + public final List componentSpliceList; + public final boolean autoReturn; + public final long breakDuration; + public final int uniqueProgramId; + public final int availNum; + public final int availsExpected; + + private Event(long spliceEventId, boolean spliceEventCancelIndicator, + boolean outOfNetworkIndicator, boolean programSpliceFlag, + List componentSpliceList, long utcSpliceTime, boolean autoReturn, + long breakDuration, int uniqueProgramId, int availNum, int availsExpected) { + this.spliceEventId = spliceEventId; + this.spliceEventCancelIndicator = spliceEventCancelIndicator; + this.outOfNetworkIndicator = outOfNetworkIndicator; + this.programSpliceFlag = programSpliceFlag; + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.utcSpliceTime = utcSpliceTime; + this.autoReturn = autoReturn; + this.breakDuration = breakDuration; + this.uniqueProgramId = uniqueProgramId; + this.availNum = availNum; + this.availsExpected = availsExpected; + } + + private Event(Parcel in) { + this.spliceEventId = in.readLong(); + this.spliceEventCancelIndicator = in.readByte() == 1; + this.outOfNetworkIndicator = in.readByte() == 1; + this.programSpliceFlag = in.readByte() == 1; + int componentSpliceListLength = in.readInt(); + ArrayList componentSpliceList = new ArrayList<>(componentSpliceListLength); + for (int i = 0; i < componentSpliceListLength; i++) { + componentSpliceList.add(ComponentSplice.createFromParcel(in)); + } + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.utcSpliceTime = in.readLong(); + this.autoReturn = in.readByte() == 1; + this.breakDuration = in.readLong(); + this.uniqueProgramId = in.readInt(); + this.availNum = in.readInt(); + this.availsExpected = in.readInt(); + } + + private static Event parseFromSection(ParsableByteArray sectionData) { + long spliceEventId = sectionData.readUnsignedInt(); + // splice_event_cancel_indicator(1), reserved(7). + boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0; + boolean outOfNetworkIndicator = false; + boolean programSpliceFlag = false; + long utcSpliceTime = C.TIME_UNSET; + ArrayList componentSplices = new ArrayList<>(); + int uniqueProgramId = 0; + int availNum = 0; + int availsExpected = 0; + boolean autoReturn = false; + long duration = C.TIME_UNSET; + if (!spliceEventCancelIndicator) { + int headerByte = sectionData.readUnsignedByte(); + outOfNetworkIndicator = (headerByte & 0x80) != 0; + programSpliceFlag = (headerByte & 0x40) != 0; + boolean durationFlag = (headerByte & 0x20) != 0; + if (programSpliceFlag) { + utcSpliceTime = sectionData.readUnsignedInt(); + } + if (!programSpliceFlag) { + int componentCount = sectionData.readUnsignedByte(); + componentSplices = new ArrayList<>(componentCount); + for (int i = 0; i < componentCount; i++) { + int componentTag = sectionData.readUnsignedByte(); + long componentUtcSpliceTime = sectionData.readUnsignedInt(); + componentSplices.add(new ComponentSplice(componentTag, componentUtcSpliceTime)); + } + } + if (durationFlag) { + long firstByte = sectionData.readUnsignedByte(); + autoReturn = (firstByte & 0x80) != 0; + duration = ((firstByte & 0x01) << 32) | sectionData.readUnsignedInt(); + } + uniqueProgramId = sectionData.readUnsignedShort(); + availNum = sectionData.readUnsignedByte(); + availsExpected = sectionData.readUnsignedByte(); + } + return new Event(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator, + programSpliceFlag, componentSplices, utcSpliceTime, autoReturn, duration, uniqueProgramId, + availNum, availsExpected); + } + + private void writeToParcel(Parcel dest) { + dest.writeLong(spliceEventId); + dest.writeByte((byte) (spliceEventCancelIndicator ? 1 : 0)); + dest.writeByte((byte) (outOfNetworkIndicator ? 1 : 0)); + dest.writeByte((byte) (programSpliceFlag ? 1 : 0)); + int componentSpliceListSize = componentSpliceList.size(); + dest.writeInt(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.get(i).writeToParcel(dest); + } + dest.writeLong(utcSpliceTime); + dest.writeByte((byte) (autoReturn ? 1 : 0)); + dest.writeLong(breakDuration); + dest.writeInt(uniqueProgramId); + dest.writeInt(availNum); + dest.writeInt(availsExpected); + } + + private static Event createFromParcel(Parcel in) { + return new Event(in); + } + + } + + /** + * Holds splicing information for specific splice schedule command components. + */ + public static final class ComponentSplice { + + public final int componentTag; + public final long utcSpliceTime; + + private ComponentSplice(int componentTag, long utcSpliceTime) { + this.componentTag = componentTag; + this.utcSpliceTime = utcSpliceTime; + } + + private static ComponentSplice createFromParcel(Parcel in) { + return new ComponentSplice(in.readInt(), in.readLong()); + } + + private void writeToParcel(Parcel dest) { + dest.writeInt(componentTag); + dest.writeLong(utcSpliceTime); + } + + } + + public final List events; + + private SpliceScheduleCommand(List events) { + this.events = Collections.unmodifiableList(events); + } + + private SpliceScheduleCommand(Parcel in) { + int eventsSize = in.readInt(); + ArrayList events = new ArrayList<>(eventsSize); + for (int i = 0; i < eventsSize; i++) { + events.add(Event.createFromParcel(in)); + } + this.events = Collections.unmodifiableList(events); + } + + /* package */ static SpliceScheduleCommand parseFromSection(ParsableByteArray sectionData) { + int spliceCount = sectionData.readUnsignedByte(); + ArrayList events = new ArrayList<>(spliceCount); + for (int i = 0; i < spliceCount; i++) { + events.add(Event.parseFromSection(sectionData)); + } + return new SpliceScheduleCommand(events); + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + int eventsSize = events.size(); + dest.writeInt(eventsSize); + for (int i = 0; i < eventsSize; i++) { + events.get(i).writeToParcel(dest); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SpliceScheduleCommand createFromParcel(Parcel in) { + return new SpliceScheduleCommand(in); + } + + @Override + public SpliceScheduleCommand[] newArray(int size) { + return new SpliceScheduleCommand[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/TimeSignalCommand.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/TimeSignalCommand.java new file mode 100644 index 0000000..9d6fd76 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/metadata/scte35/TimeSignalCommand.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.metadata.scte35; + +import android.os.Parcel; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; + +/** + * Represents a time signal command as defined in SCTE35, Section 9.3.4. + */ +public final class TimeSignalCommand extends SpliceCommand { + + public final long ptsTime; + public final long playbackPositionUs; + + private TimeSignalCommand(long ptsTime, long playbackPositionUs) { + this.ptsTime = ptsTime; + this.playbackPositionUs = playbackPositionUs; + } + + /* package */ static TimeSignalCommand parseFromSection(ParsableByteArray sectionData, + long ptsAdjustment, TimestampAdjuster timestampAdjuster) { + long ptsTime = parseSpliceTime(sectionData, ptsAdjustment); + long playbackPositionUs = timestampAdjuster.adjustTsTimestamp(ptsTime); + return new TimeSignalCommand(ptsTime, playbackPositionUs); + } + + /** + * Parses pts_time from splice_time(), defined in Section 9.4.1. Returns {@link C#TIME_UNSET}, if + * time_specified_flag is false. + * + * @param sectionData The section data from which the pts_time is parsed. + * @param ptsAdjustment The pts adjustment provided by the splice info section header. + * @return The pts_time defined by splice_time(), or {@link C#TIME_UNSET}, if time_specified_flag + * is false. + */ + /* package */ static long parseSpliceTime(ParsableByteArray sectionData, long ptsAdjustment) { + long firstByte = sectionData.readUnsignedByte(); + long ptsTime = C.TIME_UNSET; + if ((firstByte & 0x80) != 0 /* time_specified_flag */) { + // See SCTE35 9.2.1 for more information about pts adjustment. + ptsTime = (firstByte & 0x01) << 32 | sectionData.readUnsignedInt(); + ptsTime += ptsAdjustment; + ptsTime &= 0x1FFFFFFFFL; + } + return ptsTime; + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(ptsTime); + dest.writeLong(playbackPositionUs); + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public TimeSignalCommand createFromParcel(Parcel in) { + return new TimeSignalCommand(in.readLong(), in.readLong()); + } + + @Override + public TimeSignalCommand[] newArray(int size) { + return new TimeSignalCommand[size]; + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/AdaptiveMediaSourceEventListener.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/AdaptiveMediaSourceEventListener.java new file mode 100644 index 0000000..24e41cc --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/AdaptiveMediaSourceEventListener.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.os.Handler; +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Interface for callbacks to be notified of adaptive {@link MediaSource} events. + */ +public interface AdaptiveMediaSourceEventListener { + + /** + * Called when a load begins. + * + * @param dataSpec Defines the data being loaded. + * @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data + * being loaded. + * @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds + * to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaStartTimeMs The start time of the media being loaded, or {@link C#TIME_UNSET} if + * the load is not for media data. + * @param mediaEndTimeMs The end time of the media being loaded, or {@link C#TIME_UNSET} if the + * load is not for media data. + * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load began. + */ + void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs); + + /** + * Called when a load ends. + * + * @param dataSpec Defines the data being loaded. + * @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data + * being loaded. + * @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds + * to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaStartTimeMs The start time of the media being loaded, or {@link C#TIME_UNSET} if + * the load is not for media data. + * @param mediaEndTimeMs The end time of the media being loaded, or {@link C#TIME_UNSET} if the + * load is not for media data. + * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load ended. + * @param loadDurationMs The duration of the load. + * @param bytesLoaded The number of bytes that were loaded. + */ + void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded); + + /** + * Called when a load is canceled. + * + * @param dataSpec Defines the data being loaded. + * @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data + * being loaded. + * @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds + * to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaStartTimeMs The start time of the media being loaded, or {@link C#TIME_UNSET} if + * the load is not for media data. + * @param mediaEndTimeMs The end time of the media being loaded, or {@link C#TIME_UNSET} if the + * load is not for media data. + * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load was + * canceled. + * @param loadDurationMs The duration of the load up to the point at which it was canceled. + * @param bytesLoaded The number of bytes that were loaded prior to cancelation. + */ + void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded); + + /** + * Called when a load error occurs. + *

    + * The error may or may not have resulted in the load being canceled, as indicated by the + * {@code wasCanceled} parameter. If the load was canceled, {@link #onLoadCanceled} will + * not be called in addition to this method. + * + * @param dataSpec Defines the data being loaded. + * @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data + * being loaded. + * @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds + * to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaStartTimeMs The start time of the media being loaded, or {@link C#TIME_UNSET} if + * the load is not for media data. + * @param mediaEndTimeMs The end time of the media being loaded, or {@link C#TIME_UNSET} if the + * load is not for media data. + * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the error + * occurred. + * @param loadDurationMs The duration of the load up to the point at which the error occurred. + * @param bytesLoaded The number of bytes that were loaded prior to the error. + * @param error The load error. + * @param wasCanceled Whether the load was canceled as a result of the error. + */ + void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded, + IOException error, boolean wasCanceled); + + /** + * Called when data is removed from the back of a media buffer, typically so that it can be + * re-buffered in a different format. + * + * @param trackType The type of the media. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @param mediaStartTimeMs The start time of the media being discarded. + * @param mediaEndTimeMs The end time of the media being discarded. + */ + void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs); + + /** + * Called when a downstream format change occurs (i.e. when the format of the media being read + * from one or more {@link SampleStream}s provided by the source changes). + * + * @param trackType The type of the media. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaTimeMs The media time at which the change occurred. + */ + void onDownstreamFormatChanged(int trackType, Format trackFormat, int trackSelectionReason, + Object trackSelectionData, long mediaTimeMs); + + /** + * Dispatches events to a {@link AdaptiveMediaSourceEventListener}. + */ + final class EventDispatcher { + + private final Handler handler; + private final AdaptiveMediaSourceEventListener listener; + private final long mediaTimeOffsetMs; + + public EventDispatcher(Handler handler, AdaptiveMediaSourceEventListener listener) { + this(handler, listener, 0); + } + + public EventDispatcher(Handler handler, AdaptiveMediaSourceEventListener listener, + long mediaTimeOffsetMs) { + this.handler = listener != null ? Assertions.checkNotNull(handler) : null; + this.listener = listener; + this.mediaTimeOffsetMs = mediaTimeOffsetMs; + } + + public EventDispatcher copyWithMediaTimeOffsetMs(long mediaTimeOffsetMs) { + return new EventDispatcher(handler, listener, mediaTimeOffsetMs); + } + + public void loadStarted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs) { + loadStarted(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, C.SELECTION_REASON_UNKNOWN, + null, C.TIME_UNSET, C.TIME_UNSET, elapsedRealtimeMs); + } + + public void loadStarted(final DataSpec dataSpec, final int dataType, final int trackType, + final Format trackFormat, final int trackSelectionReason, final Object trackSelectionData, + final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onLoadStarted(dataSpec, dataType, trackType, trackFormat, trackSelectionReason, + trackSelectionData, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs), elapsedRealtimeMs); + } + }); + } + } + + public void loadCompleted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded) { + loadCompleted(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, C.SELECTION_REASON_UNKNOWN, + null, C.TIME_UNSET, C.TIME_UNSET, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + } + + public void loadCompleted(final DataSpec dataSpec, final int dataType, final int trackType, + final Format trackFormat, final int trackSelectionReason, final Object trackSelectionData, + final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs, + final long loadDurationMs, final long bytesLoaded) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onLoadCompleted(dataSpec, dataType, trackType, trackFormat, + trackSelectionReason, trackSelectionData, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs, bytesLoaded); + } + }); + } + } + + public void loadCanceled(DataSpec dataSpec, int dataType, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded) { + loadCanceled(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, C.SELECTION_REASON_UNKNOWN, + null, C.TIME_UNSET, C.TIME_UNSET, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + } + + public void loadCanceled(final DataSpec dataSpec, final int dataType, final int trackType, + final Format trackFormat, final int trackSelectionReason, final Object trackSelectionData, + final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs, + final long loadDurationMs, final long bytesLoaded) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onLoadCanceled(dataSpec, dataType, trackType, trackFormat, + trackSelectionReason, trackSelectionData, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs, bytesLoaded); + } + }); + } + } + + public void loadError(DataSpec dataSpec, int dataType, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded, IOException error, boolean wasCanceled) { + loadError(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, C.SELECTION_REASON_UNKNOWN, + null, C.TIME_UNSET, C.TIME_UNSET, elapsedRealtimeMs, loadDurationMs, bytesLoaded, + error, wasCanceled); + } + + public void loadError(final DataSpec dataSpec, final int dataType, final int trackType, + final Format trackFormat, final int trackSelectionReason, final Object trackSelectionData, + final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs, + final long loadDurationMs, final long bytesLoaded, final IOException error, + final boolean wasCanceled) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onLoadError(dataSpec, dataType, trackType, trackFormat, trackSelectionReason, + trackSelectionData, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs, bytesLoaded, + error, wasCanceled); + } + }); + } + } + + public void upstreamDiscarded(final int trackType, final long mediaStartTimeUs, + final long mediaEndTimeUs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onUpstreamDiscarded(trackType, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs)); + } + }); + } + } + + public void downstreamFormatChanged(final int trackType, final Format trackFormat, + final int trackSelectionReason, final Object trackSelectionData, + final long mediaTimeUs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onDownstreamFormatChanged(trackType, trackFormat, trackSelectionReason, + trackSelectionData, adjustMediaTime(mediaTimeUs)); + } + }); + } + } + + private long adjustMediaTime(long mediaTimeUs) { + long mediaTimeMs = C.usToMs(mediaTimeUs); + return mediaTimeMs == C.TIME_UNSET ? C.TIME_UNSET : mediaTimeOffsetMs + mediaTimeMs; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/BehindLiveWindowException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/BehindLiveWindowException.java new file mode 100644 index 0000000..bd962a8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/BehindLiveWindowException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import java.io.IOException; + +/** + * Thrown when a live playback falls behind the available media window. + */ +public final class BehindLiveWindowException extends IOException { + + public BehindLiveWindowException() { + super(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ClippingMediaPeriod.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ClippingMediaPeriod.java new file mode 100644 index 0000000..0716bbc --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ClippingMediaPeriod.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Wraps a {@link MediaPeriod} and clips its {@link SampleStream}s to provide a subsequence of their + * samples. + */ +public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callback { + + /** + * The {@link MediaPeriod} wrapped by this clipping media period. + */ + public final MediaPeriod mediaPeriod; + + private MediaPeriod.Callback callback; + private long startUs; + private long endUs; + private ClippingSampleStream[] sampleStreams; + private boolean pendingInitialDiscontinuity; + + /** + * Creates a new clipping media period that provides a clipped view of the specified + * {@link MediaPeriod}'s sample streams. + *

    + * The clipping start/end positions must be specified by calling {@link #setClipping(long, long)} + * on the playback thread before preparation completes. + * + * @param mediaPeriod The media period to clip. + */ + public ClippingMediaPeriod(MediaPeriod mediaPeriod) { + this.mediaPeriod = mediaPeriod; + startUs = C.TIME_UNSET; + endUs = C.TIME_UNSET; + sampleStreams = new ClippingSampleStream[0]; + } + + /** + * Sets the clipping start/end times for this period, in microseconds. + * + * @param startUs The clipping start time, in microseconds. + * @param endUs The clipping end time, in microseconds, or {@link C#TIME_END_OF_SOURCE} to + * indicate the end of the period. + */ + public void setClipping(long startUs, long endUs) { + this.startUs = startUs; + this.endUs = endUs; + } + + @Override + public void prepare(MediaPeriod.Callback callback) { + this.callback = callback; + mediaPeriod.prepare(this); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + mediaPeriod.maybeThrowPrepareError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return mediaPeriod.getTrackGroups(); + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + sampleStreams = new ClippingSampleStream[streams.length]; + SampleStream[] internalStreams = new SampleStream[streams.length]; + for (int i = 0; i < streams.length; i++) { + sampleStreams[i] = (ClippingSampleStream) streams[i]; + internalStreams[i] = sampleStreams[i] != null ? sampleStreams[i].stream : null; + } + long enablePositionUs = mediaPeriod.selectTracks(selections, mayRetainStreamFlags, + internalStreams, streamResetFlags, positionUs + startUs); + Assertions.checkState(enablePositionUs == positionUs + startUs + || (enablePositionUs >= startUs + && (endUs == C.TIME_END_OF_SOURCE || enablePositionUs <= endUs))); + for (int i = 0; i < streams.length; i++) { + if (internalStreams[i] == null) { + sampleStreams[i] = null; + } else if (streams[i] == null || sampleStreams[i].stream != internalStreams[i]) { + sampleStreams[i] = new ClippingSampleStream(this, internalStreams[i], startUs, endUs, + pendingInitialDiscontinuity); + } + streams[i] = sampleStreams[i]; + } + return enablePositionUs - startUs; + } + + @Override + public void discardBuffer(long positionUs) { + mediaPeriod.discardBuffer(positionUs + startUs); + } + + @Override + public long readDiscontinuity() { + if (pendingInitialDiscontinuity) { + for (ClippingSampleStream sampleStream : sampleStreams) { + if (sampleStream != null) { + sampleStream.clearPendingDiscontinuity(); + } + } + pendingInitialDiscontinuity = false; + // Always read an initial discontinuity, using mediaPeriod's discontinuity if set. + long discontinuityUs = readDiscontinuity(); + return discontinuityUs != C.TIME_UNSET ? discontinuityUs : 0; + } + long discontinuityUs = mediaPeriod.readDiscontinuity(); + if (discontinuityUs == C.TIME_UNSET) { + return C.TIME_UNSET; + } + Assertions.checkState(discontinuityUs >= startUs); + Assertions.checkState(endUs == C.TIME_END_OF_SOURCE || discontinuityUs <= endUs); + return discontinuityUs - startUs; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = mediaPeriod.getBufferedPositionUs(); + if (bufferedPositionUs == C.TIME_END_OF_SOURCE + || (endUs != C.TIME_END_OF_SOURCE && bufferedPositionUs >= endUs)) { + return C.TIME_END_OF_SOURCE; + } + return Math.max(0, bufferedPositionUs - startUs); + } + + @Override + public long seekToUs(long positionUs) { + for (ClippingSampleStream sampleStream : sampleStreams) { + if (sampleStream != null) { + sampleStream.clearSentEos(); + } + } + long seekUs = mediaPeriod.seekToUs(positionUs + startUs); + Assertions.checkState(seekUs == positionUs + startUs + || (seekUs >= startUs && (endUs == C.TIME_END_OF_SOURCE || seekUs <= endUs))); + return seekUs - startUs; + } + + @Override + public long getNextLoadPositionUs() { + long nextLoadPositionUs = mediaPeriod.getNextLoadPositionUs(); + if (nextLoadPositionUs == C.TIME_END_OF_SOURCE + || (endUs != C.TIME_END_OF_SOURCE && nextLoadPositionUs >= endUs)) { + return C.TIME_END_OF_SOURCE; + } + return nextLoadPositionUs - startUs; + } + + @Override + public boolean continueLoading(long positionUs) { + return mediaPeriod.continueLoading(positionUs + startUs); + } + + // MediaPeriod.Callback implementation. + + @Override + public void onPrepared(MediaPeriod mediaPeriod) { + Assertions.checkState(startUs != C.TIME_UNSET && endUs != C.TIME_UNSET); + // If the clipping start position is non-zero, the clipping sample streams will adjust + // timestamps on buffers they read from the unclipped sample streams. These adjusted buffer + // timestamps can be negative, because sample streams provide buffers starting at a key-frame, + // which may be before the clipping start point. When the renderer reads a buffer with a + // negative timestamp, its offset timestamp can jump backwards compared to the last timestamp + // read in the previous period. Renderer implementations may not allow this, so we signal a + // discontinuity which resets the renderers before they read the clipping sample stream. + pendingInitialDiscontinuity = startUs != 0; + callback.onPrepared(this); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + callback.onContinueLoadingRequested(this); + } + + /** + * Wraps a {@link SampleStream} and clips its samples. + */ + private static final class ClippingSampleStream implements SampleStream { + + private final MediaPeriod mediaPeriod; + private final SampleStream stream; + private final long startUs; + private final long endUs; + + private boolean pendingDiscontinuity; + private boolean sentEos; + + public ClippingSampleStream(MediaPeriod mediaPeriod, SampleStream stream, long startUs, + long endUs, boolean pendingDiscontinuity) { + this.mediaPeriod = mediaPeriod; + this.stream = stream; + this.startUs = startUs; + this.endUs = endUs; + this.pendingDiscontinuity = pendingDiscontinuity; + } + + public void clearPendingDiscontinuity() { + pendingDiscontinuity = false; + } + + public void clearSentEos() { + sentEos = false; + } + + @Override + public boolean isReady() { + return stream.isReady(); + } + + @Override + public void maybeThrowError() throws IOException { + stream.maybeThrowError(); + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean requireFormat) { + if (pendingDiscontinuity) { + return C.RESULT_NOTHING_READ; + } + if (sentEos) { + buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + return C.RESULT_BUFFER_READ; + } + int result = stream.readData(formatHolder, buffer, requireFormat); + // TODO: Clear gapless playback metadata if a format was read (if applicable). + if (endUs != C.TIME_END_OF_SOURCE && ((result == C.RESULT_BUFFER_READ + && buffer.timeUs >= endUs) || (result == C.RESULT_NOTHING_READ + && mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE))) { + buffer.clear(); + buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + sentEos = true; + return C.RESULT_BUFFER_READ; + } + if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream()) { + buffer.timeUs -= startUs; + } + return result; + } + + @Override + public void skipData(long positionUs) { + stream.skipData(startUs + positionUs); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ClippingMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ClippingMediaSource.java new file mode 100644 index 0000000..8585945 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ClippingMediaSource.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.ArrayList; + +/** + * {@link MediaSource} that wraps a source and clips its timeline based on specified start/end + * positions. The wrapped source may only have a single period/window and it must not be dynamic + * (live). + */ +public final class ClippingMediaSource implements MediaSource, MediaSource.Listener { + + private final MediaSource mediaSource; + private final long startUs; + private final long endUs; + private final ArrayList mediaPeriods; + + private MediaSource.Listener sourceListener; + private ClippingTimeline clippingTimeline; + + /** + * Creates a new clipping source that wraps the specified source. + * + * @param mediaSource The single-period, non-dynamic source to wrap. + * @param startPositionUs The start position within {@code mediaSource}'s timeline at which to + * start providing samples, in microseconds. + * @param endPositionUs The end position within {@code mediaSource}'s timeline at which to stop + * providing samples, in microseconds. Specify {@link C#TIME_END_OF_SOURCE} to provide samples + * from the specified start point up to the end of the source. + */ + public ClippingMediaSource(MediaSource mediaSource, long startPositionUs, long endPositionUs) { + Assertions.checkArgument(startPositionUs >= 0); + this.mediaSource = Assertions.checkNotNull(mediaSource); + startUs = startPositionUs; + endUs = endPositionUs; + mediaPeriods = new ArrayList<>(); + } + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + this.sourceListener = listener; + mediaSource.prepareSource(player, false, this); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + mediaSource.maybeThrowSourceInfoRefreshError(); + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + ClippingMediaPeriod mediaPeriod = new ClippingMediaPeriod( + mediaSource.createPeriod(index, allocator, startUs + positionUs)); + mediaPeriods.add(mediaPeriod); + mediaPeriod.setClipping(clippingTimeline.startUs, clippingTimeline.endUs); + return mediaPeriod; + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + Assertions.checkState(mediaPeriods.remove(mediaPeriod)); + mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); + } + + @Override + public void releaseSource() { + mediaSource.releaseSource(); + } + + // MediaSource.Listener implementation. + + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + clippingTimeline = new ClippingTimeline(timeline, startUs, endUs); + sourceListener.onSourceInfoRefreshed(clippingTimeline, manifest); + long startUs = clippingTimeline.startUs; + long endUs = clippingTimeline.endUs == C.TIME_UNSET ? C.TIME_END_OF_SOURCE + : clippingTimeline.endUs; + int count = mediaPeriods.size(); + for (int i = 0; i < count; i++) { + mediaPeriods.get(i).setClipping(startUs, endUs); + } + } + + /** + * Provides a clipped view of a specified timeline. + */ + private static final class ClippingTimeline extends Timeline { + + private final Timeline timeline; + private final long startUs; + private final long endUs; + + /** + * Creates a new clipping timeline that wraps the specified timeline. + * + * @param timeline The timeline to clip. + * @param startUs The number of microseconds to clip from the start of {@code timeline}. + * @param endUs The end position in microseconds for the clipped timeline relative to the start + * of {@code timeline}, or {@link C#TIME_END_OF_SOURCE} to clip no samples from the end. + */ + public ClippingTimeline(Timeline timeline, long startUs, long endUs) { + Assertions.checkArgument(timeline.getWindowCount() == 1); + Assertions.checkArgument(timeline.getPeriodCount() == 1); + Window window = timeline.getWindow(0, new Window(), false); + Assertions.checkArgument(!window.isDynamic); + long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : endUs; + if (window.durationUs != C.TIME_UNSET) { + Assertions.checkArgument(startUs == 0 || window.isSeekable); + Assertions.checkArgument(resolvedEndUs <= window.durationUs); + Assertions.checkArgument(startUs <= resolvedEndUs); + } + Period period = timeline.getPeriod(0, new Period()); + Assertions.checkArgument(period.getPositionInWindowUs() == 0); + this.timeline = timeline; + this.startUs = startUs; + this.endUs = resolvedEndUs; + } + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + window = timeline.getWindow(0, window, setIds, defaultPositionProjectionUs); + window.durationUs = endUs != C.TIME_UNSET ? endUs - startUs : C.TIME_UNSET; + if (window.defaultPositionUs != C.TIME_UNSET) { + window.defaultPositionUs = Math.max(window.defaultPositionUs, startUs); + window.defaultPositionUs = endUs == C.TIME_UNSET ? window.defaultPositionUs + : Math.min(window.defaultPositionUs, endUs); + window.defaultPositionUs -= startUs; + } + long startMs = C.usToMs(startUs); + if (window.presentationStartTimeMs != C.TIME_UNSET) { + window.presentationStartTimeMs += startMs; + } + if (window.windowStartTimeMs != C.TIME_UNSET) { + window.windowStartTimeMs += startMs; + } + return window; + } + + @Override + public int getPeriodCount() { + return 1; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + period = timeline.getPeriod(0, period, setIds); + period.durationUs = endUs != C.TIME_UNSET ? endUs - startUs : C.TIME_UNSET; + return period; + } + + @Override + public int getIndexOfPeriod(Object uid) { + return timeline.getIndexOfPeriod(uid); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/CompositeSequenceableLoader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/CompositeSequenceableLoader.java new file mode 100644 index 0000000..3e536b7 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/CompositeSequenceableLoader.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +/** + * A {@link SequenceableLoader} that encapsulates multiple other {@link SequenceableLoader}s. + */ +public final class CompositeSequenceableLoader implements SequenceableLoader { + + private final SequenceableLoader[] loaders; + + public CompositeSequenceableLoader(SequenceableLoader[] loaders) { + this.loaders = loaders; + } + + @Override + public long getNextLoadPositionUs() { + long nextLoadPositionUs = Long.MAX_VALUE; + for (SequenceableLoader loader : loaders) { + long loaderNextLoadPositionUs = loader.getNextLoadPositionUs(); + if (loaderNextLoadPositionUs != C.TIME_END_OF_SOURCE) { + nextLoadPositionUs = Math.min(nextLoadPositionUs, loaderNextLoadPositionUs); + } + } + return nextLoadPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : nextLoadPositionUs; + } + + @Override + public boolean continueLoading(long positionUs) { + boolean madeProgress = false; + boolean madeProgressThisIteration; + do { + madeProgressThisIteration = false; + long nextLoadPositionUs = getNextLoadPositionUs(); + if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { + break; + } + for (SequenceableLoader loader : loaders) { + if (loader.getNextLoadPositionUs() == nextLoadPositionUs) { + madeProgressThisIteration |= loader.continueLoading(positionUs); + } + } + madeProgress |= madeProgressThisIteration; + } while (madeProgressThisIteration); + return madeProgress; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ConcatenatingMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ConcatenatingMediaSource.java new file mode 100644 index 0000000..2a20290 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ConcatenatingMediaSource.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * Concatenates multiple {@link MediaSource}s. It is valid for the same {@link MediaSource} instance + * to be present more than once in the concatenation. + */ +public final class ConcatenatingMediaSource implements MediaSource { + + private final MediaSource[] mediaSources; + private final Timeline[] timelines; + private final Object[] manifests; + private final Map sourceIndexByMediaPeriod; + private final boolean[] duplicateFlags; + + private Listener listener; + private ConcatenatedTimeline timeline; + + /** + * @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same + * {@link MediaSource} instance to be present more than once in the array. + */ + public ConcatenatingMediaSource(MediaSource... mediaSources) { + this.mediaSources = mediaSources; + timelines = new Timeline[mediaSources.length]; + manifests = new Object[mediaSources.length]; + sourceIndexByMediaPeriod = new HashMap<>(); + duplicateFlags = buildDuplicateFlags(mediaSources); + } + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + this.listener = listener; + for (int i = 0; i < mediaSources.length; i++) { + if (!duplicateFlags[i]) { + final int index = i; + mediaSources[i].prepareSource(player, false, new Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + handleSourceInfoRefreshed(index, timeline, manifest); + } + }); + } + } + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + for (int i = 0; i < mediaSources.length; i++) { + if (!duplicateFlags[i]) { + mediaSources[i].maybeThrowSourceInfoRefreshError(); + } + } + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + int sourceIndex = timeline.getSourceIndexForPeriod(index); + int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex); + MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator, + positionUs); + sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex); + return mediaPeriod; + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + int sourceIndex = sourceIndexByMediaPeriod.get(mediaPeriod); + sourceIndexByMediaPeriod.remove(mediaPeriod); + mediaSources[sourceIndex].releasePeriod(mediaPeriod); + } + + @Override + public void releaseSource() { + for (int i = 0; i < mediaSources.length; i++) { + if (!duplicateFlags[i]) { + mediaSources[i].releaseSource(); + } + } + } + + private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline, + Object sourceManifest) { + // Set the timeline and manifest. + timelines[sourceFirstIndex] = sourceTimeline; + manifests[sourceFirstIndex] = sourceManifest; + // Also set the timeline and manifest for any duplicate entries of the same source. + for (int i = sourceFirstIndex + 1; i < mediaSources.length; i++) { + if (mediaSources[i] == mediaSources[sourceFirstIndex]) { + timelines[i] = sourceTimeline; + manifests[i] = sourceManifest; + } + } + for (Timeline timeline : timelines) { + if (timeline == null) { + // Don't invoke the listener until all sources have timelines. + return; + } + } + timeline = new ConcatenatedTimeline(timelines.clone()); + listener.onSourceInfoRefreshed(timeline, manifests.clone()); + } + + private static boolean[] buildDuplicateFlags(MediaSource[] mediaSources) { + boolean[] duplicateFlags = new boolean[mediaSources.length]; + IdentityHashMap sources = new IdentityHashMap<>(mediaSources.length); + for (int i = 0; i < mediaSources.length; i++) { + MediaSource source = mediaSources[i]; + if (!sources.containsKey(source)) { + sources.put(source, null); + } else { + duplicateFlags[i] = true; + } + } + return duplicateFlags; + } + + /** + * A {@link Timeline} that is the concatenation of one or more {@link Timeline}s. + */ + private static final class ConcatenatedTimeline extends Timeline { + + private final Timeline[] timelines; + private final int[] sourcePeriodOffsets; + private final int[] sourceWindowOffsets; + + public ConcatenatedTimeline(Timeline[] timelines) { + int[] sourcePeriodOffsets = new int[timelines.length]; + int[] sourceWindowOffsets = new int[timelines.length]; + long periodCount = 0; + int windowCount = 0; + for (int i = 0; i < timelines.length; i++) { + Timeline timeline = timelines[i]; + periodCount += timeline.getPeriodCount(); + Assertions.checkState(periodCount <= Integer.MAX_VALUE, + "ConcatenatingMediaSource children contain too many periods"); + sourcePeriodOffsets[i] = (int) periodCount; + windowCount += timeline.getWindowCount(); + sourceWindowOffsets[i] = windowCount; + } + this.timelines = timelines; + this.sourcePeriodOffsets = sourcePeriodOffsets; + this.sourceWindowOffsets = sourceWindowOffsets; + } + + @Override + public int getWindowCount() { + return sourceWindowOffsets[sourceWindowOffsets.length - 1]; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + int sourceIndex = getSourceIndexForWindow(windowIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds, + defaultPositionProjectionUs); + window.firstPeriodIndex += firstPeriodIndexInSource; + window.lastPeriodIndex += firstPeriodIndexInSource; + return window; + } + + @Override + public int getPeriodCount() { + return sourcePeriodOffsets[sourcePeriodOffsets.length - 1]; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + int sourceIndex = getSourceIndexForPeriod(periodIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + timelines[sourceIndex].getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds); + period.windowIndex += firstWindowIndexInSource; + if (setIds) { + period.uid = Pair.create(sourceIndex, period.uid); + } + return period; + } + + @Override + public int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Pair)) { + return C.INDEX_UNSET; + } + Pair sourceIndexAndPeriodId = (Pair) uid; + if (!(sourceIndexAndPeriodId.first instanceof Integer)) { + return C.INDEX_UNSET; + } + int sourceIndex = (Integer) sourceIndexAndPeriodId.first; + Object periodId = sourceIndexAndPeriodId.second; + if (sourceIndex < 0 || sourceIndex >= timelines.length) { + return C.INDEX_UNSET; + } + int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(periodId); + return periodIndexInSource == C.INDEX_UNSET ? C.INDEX_UNSET + : getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource; + } + + private int getSourceIndexForPeriod(int periodIndex) { + return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1; + } + + private int getFirstPeriodIndexInSource(int sourceIndex) { + return sourceIndex == 0 ? 0 : sourcePeriodOffsets[sourceIndex - 1]; + } + + private int getSourceIndexForWindow(int windowIndex) { + return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1; + } + + private int getFirstWindowIndexInSource(int sourceIndex) { + return sourceIndex == 0 ? 0 : sourceWindowOffsets[sourceIndex - 1]; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/EmptySampleStream.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/EmptySampleStream.java new file mode 100644 index 0000000..6106792 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/EmptySampleStream.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import java.io.IOException; + +/** + * An empty {@link SampleStream}. + */ +public final class EmptySampleStream implements SampleStream { + + @Override + public boolean isReady() { + return true; + } + + @Override + public void maybeThrowError() throws IOException { + // Do nothing. + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired) { + buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + return C.RESULT_BUFFER_READ; + } + + @Override + public void skipData(long positionUs) { + // Do nothing. + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ExtractorMediaPeriod.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ExtractorMediaPeriod.java new file mode 100644 index 0000000..582edeb --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ExtractorMediaPeriod.java @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.net.Uri; +import android.os.Handler; +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultTrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultTrackOutput.UpstreamFormatChangedListener; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader.Loadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ConditionVariable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; + +/** + * A {@link MediaPeriod} that extracts data using an {@link Extractor}. + */ +/* package */ final class ExtractorMediaPeriod implements MediaPeriod, ExtractorOutput, + Loader.Callback, UpstreamFormatChangedListener { + + /** + * When the source's duration is unknown, it is calculated by adding this value to the largest + * sample timestamp seen when buffering completes. + */ + private static final long DEFAULT_LAST_SAMPLE_DURATION_US = 10000; + + private final Uri uri; + private final DataSource dataSource; + private final int minLoadableRetryCount; + private final Handler eventHandler; + private final ExtractorMediaSource.EventListener eventListener; + private final MediaSource.Listener sourceListener; + private final Allocator allocator; + private final String customCacheKey; + private final Loader loader; + private final ExtractorHolder extractorHolder; + private final ConditionVariable loadCondition; + private final Runnable maybeFinishPrepareRunnable; + private final Runnable onContinueLoadingRequestedRunnable; + private final Handler handler; + private final SparseArray sampleQueues; + + private Callback callback; + private SeekMap seekMap; + private boolean tracksBuilt; + private boolean prepared; + + private boolean seenFirstTrackSelection; + private boolean notifyReset; + private int enabledTrackCount; + private TrackGroupArray tracks; + private long durationUs; + private boolean[] trackEnabledStates; + private boolean[] trackIsAudioVideoFlags; + private boolean haveAudioVideoTracks; + private long length; + + private long lastSeekPositionUs; + private long pendingResetPositionUs; + + private int extractedSamplesCountAtStartOfLoad; + private boolean loadingFinished; + private boolean released; + + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSource The data source to read the media. + * @param extractors The extractors to use to read the data source. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param sourceListener A listener to notify when the timeline has been loaded. + * @param allocator An {@link Allocator} from which to obtain media buffer allocations. + * @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache + * indexing. May be null. + */ + public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, + int minLoadableRetryCount, Handler eventHandler, + ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, + Allocator allocator, String customCacheKey) { + this.uri = uri; + this.dataSource = dataSource; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.sourceListener = sourceListener; + this.allocator = allocator; + this.customCacheKey = customCacheKey; + loader = new Loader("Loader:ExtractorMediaPeriod"); + extractorHolder = new ExtractorHolder(extractors, this); + loadCondition = new ConditionVariable(); + maybeFinishPrepareRunnable = new Runnable() { + @Override + public void run() { + maybeFinishPrepare(); + } + }; + onContinueLoadingRequestedRunnable = new Runnable() { + @Override + public void run() { + if (!released) { + callback.onContinueLoadingRequested(ExtractorMediaPeriod.this); + } + } + }; + handler = new Handler(); + + pendingResetPositionUs = C.TIME_UNSET; + sampleQueues = new SparseArray<>(); + length = C.LENGTH_UNSET; + } + + public void release() { + final ExtractorHolder extractorHolder = this.extractorHolder; + loader.release(new Runnable() { + @Override + public void run() { + extractorHolder.release(); + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + sampleQueues.valueAt(i).disable(); + } + } + }); + handler.removeCallbacksAndMessages(null); + released = true; + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + loadCondition.open(); + startLoading(); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + maybeThrowError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return tracks; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + Assertions.checkState(prepared); + // Disable old tracks. + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { + int track = ((SampleStreamImpl) streams[i]).track; + Assertions.checkState(trackEnabledStates[track]); + enabledTrackCount--; + trackEnabledStates[track] = false; + sampleQueues.valueAt(track).disable(); + streams[i] = null; + } + } + // Enable new tracks. + boolean selectedNewTracks = false; + for (int i = 0; i < selections.length; i++) { + if (streams[i] == null && selections[i] != null) { + TrackSelection selection = selections[i]; + Assertions.checkState(selection.length() == 1); + Assertions.checkState(selection.getIndexInTrackGroup(0) == 0); + int track = tracks.indexOf(selection.getTrackGroup()); + Assertions.checkState(!trackEnabledStates[track]); + enabledTrackCount++; + trackEnabledStates[track] = true; + streams[i] = new SampleStreamImpl(track); + streamResetFlags[i] = true; + selectedNewTracks = true; + } + } + if (!seenFirstTrackSelection) { + // At the time of the first track selection all queues will be enabled, so we need to disable + // any that are no longer required. + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + if (!trackEnabledStates[i]) { + sampleQueues.valueAt(i).disable(); + } + } + } + if (enabledTrackCount == 0) { + notifyReset = false; + if (loader.isLoading()) { + loader.cancelLoading(); + } + } else if (seenFirstTrackSelection ? selectedNewTracks : positionUs != 0) { + positionUs = seekToUs(positionUs); + // We'll need to reset renderers consuming from all streams due to the seek. + for (int i = 0; i < streams.length; i++) { + if (streams[i] != null) { + streamResetFlags[i] = true; + } + } + } + seenFirstTrackSelection = true; + return positionUs; + } + + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + + @Override + public boolean continueLoading(long playbackPositionUs) { + if (loadingFinished || (prepared && enabledTrackCount == 0)) { + return false; + } + boolean continuedLoading = loadCondition.open(); + if (!loader.isLoading()) { + startLoading(); + continuedLoading = true; + } + return continuedLoading; + } + + @Override + public long getNextLoadPositionUs() { + return enabledTrackCount == 0 ? C.TIME_END_OF_SOURCE : getBufferedPositionUs(); + } + + @Override + public long readDiscontinuity() { + if (notifyReset) { + notifyReset = false; + return lastSeekPositionUs; + } + return C.TIME_UNSET; + } + + @Override + public long getBufferedPositionUs() { + if (loadingFinished) { + return C.TIME_END_OF_SOURCE; + } else if (isPendingReset()) { + return pendingResetPositionUs; + } + long largestQueuedTimestampUs; + if (haveAudioVideoTracks) { + // Ignore non-AV tracks, which may be sparse or poorly interleaved. + largestQueuedTimestampUs = Long.MAX_VALUE; + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + if (trackIsAudioVideoFlags[i]) { + largestQueuedTimestampUs = Math.min(largestQueuedTimestampUs, + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + } + } + } else { + largestQueuedTimestampUs = getLargestQueuedTimestampUs(); + } + return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs + : largestQueuedTimestampUs; + } + + @Override + public long seekToUs(long positionUs) { + // Treat all seeks into non-seekable media as being to t=0. + positionUs = seekMap.isSeekable() ? positionUs : 0; + lastSeekPositionUs = positionUs; + int trackCount = sampleQueues.size(); + // If we're not pending a reset, see if we can seek within the sample queues. + boolean seekInsideBuffer = !isPendingReset(); + for (int i = 0; seekInsideBuffer && i < trackCount; i++) { + if (trackEnabledStates[i]) { + seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs, false); + } + } + // If we failed to seek within the sample queues, we need to restart. + if (!seekInsideBuffer) { + pendingResetPositionUs = positionUs; + loadingFinished = false; + if (loader.isLoading()) { + loader.cancelLoading(); + } else { + for (int i = 0; i < trackCount; i++) { + sampleQueues.valueAt(i).reset(trackEnabledStates[i]); + } + } + } + notifyReset = false; + return positionUs; + } + + // SampleStream methods. + + /* package */ boolean isReady(int track) { + return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(track).isEmpty()); + } + + /* package */ void maybeThrowError() throws IOException { + loader.maybeThrowError(); + } + + /* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired) { + if (notifyReset || isPendingReset()) { + return C.RESULT_NOTHING_READ; + } + + return sampleQueues.valueAt(track).readData(formatHolder, buffer, formatRequired, + loadingFinished, lastSeekPositionUs); + } + + /* package */ void skipData(int track, long positionUs) { + DefaultTrackOutput sampleQueue = sampleQueues.valueAt(track); + if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { + sampleQueue.skipAll(); + } else { + sampleQueue.skipToKeyframeBefore(positionUs, true); + } + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(ExtractingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + copyLengthFromLoader(loadable); + loadingFinished = true; + if (durationUs == C.TIME_UNSET) { + long largestQueuedTimestampUs = getLargestQueuedTimestampUs(); + durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 + : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; + sourceListener.onSourceInfoRefreshed( + new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); + } + callback.onContinueLoadingRequested(this); + } + + @Override + public void onLoadCanceled(ExtractingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + copyLengthFromLoader(loadable); + if (!released && enabledTrackCount > 0) { + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + sampleQueues.valueAt(i).reset(trackEnabledStates[i]); + } + callback.onContinueLoadingRequested(this); + } + } + + @Override + public int onLoadError(ExtractingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + copyLengthFromLoader(loadable); + notifyLoadError(error); + if (isLoadableExceptionFatal(error)) { + return Loader.DONT_RETRY_FATAL; + } + int extractedSamplesCount = getExtractedSamplesCount(); + boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad; + configureRetry(loadable); // May reset the sample queues. + extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); + return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY; + } + + // ExtractorOutput implementation. Called by the loading thread. + + @Override + public TrackOutput track(int id, int type) { + DefaultTrackOutput trackOutput = sampleQueues.get(id); + if (trackOutput == null) { + trackOutput = new DefaultTrackOutput(allocator); + trackOutput.setUpstreamFormatChangeListener(this); + sampleQueues.put(id, trackOutput); + } + return trackOutput; + } + + @Override + public void endTracks() { + tracksBuilt = true; + handler.post(maybeFinishPrepareRunnable); + } + + @Override + public void seekMap(SeekMap seekMap) { + this.seekMap = seekMap; + handler.post(maybeFinishPrepareRunnable); + } + + // UpstreamFormatChangedListener implementation. Called by the loading thread. + + @Override + public void onUpstreamFormatChanged(Format format) { + handler.post(maybeFinishPrepareRunnable); + } + + // Internal methods. + + private void maybeFinishPrepare() { + if (released || prepared || seekMap == null || !tracksBuilt) { + return; + } + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + if (sampleQueues.valueAt(i).getUpstreamFormat() == null) { + return; + } + } + loadCondition.close(); + TrackGroup[] trackArray = new TrackGroup[trackCount]; + trackIsAudioVideoFlags = new boolean[trackCount]; + trackEnabledStates = new boolean[trackCount]; + durationUs = seekMap.getDurationUs(); + for (int i = 0; i < trackCount; i++) { + Format trackFormat = sampleQueues.valueAt(i).getUpstreamFormat(); + trackArray[i] = new TrackGroup(trackFormat); + String mimeType = trackFormat.sampleMimeType; + boolean isAudioVideo = MimeTypes.isVideo(mimeType) || MimeTypes.isAudio(mimeType); + trackIsAudioVideoFlags[i] = isAudioVideo; + haveAudioVideoTracks |= isAudioVideo; + } + tracks = new TrackGroupArray(trackArray); + prepared = true; + sourceListener.onSourceInfoRefreshed( + new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); + callback.onPrepared(this); + } + + private void copyLengthFromLoader(ExtractingLoadable loadable) { + if (length == C.LENGTH_UNSET) { + length = loadable.length; + } + } + + private void startLoading() { + ExtractingLoadable loadable = new ExtractingLoadable(uri, dataSource, extractorHolder, + loadCondition); + if (prepared) { + Assertions.checkState(isPendingReset()); + if (durationUs != C.TIME_UNSET && pendingResetPositionUs >= durationUs) { + loadingFinished = true; + pendingResetPositionUs = C.TIME_UNSET; + return; + } + loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs), pendingResetPositionUs); + pendingResetPositionUs = C.TIME_UNSET; + } + extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); + + int minRetryCount = minLoadableRetryCount; + if (minRetryCount == ExtractorMediaSource.MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA) { + // We assume on-demand before we're prepared. + minRetryCount = !prepared || length != C.LENGTH_UNSET + || (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET) + ? ExtractorMediaSource.DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND + : ExtractorMediaSource.DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE; + } + loader.startLoading(loadable, this, minRetryCount); + } + + private void configureRetry(ExtractingLoadable loadable) { + if (length != C.LENGTH_UNSET + || (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET)) { + // We're playing an on-demand stream. Resume the current loadable, which will + // request data starting from the point it left off. + } else { + // We're playing a stream of unknown length and duration. Assume it's live, and + // therefore that the data at the uri is a continuously shifting window of the latest + // available media. For this case there's no way to continue loading from where a + // previous load finished, so it's necessary to load from the start whenever commencing + // a new load. + lastSeekPositionUs = 0; + notifyReset = prepared; + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]); + } + loadable.setLoadPosition(0, 0); + } + } + + private int getExtractedSamplesCount() { + int extractedSamplesCount = 0; + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + extractedSamplesCount += sampleQueues.valueAt(i).getWriteIndex(); + } + return extractedSamplesCount; + } + + private long getLargestQueuedTimestampUs() { + long largestQueuedTimestampUs = Long.MIN_VALUE; + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + } + return largestQueuedTimestampUs; + } + + private boolean isPendingReset() { + return pendingResetPositionUs != C.TIME_UNSET; + } + + private boolean isLoadableExceptionFatal(IOException e) { + return e instanceof UnrecognizedInputFormatException; + } + + private void notifyLoadError(final IOException error) { + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onLoadError(error); + } + }); + } + } + + private final class SampleStreamImpl implements SampleStream { + + private final int track; + + public SampleStreamImpl(int track) { + this.track = track; + } + + @Override + public boolean isReady() { + return ExtractorMediaPeriod.this.isReady(track); + } + + @Override + public void maybeThrowError() throws IOException { + ExtractorMediaPeriod.this.maybeThrowError(); + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired) { + return ExtractorMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired); + } + + @Override + public void skipData(long positionUs) { + ExtractorMediaPeriod.this.skipData(track, positionUs); + } + + } + + /** + * Loads the media stream and extracts sample data from it. + */ + /* package */ final class ExtractingLoadable implements Loadable { + + /** + * The number of bytes that should be loaded between each each invocation of + * {@link Callback#onContinueLoadingRequested(SequenceableLoader)}. + */ + private static final int CONTINUE_LOADING_CHECK_INTERVAL_BYTES = 1024 * 1024; + + private final Uri uri; + private final DataSource dataSource; + private final ExtractorHolder extractorHolder; + private final ConditionVariable loadCondition; + private final PositionHolder positionHolder; + + private volatile boolean loadCanceled; + + private boolean pendingExtractorSeek; + private long seekTimeUs; + private long length; + + public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, + ConditionVariable loadCondition) { + this.uri = Assertions.checkNotNull(uri); + this.dataSource = Assertions.checkNotNull(dataSource); + this.extractorHolder = Assertions.checkNotNull(extractorHolder); + this.loadCondition = loadCondition; + this.positionHolder = new PositionHolder(); + this.pendingExtractorSeek = true; + this.length = C.LENGTH_UNSET; + } + + public void setLoadPosition(long position, long timeUs) { + positionHolder.position = position; + seekTimeUs = timeUs; + pendingExtractorSeek = true; + } + + @Override + public void cancelLoad() { + loadCanceled = true; + } + + @Override + public boolean isLoadCanceled() { + return loadCanceled; + } + + @Override + public void load() throws IOException, InterruptedException { + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + ExtractorInput input = null; + try { + long position = positionHolder.position; + length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNSET, customCacheKey)); + if (length != C.LENGTH_UNSET) { + length += position; + } + input = new DefaultExtractorInput(dataSource, position, length); + Extractor extractor = extractorHolder.selectExtractor(input, dataSource.getUri()); + if (pendingExtractorSeek) { + extractor.seek(position, seekTimeUs); + pendingExtractorSeek = false; + } + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + loadCondition.block(); + result = extractor.read(input, positionHolder); + if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) { + position = input.getPosition(); + loadCondition.close(); + handler.post(onContinueLoadingRequestedRunnable); + } + } + } finally { + if (result == Extractor.RESULT_SEEK) { + result = Extractor.RESULT_CONTINUE; + } else if (input != null) { + positionHolder.position = input.getPosition(); + } + Util.closeQuietly(dataSource); + } + } + } + + } + + /** + * Stores a list of extractors and a selected extractor when the format has been detected. + */ + private static final class ExtractorHolder { + + private final Extractor[] extractors; + private final ExtractorOutput extractorOutput; + private Extractor extractor; + + /** + * Creates a holder that will select an extractor and initialize it using the specified output. + * + * @param extractors One or more extractors to choose from. + * @param extractorOutput The output that will be used to initialize the selected extractor. + */ + public ExtractorHolder(Extractor[] extractors, ExtractorOutput extractorOutput) { + this.extractors = extractors; + this.extractorOutput = extractorOutput; + } + + /** + * Returns an initialized extractor for reading {@code input}, and returns the same extractor on + * later calls. + * + * @param input The {@link ExtractorInput} from which data should be read. + * @param uri The {@link Uri} of the data. + * @return An initialized extractor for reading {@code input}. + * @throws UnrecognizedInputFormatException Thrown if the input format could not be detected. + * @throws IOException Thrown if the input could not be read. + * @throws InterruptedException Thrown if the thread was interrupted. + */ + public Extractor selectExtractor(ExtractorInput input, Uri uri) + throws IOException, InterruptedException { + if (extractor != null) { + return extractor; + } + for (Extractor extractor : extractors) { + try { + if (extractor.sniff(input)) { + this.extractor = extractor; + break; + } + } catch (EOFException e) { + e.getStackTrace(); + // Do nothing. + } finally { + input.resetPeekPosition(); + } + } + if (extractor == null) { + throw new UnrecognizedInputFormatException("None of the available extractors (" + + Util.getCommaDelimitedSimpleClassNames(extractors) + ") could read the stream.", uri); + } + extractor.init(extractorOutput); + return extractor; + } + + public void release() { + if (extractor != null) { + extractor.release(); + extractor = null; + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ExtractorMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ExtractorMediaSource.java new file mode 100644 index 0000000..c7865b2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/ExtractorMediaSource.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.net.Uri; +import android.os.Handler; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorsFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Provides one period that loads data from a {@link Uri} and extracted using an {@link Extractor}. + *

    + * If the possible input stream container formats are known, pass a factory that instantiates + * extractors for them to the constructor. Otherwise, pass a {@link DefaultExtractorsFactory} to + * use the default extractors. When reading a new stream, the first {@link Extractor} in the array + * of extractors created by the factory that returns {@code true} from {@link Extractor#sniff} will + * be used to extract samples from the input stream. + *

    + * Note that the built-in extractors for AAC, MPEG PS/TS and FLV streams do not support seeking. + */ +public final class ExtractorMediaSource implements MediaSource, MediaSource.Listener { + + /** + * Listener of {@link ExtractorMediaSource} events. + */ + public interface EventListener { + + /** + * Called when an error occurs loading media data. + * + * @param error The load error. + */ + void onLoadError(IOException error); + + } + + /** + * The default minimum number of times to retry loading prior to failing for on-demand streams. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND = 3; + + /** + * The default minimum number of times to retry loading prior to failing for live streams. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE = 6; + + /** + * Value for {@code minLoadableRetryCount} that causes the loader to retry + * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE} times for live streams and + * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND} for on-demand streams. + */ + public static final int MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA = -1; + + private final Uri uri; + private final DataSource.Factory dataSourceFactory; + private final ExtractorsFactory extractorsFactory; + private final int minLoadableRetryCount; + private final Handler eventHandler; + private final EventListener eventListener; + private final Timeline.Period period; + private final String customCacheKey; + + private MediaSource.Listener sourceListener; + private Timeline timeline; + private boolean timelineHasDuration; + + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSourceFactory A factory for {@link DataSource}s to read the media. + * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the + * possible formats are known, pass a factory that instantiates extractors for those formats. + * Otherwise, pass a {@link DefaultExtractorsFactory} to use default extractors. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public ExtractorMediaSource(Uri uri, DataSource.Factory dataSourceFactory, + ExtractorsFactory extractorsFactory, Handler eventHandler, EventListener eventListener) { + this(uri, dataSourceFactory, extractorsFactory, MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA, eventHandler, + eventListener, null); + } + + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSourceFactory A factory for {@link DataSource}s to read the media. + * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the + * possible formats are known, pass a factory that instantiates extractors for those formats. + * Otherwise, pass a {@link DefaultExtractorsFactory} to use default extractors. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache + * indexing. May be null. + */ + public ExtractorMediaSource(Uri uri, DataSource.Factory dataSourceFactory, + ExtractorsFactory extractorsFactory, Handler eventHandler, EventListener eventListener, + String customCacheKey) { + this(uri, dataSourceFactory, extractorsFactory, MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA, eventHandler, + eventListener, customCacheKey); + } + + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSourceFactory A factory for {@link DataSource}s to read the media. + * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the + * possible formats are known, pass a factory that instantiates extractors for those formats. + * Otherwise, pass a {@link DefaultExtractorsFactory} to use default extractors. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache + * indexing. May be null. + */ + public ExtractorMediaSource(Uri uri, DataSource.Factory dataSourceFactory, + ExtractorsFactory extractorsFactory, int minLoadableRetryCount, Handler eventHandler, + EventListener eventListener, String customCacheKey) { + this.uri = uri; + this.dataSourceFactory = dataSourceFactory; + this.extractorsFactory = extractorsFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.customCacheKey = customCacheKey; + period = new Timeline.Period(); + } + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + sourceListener = listener; + timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); + listener.onSourceInfoRefreshed(timeline, null); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + // Do nothing. + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + Assertions.checkArgument(index == 0); + return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(), + extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener, + this, allocator, customCacheKey); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + ((ExtractorMediaPeriod) mediaPeriod).release(); + } + + @Override + public void releaseSource() { + sourceListener = null; + } + + // MediaSource.Listener implementation. + + @Override + public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) { + long newTimelineDurationUs = newTimeline.getPeriod(0, period).getDurationUs(); + boolean newTimelineHasDuration = newTimelineDurationUs != C.TIME_UNSET; + if (timelineHasDuration && !newTimelineHasDuration) { + // Suppress source info changes that would make the duration unknown when it is already known. + return; + } + timeline = newTimeline; + timelineHasDuration = newTimelineHasDuration; + sourceListener.onSourceInfoRefreshed(timeline, null); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/LoopingMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/LoopingMediaSource.java new file mode 100644 index 0000000..54cddb0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/LoopingMediaSource.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.util.Log; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Loops a {@link MediaSource}. + */ +public final class LoopingMediaSource implements MediaSource { + + /** + * The maximum number of periods that can be exposed by the source. The value of this constant is + * large enough to cause indefinite looping in practice (the total duration of the looping source + * will be approximately five years if the duration of each period is one second). + */ + public static final int MAX_EXPOSED_PERIODS = 157680000; + + private static final String TAG = "LoopingMediaSource"; + + private final MediaSource childSource; + private final int loopCount; + + private int childPeriodCount; + + /** + * Loops the provided source indefinitely. + * + * @param childSource The {@link MediaSource} to loop. + */ + public LoopingMediaSource(MediaSource childSource) { + this(childSource, Integer.MAX_VALUE); + } + + /** + * Loops the provided source a specified number of times. + * + * @param childSource The {@link MediaSource} to loop. + * @param loopCount The desired number of loops. Must be strictly positive. The actual number of + * loops will be capped at the maximum that can achieved without causing the number of + * periods exposed by the source to exceed {@link #MAX_EXPOSED_PERIODS}. + */ + public LoopingMediaSource(MediaSource childSource, int loopCount) { + Assertions.checkArgument(loopCount > 0); + this.childSource = childSource; + this.loopCount = loopCount; + } + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, final Listener listener) { + childSource.prepareSource(player, false, new Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + childPeriodCount = timeline.getPeriodCount(); + listener.onSourceInfoRefreshed(new LoopingTimeline(timeline, loopCount), manifest); + } + }); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + childSource.maybeThrowSourceInfoRefreshError(); + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + return childSource.createPeriod(index % childPeriodCount, allocator, positionUs); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + childSource.releasePeriod(mediaPeriod); + } + + @Override + public void releaseSource() { + childSource.releaseSource(); + } + + private static final class LoopingTimeline extends Timeline { + + private final Timeline childTimeline; + private final int childPeriodCount; + private final int childWindowCount; + private final int loopCount; + + public LoopingTimeline(Timeline childTimeline, int loopCount) { + this.childTimeline = childTimeline; + childPeriodCount = childTimeline.getPeriodCount(); + childWindowCount = childTimeline.getWindowCount(); + // This is the maximum number of loops that can be performed without exceeding + // MAX_EXPOSED_PERIODS periods. + int maxLoopCount = MAX_EXPOSED_PERIODS / childPeriodCount; + if (loopCount > maxLoopCount) { + if (loopCount != Integer.MAX_VALUE) { + Log.w(TAG, "Capped loops to avoid overflow: " + loopCount + " -> " + maxLoopCount); + } + this.loopCount = maxLoopCount; + } else { + this.loopCount = loopCount; + } + } + + @Override + public int getWindowCount() { + return childWindowCount * loopCount; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + childTimeline.getWindow(windowIndex % childWindowCount, window, setIds, + defaultPositionProjectionUs); + int periodIndexOffset = (windowIndex / childWindowCount) * childPeriodCount; + window.firstPeriodIndex += periodIndexOffset; + window.lastPeriodIndex += periodIndexOffset; + return window; + } + + @Override + public int getPeriodCount() { + return childPeriodCount * loopCount; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + childTimeline.getPeriod(periodIndex % childPeriodCount, period, setIds); + int loopCount = (periodIndex / childPeriodCount); + period.windowIndex += loopCount * childWindowCount; + if (setIds) { + period.uid = Pair.create(loopCount, period.uid); + } + return period; + } + + @Override + public int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Pair)) { + return C.INDEX_UNSET; + } + Pair loopCountAndChildUid = (Pair) uid; + if (!(loopCountAndChildUid.first instanceof Integer)) { + return C.INDEX_UNSET; + } + int loopCount = (Integer) loopCountAndChildUid.first; + int periodIndexOffset = loopCount * childPeriodCount; + return childTimeline.getIndexOfPeriod(loopCountAndChildUid.second) + periodIndexOffset; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MediaPeriod.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MediaPeriod.java new file mode 100644 index 0000000..fd0b5c4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MediaPeriod.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import java.io.IOException; + +/** + * A source of a single period of media. + */ +public interface MediaPeriod extends SequenceableLoader { + + /** + * A callback to be notified of {@link MediaPeriod} events. + */ + interface Callback extends SequenceableLoader.Callback { + + /** + * Called when preparation completes. + *

    + * Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect + * for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be + * called with the initial track selection. + * + * @param mediaPeriod The prepared {@link MediaPeriod}. + */ + void onPrepared(MediaPeriod mediaPeriod); + + } + + /** + * Prepares this media period asynchronously. + *

    + * {@code callback.onPrepared} is called when preparation completes. If preparation fails, + * {@link #maybeThrowPrepareError()} will throw an {@link IOException}. + *

    + * If preparation succeeds and results in a source timeline change (e.g. the period duration + * becoming known), {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} will be + * called before {@code callback.onPrepared}. + * + * @param callback Callback to receive updates from this period, including being notified when + * preparation completes. + */ + void prepare(Callback callback); + + /** + * Throws an error that's preventing the period from becoming prepared. Does nothing if no such + * error exists. + *

    + * This method should only be called before the period has completed preparation. + * + * @throws IOException The underlying error. + */ + void maybeThrowPrepareError() throws IOException; + + /** + * Returns the {@link TrackGroup}s exposed by the period. + *

    + * This method should only be called after the period has been prepared. + * + * @return The {@link TrackGroup}s. + */ + TrackGroupArray getTrackGroups(); + + /** + * Performs a track selection. + *

    + * The call receives track {@code selections} for each renderer, {@code mayRetainStreamFlags} + * indicating whether the existing {@code SampleStream} can be retained for each selection, and + * the existing {@code stream}s themselves. The call will update {@code streams} to reflect the + * provided selections, clearing, setting and replacing entries as required. If an existing sample + * stream is retained but with the requirement that the consuming renderer be reset, then the + * corresponding flag in {@code streamResetFlags} will be set to true. This flag will also be set + * if a new sample stream is created. + *

    + * This method should only be called after the period has been prepared. + * + * @param selections The renderer track selections. + * @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained + * for each selection. A {@code true} value indicates that the selection is unchanged, and + * that the caller does not require that the sample stream be recreated. + * @param streams The existing sample streams, which will be updated to reflect the provided + * selections. + * @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that + * have been retained but with the requirement that the consuming renderer be reset. + * @param positionUs The current playback position in microseconds. + * @return The actual position at which the tracks were enabled, in microseconds. + */ + long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs); + + /** + * Discards buffered media up to the specified position. + * + * @param positionUs The position in microseconds. + */ + void discardBuffer(long positionUs); + + /** + * Attempts to read a discontinuity. + *

    + * After this method has returned a value other than {@link C#TIME_UNSET}, all + * {@link SampleStream}s provided by the period are guaranteed to start from a key frame. + * + * @return If a discontinuity was read then the playback position in microseconds after the + * discontinuity. Else {@link C#TIME_UNSET}. + */ + long readDiscontinuity(); + + /** + * Returns an estimate of the position up to which data is buffered for the enabled tracks. + *

    + * This method should only be called when at least one track is selected. + * + * @return An estimate of the absolute position in microseconds up to which data is buffered, or + * {@link C#TIME_END_OF_SOURCE} if the track is fully buffered. + */ + long getBufferedPositionUs(); + + /** + * Attempts to seek to the specified position in microseconds. + *

    + * After this method has been called, all {@link SampleStream}s provided by the period are + * guaranteed to start from a key frame. + *

    + * This method should only be called when at least one track is selected. + * + * @param positionUs The seek position in microseconds. + * @return The actual position to which the period was seeked, in microseconds. + */ + long seekToUs(long positionUs); + + // SequenceableLoader interface. Overridden to provide more specific documentation. + + /** + * Returns the next load time, or {@link C#TIME_END_OF_SOURCE} if loading has finished. + *

    + * This method should only be called after the period has been prepared. It may be called when no + * tracks are selected. + */ + @Override + long getNextLoadPositionUs(); + + /** + * Attempts to continue loading. + *

    + * This method may be called both during and after the period has been prepared. + *

    + * A period may call {@link Callback#onContinueLoadingRequested(SequenceableLoader)} on the + * {@link Callback} passed to {@link #prepare(Callback)} to request that this method be called + * when the period is permitted to continue loading data. A period may do this both during and + * after preparation. + * + * @param positionUs The current playback position. + * @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return + * a different value than prior to the call. False otherwise. + */ + @Override + boolean continueLoading(long positionUs); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MediaSource.java new file mode 100644 index 0000000..2433020 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MediaSource.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import java.io.IOException; + +/** + * A source of media consisting of one or more {@link MediaPeriod}s. + */ +public interface MediaSource { + + /** + * Listener for source events. + */ + interface Listener { + + /** + * Called when manifest and/or timeline has been refreshed. + * + * @param timeline The source's timeline. + * @param manifest The loaded manifest. + */ + void onSourceInfoRefreshed(Timeline timeline, Object manifest); + + } + + /** + * Starts preparation of the source. + * + * @param player The player for which this source is being prepared. + * @param isTopLevelSource Whether this source has been passed directly to + * {@link ExoPlayer#prepare(MediaSource)} or + * {@link ExoPlayer#prepare(MediaSource, boolean, boolean)}. If {@code false}, this source is + * being prepared by another source (e.g. {@link ConcatenatingMediaSource}) for composition. + * @param listener The listener for source events. + */ + void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener); + + /** + * Throws any pending error encountered while loading or refreshing source information. + */ + void maybeThrowSourceInfoRefreshError() throws IOException; + + /** + * Returns a new {@link MediaPeriod} corresponding to the period at the specified {@code index}. + * This method may be called multiple times with the same index without an intervening call to + * {@link #releasePeriod(MediaPeriod)}. + * + * @param index The index of the period. + * @param allocator An {@link Allocator} from which to obtain media buffer allocations. + * @param positionUs The player's current playback position. + * @return A new {@link MediaPeriod}. + */ + MediaPeriod createPeriod(int index, Allocator allocator, long positionUs); + + /** + * Releases the period. + * + * @param mediaPeriod The period to release. + */ + void releasePeriod(MediaPeriod mediaPeriod); + + /** + * Releases the source. + *

    + * This method should be called when the source is no longer required. It may be called in any + * state. + */ + void releaseSource(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MergingMediaPeriod.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MergingMediaPeriod.java new file mode 100644 index 0000000..9841907 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MergingMediaPeriod.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.ArrayList; +import java.util.IdentityHashMap; + +/** + * Merges multiple {@link MediaPeriod}s. + */ +/* package */ final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callback { + + public final MediaPeriod[] periods; + + private final IdentityHashMap streamPeriodIndices; + + private Callback callback; + private int pendingChildPrepareCount; + private TrackGroupArray trackGroups; + + private MediaPeriod[] enabledPeriods; + private SequenceableLoader sequenceableLoader; + + public MergingMediaPeriod(MediaPeriod... periods) { + this.periods = periods; + streamPeriodIndices = new IdentityHashMap<>(); + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + pendingChildPrepareCount = periods.length; + for (MediaPeriod period : periods) { + period.prepare(this); + } + } + + @Override + public void maybeThrowPrepareError() throws IOException { + for (MediaPeriod period : periods) { + period.maybeThrowPrepareError(); + } + } + + @Override + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + // Map each selection and stream onto a child period index. + int[] streamChildIndices = new int[selections.length]; + int[] selectionChildIndices = new int[selections.length]; + for (int i = 0; i < selections.length; i++) { + streamChildIndices[i] = streams[i] == null ? C.INDEX_UNSET + : streamPeriodIndices.get(streams[i]); + selectionChildIndices[i] = C.INDEX_UNSET; + if (selections[i] != null) { + TrackGroup trackGroup = selections[i].getTrackGroup(); + for (int j = 0; j < periods.length; j++) { + if (periods[j].getTrackGroups().indexOf(trackGroup) != C.INDEX_UNSET) { + selectionChildIndices[i] = j; + break; + } + } + } + } + streamPeriodIndices.clear(); + // Select tracks for each child, copying the resulting streams back into a new streams array. + SampleStream[] newStreams = new SampleStream[selections.length]; + SampleStream[] childStreams = new SampleStream[selections.length]; + TrackSelection[] childSelections = new TrackSelection[selections.length]; + ArrayList enabledPeriodsList = new ArrayList<>(periods.length); + for (int i = 0; i < periods.length; i++) { + for (int j = 0; j < selections.length; j++) { + childStreams[j] = streamChildIndices[j] == i ? streams[j] : null; + childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null; + } + long selectPositionUs = periods[i].selectTracks(childSelections, mayRetainStreamFlags, + childStreams, streamResetFlags, positionUs); + if (i == 0) { + positionUs = selectPositionUs; + } else if (selectPositionUs != positionUs) { + throw new IllegalStateException("Children enabled at different positions"); + } + boolean periodEnabled = false; + for (int j = 0; j < selections.length; j++) { + if (selectionChildIndices[j] == i) { + // Assert that the child provided a stream for the selection. + Assertions.checkState(childStreams[j] != null); + newStreams[j] = childStreams[j]; + periodEnabled = true; + streamPeriodIndices.put(childStreams[j], i); + } else if (streamChildIndices[j] == i) { + // Assert that the child cleared any previous stream. + Assertions.checkState(childStreams[j] == null); + } + } + if (periodEnabled) { + enabledPeriodsList.add(periods[i]); + } + } + // Copy the new streams back into the streams array. + System.arraycopy(newStreams, 0, streams, 0, newStreams.length); + // Update the local state. + enabledPeriods = new MediaPeriod[enabledPeriodsList.size()]; + enabledPeriodsList.toArray(enabledPeriods); + sequenceableLoader = new CompositeSequenceableLoader(enabledPeriods); + return positionUs; + } + + @Override + public void discardBuffer(long positionUs) { + for (MediaPeriod period : enabledPeriods) { + period.discardBuffer(positionUs); + } + } + + @Override + public boolean continueLoading(long positionUs) { + return sequenceableLoader.continueLoading(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return sequenceableLoader.getNextLoadPositionUs(); + } + + @Override + public long readDiscontinuity() { + long positionUs = periods[0].readDiscontinuity(); + // Periods other than the first one are not allowed to report discontinuities. + for (int i = 1; i < periods.length; i++) { + if (periods[i].readDiscontinuity() != C.TIME_UNSET) { + throw new IllegalStateException("Child reported discontinuity"); + } + } + // It must be possible to seek enabled periods to the new position, if there is one. + if (positionUs != C.TIME_UNSET) { + for (MediaPeriod enabledPeriod : enabledPeriods) { + if (enabledPeriod != periods[0] + && enabledPeriod.seekToUs(positionUs) != positionUs) { + throw new IllegalStateException("Children seeked to different positions"); + } + } + } + return positionUs; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (MediaPeriod period : enabledPeriods) { + long rendererBufferedPositionUs = period.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public long seekToUs(long positionUs) { + positionUs = enabledPeriods[0].seekToUs(positionUs); + // Additional periods must seek to the same position. + for (int i = 1; i < enabledPeriods.length; i++) { + if (enabledPeriods[i].seekToUs(positionUs) != positionUs) { + throw new IllegalStateException("Children seeked to different positions"); + } + } + return positionUs; + } + + // MediaPeriod.Callback implementation + + @Override + public void onPrepared(MediaPeriod ignored) { + if (--pendingChildPrepareCount > 0) { + return; + } + int totalTrackGroupCount = 0; + for (MediaPeriod period : periods) { + totalTrackGroupCount += period.getTrackGroups().length; + } + TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; + int trackGroupIndex = 0; + for (MediaPeriod period : periods) { + TrackGroupArray periodTrackGroups = period.getTrackGroups(); + int periodTrackGroupCount = periodTrackGroups.length; + for (int j = 0; j < periodTrackGroupCount; j++) { + trackGroupArray[trackGroupIndex++] = periodTrackGroups.get(j); + } + } + trackGroups = new TrackGroupArray(trackGroupArray); + callback.onPrepared(this); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod ignored) { + if (trackGroups == null) { + // Still preparing. + return; + } + callback.onContinueLoadingRequested(this); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MergingMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MergingMediaSource.java new file mode 100644 index 0000000..6cc6088 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/MergingMediaSource.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Merges multiple {@link MediaSource}s. + *

    + * The {@link Timeline}s of the sources being merged must have the same number of periods, and must + * not have any dynamic windows. + */ +public final class MergingMediaSource implements MediaSource { + + /** + * Thrown when a {@link MergingMediaSource} cannot merge its sources. + */ + public static final class IllegalMergeException extends IOException { + + /** + * The reason the merge failed. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REASON_WINDOWS_ARE_DYNAMIC, REASON_PERIOD_COUNT_MISMATCH}) + public @interface Reason {} + /** + * The merge failed because one of the sources being merged has a dynamic window. + */ + public static final int REASON_WINDOWS_ARE_DYNAMIC = 0; + /** + * The merge failed because the sources have different period counts. + */ + public static final int REASON_PERIOD_COUNT_MISMATCH = 1; + + /** + * The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and + * {@link #REASON_PERIOD_COUNT_MISMATCH}. + */ + @Reason public final int reason; + + /** + * @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and + * {@link #REASON_PERIOD_COUNT_MISMATCH}. + */ + public IllegalMergeException(@Reason int reason) { + this.reason = reason; + } + + } + + private static final int PERIOD_COUNT_UNSET = -1; + + private final MediaSource[] mediaSources; + private final ArrayList pendingTimelineSources; + private final Timeline.Window window; + + private Listener listener; + private Timeline primaryTimeline; + private Object primaryManifest; + private int periodCount; + private IllegalMergeException mergeError; + + /** + * @param mediaSources The {@link MediaSource}s to merge. + */ + public MergingMediaSource(MediaSource... mediaSources) { + this.mediaSources = mediaSources; + pendingTimelineSources = new ArrayList<>(Arrays.asList(mediaSources)); + window = new Timeline.Window(); + periodCount = PERIOD_COUNT_UNSET; + } + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + this.listener = listener; + for (int i = 0; i < mediaSources.length; i++) { + final int sourceIndex = i; + mediaSources[sourceIndex].prepareSource(player, false, new Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + handleSourceInfoRefreshed(sourceIndex, timeline, manifest); + } + }); + } + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + if (mergeError != null) { + throw mergeError; + } + for (MediaSource mediaSource : mediaSources) { + mediaSource.maybeThrowSourceInfoRefreshError(); + } + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; + for (int i = 0; i < periods.length; i++) { + periods[i] = mediaSources[i].createPeriod(index, allocator, positionUs); + } + return new MergingMediaPeriod(periods); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + MergingMediaPeriod mergingPeriod = (MergingMediaPeriod) mediaPeriod; + for (int i = 0; i < mediaSources.length; i++) { + mediaSources[i].releasePeriod(mergingPeriod.periods[i]); + } + } + + @Override + public void releaseSource() { + for (MediaSource mediaSource : mediaSources) { + mediaSource.releaseSource(); + } + } + + private void handleSourceInfoRefreshed(int sourceIndex, Timeline timeline, Object manifest) { + if (mergeError == null) { + mergeError = checkTimelineMerges(timeline); + } + if (mergeError != null) { + return; + } + pendingTimelineSources.remove(mediaSources[sourceIndex]); + if (sourceIndex == 0) { + primaryTimeline = timeline; + primaryManifest = manifest; + } + if (pendingTimelineSources.isEmpty()) { + listener.onSourceInfoRefreshed(primaryTimeline, primaryManifest); + } + } + + private IllegalMergeException checkTimelineMerges(Timeline timeline) { + int windowCount = timeline.getWindowCount(); + for (int i = 0; i < windowCount; i++) { + if (timeline.getWindow(i, window, false).isDynamic) { + return new IllegalMergeException(IllegalMergeException.REASON_WINDOWS_ARE_DYNAMIC); + } + } + if (periodCount == PERIOD_COUNT_UNSET) { + periodCount = timeline.getPeriodCount(); + } else if (timeline.getPeriodCount() != periodCount) { + return new IllegalMergeException(IllegalMergeException.REASON_PERIOD_COUNT_MISMATCH); + } + return null; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SampleStream.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SampleStream.java new file mode 100644 index 0000000..801307a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SampleStream.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import java.io.IOException; + +/** + * A stream of media samples (and associated format information). + */ +public interface SampleStream { + + /** + * Returns whether data is available to be read. + *

    + * Note: If the stream has ended then a buffer with the end of stream flag can always be read from + * {@link #readData(FormatHolder, DecoderInputBuffer, boolean)}. Hence an ended stream is always + * ready. + * + * @return Whether data is available to be read. + */ + boolean isReady(); + + /** + * Throws an error that's preventing data from being read. Does nothing if no such error exists. + * + * @throws IOException The underlying error. + */ + void maybeThrowError() throws IOException; + + /** + * Attempts to read from the stream. + *

    + * If the stream has ended then {@link C#BUFFER_FLAG_END_OF_STREAM} flag is set on {@code buffer} + * and {@link C#RESULT_BUFFER_READ} is returned. Else if no data is available then + * {@link C#RESULT_NOTHING_READ} is returned. Else if the format of the media is changing or if + * {@code formatRequired} is set then {@code formatHolder} is populated and + * {@link C#RESULT_FORMAT_READ} is returned. Else {@code buffer} is populated and + * {@link C#RESULT_BUFFER_READ} is returned. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If the end of the stream has been reached, the + * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @param formatRequired Whether the caller requires that the format of the stream be read even if + * it's not changing. A sample will never be read if set to true, however it is still possible + * for the end of stream or nothing to be read. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or + * {@link C#RESULT_BUFFER_READ}. + */ + int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired); + + /** + * Attempts to skip to the keyframe before the specified position, or to the end of the stream if + * {@code positionUs} is beyond it. + * + * @param positionUs The specified time. + */ + void skipData(long positionUs); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SequenceableLoader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SequenceableLoader.java new file mode 100644 index 0000000..458a0b8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SequenceableLoader.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +// TODO: Clarify the requirements for implementing this interface [Internal ref: b/36250203]. +/** + * A loader that can proceed in approximate synchronization with other loaders. + */ +public interface SequenceableLoader { + + /** + * A callback to be notified of {@link SequenceableLoader} events. + */ + interface Callback { + + /** + * Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method + * to be called when it can continue to load data. Called on the playback thread. + */ + void onContinueLoadingRequested(T source); + + } + + /** + * Returns the next load time, or {@link C#TIME_END_OF_SOURCE} if loading has finished. + */ + long getNextLoadPositionUs(); + + /** + * Attempts to continue loading. + * + * @param positionUs The current playback position. + * @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return + * a different value than prior to the call. False otherwise. + */ + boolean continueLoading(long positionUs); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SinglePeriodTimeline.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SinglePeriodTimeline.java new file mode 100644 index 0000000..812e79d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SinglePeriodTimeline.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * A {@link Timeline} consisting of a single period and static window. + */ +public final class SinglePeriodTimeline extends Timeline { + + private static final Object ID = new Object(); + + private final long periodDurationUs; + private final long windowDurationUs; + private final long windowPositionInPeriodUs; + private final long windowDefaultStartPositionUs; + private final boolean isSeekable; + private final boolean isDynamic; + + /** + * Creates a timeline of one period of known duration, and a static window starting at zero and + * extending to that duration. + * + * @param durationUs The duration of the period, in microseconds. + * @param isSeekable Whether seeking is supported within the period. + */ + public SinglePeriodTimeline(long durationUs, boolean isSeekable) { + this(durationUs, durationUs, 0, 0, isSeekable, false); + } + + /** + * Creates a timeline with one period of known duration, and a window of known duration starting + * at a specified position in the period. + * + * @param periodDurationUs The duration of the period in microseconds. + * @param windowDurationUs The duration of the window in microseconds. + * @param windowPositionInPeriodUs The position of the start of the window in the period, in + * microseconds. + * @param windowDefaultStartPositionUs The default position relative to the start of the window at + * which to begin playback, in microseconds. + * @param isSeekable Whether seeking is supported within the window. + * @param isDynamic Whether the window may change when the timeline is updated. + */ + public SinglePeriodTimeline(long periodDurationUs, long windowDurationUs, + long windowPositionInPeriodUs, long windowDefaultStartPositionUs, boolean isSeekable, + boolean isDynamic) { + this.periodDurationUs = periodDurationUs; + this.windowDurationUs = windowDurationUs; + this.windowPositionInPeriodUs = windowPositionInPeriodUs; + this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; + this.isSeekable = isSeekable; + this.isDynamic = isDynamic; + } + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + Assertions.checkIndex(windowIndex, 0, 1); + Object id = setIds ? ID : null; + long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs; + if (isDynamic) { + windowDefaultStartPositionUs += defaultPositionProjectionUs; + if (windowDefaultStartPositionUs > windowDurationUs) { + // The projection takes us beyond the end of the live window. + windowDefaultStartPositionUs = C.TIME_UNSET; + } + } + return window.set(id, C.TIME_UNSET, C.TIME_UNSET, isSeekable, isDynamic, + windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs); + } + + @Override + public int getPeriodCount() { + return 1; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + Assertions.checkIndex(periodIndex, 0, 1); + Object id = setIds ? ID : null; + return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs, false); + } + + @Override + public int getIndexOfPeriod(Object uid) { + return ID.equals(uid) ? 0 : C.INDEX_UNSET; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SingleSampleMediaPeriod.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SingleSampleMediaPeriod.java new file mode 100644 index 0000000..3ae6da8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SingleSampleMediaPeriod.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.net.Uri; +import android.os.Handler; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SingleSampleMediaSource.EventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader.Loadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A {@link MediaPeriod} with a single sample. + */ +/* package */ final class SingleSampleMediaPeriod implements MediaPeriod, + Loader.Callback { + + /** + * The initial size of the allocation used to hold the sample data. + */ + private static final int INITIAL_SAMPLE_SIZE = 1024; + + private final Uri uri; + private final DataSource.Factory dataSourceFactory; + private final int minLoadableRetryCount; + private final Handler eventHandler; + private final EventListener eventListener; + private final int eventSourceId; + private final TrackGroupArray tracks; + private final ArrayList sampleStreams; + /* package */ final Loader loader; + /* package */ final Format format; + + /* package */ boolean loadingFinished; + /* package */ byte[] sampleData; + /* package */ int sampleSize; + + public SingleSampleMediaPeriod(Uri uri, DataSource.Factory dataSourceFactory, Format format, + int minLoadableRetryCount, Handler eventHandler, EventListener eventListener, + int eventSourceId) { + this.uri = uri; + this.dataSourceFactory = dataSourceFactory; + this.format = format; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.eventSourceId = eventSourceId; + tracks = new TrackGroupArray(new TrackGroup(format)); + sampleStreams = new ArrayList<>(); + loader = new Loader("Loader:SingleSampleMediaPeriod"); + } + + public void release() { + loader.release(); + } + + @Override + public void prepare(Callback callback) { + callback.onPrepared(this); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + loader.maybeThrowError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return tracks; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { + sampleStreams.remove(streams[i]); + streams[i] = null; + } + if (streams[i] == null && selections[i] != null) { + SampleStreamImpl stream = new SampleStreamImpl(); + sampleStreams.add(stream); + streams[i] = stream; + streamResetFlags[i] = true; + } + } + return positionUs; + } + + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + + @Override + public boolean continueLoading(long positionUs) { + if (loadingFinished || loader.isLoading()) { + return false; + } + loader.startLoading(new SourceLoadable(uri, dataSourceFactory.createDataSource()), this, + minLoadableRetryCount); + return true; + } + + @Override + public long readDiscontinuity() { + return C.TIME_UNSET; + } + + @Override + public long getNextLoadPositionUs() { + return loadingFinished || loader.isLoading() ? C.TIME_END_OF_SOURCE : 0; + } + + @Override + public long getBufferedPositionUs() { + return loadingFinished ? C.TIME_END_OF_SOURCE : 0; + } + + @Override + public long seekToUs(long positionUs) { + for (int i = 0; i < sampleStreams.size(); i++) { + sampleStreams.get(i).seekToUs(positionUs); + } + return positionUs; + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(SourceLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + sampleSize = loadable.sampleSize; + sampleData = loadable.sampleData; + loadingFinished = true; + } + + @Override + public void onLoadCanceled(SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, + boolean released) { + // Do nothing. + } + + @Override + public int onLoadError(SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, + IOException error) { + notifyLoadError(error); + return Loader.RETRY; + } + + // Internal methods. + + private void notifyLoadError(final IOException e) { + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onLoadError(eventSourceId, e); + } + }); + } + } + + private final class SampleStreamImpl implements SampleStream { + + private static final int STREAM_STATE_SEND_FORMAT = 0; + private static final int STREAM_STATE_SEND_SAMPLE = 1; + private static final int STREAM_STATE_END_OF_STREAM = 2; + + private int streamState; + + public void seekToUs(long positionUs) { + if (streamState == STREAM_STATE_END_OF_STREAM) { + streamState = STREAM_STATE_SEND_SAMPLE; + } + } + + @Override + public boolean isReady() { + return loadingFinished; + } + + @Override + public void maybeThrowError() throws IOException { + loader.maybeThrowError(); + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean requireFormat) { + if (streamState == STREAM_STATE_END_OF_STREAM) { + buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + return C.RESULT_BUFFER_READ; + } else if (requireFormat || streamState == STREAM_STATE_SEND_FORMAT) { + formatHolder.format = format; + streamState = STREAM_STATE_SEND_SAMPLE; + return C.RESULT_FORMAT_READ; + } + + Assertions.checkState(streamState == STREAM_STATE_SEND_SAMPLE); + if (!loadingFinished) { + return C.RESULT_NOTHING_READ; + } else { + buffer.timeUs = 0; + buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); + buffer.ensureSpaceForWrite(sampleSize); + buffer.data.put(sampleData, 0, sampleSize); + streamState = STREAM_STATE_END_OF_STREAM; + return C.RESULT_BUFFER_READ; + } + } + + @Override + public void skipData(long positionUs) { + if (positionUs > 0) { + streamState = STREAM_STATE_END_OF_STREAM; + } + } + + } + + /* package */ static final class SourceLoadable implements Loadable { + + private final Uri uri; + private final DataSource dataSource; + + private int sampleSize; + private byte[] sampleData; + + public SourceLoadable(Uri uri, DataSource dataSource) { + this.uri = uri; + this.dataSource = dataSource; + } + + @Override + public void cancelLoad() { + // Never happens. + } + + @Override + public boolean isLoadCanceled() { + return false; + } + + @Override + public void load() throws IOException, InterruptedException { + // We always load from the beginning, so reset the sampleSize to 0. + sampleSize = 0; + try { + // Create and open the input. + dataSource.open(new DataSpec(uri)); + // Load the sample data. + int result = 0; + while (result != C.RESULT_END_OF_INPUT) { + sampleSize += result; + if (sampleData == null) { + sampleData = new byte[INITIAL_SAMPLE_SIZE]; + } else if (sampleSize == sampleData.length) { + sampleData = Arrays.copyOf(sampleData, sampleData.length * 2); + } + result = dataSource.read(sampleData, sampleSize, sampleData.length - sampleSize); + } + } finally { + Util.closeQuietly(dataSource); + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SingleSampleMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SingleSampleMediaSource.java new file mode 100644 index 0000000..3802996 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/SingleSampleMediaSource.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.net.Uri; +import android.os.Handler; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Loads data at a given {@link Uri} as a single sample belonging to a single {@link MediaPeriod}. + */ +public final class SingleSampleMediaSource implements MediaSource { + + /** + * Listener of {@link SingleSampleMediaSource} events. + */ + public interface EventListener { + + /** + * Called when an error occurs loading media data. + * + * @param sourceId The id of the reporting {@link SingleSampleMediaSource}. + * @param e The cause of the failure. + */ + void onLoadError(int sourceId, IOException e); + + } + + /** + * The default minimum number of times to retry loading data prior to failing. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + + private final Uri uri; + private final DataSource.Factory dataSourceFactory; + private final Format format; + private final int minLoadableRetryCount; + private final Handler eventHandler; + private final EventListener eventListener; + private final int eventSourceId; + private final Timeline timeline; + + public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, + long durationUs) { + this(uri, dataSourceFactory, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT); + } + + public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, + long durationUs, int minLoadableRetryCount) { + this(uri, dataSourceFactory, format, durationUs, minLoadableRetryCount, null, null, 0); + } + + public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, + long durationUs, int minLoadableRetryCount, Handler eventHandler, EventListener eventListener, + int eventSourceId) { + this.uri = uri; + this.dataSourceFactory = dataSourceFactory; + this.format = format; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.eventSourceId = eventSourceId; + timeline = new SinglePeriodTimeline(durationUs, true); + } + + // MediaSource implementation. + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + listener.onSourceInfoRefreshed(timeline, null); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + // Do nothing. + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + Assertions.checkArgument(index == 0); + return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount, + eventHandler, eventListener, eventSourceId); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + ((SingleSampleMediaPeriod) mediaPeriod).release(); + } + + @Override + public void releaseSource() { + // Do nothing. + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/TrackGroup.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/TrackGroup.java new file mode 100644 index 0000000..18d6bce --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/TrackGroup.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.Arrays; + +// TODO: Add an allowMultipleStreams boolean to indicate where the one stream per group restriction +// does not apply. +/** + * Defines a group of tracks exposed by a {@link MediaPeriod}. + *

    + * A {@link MediaPeriod} is only able to provide one {@link SampleStream} corresponding to a group + * at any given time, however this {@link SampleStream} may adapt between multiple tracks within the + * group. + */ +public final class TrackGroup { + + /** + * The number of tracks in the group. + */ + public final int length; + + private final Format[] formats; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param formats The track formats. Must not be null or contain null elements. + */ + public TrackGroup(Format... formats) { + Assertions.checkState(formats.length > 0); + this.formats = formats; + this.length = formats.length; + } + + /** + * Returns the format of the track at a given index. + * + * @param index The index of the track. + * @return The track's format. + */ + public Format getFormat(int index) { + return formats[index]; + } + + /** + * Returns the index of the track with the given format in the group. + * + * @param format The format. + * @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists. + */ + public int indexOf(Format format) { + for (int i = 0; i < formats.length; i++) { + if (format == formats[i]) { + return i; + } + } + return C.INDEX_UNSET; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + Arrays.hashCode(formats); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackGroup other = (TrackGroup) obj; + return length == other.length && Arrays.equals(formats, other.formats); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/TrackGroupArray.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/TrackGroupArray.java new file mode 100644 index 0000000..deee551 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/TrackGroupArray.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.util.Arrays; + +/** + * An array of {@link TrackGroup}s exposed by a {@link MediaPeriod}. + */ +public final class TrackGroupArray { + + /** + * The empty array. + */ + public static final TrackGroupArray EMPTY = new TrackGroupArray(); + + /** + * The number of groups in the array. Greater than or equal to zero. + */ + public final int length; + + private final TrackGroup[] trackGroups; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param trackGroups The groups. Must not be null or contain null elements, but may be empty. + */ + public TrackGroupArray(TrackGroup... trackGroups) { + this.trackGroups = trackGroups; + this.length = trackGroups.length; + } + + /** + * Returns the group at a given index. + * + * @param index The index of the group. + * @return The group. + */ + public TrackGroup get(int index) { + return trackGroups[index]; + } + + /** + * Returns the index of a group within the array. + * + * @param group The group. + * @return The index of the group, or {@link C#INDEX_UNSET} if no such group exists. + */ + public int indexOf(TrackGroup group) { + for (int i = 0; i < length; i++) { + if (trackGroups[i] == group) { + return i; + } + } + return C.INDEX_UNSET; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Arrays.hashCode(trackGroups); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackGroupArray other = (TrackGroupArray) obj; + return length == other.length && Arrays.equals(trackGroups, other.trackGroups); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/UnrecognizedInputFormatException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/UnrecognizedInputFormatException.java new file mode 100644 index 0000000..2703864 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/UnrecognizedInputFormatException.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; + +/** + * Thrown if the input format was not recognized. + */ +public class UnrecognizedInputFormatException extends ParserException { + + /** + * The {@link Uri} from which the unrecognized data was read. + */ + public final Uri uri; + + /** + * @param message The detail message for the exception. + * @param uri The {@link Uri} from which the unrecognized data was read. + */ + public UnrecognizedInputFormatException(String message, Uri uri) { + super(message); + this.uri = uri; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/BaseMediaChunk.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/BaseMediaChunk.java new file mode 100644 index 0000000..01bd7f8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/BaseMediaChunk.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; + +/** + * A base implementation of {@link MediaChunk} that outputs to a {@link BaseMediaChunkOutput}. + */ +public abstract class BaseMediaChunk extends MediaChunk { + + private BaseMediaChunkOutput output; + private int[] firstSampleIndices; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the media contained by the chunk, in microseconds. + * @param endTimeUs The end time of the media contained by the chunk, in microseconds. + * @param chunkIndex The index of the chunk. + */ + public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, + int chunkIndex) { + super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, + endTimeUs, chunkIndex); + } + + /** + * Initializes the chunk for loading, setting the {@link BaseMediaChunkOutput} that will receive + * samples as they are loaded. + * + * @param output The output that will receive the loaded media samples. + */ + public void init(BaseMediaChunkOutput output) { + this.output = output; + firstSampleIndices = output.getWriteIndices(); + } + + /** + * Returns the index of the first sample in the specified track of the output that will originate + * from this chunk. + */ + public final int getFirstSampleIndex(int trackIndex) { + return firstSampleIndices[trackIndex]; + } + + /** + * Returns the output most recently passed to {@link #init(BaseMediaChunkOutput)}. + */ + protected final BaseMediaChunkOutput getOutput() { + return output; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/BaseMediaChunkOutput.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/BaseMediaChunkOutput.java new file mode 100644 index 0000000..2a29772 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/BaseMediaChunkOutput.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultTrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DummyTrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOutputProvider; + +/** + * An output for {@link BaseMediaChunk}s. + */ +/* package */ final class BaseMediaChunkOutput implements TrackOutputProvider { + + private static final String TAG = "BaseMediaChunkOutput"; + + private final int[] trackTypes; + private final DefaultTrackOutput[] trackOutputs; + + /** + * @param trackTypes The track types of the individual track outputs. + * @param trackOutputs The individual track outputs. + */ + public BaseMediaChunkOutput(int[] trackTypes, DefaultTrackOutput[] trackOutputs) { + this.trackTypes = trackTypes; + this.trackOutputs = trackOutputs; + } + + @Override + public TrackOutput track(int id, int type) { + for (int i = 0; i < trackTypes.length; i++) { + if (type == trackTypes[i]) { + return trackOutputs[i]; + } + } + return new DummyTrackOutput(); + } + + /** + * Returns the current absolute write indices of the individual track outputs. + */ + public int[] getWriteIndices() { + int[] writeIndices = new int[trackOutputs.length]; + for (int i = 0; i < trackOutputs.length; i++) { + if (trackOutputs[i] != null) { + writeIndices[i] = trackOutputs[i].getWriteIndex(); + } + } + return writeIndices; + } + + /** + * Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples + * subsequently written to the track outputs. + */ + public void setSampleOffsetUs(long sampleOffsetUs) { + for (DefaultTrackOutput trackOutput : trackOutputs) { + if (trackOutput != null) { + trackOutput.setSampleOffsetUs(sampleOffsetUs); + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/Chunk.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/Chunk.java new file mode 100644 index 0000000..35aaaeb --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/Chunk.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader.Loadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * An abstract base class for {@link Loadable} implementations that load chunks of data required + * for the playback of streams. + */ +public abstract class Chunk implements Loadable { + + /** + * The {@link DataSpec} that defines the data to be loaded. + */ + public final DataSpec dataSpec; + /** + * The type of the chunk. One of the {@code DATA_TYPE_*} constants defined in {@link C}. For + * reporting only. + */ + public final int type; + /** + * The format of the track to which this chunk belongs, or null if the chunk does not belong to + * a track. + */ + public final Format trackFormat; + /** + * One of the {@link C} {@code SELECTION_REASON_*} constants if the chunk belongs to a track. + * {@link C#SELECTION_REASON_UNKNOWN} if the chunk does not belong to a track. + */ + public final int trackSelectionReason; + /** + * Optional data associated with the selection of the track to which this chunk belongs. Null if + * the chunk does not belong to a track. + */ + public final Object trackSelectionData; + /** + * The start time of the media contained by the chunk, or {@link C#TIME_UNSET} if the data + * being loaded does not contain media samples. + */ + public final long startTimeUs; + /** + * The end time of the media contained by the chunk, or {@link C#TIME_UNSET} if the data being + * loaded does not contain media samples. + */ + public final long endTimeUs; + + protected final DataSource dataSource; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param type See {@link #type}. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs See {@link #startTimeUs}. + * @param endTimeUs See {@link #endTimeUs}. + */ + public Chunk(DataSource dataSource, DataSpec dataSpec, int type, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs) { + this.dataSource = Assertions.checkNotNull(dataSource); + this.dataSpec = Assertions.checkNotNull(dataSpec); + this.type = type; + this.trackFormat = trackFormat; + this.trackSelectionReason = trackSelectionReason; + this.trackSelectionData = trackSelectionData; + this.startTimeUs = startTimeUs; + this.endTimeUs = endTimeUs; + } + + /** + * Returns the duration of the chunk in microseconds. + */ + public final long getDurationUs() { + return endTimeUs - startTimeUs; + } + + /** + * Returns the number of bytes that have been loaded. + */ + public abstract long bytesLoaded(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkExtractorWrapper.java new file mode 100644 index 0000000..c815cb0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkExtractorWrapper.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DummyTrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * An {@link Extractor} wrapper for loading chunks containing a single track. + *

    + * The wrapper allows switching of the {@link TrackOutput} that receives parsed data. + */ +public final class ChunkExtractorWrapper implements ExtractorOutput { + + /** + * Provides {@link TrackOutput} instances to be written to by the wrapper. + */ + public interface TrackOutputProvider { + + /** + * Called to get the {@link TrackOutput} for a specific track. + *

    + * The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}. + * + * @param id A track identifier. + * @param type The type of the track. Typically one of the + * {@link com.tangxiaolv.telegramgallery.exoplayer2.C} {@code TRACK_TYPE_*} constants. + * @return The {@link TrackOutput} for the given track identifier. + */ + TrackOutput track(int id, int type); + + } + + public final Extractor extractor; + + private final Format manifestFormat; + private final SparseArray bindingTrackOutputs; + + private boolean extractorInitialized; + private TrackOutputProvider trackOutputProvider; + private SeekMap seekMap; + private Format[] sampleFormats; + + /** + * @param extractor The extractor to wrap. + * @param manifestFormat A manifest defined {@link Format} whose data should be merged into any + * sample {@link Format} output from the {@link Extractor}. + */ + public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat) { + this.extractor = extractor; + this.manifestFormat = manifestFormat; + bindingTrackOutputs = new SparseArray<>(); + } + + /** + * Returns the {@link SeekMap} most recently output by the extractor, or null. + */ + public SeekMap getSeekMap() { + return seekMap; + } + + /** + * Returns the sample {@link Format}s most recently output by the extractor, or null. + */ + public Format[] getSampleFormats() { + return sampleFormats; + } + + /** + * Initializes the extractor to output to the provided {@link TrackOutput}, and configures it to + * receive data from a new chunk. + * + * @param trackOutputProvider The provider of {@link TrackOutput}s that will receive sample data. + */ + public void init(TrackOutputProvider trackOutputProvider) { + this.trackOutputProvider = trackOutputProvider; + if (!extractorInitialized) { + extractor.init(this); + extractorInitialized = true; + } else { + extractor.seek(0, 0); + for (int i = 0; i < bindingTrackOutputs.size(); i++) { + bindingTrackOutputs.valueAt(i).bind(trackOutputProvider); + } + } + } + + // ExtractorOutput implementation. + + @Override + public TrackOutput track(int id, int type) { + BindingTrackOutput bindingTrackOutput = bindingTrackOutputs.get(id); + if (bindingTrackOutput == null) { + // Assert that if we're seeing a new track we have not seen endTracks. + Assertions.checkState(sampleFormats == null); + bindingTrackOutput = new BindingTrackOutput(id, type, manifestFormat); + bindingTrackOutput.bind(trackOutputProvider); + bindingTrackOutputs.put(id, bindingTrackOutput); + } + return bindingTrackOutput; + } + + @Override + public void endTracks() { + Format[] sampleFormats = new Format[bindingTrackOutputs.size()]; + for (int i = 0; i < bindingTrackOutputs.size(); i++) { + sampleFormats[i] = bindingTrackOutputs.valueAt(i).sampleFormat; + } + this.sampleFormats = sampleFormats; + } + + @Override + public void seekMap(SeekMap seekMap) { + this.seekMap = seekMap; + } + + // Internal logic. + + private static final class BindingTrackOutput implements TrackOutput { + + private final int id; + private final int type; + private final Format manifestFormat; + + public Format sampleFormat; + private TrackOutput trackOutput; + + public BindingTrackOutput(int id, int type, Format manifestFormat) { + this.id = id; + this.type = type; + this.manifestFormat = manifestFormat; + } + + public void bind(TrackOutputProvider trackOutputProvider) { + if (trackOutputProvider == null) { + trackOutput = new DummyTrackOutput(); + return; + } + trackOutput = trackOutputProvider.track(id, type); + if (trackOutput != null) { + trackOutput.format(sampleFormat); + } + } + + @Override + public void format(Format format) { + // TODO: This should only happen for the primary track. Additional metadata/text tracks need + // to be copied with different manifest derived formats. + sampleFormat = format.copyWithManifestFormatInfo(manifestFormat); + trackOutput.format(sampleFormat); + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + return trackOutput.sampleData(input, length, allowEndOfInput); + } + + @Override + public void sampleData(ParsableByteArray data, int length) { + trackOutput.sampleData(data, length); + } + + @Override + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { + trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkHolder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkHolder.java new file mode 100644 index 0000000..3a87493 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkHolder.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +/** + * Holds a chunk or an indication that the end of the stream has been reached. + */ +public final class ChunkHolder { + + /** + * The chunk. + */ + public Chunk chunk; + + /** + * Indicates that the end of the stream has been reached. + */ + public boolean endOfStream; + + /** + * Clears the holder. + */ + public void clear() { + chunk = null; + endOfStream = false; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkSampleStream.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkSampleStream.java new file mode 100644 index 0000000..da4612c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkSampleStream.java @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultTrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SequenceableLoader; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * A {@link SampleStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}. + * May also be configured to expose additional embedded {@link SampleStream}s. + */ +public class ChunkSampleStream implements SampleStream, SequenceableLoader, + Loader.Callback { + + private final int primaryTrackType; + private final int[] embeddedTrackTypes; + private final boolean[] embeddedTracksSelected; + private final T chunkSource; + private final SequenceableLoader.Callback> callback; + private final EventDispatcher eventDispatcher; + private final int minLoadableRetryCount; + private final Loader loader; + private final ChunkHolder nextChunkHolder; + private final LinkedList mediaChunks; + private final List readOnlyMediaChunks; + private final DefaultTrackOutput primarySampleQueue; + private final DefaultTrackOutput[] embeddedSampleQueues; + private final BaseMediaChunkOutput mediaChunkOutput; + + private Format primaryDownstreamTrackFormat; + private long pendingResetPositionUs; + /* package */ long lastSeekPositionUs; + /* package */ boolean loadingFinished; + + /** + * @param primaryTrackType The type of the primary track. One of the {@link C} + * {@code TRACK_TYPE_*} constants. + * @param embeddedTrackTypes The types of any embedded tracks, or null. + * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. + * @param callback An {@link Callback} for the stream. + * @param allocator An {@link Allocator} from which allocations can be obtained. + * @param positionUs The position from which to start loading media. + * @param minLoadableRetryCount The minimum number of times that the source should retry a load + * before propagating an error. + * @param eventDispatcher A dispatcher to notify of events. + */ + public ChunkSampleStream(int primaryTrackType, int[] embeddedTrackTypes, T chunkSource, + Callback> callback, Allocator allocator, long positionUs, + int minLoadableRetryCount, EventDispatcher eventDispatcher) { + this.primaryTrackType = primaryTrackType; + this.embeddedTrackTypes = embeddedTrackTypes; + this.chunkSource = chunkSource; + this.callback = callback; + this.eventDispatcher = eventDispatcher; + this.minLoadableRetryCount = minLoadableRetryCount; + loader = new Loader("Loader:ChunkSampleStream"); + nextChunkHolder = new ChunkHolder(); + mediaChunks = new LinkedList<>(); + readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); + + int embeddedTrackCount = embeddedTrackTypes == null ? 0 : embeddedTrackTypes.length; + embeddedSampleQueues = new DefaultTrackOutput[embeddedTrackCount]; + embeddedTracksSelected = new boolean[embeddedTrackCount]; + int[] trackTypes = new int[1 + embeddedTrackCount]; + DefaultTrackOutput[] sampleQueues = new DefaultTrackOutput[1 + embeddedTrackCount]; + + primarySampleQueue = new DefaultTrackOutput(allocator); + trackTypes[0] = primaryTrackType; + sampleQueues[0] = primarySampleQueue; + + for (int i = 0; i < embeddedTrackCount; i++) { + DefaultTrackOutput trackOutput = new DefaultTrackOutput(allocator); + embeddedSampleQueues[i] = trackOutput; + sampleQueues[i + 1] = trackOutput; + trackTypes[i + 1] = embeddedTrackTypes[i]; + } + + mediaChunkOutput = new BaseMediaChunkOutput(trackTypes, sampleQueues); + pendingResetPositionUs = positionUs; + lastSeekPositionUs = positionUs; + } + + /** + * Discards buffered media for embedded tracks that are not currently selected, up to the + * specified position. + * + * @param positionUs The position to discard up to, in microseconds. + */ + public void discardUnselectedEmbeddedTracksTo(long positionUs) { + for (int i = 0; i < embeddedSampleQueues.length; i++) { + if (!embeddedTracksSelected[i]) { + embeddedSampleQueues[i].skipToKeyframeBefore(positionUs, true); + } + } + } + + /** + * Selects the embedded track, returning a new {@link EmbeddedSampleStream} from which the track's + * samples can be consumed. {@link EmbeddedSampleStream#release()} must be called on the returned + * stream when the track is no longer required, and before calling this method again to obtain + * another stream for the same track. + * + * @param positionUs The current playback position in microseconds. + * @param trackType The type of the embedded track to enable. + * @return The {@link EmbeddedSampleStream} for the embedded track. + */ + public EmbeddedSampleStream selectEmbeddedTrack(long positionUs, int trackType) { + for (int i = 0; i < embeddedSampleQueues.length; i++) { + if (embeddedTrackTypes[i] == trackType) { + Assertions.checkState(!embeddedTracksSelected[i]); + embeddedTracksSelected[i] = true; + embeddedSampleQueues[i].skipToKeyframeBefore(positionUs, true); + return new EmbeddedSampleStream(this, embeddedSampleQueues[i], i); + } + } + // Should never happen. + throw new IllegalStateException(); + } + + /** + * Returns the {@link ChunkSource} used by this stream. + */ + public T getChunkSource() { + return chunkSource; + } + + /** + * Returns an estimate of the position up to which data is buffered. + * + * @return An estimate of the absolute position in microseconds up to which data is buffered, or + * {@link C#TIME_END_OF_SOURCE} if the track is fully buffered. + */ + public long getBufferedPositionUs() { + if (loadingFinished) { + return C.TIME_END_OF_SOURCE; + } else if (isPendingReset()) { + return pendingResetPositionUs; + } else { + long bufferedPositionUs = lastSeekPositionUs; + BaseMediaChunk lastMediaChunk = mediaChunks.getLast(); + BaseMediaChunk lastCompletedMediaChunk = lastMediaChunk.isLoadCompleted() ? lastMediaChunk + : mediaChunks.size() > 1 ? mediaChunks.get(mediaChunks.size() - 2) : null; + if (lastCompletedMediaChunk != null) { + bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); + } + return Math.max(bufferedPositionUs, primarySampleQueue.getLargestQueuedTimestampUs()); + } + } + + /** + * Seeks to the specified position in microseconds. + * + * @param positionUs The seek position in microseconds. + */ + public void seekToUs(long positionUs) { + lastSeekPositionUs = positionUs; + // If we're not pending a reset, see if we can seek within the primary sample queue. + boolean seekInsideBuffer = !isPendingReset() && primarySampleQueue.skipToKeyframeBefore( + positionUs, positionUs < getNextLoadPositionUs()); + if (seekInsideBuffer) { + // We succeeded. We need to discard any chunks that we've moved past and perform the seek for + // any embedded streams as well. + while (mediaChunks.size() > 1 + && mediaChunks.get(1).getFirstSampleIndex(0) <= primarySampleQueue.getReadIndex()) { + mediaChunks.removeFirst(); + } + // TODO: For this to work correctly, the embedded streams must not discard anything from their + // sample queues beyond the current read position of the primary stream. + for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.skipToKeyframeBefore(positionUs, true); + } + } else { + // We failed, and need to restart. + pendingResetPositionUs = positionUs; + loadingFinished = false; + mediaChunks.clear(); + if (loader.isLoading()) { + loader.cancelLoading(); + } else { + primarySampleQueue.reset(true); + for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.reset(true); + } + } + } + } + + /** + * Releases the stream. + *

    + * This method should be called when the stream is no longer required. + */ + public void release() { + primarySampleQueue.disable(); + for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.disable(); + } + loader.release(); + } + + // SampleStream implementation. + + @Override + public boolean isReady() { + return loadingFinished || (!isPendingReset() && !primarySampleQueue.isEmpty()); + } + + @Override + public void maybeThrowError() throws IOException { + loader.maybeThrowError(); + if (!loader.isLoading()) { + chunkSource.maybeThrowError(); + } + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired) { + if (isPendingReset()) { + return C.RESULT_NOTHING_READ; + } + discardDownstreamMediaChunks(primarySampleQueue.getReadIndex()); + return primarySampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, + lastSeekPositionUs); + } + + @Override + public void skipData(long positionUs) { + if (loadingFinished && positionUs > primarySampleQueue.getLargestQueuedTimestampUs()) { + primarySampleQueue.skipAll(); + } else { + primarySampleQueue.skipToKeyframeBefore(positionUs, true); + } + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) { + chunkSource.onChunkLoadCompleted(loadable); + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, primaryTrackType, + loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, + loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded()); + callback.onContinueLoadingRequested(this); + } + + @Override + public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, + boolean released) { + eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, primaryTrackType, + loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, + loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded()); + if (!released) { + primarySampleQueue.reset(true); + for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.reset(true); + } + callback.onContinueLoadingRequested(this); + } + } + + @Override + public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, + IOException error) { + long bytesLoaded = loadable.bytesLoaded(); + boolean isMediaChunk = isMediaChunk(loadable); + boolean cancelable = !isMediaChunk || bytesLoaded == 0 || mediaChunks.size() > 1; + boolean canceled = false; + if (chunkSource.onChunkLoadError(loadable, cancelable, error)) { + canceled = true; + if (isMediaChunk) { + BaseMediaChunk removed = mediaChunks.removeLast(); + Assertions.checkState(removed == loadable); + primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); + for (int i = 0; i < embeddedSampleQueues.length; i++) { + embeddedSampleQueues[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); + } + if (mediaChunks.isEmpty()) { + pendingResetPositionUs = lastSeekPositionUs; + } + } + } + eventDispatcher.loadError(loadable.dataSpec, loadable.type, primaryTrackType, + loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, + loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, bytesLoaded, + error, canceled); + if (canceled) { + callback.onContinueLoadingRequested(this); + return Loader.DONT_RETRY; + } else { + return Loader.RETRY; + } + } + + // SequenceableLoader implementation + + @Override + public boolean continueLoading(long positionUs) { + if (loadingFinished || loader.isLoading()) { + return false; + } + + chunkSource.getNextChunk(mediaChunks.isEmpty() ? null : mediaChunks.getLast(), + pendingResetPositionUs != C.TIME_UNSET ? pendingResetPositionUs : positionUs, + nextChunkHolder); + boolean endOfStream = nextChunkHolder.endOfStream; + Chunk loadable = nextChunkHolder.chunk; + nextChunkHolder.clear(); + + if (endOfStream) { + loadingFinished = true; + return true; + } + + if (loadable == null) { + return false; + } + + if (isMediaChunk(loadable)) { + pendingResetPositionUs = C.TIME_UNSET; + BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable; + mediaChunk.init(mediaChunkOutput); + mediaChunks.add(mediaChunk); + } + long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, primaryTrackType, + loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData, + loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs); + return true; + } + + @Override + public long getNextLoadPositionUs() { + if (isPendingReset()) { + return pendingResetPositionUs; + } else { + return loadingFinished ? C.TIME_END_OF_SOURCE : mediaChunks.getLast().endTimeUs; + } + } + + // Internal methods + + // TODO[REFACTOR]: Call maybeDiscardUpstream for DASH and SmoothStreaming. + /** + * Discards media chunks from the back of the buffer if conditions have changed such that it's + * preferable to re-buffer the media at a different quality. + * + * @param positionUs The current playback position in microseconds. + */ + private void maybeDiscardUpstream(long positionUs) { + int queueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks); + discardUpstreamMediaChunks(Math.max(1, queueSize)); + } + + private boolean isMediaChunk(Chunk chunk) { + return chunk instanceof BaseMediaChunk; + } + + /* package */ boolean isPendingReset() { + return pendingResetPositionUs != C.TIME_UNSET; + } + + private void discardDownstreamMediaChunks(int primaryStreamReadIndex) { + while (mediaChunks.size() > 1 + && mediaChunks.get(1).getFirstSampleIndex(0) <= primaryStreamReadIndex) { + mediaChunks.removeFirst(); + } + BaseMediaChunk currentChunk = mediaChunks.getFirst(); + Format trackFormat = currentChunk.trackFormat; + if (!trackFormat.equals(primaryDownstreamTrackFormat)) { + eventDispatcher.downstreamFormatChanged(primaryTrackType, trackFormat, + currentChunk.trackSelectionReason, currentChunk.trackSelectionData, + currentChunk.startTimeUs); + } + primaryDownstreamTrackFormat = trackFormat; + } + + /** + * Discard upstream media chunks until the queue length is equal to the length specified. + * + * @param queueLength The desired length of the queue. + * @return Whether chunks were discarded. + */ + private boolean discardUpstreamMediaChunks(int queueLength) { + if (mediaChunks.size() <= queueLength) { + return false; + } + long startTimeUs = 0; + long endTimeUs = mediaChunks.getLast().endTimeUs; + BaseMediaChunk removed = null; + while (mediaChunks.size() > queueLength) { + removed = mediaChunks.removeLast(); + startTimeUs = removed.startTimeUs; + loadingFinished = false; + } + primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); + for (int i = 0; i < embeddedSampleQueues.length; i++) { + embeddedSampleQueues[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1)); + } + eventDispatcher.upstreamDiscarded(primaryTrackType, startTimeUs, endTimeUs); + return true; + } + + /** + * A {@link SampleStream} embedded in a {@link ChunkSampleStream}. + */ + public final class EmbeddedSampleStream implements SampleStream { + + public final ChunkSampleStream parent; + + private final DefaultTrackOutput sampleQueue; + private final int index; + + public EmbeddedSampleStream(ChunkSampleStream parent, DefaultTrackOutput sampleQueue, + int index) { + this.parent = parent; + this.sampleQueue = sampleQueue; + this.index = index; + } + + @Override + public boolean isReady() { + return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty()); + } + + @Override + public void skipData(long positionUs) { + if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { + sampleQueue.skipAll(); + } else { + sampleQueue.skipToKeyframeBefore(positionUs, true); + } + } + + @Override + public void maybeThrowError() throws IOException { + // Do nothing. Errors will be thrown from the primary stream. + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean formatRequired) { + if (isPendingReset()) { + return C.RESULT_NOTHING_READ; + } + return sampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished, + lastSeekPositionUs); + } + + public void release() { + Assertions.checkState(embeddedTracksSelected[index]); + embeddedTracksSelected[index] = false; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkSource.java new file mode 100644 index 0000000..84ace01 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkSource.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import java.io.IOException; +import java.util.List; + +/** + * A provider of {@link Chunk}s for a {@link ChunkSampleStream} to load. + */ +public interface ChunkSource { + + /** + * If the source is currently having difficulty providing chunks, then this method throws the + * underlying error. Otherwise does nothing. + *

    + * This method should only be called after the source has been prepared. + * + * @throws IOException The underlying error. + */ + void maybeThrowError() throws IOException; + + /** + * Evaluates whether {@link MediaChunk}s should be removed from the back of the queue. + *

    + * Removing {@link MediaChunk}s from the back of the queue can be useful if they could be replaced + * with chunks of a significantly higher quality (e.g. because the available bandwidth has + * substantially increased). + * + * @param playbackPositionUs The current playback position. + * @param queue The queue of buffered {@link MediaChunk}s. + * @return The preferred queue size. + */ + int getPreferredQueueSize(long playbackPositionUs, List queue); + + /** + * Returns the next chunk to load. + *

    + * If a chunk is available then {@link ChunkHolder#chunk} is set. If the end of the stream has + * been reached then {@link ChunkHolder#endOfStream} is set. If a chunk is not available but the + * end of the stream has not been reached, the {@link ChunkHolder} is not modified. + * + * @param previous The most recently loaded media chunk. + * @param playbackPositionUs The current playback position. If {@code previous} is null then this + * parameter is the position from which playback is expected to start (or restart) and hence + * should be interpreted as a seek position. + * @param out A holder to populate. + */ + void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out); + + /** + * Called when the {@link ChunkSampleStream} has finished loading a chunk obtained from this + * source. + *

    + * This method should only be called when the source is enabled. + * + * @param chunk The chunk whose load has been completed. + */ + void onChunkLoadCompleted(Chunk chunk); + + /** + * Called when the {@link ChunkSampleStream} encounters an error loading a chunk obtained from + * this source. + *

    + * This method should only be called when the source is enabled. + * + * @param chunk The chunk whose load encountered the error. + * @param cancelable Whether the load can be canceled. + * @param e The error. + * @return Whether the load should be canceled. + */ + boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java new file mode 100644 index 0000000..92cb16d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; + +/** + * Helper class for blacklisting tracks in a {@link TrackSelection} when 404 (Not Found) and 410 + * (Gone) HTTP response codes are encountered. + */ +public final class ChunkedTrackBlacklistUtil { + + /** + * The default duration for which a track is blacklisted in milliseconds. + */ + public static final long DEFAULT_TRACK_BLACKLIST_MS = 60000; + + private static final String TAG = "ChunkedTrackBlacklist"; + + /** + * Blacklists {@code trackSelectionIndex} in {@code trackSelection} for + * {@link #DEFAULT_TRACK_BLACKLIST_MS} if {@code e} is an {@link InvalidResponseCodeException} + * with {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. Else does nothing. + * Note that blacklisting will fail if the track is the only non-blacklisted track in the + * selection. + * + * @param trackSelection The track selection. + * @param trackSelectionIndex The index in the selection to consider blacklisting. + * @param e The error to inspect. + * @return Whether the track was blacklisted in the selection. + */ + public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex, + Exception e) { + return maybeBlacklistTrack(trackSelection, trackSelectionIndex, e, DEFAULT_TRACK_BLACKLIST_MS); + } + + /** + * Blacklists {@code trackSelectionIndex} in {@code trackSelection} for + * {@code blacklistDurationMs} if calling {@link #shouldBlacklist(Exception)} for {@code e} + * returns true. Else does nothing. Note that blacklisting will fail if the track is the only + * non-blacklisted track in the selection. + * + * @param trackSelection The track selection. + * @param trackSelectionIndex The index in the selection to consider blacklisting. + * @param e The error to inspect. + * @param blacklistDurationMs The duration to blacklist the track for, if it is blacklisted. + * @return Whether the track was blacklisted. + */ + public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex, + Exception e, long blacklistDurationMs) { + if (shouldBlacklist(e)) { + boolean blacklisted = trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs); + int responseCode = ((InvalidResponseCodeException) e).responseCode; + if (blacklisted) { + Log.w(TAG, "Blacklisted: duration=" + blacklistDurationMs + ", responseCode=" + + responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex)); + } else { + Log.w(TAG, "Blacklisting failed (cannot blacklist last enabled track): responseCode=" + + responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex)); + } + return blacklisted; + } + return false; + } + + /** + * Returns whether a loading error is an {@link InvalidResponseCodeException} with + * {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. + * + * @param e The loading error. + * @return Wheter the loading error is an {@link InvalidResponseCodeException} with + * {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. + */ + public static boolean shouldBlacklist(Exception e) { + if (e instanceof InvalidResponseCodeException) { + int responseCode = ((InvalidResponseCodeException) e).responseCode; + return responseCode == 404 || responseCode == 410; + } + return false; + } + + private ChunkedTrackBlacklistUtil() {} + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ContainerMediaChunk.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ContainerMediaChunk.java new file mode 100644 index 0000000..dfa15cd --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/ContainerMediaChunk.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A {@link BaseMediaChunk} that uses an {@link Extractor} to decode sample data. + */ +public class ContainerMediaChunk extends BaseMediaChunk { + + private final int chunkCount; + private final long sampleOffsetUs; + private final ChunkExtractorWrapper extractorWrapper; + + private volatile int bytesLoaded; + private volatile boolean loadCanceled; + private volatile boolean loadCompleted; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the media contained by the chunk, in microseconds. + * @param endTimeUs The end time of the media contained by the chunk, in microseconds. + * @param chunkIndex The index of the chunk. + * @param chunkCount The number of chunks in the underlying media that are spanned by this + * instance. Normally equal to one, but may be larger if multiple chunks as defined by the + * underlying media are being merged into a single load. + * @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor. + * @param extractorWrapper A wrapped extractor to use for parsing the data. + */ + public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, + int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper) { + super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, + endTimeUs, chunkIndex); + this.chunkCount = chunkCount; + this.sampleOffsetUs = sampleOffsetUs; + this.extractorWrapper = extractorWrapper; + } + + @Override + public int getNextChunkIndex() { + return chunkIndex + chunkCount; + } + + @Override + public boolean isLoadCompleted() { + return loadCompleted; + } + + @Override + public final long bytesLoaded() { + return bytesLoaded; + } + + // Loadable implementation. + + @Override + public final void cancelLoad() { + loadCanceled = true; + } + + @Override + public final boolean isLoadCanceled() { + return loadCanceled; + } + + @SuppressWarnings("NonAtomicVolatileUpdate") + @Override + public final void load() throws IOException, InterruptedException { + DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + try { + // Create and open the input. + ExtractorInput input = new DefaultExtractorInput(dataSource, + loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); + if (bytesLoaded == 0) { + // Configure the output and set it as the target for the extractor wrapper. + BaseMediaChunkOutput output = getOutput(); + output.setSampleOffsetUs(sampleOffsetUs); + extractorWrapper.init(output); + } + // Load and decode the sample data. + try { + Extractor extractor = extractorWrapper.extractor; + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractor.read(input, null); + } + Assertions.checkState(result != Extractor.RESULT_SEEK); + } finally { + bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); + } + } finally { + Util.closeQuietly(dataSource); + } + loadCompleted = true; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/DataChunk.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/DataChunk.java new file mode 100644 index 0000000..5f6d534 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/DataChunk.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.Arrays; + +/** + * A base class for {@link Chunk} implementations where the data should be loaded into a + * {@code byte[]} before being consumed. + */ +public abstract class DataChunk extends Chunk { + + private static final int READ_GRANULARITY = 16 * 1024; + + private byte[] data; + private int limit; + + private volatile boolean loadCanceled; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param type See {@link #type}. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param data An optional recycled array that can be used as a holder for the data. + */ + public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, byte[] data) { + super(dataSource, dataSpec, type, trackFormat, trackSelectionReason, trackSelectionData, + C.TIME_UNSET, C.TIME_UNSET); + this.data = data; + } + + /** + * Returns the array in which the data is held. + *

    + * This method should be used for recycling the holder only, and not for reading the data. + * + * @return The array in which the data is held. + */ + public byte[] getDataHolder() { + return data; + } + + @Override + public long bytesLoaded() { + return limit; + } + + // Loadable implementation + + @Override + public final void cancelLoad() { + loadCanceled = true; + } + + @Override + public final boolean isLoadCanceled() { + return loadCanceled; + } + + @Override + public final void load() throws IOException, InterruptedException { + try { + dataSource.open(dataSpec); + limit = 0; + int bytesRead = 0; + while (bytesRead != C.RESULT_END_OF_INPUT && !loadCanceled) { + maybeExpandData(); + bytesRead = dataSource.read(data, limit, READ_GRANULARITY); + if (bytesRead != -1) { + limit += bytesRead; + } + } + if (!loadCanceled) { + consume(data, limit); + } + } finally { + Util.closeQuietly(dataSource); + } + } + + /** + * Called by {@link #load()}. Implementations should override this method to consume the loaded + * data. + * + * @param data An array containing the data. + * @param limit The limit of the data. + * @throws IOException If an error occurs consuming the loaded data. + */ + protected abstract void consume(byte[] data, int limit) throws IOException; + + private void maybeExpandData() { + if (data == null) { + data = new byte[READ_GRANULARITY]; + } else if (data.length < limit + READ_GRANULARITY) { + // The new length is calculated as (data.length + READ_GRANULARITY) rather than + // (limit + READ_GRANULARITY) in order to avoid small increments in the length. + data = Arrays.copyOf(data, data.length + READ_GRANULARITY); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/InitializationChunk.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/InitializationChunk.java new file mode 100644 index 0000000..0ed78cf --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/InitializationChunk.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A {@link Chunk} that uses an {@link Extractor} to decode initialization data for single track. + */ +public final class InitializationChunk extends Chunk { + + private final ChunkExtractorWrapper extractorWrapper; + + private volatile int bytesLoaded; + private volatile boolean loadCanceled; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param extractorWrapper A wrapped extractor to use for parsing the initialization data. + */ + public InitializationChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, + ChunkExtractorWrapper extractorWrapper) { + super(dataSource, dataSpec, C.DATA_TYPE_MEDIA_INITIALIZATION, trackFormat, trackSelectionReason, + trackSelectionData, C.TIME_UNSET, C.TIME_UNSET); + this.extractorWrapper = extractorWrapper; + } + + @Override + public long bytesLoaded() { + return bytesLoaded; + } + + // Loadable implementation. + + @Override + public void cancelLoad() { + loadCanceled = true; + } + + @Override + public boolean isLoadCanceled() { + return loadCanceled; + } + + @SuppressWarnings("NonAtomicVolatileUpdate") + @Override + public void load() throws IOException, InterruptedException { + DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + try { + // Create and open the input. + ExtractorInput input = new DefaultExtractorInput(dataSource, + loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); + if (bytesLoaded == 0) { + extractorWrapper.init(null); + } + // Load and decode the initialization data. + try { + Extractor extractor = extractorWrapper.extractor; + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractor.read(input, null); + } + Assertions.checkState(result != Extractor.RESULT_SEEK); + } finally { + bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); + } + } finally { + Util.closeQuietly(dataSource); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/MediaChunk.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/MediaChunk.java new file mode 100644 index 0000000..cabbfea --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/MediaChunk.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * An abstract base class for {@link Chunk}s that contain media samples. + */ +public abstract class MediaChunk extends Chunk { + + /** + * The chunk index. + */ + public final int chunkIndex; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the media contained by the chunk, in microseconds. + * @param endTimeUs The end time of the media contained by the chunk, in microseconds. + * @param chunkIndex The index of the chunk. + */ + public MediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, + int chunkIndex) { + super(dataSource, dataSpec, C.DATA_TYPE_MEDIA, trackFormat, trackSelectionReason, + trackSelectionData, startTimeUs, endTimeUs); + Assertions.checkNotNull(trackFormat); + this.chunkIndex = chunkIndex; + } + + /** + * Returns the next chunk index. + */ + public int getNextChunkIndex() { + return chunkIndex + 1; + } + + /** + * Returns whether the chunk has been fully loaded. + */ + public abstract boolean isLoadCompleted(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/SingleSampleMediaChunk.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/SingleSampleMediaChunk.java new file mode 100644 index 0000000..556bf2f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/chunk/SingleSampleMediaChunk.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.chunk; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A {@link BaseMediaChunk} for chunks consisting of a single raw sample. + */ +public final class SingleSampleMediaChunk extends BaseMediaChunk { + + private final int trackType; + private final Format sampleFormat; + + private volatile int bytesLoaded; + private volatile boolean loadCanceled; + private volatile boolean loadCompleted; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the media contained by the chunk, in microseconds. + * @param endTimeUs The end time of the media contained by the chunk, in microseconds. + * @param chunkIndex The index of the chunk. + * @param trackType The type of the chunk. Typically one of the {@link C} {@code TRACK_TYPE_*} + * constants. + * @param sampleFormat The {@link Format} of the sample in the chunk. + */ + public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, + int chunkIndex, int trackType, Format sampleFormat) { + super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, + endTimeUs, chunkIndex); + this.trackType = trackType; + this.sampleFormat = sampleFormat; + } + + + @Override + public boolean isLoadCompleted() { + return loadCompleted; + } + + @Override + public long bytesLoaded() { + return bytesLoaded; + } + + // Loadable implementation. + + @Override + public void cancelLoad() { + loadCanceled = true; + } + + @Override + public boolean isLoadCanceled() { + return loadCanceled; + } + + @SuppressWarnings("NonAtomicVolatileUpdate") + @Override + public void load() throws IOException, InterruptedException { + DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + try { + // Create and open the input. + long length = dataSource.open(loadDataSpec); + if (length != C.LENGTH_UNSET) { + length += bytesLoaded; + } + ExtractorInput extractorInput = new DefaultExtractorInput(dataSource, bytesLoaded, length); + BaseMediaChunkOutput output = getOutput(); + output.setSampleOffsetUs(0); + TrackOutput trackOutput = output.track(0, trackType); + trackOutput.format(sampleFormat); + // Load the sample data. + int result = 0; + while (result != C.RESULT_END_OF_INPUT) { + bytesLoaded += result; + result = trackOutput.sampleData(extractorInput, Integer.MAX_VALUE, true); + } + int sampleSize = bytesLoaded; + trackOutput.sampleMetadata(startTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } finally { + Util.closeQuietly(dataSource); + } + loadCompleted = true; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashChunkSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashChunkSource.java new file mode 100644 index 0000000..5a95ddc --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashChunkSource.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash; + +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.DashManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.LoaderErrorThrower; + +/** + * An {@link ChunkSource} for DASH streams. + */ +public interface DashChunkSource extends ChunkSource { + + interface Factory { + + DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + DashManifest manifest, int periodIndex, int adaptationSetIndex, + TrackSelection trackSelection, long elapsedRealtimeOffsetMs, + boolean enableEventMessageTrack, boolean enableCea608Track); + + } + + void updateManifest(DashManifest newManifest, int periodIndex); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashMediaPeriod.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashMediaPeriod.java new file mode 100644 index 0000000..1a130a3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashMediaPeriod.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash; + +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.CompositeSequenceableLoader; +import com.tangxiaolv.telegramgallery.exoplayer2.source.EmptySampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaPeriod; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SequenceableLoader; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkSampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.AdaptationSet; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.DashManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.Representation; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.SchemeValuePair; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.LoaderErrorThrower; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +/** + * A DASH {@link MediaPeriod}. + */ +/* package */ final class DashMediaPeriod implements MediaPeriod, + SequenceableLoader.Callback> { + + /* package */ final int id; + private final DashChunkSource.Factory chunkSourceFactory; + private final int minLoadableRetryCount; + private final EventDispatcher eventDispatcher; + private final long elapsedRealtimeOffset; + private final LoaderErrorThrower manifestLoaderErrorThrower; + private final Allocator allocator; + private final TrackGroupArray trackGroups; + private final EmbeddedTrackInfo[] embeddedTrackInfos; + + private Callback callback; + private ChunkSampleStream[] sampleStreams; + private CompositeSequenceableLoader sequenceableLoader; + private DashManifest manifest; + private int periodIndex; + private List adaptationSets; + + public DashMediaPeriod(int id, DashManifest manifest, int periodIndex, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + EventDispatcher eventDispatcher, long elapsedRealtimeOffset, + LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { + this.id = id; + this.manifest = manifest; + this.periodIndex = periodIndex; + this.chunkSourceFactory = chunkSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventDispatcher = eventDispatcher; + this.elapsedRealtimeOffset = elapsedRealtimeOffset; + this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; + this.allocator = allocator; + sampleStreams = newSampleStreamArray(0); + sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; + Pair result = buildTrackGroups(adaptationSets); + trackGroups = result.first; + embeddedTrackInfos = result.second; + } + + public void updateManifest(DashManifest manifest, int periodIndex) { + this.manifest = manifest; + this.periodIndex = periodIndex; + adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; + if (sampleStreams != null) { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.getChunkSource().updateManifest(manifest, periodIndex); + } + callback.onContinueLoadingRequested(this); + } + } + + public void release() { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.release(); + } + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + callback.onPrepared(this); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + manifestLoaderErrorThrower.maybeThrowError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + int adaptationSetCount = adaptationSets.size(); + HashMap> primarySampleStreams = new HashMap<>(); + // First pass for primary tracks. + for (int i = 0; i < selections.length; i++) { + if (streams[i] instanceof ChunkSampleStream) { + @SuppressWarnings("unchecked") + ChunkSampleStream stream = (ChunkSampleStream) streams[i]; + if (selections[i] == null || !mayRetainStreamFlags[i]) { + stream.release(); + streams[i] = null; + } else { + int adaptationSetIndex = trackGroups.indexOf(selections[i].getTrackGroup()); + primarySampleStreams.put(adaptationSetIndex, stream); + } + } + if (streams[i] == null && selections[i] != null) { + int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); + if (trackGroupIndex < adaptationSetCount) { + ChunkSampleStream stream = buildSampleStream(trackGroupIndex, + selections[i], positionUs); + primarySampleStreams.put(trackGroupIndex, stream); + streams[i] = stream; + streamResetFlags[i] = true; + } + } + } + // Second pass for embedded tracks. + for (int i = 0; i < selections.length; i++) { + if ((streams[i] instanceof EmbeddedSampleStream || streams[i] instanceof EmptySampleStream) + && (selections[i] == null || !mayRetainStreamFlags[i])) { + // The stream is for an embedded track and is either no longer selected or needs replacing. + releaseIfEmbeddedSampleStream(streams[i]); + streams[i] = null; + } + // We need to consider replacing the stream even if it's non-null because the primary stream + // may have been replaced, selected or deselected. + if (selections[i] != null) { + int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); + if (trackGroupIndex >= adaptationSetCount) { + int embeddedTrackIndex = trackGroupIndex - adaptationSetCount; + EmbeddedTrackInfo embeddedTrackInfo = embeddedTrackInfos[embeddedTrackIndex]; + int adaptationSetIndex = embeddedTrackInfo.adaptationSetIndex; + ChunkSampleStream primaryStream = primarySampleStreams.get(adaptationSetIndex); + SampleStream stream = streams[i]; + boolean mayRetainStream = primaryStream == null ? stream instanceof EmptySampleStream + : (stream instanceof EmbeddedSampleStream + && ((EmbeddedSampleStream) stream).parent == primaryStream); + if (!mayRetainStream) { + releaseIfEmbeddedSampleStream(stream); + streams[i] = primaryStream == null ? new EmptySampleStream() + : primaryStream.selectEmbeddedTrack(positionUs, embeddedTrackInfo.trackType); + streamResetFlags[i] = true; + } + } + } + } + sampleStreams = newSampleStreamArray(primarySampleStreams.size()); + primarySampleStreams.values().toArray(sampleStreams); + sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + return positionUs; + } + + @Override + public void discardBuffer(long positionUs) { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.discardUnselectedEmbeddedTracksTo(positionUs); + } + } + + @Override + public boolean continueLoading(long positionUs) { + return sequenceableLoader.continueLoading(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return sequenceableLoader.getNextLoadPositionUs(); + } + + @Override + public long readDiscontinuity() { + return C.TIME_UNSET; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (ChunkSampleStream sampleStream : sampleStreams) { + long rendererBufferedPositionUs = sampleStream.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public long seekToUs(long positionUs) { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.seekToUs(positionUs); + } + return positionUs; + } + + // SequenceableLoader.Callback implementation. + + @Override + public void onContinueLoadingRequested(ChunkSampleStream sampleStream) { + callback.onContinueLoadingRequested(this); + } + + // Internal methods. + + private static Pair buildTrackGroups( + List adaptationSets) { + int adaptationSetCount = adaptationSets.size(); + int embeddedTrackCount = getEmbeddedTrackCount(adaptationSets); + TrackGroup[] trackGroupArray = new TrackGroup[adaptationSetCount + embeddedTrackCount]; + EmbeddedTrackInfo[] embeddedTrackInfos = new EmbeddedTrackInfo[embeddedTrackCount]; + + int embeddedTrackIndex = 0; + for (int i = 0; i < adaptationSetCount; i++) { + AdaptationSet adaptationSet = adaptationSets.get(i); + List representations = adaptationSet.representations; + Format[] formats = new Format[representations.size()]; + for (int j = 0; j < formats.length; j++) { + formats[j] = representations.get(j).format; + } + trackGroupArray[i] = new TrackGroup(formats); + if (hasEventMessageTrack(adaptationSet)) { + Format format = Format.createSampleFormat(adaptationSet.id + ":emsg", + MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null); + trackGroupArray[adaptationSetCount + embeddedTrackIndex] = new TrackGroup(format); + embeddedTrackInfos[embeddedTrackIndex++] = new EmbeddedTrackInfo(i, C.TRACK_TYPE_METADATA); + } + if (hasCea608Track(adaptationSet)) { + Format format = Format.createTextSampleFormat(adaptationSet.id + ":cea608", + MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null); + trackGroupArray[adaptationSetCount + embeddedTrackIndex] = new TrackGroup(format); + embeddedTrackInfos[embeddedTrackIndex++] = new EmbeddedTrackInfo(i, C.TRACK_TYPE_TEXT); + } + } + + return Pair.create(new TrackGroupArray(trackGroupArray), embeddedTrackInfos); + } + + private ChunkSampleStream buildSampleStream(int adaptationSetIndex, + TrackSelection selection, long positionUs) { + AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); + int embeddedTrackCount = 0; + int[] embeddedTrackTypes = new int[2]; + boolean enableEventMessageTrack = hasEventMessageTrack(adaptationSet); + if (enableEventMessageTrack) { + embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_METADATA; + } + boolean enableCea608Track = hasCea608Track(adaptationSet); + if (enableCea608Track) { + embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_TEXT; + } + if (embeddedTrackCount < embeddedTrackTypes.length) { + embeddedTrackTypes = Arrays.copyOf(embeddedTrackTypes, embeddedTrackCount); + } + DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( + manifestLoaderErrorThrower, manifest, periodIndex, adaptationSetIndex, selection, + elapsedRealtimeOffset, enableEventMessageTrack, enableCea608Track); + ChunkSampleStream stream = new ChunkSampleStream<>(adaptationSet.type, + embeddedTrackTypes, chunkSource, this, allocator, positionUs, minLoadableRetryCount, + eventDispatcher); + return stream; + } + + private static int getEmbeddedTrackCount(List adaptationSets) { + int embeddedTrackCount = 0; + for (int i = 0; i < adaptationSets.size(); i++) { + AdaptationSet adaptationSet = adaptationSets.get(i); + if (hasEventMessageTrack(adaptationSet)) { + embeddedTrackCount++; + } + if (hasCea608Track(adaptationSet)) { + embeddedTrackCount++; + } + } + return embeddedTrackCount; + } + + private static boolean hasEventMessageTrack(AdaptationSet adaptationSet) { + List representations = adaptationSet.representations; + for (int i = 0; i < representations.size(); i++) { + Representation representation = representations.get(i); + if (!representation.inbandEventStreams.isEmpty()) { + return true; + } + } + return false; + } + + private static boolean hasCea608Track(AdaptationSet adaptationSet) { + List descriptors = adaptationSet.accessibilityDescriptors; + for (int i = 0; i < descriptors.size(); i++) { + SchemeValuePair descriptor = descriptors.get(i); + if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri)) { + return true; + } + } + return false; + } + + @SuppressWarnings("unchecked") + private static ChunkSampleStream[] newSampleStreamArray(int length) { + return new ChunkSampleStream[length]; + } + + private static void releaseIfEmbeddedSampleStream(SampleStream sampleStream) { + if (sampleStream instanceof EmbeddedSampleStream) { + ((EmbeddedSampleStream) sampleStream).release(); + } + } + + private static final class EmbeddedTrackInfo { + + public final int adaptationSetIndex; + public final int trackType; + + public EmbeddedTrackInfo(int adaptationSetIndex, int trackType) { + this.adaptationSetIndex = adaptationSetIndex; + this.trackType = trackType; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashMediaSource.java new file mode 100644 index 0000000..22f4c02 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashMediaSource.java @@ -0,0 +1,795 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash; + +import android.net.Uri; +import android.os.Handler; +import android.os.SystemClock; +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaPeriod; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.DashManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.DashManifestParser; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.UtcTimingElement; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.LoaderErrorThrower; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.ParsingLoadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.TimeZone; + +/** + * A DASH {@link MediaSource}. + */ +public final class DashMediaSource implements MediaSource { + + /** + * The default minimum number of times to retry loading data prior to failing. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + /** + * A constant indicating that the presentation delay for live streams should be set to + * {@link DashManifest#suggestedPresentationDelay} if specified by the manifest, or + * {@link #DEFAULT_LIVE_PRESENTATION_DELAY_FIXED_MS} otherwise. The presentation delay is the + * duration by which the default start position precedes the end of the live window. + */ + public static final long DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS = -1; + /** + * A fixed default presentation delay for live streams. The presentation delay is the duration + * by which the default start position precedes the end of the live window. + */ + public static final long DEFAULT_LIVE_PRESENTATION_DELAY_FIXED_MS = 30000; + + /** + * The interval in milliseconds between invocations of + * {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} when the source's + * {@link Timeline} is changing dynamically (for example, for incomplete live streams). + */ + private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000; + /** + * The minimum default start position for live streams, relative to the start of the live window. + */ + private static final long MIN_LIVE_DEFAULT_START_POSITION_US = 5000000; + + private static final String TAG = "DashMediaSource"; + + private final boolean sideloadedManifest; + private final DataSource.Factory manifestDataSourceFactory; + private final DashChunkSource.Factory chunkSourceFactory; + private final int minLoadableRetryCount; + private final long livePresentationDelayMs; + private final EventDispatcher eventDispatcher; + private final ParsingLoadable.Parser manifestParser; + private final ManifestCallback manifestCallback; + private final Object manifestUriLock; + private final SparseArray periodsById; + private final Runnable refreshManifestRunnable; + private final Runnable simulateManifestRefreshRunnable; + + private Listener sourceListener; + private DataSource dataSource; + private Loader loader; + private LoaderErrorThrower loaderErrorThrower; + + private Uri manifestUri; + private long manifestLoadStartTimestamp; + private long manifestLoadEndTimestamp; + private DashManifest manifest; + private Handler handler; + private long elapsedRealtimeOffsetMs; + + private int firstPeriodId; + + /** + * Constructs an instance to play a given {@link DashManifest}, which must be static. + * + * @param manifest The manifest. {@link DashManifest#dynamic} must be false. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(DashManifest manifest, DashChunkSource.Factory chunkSourceFactory, + Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { + this(manifest, chunkSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler, + eventListener); + } + + /** + * Constructs an instance to play a given {@link DashManifest}, which must be static. + * + * @param manifest The manifest. {@link DashManifest#dynamic} must be false. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(DashManifest manifest, DashChunkSource.Factory chunkSourceFactory, + int minLoadableRetryCount, Handler eventHandler, AdaptiveMediaSourceEventListener + eventListener) { + this(manifest, null, null, null, chunkSourceFactory, minLoadableRetryCount, + DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or + * static. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + DashChunkSource.Factory chunkSourceFactory, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, manifestDataSourceFactory, chunkSourceFactory, + DEFAULT_MIN_LOADABLE_RETRY_COUNT, DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, + eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or + * static. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the + * default start position should precede the end of the live window. Use + * {@link #DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS} to use the value specified by + * the manifest, if present. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, manifestDataSourceFactory, new DashManifestParser(), chunkSourceFactory, + minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or + * static. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param manifestParser A parser for loaded manifest data. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the + * default start position should precede the end of the live window. Use + * {@link #DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS} to use the value specified by + * the manifest, if present. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + ParsingLoadable.Parser manifestParser, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, + minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + } + + private DashMediaSource(DashManifest manifest, Uri manifestUri, + DataSource.Factory manifestDataSourceFactory, + ParsingLoadable.Parser manifestParser, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this.manifest = manifest; + this.manifestUri = manifestUri; + this.manifestDataSourceFactory = manifestDataSourceFactory; + this.manifestParser = manifestParser; + this.chunkSourceFactory = chunkSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.livePresentationDelayMs = livePresentationDelayMs; + sideloadedManifest = manifest != null; + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + manifestUriLock = new Object(); + periodsById = new SparseArray<>(); + if (sideloadedManifest) { + Assertions.checkState(!manifest.dynamic); + manifestCallback = null; + refreshManifestRunnable = null; + simulateManifestRefreshRunnable = null; + } else { + manifestCallback = new ManifestCallback(); + refreshManifestRunnable = new Runnable() { + @Override + public void run() { + startLoadingManifest(); + } + }; + simulateManifestRefreshRunnable = new Runnable() { + @Override + public void run() { + processManifest(false); + } + }; + } + } + + /** + * Manually replaces the manifest {@link Uri}. + * + * @param manifestUri The replacement manifest {@link Uri}. + */ + public void replaceManifestUri(Uri manifestUri) { + synchronized (manifestUriLock) { + this.manifestUri = manifestUri; + } + } + + // MediaSource implementation. + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + sourceListener = listener; + if (sideloadedManifest) { + loaderErrorThrower = new LoaderErrorThrower.Dummy(); + processManifest(false); + } else { + dataSource = manifestDataSourceFactory.createDataSource(); + loader = new Loader("Loader:DashMediaSource"); + loaderErrorThrower = loader; + handler = new Handler(); + startLoadingManifest(); + } + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + loaderErrorThrower.maybeThrowError(); + } + + @Override + public MediaPeriod createPeriod(int periodIndex, Allocator allocator, long positionUs) { + EventDispatcher periodEventDispatcher = eventDispatcher.copyWithMediaTimeOffsetMs( + manifest.getPeriod(periodIndex).startMs); + DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + periodIndex, manifest, + periodIndex, chunkSourceFactory, minLoadableRetryCount, periodEventDispatcher, + elapsedRealtimeOffsetMs, loaderErrorThrower, allocator); + periodsById.put(mediaPeriod.id, mediaPeriod); + return mediaPeriod; + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + DashMediaPeriod dashMediaPeriod = (DashMediaPeriod) mediaPeriod; + dashMediaPeriod.release(); + periodsById.remove(dashMediaPeriod.id); + } + + @Override + public void releaseSource() { + dataSource = null; + loaderErrorThrower = null; + if (loader != null) { + loader.release(); + loader = null; + } + manifestLoadStartTimestamp = 0; + manifestLoadEndTimestamp = 0; + manifest = null; + if (handler != null) { + handler.removeCallbacksAndMessages(null); + handler = null; + } + elapsedRealtimeOffsetMs = 0; + periodsById.clear(); + } + + // Loadable callbacks. + + /* package */ void onManifestLoadCompleted(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs) { + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + DashManifest newManifest = loadable.getResult(); + + int periodCount = manifest == null ? 0 : manifest.getPeriodCount(); + int removedPeriodCount = 0; + long newFirstPeriodStartTimeMs = newManifest.getPeriod(0).startMs; + while (removedPeriodCount < periodCount + && manifest.getPeriod(removedPeriodCount).startMs < newFirstPeriodStartTimeMs) { + removedPeriodCount++; + } + + // After discarding old periods, we should never have more periods than listed in the new + // manifest. That would mean that a previously announced period is no longer advertised. If + // this condition occurs, assume that we are hitting a manifest server that is out of sync and + // behind, discard this manifest, and try again later. + if (periodCount - removedPeriodCount > newManifest.getPeriodCount()) { + scheduleManifestRefresh(); + return; + } + + manifest = newManifest; + manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs; + manifestLoadEndTimestamp = elapsedRealtimeMs; + if (manifest.location != null) { + synchronized (manifestUriLock) { + // This condition checks that replaceManifestUri wasn't called between the start and end of + // this load. If it was, we ignore the manifest location and prefer the manual replacement. + if (loadable.dataSpec.uri == manifestUri) { + manifestUri = manifest.location; + } + } + } + + if (periodCount == 0) { + if (manifest.utcTiming != null) { + resolveUtcTimingElement(manifest.utcTiming); + } else { + processManifest(true); + } + } else { + firstPeriodId += removedPeriodCount; + processManifest(true); + } + } + + /* package */ int onManifestLoadError(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs, IOException error) { + boolean isFatal = error instanceof ParserException; + eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded(), error, isFatal); + return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; + } + + /* package */ void onUtcTimestampLoadCompleted(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs) { + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + onUtcTimestampResolved(loadable.getResult() - elapsedRealtimeMs); + } + + /* package */ int onUtcTimestampLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded(), error, true); + onUtcTimestampResolutionError(error); + return Loader.DONT_RETRY; + } + + /* package */ void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + // Internal methods. + + private void startLoadingManifest() { + Uri manifestUri; + synchronized (manifestUriLock) { + manifestUri = this.manifestUri; + } + startLoading(new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST, + manifestParser), manifestCallback, minLoadableRetryCount); + } + + private void resolveUtcTimingElement(UtcTimingElement timingElement) { + String scheme = timingElement.schemeIdUri; + if (Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2012")) { + resolveUtcTimingElementDirect(timingElement); + } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-iso:2014")) { + resolveUtcTimingElementHttp(timingElement, new Iso8601Parser()); + } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2012") + || Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2014")) { + resolveUtcTimingElementHttp(timingElement, new XsDateTimeParser()); + } else { + // Unsupported scheme. + onUtcTimestampResolutionError(new IOException("Unsupported UTC timing scheme")); + } + } + + private void resolveUtcTimingElementDirect(UtcTimingElement timingElement) { + try { + long utcTimestamp = Util.parseXsDateTime(timingElement.value); + onUtcTimestampResolved(utcTimestamp - manifestLoadEndTimestamp); + } catch (ParserException e) { + onUtcTimestampResolutionError(e); + } + } + + private void resolveUtcTimingElementHttp(UtcTimingElement timingElement, + ParsingLoadable.Parser parser) { + startLoading(new ParsingLoadable<>(dataSource, Uri.parse(timingElement.value), + C.DATA_TYPE_TIME_SYNCHRONIZATION, parser), new UtcTimestampCallback(), 1); + } + + private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) { + this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; + processManifest(true); + } + + private void onUtcTimestampResolutionError(IOException error) { + // Be optimistic and continue in the hope that the device clock is correct. + processManifest(true); + } + + private void processManifest(boolean scheduleRefresh) { + // Update any periods. + for (int i = 0; i < periodsById.size(); i++) { + int id = periodsById.keyAt(i); + if (id >= firstPeriodId) { + periodsById.valueAt(i).updateManifest(manifest, id - firstPeriodId); + } else { + // This period has been removed from the manifest so it doesn't need to be updated. + } + } + // Update the window. + boolean windowChangingImplicitly = false; + int lastPeriodIndex = manifest.getPeriodCount() - 1; + PeriodSeekInfo firstPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(manifest.getPeriod(0), + manifest.getPeriodDurationUs(0)); + PeriodSeekInfo lastPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo( + manifest.getPeriod(lastPeriodIndex), manifest.getPeriodDurationUs(lastPeriodIndex)); + // Get the period-relative start/end times. + long currentStartTimeUs = firstPeriodSeekInfo.availableStartTimeUs; + long currentEndTimeUs = lastPeriodSeekInfo.availableEndTimeUs; + if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) { + // The manifest describes an incomplete live stream. Update the start/end times to reflect the + // live stream duration and the manifest's time shift buffer depth. + long liveStreamDurationUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTime); + long liveStreamEndPositionInLastPeriodUs = liveStreamDurationUs + - C.msToUs(manifest.getPeriod(lastPeriodIndex).startMs); + currentEndTimeUs = Math.min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs); + if (manifest.timeShiftBufferDepth != C.TIME_UNSET) { + long timeShiftBufferDepthUs = C.msToUs(manifest.timeShiftBufferDepth); + long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs; + int periodIndex = lastPeriodIndex; + while (offsetInPeriodUs < 0 && periodIndex > 0) { + offsetInPeriodUs += manifest.getPeriodDurationUs(--periodIndex); + } + if (periodIndex == 0) { + currentStartTimeUs = Math.max(currentStartTimeUs, offsetInPeriodUs); + } else { + // The time shift buffer starts after the earliest period. + // TODO: Does this ever happen? + currentStartTimeUs = manifest.getPeriodDurationUs(0); + } + } + windowChangingImplicitly = true; + } + long windowDurationUs = currentEndTimeUs - currentStartTimeUs; + for (int i = 0; i < manifest.getPeriodCount() - 1; i++) { + windowDurationUs += manifest.getPeriodDurationUs(i); + } + long windowDefaultStartPositionUs = 0; + if (manifest.dynamic) { + long presentationDelayForManifestMs = livePresentationDelayMs; + if (presentationDelayForManifestMs == DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS) { + presentationDelayForManifestMs = manifest.suggestedPresentationDelay != C.TIME_UNSET + ? manifest.suggestedPresentationDelay : DEFAULT_LIVE_PRESENTATION_DELAY_FIXED_MS; + } + // Snap the default position to the start of the segment containing it. + windowDefaultStartPositionUs = windowDurationUs - C.msToUs(presentationDelayForManifestMs); + if (windowDefaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) { + // The default start position is too close to the start of the live window. Set it to the + // minimum default start position provided the window is at least twice as big. Else set + // it to the middle of the window. + windowDefaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, + windowDurationUs / 2); + } + } + long windowStartTimeMs = manifest.availabilityStartTime + + manifest.getPeriod(0).startMs + C.usToMs(currentStartTimeUs); + DashTimeline timeline = new DashTimeline(manifest.availabilityStartTime, windowStartTimeMs, + firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs, + manifest); + sourceListener.onSourceInfoRefreshed(timeline, manifest); + + if (!sideloadedManifest) { + // Remove any pending simulated refresh. + handler.removeCallbacks(simulateManifestRefreshRunnable); + // If the window is changing implicitly, post a simulated manifest refresh to update it. + if (windowChangingImplicitly) { + handler.postDelayed(simulateManifestRefreshRunnable, NOTIFY_MANIFEST_INTERVAL_MS); + } + // Schedule an explicit refresh if needed. + if (scheduleRefresh) { + scheduleManifestRefresh(); + } + } + } + + private void scheduleManifestRefresh() { + if (!manifest.dynamic) { + return; + } + long minUpdatePeriod = manifest.minUpdatePeriod; + if (minUpdatePeriod == 0) { + // TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where + // minUpdatePeriod is set to 0. In such cases we shouldn't refresh unless there is explicit + // signaling in the stream, according to: + // http://azure.microsoft.com/blog/2014/09/13/dash-live-streaming-with-azure-media-service/ + minUpdatePeriod = 5000; + } + long nextLoadTimestamp = manifestLoadStartTimestamp + minUpdatePeriod; + long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime()); + handler.postDelayed(refreshManifestRunnable, delayUntilNextLoad); + } + + private void startLoading(ParsingLoadable loadable, + Loader.Callback> callback, int minRetryCount) { + long elapsedRealtimeMs = loader.startLoading(loadable, callback, minRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs); + } + + private long getNowUnixTimeUs() { + if (elapsedRealtimeOffsetMs != 0) { + return C.msToUs(SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs); + } else { + return C.msToUs(System.currentTimeMillis()); + } + } + + private static final class PeriodSeekInfo { + + public static PeriodSeekInfo createPeriodSeekInfo( + com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.Period period, long durationUs) { + int adaptationSetCount = period.adaptationSets.size(); + long availableStartTimeUs = 0; + long availableEndTimeUs = Long.MAX_VALUE; + boolean isIndexExplicit = false; + boolean seenEmptyIndex = false; + for (int i = 0; i < adaptationSetCount; i++) { + DashSegmentIndex index = period.adaptationSets.get(i).representations.get(0).getIndex(); + if (index == null) { + return new PeriodSeekInfo(true, 0, durationUs); + } + isIndexExplicit |= index.isExplicit(); + int segmentCount = index.getSegmentCount(durationUs); + if (segmentCount == 0) { + seenEmptyIndex = true; + availableStartTimeUs = 0; + availableEndTimeUs = 0; + } else if (!seenEmptyIndex) { + int firstSegmentNum = index.getFirstSegmentNum(); + long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstSegmentNum); + availableStartTimeUs = Math.max(availableStartTimeUs, adaptationSetAvailableStartTimeUs); + if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED) { + int lastSegmentNum = firstSegmentNum + segmentCount - 1; + long adaptationSetAvailableEndTimeUs = index.getTimeUs(lastSegmentNum) + + index.getDurationUs(lastSegmentNum, durationUs); + availableEndTimeUs = Math.min(availableEndTimeUs, adaptationSetAvailableEndTimeUs); + } + } + } + return new PeriodSeekInfo(isIndexExplicit, availableStartTimeUs, availableEndTimeUs); + } + + public final boolean isIndexExplicit; + public final long availableStartTimeUs; + public final long availableEndTimeUs; + + private PeriodSeekInfo(boolean isIndexExplicit, long availableStartTimeUs, + long availableEndTimeUs) { + this.isIndexExplicit = isIndexExplicit; + this.availableStartTimeUs = availableStartTimeUs; + this.availableEndTimeUs = availableEndTimeUs; + } + + } + + private static final class DashTimeline extends Timeline { + + private final long presentationStartTimeMs; + private final long windowStartTimeMs; + + private final int firstPeriodId; + private final long offsetInFirstPeriodUs; + private final long windowDurationUs; + private final long windowDefaultStartPositionUs; + private final DashManifest manifest; + + public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs, + int firstPeriodId, long offsetInFirstPeriodUs, long windowDurationUs, + long windowDefaultStartPositionUs, DashManifest manifest) { + this.presentationStartTimeMs = presentationStartTimeMs; + this.windowStartTimeMs = windowStartTimeMs; + this.firstPeriodId = firstPeriodId; + this.offsetInFirstPeriodUs = offsetInFirstPeriodUs; + this.windowDurationUs = windowDurationUs; + this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; + this.manifest = manifest; + } + + @Override + public int getPeriodCount() { + return manifest.getPeriodCount(); + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIdentifiers) { + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); + Object id = setIdentifiers ? manifest.getPeriod(periodIndex).id : null; + Object uid = setIdentifiers ? firstPeriodId + + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()) : null; + return period.set(id, uid, 0, manifest.getPeriodDurationUs(periodIndex), + C.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs) + - offsetInFirstPeriodUs, false); + } + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIdentifier, + long defaultPositionProjectionUs) { + Assertions.checkIndex(windowIndex, 0, 1); + long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs( + defaultPositionProjectionUs); + return window.set(null, presentationStartTimeMs, windowStartTimeMs, true /* isSeekable */, + manifest.dynamic, windowDefaultStartPositionUs, windowDurationUs, 0, + manifest.getPeriodCount() - 1, offsetInFirstPeriodUs); + } + + @Override + public int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Integer)) { + return C.INDEX_UNSET; + } + int periodId = (int) uid; + return periodId < firstPeriodId || periodId >= firstPeriodId + getPeriodCount() + ? C.INDEX_UNSET : (periodId - firstPeriodId); + } + + private long getAdjustedWindowDefaultStartPositionUs(long defaultPositionProjectionUs) { + long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs; + if (!manifest.dynamic) { + return windowDefaultStartPositionUs; + } + if (defaultPositionProjectionUs > 0) { + windowDefaultStartPositionUs += defaultPositionProjectionUs; + if (windowDefaultStartPositionUs > windowDurationUs) { + // The projection takes us beyond the end of the live window. + return C.TIME_UNSET; + } + } + // Attempt to snap to the start of the corresponding video segment. + int periodIndex = 0; + long defaultStartPositionInPeriodUs = offsetInFirstPeriodUs + windowDefaultStartPositionUs; + long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + while (periodIndex < manifest.getPeriodCount() - 1 + && defaultStartPositionInPeriodUs >= periodDurationUs) { + defaultStartPositionInPeriodUs -= periodDurationUs; + periodIndex++; + periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + } + com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.Period period = + manifest.getPeriod(periodIndex); + int videoAdaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO); + if (videoAdaptationSetIndex == C.INDEX_UNSET) { + // No video adaptation set for snapping. + return windowDefaultStartPositionUs; + } + // If there are multiple video adaptation sets with unaligned segments, the initial time may + // not correspond to the start of a segment in both, but this is an edge case. + DashSegmentIndex snapIndex = period.adaptationSets.get(videoAdaptationSetIndex) + .representations.get(0).getIndex(); + if (snapIndex == null || snapIndex.getSegmentCount(periodDurationUs) == 0) { + // Video adaptation set does not include a non-empty index for snapping. + return windowDefaultStartPositionUs; + } + int segmentNum = snapIndex.getSegmentNum(defaultStartPositionInPeriodUs, periodDurationUs); + return windowDefaultStartPositionUs + snapIndex.getTimeUs(segmentNum) + - defaultStartPositionInPeriodUs; + } + + } + + private final class ManifestCallback implements + Loader.Callback> { + + @Override + public void onLoadCompleted(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs) { + onManifestLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs, boolean released) { + DashMediaSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs); + } + + @Override + public int onLoadError(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs, IOException error) { + return onManifestLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error); + } + + } + + private final class UtcTimestampCallback implements Loader.Callback> { + + @Override + public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + onUtcTimestampLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + DashMediaSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs); + } + + @Override + public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + return onUtcTimestampLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error); + } + + } + + private static final class XsDateTimeParser implements ParsingLoadable.Parser { + + @Override + public Long parse(Uri uri, InputStream inputStream) throws IOException { + String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine(); + return Util.parseXsDateTime(firstLine); + } + + } + + private static final class Iso8601Parser implements ParsingLoadable.Parser { + + @Override + public Long parse(Uri uri, InputStream inputStream) throws IOException { + String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine(); + try { + // TODO: It may be necessary to handle timestamp offsets from UTC. + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format.parse(firstLine).getTime(); + } catch (ParseException e) { + throw new ParserException(e); + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashSegmentIndex.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashSegmentIndex.java new file mode 100644 index 0000000..a3c5045 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashSegmentIndex.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.RangedUri; + +/** + * Indexes the segments within a media stream. + */ +public interface DashSegmentIndex { + + int INDEX_UNBOUNDED = -1; + + /** + * Returns {@code getFirstSegmentNum()} if the index has no segments or if the given media time is + * earlier than the start of the first segment. Returns {@code getFirstSegmentNum() + + * getSegmentCount() - 1} if the given media time is later than the end of the last segment. + * Otherwise, returns the segment number of the segment containing the given media time. + * + * @param timeUs The time in microseconds. + * @param periodDurationUs The duration of the enclosing period in microseconds, or + * {@link C#TIME_UNSET} if the period's duration is not yet known. + * @return The segment number of the corresponding segment. + */ + int getSegmentNum(long timeUs, long periodDurationUs); + + /** + * Returns the start time of a segment. + * + * @param segmentNum The segment number. + * @return The corresponding start time in microseconds. + */ + long getTimeUs(int segmentNum); + + /** + * Returns the duration of a segment. + * + * @param segmentNum The segment number. + * @param periodDurationUs The duration of the enclosing period in microseconds, or + * {@link C#TIME_UNSET} if the period's duration is not yet known. + * @return The duration of the segment, in microseconds. + */ + long getDurationUs(int segmentNum, long periodDurationUs); + + /** + * Returns a {@link RangedUri} defining the location of a segment. + * + * @param segmentNum The segment number. + * @return The {@link RangedUri} defining the location of the data. + */ + RangedUri getSegmentUrl(int segmentNum); + + /** + * Returns the segment number of the first segment. + * + * @return The segment number of the first segment. + */ + int getFirstSegmentNum(); + + /** + * Returns the number of segments in the index, or {@link #INDEX_UNBOUNDED}. + *

    + * An unbounded index occurs if a dynamic manifest uses SegmentTemplate elements without a + * SegmentTimeline element, and if the period duration is not yet known. In this case the caller + * must manually determine the window of currently available segments. + * + * @param periodDurationUs The duration of the enclosing period in microseconds, or + * {@link C#TIME_UNSET} if the period's duration is not yet known. + * @return The number of segments in the index, or {@link #INDEX_UNBOUNDED}. + */ + int getSegmentCount(long periodDurationUs); + + /** + * Returns true if segments are defined explicitly by the index. + *

    + * If true is returned, each segment is defined explicitly by the index data, and all of the + * listed segments are guaranteed to be available at the time when the index was obtained. + *

    + * If false is returned then segment information was derived from properties such as a fixed + * segment duration. If the presentation is dynamic, it's possible that only a subset of the + * segments are available. + * + * @return Whether segments are defined explicitly by the index. + */ + boolean isExplicit(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashUtil.java new file mode 100644 index 0000000..dec273e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashUtil.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ChunkIndex; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv.MatroskaExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkExtractorWrapper; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.InitializationChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.AdaptationSet; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.DashManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.DashManifestParser; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.Period; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.RangedUri; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.Representation; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSourceInputStream; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import java.io.IOException; +import java.util.List; + +/** + * Utility methods for DASH streams. + */ +public final class DashUtil { + + /** + * Loads a DASH manifest. + * + * @param dataSource The {@link HttpDataSource} from which the manifest should be read. + * @param manifestUri The URI of the manifest to be read. + * @return An instance of {@link DashManifest}. + * @throws IOException Thrown when there is an error while loading. + */ + public static DashManifest loadManifest(DataSource dataSource, String manifestUri) + throws IOException { + DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, + new DataSpec(Uri.parse(manifestUri), DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)); + try { + inputStream.open(); + DashManifestParser parser = new DashManifestParser(); + return parser.parse(dataSource.getUri(), inputStream); + } finally { + inputStream.close(); + } + } + + /** + * Loads {@link DrmInitData} for a given manifest. + * + * @param dataSource The {@link HttpDataSource} from which data should be loaded. + * @param dashManifest The {@link DashManifest} of the DASH content. + * @return The loaded {@link DrmInitData}. + */ + public static DrmInitData loadDrmInitData(DataSource dataSource, DashManifest dashManifest) + throws IOException, InterruptedException { + // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream, + // as per DASH IF Interoperability Recommendations V3.0, 7.5.3. + if (dashManifest.getPeriodCount() < 1) { + return null; + } + Period period = dashManifest.getPeriod(0); + int adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO); + if (adaptationSetIndex == C.INDEX_UNSET) { + adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_AUDIO); + if (adaptationSetIndex == C.INDEX_UNSET) { + return null; + } + } + AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); + if (adaptationSet.representations.isEmpty()) { + return null; + } + Representation representation = adaptationSet.representations.get(0); + DrmInitData drmInitData = representation.format.drmInitData; + if (drmInitData == null) { + Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation); + if (sampleFormat != null) { + drmInitData = sampleFormat.drmInitData; + } + if (drmInitData == null) { + return null; + } + } + return drmInitData; + } + + /** + * Loads initialization data for the {@code representation} and returns the sample {@link + * Format}. + * Loads {@link DrmInitData} for a given period in a DASH manifest. + * + * @param dataSource The {@link HttpDataSource} from which data should be loaded. + * @param period The {@link Period}. + * @return The loaded {@link DrmInitData}, or null if none is defined. + * @throws IOException Thrown when there is an error while loading. + * @throws InterruptedException Thrown if the thread was interrupted. + */ + public static DrmInitData loadDrmInitData(DataSource dataSource, Period period) + throws IOException, InterruptedException { + Representation representation = getFirstRepresentation(period, C.TRACK_TYPE_VIDEO); + if (representation == null) { + representation = getFirstRepresentation(period, C.TRACK_TYPE_AUDIO); + if (representation == null) { + return null; + } + } + DrmInitData drmInitData = representation.format.drmInitData; + if (drmInitData != null) { + // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream, + // as per DASH IF Interoperability Recommendations V3.0, 7.5.3. + return drmInitData; + } + Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation); + return sampleFormat == null ? null : sampleFormat.drmInitData; + } + + /** + * Loads initialization data for the {@code representation} and returns the sample {@link Format}. + * + * @param dataSource The source from which the data should be loaded. + * @param representation The representation which initialization chunk belongs to. + * @return the sample {@link Format} of the given representation. + * @throws IOException Thrown when there is an error while loading. + * @throws InterruptedException Thrown if the thread was interrupted. + */ + public static Format loadSampleFormat(DataSource dataSource, Representation representation) + throws IOException, InterruptedException { + ChunkExtractorWrapper extractorWrapper = loadInitializationData(dataSource, representation, + false); + return extractorWrapper == null ? null : extractorWrapper.getSampleFormats()[0]; + } + + /** + * Loads initialization and index data for the {@code representation} and returns the {@link + * ChunkIndex}. + * + * @param dataSource The source from which the data should be loaded. + * @param representation The representation which initialization chunk belongs to. + * @return {@link ChunkIndex} of the given representation. + * @throws IOException Thrown when there is an error while loading. + * @throws InterruptedException Thrown if the thread was interrupted. + */ + public static ChunkIndex loadChunkIndex(DataSource dataSource, Representation representation) + throws IOException, InterruptedException { + ChunkExtractorWrapper extractorWrapper = loadInitializationData(dataSource, representation, + true); + return extractorWrapper == null ? null : (ChunkIndex) extractorWrapper.getSeekMap(); + } + + /** + * Loads initialization data for the {@code representation} and optionally index data then + * returns a {@link ChunkExtractorWrapper} which contains the output. + * + * @param dataSource The source from which the data should be loaded. + * @param representation The representation which initialization chunk belongs to. + * @param loadIndex Whether to load index data too. + * @return A {@link ChunkExtractorWrapper} for the {@code representation}, or null if no + * initialization or (if requested) index data exists. + * @throws IOException Thrown when there is an error while loading. + * @throws InterruptedException Thrown if the thread was interrupted. + */ + private static ChunkExtractorWrapper loadInitializationData(DataSource dataSource, + Representation representation, boolean loadIndex) + throws IOException, InterruptedException { + RangedUri initializationUri = representation.getInitializationUri(); + if (initializationUri == null) { + return null; + } + ChunkExtractorWrapper extractorWrapper = newWrappedExtractor(representation.format); + RangedUri requestUri; + if (loadIndex) { + RangedUri indexUri = representation.getIndexUri(); + if (indexUri == null) { + return null; + } + // It's common for initialization and index data to be stored adjacently. Attempt to merge + // the two requests together to request both at once. + requestUri = initializationUri.attemptMerge(indexUri, representation.baseUrl); + if (requestUri == null) { + loadInitializationData(dataSource, representation, extractorWrapper, initializationUri); + requestUri = indexUri; + } + } else { + requestUri = initializationUri; + } + loadInitializationData(dataSource, representation, extractorWrapper, requestUri); + return extractorWrapper; + } + + private static void loadInitializationData(DataSource dataSource, + Representation representation, ChunkExtractorWrapper extractorWrapper, RangedUri requestUri) + throws IOException, InterruptedException { + DataSpec dataSpec = new DataSpec(requestUri.resolveUri(representation.baseUrl), + requestUri.start, requestUri.length, representation.getCacheKey()); + InitializationChunk initializationChunk = new InitializationChunk(dataSource, dataSpec, + representation.format, C.SELECTION_REASON_UNKNOWN, null /* trackSelectionData */, + extractorWrapper); + initializationChunk.load(); + } + + private static ChunkExtractorWrapper newWrappedExtractor(Format format) { + String mimeType = format.containerMimeType; + boolean isWebm = mimeType.startsWith(MimeTypes.VIDEO_WEBM) + || mimeType.startsWith(MimeTypes.AUDIO_WEBM); + Extractor extractor = isWebm ? new MatroskaExtractor() : new FragmentedMp4Extractor(); + return new ChunkExtractorWrapper(extractor, format); + } + + private static Representation getFirstRepresentation(Period period, int type) { + int index = period.getAdaptationSetIndex(type); + if (index == C.INDEX_UNSET) { + return null; + } + List representations = period.adaptationSets.get(index).representations; + return representations.isEmpty() ? null : representations.get(0); + } + + private DashUtil() {} + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashWrappingSegmentIndex.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashWrappingSegmentIndex.java new file mode 100644 index 0000000..a3732af --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DashWrappingSegmentIndex.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash; + +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ChunkIndex; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.RangedUri; + +/** + * An implementation of {@link DashSegmentIndex} that wraps a {@link ChunkIndex} parsed from a + * media stream. + */ +public final class DashWrappingSegmentIndex implements DashSegmentIndex { + + private final ChunkIndex chunkIndex; + + /** + * @param chunkIndex The {@link ChunkIndex} to wrap. + */ + public DashWrappingSegmentIndex(ChunkIndex chunkIndex) { + this.chunkIndex = chunkIndex; + } + + @Override + public int getFirstSegmentNum() { + return 0; + } + + @Override + public int getSegmentCount(long periodDurationUs) { + return chunkIndex.length; + } + + @Override + public long getTimeUs(int segmentNum) { + return chunkIndex.timesUs[segmentNum]; + } + + @Override + public long getDurationUs(int segmentNum, long periodDurationUs) { + return chunkIndex.durationsUs[segmentNum]; + } + + @Override + public RangedUri getSegmentUrl(int segmentNum) { + return new RangedUri(null, chunkIndex.offsets[segmentNum], chunkIndex.sizes[segmentNum]); + } + + @Override + public int getSegmentNum(long timeUs, long periodDurationUs) { + return chunkIndex.getChunkIndex(timeUs); + } + + @Override + public boolean isExplicit() { + return true; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DefaultDashChunkSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DefaultDashChunkSource.java new file mode 100644 index 0000000..7796ef7 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash; + +import android.net.Uri; +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ChunkIndex; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mkv.MatroskaExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.rawcc.RawCcExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.source.BehindLiveWindowException; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.Chunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkExtractorWrapper; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ContainerMediaChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.InitializationChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.MediaChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.SingleSampleMediaChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.AdaptationSet; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.DashManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.RangedUri; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.Representation; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.LoaderErrorThrower; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.List; + +/** + * A default {@link DashChunkSource} implementation. + */ +public class DefaultDashChunkSource implements DashChunkSource { + + public static final class Factory implements DashChunkSource.Factory { + + private final DataSource.Factory dataSourceFactory; + private final int maxSegmentsPerLoad; + + public Factory(DataSource.Factory dataSourceFactory) { + this(dataSourceFactory, 1); + } + + public Factory(DataSource.Factory dataSourceFactory, int maxSegmentsPerLoad) { + this.dataSourceFactory = dataSourceFactory; + this.maxSegmentsPerLoad = maxSegmentsPerLoad; + } + + @Override + public DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + DashManifest manifest, int periodIndex, int adaptationSetIndex, + TrackSelection trackSelection, long elapsedRealtimeOffsetMs, + boolean enableEventMessageTrack, boolean enableCea608Track) { + DataSource dataSource = dataSourceFactory.createDataSource(); + return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex, + adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs, + maxSegmentsPerLoad, enableEventMessageTrack, enableCea608Track); + } + + } + + private final LoaderErrorThrower manifestLoaderErrorThrower; + private final int adaptationSetIndex; + private final TrackSelection trackSelection; + private final RepresentationHolder[] representationHolders; + private final DataSource dataSource; + private final long elapsedRealtimeOffsetMs; + private final int maxSegmentsPerLoad; + + private DashManifest manifest; + private int periodIndex; + + private IOException fatalError; + private boolean missingLastSegment; + + /** + * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. + * @param manifest The initial manifest. + * @param periodIndex The index of the period in the manifest. + * @param adaptationSetIndex The index of the adaptation set in the period. + * @param trackSelection The track selection. + * @param dataSource A {@link DataSource} suitable for loading the media data. + * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between + * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified + * as the server's unix time minus the local elapsed time. If unknown, set to 0. + * @param maxSegmentsPerLoad The maximum number of segments to combine into a single request. + * Note that segments will only be combined if their {@link Uri}s are the same and if their + * data ranges are adjacent. + * @param enableEventMessageTrack Whether the chunks generated by the source may output an event + * message track. + * @param enableCea608Track Whether the chunks generated by the source may output a CEA-608 track. + */ + public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection, + DataSource dataSource, long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad, + boolean enableEventMessageTrack, boolean enableCea608Track) { + this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; + this.manifest = manifest; + this.adaptationSetIndex = adaptationSetIndex; + this.trackSelection = trackSelection; + this.dataSource = dataSource; + this.periodIndex = periodIndex; + this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; + this.maxSegmentsPerLoad = maxSegmentsPerLoad; + + long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + AdaptationSet adaptationSet = getAdaptationSet(); + List representations = adaptationSet.representations; + representationHolders = new RepresentationHolder[trackSelection.length()]; + for (int i = 0; i < representationHolders.length; i++) { + Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i)); + representationHolders[i] = new RepresentationHolder(periodDurationUs, representation, + enableEventMessageTrack, enableCea608Track, adaptationSet.type); + } + } + + @Override + public void updateManifest(DashManifest newManifest, int newPeriodIndex) { + try { + manifest = newManifest; + periodIndex = newPeriodIndex; + long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + List representations = getAdaptationSet().representations; + for (int i = 0; i < representationHolders.length; i++) { + Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i)); + representationHolders[i].updateRepresentation(periodDurationUs, representation); + } + } catch (BehindLiveWindowException e) { + fatalError = e; + } + } + + @Override + public void maybeThrowError() throws IOException { + if (fatalError != null) { + throw fatalError; + } else { + manifestLoaderErrorThrower.maybeThrowError(); + } + } + + @Override + public int getPreferredQueueSize(long playbackPositionUs, List queue) { + if (fatalError != null || trackSelection.length() < 2) { + return queue.size(); + } + return trackSelection.evaluateQueueSize(playbackPositionUs, queue); + } + + @Override + public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) { + if (fatalError != null) { + return; + } + + long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; + trackSelection.updateSelectedTrack(bufferedDurationUs); + + RepresentationHolder representationHolder = + representationHolders[trackSelection.getSelectedIndex()]; + + if (representationHolder.extractorWrapper != null) { + Representation selectedRepresentation = representationHolder.representation; + RangedUri pendingInitializationUri = null; + RangedUri pendingIndexUri = null; + if (representationHolder.extractorWrapper.getSampleFormats() == null) { + pendingInitializationUri = selectedRepresentation.getInitializationUri(); + } + if (representationHolder.segmentIndex == null) { + pendingIndexUri = selectedRepresentation.getIndexUri(); + } + if (pendingInitializationUri != null || pendingIndexUri != null) { + // We have initialization and/or index requests to make. + out.chunk = newInitializationChunk(representationHolder, dataSource, + trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), + trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri); + return; + } + } + + long nowUs = getNowUnixTimeUs(); + int availableSegmentCount = representationHolder.getSegmentCount(); + if (availableSegmentCount == 0) { + // The index doesn't define any segments. + out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1); + return; + } + + int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum(); + int lastAvailableSegmentNum; + if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) { + // The index is itself unbounded. We need to use the current time to calculate the range of + // available segments. + long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000; + long periodStartUs = manifest.getPeriod(periodIndex).startMs * 1000; + long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs; + if (manifest.timeShiftBufferDepth != C.TIME_UNSET) { + long bufferDepthUs = manifest.timeShiftBufferDepth * 1000; + firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum, + representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs)); + } + // getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the + // index of the last completed segment. + lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1; + } else { + lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1; + } + + int segmentNum; + if (previous == null) { + segmentNum = Util.constrainValue(representationHolder.getSegmentNum(playbackPositionUs), + firstAvailableSegmentNum, lastAvailableSegmentNum); + } else { + segmentNum = previous.getNextChunkIndex(); + if (segmentNum < firstAvailableSegmentNum) { + // This is before the first chunk in the current manifest. + fatalError = new BehindLiveWindowException(); + return; + } + } + + if (segmentNum > lastAvailableSegmentNum + || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) { + // This is beyond the last chunk in the current manifest. + out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1); + return; + } + + int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1); + out.chunk = newMediaChunk(representationHolder, dataSource, trackSelection.getSelectedFormat(), + trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segmentNum, + maxSegmentCount); + } + + @Override + public void onChunkLoadCompleted(Chunk chunk) { + if (chunk instanceof InitializationChunk) { + InitializationChunk initializationChunk = (InitializationChunk) chunk; + RepresentationHolder representationHolder = + representationHolders[trackSelection.indexOf(initializationChunk.trackFormat)]; + // The null check avoids overwriting an index obtained from the manifest with one obtained + // from the stream. If the manifest defines an index then the stream shouldn't, but in cases + // where it does we should ignore it. + if (representationHolder.segmentIndex == null) { + SeekMap seekMap = representationHolder.extractorWrapper.getSeekMap(); + if (seekMap != null) { + representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap); + } + } + } + } + + @Override + public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) { + if (!cancelable) { + return false; + } + // Workaround for missing segment at the end of the period + if (!manifest.dynamic && chunk instanceof MediaChunk + && e instanceof InvalidResponseCodeException + && ((InvalidResponseCodeException) e).responseCode == 404) { + RepresentationHolder representationHolder = + representationHolders[trackSelection.indexOf(chunk.trackFormat)]; + int segmentCount = representationHolder.getSegmentCount(); + if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED && segmentCount != 0) { + int lastAvailableSegmentNum = representationHolder.getFirstSegmentNum() + segmentCount - 1; + if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) { + missingLastSegment = true; + return true; + } + } + } + // Blacklist if appropriate. + return ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, + trackSelection.indexOf(chunk.trackFormat), e); + } + + // Private methods. + + private AdaptationSet getAdaptationSet() { + return manifest.getPeriod(periodIndex).adaptationSets.get(adaptationSetIndex); + } + + private long getNowUnixTimeUs() { + if (elapsedRealtimeOffsetMs != 0) { + return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000; + } else { + return System.currentTimeMillis() * 1000; + } + } + + private static Chunk newInitializationChunk(RepresentationHolder representationHolder, + DataSource dataSource, Format trackFormat, int trackSelectionReason, + Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) { + RangedUri requestUri; + String baseUrl = representationHolder.representation.baseUrl; + if (initializationUri != null) { + // It's common for initialization and index data to be stored adjacently. Attempt to merge + // the two requests together to request both at once. + requestUri = initializationUri.attemptMerge(indexUri, baseUrl); + if (requestUri == null) { + requestUri = initializationUri; + } + } else { + requestUri = indexUri; + } + DataSpec dataSpec = new DataSpec(requestUri.resolveUri(baseUrl), requestUri.start, + requestUri.length, representationHolder.representation.getCacheKey()); + return new InitializationChunk(dataSource, dataSpec, trackFormat, + trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper); + } + + private static Chunk newMediaChunk(RepresentationHolder representationHolder, + DataSource dataSource, Format trackFormat, int trackSelectionReason, + Object trackSelectionData, int firstSegmentNum, int maxSegmentCount) { + Representation representation = representationHolder.representation; + long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum); + RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum); + String baseUrl = representation.baseUrl; + if (representationHolder.extractorWrapper == null) { + long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum); + DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), + segmentUri.start, segmentUri.length, representation.getCacheKey()); + return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, + trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, + representationHolder.trackType, trackFormat); + } else { + int segmentCount = 1; + for (int i = 1; i < maxSegmentCount; i++) { + RangedUri nextSegmentUri = representationHolder.getSegmentUrl(firstSegmentNum + i); + RangedUri mergedSegmentUri = segmentUri.attemptMerge(nextSegmentUri, baseUrl); + if (mergedSegmentUri == null) { + // Unable to merge segment fetches because the URIs do not merge. + break; + } + segmentUri = mergedSegmentUri; + segmentCount++; + } + long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1); + DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), + segmentUri.start, segmentUri.length, representation.getCacheKey()); + long sampleOffsetUs = -representation.presentationTimeOffsetUs; + return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, + trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, segmentCount, + sampleOffsetUs, representationHolder.extractorWrapper); + } + } + + // Protected classes. + + protected static final class RepresentationHolder { + + public final int trackType; + public final ChunkExtractorWrapper extractorWrapper; + + public Representation representation; + public DashSegmentIndex segmentIndex; + + private long periodDurationUs; + private int segmentNumShift; + + public RepresentationHolder(long periodDurationUs, Representation representation, + boolean enableEventMessageTrack, boolean enableCea608Track, int trackType) { + this.periodDurationUs = periodDurationUs; + this.representation = representation; + this.trackType = trackType; + String containerMimeType = representation.format.containerMimeType; + if (mimeTypeIsRawText(containerMimeType)) { + extractorWrapper = null; + } else { + Extractor extractor; + if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { + extractor = new RawCcExtractor(representation.format); + } else if (mimeTypeIsWebm(containerMimeType)) { + extractor = new MatroskaExtractor(MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES); + } else { + int flags = 0; + if (enableEventMessageTrack) { + flags |= FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK; + } + if (enableCea608Track) { + flags |= FragmentedMp4Extractor.FLAG_ENABLE_CEA608_TRACK; + } + extractor = new FragmentedMp4Extractor(flags); + } + // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream, + // as per DASH IF Interoperability Recommendations V3.0, 7.5.3. + extractorWrapper = new ChunkExtractorWrapper(extractor, representation.format); + } + segmentIndex = representation.getIndex(); + } + + public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation) + throws BehindLiveWindowException{ + DashSegmentIndex oldIndex = representation.getIndex(); + DashSegmentIndex newIndex = newRepresentation.getIndex(); + + periodDurationUs = newPeriodDurationUs; + representation = newRepresentation; + if (oldIndex == null) { + // Segment numbers cannot shift if the index isn't defined by the manifest. + return; + } + + segmentIndex = newIndex; + if (!oldIndex.isExplicit()) { + // Segment numbers cannot shift if the index isn't explicit. + return; + } + + int oldIndexSegmentCount = oldIndex.getSegmentCount(periodDurationUs); + if (oldIndexSegmentCount == 0) { + // Segment numbers cannot shift if the old index was empty. + return; + } + + int oldIndexLastSegmentNum = oldIndex.getFirstSegmentNum() + oldIndexSegmentCount - 1; + long oldIndexEndTimeUs = oldIndex.getTimeUs(oldIndexLastSegmentNum) + + oldIndex.getDurationUs(oldIndexLastSegmentNum, periodDurationUs); + int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum(); + long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum); + if (oldIndexEndTimeUs == newIndexStartTimeUs) { + // The new index continues where the old one ended, with no overlap. + segmentNumShift += oldIndexLastSegmentNum + 1 - newIndexFirstSegmentNum; + } else if (oldIndexEndTimeUs < newIndexStartTimeUs) { + // There's a gap between the old index and the new one which means we've slipped behind the + // live window and can't proceed. + throw new BehindLiveWindowException(); + } else { + // The new index overlaps with the old one. + segmentNumShift += oldIndex.getSegmentNum(newIndexStartTimeUs, periodDurationUs) + - newIndexFirstSegmentNum; + } + } + + public int getFirstSegmentNum() { + return segmentIndex.getFirstSegmentNum() + segmentNumShift; + } + + public int getSegmentCount() { + return segmentIndex.getSegmentCount(periodDurationUs); + } + + public long getSegmentStartTimeUs(int segmentNum) { + return segmentIndex.getTimeUs(segmentNum - segmentNumShift); + } + + public long getSegmentEndTimeUs(int segmentNum) { + return getSegmentStartTimeUs(segmentNum) + + segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs); + } + + public int getSegmentNum(long positionUs) { + return segmentIndex.getSegmentNum(positionUs, periodDurationUs) + segmentNumShift; + } + + public RangedUri getSegmentUrl(int segmentNum) { + return segmentIndex.getSegmentUrl(segmentNum - segmentNumShift); + } + + private static boolean mimeTypeIsWebm(String mimeType) { + return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM) + || mimeType.startsWith(MimeTypes.APPLICATION_WEBM); + } + + private static boolean mimeTypeIsRawText(String mimeType) { + return MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/AdaptationSet.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/AdaptationSet.java new file mode 100644 index 0000000..abd787d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/AdaptationSet.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import java.util.Collections; +import java.util.List; + +/** + * Represents a set of interchangeable encoded versions of a media content component. + */ +public class AdaptationSet { + + /** + * Value of {@link #id} indicating no value is set.= + */ + public static final int ID_UNSET = -1; + + /** + * A non-negative identifier for the adaptation set that's unique in the scope of its containing + * period, or {@link #ID_UNSET} if not specified. + */ + public final int id; + + /** + * The type of the adaptation set. One of the {@link com.tangxiaolv.telegramgallery.exoplayer2.C} + * {@code TRACK_TYPE_*} constants. + */ + public final int type; + + /** + * The {@link Representation}s in the adaptation set. + */ + public final List representations; + + /** + * The accessibility descriptors in the adaptation set. + */ + public final List accessibilityDescriptors; + + /** + * @param id A non-negative identifier for the adaptation set that's unique in the scope of its + * containing period, or {@link #ID_UNSET} if not specified. + * @param type The type of the adaptation set. One of the {@link com.tangxiaolv.telegramgallery.exoplayer2.C} + * {@code TRACK_TYPE_*} constants. + * @param representations The {@link Representation}s in the adaptation set. + * @param accessibilityDescriptors The accessibility descriptors in the adaptation set. + */ + public AdaptationSet(int id, int type, List representations, + List accessibilityDescriptors) { + this.id = id; + this.type = type; + this.representations = Collections.unmodifiableList(representations); + this.accessibilityDescriptors = accessibilityDescriptors == null + ? Collections.emptyList() + : Collections.unmodifiableList(accessibilityDescriptors); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/DashManifest.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/DashManifest.java new file mode 100644 index 0000000..402a06b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/DashManifest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Represents a DASH media presentation description (mpd). + */ +public class DashManifest { + + public final long availabilityStartTime; + + public final long duration; + + public final long minBufferTime; + + public final boolean dynamic; + + public final long minUpdatePeriod; + + public final long timeShiftBufferDepth; + + public final long suggestedPresentationDelay; + + public final UtcTimingElement utcTiming; + + public final Uri location; + + private final List periods; + + public DashManifest(long availabilityStartTime, long duration, long minBufferTime, + boolean dynamic, long minUpdatePeriod, long timeShiftBufferDepth, + long suggestedPresentationDelay, UtcTimingElement utcTiming, Uri location, + List periods) { + this.availabilityStartTime = availabilityStartTime; + this.duration = duration; + this.minBufferTime = minBufferTime; + this.dynamic = dynamic; + this.minUpdatePeriod = minUpdatePeriod; + this.timeShiftBufferDepth = timeShiftBufferDepth; + this.suggestedPresentationDelay = suggestedPresentationDelay; + this.utcTiming = utcTiming; + this.location = location; + this.periods = periods == null ? Collections.emptyList() : periods; + } + + public final int getPeriodCount() { + return periods.size(); + } + + public final Period getPeriod(int index) { + return periods.get(index); + } + + public final long getPeriodDurationMs(int index) { + return index == periods.size() - 1 + ? (duration == C.TIME_UNSET ? C.TIME_UNSET : (duration - periods.get(index).startMs)) + : (periods.get(index + 1).startMs - periods.get(index).startMs); + } + + public final long getPeriodDurationUs(int index) { + return C.msToUs(getPeriodDurationMs(index)); + } + + /** + * Creates a copy of this manifest which includes only the representations identified by the given + * keys. + * + * @param representationKeys List of keys for the representations to be included in the copy. + * @return A copy of this manifest with the selected representations. + * @throws IndexOutOfBoundsException If a key has an invalid index. + */ + public final DashManifest copy(List representationKeys) { + LinkedList keys = new LinkedList<>(representationKeys); + Collections.sort(keys); + keys.add(new RepresentationKey(-1, -1, -1)); // Add a stopper key to the end + + ArrayList copyPeriods = new ArrayList<>(); + long shiftMs = 0; + for (int periodIndex = 0; periodIndex < getPeriodCount(); periodIndex++) { + if (keys.peek().periodIndex != periodIndex) { + // No representations selected in this period. + long periodDurationMs = getPeriodDurationMs(periodIndex); + if (periodDurationMs != C.TIME_UNSET) { + shiftMs += periodDurationMs; + } + } else { + Period period = getPeriod(periodIndex); + ArrayList copyAdaptationSets = + copyAdaptationSets(period.adaptationSets, keys); + copyPeriods.add(new Period(period.id, period.startMs - shiftMs, copyAdaptationSets)); + } + } + long newDuration = duration != C.TIME_UNSET ? duration - shiftMs : C.TIME_UNSET; + return new DashManifest(availabilityStartTime, newDuration, minBufferTime, dynamic, + minUpdatePeriod, timeShiftBufferDepth, suggestedPresentationDelay, utcTiming, location, + copyPeriods); + } + + private static ArrayList copyAdaptationSets( + List adaptationSets, LinkedList keys) { + RepresentationKey key = keys.poll(); + int periodIndex = key.periodIndex; + ArrayList copyAdaptationSets = new ArrayList<>(); + do { + int adaptationSetIndex = key.adaptationSetIndex; + AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); + + List representations = adaptationSet.representations; + ArrayList copyRepresentations = new ArrayList<>(); + do { + Representation representation = representations.get(key.representationIndex); + copyRepresentations.add(representation); + key = keys.poll(); + } while(key.periodIndex == periodIndex && key.adaptationSetIndex == adaptationSetIndex); + + copyAdaptationSets.add(new AdaptationSet(adaptationSet.id, adaptationSet.type, + copyRepresentations, adaptationSet.accessibilityDescriptors)); + } while(key.periodIndex == periodIndex); + // Add back the last key which doesn't belong to the period being processed + keys.addFirst(key); + return copyAdaptationSets; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/DashManifestParser.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/DashManifestParser.java new file mode 100644 index 0000000..2bbaa52 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -0,0 +1,941 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData.SchemeData; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.PsshAtomUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.SegmentBase.SegmentList; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.SegmentBase.SegmentTemplate; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.SegmentBase.SegmentTimelineElement; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.ParsingLoadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.UriUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import com.tangxiaolv.telegramgallery.exoplayer2.util.XmlPullParserUtil; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.xml.sax.helpers.DefaultHandler; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +/** + * A parser of media presentation description files. + */ +public class DashManifestParser extends DefaultHandler + implements ParsingLoadable.Parser { + + private static final String TAG = "MpdParser"; + + private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("(\\d+)(?:/(\\d+))?"); + + private static final Pattern CEA_608_ACCESSIBILITY_PATTERN = Pattern.compile("CC([1-4])=.*"); + private static final Pattern CEA_708_ACCESSIBILITY_PATTERN = + Pattern.compile("([1-9]|[1-5][0-9]|6[0-3])=.*"); + + private final String contentId; + private final XmlPullParserFactory xmlParserFactory; + + /** + * Equivalent to calling {@code new DashManifestParser(null)}. + */ + public DashManifestParser() { + this(null); + } + + /** + * @param contentId An optional content identifier to include in the parsed manifest. + */ + public DashManifestParser(String contentId) { + this.contentId = contentId; + try { + xmlParserFactory = XmlPullParserFactory.newInstance(); + } catch (XmlPullParserException e) { + throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); + } + } + + // MPD parsing. + + @Override + public DashManifest parse(Uri uri, InputStream inputStream) throws IOException { + try { + XmlPullParser xpp = xmlParserFactory.newPullParser(); + xpp.setInput(inputStream, null); + int eventType = xpp.next(); + if (eventType != XmlPullParser.START_TAG || !"MPD".equals(xpp.getName())) { + throw new ParserException( + "inputStream does not contain a valid media presentation description"); + } + return parseMediaPresentationDescription(xpp, uri.toString()); + } catch (XmlPullParserException e) { + throw new ParserException(e); + } + } + + protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, + String baseUrl) throws XmlPullParserException, IOException { + long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", C.TIME_UNSET); + long durationMs = parseDuration(xpp, "mediaPresentationDuration", C.TIME_UNSET); + long minBufferTimeMs = parseDuration(xpp, "minBufferTime", C.TIME_UNSET); + String typeString = xpp.getAttributeValue(null, "type"); + boolean dynamic = typeString != null && typeString.equals("dynamic"); + long minUpdateTimeMs = dynamic ? parseDuration(xpp, "minimumUpdatePeriod", C.TIME_UNSET) + : C.TIME_UNSET; + long timeShiftBufferDepthMs = dynamic + ? parseDuration(xpp, "timeShiftBufferDepth", C.TIME_UNSET) : C.TIME_UNSET; + long suggestedPresentationDelayMs = dynamic + ? parseDuration(xpp, "suggestedPresentationDelay", C.TIME_UNSET) : C.TIME_UNSET; + UtcTimingElement utcTiming = null; + Uri location = null; + + List periods = new ArrayList<>(); + long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0; + boolean seenEarlyAccessPeriod = false; + boolean seenFirstBaseUrl = false; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { + if (!seenFirstBaseUrl) { + baseUrl = parseBaseUrl(xpp, baseUrl); + seenFirstBaseUrl = true; + } + } else if (XmlPullParserUtil.isStartTag(xpp, "UTCTiming")) { + utcTiming = parseUtcTiming(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "Location")) { + location = Uri.parse(xpp.nextText()); + } else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { + Pair periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs); + Period period = periodWithDurationMs.first; + if (period.startMs == C.TIME_UNSET) { + if (dynamic) { + // This is an early access period. Ignore it. All subsequent periods must also be + // early access. + seenEarlyAccessPeriod = true; + } else { + throw new ParserException("Unable to determine start of period " + periods.size()); + } + } else { + long periodDurationMs = periodWithDurationMs.second; + nextPeriodStartMs = periodDurationMs == C.TIME_UNSET ? C.TIME_UNSET + : (period.startMs + periodDurationMs); + periods.add(period); + } + } + } while (!XmlPullParserUtil.isEndTag(xpp, "MPD")); + + if (durationMs == C.TIME_UNSET) { + if (nextPeriodStartMs != C.TIME_UNSET) { + // If we know the end time of the final period, we can use it as the duration. + durationMs = nextPeriodStartMs; + } else if (!dynamic) { + throw new ParserException("Unable to determine duration of static manifest."); + } + } + + if (periods.isEmpty()) { + throw new ParserException("No periods found."); + } + + return buildMediaPresentationDescription(availabilityStartTime, durationMs, minBufferTimeMs, + dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, utcTiming, + location, periods); + } + + protected DashManifest buildMediaPresentationDescription(long availabilityStartTime, + long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdateTimeMs, + long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, UtcTimingElement utcTiming, + Uri location, List periods) { + return new DashManifest(availabilityStartTime, durationMs, minBufferTimeMs, + dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, utcTiming, + location, periods); + } + + protected UtcTimingElement parseUtcTiming(XmlPullParser xpp) { + String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); + String value = xpp.getAttributeValue(null, "value"); + return buildUtcTimingElement(schemeIdUri, value); + } + + protected UtcTimingElement buildUtcTimingElement(String schemeIdUri, String value) { + return new UtcTimingElement(schemeIdUri, value); + } + + protected Pair parsePeriod(XmlPullParser xpp, String baseUrl, long defaultStartMs) + throws XmlPullParserException, IOException { + String id = xpp.getAttributeValue(null, "id"); + long startMs = parseDuration(xpp, "start", defaultStartMs); + long durationMs = parseDuration(xpp, "duration", C.TIME_UNSET); + SegmentBase segmentBase = null; + List adaptationSets = new ArrayList<>(); + boolean seenFirstBaseUrl = false; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { + if (!seenFirstBaseUrl) { + baseUrl = parseBaseUrl(xpp, baseUrl); + seenFirstBaseUrl = true; + } + } else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) { + adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase)); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { + segmentBase = parseSegmentBase(xpp, null); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { + segmentBase = parseSegmentList(xpp, null); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { + segmentBase = parseSegmentTemplate(xpp, null); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "Period")); + + return Pair.create(buildPeriod(id, startMs, adaptationSets), durationMs); + } + + protected Period buildPeriod(String id, long startMs, List adaptationSets) { + return new Period(id, startMs, adaptationSets); + } + + // AdaptationSet parsing. + + protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, String baseUrl, + SegmentBase segmentBase) throws XmlPullParserException, IOException { + int id = parseInt(xpp, "id", AdaptationSet.ID_UNSET); + int contentType = parseContentType(xpp); + + String mimeType = xpp.getAttributeValue(null, "mimeType"); + String codecs = xpp.getAttributeValue(null, "codecs"); + int width = parseInt(xpp, "width", Format.NO_VALUE); + int height = parseInt(xpp, "height", Format.NO_VALUE); + float frameRate = parseFrameRate(xpp, Format.NO_VALUE); + int audioChannels = Format.NO_VALUE; + int audioSamplingRate = parseInt(xpp, "audioSamplingRate", Format.NO_VALUE); + String language = xpp.getAttributeValue(null, "lang"); + ArrayList drmSchemeDatas = new ArrayList<>(); + ArrayList inbandEventStreams = new ArrayList<>(); + ArrayList accessibilityDescriptors = new ArrayList<>(); + List representationInfos = new ArrayList<>(); + @C.SelectionFlags int selectionFlags = 0; + + boolean seenFirstBaseUrl = false; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { + if (!seenFirstBaseUrl) { + baseUrl = parseBaseUrl(xpp, baseUrl); + seenFirstBaseUrl = true; + } + } else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) { + SchemeData contentProtection = parseContentProtection(xpp); + if (contentProtection != null) { + drmSchemeDatas.add(contentProtection); + } + } else if (XmlPullParserUtil.isStartTag(xpp, "ContentComponent")) { + language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang")); + contentType = checkContentTypeConsistency(contentType, parseContentType(xpp)); + } else if (XmlPullParserUtil.isStartTag(xpp, "Role")) { + selectionFlags |= parseRole(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { + audioChannels = parseAudioChannelConfiguration(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "Accessibility")) { + accessibilityDescriptors.add(parseAccessibility(xpp)); + } else if (XmlPullParserUtil.isStartTag(xpp, "Representation")) { + RepresentationInfo representationInfo = parseRepresentation(xpp, baseUrl, mimeType, codecs, + width, height, frameRate, audioChannels, audioSamplingRate, language, + selectionFlags, accessibilityDescriptors, segmentBase); + contentType = checkContentTypeConsistency(contentType, + getContentType(representationInfo.format)); + representationInfos.add(representationInfo); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { + segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { + segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { + segmentBase = parseSegmentTemplate(xpp, (SegmentTemplate) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) { + inbandEventStreams.add(parseInbandEventStream(xpp)); + } else if (XmlPullParserUtil.isStartTag(xpp)) { + parseAdaptationSetChild(xpp); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "AdaptationSet")); + + // Build the representations. + List representations = new ArrayList<>(representationInfos.size()); + for (int i = 0; i < representationInfos.size(); i++) { + representations.add(buildRepresentation(representationInfos.get(i), contentId, + drmSchemeDatas, inbandEventStreams)); + } + + return buildAdaptationSet(id, contentType, representations, accessibilityDescriptors); + } + + protected AdaptationSet buildAdaptationSet(int id, int contentType, + List representations, List accessibilityDescriptors) { + return new AdaptationSet(id, contentType, representations, accessibilityDescriptors); + } + + protected int parseContentType(XmlPullParser xpp) { + String contentType = xpp.getAttributeValue(null, "contentType"); + return TextUtils.isEmpty(contentType) ? C.TRACK_TYPE_UNKNOWN + : MimeTypes.BASE_TYPE_AUDIO.equals(contentType) ? C.TRACK_TYPE_AUDIO + : MimeTypes.BASE_TYPE_VIDEO.equals(contentType) ? C.TRACK_TYPE_VIDEO + : MimeTypes.BASE_TYPE_TEXT.equals(contentType) ? C.TRACK_TYPE_TEXT + : C.TRACK_TYPE_UNKNOWN; + } + + protected int getContentType(Format format) { + String sampleMimeType = format.sampleMimeType; + if (TextUtils.isEmpty(sampleMimeType)) { + return C.TRACK_TYPE_UNKNOWN; + } else if (MimeTypes.isVideo(sampleMimeType)) { + return C.TRACK_TYPE_VIDEO; + } else if (MimeTypes.isAudio(sampleMimeType)) { + return C.TRACK_TYPE_AUDIO; + } else if (mimeTypeIsRawText(sampleMimeType)) { + return C.TRACK_TYPE_TEXT; + } + return C.TRACK_TYPE_UNKNOWN; + } + + /** + * Parses a ContentProtection element. + * + * @param xpp The parser from which to read. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + * @return {@link SchemeData} parsed from the ContentProtection element, or null if the element is + * unsupported. + */ + protected SchemeData parseContentProtection(XmlPullParser xpp) throws XmlPullParserException, + IOException { + String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); + boolean isPlayReady = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95".equals(schemeIdUri); + byte[] data = null; + UUID uuid = null; + boolean requiresSecureDecoder = false; + do { + xpp.next(); + if (data == null && XmlPullParserUtil.isStartTag(xpp, "cenc:pssh") + && xpp.next() == XmlPullParser.TEXT) { + // The cenc:pssh element is defined in 23001-7:2015. + data = Base64.decode(xpp.getText(), Base64.DEFAULT); + uuid = PsshAtomUtil.parseUuid(data); + if (uuid == null) { + Log.w(TAG, "Skipping malformed cenc:pssh data"); + data = null; + } + } else if (data == null && isPlayReady && XmlPullParserUtil.isStartTag(xpp, "mspr:pro") + && xpp.next() == XmlPullParser.TEXT) { + // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady. + data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, + Base64.decode(xpp.getText(), Base64.DEFAULT)); + uuid = C.PLAYREADY_UUID; + } else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) { + String robustnessLevel = xpp.getAttributeValue(null, "robustness_level"); + requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); + return data != null ? new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) + : null; + } + + /** + * Parses an InbandEventStream element. + * + * @param xpp The parser from which to read. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + * @return A {@link SchemeValuePair} parsed from the element. + */ + protected SchemeValuePair parseInbandEventStream(XmlPullParser xpp) + throws XmlPullParserException, IOException { + return parseSchemeValuePair(xpp, "InbandEventStream"); + } + + /** + * Parses an Accessibility element. + * + * @param xpp The parser from which to read. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + * @return A {@link SchemeValuePair} parsed from the element. + */ + protected SchemeValuePair parseAccessibility(XmlPullParser xpp) + throws XmlPullParserException, IOException { + return parseSchemeValuePair(xpp, "Accessibility"); + } + + /** + * Parses a Role element. + * + * @param xpp The parser from which to read. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + * @return {@link C.SelectionFlags} parsed from the element. + */ + protected int parseRole(XmlPullParser xpp) throws XmlPullParserException, IOException { + String schemeIdUri = parseString(xpp, "schemeIdUri", null); + String value = parseString(xpp, "value", null); + do { + xpp.next(); + } while (!XmlPullParserUtil.isEndTag(xpp, "Role")); + return "urn:mpeg:dash:role:2011".equals(schemeIdUri) && "main".equals(value) + ? C.SELECTION_FLAG_DEFAULT : 0; + } + + /** + * Parses children of AdaptationSet elements not specifically parsed elsewhere. + * + * @param xpp The XmpPullParser from which the AdaptationSet child should be parsed. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + */ + protected void parseAdaptationSetChild(XmlPullParser xpp) + throws XmlPullParserException, IOException { + // pass + } + + // Representation parsing. + + protected RepresentationInfo parseRepresentation(XmlPullParser xpp, String baseUrl, + String adaptationSetMimeType, String adaptationSetCodecs, int adaptationSetWidth, + int adaptationSetHeight, float adaptationSetFrameRate, int adaptationSetAudioChannels, + int adaptationSetAudioSamplingRate, String adaptationSetLanguage, + @C.SelectionFlags int adaptationSetSelectionFlags, + List adaptationSetAccessibilityDescriptors, SegmentBase segmentBase) + throws XmlPullParserException, IOException { + String id = xpp.getAttributeValue(null, "id"); + int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE); + + String mimeType = parseString(xpp, "mimeType", adaptationSetMimeType); + String codecs = parseString(xpp, "codecs", adaptationSetCodecs); + int width = parseInt(xpp, "width", adaptationSetWidth); + int height = parseInt(xpp, "height", adaptationSetHeight); + float frameRate = parseFrameRate(xpp, adaptationSetFrameRate); + int audioChannels = adaptationSetAudioChannels; + int audioSamplingRate = parseInt(xpp, "audioSamplingRate", adaptationSetAudioSamplingRate); + ArrayList drmSchemeDatas = new ArrayList<>(); + ArrayList inbandEventStreams = new ArrayList<>(); + + boolean seenFirstBaseUrl = false; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { + if (!seenFirstBaseUrl) { + baseUrl = parseBaseUrl(xpp, baseUrl); + seenFirstBaseUrl = true; + } + } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { + audioChannels = parseAudioChannelConfiguration(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { + segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { + segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { + segmentBase = parseSegmentTemplate(xpp, (SegmentTemplate) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) { + SchemeData contentProtection = parseContentProtection(xpp); + if (contentProtection != null) { + drmSchemeDatas.add(contentProtection); + } + } else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) { + inbandEventStreams.add(parseInbandEventStream(xpp)); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "Representation")); + + Format format = buildFormat(id, mimeType, width, height, frameRate, audioChannels, + audioSamplingRate, bandwidth, adaptationSetLanguage, adaptationSetSelectionFlags, + adaptationSetAccessibilityDescriptors, codecs); + segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase(); + + return new RepresentationInfo(format, baseUrl, segmentBase, drmSchemeDatas, inbandEventStreams); + } + + protected Format buildFormat(String id, String containerMimeType, int width, int height, + float frameRate, int audioChannels, int audioSamplingRate, int bitrate, String language, + @C.SelectionFlags int selectionFlags, List accessibilityDescriptors, + String codecs) { + String sampleMimeType = getSampleMimeType(containerMimeType, codecs); + if (sampleMimeType != null) { + if (MimeTypes.isVideo(sampleMimeType)) { + return Format.createVideoContainerFormat(id, containerMimeType, sampleMimeType, codecs, + bitrate, width, height, frameRate, null, selectionFlags); + } else if (MimeTypes.isAudio(sampleMimeType)) { + return Format.createAudioContainerFormat(id, containerMimeType, sampleMimeType, codecs, + bitrate, audioChannels, audioSamplingRate, null, selectionFlags, language); + } else if (mimeTypeIsRawText(sampleMimeType)) { + int accessibilityChannel; + if (MimeTypes.APPLICATION_CEA608.equals(sampleMimeType)) { + accessibilityChannel = parseCea608AccessibilityChannel(accessibilityDescriptors); + } else if (MimeTypes.APPLICATION_CEA708.equals(sampleMimeType)) { + accessibilityChannel = parseCea708AccessibilityChannel(accessibilityDescriptors); + } else { + accessibilityChannel = Format.NO_VALUE; + } + return Format.createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, + bitrate, selectionFlags, language, accessibilityChannel); + } + } + return Format.createContainerFormat(id, containerMimeType, sampleMimeType, codecs, bitrate, + selectionFlags, language); + } + + protected Representation buildRepresentation(RepresentationInfo representationInfo, + String contentId, ArrayList extraDrmSchemeDatas, + ArrayList extraInbandEventStreams) { + Format format = representationInfo.format; + ArrayList drmSchemeDatas = representationInfo.drmSchemeDatas; + drmSchemeDatas.addAll(extraDrmSchemeDatas); + if (!drmSchemeDatas.isEmpty()) { + format = format.copyWithDrmInitData(new DrmInitData(drmSchemeDatas)); + } + ArrayList inbandEventStremas = representationInfo.inbandEventStreams; + inbandEventStremas.addAll(extraInbandEventStreams); + return Representation.newInstance(contentId, Representation.REVISION_ID_DEFAULT, format, + representationInfo.baseUrl, representationInfo.segmentBase, inbandEventStremas); + } + + // SegmentBase, SegmentList and SegmentTemplate parsing. + + protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, SingleSegmentBase parent) + throws XmlPullParserException, IOException { + + long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); + long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", + parent != null ? parent.presentationTimeOffset : 0); + + long indexStart = parent != null ? parent.indexStart : 0; + long indexLength = parent != null ? parent.indexLength : 0; + String indexRangeText = xpp.getAttributeValue(null, "indexRange"); + if (indexRangeText != null) { + String[] indexRange = indexRangeText.split("-"); + indexStart = Long.parseLong(indexRange[0]); + indexLength = Long.parseLong(indexRange[1]) - indexStart + 1; + } + + RangedUri initialization = parent != null ? parent.initialization : null; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { + initialization = parseInitialization(xpp); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase")); + + return buildSingleSegmentBase(initialization, timescale, presentationTimeOffset, indexStart, + indexLength); + } + + protected SingleSegmentBase buildSingleSegmentBase(RangedUri initialization, long timescale, + long presentationTimeOffset, long indexStart, long indexLength) { + return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, indexStart, + indexLength); + } + + protected SegmentList parseSegmentList(XmlPullParser xpp, SegmentList parent) + throws XmlPullParserException, IOException { + + long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); + long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", + parent != null ? parent.presentationTimeOffset : 0); + long duration = parseLong(xpp, "duration", parent != null ? parent.duration : C.TIME_UNSET); + int startNumber = parseInt(xpp, "startNumber", parent != null ? parent.startNumber : 1); + + RangedUri initialization = null; + List timeline = null; + List segments = null; + + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { + initialization = parseInitialization(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { + timeline = parseSegmentTimeline(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentURL")) { + if (segments == null) { + segments = new ArrayList<>(); + } + segments.add(parseSegmentUrl(xpp)); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList")); + + if (parent != null) { + initialization = initialization != null ? initialization : parent.initialization; + timeline = timeline != null ? timeline : parent.segmentTimeline; + segments = segments != null ? segments : parent.mediaSegments; + } + + return buildSegmentList(initialization, timescale, presentationTimeOffset, + startNumber, duration, timeline, segments); + } + + protected SegmentList buildSegmentList(RangedUri initialization, long timescale, + long presentationTimeOffset, int startNumber, long duration, + List timeline, List segments) { + return new SegmentList(initialization, timescale, presentationTimeOffset, + startNumber, duration, timeline, segments); + } + + protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, SegmentTemplate parent) + throws XmlPullParserException, IOException { + long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); + long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", + parent != null ? parent.presentationTimeOffset : 0); + long duration = parseLong(xpp, "duration", parent != null ? parent.duration : C.TIME_UNSET); + int startNumber = parseInt(xpp, "startNumber", parent != null ? parent.startNumber : 1); + UrlTemplate mediaTemplate = parseUrlTemplate(xpp, "media", + parent != null ? parent.mediaTemplate : null); + UrlTemplate initializationTemplate = parseUrlTemplate(xpp, "initialization", + parent != null ? parent.initializationTemplate : null); + + RangedUri initialization = null; + List timeline = null; + + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { + initialization = parseInitialization(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { + timeline = parseSegmentTimeline(xpp); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate")); + + if (parent != null) { + initialization = initialization != null ? initialization : parent.initialization; + timeline = timeline != null ? timeline : parent.segmentTimeline; + } + + return buildSegmentTemplate(initialization, timescale, presentationTimeOffset, + startNumber, duration, timeline, initializationTemplate, mediaTemplate); + } + + protected SegmentTemplate buildSegmentTemplate(RangedUri initialization, long timescale, + long presentationTimeOffset, int startNumber, long duration, + List timeline, UrlTemplate initializationTemplate, + UrlTemplate mediaTemplate) { + return new SegmentTemplate(initialization, timescale, presentationTimeOffset, + startNumber, duration, timeline, initializationTemplate, mediaTemplate); + } + + protected List parseSegmentTimeline(XmlPullParser xpp) + throws XmlPullParserException, IOException { + List segmentTimeline = new ArrayList<>(); + long elapsedTime = 0; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "S")) { + elapsedTime = parseLong(xpp, "t", elapsedTime); + long duration = parseLong(xpp, "d", C.TIME_UNSET); + int count = 1 + parseInt(xpp, "r", 0); + for (int i = 0; i < count; i++) { + segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration)); + elapsedTime += duration; + } + } + } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline")); + return segmentTimeline; + } + + protected SegmentTimelineElement buildSegmentTimelineElement(long elapsedTime, long duration) { + return new SegmentTimelineElement(elapsedTime, duration); + } + + protected UrlTemplate parseUrlTemplate(XmlPullParser xpp, String name, + UrlTemplate defaultValue) { + String valueString = xpp.getAttributeValue(null, name); + if (valueString != null) { + return UrlTemplate.compile(valueString); + } + return defaultValue; + } + + protected RangedUri parseInitialization(XmlPullParser xpp) { + return parseRangedUrl(xpp, "sourceURL", "range"); + } + + protected RangedUri parseSegmentUrl(XmlPullParser xpp) { + return parseRangedUrl(xpp, "media", "mediaRange"); + } + + protected RangedUri parseRangedUrl(XmlPullParser xpp, String urlAttribute, + String rangeAttribute) { + String urlText = xpp.getAttributeValue(null, urlAttribute); + long rangeStart = 0; + long rangeLength = C.LENGTH_UNSET; + String rangeText = xpp.getAttributeValue(null, rangeAttribute); + if (rangeText != null) { + String[] rangeTextArray = rangeText.split("-"); + rangeStart = Long.parseLong(rangeTextArray[0]); + if (rangeTextArray.length == 2) { + rangeLength = Long.parseLong(rangeTextArray[1]) - rangeStart + 1; + } + } + return buildRangedUri(urlText, rangeStart, rangeLength); + } + + protected RangedUri buildRangedUri(String urlText, long rangeStart, long rangeLength) { + return new RangedUri(urlText, rangeStart, rangeLength); + } + + // AudioChannelConfiguration parsing. + + protected int parseAudioChannelConfiguration(XmlPullParser xpp) + throws XmlPullParserException, IOException { + String schemeIdUri = parseString(xpp, "schemeIdUri", null); + int audioChannels = "urn:mpeg:dash:23003:3:audio_channel_configuration:2011".equals(schemeIdUri) + ? parseInt(xpp, "value", Format.NO_VALUE) : Format.NO_VALUE; + do { + xpp.next(); + } while (!XmlPullParserUtil.isEndTag(xpp, "AudioChannelConfiguration")); + return audioChannels; + } + + // Utility methods. + + /** + * Derives a sample mimeType from a container mimeType and codecs attribute. + * + * @param containerMimeType The mimeType of the container. + * @param codecs The codecs attribute. + * @return The derived sample mimeType, or null if it could not be derived. + */ + private static String getSampleMimeType(String containerMimeType, String codecs) { + if (MimeTypes.isAudio(containerMimeType)) { + return MimeTypes.getAudioMediaMimeType(codecs); + } else if (MimeTypes.isVideo(containerMimeType)) { + return MimeTypes.getVideoMediaMimeType(codecs); + } else if (mimeTypeIsRawText(containerMimeType)) { + return containerMimeType; + } else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) { + if ("stpp".equals(codecs)) { + return MimeTypes.APPLICATION_TTML; + } else if ("wvtt".equals(codecs)) { + return MimeTypes.APPLICATION_MP4VTT; + } + } else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { + if (codecs != null) { + if (codecs.contains("cea708")) { + return MimeTypes.APPLICATION_CEA708; + } else if (codecs.contains("eia608") || codecs.contains("cea608")) { + return MimeTypes.APPLICATION_CEA608; + } + } + return null; + } + return null; + } + + /** + * Returns whether a mimeType is a text sample mimeType. + * + * @param mimeType The mimeType. + * @return Whether the mimeType is a text sample mimeType. + */ + private static boolean mimeTypeIsRawText(String mimeType) { + return MimeTypes.isText(mimeType) + || MimeTypes.APPLICATION_TTML.equals(mimeType) + || MimeTypes.APPLICATION_MP4VTT.equals(mimeType) + || MimeTypes.APPLICATION_CEA708.equals(mimeType) + || MimeTypes.APPLICATION_CEA608.equals(mimeType); + } + + /** + * Checks two languages for consistency, returning the consistent language, or throwing an + * {@link IllegalStateException} if the languages are inconsistent. + *

    + * Two languages are consistent if they are equal, or if one is null. + * + * @param firstLanguage The first language. + * @param secondLanguage The second language. + * @return The consistent language. + */ + private static String checkLanguageConsistency(String firstLanguage, String secondLanguage) { + if (firstLanguage == null) { + return secondLanguage; + } else if (secondLanguage == null) { + return firstLanguage; + } else { + Assertions.checkState(firstLanguage.equals(secondLanguage)); + return firstLanguage; + } + } + + /** + * Checks two adaptation set content types for consistency, returning the consistent type, or + * throwing an {@link IllegalStateException} if the types are inconsistent. + *

    + * Two types are consistent if they are equal, or if one is {@link C#TRACK_TYPE_UNKNOWN}. + * Where one of the types is {@link C#TRACK_TYPE_UNKNOWN}, the other is returned. + * + * @param firstType The first type. + * @param secondType The second type. + * @return The consistent type. + */ + private static int checkContentTypeConsistency(int firstType, int secondType) { + if (firstType == C.TRACK_TYPE_UNKNOWN) { + return secondType; + } else if (secondType == C.TRACK_TYPE_UNKNOWN) { + return firstType; + } else { + Assertions.checkState(firstType == secondType); + return firstType; + } + } + + /** + * Parses a {@link SchemeValuePair} from an element. + * + * @param xpp The parser from which to read. + * @param tag The tag of the element being parsed. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + * @return The parsed {@link SchemeValuePair}. + */ + protected static SchemeValuePair parseSchemeValuePair(XmlPullParser xpp, String tag) + throws XmlPullParserException, IOException { + String schemeIdUri = parseString(xpp, "schemeIdUri", null); + String value = parseString(xpp, "value", null); + do { + xpp.next(); + } while (!XmlPullParserUtil.isEndTag(xpp, tag)); + return new SchemeValuePair(schemeIdUri, value); + } + + protected static int parseCea608AccessibilityChannel( + List accessibilityDescriptors) { + for (int i = 0; i < accessibilityDescriptors.size(); i++) { + SchemeValuePair descriptor = accessibilityDescriptors.get(i); + if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri) + && descriptor.value != null) { + Matcher accessibilityValueMatcher = CEA_608_ACCESSIBILITY_PATTERN.matcher(descriptor.value); + if (accessibilityValueMatcher.matches()) { + return Integer.parseInt(accessibilityValueMatcher.group(1)); + } else { + Log.w(TAG, "Unable to parse CEA-608 channel number from: " + descriptor.value); + } + } + } + return Format.NO_VALUE; + } + + protected static int parseCea708AccessibilityChannel( + List accessibilityDescriptors) { + for (int i = 0; i < accessibilityDescriptors.size(); i++) { + SchemeValuePair descriptor = accessibilityDescriptors.get(i); + if ("urn:scte:dash:cc:cea-708:2015".equals(descriptor.schemeIdUri) + && descriptor.value != null) { + Matcher accessibilityValueMatcher = CEA_708_ACCESSIBILITY_PATTERN.matcher(descriptor.value); + if (accessibilityValueMatcher.matches()) { + return Integer.parseInt(accessibilityValueMatcher.group(1)); + } else { + Log.w(TAG, "Unable to parse CEA-708 service block number from: " + descriptor.value); + } + } + } + return Format.NO_VALUE; + } + + protected static float parseFrameRate(XmlPullParser xpp, float defaultValue) { + float frameRate = defaultValue; + String frameRateAttribute = xpp.getAttributeValue(null, "frameRate"); + if (frameRateAttribute != null) { + Matcher frameRateMatcher = FRAME_RATE_PATTERN.matcher(frameRateAttribute); + if (frameRateMatcher.matches()) { + int numerator = Integer.parseInt(frameRateMatcher.group(1)); + String denominatorString = frameRateMatcher.group(2); + if (!TextUtils.isEmpty(denominatorString)) { + frameRate = (float) numerator / Integer.parseInt(denominatorString); + } else { + frameRate = numerator; + } + } + } + return frameRate; + } + + protected static long parseDuration(XmlPullParser xpp, String name, long defaultValue) { + String value = xpp.getAttributeValue(null, name); + if (value == null) { + return defaultValue; + } else { + return Util.parseXsDuration(value); + } + } + + protected static long parseDateTime(XmlPullParser xpp, String name, long defaultValue) + throws ParserException { + String value = xpp.getAttributeValue(null, name); + if (value == null) { + return defaultValue; + } else { + return Util.parseXsDateTime(value); + } + } + + protected static String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl) + throws XmlPullParserException, IOException { + xpp.next(); + return UriUtil.resolve(parentBaseUrl, xpp.getText()); + } + + protected static int parseInt(XmlPullParser xpp, String name, int defaultValue) { + String value = xpp.getAttributeValue(null, name); + return value == null ? defaultValue : Integer.parseInt(value); + } + + protected static long parseLong(XmlPullParser xpp, String name, long defaultValue) { + String value = xpp.getAttributeValue(null, name); + return value == null ? defaultValue : Long.parseLong(value); + } + + protected static String parseString(XmlPullParser xpp, String name, String defaultValue) { + String value = xpp.getAttributeValue(null, name); + return value == null ? defaultValue : value; + } + + private static final class RepresentationInfo { + + public final Format format; + public final String baseUrl; + public final SegmentBase segmentBase; + public final ArrayList drmSchemeDatas; + public final ArrayList inbandEventStreams; + + public RepresentationInfo(Format format, String baseUrl, SegmentBase segmentBase, + ArrayList drmSchemeDatas, ArrayList inbandEventStreams) { + this.format = format; + this.baseUrl = baseUrl; + this.segmentBase = segmentBase; + this.drmSchemeDatas = drmSchemeDatas; + this.inbandEventStreams = inbandEventStreams; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/Period.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/Period.java new file mode 100644 index 0000000..0612a6c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/Period.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.util.Collections; +import java.util.List; + +/** + * Encapsulates media content components over a contiguous period of time. + */ +public class Period { + + /** + * The period identifier, if one exists. + */ + public final String id; + + /** + * The start time of the period in milliseconds. + */ + public final long startMs; + + /** + * The adaptation sets belonging to the period. + */ + public final List adaptationSets; + + /** + * @param id The period identifier. May be null. + * @param startMs The start time of the period in milliseconds. + * @param adaptationSets The adaptation sets belonging to the period. + */ + public Period(String id, long startMs, List adaptationSets) { + this.id = id; + this.startMs = startMs; + this.adaptationSets = Collections.unmodifiableList(adaptationSets); + } + + /** + * Returns the index of the first adaptation set of a given type, or {@link C#INDEX_UNSET} if no + * adaptation set of the specified type exists. + * + * @param type An adaptation set type. + * @return The index of the first adaptation set of the specified type, or {@link C#INDEX_UNSET}. + */ + public int getAdaptationSetIndex(int type) { + int adaptationCount = adaptationSets.size(); + for (int i = 0; i < adaptationCount; i++) { + if (adaptationSets.get(i).type == type) { + return i; + } + } + return C.INDEX_UNSET; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/RangedUri.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/RangedUri.java new file mode 100644 index 0000000..71af16c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/RangedUri.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.UriUtil; + +/** + * Defines a range of data located at a reference uri. + */ +public final class RangedUri { + + /** + * The (zero based) index of the first byte of the range. + */ + public final long start; + + /** + * The length of the range, or {@link C#LENGTH_UNSET} to indicate that the range is unbounded. + */ + public final long length; + + private final String referenceUri; + + private int hashCode; + + /** + * Constructs an ranged uri. + * + * @param referenceUri The reference uri. + * @param start The (zero based) index of the first byte of the range. + * @param length The length of the range, or {@link C#LENGTH_UNSET} to indicate that the range is + * unbounded. + */ + public RangedUri(String referenceUri, long start, long length) { + this.referenceUri = referenceUri == null ? "" : referenceUri; + this.start = start; + this.length = length; + } + + /** + * Returns the resolved {@link Uri} represented by the instance. + * + * @param baseUri The base Uri. + * @return The {@link Uri} represented by the instance. + */ + public Uri resolveUri(String baseUri) { + return UriUtil.resolveToUri(baseUri, referenceUri); + } + + /** + * Returns the resolved uri represented by the instance as a string. + * + * @param baseUri The base Uri. + * @return The uri represented by the instance. + */ + public String resolveUriString(String baseUri) { + return UriUtil.resolve(baseUri, referenceUri); + } + + /** + * Attempts to merge this {@link RangedUri} with another and an optional common base uri. + *

    + * A merge is successful if both instances define the same {@link Uri} after resolution with the + * base uri, and if one starts the byte after the other ends, forming a contiguous region with + * no overlap. + *

    + * If {@code other} is null then the merge is considered unsuccessful, and null is returned. + * + * @param other The {@link RangedUri} to merge. + * @param baseUri The optional base Uri. + * @return The merged {@link RangedUri} if the merge was successful. Null otherwise. + */ + public RangedUri attemptMerge(RangedUri other, String baseUri) { + final String resolvedUri = resolveUriString(baseUri); + if (other == null || !resolvedUri.equals(other.resolveUriString(baseUri))) { + return null; + } else if (length != C.LENGTH_UNSET && start + length == other.start) { + return new RangedUri(resolvedUri, start, + other.length == C.LENGTH_UNSET ? C.LENGTH_UNSET : length + other.length); + } else if (other.length != C.LENGTH_UNSET && other.start + other.length == start) { + return new RangedUri(resolvedUri, other.start, + length == C.LENGTH_UNSET ? C.LENGTH_UNSET : other.length + length); + } else { + return null; + } + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + (int) start; + result = 31 * result + (int) length; + result = 31 * result + referenceUri.hashCode(); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + RangedUri other = (RangedUri) obj; + return this.start == other.start + && this.length == other.length + && referenceUri.equals(other.referenceUri); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/Representation.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/Representation.java new file mode 100644 index 0000000..22d8a05 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/Representation.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.DashSegmentIndex; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.SegmentBase.MultiSegmentBase; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; +import java.util.Collections; +import java.util.List; + +/** + * A DASH representation. + */ +public abstract class Representation { + + /** + * A default value for {@link #revisionId}. + */ + public static final long REVISION_ID_DEFAULT = -1; + + /** + * Identifies the piece of content to which this {@link Representation} belongs. + *

    + * For example, all {@link Representation}s belonging to a video should have the same content + * identifier that uniquely identifies that video. + */ + public final String contentId; + /** + * Identifies the revision of the content. + *

    + * If the media for a given ({@link #contentId} can change over time without a change to the + * {@link #format}'s {@link Format#id} (e.g. as a result of re-encoding the media with an + * updated encoder), then this identifier must uniquely identify the revision of the media. The + * timestamp at which the media was encoded is often a suitable. + */ + public final long revisionId; + /** + * The format of the representation. + */ + public final Format format; + /** + * The base URL of the representation. + */ + public final String baseUrl; + /** + * The offset of the presentation timestamps in the media stream relative to media time. + */ + public final long presentationTimeOffsetUs; + /** + * The in-band event streams in the representation. Never null, but may be empty. + */ + public final List inbandEventStreams; + + private final RangedUri initializationUri; + + /** + * Constructs a new instance. + * + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL. + * @param segmentBase A segment base element for the representation. + * @return The constructed instance. + */ + public static Representation newInstance(String contentId, long revisionId, Format format, + String baseUrl, SegmentBase segmentBase) { + return newInstance(contentId, revisionId, format, baseUrl, segmentBase, null); + } + + /** + * Constructs a new instance. + * + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL. + * @param segmentBase A segment base element for the representation. + * @param inbandEventStreams The in-band event streams in the representation. May be null. + * @return The constructed instance. + */ + public static Representation newInstance(String contentId, long revisionId, Format format, + String baseUrl, SegmentBase segmentBase, List inbandEventStreams) { + return newInstance(contentId, revisionId, format, baseUrl, segmentBase, inbandEventStreams, + null); + } + + /** + * Constructs a new instance. + * + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL of the representation. + * @param segmentBase A segment base element for the representation. + * @param inbandEventStreams The in-band event streams in the representation. May be null. + * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. This + * parameter is ignored if {@code segmentBase} consists of multiple segments. + * @return The constructed instance. + */ + public static Representation newInstance(String contentId, long revisionId, Format format, + String baseUrl, SegmentBase segmentBase, List inbandEventStreams, + String customCacheKey) { + if (segmentBase instanceof SingleSegmentBase) { + return new SingleSegmentRepresentation(contentId, revisionId, format, baseUrl, + (SingleSegmentBase) segmentBase, inbandEventStreams, customCacheKey, C.LENGTH_UNSET); + } else if (segmentBase instanceof MultiSegmentBase) { + return new MultiSegmentRepresentation(contentId, revisionId, format, baseUrl, + (MultiSegmentBase) segmentBase, inbandEventStreams); + } else { + throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or " + + "MultiSegmentBase"); + } + } + + private Representation(String contentId, long revisionId, Format format, String baseUrl, + SegmentBase segmentBase, List inbandEventStreams) { + this.contentId = contentId; + this.revisionId = revisionId; + this.format = format; + this.baseUrl = baseUrl; + this.inbandEventStreams = inbandEventStreams == null + ? Collections.emptyList() + : Collections.unmodifiableList(inbandEventStreams); + initializationUri = segmentBase.getInitialization(this); + presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); + } + + /** + * Returns a {@link RangedUri} defining the location of the representation's initialization data, + * or null if no initialization data exists. + */ + public RangedUri getInitializationUri() { + return initializationUri; + } + + /** + * Returns a {@link RangedUri} defining the location of the representation's segment index, or + * null if the representation provides an index directly. + */ + public abstract RangedUri getIndexUri(); + + /** + * Returns an index if the representation provides one directly, or null otherwise. + */ + public abstract DashSegmentIndex getIndex(); + + /** + * Returns a cache key for the representation if a custom cache key or content id has been + * provided and there is only single segment. + */ + public abstract String getCacheKey(); + + /** + * A DASH representation consisting of a single segment. + */ + public static class SingleSegmentRepresentation extends Representation { + + /** + * The uri of the single segment. + */ + public final Uri uri; + + /** + * The content length, or {@link C#LENGTH_UNSET} if unknown. + */ + public final long contentLength; + + private final String cacheKey; + private final RangedUri indexUri; + private final SingleSegmentIndex segmentIndex; + + /** + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param uri The uri of the media. + * @param initializationStart The offset of the first byte of initialization data. + * @param initializationEnd The offset of the last byte of initialization data. + * @param indexStart The offset of the first byte of index data. + * @param indexEnd The offset of the last byte of index data. + * @param inbandEventStreams The in-band event streams in the representation. May be null. + * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. + * @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown. + */ + public static SingleSegmentRepresentation newInstance(String contentId, long revisionId, + Format format, String uri, long initializationStart, long initializationEnd, + long indexStart, long indexEnd, List inbandEventStreams, + String customCacheKey, long contentLength) { + RangedUri rangedUri = new RangedUri(null, initializationStart, + initializationEnd - initializationStart + 1); + SingleSegmentBase segmentBase = new SingleSegmentBase(rangedUri, 1, 0, indexStart, + indexEnd - indexStart + 1); + return new SingleSegmentRepresentation(contentId, revisionId, + format, uri, segmentBase, inbandEventStreams, customCacheKey, contentLength); + } + + /** + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL of the representation. + * @param segmentBase The segment base underlying the representation. + * @param inbandEventStreams The in-band event streams in the representation. May be null. + * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. + * @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown. + */ + public SingleSegmentRepresentation(String contentId, long revisionId, Format format, + String baseUrl, SingleSegmentBase segmentBase, List inbandEventStreams, + String customCacheKey, long contentLength) { + super(contentId, revisionId, format, baseUrl, segmentBase, inbandEventStreams); + this.uri = Uri.parse(baseUrl); + this.indexUri = segmentBase.getIndex(); + this.cacheKey = customCacheKey != null ? customCacheKey + : contentId != null ? contentId + "." + format.id + "." + revisionId : null; + this.contentLength = contentLength; + // If we have an index uri then the index is defined externally, and we shouldn't return one + // directly. If we don't, then we can't do better than an index defining a single segment. + segmentIndex = indexUri != null ? null + : new SingleSegmentIndex(new RangedUri(null, 0, contentLength)); + } + + @Override + public RangedUri getIndexUri() { + return indexUri; + } + + @Override + public DashSegmentIndex getIndex() { + return segmentIndex; + } + + @Override + public String getCacheKey() { + return cacheKey; + } + + } + + /** + * A DASH representation consisting of multiple segments. + */ + public static class MultiSegmentRepresentation extends Representation + implements DashSegmentIndex { + + private final MultiSegmentBase segmentBase; + + /** + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL of the representation. + * @param segmentBase The segment base underlying the representation. + * @param inbandEventStreams The in-band event streams in the representation. May be null. + */ + public MultiSegmentRepresentation(String contentId, long revisionId, Format format, + String baseUrl, MultiSegmentBase segmentBase, List inbandEventStreams) { + super(contentId, revisionId, format, baseUrl, segmentBase, inbandEventStreams); + this.segmentBase = segmentBase; + } + + @Override + public RangedUri getIndexUri() { + return null; + } + + @Override + public DashSegmentIndex getIndex() { + return this; + } + + @Override + public String getCacheKey() { + return null; + } + + // DashSegmentIndex implementation. + + @Override + public RangedUri getSegmentUrl(int segmentIndex) { + return segmentBase.getSegmentUrl(this, segmentIndex); + } + + @Override + public int getSegmentNum(long timeUs, long periodDurationUs) { + return segmentBase.getSegmentNum(timeUs, periodDurationUs); + } + + @Override + public long getTimeUs(int segmentIndex) { + return segmentBase.getSegmentTimeUs(segmentIndex); + } + + @Override + public long getDurationUs(int segmentIndex, long periodDurationUs) { + return segmentBase.getSegmentDurationUs(segmentIndex, periodDurationUs); + } + + @Override + public int getFirstSegmentNum() { + return segmentBase.getFirstSegmentNum(); + } + + @Override + public int getSegmentCount(long periodDurationUs) { + return segmentBase.getSegmentCount(periodDurationUs); + } + + @Override + public boolean isExplicit() { + return segmentBase.isExplicit(); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/RepresentationKey.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/RepresentationKey.java new file mode 100644 index 0000000..61a14af --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/RepresentationKey.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +/** + * Uniquely identifies a {@link Representation} in a {@link DashManifest}. + */ +public final class RepresentationKey implements Parcelable, Comparable { + + public final int periodIndex; + public final int adaptationSetIndex; + public final int representationIndex; + + public RepresentationKey(int periodIndex, int adaptationSetIndex, int representationIndex) { + this.periodIndex = periodIndex; + this.adaptationSetIndex = adaptationSetIndex; + this.representationIndex = representationIndex; + } + + @Override + public String toString() { + return periodIndex + "." + adaptationSetIndex + "." + representationIndex; + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(periodIndex); + dest.writeInt(adaptationSetIndex); + dest.writeInt(representationIndex); + } + + public static final Creator CREATOR = + new Creator() { + @Override + public RepresentationKey createFromParcel(Parcel in) { + return new RepresentationKey(in.readInt(), in.readInt(), in.readInt()); + } + + @Override + public RepresentationKey[] newArray(int size) { + return new RepresentationKey[size]; + } + }; + + // Comparable implementation. + + @Override + public int compareTo(@NonNull RepresentationKey o) { + int result = periodIndex - o.periodIndex; + if (result == 0) { + result = adaptationSetIndex - o.adaptationSetIndex; + if (result == 0) { + result = representationIndex - o.representationIndex; + } + } + return result; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SchemeValuePair.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SchemeValuePair.java new file mode 100644 index 0000000..94bd906 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SchemeValuePair.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * A pair consisting of a scheme ID and value. + */ +public class SchemeValuePair { + + public final String schemeIdUri; + public final String value; + + public SchemeValuePair(String schemeIdUri, String value) { + this.schemeIdUri = schemeIdUri; + this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + SchemeValuePair other = (SchemeValuePair) obj; + return Util.areEqual(schemeIdUri, other.schemeIdUri) && Util.areEqual(value, other.value); + } + + @Override + public int hashCode() { + return 31 * (schemeIdUri != null ? schemeIdUri.hashCode() : 0) + + (value != null ? value.hashCode() : 0); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SegmentBase.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SegmentBase.java new file mode 100644 index 0000000..98e84c5 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SegmentBase.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.DashSegmentIndex; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.List; + +/** + * An approximate representation of a SegmentBase manifest element. + */ +public abstract class SegmentBase { + + /* package */ final RangedUri initialization; + /* package */ final long timescale; + /* package */ final long presentationTimeOffset; + + /** + * @param initialization A {@link RangedUri} corresponding to initialization data, if such data + * exists. + * @param timescale The timescale in units per second. + * @param presentationTimeOffset The presentation time offset. The value in seconds is the + * division of this value and {@code timescale}. + */ + public SegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset) { + this.initialization = initialization; + this.timescale = timescale; + this.presentationTimeOffset = presentationTimeOffset; + } + + /** + * Returns the {@link RangedUri} defining the location of initialization data for a given + * representation, or null if no initialization data exists. + * + * @param representation The {@link Representation} for which initialization data is required. + * @return A {@link RangedUri} defining the location of the initialization data, or null. + */ + public RangedUri getInitialization(Representation representation) { + return initialization; + } + + /** + * Returns the presentation time offset, in microseconds. + */ + public long getPresentationTimeOffsetUs() { + return Util.scaleLargeTimestamp(presentationTimeOffset, C.MICROS_PER_SECOND, timescale); + } + + /** + * A {@link SegmentBase} that defines a single segment. + */ + public static class SingleSegmentBase extends SegmentBase { + + /* package */ final long indexStart; + /* package */ final long indexLength; + + /** + * @param initialization A {@link RangedUri} corresponding to initialization data, if such data + * exists. + * @param timescale The timescale in units per second. + * @param presentationTimeOffset The presentation time offset. The value in seconds is the + * division of this value and {@code timescale}. + * @param indexStart The byte offset of the index data in the segment. + * @param indexLength The length of the index data in bytes. + */ + public SingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, + long indexStart, long indexLength) { + super(initialization, timescale, presentationTimeOffset); + this.indexStart = indexStart; + this.indexLength = indexLength; + } + + public SingleSegmentBase() { + this(null, 1, 0, 0, 0); + } + + public RangedUri getIndex() { + return indexLength <= 0 ? null : new RangedUri(null, indexStart, indexLength); + } + + } + + /** + * A {@link SegmentBase} that consists of multiple segments. + */ + public abstract static class MultiSegmentBase extends SegmentBase { + + /* package */ final int startNumber; + /* package */ final long duration; + /* package */ final List segmentTimeline; + + /** + * @param initialization A {@link RangedUri} corresponding to initialization data, if such data + * exists. + * @param timescale The timescale in units per second. + * @param presentationTimeOffset The presentation time offset. The value in seconds is the + * division of this value and {@code timescale}. + * @param startNumber The sequence number of the first segment. + * @param duration The duration of each segment in the case of fixed duration segments. The + * value in seconds is the division of this value and {@code timescale}. If + * {@code segmentTimeline} is non-null then this parameter is ignored. + * @param segmentTimeline A segment timeline corresponding to the segments. If null, then + * segments are assumed to be of fixed duration as specified by the {@code duration} + * parameter. + */ + public MultiSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, + int startNumber, long duration, List segmentTimeline) { + super(initialization, timescale, presentationTimeOffset); + this.startNumber = startNumber; + this.duration = duration; + this.segmentTimeline = segmentTimeline; + } + + /** + * @see DashSegmentIndex#getSegmentNum(long, long) + */ + public int getSegmentNum(long timeUs, long periodDurationUs) { + final int firstSegmentNum = getFirstSegmentNum(); + final int segmentCount = getSegmentCount(periodDurationUs); + if (segmentCount == 0) { + return firstSegmentNum; + } + if (segmentTimeline == null) { + // All segments are of equal duration (with the possible exception of the last one). + long durationUs = (duration * C.MICROS_PER_SECOND) / timescale; + int segmentNum = startNumber + (int) (timeUs / durationUs); + // Ensure we stay within bounds. + return segmentNum < firstSegmentNum ? firstSegmentNum + : segmentCount == DashSegmentIndex.INDEX_UNBOUNDED ? segmentNum + : Math.min(segmentNum, firstSegmentNum + segmentCount - 1); + } else { + // The index cannot be unbounded. Identify the segment using binary search. + int lowIndex = firstSegmentNum; + int highIndex = firstSegmentNum + segmentCount - 1; + while (lowIndex <= highIndex) { + int midIndex = lowIndex + (highIndex - lowIndex) / 2; + long midTimeUs = getSegmentTimeUs(midIndex); + if (midTimeUs < timeUs) { + lowIndex = midIndex + 1; + } else if (midTimeUs > timeUs) { + highIndex = midIndex - 1; + } else { + return midIndex; + } + } + return lowIndex == firstSegmentNum ? lowIndex : highIndex; + } + } + + /** + * @see DashSegmentIndex#getDurationUs(int, long) + */ + public final long getSegmentDurationUs(int sequenceNumber, long periodDurationUs) { + if (segmentTimeline != null) { + long duration = segmentTimeline.get(sequenceNumber - startNumber).duration; + return (duration * C.MICROS_PER_SECOND) / timescale; + } else { + int segmentCount = getSegmentCount(periodDurationUs); + return segmentCount != DashSegmentIndex.INDEX_UNBOUNDED + && sequenceNumber == (getFirstSegmentNum() + segmentCount - 1) + ? (periodDurationUs - getSegmentTimeUs(sequenceNumber)) + : ((duration * C.MICROS_PER_SECOND) / timescale); + } + } + + /** + * @see DashSegmentIndex#getTimeUs(int) + */ + public final long getSegmentTimeUs(int sequenceNumber) { + long unscaledSegmentTime; + if (segmentTimeline != null) { + unscaledSegmentTime = segmentTimeline.get(sequenceNumber - startNumber).startTime + - presentationTimeOffset; + } else { + unscaledSegmentTime = (sequenceNumber - startNumber) * duration; + } + return Util.scaleLargeTimestamp(unscaledSegmentTime, C.MICROS_PER_SECOND, timescale); + } + + /** + * Returns a {@link RangedUri} defining the location of a segment for the given index in the + * given representation. + * + * @see DashSegmentIndex#getSegmentUrl(int) + */ + public abstract RangedUri getSegmentUrl(Representation representation, int index); + + /** + * @see DashSegmentIndex#getFirstSegmentNum() + */ + public int getFirstSegmentNum() { + return startNumber; + } + + /** + * @see DashSegmentIndex#getSegmentCount(long) + */ + public abstract int getSegmentCount(long periodDurationUs); + + /** + * @see DashSegmentIndex#isExplicit() + */ + public boolean isExplicit() { + return segmentTimeline != null; + } + + } + + /** + * A {@link MultiSegmentBase} that uses a SegmentList to define its segments. + */ + public static class SegmentList extends MultiSegmentBase { + + /* package */ final List mediaSegments; + + /** + * @param initialization A {@link RangedUri} corresponding to initialization data, if such data + * exists. + * @param timescale The timescale in units per second. + * @param presentationTimeOffset The presentation time offset. The value in seconds is the + * division of this value and {@code timescale}. + * @param startNumber The sequence number of the first segment. + * @param duration The duration of each segment in the case of fixed duration segments. The + * value in seconds is the division of this value and {@code timescale}. If + * {@code segmentTimeline} is non-null then this parameter is ignored. + * @param segmentTimeline A segment timeline corresponding to the segments. If null, then + * segments are assumed to be of fixed duration as specified by the {@code duration} + * parameter. + * @param mediaSegments A list of {@link RangedUri}s indicating the locations of the segments. + */ + public SegmentList(RangedUri initialization, long timescale, long presentationTimeOffset, + int startNumber, long duration, List segmentTimeline, + List mediaSegments) { + super(initialization, timescale, presentationTimeOffset, startNumber, duration, + segmentTimeline); + this.mediaSegments = mediaSegments; + } + + @Override + public RangedUri getSegmentUrl(Representation representation, int sequenceNumber) { + return mediaSegments.get(sequenceNumber - startNumber); + } + + @Override + public int getSegmentCount(long periodDurationUs) { + return mediaSegments.size(); + } + + @Override + public boolean isExplicit() { + return true; + } + + } + + /** + * A {@link MultiSegmentBase} that uses a SegmentTemplate to define its segments. + */ + public static class SegmentTemplate extends MultiSegmentBase { + + /* package */ final UrlTemplate initializationTemplate; + /* package */ final UrlTemplate mediaTemplate; + + /** + * @param initialization A {@link RangedUri} corresponding to initialization data, if such data + * exists. The value of this parameter is ignored if {@code initializationTemplate} is + * non-null. + * @param timescale The timescale in units per second. + * @param presentationTimeOffset The presentation time offset. The value in seconds is the + * division of this value and {@code timescale}. + * @param startNumber The sequence number of the first segment. + * @param duration The duration of each segment in the case of fixed duration segments. The + * value in seconds is the division of this value and {@code timescale}. If + * {@code segmentTimeline} is non-null then this parameter is ignored. + * @param segmentTimeline A segment timeline corresponding to the segments. If null, then + * segments are assumed to be of fixed duration as specified by the {@code duration} + * parameter. + * @param initializationTemplate A template defining the location of initialization data, if + * such data exists. If non-null then the {@code initialization} parameter is ignored. If + * null then {@code initialization} will be used. + * @param mediaTemplate A template defining the location of each media segment. + */ + public SegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset, + int startNumber, long duration, List segmentTimeline, + UrlTemplate initializationTemplate, UrlTemplate mediaTemplate) { + super(initialization, timescale, presentationTimeOffset, startNumber, + duration, segmentTimeline); + this.initializationTemplate = initializationTemplate; + this.mediaTemplate = mediaTemplate; + } + + @Override + public RangedUri getInitialization(Representation representation) { + if (initializationTemplate != null) { + String urlString = initializationTemplate.buildUri(representation.format.id, 0, + representation.format.bitrate, 0); + return new RangedUri(urlString, 0, C.LENGTH_UNSET); + } else { + return super.getInitialization(representation); + } + } + + @Override + public RangedUri getSegmentUrl(Representation representation, int sequenceNumber) { + long time; + if (segmentTimeline != null) { + time = segmentTimeline.get(sequenceNumber - startNumber).startTime; + } else { + time = (sequenceNumber - startNumber) * duration; + } + String uriString = mediaTemplate.buildUri(representation.format.id, sequenceNumber, + representation.format.bitrate, time); + return new RangedUri(uriString, 0, C.LENGTH_UNSET); + } + + @Override + public int getSegmentCount(long periodDurationUs) { + if (segmentTimeline != null) { + return segmentTimeline.size(); + } else if (periodDurationUs != C.TIME_UNSET) { + long durationUs = (duration * C.MICROS_PER_SECOND) / timescale; + return (int) Util.ceilDivide(periodDurationUs, durationUs); + } else { + return DashSegmentIndex.INDEX_UNBOUNDED; + } + } + + } + + /** + * Represents a timeline segment from the MPD's SegmentTimeline list. + */ + public static class SegmentTimelineElement { + + /* package */ final long startTime; + /* package */ final long duration; + + /** + * @param startTime The start time of the element. The value in seconds is the division of this + * value and the {@code timescale} of the enclosing element. + * @param duration The duration of the element. The value in seconds is the division of this + * value and the {@code timescale} of the enclosing element. + */ + public SegmentTimelineElement(long startTime, long duration) { + this.startTime = startTime; + this.duration = duration; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SingleSegmentIndex.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SingleSegmentIndex.java new file mode 100644 index 0000000..faea179 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/SingleSegmentIndex.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import com.tangxiaolv.telegramgallery.exoplayer2.source.dash.DashSegmentIndex; + +/** + * A {@link DashSegmentIndex} that defines a single segment. + */ +/* package */ final class SingleSegmentIndex implements DashSegmentIndex { + + private final RangedUri uri; + + /** + * @param uri A {@link RangedUri} defining the location of the segment data. + */ + public SingleSegmentIndex(RangedUri uri) { + this.uri = uri; + } + + @Override + public int getSegmentNum(long timeUs, long periodDurationUs) { + return 0; + } + + @Override + public long getTimeUs(int segmentNum) { + return 0; + } + + @Override + public long getDurationUs(int segmentNum, long periodDurationUs) { + return periodDurationUs; + } + + @Override + public RangedUri getSegmentUrl(int segmentNum) { + return uri; + } + + @Override + public int getFirstSegmentNum() { + return 0; + } + + @Override + public int getSegmentCount(long periodDurationUs) { + return 1; + } + + @Override + public boolean isExplicit() { + return true; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/UrlTemplate.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/UrlTemplate.java new file mode 100644 index 0000000..45d2027 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/UrlTemplate.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +import java.util.Locale; + +/** + * A template from which URLs can be built. + *

    + * URLs are built according to the substitution rules defined in ISO/IEC 23009-1:2014 5.3.9.4.4. + */ +public final class UrlTemplate { + + private static final String REPRESENTATION = "RepresentationID"; + private static final String NUMBER = "Number"; + private static final String BANDWIDTH = "Bandwidth"; + private static final String TIME = "Time"; + private static final String ESCAPED_DOLLAR = "$$"; + private static final String DEFAULT_FORMAT_TAG = "%01d"; + + private static final int REPRESENTATION_ID = 1; + private static final int NUMBER_ID = 2; + private static final int BANDWIDTH_ID = 3; + private static final int TIME_ID = 4; + + private final String[] urlPieces; + private final int[] identifiers; + private final String[] identifierFormatTags; + private final int identifierCount; + + /** + * Compile an instance from the provided template string. + * + * @param template The template. + * @return The compiled instance. + * @throws IllegalArgumentException If the template string is malformed. + */ + public static UrlTemplate compile(String template) { + // These arrays are sizes assuming each of the four possible identifiers will be present at + // most once in the template, which seems like a reasonable assumption. + String[] urlPieces = new String[5]; + int[] identifiers = new int[4]; + String[] identifierFormatTags = new String[4]; + int identifierCount = parseTemplate(template, urlPieces, identifiers, identifierFormatTags); + return new UrlTemplate(urlPieces, identifiers, identifierFormatTags, identifierCount); + } + + /** + * Internal constructor. Use {@link #compile(String)} to build instances of this class. + */ + private UrlTemplate(String[] urlPieces, int[] identifiers, String[] identifierFormatTags, + int identifierCount) { + this.urlPieces = urlPieces; + this.identifiers = identifiers; + this.identifierFormatTags = identifierFormatTags; + this.identifierCount = identifierCount; + } + + /** + * Constructs a Uri from the template, substituting in the provided arguments. + *

    + * Arguments whose corresponding identifiers are not present in the template will be ignored. + * + * @param representationId The representation identifier. + * @param segmentNumber The segment number. + * @param bandwidth The bandwidth. + * @param time The time as specified by the segment timeline. + * @return The built Uri. + */ + public String buildUri(String representationId, int segmentNumber, int bandwidth, long time) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < identifierCount; i++) { + builder.append(urlPieces[i]); + if (identifiers[i] == REPRESENTATION_ID) { + builder.append(representationId); + } else if (identifiers[i] == NUMBER_ID) { + builder.append(String.format(Locale.US, identifierFormatTags[i], segmentNumber)); + } else if (identifiers[i] == BANDWIDTH_ID) { + builder.append(String.format(Locale.US, identifierFormatTags[i], bandwidth)); + } else if (identifiers[i] == TIME_ID) { + builder.append(String.format(Locale.US, identifierFormatTags[i], time)); + } + } + builder.append(urlPieces[identifierCount]); + return builder.toString(); + } + + /** + * Parses {@code template}, placing the decomposed components into the provided arrays. + *

    + * If the return value is N, {@code urlPieces} will contain (N+1) strings that must be + * interleaved with N arguments in order to construct a url. The N identifiers that correspond to + * the required arguments, together with the tags that define their required formatting, are + * returned in {@code identifiers} and {@code identifierFormatTags} respectively. + * + * @param template The template to parse. + * @param urlPieces A holder for pieces of url parsed from the template. + * @param identifiers A holder for identifiers parsed from the template. + * @param identifierFormatTags A holder for format tags corresponding to the parsed identifiers. + * @return The number of identifiers in the template url. + * @throws IllegalArgumentException If the template string is malformed. + */ + private static int parseTemplate(String template, String[] urlPieces, int[] identifiers, + String[] identifierFormatTags) { + urlPieces[0] = ""; + int templateIndex = 0; + int identifierCount = 0; + while (templateIndex < template.length()) { + int dollarIndex = template.indexOf("$", templateIndex); + if (dollarIndex == -1) { + urlPieces[identifierCount] += template.substring(templateIndex); + templateIndex = template.length(); + } else if (dollarIndex != templateIndex) { + urlPieces[identifierCount] += template.substring(templateIndex, dollarIndex); + templateIndex = dollarIndex; + } else if (template.startsWith(ESCAPED_DOLLAR, templateIndex)) { + urlPieces[identifierCount] += "$"; + templateIndex += 2; + } else { + int secondIndex = template.indexOf("$", templateIndex + 1); + String identifier = template.substring(templateIndex + 1, secondIndex); + if (identifier.equals(REPRESENTATION)) { + identifiers[identifierCount] = REPRESENTATION_ID; + } else { + int formatTagIndex = identifier.indexOf("%0"); + String formatTag = DEFAULT_FORMAT_TAG; + if (formatTagIndex != -1) { + formatTag = identifier.substring(formatTagIndex); + if (!formatTag.endsWith("d")) { + formatTag += "d"; + } + identifier = identifier.substring(0, formatTagIndex); + } + switch (identifier) { + case NUMBER: + identifiers[identifierCount] = NUMBER_ID; + break; + case BANDWIDTH: + identifiers[identifierCount] = BANDWIDTH_ID; + break; + case TIME: + identifiers[identifierCount] = TIME_ID; + break; + default: + throw new IllegalArgumentException("Invalid template: " + template); + } + identifierFormatTags[identifierCount] = formatTag; + } + identifierCount++; + urlPieces[identifierCount] = ""; + templateIndex = secondIndex + 1; + } + } + return identifierCount; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/UtcTimingElement.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/UtcTimingElement.java new file mode 100644 index 0000000..e563d2b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/dash/manifest/UtcTimingElement.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.dash.manifest; + +/** + * Represents a UTCTiming element. + */ +public final class UtcTimingElement { + + public final String schemeIdUri; + public final String value; + + public UtcTimingElement(String schemeIdUri, String value) { + this.schemeIdUri = schemeIdUri; + this.value = value; + } + + @Override + public String toString() { + return schemeIdUri + ", " + value; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/Aes128DataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/Aes128DataSource.java new file mode 100644 index 0000000..3d1f877 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/Aes128DataSource.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSourceInputStream; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * A {@link DataSource} that decrypts data read from an upstream source, encrypted with AES-128 with + * a 128-bit key and PKCS7 padding. + *

    + * Note that this {@link DataSource} does not support being opened from arbitrary offsets. It is + * designed specifically for reading whole files as defined in an HLS media playlist. For this + * reason the implementation is private to the HLS package. + */ +/* package */ final class Aes128DataSource implements DataSource { + + private final DataSource upstream; + private final byte[] encryptionKey; + private final byte[] encryptionIv; + + private CipherInputStream cipherInputStream; + + /** + * @param upstream The upstream {@link DataSource}. + * @param encryptionKey The encryption key. + * @param encryptionIv The encryption initialization vector. + */ + public Aes128DataSource(DataSource upstream, byte[] encryptionKey, byte[] encryptionIv) { + this.upstream = upstream; + this.encryptionKey = encryptionKey; + this.encryptionIv = encryptionIv; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + Cipher cipher; + try { + cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new RuntimeException(e); + } + + Key cipherKey = new SecretKeySpec(encryptionKey, "AES"); + AlgorithmParameterSpec cipherIV = new IvParameterSpec(encryptionIv); + + try { + cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIV); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } + + cipherInputStream = new CipherInputStream( + new DataSourceInputStream(upstream, dataSpec), cipher); + + return C.LENGTH_UNSET; + } + + @Override + public void close() throws IOException { + cipherInputStream = null; + upstream.close(); + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + Assertions.checkState(cipherInputStream != null); + int bytesRead = cipherInputStream.read(buffer, offset, readLength); + if (bytesRead < 0) { + return C.RESULT_END_OF_INPUT; + } + return bytesRead; + } + + @Override + public Uri getUri() { + return upstream.getUri(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java new file mode 100644 index 0000000..bc5d37e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; + +/** + * Default implementation of {@link HlsDataSourceFactory}. + */ +public final class DefaultHlsDataSourceFactory implements HlsDataSourceFactory { + + private final DataSource.Factory dataSourceFactory; + + /** + * @param dataSourceFactory The {@link DataSource.Factory} to use for all data types. + */ + public DefaultHlsDataSourceFactory(DataSource.Factory dataSourceFactory) { + this.dataSourceFactory = dataSourceFactory; + } + + @Override + public DataSource createDataSource(int dataType) { + return dataSourceFactory.createDataSource(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsChunkSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsChunkSource.java new file mode 100644 index 0000000..07b89ce --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsChunkSource.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import android.net.Uri; +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.source.BehindLiveWindowException; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.Chunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.DataChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMediaPlaylist; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsPlaylistTracker; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.BaseTrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import com.tangxiaolv.telegramgallery.exoplayer2.util.UriUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * Source of Hls (possibly adaptive) chunks. + */ +/* package */ class HlsChunkSource { + + /** + * Chunk holder that allows the scheduling of retries. + */ + public static final class HlsChunkHolder { + + public HlsChunkHolder() { + clear(); + } + + /** + * The chunk to be loaded next. + */ + public Chunk chunk; + + /** + * Indicates that the end of the stream has been reached. + */ + public boolean endOfStream; + + /** + * Indicates that the chunk source is waiting for the referred playlist to be refreshed. + */ + public HlsUrl playlist; + + /** + * Clears the holder. + */ + public void clear() { + chunk = null; + endOfStream = false; + playlist = null; + } + + } + + private final DataSource mediaDataSource; + private final DataSource encryptionDataSource; + private final TimestampAdjusterProvider timestampAdjusterProvider; + private final HlsUrl[] variants; + private final HlsPlaylistTracker playlistTracker; + private final TrackGroup trackGroup; + private final List muxedCaptionFormats; + + private boolean isTimestampMaster; + private byte[] scratchSpace; + private IOException fatalError; + + private Uri encryptionKeyUri; + private byte[] encryptionKey; + private String encryptionIvString; + private byte[] encryptionIv; + + // Note: The track group in the selection is typically *not* equal to trackGroup. This is due to + // the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods + // in TrackSelection to avoid unexpected behavior. + private TrackSelection trackSelection; + + /** + * @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists. + * @param variants The available variants. + * @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the + * chunks. + * @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If + * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the + * same provider. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. + */ + public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants, + HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider, + List muxedCaptionFormats) { + this.playlistTracker = playlistTracker; + this.variants = variants; + this.timestampAdjusterProvider = timestampAdjusterProvider; + this.muxedCaptionFormats = muxedCaptionFormats; + Format[] variantFormats = new Format[variants.length]; + int[] initialTrackSelection = new int[variants.length]; + for (int i = 0; i < variants.length; i++) { + variantFormats[i] = variants[i].format; + initialTrackSelection[i] = i; + } + mediaDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_MEDIA); + encryptionDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_DRM); + trackGroup = new TrackGroup(variantFormats); + trackSelection = new InitializationTrackSelection(trackGroup, initialTrackSelection); + } + + /** + * If the source is currently having difficulty providing chunks, then this method throws the + * underlying error. Otherwise does nothing. + * + * @throws IOException The underlying error. + */ + public void maybeThrowError() throws IOException { + if (fatalError != null) { + throw fatalError; + } + } + + /** + * Returns the track group exposed by the source. + */ + public TrackGroup getTrackGroup() { + return trackGroup; + } + + /** + * Selects tracks for use. + * + * @param trackSelection The track selection. + */ + public void selectTracks(TrackSelection trackSelection) { + this.trackSelection = trackSelection; + } + + /** + * Resets the source. + */ + public void reset() { + fatalError = null; + } + + /** + * Sets whether this chunk source is responsible for initializing timestamp adjusters. + * + * @param isTimestampMaster True if this chunk source is responsible for initializing timestamp + * adjusters. + */ + public void setIsTimestampMaster(boolean isTimestampMaster) { + this.isTimestampMaster = isTimestampMaster; + } + + /** + * Returns the next chunk to load. + *

    + * If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream has + * been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available but + * the end of the stream has not been reached, {@link HlsChunkHolder#playlist} is set to + * contain the {@link HlsUrl} that refers to the playlist that needs refreshing. + * + * @param previous The most recently loaded media chunk. + * @param playbackPositionUs The current playback position. If {@code previous} is null then this + * parameter is the position from which playback is expected to start (or restart) and hence + * should be interpreted as a seek position. + * @param out A holder to populate. + */ + public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) { + int oldVariantIndex = previous == null ? C.INDEX_UNSET + : trackGroup.indexOf(previous.trackFormat); + // Use start time of the previous chunk rather than its end time because switching format will + // require downloading overlapping segments. + long bufferedDurationUs = previous == null ? 0 + : Math.max(0, previous.startTimeUs - playbackPositionUs); + + // Select the variant. + trackSelection.updateSelectedTrack(bufferedDurationUs); + int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup(); + + boolean switchingVariant = oldVariantIndex != selectedVariantIndex; + HlsUrl selectedUrl = variants[selectedVariantIndex]; + if (!playlistTracker.isSnapshotValid(selectedUrl)) { + out.playlist = selectedUrl; + // Retry when playlist is refreshed. + return; + } + HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); + + // Select the chunk. + int chunkMediaSequence; + if (previous == null || switchingVariant) { + long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs; + if (!mediaPlaylist.hasEndTag && targetPositionUs > mediaPlaylist.getEndTimeUs()) { + // If the playlist is too old to contain the chunk, we need to refresh it. + chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(); + } else { + chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, + targetPositionUs - mediaPlaylist.startTimeUs, true, + !playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence; + if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) { + // We try getting the next chunk without adapting in case that's the reason for falling + // behind the live window. + selectedVariantIndex = oldVariantIndex; + selectedUrl = variants[selectedVariantIndex]; + mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); + chunkMediaSequence = previous.getNextChunkIndex(); + } + } + } else { + chunkMediaSequence = previous.getNextChunkIndex(); + } + if (chunkMediaSequence < mediaPlaylist.mediaSequence) { + fatalError = new BehindLiveWindowException(); + return; + } + + int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence; + if (chunkIndex >= mediaPlaylist.segments.size()) { + if (mediaPlaylist.hasEndTag) { + out.endOfStream = true; + } else /* Live */ { + out.playlist = selectedUrl; + } + return; + } + + // Handle encryption. + HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); + + // Check if encryption is specified. + if (segment.isEncrypted) { + Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); + if (!keyUri.equals(encryptionKeyUri)) { + // Encryption is specified and the key has changed. + out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex, + trackSelection.getSelectionReason(), trackSelection.getSelectionData()); + return; + } + if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) { + setEncryptionData(keyUri, segment.encryptionIV, encryptionKey); + } + } else { + clearEncryptionData(); + } + + DataSpec initDataSpec = null; + Segment initSegment = mediaPlaylist.initializationSegment; + if (initSegment != null) { + Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url); + initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset, + initSegment.byterangeLength, null); + } + + // Compute start time of the next chunk. + long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs; + int discontinuitySequence = mediaPlaylist.discontinuitySequence + + segment.relativeDiscontinuitySequence; + TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster( + discontinuitySequence); + + // Configure the data source and spec for the chunk. + Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); + DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, + null); + out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl, + muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), + startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence, + isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv); + } + + /** + * Called when the {@link HlsSampleStreamWrapper} has finished loading a chunk obtained from this + * source. + * + * @param chunk The chunk whose load has been completed. + */ + public void onChunkLoadCompleted(Chunk chunk) { + if (chunk instanceof EncryptionKeyChunk) { + EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk; + scratchSpace = encryptionKeyChunk.getDataHolder(); + setEncryptionData(encryptionKeyChunk.dataSpec.uri, encryptionKeyChunk.iv, + encryptionKeyChunk.getResult()); + } + } + + /** + * Called when the {@link HlsSampleStreamWrapper} encounters an error loading a chunk obtained + * from this source. + * + * @param chunk The chunk whose load encountered the error. + * @param cancelable Whether the load can be canceled. + * @param error The error. + * @return Whether the load should be canceled. + */ + public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException error) { + return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, + trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), error); + } + + /** + * Called when a playlist is blacklisted. + * + * @param url The url that references the blacklisted playlist. + * @param blacklistMs The amount of milliseconds for which the playlist was blacklisted. + */ + public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) { + int trackGroupIndex = trackGroup.indexOf(url.format); + if (trackGroupIndex != C.INDEX_UNSET) { + int trackSelectionIndex = trackSelection.indexOf(trackGroupIndex); + if (trackSelectionIndex != C.INDEX_UNSET) { + trackSelection.blacklist(trackSelectionIndex, blacklistMs); + } + } + } + + // Private methods. + + private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex, + int trackSelectionReason, Object trackSelectionData) { + DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP); + return new EncryptionKeyChunk(encryptionDataSource, dataSpec, variants[variantIndex].format, + trackSelectionReason, trackSelectionData, scratchSpace, iv); + } + + private void setEncryptionData(Uri keyUri, String iv, byte[] secretKey) { + String trimmedIv; + if (iv.toLowerCase(Locale.getDefault()).startsWith("0x")) { + trimmedIv = iv.substring(2); + } else { + trimmedIv = iv; + } + + byte[] ivData = new BigInteger(trimmedIv, 16).toByteArray(); + byte[] ivDataWithPadding = new byte[16]; + int offset = ivData.length > 16 ? ivData.length - 16 : 0; + System.arraycopy(ivData, offset, ivDataWithPadding, ivDataWithPadding.length - ivData.length + + offset, ivData.length - offset); + + encryptionKeyUri = keyUri; + encryptionKey = secretKey; + encryptionIvString = iv; + encryptionIv = ivDataWithPadding; + } + + private void clearEncryptionData() { + encryptionKeyUri = null; + encryptionKey = null; + encryptionIvString = null; + encryptionIv = null; + } + + // Private classes. + + /** + * A {@link TrackSelection} to use for initialization. + */ + private static final class InitializationTrackSelection extends BaseTrackSelection { + + private int selectedIndex; + + public InitializationTrackSelection(TrackGroup group, int[] tracks) { + super(group, tracks); + selectedIndex = indexOf(group.getFormat(0)); + } + + @Override + public void updateSelectedTrack(long bufferedDurationUs) { + long nowMs = SystemClock.elapsedRealtime(); + if (!isBlacklisted(selectedIndex, nowMs)) { + return; + } + // Try from lowest bitrate to highest. + for (int i = length - 1; i >= 0; i--) { + if (!isBlacklisted(i, nowMs)) { + selectedIndex = i; + return; + } + } + // Should never happen. + throw new IllegalStateException(); + } + + @Override + public int getSelectedIndex() { + return selectedIndex; + } + + @Override + public int getSelectionReason() { + return C.SELECTION_REASON_UNKNOWN; + } + + @Override + public Object getSelectionData() { + return null; + } + + } + + private static final class EncryptionKeyChunk extends DataChunk { + + public final String iv; + + private byte[] result; + + public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, byte[] scratchSpace, String iv) { + super(dataSource, dataSpec, C.DATA_TYPE_DRM, trackFormat, trackSelectionReason, + trackSelectionData, scratchSpace); + this.iv = iv; + } + + @Override + protected void consume(byte[] data, int limit) throws IOException { + result = Arrays.copyOf(data, limit); + } + + public byte[] getResult() { + return result; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsDataSourceFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsDataSourceFactory.java new file mode 100644 index 0000000..dbe4cb8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsDataSourceFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; + +/** + * Creates {@link DataSource}s for HLS playlists, encryption and media chunks. + */ +public interface HlsDataSourceFactory { + + /** + * Creates a {@link DataSource} for the given data type. + * + * @param dataType The data type for which the {@link DataSource} will be used. One of {@link C} + * {@code .DATA_TYPE_*} constants. + * @return A {@link DataSource} for the given data type. + */ + DataSource createDataSource(int dataType); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsManifest.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsManifest.java new file mode 100644 index 0000000..8de7781 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsManifest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMediaPlaylist; + +/** + * Holds a master playlist along with a snapshot of one of its media playlists. + */ +public final class HlsManifest { + + /** + * The master playlist of an HLS stream. + */ + public final HlsMasterPlaylist masterPlaylist; + /** + * A snapshot of a media playlist referred to by {@link #masterPlaylist}. + */ + public final HlsMediaPlaylist mediaPlaylist; + + /** + * @param masterPlaylist The master playlist. + * @param mediaPlaylist The media playlist. + */ + HlsManifest(HlsMasterPlaylist masterPlaylist, HlsMediaPlaylist mediaPlaylist) { + this.masterPlaylist = masterPlaylist; + this.mediaPlaylist = mediaPlaylist; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaChunk.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaChunk.java new file mode 100644 index 0000000..263aeac --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaChunk.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp3.Mp3Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.Ac3Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.AdtsExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ts.TsExtractor; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.Metadata; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.Id3Decoder; +import com.tangxiaolv.telegramgallery.exoplayer2.metadata.id3.PrivFrame; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.MediaChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * An HLS {@link MediaChunk}. + */ +/* package */ final class HlsMediaChunk extends MediaChunk { + + private static final AtomicInteger UID_SOURCE = new AtomicInteger(); + + private static final String PRIV_TIMESTAMP_FRAME_OWNER = + "com.apple.streaming.transportStreamTimestamp"; + + private static final String AAC_FILE_EXTENSION = ".aac"; + private static final String AC3_FILE_EXTENSION = ".ac3"; + private static final String EC3_FILE_EXTENSION = ".ec3"; + private static final String MP3_FILE_EXTENSION = ".mp3"; + private static final String MP4_FILE_EXTENSION = ".mp4"; + private static final String M4_FILE_EXTENSION_PREFIX = ".m4"; + private static final String VTT_FILE_EXTENSION = ".vtt"; + private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; + + /** + * A unique identifier for the chunk. + */ + public final int uid; + + /** + * The discontinuity sequence number of the chunk. + */ + public final int discontinuitySequenceNumber; + + /** + * The url of the playlist from which this chunk was obtained. + */ + public final HlsUrl hlsUrl; + + private final DataSource initDataSource; + private final DataSpec initDataSpec; + private final boolean isEncrypted; + private final boolean isMasterTimestampSource; + private final TimestampAdjuster timestampAdjuster; + private final String lastPathSegment; + private final Extractor previousExtractor; + private final boolean shouldSpliceIn; + private final boolean needNewExtractor; + private final List muxedCaptionFormats; + + private final boolean isPackedAudio; + private final Id3Decoder id3Decoder; + private final ParsableByteArray id3Data; + + private Extractor extractor; + private int initSegmentBytesLoaded; + private int bytesLoaded; + private boolean initLoadCompleted; + private HlsSampleStreamWrapper extractorOutput; + private volatile boolean loadCanceled; + private volatile boolean loadCompleted; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param initDataSpec Defines the initialization data to be fed to new extractors. May be null. + * @param hlsUrl The url of the playlist from which this chunk was obtained. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the chunk in microseconds. + * @param endTimeUs The end time of the chunk in microseconds. + * @param chunkIndex The media sequence number of the chunk. + * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk. + * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. + * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. + * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. + * @param encryptionKey For AES encryption chunks, the encryption key. + * @param encryptionIv For AES encryption chunks, the encryption initialization vector. + */ + public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec, + HlsUrl hlsUrl, List muxedCaptionFormats, int trackSelectionReason, + Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex, + int discontinuitySequenceNumber, boolean isMasterTimestampSource, + TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, byte[] encryptionKey, + byte[] encryptionIv) { + super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, + trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex); + this.discontinuitySequenceNumber = discontinuitySequenceNumber; + this.initDataSpec = initDataSpec; + this.hlsUrl = hlsUrl; + this.muxedCaptionFormats = muxedCaptionFormats; + this.isMasterTimestampSource = isMasterTimestampSource; + this.timestampAdjuster = timestampAdjuster; + // Note: this.dataSource and dataSource may be different. + this.isEncrypted = this.dataSource instanceof Aes128DataSource; + lastPathSegment = dataSpec.uri.getLastPathSegment(); + isPackedAudio = lastPathSegment.endsWith(AAC_FILE_EXTENSION) + || lastPathSegment.endsWith(AC3_FILE_EXTENSION) + || lastPathSegment.endsWith(EC3_FILE_EXTENSION) + || lastPathSegment.endsWith(MP3_FILE_EXTENSION); + if (previousChunk != null) { + id3Decoder = previousChunk.id3Decoder; + id3Data = previousChunk.id3Data; + previousExtractor = previousChunk.extractor; + shouldSpliceIn = previousChunk.hlsUrl != hlsUrl; + needNewExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber + || shouldSpliceIn; + } else { + id3Decoder = isPackedAudio ? new Id3Decoder() : null; + id3Data = isPackedAudio ? new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH) : null; + previousExtractor = null; + shouldSpliceIn = false; + needNewExtractor = true; + } + initDataSource = dataSource; + uid = UID_SOURCE.getAndIncrement(); + } + + /** + * Initializes the chunk for loading, setting the {@link HlsSampleStreamWrapper} that will receive + * samples as they are loaded. + * + * @param output The output that will receive the loaded samples. + */ + public void init(HlsSampleStreamWrapper output) { + extractorOutput = output; + output.init(uid, shouldSpliceIn); + } + + @Override + public boolean isLoadCompleted() { + return loadCompleted; + } + + @Override + public long bytesLoaded() { + return bytesLoaded; + } + + // Loadable implementation + + @Override + public void cancelLoad() { + loadCanceled = true; + } + + @Override + public boolean isLoadCanceled() { + return loadCanceled; + } + + @Override + public void load() throws IOException, InterruptedException { + if (extractor == null && !isPackedAudio) { + // See HLS spec, version 20, Section 3.4 for more information on packed audio extraction. + extractor = createExtractor(); + } + maybeLoadInitData(); + if (!loadCanceled) { + loadMedia(); + } + } + + // Internal loading methods. + + private void maybeLoadInitData() throws IOException, InterruptedException { + if (previousExtractor == extractor || initLoadCompleted || initDataSpec == null) { + // According to spec, for packed audio, initDataSpec is expected to be null. + return; + } + DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded); + try { + ExtractorInput input = new DefaultExtractorInput(initDataSource, + initSegmentDataSpec.absoluteStreamPosition, initDataSource.open(initSegmentDataSpec)); + try { + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractor.read(input, null); + } + } finally { + initSegmentBytesLoaded = (int) (input.getPosition() - initDataSpec.absoluteStreamPosition); + } + } finally { + Util.closeQuietly(dataSource); + } + initLoadCompleted = true; + } + + private void loadMedia() throws IOException, InterruptedException { + // If we previously fed part of this chunk to the extractor, we need to skip it this time. For + // encrypted content we need to skip the data by reading it through the source, so as to ensure + // correct decryption of the remainder of the chunk. For clear content, we can request the + // remainder of the chunk directly. + DataSpec loadDataSpec; + boolean skipLoadedBytes; + if (isEncrypted) { + loadDataSpec = dataSpec; + skipLoadedBytes = bytesLoaded != 0; + } else { + loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + skipLoadedBytes = false; + } + if (!isMasterTimestampSource) { + timestampAdjuster.waitUntilInitialized(); + } else if (timestampAdjuster.getFirstSampleTimestampUs() == TimestampAdjuster.DO_NOT_OFFSET) { + // We're the master and we haven't set the desired first sample timestamp yet. + timestampAdjuster.setFirstSampleTimestampUs(startTimeUs); + } + try { + ExtractorInput input = new DefaultExtractorInput(dataSource, + loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); + if (extractor == null) { + // Media segment format is packed audio. + long id3Timestamp = peekId3PrivTimestamp(input); + extractor = buildPackedAudioExtractor(id3Timestamp != C.TIME_UNSET + ? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs); + } + if (skipLoadedBytes) { + input.skipFully(bytesLoaded); + } + try { + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractor.read(input, null); + } + } finally { + bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); + } + } finally { + Util.closeQuietly(dataSource); + } + loadCompleted = true; + } + + /** + * Peek the presentation timestamp of the first sample in the chunk from an ID3 PRIV as defined + * in the HLS spec, version 20, Section 3.4. Returns {@link C#TIME_UNSET} if the frame is not + * found. This method only modifies the peek position. + * + * @param input The {@link ExtractorInput} to obtain the PRIV frame from. + * @return The parsed, adjusted timestamp in microseconds + * @throws IOException If an error occurred peeking from the input. + * @throws InterruptedException If the thread was interrupted. + */ + private long peekId3PrivTimestamp(ExtractorInput input) throws IOException, InterruptedException { + input.resetPeekPosition(); + if (!input.peekFully(id3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH, true)) { + return C.TIME_UNSET; + } + id3Data.reset(Id3Decoder.ID3_HEADER_LENGTH); + int id = id3Data.readUnsignedInt24(); + if (id != Id3Decoder.ID3_TAG) { + return C.TIME_UNSET; + } + id3Data.skipBytes(3); // version(2), flags(1). + int id3Size = id3Data.readSynchSafeInt(); + int requiredCapacity = id3Size + Id3Decoder.ID3_HEADER_LENGTH; + if (requiredCapacity > id3Data.capacity()) { + byte[] data = id3Data.data; + id3Data.reset(requiredCapacity); + System.arraycopy(data, 0, id3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH); + } + if (!input.peekFully(id3Data.data, Id3Decoder.ID3_HEADER_LENGTH, id3Size, true)) { + return C.TIME_UNSET; + } + Metadata metadata = id3Decoder.decode(id3Data.data, id3Size); + if (metadata == null) { + return C.TIME_UNSET; + } + int metadataLength = metadata.length(); + for (int i = 0; i < metadataLength; i++) { + Metadata.Entry frame = metadata.get(i); + if (frame instanceof PrivFrame) { + PrivFrame privFrame = (PrivFrame) frame; + if (PRIV_TIMESTAMP_FRAME_OWNER.equals(privFrame.owner)) { + System.arraycopy(privFrame.privateData, 0, id3Data.data, 0, 8 /* timestamp size */); + id3Data.reset(8); + return id3Data.readLong(); + } + } + } + return C.TIME_UNSET; + } + + // Internal factory methods. + + /** + * If the content is encrypted, returns an {@link Aes128DataSource} that wraps the original in + * order to decrypt the loaded data. Else returns the original. + */ + private static DataSource buildDataSource(DataSource dataSource, byte[] encryptionKey, + byte[] encryptionIv) { + if (encryptionKey == null || encryptionIv == null) { + return dataSource; + } + return new Aes128DataSource(dataSource, encryptionKey, encryptionIv); + } + + private Extractor createExtractor() { + // Select the extractor that will read the chunk. + Extractor extractor; + boolean usingNewExtractor = true; + if (MimeTypes.TEXT_VTT.equals(hlsUrl.format.sampleMimeType) + || lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION) + || lastPathSegment.endsWith(VTT_FILE_EXTENSION)) { + extractor = new WebvttExtractor(trackFormat.language, timestampAdjuster); + } else if (!needNewExtractor) { + // Only reuse TS and fMP4 extractors. + usingNewExtractor = false; + extractor = previousExtractor; + } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION) + || lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4)) { + extractor = new FragmentedMp4Extractor(0, timestampAdjuster); + } else { + // MPEG-2 TS segments, but we need a new extractor. + // This flag ensures the change of pid between streams does not affect the sample queues. + @DefaultTsPayloadReaderFactory.Flags + int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM; + if (!muxedCaptionFormats.isEmpty()) { + // The playlist declares closed caption renditions, we should ignore descriptors. + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS; + } + String codecs = trackFormat.codecs; + if (!TextUtils.isEmpty(codecs)) { + // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really + // exist. If we know from the codec attribute that they don't exist, then we can + // explicitly ignore them even if they're declared. + if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; + } + if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; + } + } + extractor = new TsExtractor(TsExtractor.MODE_HLS, timestampAdjuster, + new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats)); + } + if (usingNewExtractor) { + extractor.init(extractorOutput); + } + return extractor; + } + + private Extractor buildPackedAudioExtractor(long startTimeUs) { + Extractor extractor; + if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) { + extractor = new AdtsExtractor(startTimeUs); + } else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION) + || lastPathSegment.endsWith(EC3_FILE_EXTENSION)) { + extractor = new Ac3Extractor(startTimeUs); + } else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) { + extractor = new Mp3Extractor(0, startTimeUs); + } else { + throw new IllegalArgumentException("Unkown extension for audio file: " + lastPathSegment); + } + extractor.init(extractorOutput); + return extractor; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaPeriod.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaPeriod.java new file mode 100644 index 0000000..d2123fc --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaPeriod.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import android.os.Handler; +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.CompositeSequenceableLoader; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaPeriod; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsPlaylistTracker; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; + +/** + * A {@link MediaPeriod} that loads an HLS stream. + */ +public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback, + HlsPlaylistTracker.PlaylistEventListener { + + private final HlsPlaylistTracker playlistTracker; + private final HlsDataSourceFactory dataSourceFactory; + private final int minLoadableRetryCount; + private final EventDispatcher eventDispatcher; + private final Allocator allocator; + private final IdentityHashMap streamWrapperIndices; + private final TimestampAdjusterProvider timestampAdjusterProvider; + private final Handler continueLoadingHandler; + private final long preparePositionUs; + + private Callback callback; + private int pendingPrepareCount; + private boolean seenFirstTrackSelection; + private TrackGroupArray trackGroups; + private HlsSampleStreamWrapper[] sampleStreamWrappers; + private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; + private CompositeSequenceableLoader sequenceableLoader; + + public HlsMediaPeriod(HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory, + int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator, + long positionUs) { + this.playlistTracker = playlistTracker; + this.dataSourceFactory = dataSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventDispatcher = eventDispatcher; + this.allocator = allocator; + streamWrapperIndices = new IdentityHashMap<>(); + timestampAdjusterProvider = new TimestampAdjusterProvider(); + continueLoadingHandler = new Handler(); + preparePositionUs = positionUs; + } + + public void release() { + playlistTracker.removeListener(this); + continueLoadingHandler.removeCallbacksAndMessages(null); + if (sampleStreamWrappers != null) { + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + sampleStreamWrapper.release(); + } + } + } + + @Override + public void prepare(Callback callback) { + playlistTracker.addListener(this); + this.callback = callback; + buildAndPrepareSampleStreamWrappers(); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + if (sampleStreamWrappers != null) { + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + sampleStreamWrapper.maybeThrowPrepareError(); + } + } + } + + @Override + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + // Map each selection and stream onto a child period index. + int[] streamChildIndices = new int[selections.length]; + int[] selectionChildIndices = new int[selections.length]; + for (int i = 0; i < selections.length; i++) { + streamChildIndices[i] = streams[i] == null ? C.INDEX_UNSET + : streamWrapperIndices.get(streams[i]); + selectionChildIndices[i] = C.INDEX_UNSET; + if (selections[i] != null) { + TrackGroup trackGroup = selections[i].getTrackGroup(); + for (int j = 0; j < sampleStreamWrappers.length; j++) { + if (sampleStreamWrappers[j].getTrackGroups().indexOf(trackGroup) != C.INDEX_UNSET) { + selectionChildIndices[i] = j; + break; + } + } + } + } + boolean selectedNewTracks = false; + streamWrapperIndices.clear(); + // Select tracks for each child, copying the resulting streams back into a new streams array. + SampleStream[] newStreams = new SampleStream[selections.length]; + SampleStream[] childStreams = new SampleStream[selections.length]; + TrackSelection[] childSelections = new TrackSelection[selections.length]; + ArrayList enabledSampleStreamWrapperList = new ArrayList<>( + sampleStreamWrappers.length); + for (int i = 0; i < sampleStreamWrappers.length; i++) { + for (int j = 0; j < selections.length; j++) { + childStreams[j] = streamChildIndices[j] == i ? streams[j] : null; + childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null; + } + selectedNewTracks |= sampleStreamWrappers[i].selectTracks(childSelections, + mayRetainStreamFlags, childStreams, streamResetFlags, !seenFirstTrackSelection); + boolean wrapperEnabled = false; + for (int j = 0; j < selections.length; j++) { + if (selectionChildIndices[j] == i) { + // Assert that the child provided a stream for the selection. + Assertions.checkState(childStreams[j] != null); + newStreams[j] = childStreams[j]; + wrapperEnabled = true; + streamWrapperIndices.put(childStreams[j], i); + } else if (streamChildIndices[j] == i) { + // Assert that the child cleared any previous stream. + Assertions.checkState(childStreams[j] == null); + } + } + if (wrapperEnabled) { + enabledSampleStreamWrapperList.add(sampleStreamWrappers[i]); + } + } + // Copy the new streams back into the streams array. + System.arraycopy(newStreams, 0, streams, 0, newStreams.length); + // Update the local state. + enabledSampleStreamWrappers = new HlsSampleStreamWrapper[enabledSampleStreamWrapperList.size()]; + enabledSampleStreamWrapperList.toArray(enabledSampleStreamWrappers); + + // The first enabled sample stream wrapper is responsible for intializing the timestamp + // adjuster. This way, if present, variants are responsible. Otherwise, audio renditions are. + // If only subtitles are present, then text renditions are used for timestamp adjustment + // initialization. + if (enabledSampleStreamWrappers.length > 0) { + enabledSampleStreamWrappers[0].setIsTimestampMaster(true); + for (int i = 1; i < enabledSampleStreamWrappers.length; i++) { + enabledSampleStreamWrappers[i].setIsTimestampMaster(false); + } + } + + sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers); + if (seenFirstTrackSelection && selectedNewTracks) { + seekToUs(positionUs); + // We'll need to reset renderers consuming from all streams due to the seek. + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null) { + streamResetFlags[i] = true; + } + } + } + seenFirstTrackSelection = true; + return positionUs; + } + + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + + @Override + public boolean continueLoading(long positionUs) { + return sequenceableLoader.continueLoading(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return sequenceableLoader.getNextLoadPositionUs(); + } + + @Override + public long readDiscontinuity() { + return C.TIME_UNSET; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { + long rendererBufferedPositionUs = sampleStreamWrapper.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public long seekToUs(long positionUs) { + timestampAdjusterProvider.reset(); + for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { + sampleStreamWrapper.seekTo(positionUs); + } + return positionUs; + } + + // HlsSampleStreamWrapper.Callback implementation. + + @Override + public void onPrepared() { + if (--pendingPrepareCount > 0) { + return; + } + + int totalTrackGroupCount = 0; + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + totalTrackGroupCount += sampleStreamWrapper.getTrackGroups().length; + } + TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; + int trackGroupIndex = 0; + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + int wrapperTrackGroupCount = sampleStreamWrapper.getTrackGroups().length; + for (int j = 0; j < wrapperTrackGroupCount; j++) { + trackGroupArray[trackGroupIndex++] = sampleStreamWrapper.getTrackGroups().get(j); + } + } + trackGroups = new TrackGroupArray(trackGroupArray); + callback.onPrepared(this); + } + + @Override + public void onPlaylistRefreshRequired(HlsUrl url) { + playlistTracker.refreshPlaylist(url); + } + + @Override + public void onContinueLoadingRequested(HlsSampleStreamWrapper sampleStreamWrapper) { + if (trackGroups == null) { + // Still preparing. + return; + } + callback.onContinueLoadingRequested(this); + } + + // PlaylistListener implementation. + + @Override + public void onPlaylistChanged() { + continuePreparingOrLoading(); + } + + @Override + public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) { + for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) { + streamWrapper.onPlaylistBlacklisted(url, blacklistMs); + } + continuePreparingOrLoading(); + } + + // Internal methods. + + private void buildAndPrepareSampleStreamWrappers() { + HlsMasterPlaylist masterPlaylist = playlistTracker.getMasterPlaylist(); + // Build the default stream wrapper. + List selectedVariants = new ArrayList<>(masterPlaylist.variants); + ArrayList definiteVideoVariants = new ArrayList<>(); + ArrayList definiteAudioOnlyVariants = new ArrayList<>(); + for (int i = 0; i < selectedVariants.size(); i++) { + HlsUrl variant = selectedVariants.get(i); + if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) { + definiteVideoVariants.add(variant); + } else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) { + definiteAudioOnlyVariants.add(variant); + } + } + if (!definiteVideoVariants.isEmpty()) { + // We've identified some variants as definitely containing video. Assume variants within the + // master playlist are marked consistently, and hence that we have the full set. Filter out + // any other variants, which are likely to be audio only. + selectedVariants = definiteVideoVariants; + } else if (definiteAudioOnlyVariants.size() < selectedVariants.size()) { + // We've identified some variants, but not all, as being audio only. Filter them out to leave + // the remaining variants, which are likely to contain video. + selectedVariants.removeAll(definiteAudioOnlyVariants); + } else { + // Leave the enabled variants unchanged. They're likely either all video or all audio. + } + List audioRenditions = masterPlaylist.audios; + List subtitleRenditions = masterPlaylist.subtitles; + sampleStreamWrappers = new HlsSampleStreamWrapper[1 /* variants */ + audioRenditions.size() + + subtitleRenditions.size()]; + int currentWrapperIndex = 0; + pendingPrepareCount = sampleStreamWrappers.length; + + Assertions.checkArgument(!selectedVariants.isEmpty()); + HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; + selectedVariants.toArray(variants); + HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, + variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormats); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; + sampleStreamWrapper.setIsTimestampMaster(true); + sampleStreamWrapper.continuePreparing(); + + // TODO: Build video stream wrappers here. + + // Build audio stream wrappers. + for (int i = 0; i < audioRenditions.size(); i++) { + sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, + new HlsUrl[] {audioRenditions.get(i)}, null, Collections.emptyList()); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; + sampleStreamWrapper.continuePreparing(); + } + + // Build subtitle stream wrappers. + for (int i = 0; i < subtitleRenditions.size(); i++) { + HlsUrl url = subtitleRenditions.get(i); + sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, new HlsUrl[] {url}, null, + Collections.emptyList()); + sampleStreamWrapper.prepareSingleTrack(url.format); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; + } + } + + private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants, + Format muxedAudioFormat, List muxedCaptionFormats) { + HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants, + dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats); + return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, + preparePositionUs, muxedAudioFormat, minLoadableRetryCount, eventDispatcher); + } + + private void continuePreparingOrLoading() { + if (trackGroups != null) { + callback.onContinueLoadingRequested(this); + } else { + // Some of the wrappers were waiting for their media playlist to prepare. + for (HlsSampleStreamWrapper wrapper : sampleStreamWrappers) { + wrapper.continuePreparing(); + } + } + } + + private static boolean variantHasExplicitCodecWithPrefix(HlsUrl variant, String prefix) { + String codecs = variant.format.codecs; + if (TextUtils.isEmpty(codecs)) { + return false; + } + String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)"); + for (String codec : codecArray) { + if (codec.startsWith(prefix)) { + return true; + } + } + return false; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaSource.java new file mode 100644 index 0000000..898b4e6 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsMediaSource.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import android.net.Uri; +import android.os.Handler; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaPeriod; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SinglePeriodTimeline; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMediaPlaylist; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsPlaylistTracker; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.List; + +/** + * An HLS {@link MediaSource}. + */ +public final class HlsMediaSource implements MediaSource, + HlsPlaylistTracker.PrimaryPlaylistListener { + + /** + * The default minimum number of times to retry loading data prior to failing. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + + private final Uri manifestUri; + private final HlsDataSourceFactory dataSourceFactory; + private final int minLoadableRetryCount; + private final EventDispatcher eventDispatcher; + + private HlsPlaylistTracker playlistTracker; + private Listener sourceListener; + + public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler, + eventListener); + } + + public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, + int minLoadableRetryCount, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory), minLoadableRetryCount, + eventHandler, eventListener); + } + + public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory, + int minLoadableRetryCount, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this.manifestUri = manifestUri; + this.dataSourceFactory = dataSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + } + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + Assertions.checkState(playlistTracker == null); + playlistTracker = new HlsPlaylistTracker(manifestUri, dataSourceFactory, eventDispatcher, + minLoadableRetryCount, this); + sourceListener = listener; + playlistTracker.start(); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + playlistTracker.maybeThrowPlaylistRefreshError(); + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + Assertions.checkArgument(index == 0); + return new HlsMediaPeriod(playlistTracker, dataSourceFactory, minLoadableRetryCount, + eventDispatcher, allocator, positionUs); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + ((HlsMediaPeriod) mediaPeriod).release(); + } + + @Override + public void releaseSource() { + if (playlistTracker != null) { + playlistTracker.release(); + playlistTracker = null; + } + sourceListener = null; + } + + @Override + public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) { + SinglePeriodTimeline timeline; + long windowDefaultStartPositionUs = playlist.startOffsetUs; + if (playlistTracker.isLive()) { + long periodDurationUs = playlist.hasEndTag ? (playlist.startTimeUs + playlist.durationUs) + : C.TIME_UNSET; + List segments = playlist.segments; + if (windowDefaultStartPositionUs == C.TIME_UNSET) { + windowDefaultStartPositionUs = segments.isEmpty() ? 0 + : segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs; + } + timeline = new SinglePeriodTimeline(periodDurationUs, playlist.durationUs, + playlist.startTimeUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag); + } else /* not live */ { + if (windowDefaultStartPositionUs == C.TIME_UNSET) { + windowDefaultStartPositionUs = 0; + } + timeline = new SinglePeriodTimeline(playlist.startTimeUs + playlist.durationUs, + playlist.durationUs, playlist.startTimeUs, windowDefaultStartPositionUs, true, false); + } + sourceListener.onSourceInfoRefreshed(timeline, + new HlsManifest(playlistTracker.getMasterPlaylist(), playlist)); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsSampleStream.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsSampleStream.java new file mode 100644 index 0000000..b223eb6 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsSampleStream.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import java.io.IOException; + +/** + * {@link SampleStream} for a particular track group in HLS. + */ +/* package */ final class HlsSampleStream implements SampleStream { + + public final int group; + + private final HlsSampleStreamWrapper sampleStreamWrapper; + + public HlsSampleStream(HlsSampleStreamWrapper sampleStreamWrapper, int group) { + this.sampleStreamWrapper = sampleStreamWrapper; + this.group = group; + } + + @Override + public boolean isReady() { + return sampleStreamWrapper.isReady(group); + } + + @Override + public void maybeThrowError() throws IOException { + sampleStreamWrapper.maybeThrowError(); + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) { + return sampleStreamWrapper.readData(group, formatHolder, buffer, requireFormat); + } + + @Override + public void skipData(long positionUs) { + sampleStreamWrapper.skipData(group, positionUs); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsSampleStreamWrapper.java new file mode 100644 index 0000000..c2b6d07 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import android.os.Handler; +import android.text.TextUtils; +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultTrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.DefaultTrackOutput.UpstreamFormatChangedListener; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SequenceableLoader; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.Chunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import java.io.IOException; +import java.util.LinkedList; + +/** + * Loads {@link HlsMediaChunk}s obtained from a {@link HlsChunkSource}, and provides + * {@link SampleStream}s from which the loaded media can be consumed. + */ +/* package */ final class HlsSampleStreamWrapper implements Loader.Callback, + SequenceableLoader, ExtractorOutput, UpstreamFormatChangedListener { + + /** + * A callback to be notified of events. + */ + public interface Callback extends SequenceableLoader.Callback { + + /** + * Called when the wrapper has been prepared. + */ + void onPrepared(); + + /** + * Called to schedule a {@link #continueLoading(long)} call when the playlist referred by the + * given url changes. + */ + void onPlaylistRefreshRequired(HlsMasterPlaylist.HlsUrl playlistUrl); + + } + + private static final int PRIMARY_TYPE_NONE = 0; + private static final int PRIMARY_TYPE_TEXT = 1; + private static final int PRIMARY_TYPE_AUDIO = 2; + private static final int PRIMARY_TYPE_VIDEO = 3; + + private final int trackType; + private final Callback callback; + private final HlsChunkSource chunkSource; + private final Allocator allocator; + private final Format muxedAudioFormat; + private final int minLoadableRetryCount; + private final Loader loader; + private final EventDispatcher eventDispatcher; + private final HlsChunkSource.HlsChunkHolder nextChunkHolder; + private final SparseArray sampleQueues; + private final LinkedList mediaChunks; + private final Runnable maybeFinishPrepareRunnable; + private final Handler handler; + + private boolean sampleQueuesBuilt; + private boolean prepared; + private int enabledTrackCount; + private Format downstreamTrackFormat; + private int upstreamChunkUid; + private boolean released; + + // Tracks are complicated in HLS. See documentation of buildTracks for details. + // Indexed by track (as exposed by this source). + private TrackGroupArray trackGroups; + private int primaryTrackGroupIndex; + // Indexed by group. + private boolean[] groupEnabledStates; + + private long lastSeekPositionUs; + private long pendingResetPositionUs; + + private boolean loadingFinished; + + /** + * @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @param callback A callback for the wrapper. + * @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained. + * @param allocator An {@link Allocator} from which to obtain media buffer allocations. + * @param positionUs The position from which to start loading media. + * @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist. + * @param minLoadableRetryCount The minimum number of times that the source should retry a load + * before propagating an error. + * @param eventDispatcher A dispatcher to notify of events. + */ + public HlsSampleStreamWrapper(int trackType, Callback callback, HlsChunkSource chunkSource, + Allocator allocator, long positionUs, Format muxedAudioFormat, int minLoadableRetryCount, + EventDispatcher eventDispatcher) { + this.trackType = trackType; + this.callback = callback; + this.chunkSource = chunkSource; + this.allocator = allocator; + this.muxedAudioFormat = muxedAudioFormat; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventDispatcher = eventDispatcher; + loader = new Loader("Loader:HlsSampleStreamWrapper"); + nextChunkHolder = new HlsChunkSource.HlsChunkHolder(); + sampleQueues = new SparseArray<>(); + mediaChunks = new LinkedList<>(); + maybeFinishPrepareRunnable = new Runnable() { + @Override + public void run() { + maybeFinishPrepare(); + } + }; + handler = new Handler(); + lastSeekPositionUs = positionUs; + pendingResetPositionUs = positionUs; + } + + public void continuePreparing() { + if (!prepared) { + continueLoading(lastSeekPositionUs); + } + } + + /** + * Prepares a sample stream wrapper for which the master playlist provides enough information to + * prepare. + */ + public void prepareSingleTrack(Format format) { + track(0, C.TRACK_TYPE_UNKNOWN).format(format); + sampleQueuesBuilt = true; + maybeFinishPrepare(); + } + + public void maybeThrowPrepareError() throws IOException { + maybeThrowError(); + } + + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + public boolean selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, boolean isFirstTrackSelection) { + Assertions.checkState(prepared); + // Disable old tracks. + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { + int group = ((HlsSampleStream) streams[i]).group; + setTrackGroupEnabledState(group, false); + sampleQueues.valueAt(group).disable(); + streams[i] = null; + } + } + // Enable new tracks. + TrackSelection primaryTrackSelection = null; + boolean selectedNewTracks = false; + for (int i = 0; i < selections.length; i++) { + if (streams[i] == null && selections[i] != null) { + TrackSelection selection = selections[i]; + int group = trackGroups.indexOf(selection.getTrackGroup()); + setTrackGroupEnabledState(group, true); + if (group == primaryTrackGroupIndex) { + primaryTrackSelection = selection; + chunkSource.selectTracks(selection); + } + streams[i] = new HlsSampleStream(this, group); + streamResetFlags[i] = true; + selectedNewTracks = true; + } + } + if (isFirstTrackSelection) { + // At the time of the first track selection all queues will be enabled, so we need to disable + // any that are no longer required. + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + if (!groupEnabledStates[i]) { + sampleQueues.valueAt(i).disable(); + } + } + if (primaryTrackSelection != null && !mediaChunks.isEmpty()) { + primaryTrackSelection.updateSelectedTrack(0); + int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat); + if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) { + // The loaded preparation chunk does match the selection. We discard it. + seekTo(lastSeekPositionUs); + } + } + } + // Cancel requests if necessary. + if (enabledTrackCount == 0) { + chunkSource.reset(); + downstreamTrackFormat = null; + mediaChunks.clear(); + if (loader.isLoading()) { + loader.cancelLoading(); + } + } + return selectedNewTracks; + } + + public void seekTo(long positionUs) { + lastSeekPositionUs = positionUs; + pendingResetPositionUs = positionUs; + loadingFinished = false; + mediaChunks.clear(); + if (loader.isLoading()) { + loader.cancelLoading(); + } else { + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + sampleQueues.valueAt(i).reset(groupEnabledStates[i]); + } + } + } + + public long getBufferedPositionUs() { + if (loadingFinished) { + return C.TIME_END_OF_SOURCE; + } else if (isPendingReset()) { + return pendingResetPositionUs; + } else { + long bufferedPositionUs = lastSeekPositionUs; + HlsMediaChunk lastMediaChunk = mediaChunks.getLast(); + HlsMediaChunk lastCompletedMediaChunk = lastMediaChunk.isLoadCompleted() ? lastMediaChunk + : mediaChunks.size() > 1 ? mediaChunks.get(mediaChunks.size() - 2) : null; + if (lastCompletedMediaChunk != null) { + bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); + } + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + bufferedPositionUs = Math.max(bufferedPositionUs, + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + } + return bufferedPositionUs; + } + } + + public void release() { + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + sampleQueues.valueAt(i).disable(); + } + loader.release(); + handler.removeCallbacksAndMessages(null); + released = true; + } + + public void setIsTimestampMaster(boolean isTimestampMaster) { + chunkSource.setIsTimestampMaster(isTimestampMaster); + } + + public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) { + chunkSource.onPlaylistBlacklisted(url, blacklistMs); + } + + // SampleStream implementation. + + /* package */ boolean isReady(int group) { + return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(group).isEmpty()); + } + + /* package */ void maybeThrowError() throws IOException { + loader.maybeThrowError(); + chunkSource.maybeThrowError(); + } + + /* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer, + boolean requireFormat) { + if (isPendingReset()) { + return C.RESULT_NOTHING_READ; + } + + while (mediaChunks.size() > 1 && finishedReadingChunk(mediaChunks.getFirst())) { + mediaChunks.removeFirst(); + } + HlsMediaChunk currentChunk = mediaChunks.getFirst(); + Format trackFormat = currentChunk.trackFormat; + if (!trackFormat.equals(downstreamTrackFormat)) { + eventDispatcher.downstreamFormatChanged(trackType, trackFormat, + currentChunk.trackSelectionReason, currentChunk.trackSelectionData, + currentChunk.startTimeUs); + } + downstreamTrackFormat = trackFormat; + + return sampleQueues.valueAt(group).readData(formatHolder, buffer, requireFormat, + loadingFinished, lastSeekPositionUs); + } + + /* package */ void skipData(int group, long positionUs) { + DefaultTrackOutput sampleQueue = sampleQueues.valueAt(group); + if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { + sampleQueue.skipAll(); + } else { + sampleQueue.skipToKeyframeBefore(positionUs, true); + } + } + + private boolean finishedReadingChunk(HlsMediaChunk chunk) { + int chunkUid = chunk.uid; + for (int i = 0; i < sampleQueues.size(); i++) { + if (groupEnabledStates[i] && sampleQueues.valueAt(i).peekSourceId() == chunkUid) { + return false; + } + } + return true; + } + + // SequenceableLoader implementation + + @Override + public boolean continueLoading(long positionUs) { + if (loadingFinished || loader.isLoading()) { + return false; + } + + chunkSource.getNextChunk(mediaChunks.isEmpty() ? null : mediaChunks.getLast(), + pendingResetPositionUs != C.TIME_UNSET ? pendingResetPositionUs : positionUs, + nextChunkHolder); + boolean endOfStream = nextChunkHolder.endOfStream; + Chunk loadable = nextChunkHolder.chunk; + HlsMasterPlaylist.HlsUrl playlistToLoad = nextChunkHolder.playlist; + nextChunkHolder.clear(); + + if (endOfStream) { + loadingFinished = true; + return true; + } + + if (loadable == null) { + if (playlistToLoad != null) { + callback.onPlaylistRefreshRequired(playlistToLoad); + } + return false; + } + + if (isMediaChunk(loadable)) { + pendingResetPositionUs = C.TIME_UNSET; + HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable; + mediaChunk.init(this); + mediaChunks.add(mediaChunk); + } + long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs); + return true; + } + + @Override + public long getNextLoadPositionUs() { + if (isPendingReset()) { + return pendingResetPositionUs; + } else { + return loadingFinished ? C.TIME_END_OF_SOURCE : mediaChunks.getLast().endTimeUs; + } + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) { + chunkSource.onChunkLoadCompleted(loadable); + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); + if (!prepared) { + continueLoading(lastSeekPositionUs); + } else { + callback.onContinueLoadingRequested(this); + } + } + + @Override + public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, + boolean released) { + eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); + if (!released) { + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + sampleQueues.valueAt(i).reset(groupEnabledStates[i]); + } + callback.onContinueLoadingRequested(this); + } + } + + @Override + public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, + IOException error) { + long bytesLoaded = loadable.bytesLoaded(); + boolean isMediaChunk = isMediaChunk(loadable); + boolean cancelable = !isMediaChunk || bytesLoaded == 0; + boolean canceled = false; + if (chunkSource.onChunkLoadError(loadable, cancelable, error)) { + if (isMediaChunk) { + HlsMediaChunk removed = mediaChunks.removeLast(); + Assertions.checkState(removed == loadable); + if (mediaChunks.isEmpty()) { + pendingResetPositionUs = lastSeekPositionUs; + } + } + canceled = true; + } + eventDispatcher.loadError(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded(), error, + canceled); + if (canceled) { + if (!prepared) { + continueLoading(lastSeekPositionUs); + } else { + callback.onContinueLoadingRequested(this); + } + return Loader.DONT_RETRY; + } else { + return Loader.RETRY; + } + } + + // Called by the consuming thread, but only when there is no loading thread. + + /** + * Initializes the wrapper for loading a chunk. + * + * @param chunkUid The chunk's uid. + * @param shouldSpliceIn Whether the samples parsed from the chunk should be spliced into any + * samples already queued to the wrapper. + */ + public void init(int chunkUid, boolean shouldSpliceIn) { + upstreamChunkUid = chunkUid; + for (int i = 0; i < sampleQueues.size(); i++) { + sampleQueues.valueAt(i).sourceId(chunkUid); + } + if (shouldSpliceIn) { + for (int i = 0; i < sampleQueues.size(); i++) { + sampleQueues.valueAt(i).splice(); + } + } + } + + // ExtractorOutput implementation. Called by the loading thread. + + @Override + public DefaultTrackOutput track(int id, int type) { + if (sampleQueues.indexOfKey(id) >= 0) { + return sampleQueues.get(id); + } + DefaultTrackOutput trackOutput = new DefaultTrackOutput(allocator); + trackOutput.setUpstreamFormatChangeListener(this); + trackOutput.sourceId(upstreamChunkUid); + sampleQueues.put(id, trackOutput); + return trackOutput; + } + + @Override + public void endTracks() { + sampleQueuesBuilt = true; + handler.post(maybeFinishPrepareRunnable); + } + + @Override + public void seekMap(SeekMap seekMap) { + // Do nothing. + } + + // UpstreamFormatChangedListener implementation. Called by the loading thread. + + @Override + public void onUpstreamFormatChanged(Format format) { + handler.post(maybeFinishPrepareRunnable); + } + + // Internal methods. + + private void maybeFinishPrepare() { + if (released || prepared || !sampleQueuesBuilt) { + return; + } + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + if (sampleQueues.valueAt(i).getUpstreamFormat() == null) { + return; + } + } + buildTracks(); + prepared = true; + callback.onPrepared(); + } + + /** + * Builds tracks that are exposed by this {@link HlsSampleStreamWrapper} instance, as well as + * internal data-structures required for operation. + *

    + * Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each + * variant stream typically contains muxed video, audio and (possibly) additional audio, metadata + * and caption tracks. We wish to allow the user to select between an adaptive track that spans + * all variants, as well as each individual variant. If multiple audio tracks are present within + * each variant then we wish to allow the user to select between those also. + *

    + * To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1) tracks, + * where N is the number of variants defined in the HLS master playlist. These consist of one + * adaptive track defined to span all variants and a track for each individual variant. The + * adaptive track is initially selected. The extractor is then prepared to discover the tracks + * inside of each variant stream. The two sets of tracks are then combined by this method to + * create a third set, which is the set exposed by this {@link HlsSampleStreamWrapper}: + *

      + *
    • The extractor tracks are inspected to infer a "primary" track type. If a video track is + * present then it is always the primary type. If not, audio is the primary type if present. + * Else text is the primary type if present. Else there is no primary type.
    • + *
    • If there is exactly one extractor track of the primary type, it's expanded into (N+1) + * exposed tracks, all of which correspond to the primary extractor track and each of which + * corresponds to a different chunk source track. Selecting one of these tracks has the effect + * of switching the selected track on the chunk source.
    • + *
    • All other extractor tracks are exposed directly. Selecting one of these tracks has the + * effect of selecting an extractor track, leaving the selected track on the chunk source + * unchanged.
    • + *
    + */ + private void buildTracks() { + // Iterate through the extractor tracks to discover the "primary" track type, and the index + // of the single track of this type. + int primaryExtractorTrackType = PRIMARY_TYPE_NONE; + int primaryExtractorTrackIndex = C.INDEX_UNSET; + int extractorTrackCount = sampleQueues.size(); + for (int i = 0; i < extractorTrackCount; i++) { + String sampleMimeType = sampleQueues.valueAt(i).getUpstreamFormat().sampleMimeType; + int trackType; + if (MimeTypes.isVideo(sampleMimeType)) { + trackType = PRIMARY_TYPE_VIDEO; + } else if (MimeTypes.isAudio(sampleMimeType)) { + trackType = PRIMARY_TYPE_AUDIO; + } else if (MimeTypes.isText(sampleMimeType)) { + trackType = PRIMARY_TYPE_TEXT; + } else { + trackType = PRIMARY_TYPE_NONE; + } + if (trackType > primaryExtractorTrackType) { + primaryExtractorTrackType = trackType; + primaryExtractorTrackIndex = i; + } else if (trackType == primaryExtractorTrackType + && primaryExtractorTrackIndex != C.INDEX_UNSET) { + // We have multiple tracks of the primary type. We only want an index if there only exists a + // single track of the primary type, so unset the index again. + primaryExtractorTrackIndex = C.INDEX_UNSET; + } + } + + TrackGroup chunkSourceTrackGroup = chunkSource.getTrackGroup(); + int chunkSourceTrackCount = chunkSourceTrackGroup.length; + + // Instantiate the necessary internal data-structures. + primaryTrackGroupIndex = C.INDEX_UNSET; + groupEnabledStates = new boolean[extractorTrackCount]; + + // Construct the set of exposed track groups. + TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount]; + for (int i = 0; i < extractorTrackCount; i++) { + Format sampleFormat = sampleQueues.valueAt(i).getUpstreamFormat(); + if (i == primaryExtractorTrackIndex) { + Format[] formats = new Format[chunkSourceTrackCount]; + for (int j = 0; j < chunkSourceTrackCount; j++) { + formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat); + } + trackGroups[i] = new TrackGroup(formats); + primaryTrackGroupIndex = i; + } else { + Format trackFormat = primaryExtractorTrackType == PRIMARY_TYPE_VIDEO + && MimeTypes.isAudio(sampleFormat.sampleMimeType) ? muxedAudioFormat : null; + trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat)); + } + } + this.trackGroups = new TrackGroupArray(trackGroups); + } + + /** + * Enables or disables a specified track group. + * + * @param group The index of the track group. + * @param enabledState True if the group is being enabled, or false if it's being disabled. + */ + private void setTrackGroupEnabledState(int group, boolean enabledState) { + Assertions.checkState(groupEnabledStates[group] != enabledState); + groupEnabledStates[group] = enabledState; + enabledTrackCount = enabledTrackCount + (enabledState ? 1 : -1); + } + + /** + * Derives a track format corresponding to a given container format, by combining it with sample + * level information obtained from the samples. + * + * @param containerFormat The container format for which the track format should be derived. + * @param sampleFormat A sample format from which to obtain sample level information. + * @return The derived track format. + */ + private static Format deriveFormat(Format containerFormat, Format sampleFormat) { + if (containerFormat == null) { + return sampleFormat; + } + String codecs = null; + int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType); + if (sampleTrackType == C.TRACK_TYPE_AUDIO) { + codecs = getAudioCodecs(containerFormat.codecs); + } else if (sampleTrackType == C.TRACK_TYPE_VIDEO) { + codecs = getVideoCodecs(containerFormat.codecs); + } + return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate, + containerFormat.width, containerFormat.height, containerFormat.selectionFlags, + containerFormat.language); + } + + private boolean isMediaChunk(Chunk chunk) { + return chunk instanceof HlsMediaChunk; + } + + private boolean isPendingReset() { + return pendingResetPositionUs != C.TIME_UNSET; + } + + private static String getAudioCodecs(String codecs) { + return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO); + } + + private static String getVideoCodecs(String codecs) { + return getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO); + } + + private static String getCodecsOfType(String codecs, int trackType) { + if (TextUtils.isEmpty(codecs)) { + return null; + } + String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)"); + StringBuilder builder = new StringBuilder(); + for (String codec : codecArray) { + if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { + if (builder.length() > 0) { + builder.append(","); + } + builder.append(codec); + } + } + return builder.length() > 0 ? builder.toString() : null; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/TimestampAdjusterProvider.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/TimestampAdjusterProvider.java new file mode 100644 index 0000000..631a296 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/TimestampAdjusterProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; + +/** + * Provides {@link TimestampAdjuster} instances for use during HLS playbacks. + */ +public final class TimestampAdjusterProvider { + + // TODO: Prevent this array from growing indefinitely large by removing adjusters that are no + // longer required. + private final SparseArray timestampAdjusters; + + public TimestampAdjusterProvider() { + timestampAdjusters = new SparseArray<>(); + } + + /** + * Returns a {@link TimestampAdjuster} suitable for adjusting the pts timestamps contained in + * a chunk with a given discontinuity sequence. + * + * @param discontinuitySequence The chunk's discontinuity sequence. + * @return A {@link TimestampAdjuster}. + */ + public TimestampAdjuster getAdjuster(int discontinuitySequence) { + TimestampAdjuster adjuster = timestampAdjusters.get(discontinuitySequence); + if (adjuster == null) { + adjuster = new TimestampAdjuster(TimestampAdjuster.DO_NOT_OFFSET); + timestampAdjusters.put(discontinuitySequence, adjuster); + } + return adjuster; + } + + /** + * Resets the provider. + */ + public void reset() { + timestampAdjusters.clear(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/WebvttExtractor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/WebvttExtractor.java new file mode 100644 index 0000000..ef158dd --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/WebvttExtractor.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls; + +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorInput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ExtractorOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.PositionHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.SeekMap; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoderException; +import com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt.WebvttParserUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TimestampAdjuster; +import java.io.IOException; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A special purpose extractor for WebVTT content in HLS. + *

    + * This extractor passes through non-empty WebVTT files untouched, however derives the correct + * sample timestamp for each by sniffing the X-TIMESTAMP-MAP header along with the start timestamp + * of the first cue header. Empty WebVTT files are not passed through, since it's not possible to + * derive a sample timestamp in this case. + */ +/* package */ final class WebvttExtractor implements Extractor { + + private static final Pattern LOCAL_TIMESTAMP = Pattern.compile("LOCAL:([^,]+)"); + private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:(\\d+)"); + + private final String language; + private final TimestampAdjuster timestampAdjuster; + private final ParsableByteArray sampleDataWrapper; + + private ExtractorOutput output; + + private byte[] sampleData; + private int sampleSize; + + public WebvttExtractor(String language, TimestampAdjuster timestampAdjuster) { + this.language = language; + this.timestampAdjuster = timestampAdjuster; + this.sampleDataWrapper = new ParsableByteArray(); + sampleData = new byte[1024]; + } + + // Extractor implementation. + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + // This extractor is only used for the HLS use case, which should not call this method. + throw new IllegalStateException(); + } + + @Override + public void init(ExtractorOutput output) { + this.output = output; + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position, long timeUs) { + // This extractor is only used for the HLS use case, which should not call this method. + throw new IllegalStateException(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + int currentFileSize = (int) input.getLength(); + + // Increase the size of sampleData if necessary. + if (sampleSize == sampleData.length) { + sampleData = Arrays.copyOf(sampleData, + (currentFileSize != C.LENGTH_UNSET ? currentFileSize : sampleData.length) * 3 / 2); + } + + // Consume to the input. + int bytesRead = input.read(sampleData, sampleSize, sampleData.length - sampleSize); + if (bytesRead != C.RESULT_END_OF_INPUT) { + sampleSize += bytesRead; + if (currentFileSize == C.LENGTH_UNSET || sampleSize != currentFileSize) { + return Extractor.RESULT_CONTINUE; + } + } + + // We've reached the end of the input, which corresponds to the end of the current file. + processSample(); + return Extractor.RESULT_END_OF_INPUT; + } + + private void processSample() throws ParserException { + ParsableByteArray webvttData = new ParsableByteArray(sampleData); + + // Validate the first line of the header. + try { + WebvttParserUtil.validateWebvttHeaderLine(webvttData); + } catch (SubtitleDecoderException e) { + throw new ParserException(e); + } + + // Defaults to use if the header doesn't contain an X-TIMESTAMP-MAP header. + long vttTimestampUs = 0; + long tsTimestampUs = 0; + + // Parse the remainder of the header looking for X-TIMESTAMP-MAP. + String line; + while (!TextUtils.isEmpty(line = webvttData.readLine())) { + if (line.startsWith("X-TIMESTAMP-MAP")) { + Matcher localTimestampMatcher = LOCAL_TIMESTAMP.matcher(line); + if (!localTimestampMatcher.find()) { + throw new ParserException("X-TIMESTAMP-MAP doesn't contain local timestamp: " + line); + } + Matcher mediaTimestampMatcher = MEDIA_TIMESTAMP.matcher(line); + if (!mediaTimestampMatcher.find()) { + throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line); + } + vttTimestampUs = WebvttParserUtil.parseTimestampUs(localTimestampMatcher.group(1)); + tsTimestampUs = TimestampAdjuster.ptsToUs( + Long.parseLong(mediaTimestampMatcher.group(1))); + } + } + + // Find the first cue header and parse the start time. + Matcher cueHeaderMatcher = WebvttParserUtil.findNextCueHeader(webvttData); + if (cueHeaderMatcher == null) { + // No cues found. Don't output a sample, but still output a corresponding track. + buildTrackOutput(0); + return; + } + + long firstCueTimeUs = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)); + long sampleTimeUs = timestampAdjuster.adjustSampleTimestamp( + firstCueTimeUs + tsTimestampUs - vttTimestampUs); + long subsampleOffsetUs = sampleTimeUs - firstCueTimeUs; + // Output the track. + TrackOutput trackOutput = buildTrackOutput(subsampleOffsetUs); + // Output the sample. + sampleDataWrapper.reset(sampleData, sampleSize); + trackOutput.sampleData(sampleDataWrapper, sampleSize); + trackOutput.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } + + private TrackOutput buildTrackOutput(long subsampleOffsetUs) { + TrackOutput trackOutput = output.track(0, C.TRACK_TYPE_TEXT); + trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.TEXT_VTT, null, + Format.NO_VALUE, 0, language, null, subsampleOffsetUs)); + output.endTracks(); + return trackOutput; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java new file mode 100644 index 0000000..91d06a6 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import java.util.Collections; +import java.util.List; + +/** + * Represents an HLS master playlist. + */ +public final class HlsMasterPlaylist extends HlsPlaylist { + + /** + * Represents a url in an HLS master playlist. + */ + public static final class HlsUrl { + + public final String url; + public final Format format; + + public static HlsUrl createMediaPlaylistHlsUrl(String baseUri) { + Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null, + Format.NO_VALUE, 0, null); + return new HlsUrl(baseUri, format); + } + + public HlsUrl(String url, Format format) { + this.url = url; + this.format = format; + } + + } + + public final List variants; + public final List audios; + public final List subtitles; + + public final Format muxedAudioFormat; + public final List muxedCaptionFormats; + + public HlsMasterPlaylist(String baseUri, List variants, List audios, + List subtitles, Format muxedAudioFormat, List muxedCaptionFormats) { + super(baseUri); + this.variants = Collections.unmodifiableList(variants); + this.audios = Collections.unmodifiableList(audios); + this.subtitles = Collections.unmodifiableList(subtitles); + this.muxedAudioFormat = muxedAudioFormat; + this.muxedCaptionFormats = Collections.unmodifiableList(muxedCaptionFormats); + } + + public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUri) { + List variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUri)); + List emptyList = Collections.emptyList(); + return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, + Collections.emptyList()); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java new file mode 100644 index 0000000..38145b4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist; + +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.List; + +/** + * Represents an HLS media playlist. + */ +public final class HlsMediaPlaylist extends HlsPlaylist { + + /** + * Media segment reference. + */ + public static final class Segment implements Comparable { + + public final String url; + public final long durationUs; + public final int relativeDiscontinuitySequence; + public final long relativeStartTimeUs; + public final boolean isEncrypted; + public final String encryptionKeyUri; + public final String encryptionIV; + public final long byterangeOffset; + public final long byterangeLength; + + public Segment(String uri, long byterangeOffset, long byterangeLength) { + this(uri, 0, -1, C.TIME_UNSET, false, null, null, byterangeOffset, byterangeLength); + } + + public Segment(String uri, long durationUs, int relativeDiscontinuitySequence, + long relativeStartTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, + long byterangeOffset, long byterangeLength) { + this.url = uri; + this.durationUs = durationUs; + this.relativeDiscontinuitySequence = relativeDiscontinuitySequence; + this.relativeStartTimeUs = relativeStartTimeUs; + this.isEncrypted = isEncrypted; + this.encryptionKeyUri = encryptionKeyUri; + this.encryptionIV = encryptionIV; + this.byterangeOffset = byterangeOffset; + this.byterangeLength = byterangeLength; + } + + @Override + public int compareTo(@NonNull Long relativeStartTimeUs) { + return this.relativeStartTimeUs > relativeStartTimeUs + ? 1 : (this.relativeStartTimeUs < relativeStartTimeUs ? -1 : 0); + } + + } + + /** + * Type of the playlist as specified by #EXT-X-PLAYLIST-TYPE. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT}) + public @interface PlaylistType {} + public static final int PLAYLIST_TYPE_UNKNOWN = 0; + public static final int PLAYLIST_TYPE_VOD = 1; + public static final int PLAYLIST_TYPE_EVENT = 2; + + @PlaylistType public final int playlistType; + public final long startOffsetUs; + public final long startTimeUs; + public final boolean hasDiscontinuitySequence; + public final int discontinuitySequence; + public final int mediaSequence; + public final int version; + public final long targetDurationUs; + public final boolean hasEndTag; + public final boolean hasProgramDateTime; + public final Segment initializationSegment; + public final List segments; + public final long durationUs; + + public HlsMediaPlaylist(@PlaylistType int playlistType, String baseUri, long startOffsetUs, + long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence, + int mediaSequence, int version, long targetDurationUs, boolean hasEndTag, + boolean hasProgramDateTime, Segment initializationSegment, List segments) { + super(baseUri); + this.playlistType = playlistType; + this.startTimeUs = startTimeUs; + this.hasDiscontinuitySequence = hasDiscontinuitySequence; + this.discontinuitySequence = discontinuitySequence; + this.mediaSequence = mediaSequence; + this.version = version; + this.targetDurationUs = targetDurationUs; + this.hasEndTag = hasEndTag; + this.hasProgramDateTime = hasProgramDateTime; + this.initializationSegment = initializationSegment; + this.segments = Collections.unmodifiableList(segments); + if (!segments.isEmpty()) { + Segment last = segments.get(segments.size() - 1); + durationUs = last.relativeStartTimeUs + last.durationUs; + } else { + durationUs = 0; + } + this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET + : startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs; + } + + /** + * Returns whether this playlist is newer than {@code other}. + * + * @param other The playlist to compare. + * @return Whether this playlist is newer than {@code other}. + */ + public boolean isNewerThan(HlsMediaPlaylist other) { + if (other == null || mediaSequence > other.mediaSequence) { + return true; + } + if (mediaSequence < other.mediaSequence) { + return false; + } + // The media sequences are equal. + int segmentCount = segments.size(); + int otherSegmentCount = other.segments.size(); + return segmentCount > otherSegmentCount + || (segmentCount == otherSegmentCount && hasEndTag && !other.hasEndTag); + } + + public long getEndTimeUs() { + return startTimeUs + durationUs; + } + + /** + * Returns a playlist identical to this one except for the start time, the discontinuity sequence + * and {@code hasDiscontinuitySequence} values. The first two are set to the specified values, + * {@code hasDiscontinuitySequence} is set to true. + * + * @param startTimeUs The start time for the returned playlist. + * @param discontinuitySequence The discontinuity sequence for the returned playlist. + * @return The playlist. + */ + public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) { + return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, true, + discontinuitySequence, mediaSequence, version, targetDurationUs, hasEndTag, + hasProgramDateTime, initializationSegment, segments); + } + + /** + * Returns a playlist identical to this one except that an end tag is added. If an end tag is + * already present then the playlist will return itself. + * + * @return The playlist. + */ + public HlsMediaPlaylist copyWithEndTag() { + if (this.hasEndTag) { + return this; + } + return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, + hasDiscontinuitySequence, discontinuitySequence, mediaSequence, version, targetDurationUs, + true, hasProgramDateTime, initializationSegment, segments); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylist.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylist.java new file mode 100644 index 0000000..6e9cc10 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylist.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist; + +/** + * Represents an HLS playlist. + */ +public abstract class HlsPlaylist { + + public final String baseUri; + + protected HlsPlaylist(String baseUri) { + this.baseUri = baseUri; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylistParser.java new file mode 100644 index 0000000..624cf4d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.source.UnrecognizedInputFormatException; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.ParsingLoadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HLS playlists parsing logic. + */ +public final class HlsPlaylistParser implements ParsingLoadable.Parser { + + private static final String PLAYLIST_HEADER = "#EXTM3U"; + + private static final String TAG_VERSION = "#EXT-X-VERSION"; + private static final String TAG_PLAYLIST_TYPE = "#EXT-X-PLAYLIST-TYPE"; + private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF"; + private static final String TAG_MEDIA = "#EXT-X-MEDIA"; + private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION"; + private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; + private static final String TAG_DISCONTINUITY_SEQUENCE = "#EXT-X-DISCONTINUITY-SEQUENCE"; + private static final String TAG_PROGRAM_DATE_TIME = "#EXT-X-PROGRAM-DATE-TIME"; + private static final String TAG_INIT_SEGMENT = "#EXT-X-MAP"; + private static final String TAG_MEDIA_DURATION = "#EXTINF"; + private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE"; + private static final String TAG_START = "#EXT-X-START"; + private static final String TAG_ENDLIST = "#EXT-X-ENDLIST"; + private static final String TAG_KEY = "#EXT-X-KEY"; + private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE"; + + private static final String TYPE_AUDIO = "AUDIO"; + private static final String TYPE_VIDEO = "VIDEO"; + private static final String TYPE_SUBTITLES = "SUBTITLES"; + private static final String TYPE_CLOSED_CAPTIONS = "CLOSED-CAPTIONS"; + + private static final String METHOD_NONE = "NONE"; + private static final String METHOD_AES128 = "AES-128"; + + private static final String BOOLEAN_TRUE = "YES"; + private static final String BOOLEAN_FALSE = "NO"; + + private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b"); + private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\""); + private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)"); + private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION + + ":(\\d+)\\b"); + private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b"); + private static final Pattern REGEX_PLAYLIST_TYPE = Pattern.compile(TAG_PLAYLIST_TYPE + + ":(.+)\\b"); + private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE + + ":(\\d+)\\b"); + private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION + + ":([\\d\\.]+)\\b"); + private static final Pattern REGEX_TIME_OFFSET = Pattern.compile("TIME-OFFSET=(-?[\\d\\.]+)\\b"); + private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE + + ":(\\d+(?:@\\d+)?)\\b"); + private static final Pattern REGEX_ATTR_BYTERANGE = + Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\""); + private static final Pattern REGEX_METHOD = Pattern.compile("METHOD=(" + METHOD_NONE + "|" + + METHOD_AES128 + ")"); + private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\""); + private static final Pattern REGEX_IV = Pattern.compile("IV=([^,.*]+)"); + private static final Pattern REGEX_TYPE = Pattern.compile("TYPE=(" + TYPE_AUDIO + "|" + TYPE_VIDEO + + "|" + TYPE_SUBTITLES + "|" + TYPE_CLOSED_CAPTIONS + ")"); + private static final Pattern REGEX_LANGUAGE = Pattern.compile("LANGUAGE=\"(.+?)\""); + private static final Pattern REGEX_NAME = Pattern.compile("NAME=\"(.+?)\""); + private static final Pattern REGEX_INSTREAM_ID = + Pattern.compile("INSTREAM-ID=\"((?:CC|SERVICE)\\d+)\""); + private static final Pattern REGEX_AUTOSELECT = compileBooleanAttrPattern("AUTOSELECT"); + private static final Pattern REGEX_DEFAULT = compileBooleanAttrPattern("DEFAULT"); + private static final Pattern REGEX_FORCED = compileBooleanAttrPattern("FORCED"); + + @Override + public HlsPlaylist parse(Uri uri, InputStream inputStream) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + Queue extraLines = new LinkedList<>(); + String line; + try { + if (!checkPlaylistHeader(reader)) { + throw new UnrecognizedInputFormatException("Input does not start with the #EXTM3U header.", + uri); + } + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) { + // Do nothing. + } else if (line.startsWith(TAG_STREAM_INF)) { + extraLines.add(line); + return parseMasterPlaylist(new LineIterator(extraLines, reader), uri.toString()); + } else if (line.startsWith(TAG_TARGET_DURATION) + || line.startsWith(TAG_MEDIA_SEQUENCE) + || line.startsWith(TAG_MEDIA_DURATION) + || line.startsWith(TAG_KEY) + || line.startsWith(TAG_BYTERANGE) + || line.equals(TAG_DISCONTINUITY) + || line.equals(TAG_DISCONTINUITY_SEQUENCE) + || line.equals(TAG_ENDLIST)) { + extraLines.add(line); + return parseMediaPlaylist(new LineIterator(extraLines, reader), uri.toString()); + } else { + extraLines.add(line); + } + } + } finally { + Util.closeQuietly(reader); + } + throw new ParserException("Failed to parse the playlist, could not identify any tags."); + } + + private static boolean checkPlaylistHeader(BufferedReader reader) throws IOException { + int last = reader.read(); + if (last == 0xEF) { + if (reader.read() != 0xBB || reader.read() != 0xBF) { + return false; + } + // The playlist contains a Byte Order Mark, which gets discarded. + last = reader.read(); + } + last = skipIgnorableWhitespace(reader, true, last); + int playlistHeaderLength = PLAYLIST_HEADER.length(); + for (int i = 0; i < playlistHeaderLength; i++) { + if (last != PLAYLIST_HEADER.charAt(i)) { + return false; + } + last = reader.read(); + } + last = skipIgnorableWhitespace(reader, false, last); + return Util.isLinebreak(last); + } + + private static int skipIgnorableWhitespace(BufferedReader reader, boolean skipLinebreaks, int c) + throws IOException { + while (c != -1 && Character.isWhitespace(c) && (skipLinebreaks || !Util.isLinebreak(c))) { + c = reader.read(); + } + return c; + } + + private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri) + throws IOException { + ArrayList variants = new ArrayList<>(); + ArrayList audios = new ArrayList<>(); + ArrayList subtitles = new ArrayList<>(); + Format muxedAudioFormat = null; + ArrayList muxedCaptionFormats = new ArrayList<>(); + + String line; + while (iterator.hasNext()) { + line = iterator.next(); + if (line.startsWith(TAG_MEDIA)) { + @C.SelectionFlags int selectionFlags = parseSelectionFlags(line); + String uri = parseOptionalStringAttr(line, REGEX_URI); + String id = parseStringAttr(line, REGEX_NAME); + String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); + Format format; + switch (parseStringAttr(line, REGEX_TYPE)) { + case TYPE_AUDIO: + format = Format.createAudioContainerFormat(id, MimeTypes.APPLICATION_M3U8, null, null, + Format.NO_VALUE, Format.NO_VALUE, Format.NO_VALUE, null, selectionFlags, language); + if (uri == null) { + muxedAudioFormat = format; + } else { + audios.add(new HlsMasterPlaylist.HlsUrl(uri, format)); + } + break; + case TYPE_SUBTITLES: + format = Format.createTextContainerFormat(id, MimeTypes.APPLICATION_M3U8, + MimeTypes.TEXT_VTT, null, Format.NO_VALUE, selectionFlags, language); + subtitles.add(new HlsMasterPlaylist.HlsUrl(uri, format)); + break; + case TYPE_CLOSED_CAPTIONS: + String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID); + String mimeType; + int accessibilityChannel; + if (instreamId.startsWith("CC")) { + mimeType = MimeTypes.APPLICATION_CEA608; + accessibilityChannel = Integer.parseInt(instreamId.substring(2)); + } else /* starts with SERVICE */ { + mimeType = MimeTypes.APPLICATION_CEA708; + accessibilityChannel = Integer.parseInt(instreamId.substring(7)); + } + muxedCaptionFormats.add(Format.createTextContainerFormat(id, null, mimeType, null, + Format.NO_VALUE, selectionFlags, language, accessibilityChannel)); + break; + default: + // Do nothing. + break; + } + } else if (line.startsWith(TAG_STREAM_INF)) { + int bitrate = parseIntAttr(line, REGEX_BANDWIDTH); + String codecs = parseOptionalStringAttr(line, REGEX_CODECS); + String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION); + int width; + int height; + if (resolutionString != null) { + String[] widthAndHeight = resolutionString.split("x"); + width = Integer.parseInt(widthAndHeight[0]); + height = Integer.parseInt(widthAndHeight[1]); + if (width <= 0 || height <= 0) { + // Resolution string is invalid. + width = Format.NO_VALUE; + height = Format.NO_VALUE; + } + } else { + width = Format.NO_VALUE; + height = Format.NO_VALUE; + } + line = iterator.next(); + Format format = Format.createVideoContainerFormat(Integer.toString(variants.size()), + MimeTypes.APPLICATION_M3U8, null, codecs, bitrate, width, height, Format.NO_VALUE, null, + 0); + variants.add(new HlsMasterPlaylist.HlsUrl(line, format)); + } + } + return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat, + muxedCaptionFormats); + } + + @C.SelectionFlags + private static int parseSelectionFlags(String line) { + return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? C.SELECTION_FLAG_DEFAULT : 0) + | (parseBooleanAttribute(line, REGEX_FORCED, false) ? C.SELECTION_FLAG_FORCED : 0) + | (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? C.SELECTION_FLAG_AUTOSELECT : 0); + } + + private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) + throws IOException { + @HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN; + long startOffsetUs = C.TIME_UNSET; + int mediaSequence = 0; + int version = 1; // Default version == 1. + long targetDurationUs = C.TIME_UNSET; + boolean hasEndTag = false; + Segment initializationSegment = null; + List segments = new ArrayList<>(); + + long segmentDurationUs = 0; + boolean hasDiscontinuitySequence = false; + int playlistDiscontinuitySequence = 0; + int relativeDiscontinuitySequence = 0; + long playlistStartTimeUs = 0; + long segmentStartTimeUs = 0; + long segmentByteRangeOffset = 0; + long segmentByteRangeLength = C.LENGTH_UNSET; + int segmentMediaSequence = 0; + + boolean isEncrypted = false; + String encryptionKeyUri = null; + String encryptionIV = null; + + String line; + while (iterator.hasNext()) { + line = iterator.next(); + if (line.startsWith(TAG_PLAYLIST_TYPE)) { + String playlistTypeString = parseStringAttr(line, REGEX_PLAYLIST_TYPE); + if ("VOD".equals(playlistTypeString)) { + playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_VOD; + } else if ("EVENT".equals(playlistTypeString)) { + playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_EVENT; + } else { + throw new ParserException("Illegal playlist type: " + playlistTypeString); + } + } else if (line.startsWith(TAG_START)) { + startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND); + } else if (line.startsWith(TAG_INIT_SEGMENT)) { + String uri = parseStringAttr(line, REGEX_URI); + String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE); + if (byteRange != null) { + String[] splitByteRange = byteRange.split("@"); + segmentByteRangeLength = Long.parseLong(splitByteRange[0]); + if (splitByteRange.length > 1) { + segmentByteRangeOffset = Long.parseLong(splitByteRange[1]); + } + } + initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength); + segmentByteRangeOffset = 0; + segmentByteRangeLength = C.LENGTH_UNSET; + } else if (line.startsWith(TAG_TARGET_DURATION)) { + targetDurationUs = parseIntAttr(line, REGEX_TARGET_DURATION) * C.MICROS_PER_SECOND; + } else if (line.startsWith(TAG_MEDIA_SEQUENCE)) { + mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE); + segmentMediaSequence = mediaSequence; + } else if (line.startsWith(TAG_VERSION)) { + version = parseIntAttr(line, REGEX_VERSION); + } else if (line.startsWith(TAG_MEDIA_DURATION)) { + segmentDurationUs = + (long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND); + } else if (line.startsWith(TAG_KEY)) { + String method = parseStringAttr(line, REGEX_METHOD); + isEncrypted = METHOD_AES128.equals(method); + if (isEncrypted) { + encryptionKeyUri = parseStringAttr(line, REGEX_URI); + encryptionIV = parseOptionalStringAttr(line, REGEX_IV); + } else { + encryptionKeyUri = null; + encryptionIV = null; + } + } else if (line.startsWith(TAG_BYTERANGE)) { + String byteRange = parseStringAttr(line, REGEX_BYTERANGE); + String[] splitByteRange = byteRange.split("@"); + segmentByteRangeLength = Long.parseLong(splitByteRange[0]); + if (splitByteRange.length > 1) { + segmentByteRangeOffset = Long.parseLong(splitByteRange[1]); + } + } else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) { + hasDiscontinuitySequence = true; + playlistDiscontinuitySequence = Integer.parseInt(line.substring(line.indexOf(':') + 1)); + } else if (line.equals(TAG_DISCONTINUITY)) { + relativeDiscontinuitySequence++; + } else if (line.startsWith(TAG_PROGRAM_DATE_TIME)) { + if (playlistStartTimeUs == 0) { + long programDatetimeUs = + C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1))); + playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs; + } + } else if (!line.startsWith("#")) { + String segmentEncryptionIV; + if (!isEncrypted) { + segmentEncryptionIV = null; + } else if (encryptionIV != null) { + segmentEncryptionIV = encryptionIV; + } else { + segmentEncryptionIV = Integer.toHexString(segmentMediaSequence); + } + segmentMediaSequence++; + if (segmentByteRangeLength == C.LENGTH_UNSET) { + segmentByteRangeOffset = 0; + } + segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence, + segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV, + segmentByteRangeOffset, segmentByteRangeLength)); + segmentStartTimeUs += segmentDurationUs; + segmentDurationUs = 0; + if (segmentByteRangeLength != C.LENGTH_UNSET) { + segmentByteRangeOffset += segmentByteRangeLength; + } + segmentByteRangeLength = C.LENGTH_UNSET; + } else if (line.equals(TAG_ENDLIST)) { + hasEndTag = true; + } + } + return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, playlistStartTimeUs, + hasDiscontinuitySequence, playlistDiscontinuitySequence, mediaSequence, version, + targetDurationUs, hasEndTag, playlistStartTimeUs != 0, initializationSegment, segments); + } + + private static String parseStringAttr(String line, Pattern pattern) throws ParserException { + Matcher matcher = pattern.matcher(line); + if (matcher.find() && matcher.groupCount() == 1) { + return matcher.group(1); + } + throw new ParserException("Couldn't match " + pattern.pattern() + " in " + line); + } + + private static int parseIntAttr(String line, Pattern pattern) throws ParserException { + return Integer.parseInt(parseStringAttr(line, pattern)); + } + + private static double parseDoubleAttr(String line, Pattern pattern) throws ParserException { + return Double.parseDouble(parseStringAttr(line, pattern)); + } + + private static String parseOptionalStringAttr(String line, Pattern pattern) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + private static boolean parseBooleanAttribute(String line, Pattern pattern, boolean defaultValue) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + return matcher.group(1).equals(BOOLEAN_TRUE); + } + return defaultValue; + } + + private static Pattern compileBooleanAttrPattern(String attribute) { + return Pattern.compile(attribute + "=(" + BOOLEAN_FALSE + "|" + BOOLEAN_TRUE + ")"); + } + + private static class LineIterator { + + private final BufferedReader reader; + private final Queue extraLines; + + private String next; + + public LineIterator(Queue extraLines, BufferedReader reader) { + this.extraLines = extraLines; + this.reader = reader; + } + + public boolean hasNext() throws IOException { + if (next != null) { + return true; + } + if (!extraLines.isEmpty()) { + next = extraLines.poll(); + return true; + } + while ((next = reader.readLine()) != null) { + next = next.trim(); + if (!next.isEmpty()) { + return true; + } + } + return false; + } + + public String next() throws IOException { + String result = null; + if (hasNext()) { + result = next; + next = null; + } + return result; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java new file mode 100644 index 0000000..05e3b5d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist; + +import android.net.Uri; +import android.os.Handler; +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.HlsDataSourceFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import com.tangxiaolv.telegramgallery.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.ParsingLoadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.UriUtil; +import java.io.IOException; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; + +/** + * Tracks playlists linked to a provided playlist url. The provided url might reference an HLS + * master playlist or a media playlist. + */ +public final class HlsPlaylistTracker implements Loader.Callback> { + + /** + * Listener for primary playlist changes. + */ + public interface PrimaryPlaylistListener { + + /** + * Called when the primary playlist changes. + * + * @param mediaPlaylist The primary playlist new snapshot. + */ + void onPrimaryPlaylistRefreshed(HlsMediaPlaylist mediaPlaylist); + + } + + /** + * Called on playlist loading events. + */ + public interface PlaylistEventListener { + + /** + * Called a playlist changes. + */ + void onPlaylistChanged(); + + /** + * Called if an error is encountered while loading a playlist. + * + * @param url The loaded url that caused the error. + * @param blacklistDurationMs The number of milliseconds for which the playlist has been + * blacklisted. + */ + void onPlaylistBlacklisted(HlsUrl url, long blacklistDurationMs); + + } + + /** + * The minimum number of milliseconds that a url is kept as primary url, if no + * {@link #getPlaylistSnapshot} call is made for that url. + */ + private static final long PRIMARY_URL_KEEPALIVE_MS = 15000; + + private final Uri initialPlaylistUri; + private final HlsDataSourceFactory dataSourceFactory; + private final HlsPlaylistParser playlistParser; + private final int minRetryCount; + private final IdentityHashMap playlistBundles; + private final Handler playlistRefreshHandler; + private final PrimaryPlaylistListener primaryPlaylistListener; + private final List listeners; + private final Loader initialPlaylistLoader; + private final EventDispatcher eventDispatcher; + + private HlsMasterPlaylist masterPlaylist; + private HlsUrl primaryHlsUrl; + private HlsMediaPlaylist primaryUrlSnapshot; + private boolean isLive; + + /** + * @param initialPlaylistUri Uri for the initial playlist of the stream. Can refer a media + * playlist or a master playlist. + * @param dataSourceFactory A factory for {@link DataSource} instances. + * @param eventDispatcher A dispatcher to notify of events. + * @param minRetryCount The minimum number of times the load must be retried before blacklisting a + * playlist. + * @param primaryPlaylistListener A callback for the primary playlist change events. + */ + public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory, + EventDispatcher eventDispatcher, int minRetryCount, + PrimaryPlaylistListener primaryPlaylistListener) { + this.initialPlaylistUri = initialPlaylistUri; + this.dataSourceFactory = dataSourceFactory; + this.eventDispatcher = eventDispatcher; + this.minRetryCount = minRetryCount; + this.primaryPlaylistListener = primaryPlaylistListener; + listeners = new ArrayList<>(); + initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist"); + playlistParser = new HlsPlaylistParser(); + playlistBundles = new IdentityHashMap<>(); + playlistRefreshHandler = new Handler(); + } + + /** + * Registers a listener to receive events from the playlist tracker. + * + * @param listener The listener. + */ + public void addListener(PlaylistEventListener listener) { + listeners.add(listener); + } + + /** + * Unregisters a listener. + * + * @param listener The listener to unregister. + */ + public void removeListener(PlaylistEventListener listener) { + listeners.remove(listener); + } + + /** + * Starts tracking all the playlists related to the provided Uri. + */ + public void start() { + ParsingLoadable masterPlaylistLoadable = new ParsingLoadable<>( + dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), initialPlaylistUri, + C.DATA_TYPE_MANIFEST, playlistParser); + initialPlaylistLoader.startLoading(masterPlaylistLoadable, this, minRetryCount); + } + + /** + * Returns the master playlist. + * + * @return The master playlist. Null if the initial playlist has yet to be loaded. + */ + public HlsMasterPlaylist getMasterPlaylist() { + return masterPlaylist; + } + + /** + * Returns the most recent snapshot available of the playlist referenced by the provided + * {@link HlsUrl}. + * + * @param url The {@link HlsUrl} corresponding to the requested media playlist. + * @return The most recent snapshot of the playlist referenced by the provided {@link HlsUrl}. May + * be null if no snapshot has been loaded yet. + */ + public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) { + HlsMediaPlaylist snapshot = playlistBundles.get(url).getPlaylistSnapshot(); + if (snapshot != null) { + maybeSetPrimaryUrl(url); + } + return snapshot; + } + + /** + * Returns whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is + * valid, meaning all the segments referenced by the playlist are expected to be available. If the + * playlist is not valid then some of the segments may no longer be available. + + * @param url The {@link HlsUrl}. + * @return Whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is + * valid. + */ + public boolean isSnapshotValid(HlsUrl url) { + return playlistBundles.get(url).isSnapshotValid(); + } + + /** + * Releases the playlist tracker. + */ + public void release() { + initialPlaylistLoader.release(); + for (MediaPlaylistBundle bundle : playlistBundles.values()) { + bundle.release(); + } + playlistRefreshHandler.removeCallbacksAndMessages(null); + playlistBundles.clear(); + } + + /** + * If the tracker is having trouble refreshing the primary playlist or loading an irreplaceable + * playlist, this method throws the underlying error. Otherwise, does nothing. + * + * @throws IOException The underlying error. + */ + public void maybeThrowPlaylistRefreshError() throws IOException { + initialPlaylistLoader.maybeThrowError(); + if (primaryHlsUrl != null) { + playlistBundles.get(primaryHlsUrl).mediaPlaylistLoader.maybeThrowError(); + } + } + + /** + * Triggers a playlist refresh and whitelists it. + * + * @param url The {@link HlsUrl} of the playlist to be refreshed. + */ + public void refreshPlaylist(HlsUrl url) { + playlistBundles.get(url).loadPlaylist(); + } + + /** + * Returns whether this is live content. + * + * @return True if the content is live. False otherwise. + */ + public boolean isLive() { + return isLive; + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + HlsPlaylist result = loadable.getResult(); + HlsMasterPlaylist masterPlaylist; + boolean isMediaPlaylist = result instanceof HlsMediaPlaylist; + if (isMediaPlaylist) { + masterPlaylist = HlsMasterPlaylist.createSingleVariantMasterPlaylist(result.baseUri); + } else /* result instanceof HlsMasterPlaylist */ { + masterPlaylist = (HlsMasterPlaylist) result; + } + this.masterPlaylist = masterPlaylist; + primaryHlsUrl = masterPlaylist.variants.get(0); + ArrayList urls = new ArrayList<>(); + urls.addAll(masterPlaylist.variants); + urls.addAll(masterPlaylist.audios); + urls.addAll(masterPlaylist.subtitles); + createBundles(urls); + MediaPlaylistBundle primaryBundle = playlistBundles.get(primaryHlsUrl); + if (isMediaPlaylist) { + // We don't need to load the playlist again. We can use the same result. + primaryBundle.processLoadedPlaylist((HlsMediaPlaylist) result); + } else { + primaryBundle.loadPlaylist(); + } + eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + eventDispatcher.loadCanceled(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + boolean isFatal = error instanceof ParserException; + eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded(), error, isFatal); + return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; + } + + // Internal methods. + + private boolean maybeSelectNewPrimaryUrl() { + List variants = masterPlaylist.variants; + int variantsSize = variants.size(); + long currentTimeMs = SystemClock.elapsedRealtime(); + for (int i = 0; i < variantsSize; i++) { + MediaPlaylistBundle bundle = playlistBundles.get(variants.get(i)); + if (currentTimeMs > bundle.blacklistUntilMs) { + primaryHlsUrl = bundle.playlistUrl; + bundle.loadPlaylist(); + return true; + } + } + return false; + } + + private void maybeSetPrimaryUrl(HlsUrl url) { + if (!masterPlaylist.variants.contains(url) + || (primaryUrlSnapshot != null && primaryUrlSnapshot.hasEndTag)) { + // Only allow variant urls to be chosen as primary. Also prevent changing the primary url if + // the last primary snapshot contains an end tag. + return; + } + MediaPlaylistBundle currentPrimaryBundle = playlistBundles.get(primaryHlsUrl); + long primarySnapshotAccessAgeMs = + currentPrimaryBundle.lastSnapshotAccessTimeMs - SystemClock.elapsedRealtime(); + if (primarySnapshotAccessAgeMs > PRIMARY_URL_KEEPALIVE_MS) { + primaryHlsUrl = url; + playlistBundles.get(primaryHlsUrl).loadPlaylist(); + } + } + + private void createBundles(List urls) { + int listSize = urls.size(); + long currentTimeMs = SystemClock.elapsedRealtime(); + for (int i = 0; i < listSize; i++) { + HlsUrl url = urls.get(i); + MediaPlaylistBundle bundle = new MediaPlaylistBundle(url, currentTimeMs); + playlistBundles.put(url, bundle); + } + } + + /** + * Called by the bundles when a snapshot changes. + * + * @param url The url of the playlist. + * @param newSnapshot The new snapshot. + * @return True if a refresh should be scheduled. + */ + private boolean onPlaylistUpdated(HlsUrl url, HlsMediaPlaylist newSnapshot) { + if (url == primaryHlsUrl) { + if (primaryUrlSnapshot == null) { + // This is the first primary url snapshot. + isLive = !newSnapshot.hasEndTag; + } + primaryUrlSnapshot = newSnapshot; + primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot); + } + int listenersSize = listeners.size(); + for (int i = 0; i < listenersSize; i++) { + listeners.get(i).onPlaylistChanged(); + } + // If the primary playlist is not the final one, we should schedule a refresh. + return url == primaryHlsUrl && !newSnapshot.hasEndTag; + } + + private void notifyPlaylistBlacklisting(HlsUrl url, long blacklistMs) { + int listenersSize = listeners.size(); + for (int i = 0; i < listenersSize; i++) { + listeners.get(i).onPlaylistBlacklisted(url, blacklistMs); + } + } + + private HlsMediaPlaylist getLatestPlaylistSnapshot(HlsMediaPlaylist oldPlaylist, + HlsMediaPlaylist loadedPlaylist) { + if (!loadedPlaylist.isNewerThan(oldPlaylist)) { + if (loadedPlaylist.hasEndTag) { + // If the loaded playlist has an end tag but is not newer than the old playlist then we have + // an inconsistent state. This is typically caused by the server incorrectly resetting the + // media sequence when appending the end tag. We resolve this case as best we can by + // returning the old playlist with the end tag appended. + return oldPlaylist.copyWithEndTag(); + } else { + return oldPlaylist; + } + } + long startTimeUs = getLoadedPlaylistStartTimeUs(oldPlaylist, loadedPlaylist); + int discontinuitySequence = getLoadedPlaylistDiscontinuitySequence(oldPlaylist, loadedPlaylist); + return loadedPlaylist.copyWith(startTimeUs, discontinuitySequence); + } + + private long getLoadedPlaylistStartTimeUs(HlsMediaPlaylist oldPlaylist, + HlsMediaPlaylist loadedPlaylist) { + if (loadedPlaylist.hasProgramDateTime) { + return loadedPlaylist.startTimeUs; + } + long primarySnapshotStartTimeUs = primaryUrlSnapshot != null + ? primaryUrlSnapshot.startTimeUs : 0; + if (oldPlaylist == null) { + return primarySnapshotStartTimeUs; + } + int oldPlaylistSize = oldPlaylist.segments.size(); + Segment firstOldOverlappingSegment = getFirstOldOverlappingSegment(oldPlaylist, loadedPlaylist); + if (firstOldOverlappingSegment != null) { + return oldPlaylist.startTimeUs + firstOldOverlappingSegment.relativeStartTimeUs; + } else if (oldPlaylistSize == loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence) { + return oldPlaylist.getEndTimeUs(); + } else { + // No segments overlap, we assume the new playlist start coincides with the primary playlist. + return primarySnapshotStartTimeUs; + } + } + + private int getLoadedPlaylistDiscontinuitySequence(HlsMediaPlaylist oldPlaylist, + HlsMediaPlaylist loadedPlaylist) { + if (loadedPlaylist.hasDiscontinuitySequence) { + return loadedPlaylist.discontinuitySequence; + } + // TODO: Improve cross-playlist discontinuity adjustment. + int primaryUrlDiscontinuitySequence = primaryUrlSnapshot != null + ? primaryUrlSnapshot.discontinuitySequence : 0; + if (oldPlaylist == null) { + return primaryUrlDiscontinuitySequence; + } + Segment firstOldOverlappingSegment = getFirstOldOverlappingSegment(oldPlaylist, loadedPlaylist); + if (firstOldOverlappingSegment != null) { + return oldPlaylist.discontinuitySequence + + firstOldOverlappingSegment.relativeDiscontinuitySequence + - loadedPlaylist.segments.get(0).relativeDiscontinuitySequence; + } + return primaryUrlDiscontinuitySequence; + } + + private static Segment getFirstOldOverlappingSegment(HlsMediaPlaylist oldPlaylist, + HlsMediaPlaylist loadedPlaylist) { + int mediaSequenceOffset = loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence; + List oldSegments = oldPlaylist.segments; + return mediaSequenceOffset < oldSegments.size() ? oldSegments.get(mediaSequenceOffset) : null; + } + + /** + * Holds all information related to a specific Media Playlist. + */ + private final class MediaPlaylistBundle implements Loader.Callback>, + Runnable { + + private final HlsUrl playlistUrl; + private final Loader mediaPlaylistLoader; + private final ParsingLoadable mediaPlaylistLoadable; + + private HlsMediaPlaylist playlistSnapshot; + private long lastSnapshotLoadMs; + private long lastSnapshotAccessTimeMs; + private long blacklistUntilMs; + private boolean pendingRefresh; + + public MediaPlaylistBundle(HlsUrl playlistUrl, long initialLastSnapshotAccessTimeMs) { + this.playlistUrl = playlistUrl; + lastSnapshotAccessTimeMs = initialLastSnapshotAccessTimeMs; + mediaPlaylistLoader = new Loader("HlsPlaylistTracker:MediaPlaylist"); + mediaPlaylistLoadable = new ParsingLoadable<>( + dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), + UriUtil.resolveToUri(masterPlaylist.baseUri, playlistUrl.url), C.DATA_TYPE_MANIFEST, + playlistParser); + } + + public HlsMediaPlaylist getPlaylistSnapshot() { + lastSnapshotAccessTimeMs = SystemClock.elapsedRealtime(); + return playlistSnapshot; + } + + public boolean isSnapshotValid() { + if (playlistSnapshot == null) { + return false; + } + long currentTimeMs = SystemClock.elapsedRealtime(); + long snapshotValidityDurationMs = Math.max(30000, C.usToMs(playlistSnapshot.durationUs)); + return playlistSnapshot.hasEndTag + || playlistSnapshot.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_EVENT + || playlistSnapshot.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_VOD + || lastSnapshotLoadMs + snapshotValidityDurationMs > currentTimeMs; + } + + public void release() { + mediaPlaylistLoader.release(); + } + + public void loadPlaylist() { + blacklistUntilMs = 0; + if (!pendingRefresh && !mediaPlaylistLoader.isLoading()) { + mediaPlaylistLoader.startLoading(mediaPlaylistLoadable, this, minRetryCount); + } + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + HlsPlaylist result = loadable.getResult(); + if (result instanceof HlsMediaPlaylist) { + processLoadedPlaylist((HlsMediaPlaylist) result); + eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } else { + onLoadError(loadable, elapsedRealtimeMs, loadDurationMs, + new ParserException("Loaded playlist has unexpected type.")); + } + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + eventDispatcher.loadCanceled(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + boolean isFatal = error instanceof ParserException; + eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded(), error, isFatal); + if (isFatal) { + return Loader.DONT_RETRY_FATAL; + } + boolean shouldRetry = true; + if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) { + blacklistUntilMs = + SystemClock.elapsedRealtime() + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS; + notifyPlaylistBlacklisting(playlistUrl, + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS); + shouldRetry = primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); + } + return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY; + } + + // Runnable implementation. + + @Override + public void run() { + pendingRefresh = false; + loadPlaylist(); + } + + // Internal methods. + + private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) { + HlsMediaPlaylist oldPlaylist = playlistSnapshot; + lastSnapshotLoadMs = SystemClock.elapsedRealtime(); + playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist); + long refreshDelayUs = C.TIME_UNSET; + if (playlistSnapshot != oldPlaylist) { + if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) { + refreshDelayUs = playlistSnapshot.targetDurationUs; + } + } else if (!playlistSnapshot.hasEndTag) { + refreshDelayUs = playlistSnapshot.targetDurationUs / 2; + } + if (refreshDelayUs != C.TIME_UNSET) { + // See HLS spec v20, section 6.3.4 for more information on media playlist refreshing. + pendingRefresh = playlistRefreshHandler.postDelayed(this, C.usToMs(refreshDelayUs)); + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java new file mode 100644 index 0000000..fc5b214 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.Track; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.TrackEncryptionBox; +import com.tangxiaolv.telegramgallery.exoplayer2.source.BehindLiveWindowException; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.Chunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkExtractorWrapper; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ContainerMediaChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.MediaChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.LoaderErrorThrower; +import java.io.IOException; +import java.util.List; + +/** + * A default {@link SsChunkSource} implementation. + */ +public class DefaultSsChunkSource implements SsChunkSource { + + public static final class Factory implements SsChunkSource.Factory { + + private final DataSource.Factory dataSourceFactory; + + public Factory(DataSource.Factory dataSourceFactory) { + this.dataSourceFactory = dataSourceFactory; + } + + @Override + public SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + SsManifest manifest, int elementIndex, TrackSelection trackSelection, + TrackEncryptionBox[] trackEncryptionBoxes) { + DataSource dataSource = dataSourceFactory.createDataSource(); + return new DefaultSsChunkSource(manifestLoaderErrorThrower, manifest, elementIndex, + trackSelection, dataSource, trackEncryptionBoxes); + } + + } + + private final LoaderErrorThrower manifestLoaderErrorThrower; + private final int elementIndex; + private final TrackSelection trackSelection; + private final ChunkExtractorWrapper[] extractorWrappers; + private final DataSource dataSource; + + private SsManifest manifest; + private int currentManifestChunkOffset; + + private IOException fatalError; + + /** + * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. + * @param manifest The initial manifest. + * @param elementIndex The index of the stream element in the manifest. + * @param trackSelection The track selection. + * @param dataSource A {@link DataSource} suitable for loading the media data. + * @param trackEncryptionBoxes Track encryption boxes for the stream. + */ + public DefaultSsChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, SsManifest manifest, + int elementIndex, TrackSelection trackSelection, DataSource dataSource, + TrackEncryptionBox[] trackEncryptionBoxes) { + this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; + this.manifest = manifest; + this.elementIndex = elementIndex; + this.trackSelection = trackSelection; + this.dataSource = dataSource; + + StreamElement streamElement = manifest.streamElements[elementIndex]; + + extractorWrappers = new ChunkExtractorWrapper[trackSelection.length()]; + for (int i = 0; i < extractorWrappers.length; i++) { + int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i); + Format format = streamElement.formats[manifestTrackIndex]; + int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : 0; + Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale, + C.TIME_UNSET, manifest.durationUs, format, Track.TRANSFORMATION_NONE, + trackEncryptionBoxes, nalUnitLengthFieldLength, null, null); + FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( + FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME + | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, null, track); + extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format); + } + } + + @Override + public void updateManifest(SsManifest newManifest) { + StreamElement currentElement = manifest.streamElements[elementIndex]; + int currentElementChunkCount = currentElement.chunkCount; + StreamElement newElement = newManifest.streamElements[elementIndex]; + if (currentElementChunkCount == 0 || newElement.chunkCount == 0) { + // There's no overlap between the old and new elements because at least one is empty. + currentManifestChunkOffset += currentElementChunkCount; + } else { + long currentElementEndTimeUs = currentElement.getStartTimeUs(currentElementChunkCount - 1) + + currentElement.getChunkDurationUs(currentElementChunkCount - 1); + long newElementStartTimeUs = newElement.getStartTimeUs(0); + if (currentElementEndTimeUs <= newElementStartTimeUs) { + // There's no overlap between the old and new elements. + currentManifestChunkOffset += currentElementChunkCount; + } else { + // The new element overlaps with the old one. + currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs); + } + } + manifest = newManifest; + } + + // ChunkSource implementation. + + @Override + public void maybeThrowError() throws IOException { + if (fatalError != null) { + throw fatalError; + } else { + manifestLoaderErrorThrower.maybeThrowError(); + } + } + + @Override + public int getPreferredQueueSize(long playbackPositionUs, List queue) { + if (fatalError != null || trackSelection.length() < 2) { + return queue.size(); + } + return trackSelection.evaluateQueueSize(playbackPositionUs, queue); + } + + @Override + public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) { + if (fatalError != null) { + return; + } + + long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; + trackSelection.updateSelectedTrack(bufferedDurationUs); + + StreamElement streamElement = manifest.streamElements[elementIndex]; + if (streamElement.chunkCount == 0) { + // There aren't any chunks for us to load. + out.endOfStream = !manifest.isLive; + return; + } + + int chunkIndex; + if (previous == null) { + chunkIndex = streamElement.getChunkIndex(playbackPositionUs); + } else { + chunkIndex = previous.getNextChunkIndex() - currentManifestChunkOffset; + if (chunkIndex < 0) { + // This is before the first chunk in the current manifest. + fatalError = new BehindLiveWindowException(); + return; + } + } + + if (chunkIndex >= streamElement.chunkCount) { + // This is beyond the last chunk in the current manifest. + out.endOfStream = !manifest.isLive; + return; + } + + long chunkStartTimeUs = streamElement.getStartTimeUs(chunkIndex); + long chunkEndTimeUs = chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex); + int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset; + + int trackSelectionIndex = trackSelection.getSelectedIndex(); + ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackSelectionIndex]; + + int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex); + Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex); + + out.chunk = newMediaChunk(trackSelection.getSelectedFormat(), dataSource, uri, null, + currentAbsoluteChunkIndex, chunkStartTimeUs, chunkEndTimeUs, + trackSelection.getSelectionReason(), trackSelection.getSelectionData(), extractorWrapper); + } + + @Override + public void onChunkLoadCompleted(Chunk chunk) { + // Do nothing. + } + + @Override + public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) { + return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, + trackSelection.indexOf(chunk.trackFormat), e); + } + + // Private methods. + + private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri, + String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, + int trackSelectionReason, Object trackSelectionData, ChunkExtractorWrapper extractorWrapper) { + DataSpec dataSpec = new DataSpec(uri, 0, C.LENGTH_UNSET, cacheKey); + // In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk. + // To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs. + long sampleOffsetUs = chunkStartTimeUs; + return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason, + trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, 1, sampleOffsetUs, + extractorWrapper); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsChunkSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsChunkSource.java new file mode 100644 index 0000000..8eae0db --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsChunkSource.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming; + +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.TrackEncryptionBox; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.LoaderErrorThrower; + +/** + * A {@link ChunkSource} for SmoothStreaming. + */ +public interface SsChunkSource extends ChunkSource { + + interface Factory { + + SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + SsManifest manifest, int elementIndex, TrackSelection trackSelection, + TrackEncryptionBox[] trackEncryptionBoxes); + + } + + void updateManifest(SsManifest newManifest); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsMediaPeriod.java new file mode 100644 index 0000000..0de7603 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming; + +import android.util.Base64; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.TrackEncryptionBox; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.CompositeSequenceableLoader; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaPeriod; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SequenceableLoader; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.ChunkSampleStream; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelection; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.LoaderErrorThrower; +import java.io.IOException; +import java.util.ArrayList; + +/** + * A SmoothStreaming {@link MediaPeriod}. + */ +/* package */ final class SsMediaPeriod implements MediaPeriod, + SequenceableLoader.Callback> { + + private static final int INITIALIZATION_VECTOR_SIZE = 8; + + private final SsChunkSource.Factory chunkSourceFactory; + private final LoaderErrorThrower manifestLoaderErrorThrower; + private final int minLoadableRetryCount; + private final EventDispatcher eventDispatcher; + private final Allocator allocator; + private final TrackGroupArray trackGroups; + private final TrackEncryptionBox[] trackEncryptionBoxes; + + private Callback callback; + private SsManifest manifest; + private ChunkSampleStream[] sampleStreams; + private CompositeSequenceableLoader sequenceableLoader; + + public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, + int minLoadableRetryCount, EventDispatcher eventDispatcher, + LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { + this.chunkSourceFactory = chunkSourceFactory; + this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventDispatcher = eventDispatcher; + this.allocator = allocator; + + trackGroups = buildTrackGroups(manifest); + ProtectionElement protectionElement = manifest.protectionElement; + if (protectionElement != null) { + byte[] keyId = getProtectionElementKeyId(protectionElement.data); + trackEncryptionBoxes = new TrackEncryptionBox[] { + new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)}; + } else { + trackEncryptionBoxes = null; + } + this.manifest = manifest; + sampleStreams = newSampleStreamArray(0); + sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + } + + public void updateManifest(SsManifest manifest) { + this.manifest = manifest; + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.getChunkSource().updateManifest(manifest); + } + callback.onContinueLoadingRequested(this); + } + + public void release() { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.release(); + } + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + callback.onPrepared(this); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + manifestLoaderErrorThrower.maybeThrowError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + ArrayList> sampleStreamsList = new ArrayList<>(); + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null) { + @SuppressWarnings("unchecked") + ChunkSampleStream stream = (ChunkSampleStream) streams[i]; + if (selections[i] == null || !mayRetainStreamFlags[i]) { + stream.release(); + streams[i] = null; + } else { + sampleStreamsList.add(stream); + } + } + if (streams[i] == null && selections[i] != null) { + ChunkSampleStream stream = buildSampleStream(selections[i], positionUs); + sampleStreamsList.add(stream); + streams[i] = stream; + streamResetFlags[i] = true; + } + } + sampleStreams = newSampleStreamArray(sampleStreamsList.size()); + sampleStreamsList.toArray(sampleStreams); + sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + return positionUs; + } + + @Override + public void discardBuffer(long positionUs) { + // Do nothing. + } + + @Override + public boolean continueLoading(long positionUs) { + return sequenceableLoader.continueLoading(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return sequenceableLoader.getNextLoadPositionUs(); + } + + @Override + public long readDiscontinuity() { + return C.TIME_UNSET; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (ChunkSampleStream sampleStream : sampleStreams) { + long rendererBufferedPositionUs = sampleStream.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public long seekToUs(long positionUs) { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.seekToUs(positionUs); + } + return positionUs; + } + + // SequenceableLoader.Callback implementation + + @Override + public void onContinueLoadingRequested(ChunkSampleStream sampleStream) { + callback.onContinueLoadingRequested(this); + } + + // Private methods. + + private ChunkSampleStream buildSampleStream(TrackSelection selection, + long positionUs) { + int streamElementIndex = trackGroups.indexOf(selection.getTrackGroup()); + SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoaderErrorThrower, + manifest, streamElementIndex, selection, trackEncryptionBoxes); + return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, null, + chunkSource, this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); + } + + private static TrackGroupArray buildTrackGroups(SsManifest manifest) { + TrackGroup[] trackGroups = new TrackGroup[manifest.streamElements.length]; + for (int i = 0; i < manifest.streamElements.length; i++) { + trackGroups[i] = new TrackGroup(manifest.streamElements[i].formats); + } + return new TrackGroupArray(trackGroups); + } + + @SuppressWarnings("unchecked") + private static ChunkSampleStream[] newSampleStreamArray(int length) { + return new ChunkSampleStream[length]; + } + + private static byte[] getProtectionElementKeyId(byte[] initData) { + StringBuilder initDataStringBuilder = new StringBuilder(); + for (int i = 0; i < initData.length; i += 2) { + initDataStringBuilder.append((char) initData[i]); + } + String initDataString = initDataStringBuilder.toString(); + String keyIdString = initDataString.substring( + initDataString.indexOf("") + 5, initDataString.indexOf("")); + byte[] keyId = Base64.decode(keyIdString, Base64.DEFAULT); + swap(keyId, 0, 3); + swap(keyId, 1, 2); + swap(keyId, 4, 5); + swap(keyId, 6, 7); + return keyId; + } + + private static void swap(byte[] data, int firstPosition, int secondPosition) { + byte temp = data[firstPosition]; + data[firstPosition] = data[secondPosition]; + data[secondPosition] = temp; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsMediaSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsMediaSource.java new file mode 100644 index 0000000..d443eb8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming; + +import android.net.Uri; +import android.os.Handler; +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener; +import com.tangxiaolv.telegramgallery.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaPeriod; +import com.tangxiaolv.telegramgallery.exoplayer2.source.MediaSource; +import com.tangxiaolv.telegramgallery.exoplayer2.source.SinglePeriodTimeline; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Allocator; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.LoaderErrorThrower; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.ParsingLoadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.ArrayList; + +/** + * A SmoothStreaming {@link MediaSource}. + */ +public final class SsMediaSource implements MediaSource, + Loader.Callback> { + + /** + * The default minimum number of times to retry loading data prior to failing. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + /** + * The default presentation delay for live streams. The presentation delay is the duration by + * which the default start position precedes the end of the live window. + */ + public static final long DEFAULT_LIVE_PRESENTATION_DELAY_MS = 30000; + + /** + * The minimum period between manifest refreshes. + */ + private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000; + /** + * The minimum default start position for live streams, relative to the start of the live window. + */ + private static final long MIN_LIVE_DEFAULT_START_POSITION_US = 5000000; + + private final Uri manifestUri; + private final DataSource.Factory manifestDataSourceFactory; + private final SsChunkSource.Factory chunkSourceFactory; + private final int minLoadableRetryCount; + private final long livePresentationDelayMs; + private final EventDispatcher eventDispatcher; + private final ParsingLoadable.Parser manifestParser; + private final ArrayList mediaPeriods; + + private Listener sourceListener; + private DataSource manifestDataSource; + private Loader manifestLoader; + private LoaderErrorThrower manifestLoaderErrorThrower; + + private long manifestLoadStartTimestamp; + private SsManifest manifest; + + private Handler manifestRefreshHandler; + + /** + * Constructs an instance to play a given {@link SsManifest}, which must not be live. + * + * @param manifest The manifest. {@link SsManifest#isLive} must be false. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, + Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { + this(manifest, chunkSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, + eventHandler, eventListener); + } + + /** + * Constructs an instance to play a given {@link SsManifest}, which must not be live. + * + * @param manifest The manifest. {@link SsManifest#isLive} must be false. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, + int minLoadableRetryCount, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifest, null, null, null, chunkSourceFactory, minLoadableRetryCount, + DEFAULT_LIVE_PRESENTATION_DELAY_MS, eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be live or + * on-demand. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + SsChunkSource.Factory chunkSourceFactory, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, manifestDataSourceFactory, chunkSourceFactory, + DEFAULT_MIN_LOADABLE_RETRY_COUNT, DEFAULT_LIVE_PRESENTATION_DELAY_MS, eventHandler, + eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be live or + * on-demand. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the + * default start position should precede the end of the live window. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, manifestDataSourceFactory, new SsManifestParser(), chunkSourceFactory, + minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be live or + * on-demand. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param manifestParser A parser for loaded manifest data. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the + * default start position should precede the end of the live window. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + ParsingLoadable.Parser manifestParser, + SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, + minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + } + + private SsMediaSource(SsManifest manifest, Uri manifestUri, + DataSource.Factory manifestDataSourceFactory, + ParsingLoadable.Parser manifestParser, + SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + Assertions.checkState(manifest == null || !manifest.isLive); + this.manifest = manifest; + this.manifestUri = manifestUri == null ? null + : Util.toLowerInvariant(manifestUri.getLastPathSegment()).equals("manifest") ? manifestUri + : Uri.withAppendedPath(manifestUri, "Manifest"); + this.manifestDataSourceFactory = manifestDataSourceFactory; + this.manifestParser = manifestParser; + this.chunkSourceFactory = chunkSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.livePresentationDelayMs = livePresentationDelayMs; + this.eventDispatcher = new EventDispatcher(eventHandler, eventListener); + mediaPeriods = new ArrayList<>(); + } + + // MediaSource implementation. + + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + sourceListener = listener; + if (manifest != null) { + manifestLoaderErrorThrower = new LoaderErrorThrower.Dummy(); + processManifest(); + } else { + manifestDataSource = manifestDataSourceFactory.createDataSource(); + manifestLoader = new Loader("Loader:Manifest"); + manifestLoaderErrorThrower = manifestLoader; + manifestRefreshHandler = new Handler(); + startLoadingManifest(); + } + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + manifestLoaderErrorThrower.maybeThrowError(); + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + Assertions.checkArgument(index == 0); + SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount, + eventDispatcher, manifestLoaderErrorThrower, allocator); + mediaPeriods.add(period); + return period; + } + + @Override + public void releasePeriod(MediaPeriod period) { + ((SsMediaPeriod) period).release(); + mediaPeriods.remove(period); + } + + @Override + public void releaseSource() { + sourceListener = null; + manifest = null; + manifestDataSource = null; + manifestLoadStartTimestamp = 0; + if (manifestLoader != null) { + manifestLoader.release(); + manifestLoader = null; + } + if (manifestRefreshHandler != null) { + manifestRefreshHandler.removeCallbacksAndMessages(null); + manifestRefreshHandler = null; + } + } + + // Loader.Callback implementation + + @Override + public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + manifest = loadable.getResult(); + manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs; + processManifest(); + scheduleManifestRefresh(); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + boolean isFatal = error instanceof ParserException; + eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded(), error, isFatal); + return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; + } + + // Internal methods + + private void processManifest() { + for (int i = 0; i < mediaPeriods.size(); i++) { + mediaPeriods.get(i).updateManifest(manifest); + } + Timeline timeline; + if (manifest.isLive) { + long startTimeUs = Long.MAX_VALUE; + long endTimeUs = Long.MIN_VALUE; + for (int i = 0; i < manifest.streamElements.length; i++) { + StreamElement element = manifest.streamElements[i]; + if (element.chunkCount > 0) { + startTimeUs = Math.min(startTimeUs, element.getStartTimeUs(0)); + endTimeUs = Math.max(endTimeUs, element.getStartTimeUs(element.chunkCount - 1) + + element.getChunkDurationUs(element.chunkCount - 1)); + } + } + if (startTimeUs == Long.MAX_VALUE) { + timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); + } else { + if (manifest.dvrWindowLengthUs != C.TIME_UNSET + && manifest.dvrWindowLengthUs > 0) { + startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); + } + long durationUs = endTimeUs - startTimeUs; + long defaultStartPositionUs = durationUs - C.msToUs(livePresentationDelayMs); + if (defaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) { + // The default start position is too close to the start of the live window. Set it to the + // minimum default start position provided the window is at least twice as big. Else set + // it to the middle of the window. + defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, durationUs / 2); + } + timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs, + defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */); + } + } else { + boolean isSeekable = manifest.durationUs != C.TIME_UNSET; + timeline = new SinglePeriodTimeline(manifest.durationUs, isSeekable); + } + sourceListener.onSourceInfoRefreshed(timeline, manifest); + } + + private void scheduleManifestRefresh() { + if (!manifest.isLive) { + return; + } + long nextLoadTimestamp = manifestLoadStartTimestamp + MINIMUM_MANIFEST_REFRESH_PERIOD_MS; + long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime()); + manifestRefreshHandler.postDelayed(new Runnable() { + @Override + public void run() { + startLoadingManifest(); + } + }, delayUntilNextLoad); + } + + private void startLoadingManifest() { + ParsingLoadable loadable = new ParsingLoadable<>(manifestDataSource, + manifestUri, C.DATA_TYPE_MANIFEST, manifestParser); + long elapsedRealtimeMs = manifestLoader.startLoading(loadable, this, minLoadableRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/manifest/SsManifest.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/manifest/SsManifest.java new file mode 100644 index 0000000..272d0c1 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/manifest/SsManifest.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.UriUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.List; +import java.util.UUID; + +/** + * Represents a SmoothStreaming manifest. + * + * @see + * IIS Smooth Streaming Client Manifest Format + */ +public class SsManifest { + + public static final int UNSET_LOOKAHEAD = -1; + + /** + * The client manifest major version. + */ + public final int majorVersion; + + /** + * The client manifest minor version. + */ + public final int minorVersion; + + /** + * The number of fragments in a lookahead, or {@link #UNSET_LOOKAHEAD} if the lookahead is + * unspecified. + */ + public final int lookAheadCount; + + /** + * Whether the manifest describes a live presentation still in progress. + */ + public final boolean isLive; + + /** + * Content protection information, or null if the content is not protected. + */ + public final ProtectionElement protectionElement; + + /** + * The contained stream elements. + */ + public final StreamElement[] streamElements; + + /** + * The overall presentation duration of the media in microseconds, or {@link C#TIME_UNSET} + * if the duration is unknown. + */ + public final long durationUs; + + /** + * The length of the trailing window for a live broadcast in microseconds, or + * {@link C#TIME_UNSET} if the stream is not live or if the window length is unspecified. + */ + public final long dvrWindowLengthUs; + + /** + * @param majorVersion The client manifest major version. + * @param minorVersion The client manifest minor version. + * @param timescale The timescale of the media as the number of units that pass in one second. + * @param duration The overall presentation duration in units of the timescale attribute, or 0 + * if the duration is unknown. + * @param dvrWindowLength The length of the trailing window in units of the timescale attribute, + * or 0 if this attribute is unspecified or not applicable. + * @param lookAheadCount The number of fragments in a lookahead, or {@link #UNSET_LOOKAHEAD} if + * this attribute is unspecified or not applicable. + * @param isLive True if the manifest describes a live presentation still in progress. False + * otherwise. + * @param protectionElement Content protection information, or null if the content is not + * protected. + * @param streamElements The contained stream elements. + */ + public SsManifest(int majorVersion, int minorVersion, long timescale, long duration, + long dvrWindowLength, int lookAheadCount, boolean isLive, ProtectionElement protectionElement, + StreamElement[] streamElements) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + this.lookAheadCount = lookAheadCount; + this.isLive = isLive; + this.protectionElement = protectionElement; + this.streamElements = streamElements; + dvrWindowLengthUs = dvrWindowLength == 0 ? C.TIME_UNSET + : Util.scaleLargeTimestamp(dvrWindowLength, C.MICROS_PER_SECOND, timescale); + durationUs = duration == 0 ? C.TIME_UNSET + : Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, timescale); + } + + /** + * Represents a protection element containing a single header. + */ + public static class ProtectionElement { + + public final UUID uuid; + public final byte[] data; + + public ProtectionElement(UUID uuid, byte[] data) { + this.uuid = uuid; + this.data = data; + } + + } + + /** + * Represents a StreamIndex element. + */ + public static class StreamElement { + + private static final String URL_PLACEHOLDER_START_TIME_1 = "{start time}"; + private static final String URL_PLACEHOLDER_START_TIME_2 = "{start_time}"; + private static final String URL_PLACEHOLDER_BITRATE_1 = "{bitrate}"; + private static final String URL_PLACEHOLDER_BITRATE_2 = "{Bitrate}"; + + public final int type; + public final String subType; + public final long timescale; + public final String name; + public final int maxWidth; + public final int maxHeight; + public final int displayWidth; + public final int displayHeight; + public final String language; + public final Format[] formats; + public final int chunkCount; + + private final String baseUri; + private final String chunkTemplate; + + private final List chunkStartTimes; + private final long[] chunkStartTimesUs; + private final long lastChunkDurationUs; + + public StreamElement(String baseUri, String chunkTemplate, int type, String subType, + long timescale, String name, int maxWidth, int maxHeight, int displayWidth, + int displayHeight, String language, Format[] formats, List chunkStartTimes, + long lastChunkDuration) { + this.baseUri = baseUri; + this.chunkTemplate = chunkTemplate; + this.type = type; + this.subType = subType; + this.timescale = timescale; + this.name = name; + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this.displayWidth = displayWidth; + this.displayHeight = displayHeight; + this.language = language; + this.formats = formats; + this.chunkCount = chunkStartTimes.size(); + this.chunkStartTimes = chunkStartTimes; + lastChunkDurationUs = + Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale); + chunkStartTimesUs = + Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale); + } + + /** + * Returns the index of the chunk that contains the specified time. + * + * @param timeUs The time in microseconds. + * @return The index of the corresponding chunk. + */ + public int getChunkIndex(long timeUs) { + return Util.binarySearchFloor(chunkStartTimesUs, timeUs, true, true); + } + + /** + * Returns the start time of the specified chunk. + * + * @param chunkIndex The index of the chunk. + * @return The start time of the chunk, in microseconds. + */ + public long getStartTimeUs(int chunkIndex) { + return chunkStartTimesUs[chunkIndex]; + } + + /** + * Returns the duration of the specified chunk. + * + * @param chunkIndex The index of the chunk. + * @return The duration of the chunk, in microseconds. + */ + public long getChunkDurationUs(int chunkIndex) { + return (chunkIndex == chunkCount - 1) ? lastChunkDurationUs + : chunkStartTimesUs[chunkIndex + 1] - chunkStartTimesUs[chunkIndex]; + } + + /** + * Builds a uri for requesting the specified chunk of the specified track. + * + * @param track The index of the track for which to build the URL. + * @param chunkIndex The index of the chunk for which to build the URL. + * @return The request uri. + */ + public Uri buildRequestUri(int track, int chunkIndex) { + Assertions.checkState(formats != null); + Assertions.checkState(chunkStartTimes != null); + Assertions.checkState(chunkIndex < chunkStartTimes.size()); + String bitrateString = Integer.toString(formats[track].bitrate); + String startTimeString = chunkStartTimes.get(chunkIndex).toString(); + String chunkUrl = chunkTemplate + .replace(URL_PLACEHOLDER_BITRATE_1, bitrateString) + .replace(URL_PLACEHOLDER_BITRATE_2, bitrateString) + .replace(URL_PLACEHOLDER_START_TIME_1, startTimeString) + .replace(URL_PLACEHOLDER_START_TIME_2, startTimeString); + return UriUtil.resolveToUri(baseUri, chunkUrl); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java new file mode 100644 index 0000000..0adb5c7 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData.SchemeData; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.mp4.PsshAtomUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; +import com.tangxiaolv.telegramgallery.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.ParsingLoadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.CodecSpecificDataUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +/** + * Parses SmoothStreaming client manifests. + * + * @see + * IIS Smooth Streaming Client Manifest Format + */ +public class SsManifestParser implements ParsingLoadable.Parser { + + private final XmlPullParserFactory xmlParserFactory; + + public SsManifestParser() { + try { + xmlParserFactory = XmlPullParserFactory.newInstance(); + } catch (XmlPullParserException e) { + throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); + } + } + + @Override + public SsManifest parse(Uri uri, InputStream inputStream) throws IOException { + try { + XmlPullParser xmlParser = xmlParserFactory.newPullParser(); + xmlParser.setInput(inputStream, null); + SmoothStreamingMediaParser smoothStreamingMediaParser = + new SmoothStreamingMediaParser(null, uri.toString()); + return (SsManifest) smoothStreamingMediaParser.parse(xmlParser); + } catch (XmlPullParserException e) { + throw new ParserException(e); + } + } + + /** + * Thrown if a required field is missing. + */ + public static class MissingFieldException extends ParserException { + + public MissingFieldException(String fieldName) { + super("Missing required field: " + fieldName); + } + + } + + /** + * A base class for parsers that parse components of a smooth streaming manifest. + */ + private abstract static class ElementParser { + + private final String baseUri; + private final String tag; + + private final ElementParser parent; + private final List> normalizedAttributes; + + public ElementParser(ElementParser parent, String baseUri, String tag) { + this.parent = parent; + this.baseUri = baseUri; + this.tag = tag; + this.normalizedAttributes = new LinkedList<>(); + } + + public final Object parse(XmlPullParser xmlParser) throws XmlPullParserException, IOException { + String tagName; + boolean foundStartTag = false; + int skippingElementDepth = 0; + while (true) { + int eventType = xmlParser.getEventType(); + switch (eventType) { + case XmlPullParser.START_TAG: + tagName = xmlParser.getName(); + if (tag.equals(tagName)) { + foundStartTag = true; + parseStartTag(xmlParser); + } else if (foundStartTag) { + if (skippingElementDepth > 0) { + skippingElementDepth++; + } else if (handleChildInline(tagName)) { + parseStartTag(xmlParser); + } else { + ElementParser childElementParser = newChildParser(this, tagName, baseUri); + if (childElementParser == null) { + skippingElementDepth = 1; + } else { + addChild(childElementParser.parse(xmlParser)); + } + } + } + break; + case XmlPullParser.TEXT: + if (foundStartTag && skippingElementDepth == 0) { + parseText(xmlParser); + } + break; + case XmlPullParser.END_TAG: + if (foundStartTag) { + if (skippingElementDepth > 0) { + skippingElementDepth--; + } else { + tagName = xmlParser.getName(); + parseEndTag(xmlParser); + if (!handleChildInline(tagName)) { + return build(); + } + } + } + break; + case XmlPullParser.END_DOCUMENT: + return null; + default: + // Do nothing. + break; + } + xmlParser.next(); + } + } + + private ElementParser newChildParser(ElementParser parent, String name, String baseUri) { + if (QualityLevelParser.TAG.equals(name)) { + return new QualityLevelParser(parent, baseUri); + } else if (ProtectionParser.TAG.equals(name)) { + return new ProtectionParser(parent, baseUri); + } else if (StreamIndexParser.TAG.equals(name)) { + return new StreamIndexParser(parent, baseUri); + } + return null; + } + + /** + * Stash an attribute that may be normalized at this level. In other words, an attribute that + * may have been pulled up from the child elements because its value was the same in all + * children. + *

    + * Stashing an attribute allows child element parsers to retrieve the values of normalized + * attributes using {@link #getNormalizedAttribute(String)}. + * + * @param key The name of the attribute. + * @param value The value of the attribute. + */ + protected final void putNormalizedAttribute(String key, Object value) { + normalizedAttributes.add(Pair.create(key, value)); + } + + /** + * Attempt to retrieve a stashed normalized attribute. If there is no stashed attribute with + * the provided name, the parent element parser will be queried, and so on up the chain. + * + * @param key The name of the attribute. + * @return The stashed value, or null if the attribute was not be found. + */ + protected final Object getNormalizedAttribute(String key) { + for (int i = 0; i < normalizedAttributes.size(); i++) { + Pair pair = normalizedAttributes.get(i); + if (pair.first.equals(key)) { + return pair.second; + } + } + return parent == null ? null : parent.getNormalizedAttribute(key); + } + + /** + * Whether this {@link ElementParser} parses a child element inline. + * + * @param tagName The name of the child element. + * @return Whether the child is parsed inline. + */ + protected boolean handleChildInline(String tagName) { + return false; + } + + /** + * @param xmlParser The underlying {@link XmlPullParser} + * @throws ParserException + */ + protected void parseStartTag(XmlPullParser xmlParser) throws ParserException { + // Do nothing. + } + + /** + * @param xmlParser The underlying {@link XmlPullParser} + */ + protected void parseText(XmlPullParser xmlParser) { + // Do nothing. + } + + /** + * @param xmlParser The underlying {@link XmlPullParser} + */ + protected void parseEndTag(XmlPullParser xmlParser) { + // Do nothing. + } + + /** + * @param parsedChild A parsed child object. + */ + protected void addChild(Object parsedChild) { + // Do nothing. + } + + protected abstract Object build(); + + protected final String parseRequiredString(XmlPullParser parser, String key) + throws MissingFieldException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + return value; + } else { + throw new MissingFieldException(key); + } + } + + protected final int parseInt(XmlPullParser parser, String key, int defaultValue) + throws ParserException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new ParserException(e); + } + } else { + return defaultValue; + } + } + + protected final int parseRequiredInt(XmlPullParser parser, String key) throws ParserException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new ParserException(e); + } + } else { + throw new MissingFieldException(key); + } + } + + protected final long parseLong(XmlPullParser parser, String key, long defaultValue) + throws ParserException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new ParserException(e); + } + } else { + return defaultValue; + } + } + + protected final long parseRequiredLong(XmlPullParser parser, String key) + throws ParserException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new ParserException(e); + } + } else { + throw new MissingFieldException(key); + } + } + + protected final boolean parseBoolean(XmlPullParser parser, String key, boolean defaultValue) { + String value = parser.getAttributeValue(null, key); + if (value != null) { + return Boolean.parseBoolean(value); + } else { + return defaultValue; + } + } + + } + + private static class SmoothStreamingMediaParser extends ElementParser { + + public static final String TAG = "SmoothStreamingMedia"; + + private static final String KEY_MAJOR_VERSION = "MajorVersion"; + private static final String KEY_MINOR_VERSION = "MinorVersion"; + private static final String KEY_TIME_SCALE = "TimeScale"; + private static final String KEY_DVR_WINDOW_LENGTH = "DVRWindowLength"; + private static final String KEY_DURATION = "Duration"; + private static final String KEY_LOOKAHEAD_COUNT = "LookaheadCount"; + private static final String KEY_IS_LIVE = "IsLive"; + + private final List streamElements; + + private int majorVersion; + private int minorVersion; + private long timescale; + private long duration; + private long dvrWindowLength; + private int lookAheadCount; + private boolean isLive; + private ProtectionElement protectionElement; + + public SmoothStreamingMediaParser(ElementParser parent, String baseUri) { + super(parent, baseUri, TAG); + lookAheadCount = SsManifest.UNSET_LOOKAHEAD; + protectionElement = null; + streamElements = new LinkedList<>(); + } + + @Override + public void parseStartTag(XmlPullParser parser) throws ParserException { + majorVersion = parseRequiredInt(parser, KEY_MAJOR_VERSION); + minorVersion = parseRequiredInt(parser, KEY_MINOR_VERSION); + timescale = parseLong(parser, KEY_TIME_SCALE, 10000000L); + duration = parseRequiredLong(parser, KEY_DURATION); + dvrWindowLength = parseLong(parser, KEY_DVR_WINDOW_LENGTH, 0); + lookAheadCount = parseInt(parser, KEY_LOOKAHEAD_COUNT, SsManifest.UNSET_LOOKAHEAD); + isLive = parseBoolean(parser, KEY_IS_LIVE, false); + putNormalizedAttribute(KEY_TIME_SCALE, timescale); + } + + @Override + public void addChild(Object child) { + if (child instanceof StreamElement) { + streamElements.add((StreamElement) child); + } else if (child instanceof ProtectionElement) { + Assertions.checkState(protectionElement == null); + protectionElement = (ProtectionElement) child; + } + } + + @Override + public Object build() { + StreamElement[] streamElementArray = new StreamElement[streamElements.size()]; + streamElements.toArray(streamElementArray); + if (protectionElement != null) { + DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, + MimeTypes.VIDEO_MP4, protectionElement.data)); + for (StreamElement streamElement : streamElementArray) { + for (int i = 0; i < streamElement.formats.length; i++) { + streamElement.formats[i] = streamElement.formats[i].copyWithDrmInitData(drmInitData); + } + } + } + return new SsManifest(majorVersion, minorVersion, timescale, duration, dvrWindowLength, + lookAheadCount, isLive, protectionElement, streamElementArray); + } + + } + + private static class ProtectionParser extends ElementParser { + + public static final String TAG = "Protection"; + public static final String TAG_PROTECTION_HEADER = "ProtectionHeader"; + + public static final String KEY_SYSTEM_ID = "SystemID"; + + private boolean inProtectionHeader; + private UUID uuid; + private byte[] initData; + + public ProtectionParser(ElementParser parent, String baseUri) { + super(parent, baseUri, TAG); + } + + @Override + public boolean handleChildInline(String tag) { + return TAG_PROTECTION_HEADER.equals(tag); + } + + @Override + public void parseStartTag(XmlPullParser parser) { + if (TAG_PROTECTION_HEADER.equals(parser.getName())) { + inProtectionHeader = true; + String uuidString = parser.getAttributeValue(null, KEY_SYSTEM_ID); + uuidString = stripCurlyBraces(uuidString); + uuid = UUID.fromString(uuidString); + } + } + + @Override + public void parseText(XmlPullParser parser) { + if (inProtectionHeader) { + initData = Base64.decode(parser.getText(), Base64.DEFAULT); + } + } + + @Override + public void parseEndTag(XmlPullParser parser) { + if (TAG_PROTECTION_HEADER.equals(parser.getName())) { + inProtectionHeader = false; + } + } + + @Override + public Object build() { + return new ProtectionElement(uuid, PsshAtomUtil.buildPsshAtom(uuid, initData)); + } + + private static String stripCurlyBraces(String uuidString) { + if (uuidString.charAt(0) == '{' && uuidString.charAt(uuidString.length() - 1) == '}') { + uuidString = uuidString.substring(1, uuidString.length() - 1); + } + return uuidString; + } + } + + private static class StreamIndexParser extends ElementParser { + + public static final String TAG = "StreamIndex"; + private static final String TAG_STREAM_FRAGMENT = "c"; + + private static final String KEY_TYPE = "Type"; + private static final String KEY_TYPE_AUDIO = "audio"; + private static final String KEY_TYPE_VIDEO = "video"; + private static final String KEY_TYPE_TEXT = "text"; + private static final String KEY_SUB_TYPE = "Subtype"; + private static final String KEY_NAME = "Name"; + private static final String KEY_URL = "Url"; + private static final String KEY_MAX_WIDTH = "MaxWidth"; + private static final String KEY_MAX_HEIGHT = "MaxHeight"; + private static final String KEY_DISPLAY_WIDTH = "DisplayWidth"; + private static final String KEY_DISPLAY_HEIGHT = "DisplayHeight"; + private static final String KEY_LANGUAGE = "Language"; + private static final String KEY_TIME_SCALE = "TimeScale"; + + private static final String KEY_FRAGMENT_DURATION = "d"; + private static final String KEY_FRAGMENT_START_TIME = "t"; + private static final String KEY_FRAGMENT_REPEAT_COUNT = "r"; + + private final String baseUri; + private final List formats; + + private int type; + private String subType; + private long timescale; + private String name; + private String url; + private int maxWidth; + private int maxHeight; + private int displayWidth; + private int displayHeight; + private String language; + private ArrayList startTimes; + + private long lastChunkDuration; + + public StreamIndexParser(ElementParser parent, String baseUri) { + super(parent, baseUri, TAG); + this.baseUri = baseUri; + formats = new LinkedList<>(); + } + + @Override + public boolean handleChildInline(String tag) { + return TAG_STREAM_FRAGMENT.equals(tag); + } + + @Override + public void parseStartTag(XmlPullParser parser) throws ParserException { + if (TAG_STREAM_FRAGMENT.equals(parser.getName())) { + parseStreamFragmentStartTag(parser); + } else { + parseStreamElementStartTag(parser); + } + } + + private void parseStreamFragmentStartTag(XmlPullParser parser) throws ParserException { + int chunkIndex = startTimes.size(); + long startTime = parseLong(parser, KEY_FRAGMENT_START_TIME, C.TIME_UNSET); + if (startTime == C.TIME_UNSET) { + if (chunkIndex == 0) { + // Assume the track starts at t = 0. + startTime = 0; + } else if (lastChunkDuration != C.INDEX_UNSET) { + // Infer the start time from the previous chunk's start time and duration. + startTime = startTimes.get(chunkIndex - 1) + lastChunkDuration; + } else { + // We don't have the start time, and we're unable to infer it. + throw new ParserException("Unable to infer start time"); + } + } + chunkIndex++; + startTimes.add(startTime); + lastChunkDuration = parseLong(parser, KEY_FRAGMENT_DURATION, C.TIME_UNSET); + // Handle repeated chunks. + long repeatCount = parseLong(parser, KEY_FRAGMENT_REPEAT_COUNT, 1L); + if (repeatCount > 1 && lastChunkDuration == C.TIME_UNSET) { + throw new ParserException("Repeated chunk with unspecified duration"); + } + for (int i = 1; i < repeatCount; i++) { + chunkIndex++; + startTimes.add(startTime + (lastChunkDuration * i)); + } + } + + private void parseStreamElementStartTag(XmlPullParser parser) throws ParserException { + type = parseType(parser); + putNormalizedAttribute(KEY_TYPE, type); + if (type == C.TRACK_TYPE_TEXT) { + subType = parseRequiredString(parser, KEY_SUB_TYPE); + } else { + subType = parser.getAttributeValue(null, KEY_SUB_TYPE); + } + name = parser.getAttributeValue(null, KEY_NAME); + url = parseRequiredString(parser, KEY_URL); + maxWidth = parseInt(parser, KEY_MAX_WIDTH, Format.NO_VALUE); + maxHeight = parseInt(parser, KEY_MAX_HEIGHT, Format.NO_VALUE); + displayWidth = parseInt(parser, KEY_DISPLAY_WIDTH, Format.NO_VALUE); + displayHeight = parseInt(parser, KEY_DISPLAY_HEIGHT, Format.NO_VALUE); + language = parser.getAttributeValue(null, KEY_LANGUAGE); + putNormalizedAttribute(KEY_LANGUAGE, language); + timescale = parseInt(parser, KEY_TIME_SCALE, -1); + if (timescale == -1) { + timescale = (Long) getNormalizedAttribute(KEY_TIME_SCALE); + } + startTimes = new ArrayList<>(); + } + + private int parseType(XmlPullParser parser) throws ParserException { + String value = parser.getAttributeValue(null, KEY_TYPE); + if (value != null) { + if (KEY_TYPE_AUDIO.equalsIgnoreCase(value)) { + return C.TRACK_TYPE_AUDIO; + } else if (KEY_TYPE_VIDEO.equalsIgnoreCase(value)) { + return C.TRACK_TYPE_VIDEO; + } else if (KEY_TYPE_TEXT.equalsIgnoreCase(value)) { + return C.TRACK_TYPE_TEXT; + } else { + throw new ParserException("Invalid key value[" + value + "]"); + } + } + throw new MissingFieldException(KEY_TYPE); + } + + @Override + public void addChild(Object child) { + if (child instanceof Format) { + formats.add((Format) child); + } + } + + @Override + public Object build() { + Format[] formatArray = new Format[formats.size()]; + formats.toArray(formatArray); + return new StreamElement(baseUri, url, type, subType, timescale, name, maxWidth, maxHeight, + displayWidth, displayHeight, language, formatArray, startTimes, lastChunkDuration); + } + + } + + private static class QualityLevelParser extends ElementParser { + + public static final String TAG = "QualityLevel"; + + private static final String KEY_INDEX = "Index"; + private static final String KEY_BITRATE = "Bitrate"; + private static final String KEY_CODEC_PRIVATE_DATA = "CodecPrivateData"; + private static final String KEY_SAMPLING_RATE = "SamplingRate"; + private static final String KEY_CHANNELS = "Channels"; + private static final String KEY_FOUR_CC = "FourCC"; + private static final String KEY_TYPE = "Type"; + private static final String KEY_LANGUAGE = "Language"; + private static final String KEY_MAX_WIDTH = "MaxWidth"; + private static final String KEY_MAX_HEIGHT = "MaxHeight"; + + private Format format; + + public QualityLevelParser(ElementParser parent, String baseUri) { + super(parent, baseUri, TAG); + } + + @Override + public void parseStartTag(XmlPullParser parser) throws ParserException { + int type = (Integer) getNormalizedAttribute(KEY_TYPE); + String id = parser.getAttributeValue(null, KEY_INDEX); + int bitrate = parseRequiredInt(parser, KEY_BITRATE); + String sampleMimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC)); + + if (type == C.TRACK_TYPE_VIDEO) { + int width = parseRequiredInt(parser, KEY_MAX_WIDTH); + int height = parseRequiredInt(parser, KEY_MAX_HEIGHT); + List codecSpecificData = buildCodecSpecificData( + parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA)); + format = Format.createVideoContainerFormat(id, MimeTypes.VIDEO_MP4, sampleMimeType, null, + bitrate, width, height, Format.NO_VALUE, codecSpecificData, 0); + } else if (type == C.TRACK_TYPE_AUDIO) { + sampleMimeType = sampleMimeType == null ? MimeTypes.AUDIO_AAC : sampleMimeType; + int channels = parseRequiredInt(parser, KEY_CHANNELS); + int samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE); + List codecSpecificData = buildCodecSpecificData( + parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA)); + if (codecSpecificData.isEmpty() && MimeTypes.AUDIO_AAC.equals(sampleMimeType)) { + codecSpecificData = Collections.singletonList( + CodecSpecificDataUtil.buildAacLcAudioSpecificConfig(samplingRate, channels)); + } + String language = (String) getNormalizedAttribute(KEY_LANGUAGE); + format = Format.createAudioContainerFormat(id, MimeTypes.AUDIO_MP4, sampleMimeType, null, + bitrate, channels, samplingRate, codecSpecificData, 0, language); + } else if (type == C.TRACK_TYPE_TEXT) { + String language = (String) getNormalizedAttribute(KEY_LANGUAGE); + format = Format.createTextContainerFormat(id, MimeTypes.APPLICATION_MP4, sampleMimeType, + null, bitrate, 0, language); + } else { + format = Format.createContainerFormat(id, MimeTypes.APPLICATION_MP4, sampleMimeType, null, + bitrate, 0, null); + } + } + + @Override + public Object build() { + return format; + } + + private static List buildCodecSpecificData(String codecSpecificDataString) { + ArrayList csd = new ArrayList<>(); + if (!TextUtils.isEmpty(codecSpecificDataString)) { + byte[] codecPrivateData = Util.getBytesFromHexString(codecSpecificDataString); + byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData); + if (split == null) { + csd.add(codecPrivateData); + } else { + Collections.addAll(csd, split); + } + } + return csd; + } + + private static String fourCCToMimeType(String fourCC) { + if (fourCC.equalsIgnoreCase("H264") || fourCC.equalsIgnoreCase("X264") + || fourCC.equalsIgnoreCase("AVC1") || fourCC.equalsIgnoreCase("DAVC")) { + return MimeTypes.VIDEO_H264; + } else if (fourCC.equalsIgnoreCase("AAC") || fourCC.equalsIgnoreCase("AACL") + || fourCC.equalsIgnoreCase("AACH") || fourCC.equalsIgnoreCase("AACP")) { + return MimeTypes.AUDIO_AAC; + } else if (fourCC.equalsIgnoreCase("TTML")) { + return MimeTypes.APPLICATION_TTML; + } else if (fourCC.equalsIgnoreCase("ac-3") || fourCC.equalsIgnoreCase("dac3")) { + return MimeTypes.AUDIO_AC3; + } else if (fourCC.equalsIgnoreCase("ec-3") || fourCC.equalsIgnoreCase("dec3")) { + return MimeTypes.AUDIO_E_AC3; + } else if (fourCC.equalsIgnoreCase("dtsc")) { + return MimeTypes.AUDIO_DTS; + } else if (fourCC.equalsIgnoreCase("dtsh") || fourCC.equalsIgnoreCase("dtsl")) { + return MimeTypes.AUDIO_DTS_HD; + } else if (fourCC.equalsIgnoreCase("dtse")) { + return MimeTypes.AUDIO_DTS_EXPRESS; + } else if (fourCC.equalsIgnoreCase("opus")) { + return MimeTypes.AUDIO_OPUS; + } + return null; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/CaptionStyleCompat.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/CaptionStyleCompat.java new file mode 100644 index 0000000..16c8318 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/CaptionStyleCompat.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import android.annotation.TargetApi; +import android.graphics.Color; +import android.graphics.Typeface; +import android.support.annotation.IntDef; +import android.view.accessibility.CaptioningManager; +import android.view.accessibility.CaptioningManager.CaptionStyle; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A compatibility wrapper for {@link CaptionStyle}. + */ +public final class CaptionStyleCompat { + + /** + * The type of edge, which may be none. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({EDGE_TYPE_NONE, EDGE_TYPE_OUTLINE, EDGE_TYPE_DROP_SHADOW, EDGE_TYPE_RAISED, + EDGE_TYPE_DEPRESSED}) + public @interface EdgeType {} + /** + * Edge type value specifying no character edges. + */ + public static final int EDGE_TYPE_NONE = 0; + /** + * Edge type value specifying uniformly outlined character edges. + */ + public static final int EDGE_TYPE_OUTLINE = 1; + /** + * Edge type value specifying drop-shadowed character edges. + */ + public static final int EDGE_TYPE_DROP_SHADOW = 2; + /** + * Edge type value specifying raised bevel character edges. + */ + public static final int EDGE_TYPE_RAISED = 3; + /** + * Edge type value specifying depressed bevel character edges. + */ + public static final int EDGE_TYPE_DEPRESSED = 4; + + /** + * Use color setting specified by the track and fallback to default caption style. + */ + public static final int USE_TRACK_COLOR_SETTINGS = 1; + + /** + * Default caption style. + */ + public static final CaptionStyleCompat DEFAULT = new CaptionStyleCompat( + Color.WHITE, Color.BLACK, Color.TRANSPARENT, EDGE_TYPE_NONE, Color.WHITE, null); + + /** + * The preferred foreground color. + */ + public final int foregroundColor; + + /** + * The preferred background color. + */ + public final int backgroundColor; + + /** + * The preferred window color. + */ + public final int windowColor; + + /** + * The preferred edge type. One of: + *

      + *
    • {@link #EDGE_TYPE_NONE} + *
    • {@link #EDGE_TYPE_OUTLINE} + *
    • {@link #EDGE_TYPE_DROP_SHADOW} + *
    • {@link #EDGE_TYPE_RAISED} + *
    • {@link #EDGE_TYPE_DEPRESSED} + *
    + */ + @EdgeType public final int edgeType; + + /** + * The preferred edge color, if using an edge type other than {@link #EDGE_TYPE_NONE}. + */ + public final int edgeColor; + + /** + * The preferred typeface. + */ + public final Typeface typeface; + + /** + * Creates a {@link CaptionStyleCompat} equivalent to a provided {@link CaptionStyle}. + * + * @param captionStyle A {@link CaptionStyle}. + * @return The equivalent {@link CaptionStyleCompat}. + */ + @TargetApi(19) + public static CaptionStyleCompat createFromCaptionStyle( + CaptioningManager.CaptionStyle captionStyle) { + if (Util.SDK_INT >= 21) { + return createFromCaptionStyleV21(captionStyle); + } else { + // Note - Any caller must be on at least API level 19 or greater (because CaptionStyle did + // not exist in earlier API levels). + return createFromCaptionStyleV19(captionStyle); + } + } + + /** + * @param foregroundColor See {@link #foregroundColor}. + * @param backgroundColor See {@link #backgroundColor}. + * @param windowColor See {@link #windowColor}. + * @param edgeType See {@link #edgeType}. + * @param edgeColor See {@link #edgeColor}. + * @param typeface See {@link #typeface}. + */ + public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, + @EdgeType int edgeType, int edgeColor, Typeface typeface) { + this.foregroundColor = foregroundColor; + this.backgroundColor = backgroundColor; + this.windowColor = windowColor; + this.edgeType = edgeType; + this.edgeColor = edgeColor; + this.typeface = typeface; + } + + @TargetApi(19) + @SuppressWarnings("ResourceType") + private static CaptionStyleCompat createFromCaptionStyleV19( + CaptioningManager.CaptionStyle captionStyle) { + return new CaptionStyleCompat( + captionStyle.foregroundColor, captionStyle.backgroundColor, Color.TRANSPARENT, + captionStyle.edgeType, captionStyle.edgeColor, captionStyle.getTypeface()); + } + + @TargetApi(21) + @SuppressWarnings("ResourceType") + private static CaptionStyleCompat createFromCaptionStyleV21( + CaptioningManager.CaptionStyle captionStyle) { + return new CaptionStyleCompat( + captionStyle.hasForegroundColor() ? captionStyle.foregroundColor : DEFAULT.foregroundColor, + captionStyle.hasBackgroundColor() ? captionStyle.backgroundColor : DEFAULT.backgroundColor, + captionStyle.hasWindowColor() ? captionStyle.windowColor : DEFAULT.windowColor, + captionStyle.hasEdgeType() ? captionStyle.edgeType : DEFAULT.edgeType, + captionStyle.hasEdgeColor() ? captionStyle.edgeColor : DEFAULT.edgeColor, + captionStyle.getTypeface()); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/Cue.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/Cue.java new file mode 100644 index 0000000..0cde4d4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/Cue.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.support.annotation.IntDef; +import android.text.Layout.Alignment; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Contains information about a specific cue, including textual content and formatting data. + */ +public class Cue { + + /** + * An unset position or width. + */ + public static final float DIMEN_UNSET = Float.MIN_VALUE; + + /** + * The type of anchor, which may be unset. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END}) + public @interface AnchorType {} + + /** + * An unset anchor or line type value. + */ + public static final int TYPE_UNSET = Integer.MIN_VALUE; + + /** + * Anchors the left (for horizontal positions) or top (for vertical positions) edge of the cue + * box. + */ + public static final int ANCHOR_TYPE_START = 0; + + /** + * Anchors the middle of the cue box. + */ + public static final int ANCHOR_TYPE_MIDDLE = 1; + + /** + * Anchors the right (for horizontal positions) or bottom (for vertical positions) edge of the cue + * box. + */ + public static final int ANCHOR_TYPE_END = 2; + + /** + * The type of line, which may be unset. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER}) + public @interface LineType {} + + /** + * Value for {@link #lineType} when {@link #line} is a fractional position. + */ + public static final int LINE_TYPE_FRACTION = 0; + + /** + * Value for {@link #lineType} when {@link #line} is a line number. + */ + public static final int LINE_TYPE_NUMBER = 1; + + /** + * The cue text, or null if this is an image cue. Note the {@link CharSequence} may be decorated + * with styling spans. + */ + public final CharSequence text; + + /** + * The alignment of the cue text within the cue box, or null if the alignment is undefined. + */ + public final Alignment textAlignment; + + /** + * The cue image, or null if this is a text cue. + */ + public final Bitmap bitmap; + + /** + * The position of the {@link #lineAnchor} of the cue box within the viewport in the direction + * orthogonal to the writing direction, or {@link #DIMEN_UNSET}. When set, the interpretation of + * the value depends on the value of {@link #lineType}. + *

    + * For horizontal text and {@link #lineType} equal to {@link #LINE_TYPE_FRACTION}, this is the + * fractional vertical position relative to the top of the viewport. + */ + public final float line; + + /** + * The type of the {@link #line} value. + *

    + * {@link #LINE_TYPE_FRACTION} indicates that {@link #line} is a fractional position within the + * viewport. + *

    + * {@link #LINE_TYPE_NUMBER} indicates that {@link #line} is a line number, where the size of each + * line is taken to be the size of the first line of the cue. When {@link #line} is greater than + * or equal to 0 lines count from the start of the viewport, with 0 indicating zero offset from + * the start edge. When {@link #line} is negative lines count from the end of the viewport, with + * -1 indicating zero offset from the end edge. For horizontal text the line spacing is the height + * of the first line of the cue, and the start and end of the viewport are the top and bottom + * respectively. + *

    + * Note that it's particularly important to consider the effect of {@link #lineAnchor} when using + * {@link #LINE_TYPE_NUMBER}. {@code (line == 0 && lineAnchor == ANCHOR_TYPE_START)} positions a + * (potentially multi-line) cue at the very top of the viewport. + * {@code (line == -1 && lineAnchor == ANCHOR_TYPE_END)} positions a (potentially multi-line) cue + * at the very bottom of the viewport. {@code (line == 0 && lineAnchor == ANCHOR_TYPE_END)} + * and {@code (line == -1 && lineAnchor == ANCHOR_TYPE_START)} position cues entirely outside of + * the viewport. {@code (line == 1 && lineAnchor == ANCHOR_TYPE_END)} positions a cue so that only + * the last line is visible at the top of the viewport. + * {@code (line == -2 && lineAnchor == ANCHOR_TYPE_START)} position a cue so that only its first + * line is visible at the bottom of the viewport. + */ + @LineType public final int lineType; + + /** + * The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. + *

    + * For the normal case of horizontal text, {@link #ANCHOR_TYPE_START}, {@link #ANCHOR_TYPE_MIDDLE} + * and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box + * respectively. + */ + @AnchorType public final int lineAnchor; + + /** + * The fractional position of the {@link #positionAnchor} of the cue box within the viewport in + * the direction orthogonal to {@link #line}, or {@link #DIMEN_UNSET}. + *

    + * For horizontal text, this is the horizontal position relative to the left of the viewport. Note + * that positioning is relative to the left of the viewport even in the case of right-to-left + * text. + */ + public final float position; + + /** + * The cue box anchor positioned by {@link #position}. One of {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. + *

    + * For the normal case of horizontal text, {@link #ANCHOR_TYPE_START}, {@link #ANCHOR_TYPE_MIDDLE} + * and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box + * respectively. + */ + @AnchorType public final int positionAnchor; + + /** + * The size of the cue box in the writing direction specified as a fraction of the viewport size + * in that direction, or {@link #DIMEN_UNSET}. + */ + public final float size; + + /** + * The bitmap height as a fraction of the of the viewport size, or {@link #DIMEN_UNSET} if the + * bitmap should be displayed at its natural height given the bitmap dimensions and the specified + * {@link #size}. + */ + public final float bitmapHeight; + + /** + * Specifies whether or not the {@link #windowColor} property is set. + */ + public final boolean windowColorSet; + + /** + * The fill color of the window. + */ + public final int windowColor; + + /** + * Creates an image cue. + * + * @param bitmap See {@link #bitmap}. + * @param horizontalPosition The position of the horizontal anchor within the viewport, expressed + * as a fraction of the viewport width. + * @param horizontalPositionAnchor The horizontal anchor. One of {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. + * @param verticalPosition The position of the vertical anchor within the viewport, expressed as a + * fraction of the viewport height. + * @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. + * @param width The width of the cue as a fraction of the viewport width. + * @param height The height of the cue as a fraction of the viewport height, or + * {@link #DIMEN_UNSET} if the bitmap should be displayed at its natural height for the + * specified {@code width}. + */ + public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor, + float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float height) { + this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor, + horizontalPosition, horizontalPositionAnchor, width, height, false, Color.BLACK); + } + + /** + * Creates a text cue whose {@link #textAlignment} is null, whose type parameters are set to + * {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}. + * + * @param text See {@link #text}. + */ + public Cue(CharSequence text) { + this(text, null, DIMEN_UNSET, TYPE_UNSET, TYPE_UNSET, DIMEN_UNSET, TYPE_UNSET, DIMEN_UNSET); + } + + /** + * Creates a text cue. + * + * @param text See {@link #text}. + * @param textAlignment See {@link #textAlignment}. + * @param line See {@link #line}. + * @param lineType See {@link #lineType}. + * @param lineAnchor See {@link #lineAnchor}. + * @param position See {@link #position}. + * @param positionAnchor See {@link #positionAnchor}. + * @param size See {@link #size}. + */ + public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, + @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) { + this(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, size, false, + Color.BLACK); + } + + /** + * Creates a text cue. + * + * @param text See {@link #text}. + * @param textAlignment See {@link #textAlignment}. + * @param line See {@link #line}. + * @param lineType See {@link #lineType}. + * @param lineAnchor See {@link #lineAnchor}. + * @param position See {@link #position}. + * @param positionAnchor See {@link #positionAnchor}. + * @param size See {@link #size}. + * @param windowColorSet See {@link #windowColorSet}. + * @param windowColor See {@link #windowColor}. + */ + public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, + @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, + boolean windowColorSet, int windowColor) { + this(text, textAlignment, null, line, lineType, lineAnchor, position, positionAnchor, size, + DIMEN_UNSET, windowColorSet, windowColor); + } + + private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float line, + @LineType int lineType, @AnchorType int lineAnchor, float position, + @AnchorType int positionAnchor, float size, float bitmapHeight, boolean windowColorSet, + int windowColor) { + this.text = text; + this.textAlignment = textAlignment; + this.bitmap = bitmap; + this.line = line; + this.lineType = lineType; + this.lineAnchor = lineAnchor; + this.position = position; + this.positionAnchor = positionAnchor; + this.size = size; + this.bitmapHeight = bitmapHeight; + this.windowColorSet = windowColorSet; + this.windowColor = windowColor; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SimpleSubtitleDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SimpleSubtitleDecoder.java new file mode 100644 index 0000000..26723b3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SimpleSubtitleDecoder.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.SimpleDecoder; +import java.nio.ByteBuffer; + +/** + * Base class for subtitle parsers that use their own decode thread. + */ +public abstract class SimpleSubtitleDecoder extends + SimpleDecoder implements + SubtitleDecoder { + + private final String name; + + /** + * @param name The name of the decoder. + */ + protected SimpleSubtitleDecoder(String name) { + super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]); + this.name = name; + setInitialInputBufferSize(1024); + } + + @Override + public final String getName() { + return name; + } + + @Override + public void setPositionUs(long timeUs) { + // Do nothing + } + + @Override + protected final SubtitleInputBuffer createInputBuffer() { + return new SubtitleInputBuffer(); + } + + @Override + protected final SubtitleOutputBuffer createOutputBuffer() { + return new SimpleSubtitleOutputBuffer(this); + } + + @Override + protected final void releaseOutputBuffer(SubtitleOutputBuffer buffer) { + super.releaseOutputBuffer(buffer); + } + + @Override + protected final SubtitleDecoderException decode(SubtitleInputBuffer inputBuffer, + SubtitleOutputBuffer outputBuffer, boolean reset) { + try { + ByteBuffer inputData = inputBuffer.data; + Subtitle subtitle = decode(inputData.array(), inputData.limit(), reset); + outputBuffer.setContent(inputBuffer.timeUs, subtitle, inputBuffer.subsampleOffsetUs); + // Clear BUFFER_FLAG_DECODE_ONLY (see [Internal: b/27893809]). + outputBuffer.clearFlag(C.BUFFER_FLAG_DECODE_ONLY); + return null; + } catch (SubtitleDecoderException e) { + return e; + } + } + + /** + * Decodes data into a {@link Subtitle}. + * + * @param data An array holding the data to be decoded, starting at position 0. + * @param size The size of the data to be decoded. + * @param reset Whether the decoder must be reset before decoding. + * @return The decoded {@link Subtitle}. + * @throws SubtitleDecoderException If a decoding error occurs. + */ + protected abstract Subtitle decode(byte[] data, int size, boolean reset) + throws SubtitleDecoderException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SimpleSubtitleOutputBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SimpleSubtitleOutputBuffer.java new file mode 100644 index 0000000..567c1a2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SimpleSubtitleOutputBuffer.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +/** + * A {@link SubtitleOutputBuffer} for decoders that extend {@link SimpleSubtitleDecoder}. + */ +/* package */ final class SimpleSubtitleOutputBuffer extends SubtitleOutputBuffer { + + private final SimpleSubtitleDecoder owner; + + /** + * @param owner The decoder that owns this buffer. + */ + public SimpleSubtitleOutputBuffer(SimpleSubtitleDecoder owner) { + super(); + this.owner = owner; + } + + @Override + public final void release() { + owner.releaseOutputBuffer(this); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/Subtitle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/Subtitle.java new file mode 100644 index 0000000..ad6be4f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/Subtitle.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.util.List; + +/** + * A subtitle consisting of timed {@link Cue}s. + */ +public interface Subtitle { + + /** + * Returns the index of the first event that occurs after a given time (exclusive). + * + * @param timeUs The time in microseconds. + * @return The index of the next event, or {@link C#INDEX_UNSET} if there are no events after the + * specified time. + */ + int getNextEventTimeIndex(long timeUs); + + /** + * Returns the number of event times, where events are defined as points in time at which the cues + * returned by {@link #getCues(long)} changes. + * + * @return The number of event times. + */ + int getEventTimeCount(); + + /** + * Returns the event time at a specified index. + * + * @param index The index of the event time to obtain. + * @return The event time in microseconds. + */ + long getEventTime(int index); + + /** + * Retrieve the cues that should be displayed at a given time. + * + * @param timeUs The time in microseconds. + * @return A list of cues that should be displayed, possibly empty. + */ + List getCues(long timeUs); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoder.java new file mode 100644 index 0000000..f08fbc5 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.Decoder; + +/** + * Decodes {@link Subtitle}s from {@link SubtitleInputBuffer}s. + */ +public interface SubtitleDecoder extends + Decoder { + + /** + * Informs the decoder of the current playback position. + *

    + * Must be called prior to each attempt to dequeue output buffers from the decoder. + * + * @param positionUs The current playback position in microseconds. + */ + void setPositionUs(long positionUs); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoderException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoderException.java new file mode 100644 index 0000000..5d74b79 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoderException.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +/** + * Thrown when an error occurs decoding subtitle data. + */ +public class SubtitleDecoderException extends Exception { + + /** + * @param message The detail message for this exception. + */ + public SubtitleDecoderException(String message) { + super(message); + } + + /** + * @param message The detail message for this exception. + * @param cause The cause of this exception. + */ + public SubtitleDecoderException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoderFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoderFactory.java new file mode 100644 index 0000000..c2e6227 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleDecoderFactory.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.text.cea.Cea608Decoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.cea.Cea708Decoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.dvb.DvbDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.subrip.SubripDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.ttml.TtmlDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.tx3g.Tx3gDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt.Mp4WebvttDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt.WebvttDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; + +/** + * A factory for {@link SubtitleDecoder} instances. + */ +public interface SubtitleDecoderFactory { + + /** + * Returns whether the factory is able to instantiate a {@link SubtitleDecoder} for the given + * {@link Format}. + * + * @param format The {@link Format}. + * @return Whether the factory can instantiate a suitable {@link SubtitleDecoder}. + */ + boolean supportsFormat(Format format); + + /** + * Creates a {@link SubtitleDecoder} for the given {@link Format}. + * + * @param format The {@link Format}. + * @return A new {@link SubtitleDecoder}. + * @throws IllegalArgumentException If the {@link Format} is not supported. + */ + SubtitleDecoder createDecoder(Format format); + + /** + * Default {@link SubtitleDecoderFactory} implementation. + *

    + * The formats supported by this factory are: + *

      + *
    • WebVTT ({@link WebvttDecoder})
    • + *
    • WebVTT (MP4) ({@link Mp4WebvttDecoder})
    • + *
    • TTML ({@link TtmlDecoder})
    • + *
    • SubRip ({@link SubripDecoder})
    • + *
    • TX3G ({@link Tx3gDecoder})
    • + *
    • Cea608 ({@link Cea608Decoder})
    • + *
    • Cea708 ({@link Cea708Decoder})
    • + *
    • DVB ({@link DvbDecoder})
    • + *
    + */ + SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() { + + @Override + public boolean supportsFormat(Format format) { + String mimeType = format.sampleMimeType; + return MimeTypes.TEXT_VTT.equals(mimeType) + || MimeTypes.APPLICATION_TTML.equals(mimeType) + || MimeTypes.APPLICATION_MP4VTT.equals(mimeType) + || MimeTypes.APPLICATION_SUBRIP.equals(mimeType) + || MimeTypes.APPLICATION_TX3G.equals(mimeType) + || MimeTypes.APPLICATION_CEA608.equals(mimeType) + || MimeTypes.APPLICATION_MP4CEA608.equals(mimeType) + || MimeTypes.APPLICATION_CEA708.equals(mimeType) + || MimeTypes.APPLICATION_DVBSUBS.equals(mimeType); + } + + @Override + public SubtitleDecoder createDecoder(Format format) { + switch (format.sampleMimeType) { + case MimeTypes.TEXT_VTT: + return new WebvttDecoder(); + case MimeTypes.APPLICATION_MP4VTT: + return new Mp4WebvttDecoder(); + case MimeTypes.APPLICATION_TTML: + return new TtmlDecoder(); + case MimeTypes.APPLICATION_SUBRIP: + return new SubripDecoder(); + case MimeTypes.APPLICATION_TX3G: + return new Tx3gDecoder(format.initializationData); + case MimeTypes.APPLICATION_CEA608: + case MimeTypes.APPLICATION_MP4CEA608: + return new Cea608Decoder(format.sampleMimeType, format.accessibilityChannel); + case MimeTypes.APPLICATION_CEA708: + return new Cea708Decoder(format.accessibilityChannel); + case MimeTypes.APPLICATION_DVBSUBS: + return new DvbDecoder(format.initializationData); + default: + throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); + } + } + + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleInputBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleInputBuffer.java new file mode 100644 index 0000000..c68083f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleInputBuffer.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import android.support.annotation.NonNull; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; + +/** + * A {@link DecoderInputBuffer} for a {@link SubtitleDecoder}. + */ +public final class SubtitleInputBuffer extends DecoderInputBuffer + implements Comparable { + + /** + * An offset that must be added to the subtitle's event times after it's been decoded, or + * {@link Format#OFFSET_SAMPLE_RELATIVE} if {@link #timeUs} should be added. + */ + public long subsampleOffsetUs; + + public SubtitleInputBuffer() { + super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); + } + + @Override + public int compareTo(@NonNull SubtitleInputBuffer other) { + long delta = timeUs - other.timeUs; + if (delta == 0) { + return 0; + } + return delta > 0 ? 1 : -1; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleOutputBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleOutputBuffer.java new file mode 100644 index 0000000..b25d4fd --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/SubtitleOutputBuffer.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.OutputBuffer; +import java.util.List; + +/** + * Base class for {@link SubtitleDecoder} output buffers. + */ +public abstract class SubtitleOutputBuffer extends OutputBuffer implements Subtitle { + + private Subtitle subtitle; + private long subsampleOffsetUs; + + /** + * Sets the content of the output buffer, consisting of a {@link Subtitle} and associated + * metadata. + * + * @param timeUs The time of the start of the subtitle in microseconds. + * @param subtitle The subtitle. + * @param subsampleOffsetUs An offset that must be added to the subtitle's event times, or + * {@link Format#OFFSET_SAMPLE_RELATIVE} if {@code timeUs} should be added. + */ + public void setContent(long timeUs, Subtitle subtitle, long subsampleOffsetUs) { + this.timeUs = timeUs; + this.subtitle = subtitle; + this.subsampleOffsetUs = subsampleOffsetUs == Format.OFFSET_SAMPLE_RELATIVE ? this.timeUs + : subsampleOffsetUs; + } + + @Override + public int getEventTimeCount() { + return subtitle.getEventTimeCount(); + } + + @Override + public long getEventTime(int index) { + return subtitle.getEventTime(index) + subsampleOffsetUs; + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + return subtitle.getNextEventTimeIndex(timeUs - subsampleOffsetUs); + } + + @Override + public List getCues(long timeUs) { + return subtitle.getCues(timeUs - subsampleOffsetUs); + } + + @Override + public abstract void release(); + + @Override + public void clear() { + super.clear(); + subtitle = null; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/TextRenderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/TextRenderer.java new file mode 100644 index 0000000..73f998a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/TextRenderer.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text; + +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.BaseRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.FormatHolder; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.List; + +/** + * A renderer for text. + *

    + * {@link Subtitle}s are decoded from sample data using {@link SubtitleDecoder} instances obtained + * from a {@link SubtitleDecoderFactory}. The actual rendering of the subtitle {@link Cue}s is + * delegated to an {@link Output}. + */ +public final class TextRenderer extends BaseRenderer implements Callback { + + /** + * Receives output from a {@link TextRenderer}. + */ + public interface Output { + + /** + * Called each time there is a change in the {@link Cue}s. + * + * @param cues The {@link Cue}s. + */ + void onCues(List cues); + + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({REPLACEMENT_STATE_NONE, REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, + REPLACEMENT_STATE_WAIT_END_OF_STREAM}) + private @interface ReplacementState {} + /** + * The decoder does not need to be replaced. + */ + private static final int REPLACEMENT_STATE_NONE = 0; + /** + * The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing + * decoder. We need to do so in order to ensure that it outputs any remaining buffers before we + * release it. + */ + private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1; + /** + * The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder. + * We're waiting for the decoder to output an end of stream signal to indicate that it has output + * any remaining buffers before we release it. + */ + private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2; + + private static final int MSG_UPDATE_OUTPUT = 0; + + private final Handler outputHandler; + private final Output output; + private final SubtitleDecoderFactory decoderFactory; + private final FormatHolder formatHolder; + + private boolean inputStreamEnded; + private boolean outputStreamEnded; + @ReplacementState private int decoderReplacementState; + private Format streamFormat; + private SubtitleDecoder decoder; + private SubtitleInputBuffer nextInputBuffer; + private SubtitleOutputBuffer subtitle; + private SubtitleOutputBuffer nextSubtitle; + private int nextSubtitleEventIndex; + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be + * called. If the output makes use of standard Android UI components, then this should + * normally be the looper associated with the application's main thread, which can be obtained + * using {@link android.app.Activity#getMainLooper()}. Null may be passed if the output + * should be called directly on the player's internal rendering thread. + */ + public TextRenderer(Output output, Looper outputLooper) { + this(output, outputLooper, SubtitleDecoderFactory.DEFAULT); + } + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be + * called. If the output makes use of standard Android UI components, then this should + * normally be the looper associated with the application's main thread, which can be obtained + * using {@link android.app.Activity#getMainLooper()}. Null may be passed if the output + * should be called directly on the player's internal rendering thread. + * @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances. + */ + public TextRenderer(Output output, Looper outputLooper, SubtitleDecoderFactory decoderFactory) { + super(C.TRACK_TYPE_TEXT); + this.output = Assertions.checkNotNull(output); + this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this); + this.decoderFactory = decoderFactory; + formatHolder = new FormatHolder(); + } + + @Override + public int supportsFormat(Format format) { + return decoderFactory.supportsFormat(format) ? FORMAT_HANDLED + : (MimeTypes.isText(format.sampleMimeType) ? FORMAT_UNSUPPORTED_SUBTYPE + : FORMAT_UNSUPPORTED_TYPE); + } + + @Override + protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + streamFormat = formats[0]; + if (decoder != null) { + decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; + } else { + decoder = decoderFactory.createDecoder(streamFormat); + } + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) { + clearOutput(); + inputStreamEnded = false; + outputStreamEnded = false; + if (decoderReplacementState != REPLACEMENT_STATE_NONE) { + replaceDecoder(); + } else { + releaseBuffers(); + decoder.flush(); + } + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (outputStreamEnded) { + return; + } + + if (nextSubtitle == null) { + decoder.setPositionUs(positionUs); + try { + nextSubtitle = decoder.dequeueOutputBuffer(); + } catch (SubtitleDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + if (getState() != STATE_STARTED) { + return; + } + + boolean textRendererNeedsUpdate = false; + if (subtitle != null) { + // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we + // advance to the next event. + long subtitleNextEventTimeUs = getNextEventTime(); + while (subtitleNextEventTimeUs <= positionUs) { + nextSubtitleEventIndex++; + subtitleNextEventTimeUs = getNextEventTime(); + textRendererNeedsUpdate = true; + } + } + + if (nextSubtitle != null) { + if (nextSubtitle.isEndOfStream()) { + if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) { + if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { + replaceDecoder(); + } else { + releaseBuffers(); + outputStreamEnded = true; + } + } + } else if (nextSubtitle.timeUs <= positionUs) { + // Advance to the next subtitle. Sync the next event index and trigger an update. + if (subtitle != null) { + subtitle.release(); + } + subtitle = nextSubtitle; + nextSubtitle = null; + nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs); + textRendererNeedsUpdate = true; + } + } + + if (textRendererNeedsUpdate) { + // textRendererNeedsUpdate is set and we're playing. Update the renderer. + updateOutput(subtitle.getCues(positionUs)); + } + + if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { + return; + } + + try { + while (!inputStreamEnded) { + if (nextInputBuffer == null) { + nextInputBuffer = decoder.dequeueInputBuffer(); + if (nextInputBuffer == null) { + return; + } + } + if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) { + nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + decoder.queueInputBuffer(nextInputBuffer); + nextInputBuffer = null; + decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM; + return; + } + // Try and read the next subtitle from the source. + int result = readSource(formatHolder, nextInputBuffer, false); + if (result == C.RESULT_BUFFER_READ) { + if (nextInputBuffer.isEndOfStream()) { + inputStreamEnded = true; + } else { + nextInputBuffer.subsampleOffsetUs = formatHolder.format.subsampleOffsetUs; + nextInputBuffer.flip(); + } + decoder.queueInputBuffer(nextInputBuffer); + nextInputBuffer = null; + } else if (result == C.RESULT_NOTHING_READ) { + return; + } + } + } catch (SubtitleDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + @Override + protected void onDisabled() { + streamFormat = null; + clearOutput(); + releaseDecoder(); + super.onDisabled(); + } + + @Override + public boolean isEnded() { + return outputStreamEnded; + } + + @Override + public boolean isReady() { + // Don't block playback whilst subtitles are loading. + // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941]. + return true; + } + + private void releaseBuffers() { + nextInputBuffer = null; + nextSubtitleEventIndex = C.INDEX_UNSET; + if (subtitle != null) { + subtitle.release(); + subtitle = null; + } + if (nextSubtitle != null) { + nextSubtitle.release(); + nextSubtitle = null; + } + } + + private void releaseDecoder() { + releaseBuffers(); + decoder.release(); + decoder = null; + decoderReplacementState = REPLACEMENT_STATE_NONE; + } + + private void replaceDecoder() { + releaseDecoder(); + decoder = decoderFactory.createDecoder(streamFormat); + } + + private long getNextEventTime() { + return ((nextSubtitleEventIndex == C.INDEX_UNSET) + || (nextSubtitleEventIndex >= subtitle.getEventTimeCount())) ? Long.MAX_VALUE + : (subtitle.getEventTime(nextSubtitleEventIndex)); + } + + private void updateOutput(List cues) { + if (outputHandler != null) { + outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget(); + } else { + invokeUpdateOutputInternal(cues); + } + } + + private void clearOutput() { + updateOutput(Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_OUTPUT: + invokeUpdateOutputInternal((List) msg.obj); + return true; + default: + throw new IllegalStateException(); + } + } + + private void invokeUpdateOutputInternal(List cues) { + output.onCues(cues); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea608Decoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea608Decoder.java new file mode 100644 index 0000000..009c6e8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea608Decoder.java @@ -0,0 +1,788 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.cea; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.Layout.Alignment; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.CharacterStyle; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608"). + */ +public final class Cea608Decoder extends CeaDecoder { + + private static final int CC_VALID_FLAG = 0x04; + private static final int CC_TYPE_FLAG = 0x02; + private static final int CC_FIELD_FLAG = 0x01; + + private static final int NTSC_CC_FIELD_1 = 0x00; + private static final int NTSC_CC_FIELD_2 = 0x01; + private static final int CC_VALID_608_ID = 0x04; + + private static final int CC_MODE_UNKNOWN = 0; + private static final int CC_MODE_ROLL_UP = 1; + private static final int CC_MODE_POP_ON = 2; + private static final int CC_MODE_PAINT_ON = 3; + + private static final int[] ROW_INDICES = new int[] {11, 1, 3, 12, 14, 5, 7, 9}; + private static final int[] COLUMN_INDICES = new int[] {0, 4, 8, 12, 16, 20, 24, 28}; + private static final int[] COLORS = new int[] { + Color.WHITE, + Color.GREEN, + Color.BLUE, + Color.CYAN, + Color.RED, + Color.YELLOW, + Color.MAGENTA, + }; + + // The default number of rows to display in roll-up captions mode. + private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4; + + // An implied first byte for packets that are only 2 bytes long, consisting of marker bits + // (0b11111) + valid bit (0b1) + NTSC field 1 type bits (0b00). + private static final byte CC_IMPLICIT_DATA_HEADER = (byte) 0xFC; + + /** + * Command initiating pop-on style captioning. Subsequent data should be loaded into a + * non-displayed memory and held there until the {@link #CTRL_END_OF_CAPTION} command is received, + * at which point the non-displayed memory becomes the displayed memory (and vice versa). + */ + private static final byte CTRL_RESUME_CAPTION_LOADING = 0x20; + /** + * Command initiating roll-up style captioning, with the maximum of 2 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_2_ROWS = 0x25; + /** + * Command initiating roll-up style captioning, with the maximum of 3 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_3_ROWS = 0x26; + /** + * Command initiating roll-up style captioning, with the maximum of 4 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_4_ROWS = 0x27; + /** + * Command initiating paint-on style captioning. Subsequent data should be addressed immediately + * to displayed memory without need for the {@link #CTRL_RESUME_CAPTION_LOADING} command. + */ + private static final byte CTRL_RESUME_DIRECT_CAPTIONING = 0x29; + /** + * Command indicating the end of a pop-on style caption. At this point the caption loaded in + * non-displayed memory should be swapped with the one in displayed memory. If no + * {@link #CTRL_RESUME_CAPTION_LOADING} command has been received, this command forces the + * receiver into pop-on style. + */ + private static final byte CTRL_END_OF_CAPTION = 0x2F; + + private static final byte CTRL_ERASE_DISPLAYED_MEMORY = 0x2C; + private static final byte CTRL_CARRIAGE_RETURN = 0x2D; + private static final byte CTRL_ERASE_NON_DISPLAYED_MEMORY = 0x2E; + private static final byte CTRL_DELETE_TO_END_OF_ROW = 0x24; + + private static final byte CTRL_BACKSPACE = 0x21; + + // Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20). + private static final int[] BASIC_CHARACTER_SET = new int[] { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // ! " # $ % & ' + 0x28, 0x29, // ( ) + 0xE1, // 2A: 225 'á' "Latin small letter A with acute" + 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, // + , - . / + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0 1 2 3 4 5 6 7 + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, // 8 9 : ; < = > ? + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // @ A B C D E F G + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, // H I J K L M N O + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // P Q R S T U V W + 0x58, 0x59, 0x5A, 0x5B, // X Y Z [ + 0xE9, // 5C: 233 'é' "Latin small letter E with acute" + 0x5D, // ] + 0xED, // 5E: 237 'í' "Latin small letter I with acute" + 0xF3, // 5F: 243 'ó' "Latin small letter O with acute" + 0xFA, // 60: 250 'ú' "Latin small letter U with acute" + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // a b c d e f g + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, // h i j k l m n o + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // p q r s t u v w + 0x78, 0x79, 0x7A, // x y z + 0xE7, // 7B: 231 'ç' "Latin small letter C with cedilla" + 0xF7, // 7C: 247 '÷' "Division sign" + 0xD1, // 7D: 209 'Ñ' "Latin capital letter N with tilde" + 0xF1, // 7E: 241 'ñ' "Latin small letter N with tilde" + 0x25A0 // 7F: "Black Square" (NB: 2588 = Full Block) + }; + + // Special North American 608 CC char set. + private static final int[] SPECIAL_CHARACTER_SET = new int[] { + 0xAE, // 30: 174 '®' "Registered Sign" - registered trademark symbol + 0xB0, // 31: 176 '°' "Degree Sign" + 0xBD, // 32: 189 '½' "Vulgar Fraction One Half" (1/2 symbol) + 0xBF, // 33: 191 '¿' "Inverted Question Mark" + 0x2122, // 34: "Trade Mark Sign" (tm superscript) + 0xA2, // 35: 162 '¢' "Cent Sign" + 0xA3, // 36: 163 '£' "Pound Sign" - pounds sterling + 0x266A, // 37: "Eighth Note" - music note + 0xE0, // 38: 224 'à' "Latin small letter A with grave" + 0x20, // 39: TRANSPARENT SPACE - for now use ordinary space + 0xE8, // 3A: 232 'è' "Latin small letter E with grave" + 0xE2, // 3B: 226 'â' "Latin small letter A with circumflex" + 0xEA, // 3C: 234 'ê' "Latin small letter E with circumflex" + 0xEE, // 3D: 238 'î' "Latin small letter I with circumflex" + 0xF4, // 3E: 244 'ô' "Latin small letter O with circumflex" + 0xFB // 3F: 251 'û' "Latin small letter U with circumflex" + }; + + // Extended Spanish/Miscellaneous and French char set. + private static final int[] SPECIAL_ES_FR_CHARACTER_SET = new int[] { + // Spanish and misc. + 0xC1, 0xC9, 0xD3, 0xDA, 0xDC, 0xFC, 0x2018, 0xA1, + 0x2A, 0x27, 0x2014, 0xA9, 0x2120, 0x2022, 0x201C, 0x201D, + // French. + 0xC0, 0xC2, 0xC7, 0xC8, 0xCA, 0xCB, 0xEB, 0xCE, + 0xCF, 0xEF, 0xD4, 0xD9, 0xF9, 0xDB, 0xAB, 0xBB + }; + + //Extended Portuguese and German/Danish char set. + private static final int[] SPECIAL_PT_DE_CHARACTER_SET = new int[] { + // Portuguese. + 0xC3, 0xE3, 0xCD, 0xCC, 0xEC, 0xD2, 0xF2, 0xD5, + 0xF5, 0x7B, 0x7D, 0x5C, 0x5E, 0x5F, 0x7C, 0x7E, + // German/Danish. + 0xC4, 0xE4, 0xD6, 0xF6, 0xDF, 0xA5, 0xA4, 0x2502, + 0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518 + }; + + private final ParsableByteArray ccData; + private final int packetLength; + private final int selectedField; + private final LinkedList cueBuilders; + + private CueBuilder currentCueBuilder; + private List cues; + private List lastCues; + + private int captionMode; + private int captionRowCount; + + private boolean repeatableControlSet; + private byte repeatableControlCc1; + private byte repeatableControlCc2; + + public Cea608Decoder(String mimeType, int accessibilityChannel) { + ccData = new ParsableByteArray(); + cueBuilders = new LinkedList<>(); + currentCueBuilder = new CueBuilder(CC_MODE_UNKNOWN, DEFAULT_CAPTIONS_ROW_COUNT); + packetLength = MimeTypes.APPLICATION_MP4CEA608.equals(mimeType) ? 2 : 3; + switch (accessibilityChannel) { + case 3: + case 4: + selectedField = 2; + break; + case 1: + case 2: + case Format.NO_VALUE: + default: + selectedField = 1; + } + + setCaptionMode(CC_MODE_UNKNOWN); + resetCueBuilders(); + } + + @Override + public String getName() { + return "Cea608Decoder"; + } + + @Override + public void flush() { + super.flush(); + cues = null; + lastCues = null; + setCaptionMode(CC_MODE_UNKNOWN); + resetCueBuilders(); + captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; + repeatableControlSet = false; + repeatableControlCc1 = 0; + repeatableControlCc2 = 0; + } + + @Override + public void release() { + // Do nothing + } + + @Override + protected boolean isNewSubtitleDataAvailable() { + return cues != lastCues; + } + + @Override + protected Subtitle createSubtitle() { + lastCues = cues; + return new CeaSubtitle(cues); + } + + @Override + protected void decode(SubtitleInputBuffer inputBuffer) { + ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit()); + boolean captionDataProcessed = false; + boolean isRepeatableControl = false; + while (ccData.bytesLeft() >= packetLength) { + byte ccDataHeader = packetLength == 2 ? CC_IMPLICIT_DATA_HEADER + : (byte) ccData.readUnsignedByte(); + byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F); // strip the parity bit + byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F); // strip the parity bit + + // Only examine valid CEA-608 packets + // TODO: We're currently ignoring the top 5 marker bits, which should all be 1s according + // to the CEA-608 specification. We need to determine if the data should be handled + // differently when that is not the case. + if ((ccDataHeader & (CC_VALID_FLAG | CC_TYPE_FLAG)) != CC_VALID_608_ID) { + continue; + } + + // Only examine packets within the selected field + if ((selectedField == 1 && (ccDataHeader & CC_FIELD_FLAG) != NTSC_CC_FIELD_1) + || (selectedField == 2 && (ccDataHeader & CC_FIELD_FLAG) != NTSC_CC_FIELD_2)) { + continue; + } + + // Ignore empty captions. + if (ccData1 == 0 && ccData2 == 0) { + continue; + } + + // If we've reached this point then there is data to process; flag that work has been done. + captionDataProcessed = true; + + // Special North American character set. + // ccData1 - 0|0|0|1|C|0|0|1 + // ccData2 - 0|0|1|1|X|X|X|X + if (((ccData1 & 0xF7) == 0x11) && ((ccData2 & 0xF0) == 0x30)) { + // TODO: Make use of the channel toggle + currentCueBuilder.append(getSpecialChar(ccData2)); + continue; + } + + // Extended Western European character set. + // ccData1 - 0|0|0|1|C|0|1|S + // ccData2 - 0|0|1|X|X|X|X|X + if (((ccData1 & 0xF6) == 0x12) && (ccData2 & 0xE0) == 0x20) { + // TODO: Make use of the channel toggle + // Remove standard equivalent of the special extended char before appending new one + currentCueBuilder.backspace(); + if ((ccData1 & 0x01) == 0x00) { + // Extended Spanish/Miscellaneous and French character set (S = 0). + currentCueBuilder.append(getExtendedEsFrChar(ccData2)); + } else { + // Extended Portuguese and German/Danish character set (S = 1). + currentCueBuilder.append(getExtendedPtDeChar(ccData2)); + } + continue; + } + + // Control character. + // ccData1 - 0|0|0|X|X|X|X|X + if ((ccData1 & 0xE0) == 0x00) { + isRepeatableControl = handleCtrl(ccData1, ccData2); + continue; + } + + // Basic North American character set. + currentCueBuilder.append(getChar(ccData1)); + if ((ccData2 & 0xE0) != 0x00) { + currentCueBuilder.append(getChar(ccData2)); + } + } + + if (captionDataProcessed) { + if (!isRepeatableControl) { + repeatableControlSet = false; + } + if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { + cues = getDisplayCues(); + } + } + } + + private boolean handleCtrl(byte cc1, byte cc2) { + boolean isRepeatableControl = isRepeatable(cc1); + + // Most control commands are sent twice in succession to ensure they are received properly. + // We don't want to process duplicate commands, so if we see the same repeatable command twice + // in a row, ignore the second one. + if (isRepeatableControl) { + if (repeatableControlSet + && repeatableControlCc1 == cc1 + && repeatableControlCc2 == cc2) { + // This is a duplicate. Clear the repeatable control flag and return. + repeatableControlSet = false; + return true; + } else { + // This is a repeatable command, but we haven't see it yet, so set the repeabable control + // flag (to ensure we ignore the next one should it be a duplicate) and continue processing + // the command. + repeatableControlSet = true; + repeatableControlCc1 = cc1; + repeatableControlCc2 = cc2; + } + } + + if (isMidrowCtrlCode(cc1, cc2)) { + handleMidrowCtrl(cc2); + } else if (isPreambleAddressCode(cc1, cc2)) { + handlePreambleAddressCode(cc1, cc2); + } else if (isTabCtrlCode(cc1, cc2)) { + currentCueBuilder.setTab(cc2 - 0x20); + } else if (isMiscCode(cc1, cc2)) { + handleMiscCode(cc2); + } + + return isRepeatableControl; + } + + private void handleMidrowCtrl(byte cc2) { + // TODO: support the extended styles (i.e. backgrounds and transparencies) + + // cc2 - 0|0|1|0|ATRBT|U + // ATRBT is the 3-byte encoded attribute, and U is the underline toggle + boolean isUnderlined = (cc2 & 0x01) == 0x01; + currentCueBuilder.setUnderline(isUnderlined); + + int attribute = (cc2 >> 1) & 0x0F; + if (attribute == 0x07) { + currentCueBuilder.setMidrowStyle(new StyleSpan(Typeface.ITALIC), 2); + currentCueBuilder.setMidrowStyle(new ForegroundColorSpan(Color.WHITE), 1); + } else { + currentCueBuilder.setMidrowStyle(new ForegroundColorSpan(COLORS[attribute]), 1); + } + } + + private void handlePreambleAddressCode(byte cc1, byte cc2) { + // cc1 - 0|0|0|1|C|E|ROW + // C is the channel toggle, E is the extended flag, and ROW is the encoded row + int row = ROW_INDICES[cc1 & 0x07]; + // TODO: Make use of the channel toggle + // TODO: support the extended address and style + + // cc2 - 0|1|N|ATTRBTE|U + // N is the next row down toggle, ATTRBTE is the 4-byte encoded attribute, and U is the + // underline toggle. + boolean nextRowDown = (cc2 & 0x20) != 0; + if (nextRowDown) { + row++; + } + + if (row != currentCueBuilder.getRow()) { + if (captionMode != CC_MODE_ROLL_UP && !currentCueBuilder.isEmpty()) { + currentCueBuilder = new CueBuilder(captionMode, captionRowCount); + cueBuilders.add(currentCueBuilder); + } + currentCueBuilder.setRow(row); + } + + if ((cc2 & 0x01) == 0x01) { + currentCueBuilder.setPreambleStyle(new UnderlineSpan()); + } + + // cc2 - 0|1|N|0|STYLE|U + // cc2 - 0|1|N|1|CURSR|U + int attribute = cc2 >> 1 & 0x0F; + if (attribute <= 0x07) { + if (attribute == 0x07) { + currentCueBuilder.setPreambleStyle(new StyleSpan(Typeface.ITALIC)); + currentCueBuilder.setPreambleStyle(new ForegroundColorSpan(Color.WHITE)); + } else { + currentCueBuilder.setPreambleStyle(new ForegroundColorSpan(COLORS[attribute])); + } + } else { + currentCueBuilder.setIndent(COLUMN_INDICES[attribute & 0x07]); + } + } + + private void handleMiscCode(byte cc2) { + switch (cc2) { + case CTRL_ROLL_UP_CAPTIONS_2_ROWS: + captionRowCount = 2; + setCaptionMode(CC_MODE_ROLL_UP); + return; + case CTRL_ROLL_UP_CAPTIONS_3_ROWS: + captionRowCount = 3; + setCaptionMode(CC_MODE_ROLL_UP); + return; + case CTRL_ROLL_UP_CAPTIONS_4_ROWS: + captionRowCount = 4; + setCaptionMode(CC_MODE_ROLL_UP); + return; + case CTRL_RESUME_CAPTION_LOADING: + setCaptionMode(CC_MODE_POP_ON); + return; + case CTRL_RESUME_DIRECT_CAPTIONING: + setCaptionMode(CC_MODE_PAINT_ON); + return; + } + + if (captionMode == CC_MODE_UNKNOWN) { + return; + } + + switch (cc2) { + case CTRL_ERASE_DISPLAYED_MEMORY: + cues = null; + if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { + resetCueBuilders(); + } + break; + case CTRL_ERASE_NON_DISPLAYED_MEMORY: + resetCueBuilders(); + break; + case CTRL_END_OF_CAPTION: + cues = getDisplayCues(); + resetCueBuilders(); + break; + case CTRL_CARRIAGE_RETURN: + // carriage returns only apply to rollup captions; don't bother if we don't have anything + // to add a carriage return to + if (captionMode == CC_MODE_ROLL_UP && !currentCueBuilder.isEmpty()) { + currentCueBuilder.rollUp(); + } + break; + case CTRL_BACKSPACE: + currentCueBuilder.backspace(); + break; + case CTRL_DELETE_TO_END_OF_ROW: + // TODO: implement + break; + } + } + + private List getDisplayCues() { + List displayCues = new ArrayList<>(); + for (int i = 0; i < cueBuilders.size(); i++) { + Cue cue = cueBuilders.get(i).build(); + if (cue != null) { + displayCues.add(cue); + } + } + return displayCues; + } + + private void setCaptionMode(int captionMode) { + if (this.captionMode == captionMode) { + return; + } + + int oldCaptionMode = this.captionMode; + this.captionMode = captionMode; + + // Clear the working memory. + resetCueBuilders(); + if (oldCaptionMode == CC_MODE_PAINT_ON || captionMode == CC_MODE_ROLL_UP + || captionMode == CC_MODE_UNKNOWN) { + // When switching from paint-on or to roll-up or unknown, we also need to clear the caption. + cues = null; + } + } + + private void resetCueBuilders() { + currentCueBuilder.reset(captionMode, captionRowCount); + cueBuilders.clear(); + cueBuilders.add(currentCueBuilder); + } + + private static char getChar(byte ccData) { + int index = (ccData & 0x7F) - 0x20; + return (char) BASIC_CHARACTER_SET[index]; + } + + private static char getSpecialChar(byte ccData) { + int index = ccData & 0x0F; + return (char) SPECIAL_CHARACTER_SET[index]; + } + + private static char getExtendedEsFrChar(byte ccData) { + int index = ccData & 0x1F; + return (char) SPECIAL_ES_FR_CHARACTER_SET[index]; + } + + private static char getExtendedPtDeChar(byte ccData) { + int index = ccData & 0x1F; + return (char) SPECIAL_PT_DE_CHARACTER_SET[index]; + } + + private static boolean isMidrowCtrlCode(byte cc1, byte cc2) { + // cc1 - 0|0|0|1|C|0|0|1 + // cc2 - 0|0|1|0|X|X|X|X + return ((cc1 & 0xF7) == 0x11) && ((cc2 & 0xF0) == 0x20); + } + + private static boolean isPreambleAddressCode(byte cc1, byte cc2) { + // cc1 - 0|0|0|1|C|X|X|X + // cc2 - 0|1|X|X|X|X|X|X + return ((cc1 & 0xF0) == 0x10) && ((cc2 & 0xC0) == 0x40); + } + + private static boolean isTabCtrlCode(byte cc1, byte cc2) { + // cc1 - 0|0|0|1|C|1|1|1 + // cc2 - 0|0|1|0|0|0|0|1 to 0|0|1|0|0|0|1|1 + return ((cc1 & 0xF7) == 0x17) && (cc2 >= 0x21 && cc2 <= 0x23); + } + + private static boolean isMiscCode(byte cc1, byte cc2) { + // cc1 - 0|0|0|1|C|1|0|0 + // cc2 - 0|0|1|0|X|X|X|X + return ((cc1 & 0xF7) == 0x14) && ((cc2 & 0xF0) == 0x20); + } + + private static boolean isRepeatable(byte cc1) { + // cc1 - 0|0|0|1|X|X|X|X + return (cc1 & 0xF0) == 0x10; + } + + private static class CueBuilder { + + private static final int POSITION_UNSET = -1; + + // 608 captions define a 15 row by 32 column screen grid. These constants convert from 608 + // positions to normalized screen position. + private static final int SCREEN_CHARWIDTH = 32; + private static final int BASE_ROW = 15; + + private final List preambleStyles; + private final List midrowStyles; + private final List rolledUpCaptions; + private final SpannableStringBuilder captionStringBuilder; + + private int row; + private int indent; + private int tabOffset; + private int captionMode; + private int captionRowCount; + private int underlineStartPosition; + + public CueBuilder(int captionMode, int captionRowCount) { + preambleStyles = new ArrayList<>(); + midrowStyles = new ArrayList<>(); + rolledUpCaptions = new LinkedList<>(); + captionStringBuilder = new SpannableStringBuilder(); + reset(captionMode, captionRowCount); + } + + public void reset(int captionMode, int captionRowCount) { + preambleStyles.clear(); + midrowStyles.clear(); + rolledUpCaptions.clear(); + captionStringBuilder.clear(); + row = BASE_ROW; + indent = 0; + tabOffset = 0; + this.captionMode = captionMode; + this.captionRowCount = captionRowCount; + underlineStartPosition = POSITION_UNSET; + } + + public boolean isEmpty() { + return preambleStyles.isEmpty() && midrowStyles.isEmpty() && rolledUpCaptions.isEmpty() + && captionStringBuilder.length() == 0; + } + + public void backspace() { + int length = captionStringBuilder.length(); + if (length > 0) { + captionStringBuilder.delete(length - 1, length); + } + } + + public int getRow() { + return row; + } + + public void setRow(int row) { + this.row = row; + } + + public void rollUp() { + rolledUpCaptions.add(buildSpannableString()); + captionStringBuilder.clear(); + preambleStyles.clear(); + midrowStyles.clear(); + underlineStartPosition = POSITION_UNSET; + + int numRows = Math.min(captionRowCount, row); + while (rolledUpCaptions.size() >= numRows) { + rolledUpCaptions.remove(0); + } + } + + public void setIndent(int indent) { + this.indent = indent; + } + + public void setTab(int tabs) { + tabOffset = tabs; + } + + public void setPreambleStyle(CharacterStyle style) { + preambleStyles.add(style); + } + + public void setMidrowStyle(CharacterStyle style, int nextStyleIncrement) { + midrowStyles.add(new CueStyle(style, captionStringBuilder.length(), nextStyleIncrement)); + } + + public void setUnderline(boolean enabled) { + if (enabled) { + underlineStartPosition = captionStringBuilder.length(); + } else if (underlineStartPosition != POSITION_UNSET) { + // underline spans won't overlap, so it's safe to modify the builder directly with them + captionStringBuilder.setSpan(new UnderlineSpan(), underlineStartPosition, + captionStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + underlineStartPosition = POSITION_UNSET; + } + } + + public void append(char text) { + captionStringBuilder.append(text); + } + + public SpannableString buildSpannableString() { + int length = captionStringBuilder.length(); + + // preamble styles apply to the entire cue + for (int i = 0; i < preambleStyles.size(); i++) { + captionStringBuilder.setSpan(preambleStyles.get(i), 0, length, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // midrow styles only apply to part of the cue, and after preamble styles + for (int i = 0; i < midrowStyles.size(); i++) { + CueStyle cueStyle = midrowStyles.get(i); + int end = (i < midrowStyles.size() - cueStyle.nextStyleIncrement) + ? midrowStyles.get(i + cueStyle.nextStyleIncrement).start + : length; + captionStringBuilder.setSpan(cueStyle.style, cueStyle.start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // special case for midrow underlines that went to the end of the cue + if (underlineStartPosition != POSITION_UNSET) { + captionStringBuilder.setSpan(new UnderlineSpan(), underlineStartPosition, length, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + return new SpannableString(captionStringBuilder); + } + + public Cue build() { + SpannableStringBuilder cueString = new SpannableStringBuilder(); + // Add any rolled up captions, separated by new lines. + for (int i = 0; i < rolledUpCaptions.size(); i++) { + cueString.append(rolledUpCaptions.get(i)); + cueString.append('\n'); + } + // Add the current line. + cueString.append(buildSpannableString()); + + if (cueString.length() == 0) { + // The cue is empty. + return null; + } + + float position; + int positionAnchor; + // The number of empty columns before the start of the text, in the range [0-31]. + int startPadding = indent + tabOffset; + // The number of empty columns after the end of the text, in the same range. + int endPadding = SCREEN_CHARWIDTH - startPadding - cueString.length(); + int startEndPaddingDelta = startPadding - endPadding; + if (captionMode == CC_MODE_POP_ON && Math.abs(startEndPaddingDelta) < 3) { + // Treat approximately centered pop-on captions are middle aligned. + position = 0.5f; + positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + } else if (captionMode == CC_MODE_POP_ON && startEndPaddingDelta > 0) { + // Treat pop-on captions with less padding at the end than the start as end aligned. + position = (float) (SCREEN_CHARWIDTH - endPadding) / SCREEN_CHARWIDTH; + // Adjust the position to fit within the safe area. + position = position * 0.8f + 0.1f; + positionAnchor = Cue.ANCHOR_TYPE_END; + } else { + // For all other cases assume start aligned. + position = (float) startPadding / SCREEN_CHARWIDTH; + // Adjust the position to fit within the safe area. + position = position * 0.8f + 0.1f; + positionAnchor = Cue.ANCHOR_TYPE_START; + } + + int lineAnchor; + int line; + // Note: Row indices are in the range [1-15]. + if (captionMode == CC_MODE_ROLL_UP || row > (BASE_ROW / 2)) { + lineAnchor = Cue.ANCHOR_TYPE_END; + line = row - BASE_ROW; + // Two line adjustments. The first is because line indices from the bottom of the window + // start from -1 rather than 0. The second is a blank row to act as the safe area. + line -= 2; + } else { + lineAnchor = Cue.ANCHOR_TYPE_START; + // Line indices from the top of the window start from 0, but we want a blank row to act as + // the safe area. As a result no adjustment is necessary. + line = row; + } + + return new Cue(cueString, Alignment.ALIGN_NORMAL, line, Cue.LINE_TYPE_NUMBER, lineAnchor, + position, positionAnchor, Cue.DIMEN_UNSET); + } + + @Override + public String toString() { + return captionStringBuilder.toString(); + } + + private static class CueStyle { + + public final CharacterStyle style; + public final int start; + public final int nextStyleIncrement; + + public CueStyle(CharacterStyle style, int start, int nextStyleIncrement) { + this.style = style; + this.start = start; + this.nextStyleIncrement = nextStyleIncrement; + } + + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea708Cue.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea708Cue.java new file mode 100644 index 0000000..f54e6fb --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea708Cue.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.cea; + +import android.support.annotation.NonNull; +import android.text.Layout.Alignment; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; + +/** + * A {@link Cue} for CEA-708. + */ +/* package */ final class Cea708Cue extends Cue implements Comparable { + + /** + * An unset priority. + */ + public static final int PRIORITY_UNSET = -1; + + /** + * The priority of the cue box. + */ + public final int priority; + + /** + * @param text See {@link #text}. + * @param textAlignment See {@link #textAlignment}. + * @param line See {@link #line}. + * @param lineType See {@link #lineType}. + * @param lineAnchor See {@link #lineAnchor}. + * @param position See {@link #position}. + * @param positionAnchor See {@link #positionAnchor}. + * @param size See {@link #size}. + * @param windowColorSet See {@link #windowColorSet}. + * @param windowColor See {@link #windowColor}. + * @param priority See (@link #priority}. + */ + public Cea708Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, + @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, + boolean windowColorSet, int windowColor, int priority) { + super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, size, + windowColorSet, windowColor); + this.priority = priority; + } + + @Override + public int compareTo(@NonNull Cea708Cue other) { + if (other.priority < priority) { + return -1; + } else if (other.priority > priority) { + return 1; + } + return 0; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea708Decoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea708Decoder.java new file mode 100644 index 0000000..425a099 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/Cea708Decoder.java @@ -0,0 +1,1233 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.cea; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.Layout.Alignment; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue.AnchorType; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * A {@link SubtitleDecoder} for CEA-708 (also known as "EIA-708"). + */ +public final class Cea708Decoder extends CeaDecoder { + + private static final String TAG = "Cea708Decoder"; + + private static final int NUM_WINDOWS = 8; + + private static final int DTVCC_PACKET_DATA = 0x02; + private static final int DTVCC_PACKET_START = 0x03; + private static final int CC_VALID_FLAG = 0x04; + + // Base Commands + private static final int GROUP_C0_END = 0x1F; // Miscellaneous Control Codes + private static final int GROUP_G0_END = 0x7F; // ASCII Printable Characters + private static final int GROUP_C1_END = 0x9F; // Captioning Command Control Codes + private static final int GROUP_G1_END = 0xFF; // ISO 8859-1 LATIN-1 Character Set + + // Extended Commands + private static final int GROUP_C2_END = 0x1F; // Extended Control Code Set 1 + private static final int GROUP_G2_END = 0x7F; // Extended Miscellaneous Characters + private static final int GROUP_C3_END = 0x9F; // Extended Control Code Set 2 + private static final int GROUP_G3_END = 0xFF; // Future Expansion + + // Group C0 Commands + private static final int COMMAND_NUL = 0x00; // Nul + private static final int COMMAND_ETX = 0x03; // EndOfText + private static final int COMMAND_BS = 0x08; // Backspace + private static final int COMMAND_FF = 0x0C; // FormFeed (Flush) + private static final int COMMAND_CR = 0x0D; // CarriageReturn + private static final int COMMAND_HCR = 0x0E; // ClearLine + private static final int COMMAND_EXT1 = 0x10; // Extended Control Code Flag + private static final int COMMAND_EXT1_START = 0x11; + private static final int COMMAND_EXT1_END = 0x17; + private static final int COMMAND_P16_START = 0x18; + private static final int COMMAND_P16_END = 0x1F; + + // Group C1 Commands + private static final int COMMAND_CW0 = 0x80; // SetCurrentWindow to 0 + private static final int COMMAND_CW1 = 0x81; // SetCurrentWindow to 1 + private static final int COMMAND_CW2 = 0x82; // SetCurrentWindow to 2 + private static final int COMMAND_CW3 = 0x83; // SetCurrentWindow to 3 + private static final int COMMAND_CW4 = 0x84; // SetCurrentWindow to 4 + private static final int COMMAND_CW5 = 0x85; // SetCurrentWindow to 5 + private static final int COMMAND_CW6 = 0x86; // SetCurrentWindow to 6 + private static final int COMMAND_CW7 = 0x87; // SetCurrentWindow to 7 + private static final int COMMAND_CLW = 0x88; // ClearWindows (+1 byte) + private static final int COMMAND_DSW = 0x89; // DisplayWindows (+1 byte) + private static final int COMMAND_HDW = 0x8A; // HideWindows (+1 byte) + private static final int COMMAND_TGW = 0x8B; // ToggleWindows (+1 byte) + private static final int COMMAND_DLW = 0x8C; // DeleteWindows (+1 byte) + private static final int COMMAND_DLY = 0x8D; // Delay (+1 byte) + private static final int COMMAND_DLC = 0x8E; // DelayCancel + private static final int COMMAND_RST = 0x8F; // Reset + private static final int COMMAND_SPA = 0x90; // SetPenAttributes (+2 bytes) + private static final int COMMAND_SPC = 0x91; // SetPenColor (+3 bytes) + private static final int COMMAND_SPL = 0x92; // SetPenLocation (+2 bytes) + private static final int COMMAND_SWA = 0x97; // SetWindowAttributes (+4 bytes) + private static final int COMMAND_DF0 = 0x98; // DefineWindow 0 (+6 bytes) + private static final int COMMAND_DF1 = 0x99; // DefineWindow 1 (+6 bytes) + private static final int COMMAND_DF2 = 0x9A; // DefineWindow 2 (+6 bytes) + private static final int COMMAND_DF3 = 0x9B; // DefineWindow 3 (+6 bytes) + private static final int COMMAND_DS4 = 0x9C; // DefineWindow 4 (+6 bytes) + private static final int COMMAND_DF5 = 0x9D; // DefineWindow 5 (+6 bytes) + private static final int COMMAND_DF6 = 0x9E; // DefineWindow 6 (+6 bytes) + private static final int COMMAND_DF7 = 0x9F; // DefineWindow 7 (+6 bytes) + + // G0 Table Special Chars + private static final int CHARACTER_MN = 0x7F; // MusicNote + + // G2 Table Special Chars + private static final int CHARACTER_TSP = 0x20; + private static final int CHARACTER_NBTSP = 0x21; + private static final int CHARACTER_ELLIPSIS = 0x25; + private static final int CHARACTER_BIG_CARONS = 0x2A; + private static final int CHARACTER_BIG_OE = 0x2C; + private static final int CHARACTER_SOLID_BLOCK = 0x30; + private static final int CHARACTER_OPEN_SINGLE_QUOTE = 0x31; + private static final int CHARACTER_CLOSE_SINGLE_QUOTE = 0x32; + private static final int CHARACTER_OPEN_DOUBLE_QUOTE = 0x33; + private static final int CHARACTER_CLOSE_DOUBLE_QUOTE = 0x34; + private static final int CHARACTER_BOLD_BULLET = 0x35; + private static final int CHARACTER_TM = 0x39; + private static final int CHARACTER_SMALL_CARONS = 0x3A; + private static final int CHARACTER_SMALL_OE = 0x3C; + private static final int CHARACTER_SM = 0x3D; + private static final int CHARACTER_DIAERESIS_Y = 0x3F; + private static final int CHARACTER_ONE_EIGHTH = 0x76; + private static final int CHARACTER_THREE_EIGHTHS = 0x77; + private static final int CHARACTER_FIVE_EIGHTHS = 0x78; + private static final int CHARACTER_SEVEN_EIGHTHS = 0x79; + private static final int CHARACTER_VERTICAL_BORDER = 0x7A; + private static final int CHARACTER_UPPER_RIGHT_BORDER = 0x7B; + private static final int CHARACTER_LOWER_LEFT_BORDER = 0x7C; + private static final int CHARACTER_HORIZONTAL_BORDER = 0x7D; + private static final int CHARACTER_LOWER_RIGHT_BORDER = 0x7E; + private static final int CHARACTER_UPPER_LEFT_BORDER = 0x7F; + + private final ParsableByteArray ccData; + private final ParsableBitArray serviceBlockPacket; + + private final int selectedServiceNumber; + private final CueBuilder[] cueBuilders; + + private CueBuilder currentCueBuilder; + private List cues; + private List lastCues; + + private DtvCcPacket currentDtvCcPacket; + private int currentWindow; + + public Cea708Decoder(int accessibilityChannel) { + ccData = new ParsableByteArray(); + serviceBlockPacket = new ParsableBitArray(); + selectedServiceNumber = (accessibilityChannel == Format.NO_VALUE) ? 1 : accessibilityChannel; + + cueBuilders = new CueBuilder[NUM_WINDOWS]; + for (int i = 0; i < NUM_WINDOWS; i++) { + cueBuilders[i] = new CueBuilder(); + } + + currentCueBuilder = cueBuilders[0]; + resetCueBuilders(); + } + + @Override + public String getName() { + return "Cea708Decoder"; + } + + @Override + public void flush() { + super.flush(); + cues = null; + lastCues = null; + currentWindow = 0; + currentCueBuilder = cueBuilders[currentWindow]; + resetCueBuilders(); + currentDtvCcPacket = null; + } + + @Override + protected boolean isNewSubtitleDataAvailable() { + return cues != lastCues; + } + + @Override + protected Subtitle createSubtitle() { + lastCues = cues; + return new CeaSubtitle(cues); + } + + @Override + protected void decode(SubtitleInputBuffer inputBuffer) { + ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit()); + while (ccData.bytesLeft() >= 3) { + int ccTypeAndValid = (ccData.readUnsignedByte() & 0x07); + + int ccType = ccTypeAndValid & (DTVCC_PACKET_DATA | DTVCC_PACKET_START); + boolean ccValid = (ccTypeAndValid & CC_VALID_FLAG) == CC_VALID_FLAG; + byte ccData1 = (byte) ccData.readUnsignedByte(); + byte ccData2 = (byte) ccData.readUnsignedByte(); + + // Ignore any non-CEA-708 data + if (ccType != DTVCC_PACKET_DATA && ccType != DTVCC_PACKET_START) { + continue; + } + + if (!ccValid) { + // This byte-pair isn't valid, ignore it and continue. + continue; + } + + if (ccType == DTVCC_PACKET_START) { + finalizeCurrentPacket(); + + int sequenceNumber = (ccData1 & 0xC0) >> 6; // first 2 bits + int packetSize = ccData1 & 0x3F; // last 6 bits + if (packetSize == 0) { + packetSize = 64; + } + + currentDtvCcPacket = new DtvCcPacket(sequenceNumber, packetSize); + currentDtvCcPacket.packetData[currentDtvCcPacket.currentIndex++] = ccData2; + } else { + // The only remaining valid packet type is DTVCC_PACKET_DATA + Assertions.checkArgument(ccType == DTVCC_PACKET_DATA); + + if (currentDtvCcPacket == null) { + continue; + } + + currentDtvCcPacket.packetData[currentDtvCcPacket.currentIndex++] = ccData1; + currentDtvCcPacket.packetData[currentDtvCcPacket.currentIndex++] = ccData2; + } + + if (currentDtvCcPacket.currentIndex == (currentDtvCcPacket.packetSize * 2 - 1)) { + finalizeCurrentPacket(); + } + } + } + + private void finalizeCurrentPacket() { + if (currentDtvCcPacket == null) { + // No packet to finalize; + return; + } + + processCurrentPacket(); + currentDtvCcPacket = null; + } + + private void processCurrentPacket() { + if (currentDtvCcPacket.currentIndex != (currentDtvCcPacket.packetSize * 2 - 1)) { + return; + } + + serviceBlockPacket.reset(currentDtvCcPacket.packetData, currentDtvCcPacket.currentIndex); + + int serviceNumber = serviceBlockPacket.readBits(3); + int blockSize = serviceBlockPacket.readBits(5); + if (serviceNumber == 7) { + // extended service numbers + serviceBlockPacket.skipBits(2); + serviceNumber += serviceBlockPacket.readBits(6); + } + + // Ignore packets in which blockSize is 0 + if (blockSize == 0) { + return; + } + + if (serviceNumber != selectedServiceNumber) { + return; + } + + // The cues should be updated if we receive a C0 ETX command, any C1 command, or if after + // processing the service block any text has been added to the buffer. See CEA-708-B Section + // 8.10.4 for more details. + boolean cuesNeedUpdate = false; + + while (serviceBlockPacket.bitsLeft() > 0) { + int command = serviceBlockPacket.readBits(8); + if (command != COMMAND_EXT1) { + if (command <= GROUP_C0_END) { + handleC0Command(command); + // If the C0 command was an ETX command, the cues are updated in handleC0Command. + } else if (command <= GROUP_G0_END) { + handleG0Character(command); + cuesNeedUpdate = true; + } else if (command <= GROUP_C1_END) { + handleC1Command(command); + cuesNeedUpdate = true; + } else if (command <= GROUP_G1_END) { + handleG1Character(command); + cuesNeedUpdate = true; + } + } else { + // Read the extended command + command = serviceBlockPacket.readBits(8); + if (command <= GROUP_C2_END) { + handleC2Command(command); + } else if (command <= GROUP_G2_END) { + handleG2Character(command); + cuesNeedUpdate = true; + } else if (command <= GROUP_C3_END) { + handleC3Command(command); + } else if (command <= GROUP_G3_END) { + handleG3Character(command); + cuesNeedUpdate = true; + } + } + } + + if (cuesNeedUpdate) { + cues = getDisplayCues(); + } + } + + private void handleC0Command(int command) { + switch (command) { + case COMMAND_NUL: + // Do nothing. + break; + case COMMAND_ETX: + cues = getDisplayCues(); + break; + case COMMAND_BS: + currentCueBuilder.backspace(); + break; + case COMMAND_FF: + resetCueBuilders(); + break; + case COMMAND_CR: + currentCueBuilder.append('\n'); + break; + case COMMAND_HCR: + // TODO: Add support for this command. + break; + default: + if (command >= COMMAND_EXT1_START && command <= COMMAND_EXT1_END) { + serviceBlockPacket.skipBits(8); + } else if (command >= COMMAND_P16_START && command <= COMMAND_P16_END) { + serviceBlockPacket.skipBits(16); + } + } + } + + private void handleC1Command(int command) { + int window; + switch (command) { + case COMMAND_CW0: + case COMMAND_CW1: + case COMMAND_CW2: + case COMMAND_CW3: + case COMMAND_CW4: + case COMMAND_CW5: + case COMMAND_CW6: + case COMMAND_CW7: + window = (command - COMMAND_CW0); + if (currentWindow != window) { + currentWindow = window; + currentCueBuilder = cueBuilders[window]; + } + break; + case COMMAND_CLW: + for (int i = 1; i <= NUM_WINDOWS; i++) { + if (serviceBlockPacket.readBit()) { + cueBuilders[NUM_WINDOWS - i].clear(); + } + } + break; + case COMMAND_DSW: + for (int i = 1; i <= NUM_WINDOWS; i++) { + if (serviceBlockPacket.readBit()) { + cueBuilders[NUM_WINDOWS - i].setVisibility(true); + } + } + break; + case COMMAND_HDW: + for (int i = 1; i <= NUM_WINDOWS; i++) { + if (serviceBlockPacket.readBit()) { + cueBuilders[NUM_WINDOWS - i].setVisibility(false); + } + } + break; + case COMMAND_TGW: + for (int i = 1; i <= NUM_WINDOWS; i++) { + if (serviceBlockPacket.readBit()) { + CueBuilder cueBuilder = cueBuilders[NUM_WINDOWS - i]; + cueBuilder.setVisibility(!cueBuilder.isVisible()); + } + } + break; + case COMMAND_DLW: + for (int i = 1; i <= NUM_WINDOWS; i++) { + if (serviceBlockPacket.readBit()) { + cueBuilders[NUM_WINDOWS - i].reset(); + } + } + break; + case COMMAND_DLY: + // TODO: Add support for delay commands. + serviceBlockPacket.skipBits(8); + break; + case COMMAND_DLC: + // TODO: Add support for delay commands. + break; + case COMMAND_RST: + resetCueBuilders(); + break; + case COMMAND_SPA: + if (!currentCueBuilder.isDefined()) { + // ignore this command if the current window/cue isn't defined + serviceBlockPacket.skipBits(16); + } else { + handleSetPenAttributes(); + } + break; + case COMMAND_SPC: + if (!currentCueBuilder.isDefined()) { + // ignore this command if the current window/cue isn't defined + serviceBlockPacket.skipBits(24); + } else { + handleSetPenColor(); + } + break; + case COMMAND_SPL: + if (!currentCueBuilder.isDefined()) { + // ignore this command if the current window/cue isn't defined + serviceBlockPacket.skipBits(16); + } else { + handleSetPenLocation(); + } + break; + case COMMAND_SWA: + if (!currentCueBuilder.isDefined()) { + // ignore this command if the current window/cue isn't defined + serviceBlockPacket.skipBits(32); + } else { + handleSetWindowAttributes(); + } + break; + case COMMAND_DF0: + case COMMAND_DF1: + case COMMAND_DF2: + case COMMAND_DF3: + case COMMAND_DS4: + case COMMAND_DF5: + case COMMAND_DF6: + case COMMAND_DF7: + window = (command - COMMAND_DF0); + handleDefineWindow(window); + // We also set the current window to the newly defined window. + if (currentWindow != window) { + currentWindow = window; + currentCueBuilder = cueBuilders[window]; + } + break; + default: + Log.w(TAG, "Invalid C1 command: " + command); + } + } + + private void handleC2Command(int command) { + // C2 Table doesn't contain any commands in CEA-708-B, but we do need to skip bytes + if (command <= 0x07) { + // Do nothing. + } else if (command <= 0x0F) { + serviceBlockPacket.skipBits(8); + } else if (command <= 0x17) { + serviceBlockPacket.skipBits(16); + } else if (command <= 0x1F) { + serviceBlockPacket.skipBits(24); + } + } + + private void handleC3Command(int command) { + // C3 Table doesn't contain any commands in CEA-708-B, but we do need to skip bytes + if (command <= 0x87) { + serviceBlockPacket.skipBits(32); + } else if (command <= 0x8F) { + serviceBlockPacket.skipBits(40); + } else if (command <= 0x9F) { + // 90-9F are variable length codes; the first byte defines the header with the first + // 2 bits specifying the type and the last 6 bits specifying the remaining length of the + // command in bytes + serviceBlockPacket.skipBits(2); + int length = serviceBlockPacket.readBits(6); + serviceBlockPacket.skipBits(8 * length); + } + } + + private void handleG0Character(int characterCode) { + if (characterCode == CHARACTER_MN) { + currentCueBuilder.append('\u266B'); + } else { + currentCueBuilder.append((char) (characterCode & 0xFF)); + } + } + + private void handleG1Character(int characterCode) { + currentCueBuilder.append((char) (characterCode & 0xFF)); + } + + private void handleG2Character(int characterCode) { + switch (characterCode) { + case CHARACTER_TSP: + currentCueBuilder.append('\u0020'); + break; + case CHARACTER_NBTSP: + currentCueBuilder.append('\u00A0'); + break; + case CHARACTER_ELLIPSIS: + currentCueBuilder.append('\u2026'); + break; + case CHARACTER_BIG_CARONS: + currentCueBuilder.append('\u0160'); + break; + case CHARACTER_BIG_OE: + currentCueBuilder.append('\u0152'); + break; + case CHARACTER_SOLID_BLOCK: + currentCueBuilder.append('\u2588'); + break; + case CHARACTER_OPEN_SINGLE_QUOTE: + currentCueBuilder.append('\u2018'); + break; + case CHARACTER_CLOSE_SINGLE_QUOTE: + currentCueBuilder.append('\u2019'); + break; + case CHARACTER_OPEN_DOUBLE_QUOTE: + currentCueBuilder.append('\u201C'); + break; + case CHARACTER_CLOSE_DOUBLE_QUOTE: + currentCueBuilder.append('\u201D'); + break; + case CHARACTER_BOLD_BULLET: + currentCueBuilder.append('\u2022'); + break; + case CHARACTER_TM: + currentCueBuilder.append('\u2122'); + break; + case CHARACTER_SMALL_CARONS: + currentCueBuilder.append('\u0161'); + break; + case CHARACTER_SMALL_OE: + currentCueBuilder.append('\u0153'); + break; + case CHARACTER_SM: + currentCueBuilder.append('\u2120'); + break; + case CHARACTER_DIAERESIS_Y: + currentCueBuilder.append('\u0178'); + break; + case CHARACTER_ONE_EIGHTH: + currentCueBuilder.append('\u215B'); + break; + case CHARACTER_THREE_EIGHTHS: + currentCueBuilder.append('\u215C'); + break; + case CHARACTER_FIVE_EIGHTHS: + currentCueBuilder.append('\u215D'); + break; + case CHARACTER_SEVEN_EIGHTHS: + currentCueBuilder.append('\u215E'); + break; + case CHARACTER_VERTICAL_BORDER: + currentCueBuilder.append('\u2502'); + break; + case CHARACTER_UPPER_RIGHT_BORDER: + currentCueBuilder.append('\u2510'); + break; + case CHARACTER_LOWER_LEFT_BORDER: + currentCueBuilder.append('\u2514'); + break; + case CHARACTER_HORIZONTAL_BORDER: + currentCueBuilder.append('\u2500'); + break; + case CHARACTER_LOWER_RIGHT_BORDER: + currentCueBuilder.append('\u2518'); + break; + case CHARACTER_UPPER_LEFT_BORDER: + currentCueBuilder.append('\u250C'); + break; + default: + Log.w(TAG, "Invalid G2 character: " + characterCode); + // The CEA-708 specification doesn't specify what to do in the case of an unexpected + // value in the G2 character range, so we ignore it. + } + } + + private void handleG3Character(int characterCode) { + if (characterCode == 0xA0) { + currentCueBuilder.append('\u33C4'); + } else { + Log.w(TAG, "Invalid G3 character: " + characterCode); + // Substitute any unsupported G3 character with an underscore as per CEA-708 specification. + currentCueBuilder.append('_'); + } + } + + private void handleSetPenAttributes() { + // the SetPenAttributes command contains 2 bytes of data + // first byte + int textTag = serviceBlockPacket.readBits(4); + int offset = serviceBlockPacket.readBits(2); + int penSize = serviceBlockPacket.readBits(2); + // second byte + boolean italicsToggle = serviceBlockPacket.readBit(); + boolean underlineToggle = serviceBlockPacket.readBit(); + int edgeType = serviceBlockPacket.readBits(3); + int fontStyle = serviceBlockPacket.readBits(3); + + currentCueBuilder.setPenAttributes(textTag, offset, penSize, italicsToggle, underlineToggle, + edgeType, fontStyle); + } + + private void handleSetPenColor() { + // the SetPenColor command contains 3 bytes of data + // first byte + int foregroundO = serviceBlockPacket.readBits(2); + int foregroundR = serviceBlockPacket.readBits(2); + int foregroundG = serviceBlockPacket.readBits(2); + int foregroundB = serviceBlockPacket.readBits(2); + int foregroundColor = CueBuilder.getArgbColorFromCeaColor(foregroundR, foregroundG, foregroundB, + foregroundO); + // second byte + int backgroundO = serviceBlockPacket.readBits(2); + int backgroundR = serviceBlockPacket.readBits(2); + int backgroundG = serviceBlockPacket.readBits(2); + int backgroundB = serviceBlockPacket.readBits(2); + int backgroundColor = CueBuilder.getArgbColorFromCeaColor(backgroundR, backgroundG, backgroundB, + backgroundO); + // third byte + serviceBlockPacket.skipBits(2); // null padding + int edgeR = serviceBlockPacket.readBits(2); + int edgeG = serviceBlockPacket.readBits(2); + int edgeB = serviceBlockPacket.readBits(2); + int edgeColor = CueBuilder.getArgbColorFromCeaColor(edgeR, edgeG, edgeB); + + currentCueBuilder.setPenColor(foregroundColor, backgroundColor, edgeColor); + } + + private void handleSetPenLocation() { + // the SetPenLocation command contains 2 bytes of data + // first byte + serviceBlockPacket.skipBits(4); + int row = serviceBlockPacket.readBits(4); + // second byte + serviceBlockPacket.skipBits(2); + int column = serviceBlockPacket.readBits(6); + + currentCueBuilder.setPenLocation(row, column); + } + + private void handleSetWindowAttributes() { + // the SetWindowAttributes command contains 4 bytes of data + // first byte + int fillO = serviceBlockPacket.readBits(2); + int fillR = serviceBlockPacket.readBits(2); + int fillG = serviceBlockPacket.readBits(2); + int fillB = serviceBlockPacket.readBits(2); + int fillColor = CueBuilder.getArgbColorFromCeaColor(fillR, fillG, fillB, fillO); + // second byte + int borderType = serviceBlockPacket.readBits(2); // only the lower 2 bits of borderType + int borderR = serviceBlockPacket.readBits(2); + int borderG = serviceBlockPacket.readBits(2); + int borderB = serviceBlockPacket.readBits(2); + int borderColor = CueBuilder.getArgbColorFromCeaColor(borderR, borderG, borderB); + // third byte + if (serviceBlockPacket.readBit()) { + borderType |= 0x04; // set the top bit of the 3-bit borderType + } + boolean wordWrapToggle = serviceBlockPacket.readBit(); + int printDirection = serviceBlockPacket.readBits(2); + int scrollDirection = serviceBlockPacket.readBits(2); + int justification = serviceBlockPacket.readBits(2); + // fourth byte + // Note that we don't intend to support display effects + serviceBlockPacket.skipBits(8); // effectSpeed(4), effectDirection(2), displayEffect(2) + + currentCueBuilder.setWindowAttributes(fillColor, borderColor, wordWrapToggle, borderType, + printDirection, scrollDirection, justification); + } + + private void handleDefineWindow(int window) { + CueBuilder cueBuilder = cueBuilders[window]; + + // the DefineWindow command contains 6 bytes of data + // first byte + serviceBlockPacket.skipBits(2); // null padding + boolean visible = serviceBlockPacket.readBit(); + boolean rowLock = serviceBlockPacket.readBit(); + boolean columnLock = serviceBlockPacket.readBit(); + int priority = serviceBlockPacket.readBits(3); + // second byte + boolean relativePositioning = serviceBlockPacket.readBit(); + int verticalAnchor = serviceBlockPacket.readBits(7); + // third byte + int horizontalAnchor = serviceBlockPacket.readBits(8); + // fourth byte + int anchorId = serviceBlockPacket.readBits(4); + int rowCount = serviceBlockPacket.readBits(4); + // fifth byte + serviceBlockPacket.skipBits(2); // null padding + int columnCount = serviceBlockPacket.readBits(6); + // sixth byte + serviceBlockPacket.skipBits(2); // null padding + int windowStyle = serviceBlockPacket.readBits(3); + int penStyle = serviceBlockPacket.readBits(3); + + cueBuilder.defineWindow(visible, rowLock, columnLock, priority, relativePositioning, + verticalAnchor, horizontalAnchor, rowCount, columnCount, anchorId, windowStyle, penStyle); + } + + private List getDisplayCues() { + List displayCues = new ArrayList<>(); + for (int i = 0; i < NUM_WINDOWS; i++) { + if (!cueBuilders[i].isEmpty() && cueBuilders[i].isVisible()) { + displayCues.add(cueBuilders[i].build()); + } + } + Collections.sort(displayCues); + return Collections.unmodifiableList(displayCues); + } + + private void resetCueBuilders() { + for (int i = 0; i < NUM_WINDOWS; i++) { + cueBuilders[i].reset(); + } + } + + private static final class DtvCcPacket { + + public final int sequenceNumber; + public final int packetSize; + public final byte[] packetData; + + int currentIndex; + + public DtvCcPacket(int sequenceNumber, int packetSize) { + this.sequenceNumber = sequenceNumber; + this.packetSize = packetSize; + packetData = new byte[2 * packetSize - 1]; + currentIndex = 0; + } + + } + + // TODO: There is a lot of overlap between Cea708Decoder.CueBuilder and Cea608Decoder.CueBuilder + // which could be refactored into a separate class. + private static final class CueBuilder { + + private static final int RELATIVE_CUE_SIZE = 99; + private static final int VERTICAL_SIZE = 74; + private static final int HORIZONTAL_SIZE = 209; + + private static final int DEFAULT_PRIORITY = 4; + + private static final int MAXIMUM_ROW_COUNT = 15; + + private static final int JUSTIFICATION_LEFT = 0; + private static final int JUSTIFICATION_RIGHT = 1; + private static final int JUSTIFICATION_CENTER = 2; + private static final int JUSTIFICATION_FULL = 3; + + private static final int DIRECTION_LEFT_TO_RIGHT = 0; + private static final int DIRECTION_RIGHT_TO_LEFT = 1; + private static final int DIRECTION_TOP_TO_BOTTOM = 2; + private static final int DIRECTION_BOTTOM_TO_TOP = 3; + + // TODO: Add other border/edge types when utilized. + private static final int BORDER_AND_EDGE_TYPE_NONE = 0; + private static final int BORDER_AND_EDGE_TYPE_UNIFORM = 3; + + public static final int COLOR_SOLID_WHITE = getArgbColorFromCeaColor(2, 2, 2, 0); + public static final int COLOR_SOLID_BLACK = getArgbColorFromCeaColor(0, 0, 0, 0); + public static final int COLOR_TRANSPARENT = getArgbColorFromCeaColor(0, 0, 0, 3); + + // TODO: Add other sizes when utilized. + private static final int PEN_SIZE_STANDARD = 1; + + // TODO: Add other pen font styles when utilized. + private static final int PEN_FONT_STYLE_DEFAULT = 0; + private static final int PEN_FONT_STYLE_MONOSPACED_WITH_SERIFS = 1; + private static final int PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITH_SERIFS = 2; + private static final int PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS = 3; + private static final int PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS = 4; + + // TODO: Add other pen offsets when utilized. + private static final int PEN_OFFSET_NORMAL = 1; + + // The window style properties are specified in the CEA-708 specification. + private static final int[] WINDOW_STYLE_JUSTIFICATION = new int[]{ + JUSTIFICATION_LEFT, JUSTIFICATION_LEFT, JUSTIFICATION_LEFT, + JUSTIFICATION_LEFT, JUSTIFICATION_LEFT, JUSTIFICATION_CENTER, + JUSTIFICATION_LEFT + }; + private static final int[] WINDOW_STYLE_PRINT_DIRECTION = new int[]{ + DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT, + DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT, + DIRECTION_TOP_TO_BOTTOM + }; + private static final int[] WINDOW_STYLE_SCROLL_DIRECTION = new int[]{ + DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP, + DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP, + DIRECTION_RIGHT_TO_LEFT + }; + private static final boolean[] WINDOW_STYLE_WORD_WRAP = new boolean[]{ + false, false, false, true, true, true, false + }; + private static final int[] WINDOW_STYLE_FILL = new int[]{ + COLOR_SOLID_BLACK, COLOR_TRANSPARENT, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK, + COLOR_TRANSPARENT, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK + }; + + // The pen style properties are specified in the CEA-708 specification. + private static final int[] PEN_STYLE_FONT_STYLE = new int[]{ + PEN_FONT_STYLE_DEFAULT, PEN_FONT_STYLE_MONOSPACED_WITH_SERIFS, + PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITH_SERIFS, PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS, + PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS, + PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS, + PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS + }; + private static final int[] PEN_STYLE_EDGE_TYPE = new int[]{ + BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE, + BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_UNIFORM, + BORDER_AND_EDGE_TYPE_UNIFORM + }; + private static final int[] PEN_STYLE_BACKGROUND = new int[]{ + COLOR_SOLID_BLACK, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK, + COLOR_SOLID_BLACK, COLOR_TRANSPARENT, COLOR_TRANSPARENT}; + + private final List rolledUpCaptions; + private final SpannableStringBuilder captionStringBuilder; + + // Window/Cue properties + private boolean defined; + private boolean visible; + private int priority; + private boolean relativePositioning; + private int verticalAnchor; + private int horizontalAnchor; + private int anchorId; + private int rowCount; + private boolean rowLock; + private int justification; + private int windowStyleId; + private int penStyleId; + private int windowFillColor; + + // Pen/Text properties + private int italicsStartPosition; + private int underlineStartPosition; + private int foregroundColorStartPosition; + private int foregroundColor; + private int backgroundColorStartPosition; + private int backgroundColor; + private int row; + + public CueBuilder() { + rolledUpCaptions = new LinkedList<>(); + captionStringBuilder = new SpannableStringBuilder(); + reset(); + } + + public boolean isEmpty() { + return !isDefined() || (rolledUpCaptions.isEmpty() && captionStringBuilder.length() == 0); + } + + public void reset() { + clear(); + + defined = false; + visible = false; + priority = DEFAULT_PRIORITY; + relativePositioning = false; + verticalAnchor = 0; + horizontalAnchor = 0; + anchorId = 0; + rowCount = MAXIMUM_ROW_COUNT; + rowLock = true; + justification = JUSTIFICATION_LEFT; + windowStyleId = 0; + penStyleId = 0; + windowFillColor = COLOR_SOLID_BLACK; + + foregroundColor = COLOR_SOLID_WHITE; + backgroundColor = COLOR_SOLID_BLACK; + } + + public void clear() { + rolledUpCaptions.clear(); + captionStringBuilder.clear(); + italicsStartPosition = C.POSITION_UNSET; + underlineStartPosition = C.POSITION_UNSET; + foregroundColorStartPosition = C.POSITION_UNSET; + backgroundColorStartPosition = C.POSITION_UNSET; + row = 0; + } + + public boolean isDefined() { + return defined; + } + + public void setVisibility(boolean visible) { + this.visible = visible; + } + + public boolean isVisible() { + return visible; + } + + public void defineWindow(boolean visible, boolean rowLock, boolean columnLock, int priority, + boolean relativePositioning, int verticalAnchor, int horizontalAnchor, int rowCount, + int columnCount, int anchorId, int windowStyleId, int penStyleId) { + this.defined = true; + this.visible = visible; + this.rowLock = rowLock; + this.priority = priority; + this.relativePositioning = relativePositioning; + this.verticalAnchor = verticalAnchor; + this.horizontalAnchor = horizontalAnchor; + this.anchorId = anchorId; + + // Decoders must add one to rowCount to get the desired number of rows. + if (this.rowCount != rowCount + 1) { + this.rowCount = rowCount + 1; + + // Trim any rolled up captions that are no longer valid, if applicable. + while ((rowLock && (rolledUpCaptions.size() >= this.rowCount)) + || (rolledUpCaptions.size() >= MAXIMUM_ROW_COUNT)) { + rolledUpCaptions.remove(0); + } + } + + // TODO: Add support for column lock and count. + + if (windowStyleId != 0 && this.windowStyleId != windowStyleId) { + this.windowStyleId = windowStyleId; + // windowStyleId is 1-based. + int windowStyleIdIndex = windowStyleId - 1; + // Note that Border type and border color are the same for all window styles. + setWindowAttributes(WINDOW_STYLE_FILL[windowStyleIdIndex], COLOR_TRANSPARENT, + WINDOW_STYLE_WORD_WRAP[windowStyleIdIndex], BORDER_AND_EDGE_TYPE_NONE, + WINDOW_STYLE_PRINT_DIRECTION[windowStyleIdIndex], + WINDOW_STYLE_SCROLL_DIRECTION[windowStyleIdIndex], + WINDOW_STYLE_JUSTIFICATION[windowStyleIdIndex]); + } + + if (penStyleId != 0 && this.penStyleId != penStyleId) { + this.penStyleId = penStyleId; + // penStyleId is 1-based. + int penStyleIdIndex = penStyleId - 1; + // Note that pen size, offset, italics, underline, foreground color, and foreground + // opacity are the same for all pen styles. + setPenAttributes(0, PEN_OFFSET_NORMAL, PEN_SIZE_STANDARD, false, false, + PEN_STYLE_EDGE_TYPE[penStyleIdIndex], PEN_STYLE_FONT_STYLE[penStyleIdIndex]); + setPenColor(COLOR_SOLID_WHITE, PEN_STYLE_BACKGROUND[penStyleIdIndex], COLOR_SOLID_BLACK); + } + } + + + public void setWindowAttributes(int fillColor, int borderColor, boolean wordWrapToggle, + int borderType, int printDirection, int scrollDirection, int justification) { + this.windowFillColor = fillColor; + // TODO: Add support for border color and types. + // TODO: Add support for word wrap. + // TODO: Add support for other scroll directions. + // TODO: Add support for other print directions. + this.justification = justification; + + } + + public void setPenAttributes(int textTag, int offset, int penSize, boolean italicsToggle, + boolean underlineToggle, int edgeType, int fontStyle) { + // TODO: Add support for text tags. + // TODO: Add support for other offsets. + // TODO: Add support for other pen sizes. + + if (italicsStartPosition != C.POSITION_UNSET) { + if (!italicsToggle) { + captionStringBuilder.setSpan(new StyleSpan(Typeface.ITALIC), italicsStartPosition, + captionStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + italicsStartPosition = C.POSITION_UNSET; + } + } else if (italicsToggle) { + italicsStartPosition = captionStringBuilder.length(); + } + + if (underlineStartPosition != C.POSITION_UNSET) { + if (!underlineToggle) { + captionStringBuilder.setSpan(new UnderlineSpan(), underlineStartPosition, + captionStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + underlineStartPosition = C.POSITION_UNSET; + } + } else if (underlineToggle) { + underlineStartPosition = captionStringBuilder.length(); + } + + // TODO: Add support for edge types. + // TODO: Add support for other font styles. + } + + public void setPenColor(int foregroundColor, int backgroundColor, int edgeColor) { + if (foregroundColorStartPosition != C.POSITION_UNSET) { + if (this.foregroundColor != foregroundColor) { + captionStringBuilder.setSpan(new ForegroundColorSpan(this.foregroundColor), + foregroundColorStartPosition, captionStringBuilder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + if (foregroundColor != COLOR_SOLID_WHITE) { + foregroundColorStartPosition = captionStringBuilder.length(); + this.foregroundColor = foregroundColor; + } + + if (backgroundColorStartPosition != C.POSITION_UNSET) { + if (this.backgroundColor != backgroundColor) { + captionStringBuilder.setSpan(new BackgroundColorSpan(this.backgroundColor), + backgroundColorStartPosition, captionStringBuilder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + if (backgroundColor != COLOR_SOLID_BLACK) { + backgroundColorStartPosition = captionStringBuilder.length(); + this.backgroundColor = backgroundColor; + } + + // TODO: Add support for edge color. + } + + public void setPenLocation(int row, int column) { + // TODO: Support moving the pen location with a window properly. + + // Until we support proper pen locations, if we encounter a row that's different from the + // previous one, we should append a new line. Otherwise, we'll see strings that should be + // on new lines concatenated with the previous, resulting in 2 words being combined, as + // well as potentially drawing beyond the width of the window/screen. + if (this.row != row) { + append('\n'); + } + this.row = row; + } + + public void backspace() { + int length = captionStringBuilder.length(); + if (length > 0) { + captionStringBuilder.delete(length - 1, length); + } + } + + public void append(char text) { + if (text == '\n') { + rolledUpCaptions.add(buildSpannableString()); + captionStringBuilder.clear(); + + if (italicsStartPosition != C.POSITION_UNSET) { + italicsStartPosition = 0; + } + if (underlineStartPosition != C.POSITION_UNSET) { + underlineStartPosition = 0; + } + if (foregroundColorStartPosition != C.POSITION_UNSET) { + foregroundColorStartPosition = 0; + } + if (backgroundColorStartPosition != C.POSITION_UNSET) { + backgroundColorStartPosition = 0; + } + + while ((rowLock && (rolledUpCaptions.size() >= rowCount)) + || (rolledUpCaptions.size() >= MAXIMUM_ROW_COUNT)) { + rolledUpCaptions.remove(0); + } + } else { + captionStringBuilder.append(text); + } + } + + public SpannableString buildSpannableString() { + SpannableStringBuilder spannableStringBuilder = + new SpannableStringBuilder(captionStringBuilder); + int length = spannableStringBuilder.length(); + + if (length > 0) { + if (italicsStartPosition != C.POSITION_UNSET) { + spannableStringBuilder.setSpan(new StyleSpan(Typeface.ITALIC), italicsStartPosition, + length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + if (underlineStartPosition != C.POSITION_UNSET) { + spannableStringBuilder.setSpan(new UnderlineSpan(), underlineStartPosition, + length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + if (foregroundColorStartPosition != C.POSITION_UNSET) { + spannableStringBuilder.setSpan(new ForegroundColorSpan(foregroundColor), + foregroundColorStartPosition, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + if (backgroundColorStartPosition != C.POSITION_UNSET) { + spannableStringBuilder.setSpan(new BackgroundColorSpan(backgroundColor), + backgroundColorStartPosition, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + return new SpannableString(spannableStringBuilder); + } + + public Cea708Cue build() { + if (isEmpty()) { + // The cue is empty. + return null; + } + + SpannableStringBuilder cueString = new SpannableStringBuilder(); + + // Add any rolled up captions, separated by new lines. + for (int i = 0; i < rolledUpCaptions.size(); i++) { + cueString.append(rolledUpCaptions.get(i)); + cueString.append('\n'); + } + // Add the current line. + cueString.append(buildSpannableString()); + + // TODO: Add support for right-to-left languages (i.e. where right would correspond to normal + // alignment). + Alignment alignment; + switch (justification) { + case JUSTIFICATION_FULL: + // TODO: Add support for full justification. + case JUSTIFICATION_LEFT: + alignment = Alignment.ALIGN_NORMAL; + break; + case JUSTIFICATION_RIGHT: + alignment = Alignment.ALIGN_OPPOSITE; + break; + case JUSTIFICATION_CENTER: + alignment = Alignment.ALIGN_CENTER; + break; + default: + throw new IllegalArgumentException("Unexpected justification value: " + justification); + } + + float position; + float line; + if (relativePositioning) { + position = (float) horizontalAnchor / RELATIVE_CUE_SIZE; + line = (float) verticalAnchor / RELATIVE_CUE_SIZE; + } else { + position = (float) horizontalAnchor / HORIZONTAL_SIZE; + line = (float) verticalAnchor / VERTICAL_SIZE; + } + // Apply screen-edge padding to the line and position. + position = (position * 0.9f) + 0.05f; + line = (line * 0.9f) + 0.05f; + + // anchorId specifies where the anchor should be placed on the caption cue/window. The 9 + // possible configurations are as follows: + // 0-----1-----2 + // | | + // 3 4 5 + // | | + // 6-----7-----8 + @AnchorType int verticalAnchorType; + if (anchorId % 3 == 0) { + verticalAnchorType = Cue.ANCHOR_TYPE_START; + } else if (anchorId % 3 == 1) { + verticalAnchorType = Cue.ANCHOR_TYPE_MIDDLE; + } else { + verticalAnchorType = Cue.ANCHOR_TYPE_END; + } + // TODO: Add support for right-to-left languages (i.e. where start is on the right). + @AnchorType int horizontalAnchorType; + if (anchorId / 3 == 0) { + horizontalAnchorType = Cue.ANCHOR_TYPE_START; + } else if (anchorId / 3 == 1) { + horizontalAnchorType = Cue.ANCHOR_TYPE_MIDDLE; + } else { + horizontalAnchorType = Cue.ANCHOR_TYPE_END; + } + + boolean windowColorSet = (windowFillColor != COLOR_SOLID_BLACK); + + return new Cea708Cue(cueString, alignment, line, Cue.LINE_TYPE_FRACTION, verticalAnchorType, + position, horizontalAnchorType, Cue.DIMEN_UNSET, windowColorSet, windowFillColor, + priority); + } + + public static int getArgbColorFromCeaColor(int red, int green, int blue) { + return getArgbColorFromCeaColor(red, green, blue, 0); + } + + public static int getArgbColorFromCeaColor(int red, int green, int blue, int opacity) { + Assertions.checkIndex(red, 0, 4); + Assertions.checkIndex(green, 0, 4); + Assertions.checkIndex(blue, 0, 4); + Assertions.checkIndex(opacity, 0, 4); + + int alpha; + switch (opacity) { + case 0: + case 1: + // Note the value of '1' is actually FLASH, but we don't support that. + alpha = 255; + break; + case 2: + alpha = 127; + break; + case 3: + alpha = 0; + break; + default: + alpha = 255; + } + + // TODO: Add support for the Alternative Minimum Color List or the full 64 RGB combinations. + + // Return values based on the Minimum Color List + return Color.argb(alpha, + (red > 1 ? 255 : 0), + (green > 1 ? 255 : 0), + (blue > 1 ? 255 : 0)); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaDecoder.java new file mode 100644 index 0000000..077cb1c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaDecoder.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.cea; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoderException; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleOutputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.LinkedList; +import java.util.TreeSet; + +/** + * Base class for subtitle parsers for CEA captions. + */ +/* package */ abstract class CeaDecoder implements SubtitleDecoder { + + private static final int NUM_INPUT_BUFFERS = 10; + private static final int NUM_OUTPUT_BUFFERS = 2; + + private final LinkedList availableInputBuffers; + private final LinkedList availableOutputBuffers; + private final TreeSet queuedInputBuffers; + + private SubtitleInputBuffer dequeuedInputBuffer; + private long playbackPositionUs; + + public CeaDecoder() { + availableInputBuffers = new LinkedList<>(); + for (int i = 0; i < NUM_INPUT_BUFFERS; i++) { + availableInputBuffers.add(new SubtitleInputBuffer()); + } + availableOutputBuffers = new LinkedList<>(); + for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) { + availableOutputBuffers.add(new CeaOutputBuffer(this)); + } + queuedInputBuffers = new TreeSet<>(); + } + + @Override + public abstract String getName(); + + @Override + public void setPositionUs(long positionUs) { + playbackPositionUs = positionUs; + } + + @Override + public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException { + Assertions.checkState(dequeuedInputBuffer == null); + if (availableInputBuffers.isEmpty()) { + return null; + } + dequeuedInputBuffer = availableInputBuffers.pollFirst(); + return dequeuedInputBuffer; + } + + @Override + public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException { + Assertions.checkArgument(inputBuffer != null); + Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); + if (inputBuffer.isDecodeOnly()) { + // We can drop this buffer early (i.e. before it would be decoded) as the CEA formats allow + // for decoding to begin mid-stream. + releaseInputBuffer(inputBuffer); + } else { + queuedInputBuffers.add(inputBuffer); + } + dequeuedInputBuffer = null; + } + + @Override + public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException { + if (availableOutputBuffers.isEmpty()) { + return null; + } + + // iterate through all available input buffers whose timestamps are less than or equal + // to the current playback position; processing input buffers for future content should + // be deferred until they would be applicable + while (!queuedInputBuffers.isEmpty() + && queuedInputBuffers.first().timeUs <= playbackPositionUs) { + SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst(); + + // If the input buffer indicates we've reached the end of the stream, we can + // return immediately with an output buffer propagating that + if (inputBuffer.isEndOfStream()) { + SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); + outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + releaseInputBuffer(inputBuffer); + return outputBuffer; + } + + decode(inputBuffer); + + // check if we have any caption updates to report + if (isNewSubtitleDataAvailable()) { + // Even if the subtitle is decode-only; we need to generate it to consume the data so it + // isn't accidentally prepended to the next subtitle + Subtitle subtitle = createSubtitle(); + if (!inputBuffer.isDecodeOnly()) { + SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); + outputBuffer.setContent(inputBuffer.timeUs, subtitle, Format.OFFSET_SAMPLE_RELATIVE); + releaseInputBuffer(inputBuffer); + return outputBuffer; + } + } + + releaseInputBuffer(inputBuffer); + } + + return null; + } + + private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) { + inputBuffer.clear(); + availableInputBuffers.add(inputBuffer); + } + + protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) { + outputBuffer.clear(); + availableOutputBuffers.add(outputBuffer); + } + + @Override + public void flush() { + playbackPositionUs = 0; + while (!queuedInputBuffers.isEmpty()) { + releaseInputBuffer(queuedInputBuffers.pollFirst()); + } + if (dequeuedInputBuffer != null) { + releaseInputBuffer(dequeuedInputBuffer); + dequeuedInputBuffer = null; + } + } + + @Override + public void release() { + // Do nothing + } + + /** + * Returns whether there is data available to create a new {@link Subtitle}. + */ + protected abstract boolean isNewSubtitleDataAvailable(); + + /** + * Creates a {@link Subtitle} from the available data. + */ + protected abstract Subtitle createSubtitle(); + + /** + * Filters and processes the raw data, providing {@link Subtitle}s via {@link #createSubtitle()} + * when sufficient data has been processed. + */ + protected abstract void decode(SubtitleInputBuffer inputBuffer); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaOutputBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaOutputBuffer.java new file mode 100644 index 0000000..9140926 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaOutputBuffer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.cea; + +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleOutputBuffer; + +/** + * A {@link SubtitleOutputBuffer} for {@link CeaDecoder}s. + */ +public final class CeaOutputBuffer extends SubtitleOutputBuffer { + + private final CeaDecoder owner; + + /** + * @param owner The decoder that owns this buffer. + */ + public CeaOutputBuffer(CeaDecoder owner) { + super(); + this.owner = owner; + } + + @Override + public final void release() { + owner.releaseOutputBuffer(this); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaSubtitle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaSubtitle.java new file mode 100644 index 0000000..14cb3d3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaSubtitle.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.cea; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.Collections; +import java.util.List; + +/** + * A representation of a CEA subtitle. + */ +/* package */ final class CeaSubtitle implements Subtitle { + + private final List cues; + + /** + * @param cues The subtitle cues. + */ + public CeaSubtitle(List cues) { + this.cues = cues; + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + return timeUs < 0 ? 0 : C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return 1; + } + + @Override + public long getEventTime(int index) { + Assertions.checkArgument(index == 0); + return 0; + } + + @Override + public List getCues(long timeUs) { + return timeUs >= 0 ? cues : Collections.emptyList(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaUtil.java new file mode 100644 index 0000000..7d3f7be --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/cea/CeaUtil.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.cea; + +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.TrackOutput; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; + +/** + * Utility methods for handling CEA-608/708 messages. + */ +public final class CeaUtil { + + private static final String TAG = "CeaUtil"; + + private static final int PAYLOAD_TYPE_CC = 4; + private static final int COUNTRY_CODE = 0xB5; + private static final int PROVIDER_CODE = 0x31; + private static final int USER_ID = 0x47413934; // "GA94" + private static final int USER_DATA_TYPE_CODE = 0x3; + + /** + * Consumes the unescaped content of an SEI NAL unit, writing the content of any CEA-608 messages + * as samples to all of the provided outputs. + * + * @param presentationTimeUs The presentation time in microseconds for any samples. + * @param seiBuffer The unescaped SEI NAL unit data, excluding the NAL unit start code and type. + * @param outputs The outputs to which any samples should be written. + */ + public static void consume(long presentationTimeUs, ParsableByteArray seiBuffer, + TrackOutput[] outputs) { + while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { + int payloadType = readNon255TerminatedValue(seiBuffer); + int payloadSize = readNon255TerminatedValue(seiBuffer); + // Process the payload. + if (payloadSize == -1 || payloadSize > seiBuffer.bytesLeft()) { + // This might occur if we're trying to read an encrypted SEI NAL unit. + Log.w(TAG, "Skipping remainder of malformed SEI NAL unit."); + seiBuffer.setPosition(seiBuffer.limit()); + } else if (isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) { + // Ignore country_code (1) + provider_code (2) + user_identifier (4) + // + user_data_type_code (1). + seiBuffer.skipBytes(8); + // Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1). + int ccCount = seiBuffer.readUnsignedByte() & 0x1F; + // Ignore em_data (1) + seiBuffer.skipBytes(1); + // Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2) + // + cc_data_1 (8) + cc_data_2 (8). + int sampleLength = ccCount * 3; + int sampleStartPosition = seiBuffer.getPosition(); + for (TrackOutput output : outputs) { + seiBuffer.setPosition(sampleStartPosition); + output.sampleData(seiBuffer, sampleLength); + output.sampleMetadata(presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null); + } + // Ignore trailing information in SEI, if any. + seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3)); + } else { + seiBuffer.skipBytes(payloadSize); + } + } + } + + /** + * Reads a value from the provided buffer consisting of zero or more 0xFF bytes followed by a + * terminating byte not equal to 0xFF. The returned value is ((0xFF * N) + T), where N is the + * number of 0xFF bytes and T is the value of the terminating byte. + * + * @param buffer The buffer from which to read the value. + * @returns The read value, or -1 if the end of the buffer is reached before a value is read. + */ + private static int readNon255TerminatedValue(ParsableByteArray buffer) { + int b; + int value = 0; + do { + if (buffer.bytesLeft() == 0) { + return -1; + } + b = buffer.readUnsignedByte(); + value += b; + } while (b == 0xFF); + return value; + } + + /** + * Inspects an sei message to determine whether it contains CEA-608. + *

    + * The position of {@code payload} is left unchanged. + * + * @param payloadType The payload type of the message. + * @param payloadLength The length of the payload. + * @param payload A {@link ParsableByteArray} containing the payload. + * @return Whether the sei message contains CEA-608. + */ + private static boolean isSeiMessageCea608(int payloadType, int payloadLength, + ParsableByteArray payload) { + if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) { + return false; + } + int startPosition = payload.getPosition(); + int countryCode = payload.readUnsignedByte(); + int providerCode = payload.readUnsignedShort(); + int userIdentifier = payload.readInt(); + int userDataTypeCode = payload.readUnsignedByte(); + payload.setPosition(startPosition); + return countryCode == COUNTRY_CODE && providerCode == PROVIDER_CODE + && userIdentifier == USER_ID && userDataTypeCode == USER_DATA_TYPE_CODE; + } + + private CeaUtil() {} + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbDecoder.java new file mode 100644 index 0000000..bf54dc0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbDecoder.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.dvb; + +import com.tangxiaolv.telegramgallery.exoplayer2.text.SimpleSubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.List; + +/** + * A {@link SimpleSubtitleDecoder} for DVB Subtitles. + */ +public final class DvbDecoder extends SimpleSubtitleDecoder { + + private final DvbParser parser; + + /** + * @param initializationData The initialization data for the decoder. The initialization data + * must consist of a single byte array containing 5 bytes: flag_pes_stripped (1), + * composition_page (2), ancillary_page (2). + */ + public DvbDecoder(List initializationData) { + super("DvbDecoder"); + ParsableByteArray data = new ParsableByteArray(initializationData.get(0)); + int subtitleCompositionPage = data.readUnsignedShort(); + int subtitleAncillaryPage = data.readUnsignedShort(); + parser = new DvbParser(subtitleCompositionPage, subtitleAncillaryPage); + } + + @Override + protected DvbSubtitle decode(byte[] data, int length, boolean reset) { + if (reset) { + parser.reset(); + } + return new DvbSubtitle(parser.decode(data, length)); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbParser.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbParser.java new file mode 100644 index 0000000..8acfd1f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbParser.java @@ -0,0 +1,1025 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.dvb; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Region; +import android.util.Log; +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableBitArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Parses {@link Cue}s from a DVB subtitle bitstream. + */ +/* package */ final class DvbParser { + + private static final String TAG = "DvbParser"; + + // Segment types, as defined by ETSI EN 300 743 Table 2 + private static final int SEGMENT_TYPE_PAGE_COMPOSITION = 0x10; + private static final int SEGMENT_TYPE_REGION_COMPOSITION = 0x11; + private static final int SEGMENT_TYPE_CLUT_DEFINITION = 0x12; + private static final int SEGMENT_TYPE_OBJECT_DATA = 0x13; + private static final int SEGMENT_TYPE_DISPLAY_DEFINITION = 0x14; + + // Page states, as defined by ETSI EN 300 743 Table 3 + private static final int PAGE_STATE_NORMAL = 0; // Update. Only changed elements. + // private static final int PAGE_STATE_ACQUISITION = 1; // Refresh. All elements. + // private static final int PAGE_STATE_CHANGE = 2; // New. All elements. + + // Region depths, as defined by ETSI EN 300 743 Table 5 + // private static final int REGION_DEPTH_2_BIT = 1; + private static final int REGION_DEPTH_4_BIT = 2; + private static final int REGION_DEPTH_8_BIT = 3; + + // Object codings, as defined by ETSI EN 300 743 Table 8 + private static final int OBJECT_CODING_PIXELS = 0; + private static final int OBJECT_CODING_STRING = 1; + + // Pixel-data types, as defined by ETSI EN 300 743 Table 9 + private static final int DATA_TYPE_2BP_CODE_STRING = 0x10; + private static final int DATA_TYPE_4BP_CODE_STRING = 0x11; + private static final int DATA_TYPE_8BP_CODE_STRING = 0x12; + private static final int DATA_TYPE_24_TABLE_DATA = 0x20; + private static final int DATA_TYPE_28_TABLE_DATA = 0x21; + private static final int DATA_TYPE_48_TABLE_DATA = 0x22; + private static final int DATA_TYPE_END_LINE = 0xF0; + + // Clut mapping tables, as defined by ETSI EN 300 743 10.4, 10.5, 10.6 + private static final byte[] defaultMap2To4 = { + (byte) 0x00, (byte) 0x07, (byte) 0x08, (byte) 0x0F}; + private static final byte[] defaultMap2To8 = { + (byte) 0x00, (byte) 0x77, (byte) 0x88, (byte) 0xFF}; + private static final byte[] defaultMap4To8 = { + (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, + (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77, + (byte) 0x88, (byte) 0x99, (byte) 0xAA, (byte) 0xBB, + (byte) 0xCC, (byte) 0xDD, (byte) 0xEE, (byte) 0xFF}; + + private final Paint defaultPaint; + private final Paint fillRegionPaint; + private final Canvas canvas; + private final DisplayDefinition defaultDisplayDefinition; + private final ClutDefinition defaultClutDefinition; + private final SubtitleService subtitleService; + + private Bitmap bitmap; + + /** + * Construct an instance for the given subtitle and ancillary page ids. + * + * @param subtitlePageId The id of the subtitle page carrying the subtitle to be parsed. + * @param ancillaryPageId The id of the ancillary page containing additional data. + */ + public DvbParser(int subtitlePageId, int ancillaryPageId) { + defaultPaint = new Paint(); + defaultPaint.setStyle(Paint.Style.FILL_AND_STROKE); + defaultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + defaultPaint.setPathEffect(null); + fillRegionPaint = new Paint(); + fillRegionPaint.setStyle(Paint.Style.FILL); + fillRegionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); + fillRegionPaint.setPathEffect(null); + canvas = new Canvas(); + defaultDisplayDefinition = new DisplayDefinition(719, 575, 0, 719, 0, 575); + defaultClutDefinition = new ClutDefinition(0, generateDefault2BitClutEntries(), + generateDefault4BitClutEntries(), generateDefault8BitClutEntries()); + subtitleService = new SubtitleService(subtitlePageId, ancillaryPageId); + } + + /** + * Resets the parser. + */ + public void reset() { + subtitleService.reset(); + } + + /** + * Decodes a subtitling packet, returning a list of parsed {@link Cue}s. + * + * @param data The subtitling packet data to decode. + * @param limit The limit in {@code data} at which to stop decoding. + * @return The parsed {@link Cue}s. + */ + public List decode(byte[] data, int limit) { + // Parse the input data. + ParsableBitArray dataBitArray = new ParsableBitArray(data, limit); + while (dataBitArray.bitsLeft() >= 48 // sync_byte (8) + segment header (40) + && dataBitArray.readBits(8) == 0x0F) { + parseSubtitlingSegment(dataBitArray, subtitleService); + } + + if (subtitleService.pageComposition == null) { + return Collections.emptyList(); + } + + // Update the canvas bitmap if necessary. + DisplayDefinition displayDefinition = subtitleService.displayDefinition != null + ? subtitleService.displayDefinition : defaultDisplayDefinition; + if (bitmap == null || displayDefinition.width + 1 != bitmap.getWidth() + || displayDefinition.height + 1 != bitmap.getHeight()) { + bitmap = Bitmap.createBitmap(displayDefinition.width + 1, displayDefinition.height + 1, + Bitmap.Config.ARGB_8888); + canvas.setBitmap(bitmap); + } + + // Build the cues. + List cues = new ArrayList<>(); + SparseArray pageRegions = subtitleService.pageComposition.regions; + for (int i = 0; i < pageRegions.size(); i++) { + PageRegion pageRegion = pageRegions.valueAt(i); + int regionId = pageRegions.keyAt(i); + RegionComposition regionComposition = subtitleService.regions.get(regionId); + + // Clip drawing to the current region and display definition window. + int baseHorizontalAddress = pageRegion.horizontalAddress + + displayDefinition.horizontalPositionMinimum; + int baseVerticalAddress = pageRegion.verticalAddress + + displayDefinition.verticalPositionMinimum; + int clipRight = Math.min(baseHorizontalAddress + regionComposition.width, + displayDefinition.horizontalPositionMaximum); + int clipBottom = Math.min(baseVerticalAddress + regionComposition.height, + displayDefinition.verticalPositionMaximum); + canvas.clipRect(baseHorizontalAddress, baseVerticalAddress, clipRight, clipBottom, + Region.Op.REPLACE); + + ClutDefinition clutDefinition = subtitleService.cluts.get(regionComposition.clutId); + if (clutDefinition == null) { + clutDefinition = subtitleService.ancillaryCluts.get(regionComposition.clutId); + if (clutDefinition == null) { + clutDefinition = defaultClutDefinition; + } + } + + SparseArray regionObjects = regionComposition.regionObjects; + for (int j = 0; j < regionObjects.size(); j++) { + int objectId = regionObjects.keyAt(j); + RegionObject regionObject = regionObjects.valueAt(j); + ObjectData objectData = subtitleService.objects.get(objectId); + if (objectData == null) { + objectData = subtitleService.ancillaryObjects.get(objectId); + } + if (objectData != null) { + Paint paint = objectData.nonModifyingColorFlag ? null : defaultPaint; + paintPixelDataSubBlocks(objectData, clutDefinition, regionComposition.depth, + baseHorizontalAddress + regionObject.horizontalPosition, + baseVerticalAddress + regionObject.verticalPosition, paint, canvas); + } + } + + if (regionComposition.fillFlag) { + int color; + if (regionComposition.depth == REGION_DEPTH_8_BIT) { + color = clutDefinition.clutEntries8Bit[regionComposition.pixelCode8Bit]; + } else if (regionComposition.depth == REGION_DEPTH_4_BIT) { + color = clutDefinition.clutEntries4Bit[regionComposition.pixelCode4Bit]; + } else { + color = clutDefinition.clutEntries2Bit[regionComposition.pixelCode2Bit]; + } + fillRegionPaint.setColor(color); + canvas.drawRect(baseHorizontalAddress, baseVerticalAddress, + baseHorizontalAddress + regionComposition.width, + baseVerticalAddress + regionComposition.height, + fillRegionPaint); + } + + Bitmap cueBitmap = Bitmap.createBitmap(bitmap, baseHorizontalAddress, baseVerticalAddress, + regionComposition.width, regionComposition.height); + cues.add(new Cue(cueBitmap, (float) baseHorizontalAddress / displayDefinition.width, + Cue.ANCHOR_TYPE_START, (float) baseVerticalAddress / displayDefinition.height, + Cue.ANCHOR_TYPE_START, (float) regionComposition.width / displayDefinition.width, + (float) regionComposition.height / displayDefinition.height)); + + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + } + + return cues; + } + + // Static parsing. + + /** + * Parses a subtitling segment, as defined by ETSI EN 300 743 7.2 + *

    + * The {@link SubtitleService} is updated with the parsed segment data. + */ + private static void parseSubtitlingSegment(ParsableBitArray data, SubtitleService service) { + int segmentType = data.readBits(8); + int pageId = data.readBits(16); + int dataFieldLength = data.readBits(16); + int dataFieldLimit = data.getBytePosition() + dataFieldLength; + + if ((dataFieldLength * 8) > data.bitsLeft()) { + Log.w(TAG, "Data field length exceeds limit"); + // Skip to the very end. + data.skipBits(data.bitsLeft()); + return; + } + + switch (segmentType) { + case SEGMENT_TYPE_DISPLAY_DEFINITION: + if (pageId == service.subtitlePageId) { + service.displayDefinition = parseDisplayDefinition(data); + } + break; + case SEGMENT_TYPE_PAGE_COMPOSITION: + if (pageId == service.subtitlePageId) { + PageComposition current = service.pageComposition; + PageComposition pageComposition = parsePageComposition(data, dataFieldLength); + if (pageComposition.state != PAGE_STATE_NORMAL) { + service.pageComposition = pageComposition; + service.regions.clear(); + service.cluts.clear(); + service.objects.clear(); + } else if (current != null && current.version != pageComposition.version) { + service.pageComposition = pageComposition; + } + } + break; + case SEGMENT_TYPE_REGION_COMPOSITION: + PageComposition pageComposition = service.pageComposition; + if (pageId == service.subtitlePageId && pageComposition != null) { + RegionComposition regionComposition = parseRegionComposition(data, dataFieldLength); + if (pageComposition.state == PAGE_STATE_NORMAL) { + regionComposition.mergeFrom(service.regions.get(regionComposition.id)); + } + service.regions.put(regionComposition.id, regionComposition); + } + break; + case SEGMENT_TYPE_CLUT_DEFINITION: + if (pageId == service.subtitlePageId) { + ClutDefinition clutDefinition = parseClutDefinition(data, dataFieldLength); + service.cluts.put(clutDefinition.id, clutDefinition); + } else if (pageId == service.ancillaryPageId) { + ClutDefinition clutDefinition = parseClutDefinition(data, dataFieldLength); + service.ancillaryCluts.put(clutDefinition.id, clutDefinition); + } + break; + case SEGMENT_TYPE_OBJECT_DATA: + if (pageId == service.subtitlePageId) { + ObjectData objectData = parseObjectData(data); + service.objects.put(objectData.id, objectData); + } else if (pageId == service.ancillaryPageId) { + ObjectData objectData = parseObjectData(data); + service.ancillaryObjects.put(objectData.id, objectData); + } + break; + default: + // Do nothing. + break; + } + + // Skip to the next segment. + data.skipBytes(dataFieldLimit - data.getBytePosition()); + } + + /** + * Parses a display definition segment, as defined by ETSI EN 300 743 7.2.1. + */ + private static DisplayDefinition parseDisplayDefinition(ParsableBitArray data) { + data.skipBits(4); // dds_version_number (4). + boolean displayWindowFlag = data.readBit(); + data.skipBits(3); // Skip reserved. + int width = data.readBits(16); + int height = data.readBits(16); + + int horizontalPositionMinimum; + int horizontalPositionMaximum; + int verticalPositionMinimum; + int verticalPositionMaximum; + if (displayWindowFlag) { + horizontalPositionMinimum = data.readBits(16); + horizontalPositionMaximum = data.readBits(16); + verticalPositionMinimum = data.readBits(16); + verticalPositionMaximum = data.readBits(16); + } else { + horizontalPositionMinimum = 0; + horizontalPositionMaximum = width; + verticalPositionMinimum = 0; + verticalPositionMaximum = height; + } + + return new DisplayDefinition(width, height, horizontalPositionMinimum, + horizontalPositionMaximum, verticalPositionMinimum, verticalPositionMaximum); + } + + /** + * Parses a page composition segment, as defined by ETSI EN 300 743 7.2.2. + */ + private static PageComposition parsePageComposition(ParsableBitArray data, int length) { + int timeoutSecs = data.readBits(8); + int version = data.readBits(4); + int state = data.readBits(2); + data.skipBits(2); + int remainingLength = length - 2; + + SparseArray regions = new SparseArray<>(); + while (remainingLength > 0) { + int regionId = data.readBits(8); + data.skipBits(8); // Skip reserved. + int regionHorizontalAddress = data.readBits(16); + int regionVerticalAddress = data.readBits(16); + remainingLength -= 6; + regions.put(regionId, new PageRegion(regionHorizontalAddress, regionVerticalAddress)); + } + + return new PageComposition(timeoutSecs, version, state, regions); + } + + /** + * Parses a region composition segment, as defined by ETSI EN 300 743 7.2.3. + */ + private static RegionComposition parseRegionComposition(ParsableBitArray data, int length) { + int id = data.readBits(8); + data.skipBits(4); // Skip region_version_number + boolean fillFlag = data.readBit(); + data.skipBits(3); // Skip reserved. + int width = data.readBits(16); + int height = data.readBits(16); + int levelOfCompatibility = data.readBits(3); + int depth = data.readBits(3); + data.skipBits(2); // Skip reserved. + int clutId = data.readBits(8); + int pixelCode8Bit = data.readBits(8); + int pixelCode4Bit = data.readBits(4); + int pixelCode2Bit = data.readBits(2); + data.skipBits(2); // Skip reserved + int remainingLength = length - 10; + + SparseArray regionObjects = new SparseArray<>(); + while (remainingLength > 0) { + int objectId = data.readBits(16); + int objectType = data.readBits(2); + int objectProvider = data.readBits(2); + int objectHorizontalPosition = data.readBits(12); + data.skipBits(4); // Skip reserved. + int objectVerticalPosition = data.readBits(12); + remainingLength -= 6; + + int foregroundPixelCode = 0; + int backgroundPixelCode = 0; + if (objectType == 0x01 || objectType == 0x02) { // Only seems to affect to char subtitles. + foregroundPixelCode = data.readBits(8); + backgroundPixelCode = data.readBits(8); + remainingLength -= 2; + } + + regionObjects.put(objectId, new RegionObject(objectType, objectProvider, + objectHorizontalPosition, objectVerticalPosition, foregroundPixelCode, + backgroundPixelCode)); + } + + return new RegionComposition(id, fillFlag, width, height, levelOfCompatibility, depth, clutId, + pixelCode8Bit, pixelCode4Bit, pixelCode2Bit, regionObjects); + } + + /** + * Parses a CLUT definition segment, as defined by ETSI EN 300 743 7.2.4. + */ + private static ClutDefinition parseClutDefinition(ParsableBitArray data, int length) { + int clutId = data.readBits(8); + data.skipBits(8); // Skip clut_version_number (4), reserved (4) + int remainingLength = length - 2; + + int[] clutEntries2Bit = generateDefault2BitClutEntries(); + int[] clutEntries4Bit = generateDefault4BitClutEntries(); + int[] clutEntries8Bit = generateDefault8BitClutEntries(); + + while (remainingLength > 0) { + int entryId = data.readBits(8); + int entryFlags = data.readBits(8); + remainingLength -= 2; + + int[] clutEntries; + if ((entryFlags & 0x80) != 0) { + clutEntries = clutEntries2Bit; + } else if ((entryFlags & 0x40) != 0) { + clutEntries = clutEntries4Bit; + } else { + clutEntries = clutEntries8Bit; + } + + int y; + int cr; + int cb; + int t; + if ((entryFlags & 0x01) != 0) { + y = data.readBits(8); + cr = data.readBits(8); + cb = data.readBits(8); + t = data.readBits(8); + remainingLength -= 4; + } else { + y = data.readBits(6) << 2; + cr = data.readBits(4) << 4; + cb = data.readBits(4) << 4; + t = data.readBits(2) << 6; + remainingLength -= 2; + } + + if (y == 0x00) { + cr = 0x00; + cb = 0x00; + t = 0xFF; + } + + int a = (byte) (0xFF - (t & 0xFF)); + int r = (int) (y + (1.40200 * (cr - 128))); + int g = (int) (y - (0.34414 * (cb - 128)) - (0.71414 * (cr - 128))); + int b = (int) (y + (1.77200 * (cb - 128))); + clutEntries[entryId] = getColor(a, Util.constrainValue(r, 0, 255), + Util.constrainValue(g, 0, 255), Util.constrainValue(b, 0, 255)); + } + + return new ClutDefinition(clutId, clutEntries2Bit, clutEntries4Bit, clutEntries8Bit); + } + + /** + * Parses an object data segment, as defined by ETSI EN 300 743 7.2.5. + * + * @return The parsed object data. + */ + private static ObjectData parseObjectData(ParsableBitArray data) { + int objectId = data.readBits(16); + data.skipBits(4); // Skip object_version_number + int objectCodingMethod = data.readBits(2); + boolean nonModifyingColorFlag = data.readBit(); + data.skipBits(1); // Skip reserved. + + byte[] topFieldData = null; + byte[] bottomFieldData = null; + + if (objectCodingMethod == OBJECT_CODING_STRING) { + int numberOfCodes = data.readBits(8); + // TODO: Parse and use character_codes. + data.skipBits(numberOfCodes * 16); // Skip character_codes. + } else if (objectCodingMethod == OBJECT_CODING_PIXELS) { + int topFieldDataLength = data.readBits(16); + int bottomFieldDataLength = data.readBits(16); + if (topFieldDataLength > 0) { + topFieldData = new byte[topFieldDataLength]; + data.readBytes(topFieldData, 0, topFieldDataLength); + } + if (bottomFieldDataLength > 0) { + bottomFieldData = new byte[bottomFieldDataLength]; + data.readBytes(bottomFieldData, 0, bottomFieldDataLength); + } else { + bottomFieldData = topFieldData; + } + } + + return new ObjectData(objectId, nonModifyingColorFlag, topFieldData, bottomFieldData); + } + + private static int[] generateDefault2BitClutEntries() { + int[] entries = new int[4]; + entries[0] = 0x00000000; + entries[1] = 0xFFFFFFFF; + entries[2] = 0xFF000000; + entries[3] = 0xFF7F7F7F; + return entries; + } + + private static int[] generateDefault4BitClutEntries() { + int[] entries = new int[16]; + entries[0] = 0x00000000; + for (int i = 1; i < entries.length; i++) { + if (i < 8) { + entries[i] = getColor( + 0xFF, + ((i & 0x01) != 0 ? 0xFF : 0x00), + ((i & 0x02) != 0 ? 0xFF : 0x00), + ((i & 0x04) != 0 ? 0xFF : 0x00)); + } else { + entries[i] = getColor( + 0xFF, + ((i & 0x01) != 0 ? 0x7F : 0x00), + ((i & 0x02) != 0 ? 0x7F : 0x00), + ((i & 0x04) != 0 ? 0x7F : 0x00)); + } + } + return entries; + } + + private static int[] generateDefault8BitClutEntries() { + int[] entries = new int[256]; + entries[0] = 0x00000000; + for (int i = 0; i < entries.length; i++) { + if (i < 8) { + entries[i] = getColor( + 0x3F, + ((i & 0x01) != 0 ? 0xFF : 0x00), + ((i & 0x02) != 0 ? 0xFF : 0x00), + ((i & 0x04) != 0 ? 0xFF : 0x00)); + } else { + switch (i & 0x88) { + case 0x00: + entries[i] = getColor( + 0xFF, + (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), + (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), + (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00))); + break; + case 0x08: + entries[i] = getColor( + 0x7F, + (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), + (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), + (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00))); + break; + case 0x80: + entries[i] = getColor( + 0xFF, + (127 + ((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), + (127 + ((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), + (127 + ((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00))); + break; + case 0x88: + entries[i] = getColor( + 0xFF, + (((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), + (((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), + (((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00))); + break; + } + } + } + return entries; + } + + private static int getColor(int a, int r, int g, int b) { + return (a << 24) | (r << 16) | (g << 8) | b; + } + + // Static drawing. + + /** + * Draws a pixel data sub-block, as defined by ETSI EN 300 743 7.2.5.1, into a canvas. + */ + private static void paintPixelDataSubBlocks(ObjectData objectData, ClutDefinition clutDefinition, + int regionDepth, int horizontalAddress, int verticalAddress, Paint paint, Canvas canvas) { + int[] clutEntries; + if (regionDepth == REGION_DEPTH_8_BIT) { + clutEntries = clutDefinition.clutEntries8Bit; + } else if (regionDepth == REGION_DEPTH_4_BIT) { + clutEntries = clutDefinition.clutEntries4Bit; + } else { + clutEntries = clutDefinition.clutEntries2Bit; + } + paintPixelDataSubBlock(objectData.topFieldData, clutEntries, regionDepth, horizontalAddress, + verticalAddress, paint, canvas); + paintPixelDataSubBlock(objectData.bottomFieldData, clutEntries, regionDepth, horizontalAddress, + verticalAddress + 1, paint, canvas); + } + + /** + * Draws a pixel data sub-block, as defined by ETSI EN 300 743 7.2.5.1, into a canvas. + */ + private static void paintPixelDataSubBlock(byte[] pixelData, int[] clutEntries, int regionDepth, + int horizontalAddress, int verticalAddress, Paint paint, Canvas canvas) { + ParsableBitArray data = new ParsableBitArray(pixelData); + int column = horizontalAddress; + int line = verticalAddress; + byte[] clutMapTable2To4 = null; + byte[] clutMapTable2To8 = null; + byte[] clutMapTable4To8 = null; + + while (data.bitsLeft() != 0) { + int dataType = data.readBits(8); + switch (dataType) { + case DATA_TYPE_2BP_CODE_STRING: + byte[] clutMapTable2ToX; + if (regionDepth == REGION_DEPTH_8_BIT) { + clutMapTable2ToX = clutMapTable2To8 == null ? defaultMap2To8 : clutMapTable2To8; + } else if (regionDepth == REGION_DEPTH_4_BIT) { + clutMapTable2ToX = clutMapTable2To4 == null ? defaultMap2To4 : clutMapTable2To4; + } else { + clutMapTable2ToX = null; + } + column = paint2BitPixelCodeString(data, clutEntries, clutMapTable2ToX, column, line, + paint, canvas); + data.byteAlign(); + break; + case DATA_TYPE_4BP_CODE_STRING: + byte[] clutMapTable4ToX; + if (regionDepth == REGION_DEPTH_8_BIT) { + clutMapTable4ToX = clutMapTable4To8 == null ? defaultMap4To8 : clutMapTable4To8; + } else { + clutMapTable4ToX = null; + } + column = paint4BitPixelCodeString(data, clutEntries, clutMapTable4ToX, column, line, + paint, canvas); + data.byteAlign(); + break; + case DATA_TYPE_8BP_CODE_STRING: + column = paint8BitPixelCodeString(data, clutEntries, null, column, line, paint, canvas); + break; + case DATA_TYPE_24_TABLE_DATA: + clutMapTable2To4 = buildClutMapTable(4, 4, data); + break; + case DATA_TYPE_28_TABLE_DATA: + clutMapTable2To8 = buildClutMapTable(4, 8, data); + break; + case DATA_TYPE_48_TABLE_DATA: + clutMapTable2To8 = buildClutMapTable(16, 8, data); + break; + case DATA_TYPE_END_LINE: + column = horizontalAddress; + line += 2; + break; + default: + // Do nothing. + break; + } + } + } + + /** + * Paint a 2-bit/pixel code string, as defined by ETSI EN 300 743 7.2.5.2, to a canvas. + */ + private static int paint2BitPixelCodeString(ParsableBitArray data, int[] clutEntries, + byte[] clutMapTable, int column, int line, Paint paint, Canvas canvas) { + boolean endOfPixelCodeString = false; + do { + int runLength = 0; + int clutIndex = 0; + int peek = data.readBits(2); + if (!data.readBit()) { + runLength = 1; + clutIndex = peek; + } else if (data.readBit()) { + runLength = 3 + data.readBits(3); + clutIndex = data.readBits(2); + } else if (!data.readBit()) { + switch (data.readBits(2)) { + case 0x00: + endOfPixelCodeString = true; + break; + case 0x01: + runLength = 2; + break; + case 0x02: + runLength = 12 + data.readBits(4); + clutIndex = data.readBits(2); + break; + case 0x03: + runLength = 29 + data.readBits(8); + clutIndex = data.readBits(2); + break; + } + } + + if (runLength != 0 && paint != null) { + paint.setColor(clutEntries[clutMapTable != null ? clutMapTable[clutIndex] : clutIndex]); + canvas.drawRect(column, line, column + runLength, line + 1, paint); + } + + column += runLength; + } while (!endOfPixelCodeString); + + return column; + } + + /** + * Paint a 4-bit/pixel code string, as defined by ETSI EN 300 743 7.2.5.2, to a canvas. + */ + private static int paint4BitPixelCodeString(ParsableBitArray data, int[] clutEntries, + byte[] clutMapTable, int column, int line, Paint paint, Canvas canvas) { + boolean endOfPixelCodeString = false; + do { + int runLength = 0; + int clutIndex = 0; + int peek = data.readBits(4); + if (peek != 0x00) { + runLength = 1; + clutIndex = peek; + } else if (!data.readBit()) { + peek = data.readBits(3); + if (peek != 0x00) { + runLength = 2 + peek; + clutIndex = 0x00; + } else { + endOfPixelCodeString = true; + } + } else if (!data.readBit()) { + runLength = 4 + data.readBits(2); + clutIndex = data.readBits(4); + } else { + switch (data.readBits(2)) { + case 0x00: + runLength = 1; + break; + case 0x01: + runLength = 2; + break; + case 0x02: + runLength = 9 + data.readBits(4); + clutIndex = data.readBits(4); + break; + case 0x03: + runLength = 25 + data.readBits(8); + clutIndex = data.readBits(4); + break; + } + } + + if (runLength != 0 && paint != null) { + paint.setColor(clutEntries[clutMapTable != null ? clutMapTable[clutIndex] : clutIndex]); + canvas.drawRect(column, line, column + runLength, line + 1, paint); + } + + column += runLength; + } while (!endOfPixelCodeString); + + return column; + } + + /** + * Paint an 8-bit/pixel code string, as defined by ETSI EN 300 743 7.2.5.2, to a canvas. + */ + private static int paint8BitPixelCodeString(ParsableBitArray data, int[] clutEntries, + byte[] clutMapTable, int column, int line, Paint paint, Canvas canvas) { + boolean endOfPixelCodeString = false; + do { + int runLength = 0; + int clutIndex = 0; + int peek = data.readBits(8); + if (peek != 0x00) { + runLength = 1; + clutIndex = peek; + } else { + if (!data.readBit()) { + peek = data.readBits(7); + if (peek != 0x00) { + runLength = peek; + clutIndex = 0x00; + } else { + endOfPixelCodeString = true; + } + } else { + runLength = data.readBits(7); + clutIndex = data.readBits(8); + } + } + + if (runLength != 0 && paint != null) { + paint.setColor(clutEntries[clutMapTable != null ? clutMapTable[clutIndex] : clutIndex]); + canvas.drawRect(column, line, column + runLength, line + 1, paint); + } + column += runLength; + } while (!endOfPixelCodeString); + + return column; + } + + private static byte[] buildClutMapTable(int length, int bitsPerEntry, ParsableBitArray data) { + byte[] clutMapTable = new byte[length]; + for (int i = 0; i < length; i++) { + clutMapTable[i] = (byte) data.readBits(bitsPerEntry); + } + return clutMapTable; + } + + // Private inner classes. + + /** + * The subtitle service definition. + */ + private static final class SubtitleService { + + public final int subtitlePageId; + public final int ancillaryPageId; + + public final SparseArray regions = new SparseArray<>(); + public final SparseArray cluts = new SparseArray<>(); + public final SparseArray objects = new SparseArray<>(); + public final SparseArray ancillaryCluts = new SparseArray<>(); + public final SparseArray ancillaryObjects = new SparseArray<>(); + + public DisplayDefinition displayDefinition; + public PageComposition pageComposition; + + public SubtitleService(int subtitlePageId, int ancillaryPageId) { + this.subtitlePageId = subtitlePageId; + this.ancillaryPageId = ancillaryPageId; + } + + public void reset() { + regions.clear(); + cluts.clear(); + objects.clear(); + ancillaryCluts.clear(); + ancillaryObjects.clear(); + displayDefinition = null; + pageComposition = null; + } + + } + + /** + * Contains the geometry and active area of the subtitle service. + *

    + * See ETSI EN 300 743 7.2.1 + */ + private static final class DisplayDefinition { + + public final int width; + public final int height; + + public final int horizontalPositionMinimum; + public final int horizontalPositionMaximum; + public final int verticalPositionMinimum; + public final int verticalPositionMaximum; + + public DisplayDefinition(int width, int height, int horizontalPositionMinimum, + int horizontalPositionMaximum, int verticalPositionMinimum, int verticalPositionMaximum) { + this.width = width; + this.height = height; + this.horizontalPositionMinimum = horizontalPositionMinimum; + this.horizontalPositionMaximum = horizontalPositionMaximum; + this.verticalPositionMinimum = verticalPositionMinimum; + this.verticalPositionMaximum = verticalPositionMaximum; + } + + } + + /** + * The page is the definition and arrangement of regions in the screen. + *

    + * See ETSI EN 300 743 7.2.2 + */ + private static final class PageComposition { + + public final int timeOutSecs; // TODO: Use this or remove it. + public final int version; + public final int state; + public final SparseArray regions; + + public PageComposition(int timeoutSecs, int version, int state, + SparseArray regions) { + this.timeOutSecs = timeoutSecs; + this.version = version; + this.state = state; + this.regions = regions; + } + + } + + /** + * A region within a {@link PageComposition}. + *

    + * See ETSI EN 300 743 7.2.2 + */ + private static final class PageRegion { + + public final int horizontalAddress; + public final int verticalAddress; + + public PageRegion(int horizontalAddress, int verticalAddress) { + this.horizontalAddress = horizontalAddress; + this.verticalAddress = verticalAddress; + } + + } + + /** + * An area of the page composed of a list of objects and a CLUT. + *

    + * See ETSI EN 300 743 7.2.3 + */ + private static final class RegionComposition { + + public final int id; + public final boolean fillFlag; + public final int width; + public final int height; + public final int levelOfCompatibility; // TODO: Use this or remove it. + public final int depth; + public final int clutId; + public final int pixelCode8Bit; + public final int pixelCode4Bit; + public final int pixelCode2Bit; + public final SparseArray regionObjects; + + public RegionComposition(int id, boolean fillFlag, int width, int height, + int levelOfCompatibility, int depth, int clutId, int pixelCode8Bit, int pixelCode4Bit, + int pixelCode2Bit, SparseArray regionObjects) { + this.id = id; + this.fillFlag = fillFlag; + this.width = width; + this.height = height; + this.levelOfCompatibility = levelOfCompatibility; + this.depth = depth; + this.clutId = clutId; + this.pixelCode8Bit = pixelCode8Bit; + this.pixelCode4Bit = pixelCode4Bit; + this.pixelCode2Bit = pixelCode2Bit; + this.regionObjects = regionObjects; + } + + public void mergeFrom(RegionComposition otherRegionComposition) { + if (otherRegionComposition == null) { + return; + } + SparseArray otherRegionObjects = otherRegionComposition.regionObjects; + for (int i = 0; i < otherRegionObjects.size(); i++) { + regionObjects.put(otherRegionObjects.keyAt(i), otherRegionObjects.valueAt(i)); + } + } + + } + + /** + * An object within a {@link RegionComposition}. + *

    + * See ETSI EN 300 743 7.2.3 + */ + private static final class RegionObject { + + public final int type; // TODO: Use this or remove it. + public final int provider; // TODO: Use this or remove it. + public final int horizontalPosition; + public final int verticalPosition; + public final int foregroundPixelCode; // TODO: Use this or remove it. + public final int backgroundPixelCode; // TODO: Use this or remove it. + + public RegionObject(int type, int provider, int horizontalPosition, + int verticalPosition, int foregroundPixelCode, int backgroundPixelCode) { + this.type = type; + this.provider = provider; + this.horizontalPosition = horizontalPosition; + this.verticalPosition = verticalPosition; + this.foregroundPixelCode = foregroundPixelCode; + this.backgroundPixelCode = backgroundPixelCode; + } + + } + + /** + * CLUT family definition containing the color tables for the three bit depths defined + *

    + * See ETSI EN 300 743 7.2.4 + */ + private static final class ClutDefinition { + + public final int id; + public final int[] clutEntries2Bit; + public final int[] clutEntries4Bit; + public final int[] clutEntries8Bit; + + public ClutDefinition(int id, int[] clutEntries2Bit, int[] clutEntries4Bit, + int[] clutEntries8bit) { + this.id = id; + this.clutEntries2Bit = clutEntries2Bit; + this.clutEntries4Bit = clutEntries4Bit; + this.clutEntries8Bit = clutEntries8bit; + } + + } + + /** + * The textual or graphical representation of an object. + *

    + * See ETSI EN 300 743 7.2.5 + */ + private static final class ObjectData { + + public final int id; + public final boolean nonModifyingColorFlag; + public final byte[] topFieldData; + public final byte[] bottomFieldData; + + public ObjectData(int id, boolean nonModifyingColorFlag, byte[] topFieldData, + byte[] bottomFieldData) { + this.id = id; + this.nonModifyingColorFlag = nonModifyingColorFlag; + this.topFieldData = topFieldData; + this.bottomFieldData = bottomFieldData; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbSubtitle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbSubtitle.java new file mode 100644 index 0000000..5fe9390 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/dvb/DvbSubtitle.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.dvb; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import java.util.List; + +/** + * A representation of a DVB subtitle. + */ +/* package */ final class DvbSubtitle implements Subtitle { + + private final List cues; + + public DvbSubtitle(List cues) { + this.cues = cues; + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + return C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return 1; + } + + @Override + public long getEventTime(int index) { + return 0; + } + + @Override + public List getCues(long timeUs) { + return cues; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/subrip/SubripDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/subrip/SubripDecoder.java new file mode 100644 index 0000000..98d7c07 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/subrip/SubripDecoder.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.subrip; + +import android.text.Html; +import android.text.Spanned; +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SimpleSubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.util.LongArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link SimpleSubtitleDecoder} for SubRip. + */ +public final class SubripDecoder extends SimpleSubtitleDecoder { + + private static final String TAG = "SubripDecoder"; + + private static final String SUBRIP_TIMECODE = "(?:(\\d+):)?(\\d+):(\\d+),(\\d+)"; + private static final Pattern SUBRIP_TIMING_LINE = + Pattern.compile("\\s*(" + SUBRIP_TIMECODE + ")\\s*-->\\s*(" + SUBRIP_TIMECODE + ")?\\s*"); + + private final StringBuilder textBuilder; + + public SubripDecoder() { + super("SubripDecoder"); + textBuilder = new StringBuilder(); + } + + @Override + protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { + ArrayList cues = new ArrayList<>(); + LongArray cueTimesUs = new LongArray(); + ParsableByteArray subripData = new ParsableByteArray(bytes, length); + String currentLine; + + while ((currentLine = subripData.readLine()) != null) { + if (currentLine.length() == 0) { + // Skip blank lines. + continue; + } + + // Parse the index line as a sanity check. + try { + Integer.parseInt(currentLine); + } catch (NumberFormatException e) { + continue; + } + + // Read and parse the timing line. + boolean haveEndTimecode = false; + currentLine = subripData.readLine(); + Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); + if (matcher.matches()) { + cueTimesUs.add(parseTimecode(matcher, 1)); + if (!TextUtils.isEmpty(matcher.group(6))) { + haveEndTimecode = true; + cueTimesUs.add(parseTimecode(matcher, 6)); + } + } else { + continue; + } + + // Read and parse the text. + textBuilder.setLength(0); + while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { + if (textBuilder.length() > 0) { + textBuilder.append("
    "); + } + textBuilder.append(currentLine.trim()); + } + + Spanned text = Html.fromHtml(textBuilder.toString()); + cues.add(new Cue(text)); + if (haveEndTimecode) { + cues.add(null); + } + } + + Cue[] cuesArray = new Cue[cues.size()]; + cues.toArray(cuesArray); + long[] cueTimesUsArray = cueTimesUs.toArray(); + return new SubripSubtitle(cuesArray, cueTimesUsArray); + } + + private static long parseTimecode(Matcher matcher, int groupOffset) { + long timestampMs = Long.parseLong(matcher.group(groupOffset + 1)) * 60 * 60 * 1000; + timestampMs += Long.parseLong(matcher.group(groupOffset + 2)) * 60 * 1000; + timestampMs += Long.parseLong(matcher.group(groupOffset + 3)) * 1000; + timestampMs += Long.parseLong(matcher.group(groupOffset + 4)); + return timestampMs * 1000; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/subrip/SubripSubtitle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/subrip/SubripSubtitle.java new file mode 100644 index 0000000..c652576 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/subrip/SubripSubtitle.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.subrip; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Collections; +import java.util.List; + +/** + * A representation of a SubRip subtitle. + */ +/* package */ final class SubripSubtitle implements Subtitle { + + private final Cue[] cues; + private final long[] cueTimesUs; + + /** + * @param cues The cues in the subtitle. Null entries may be used to represent empty cues. + * @param cueTimesUs The cue times, in microseconds. + */ + public SubripSubtitle(Cue[] cues, long[] cueTimesUs) { + this.cues = cues; + this.cueTimesUs = cueTimesUs; + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + int index = Util.binarySearchCeil(cueTimesUs, timeUs, false, false); + return index < cueTimesUs.length ? index : C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return cueTimesUs.length; + } + + @Override + public long getEventTime(int index) { + Assertions.checkArgument(index >= 0); + Assertions.checkArgument(index < cueTimesUs.length); + return cueTimesUs[index]; + } + + @Override + public List getCues(long timeUs) { + int index = Util.binarySearchFloor(cueTimesUs, timeUs, true, false); + if (index == -1 || cues[index] == null) { + // timeUs is earlier than the start of the first cue, or we have an empty cue. + return Collections.emptyList(); + } else { + return Collections.singletonList(cues[index]); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlDecoder.java new file mode 100644 index 0000000..35f1d0e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlDecoder.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.ttml; + +import android.text.Layout; +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SimpleSubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoderException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ColorParser; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import com.tangxiaolv.telegramgallery.exoplayer2.util.XmlPullParserUtil; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +/** + * A {@link SimpleSubtitleDecoder} for TTML supporting the DFXP presentation profile. Features + * supported by this decoder are: + *

      + *
    • content + *
    • core + *
    • presentation + *
    • profile + *
    • structure + *
    • time-offset + *
    • timing + *
    • tickRate + *
    • time-clock-with-frames + *
    • time-clock + *
    • time-offset-with-frames + *
    • time-offset-with-ticks + *
    + * @see TTML specification + */ +public final class TtmlDecoder extends SimpleSubtitleDecoder { + + private static final String TAG = "TtmlDecoder"; + + private static final String TTP = "http://www.w3.org/ns/ttml#parameter"; + + private static final String ATTR_BEGIN = "begin"; + private static final String ATTR_DURATION = "dur"; + private static final String ATTR_END = "end"; + private static final String ATTR_STYLE = "style"; + private static final String ATTR_REGION = "region"; + + private static final Pattern CLOCK_TIME = + Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])" + + "(?:(\\.[0-9]+)|:([0-9][0-9])(?:\\.([0-9]+))?)?$"); + private static final Pattern OFFSET_TIME = + Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$"); + private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$"); + private static final Pattern PERCENTAGE_COORDINATES = + Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$"); + + private static final int DEFAULT_FRAME_RATE = 30; + + private static final FrameAndTickRate DEFAULT_FRAME_AND_TICK_RATE = + new FrameAndTickRate(DEFAULT_FRAME_RATE, 1, 1); + + private final XmlPullParserFactory xmlParserFactory; + + public TtmlDecoder() { + super("TtmlDecoder"); + try { + xmlParserFactory = XmlPullParserFactory.newInstance(); + xmlParserFactory.setNamespaceAware(true); + } catch (XmlPullParserException e) { + throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); + } + } + + @Override + protected TtmlSubtitle decode(byte[] bytes, int length, boolean reset) + throws SubtitleDecoderException { + try { + XmlPullParser xmlParser = xmlParserFactory.newPullParser(); + Map globalStyles = new HashMap<>(); + Map regionMap = new HashMap<>(); + regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); + xmlParser.setInput(inputStream, null); + TtmlSubtitle ttmlSubtitle = null; + LinkedList nodeStack = new LinkedList<>(); + int unsupportedNodeDepth = 0; + int eventType = xmlParser.getEventType(); + FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE; + while (eventType != XmlPullParser.END_DOCUMENT) { + TtmlNode parent = nodeStack.peekLast(); + if (unsupportedNodeDepth == 0) { + String name = xmlParser.getName(); + if (eventType == XmlPullParser.START_TAG) { + if (TtmlNode.TAG_TT.equals(name)) { + frameAndTickRate = parseFrameAndTickRates(xmlParser); + } + if (!isSupportedTag(name)) { + unsupportedNodeDepth++; + } else if (TtmlNode.TAG_HEAD.equals(name)) { + parseHeader(xmlParser, globalStyles, regionMap); + } else { + try { + TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate); + nodeStack.addLast(node); + if (parent != null) { + parent.addChild(node); + } + } catch (SubtitleDecoderException e) { + // Treat the node (and by extension, all of its children) as unsupported. + unsupportedNodeDepth++; + } + } + } else if (eventType == XmlPullParser.TEXT) { + parent.addChild(TtmlNode.buildTextNode(xmlParser.getText())); + } else if (eventType == XmlPullParser.END_TAG) { + if (xmlParser.getName().equals(TtmlNode.TAG_TT)) { + ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast(), globalStyles, regionMap); + } + nodeStack.removeLast(); + } + } else { + if (eventType == XmlPullParser.START_TAG) { + unsupportedNodeDepth++; + } else if (eventType == XmlPullParser.END_TAG) { + unsupportedNodeDepth--; + } + } + xmlParser.next(); + eventType = xmlParser.getEventType(); + } + return ttmlSubtitle; + } catch (XmlPullParserException xppe) { + throw new SubtitleDecoderException("Unable to decode source", xppe); + } catch (IOException e) { + throw new IllegalStateException("Unexpected error when reading input.", e); + } + } + + private FrameAndTickRate parseFrameAndTickRates(XmlPullParser xmlParser) + throws SubtitleDecoderException { + int frameRate = DEFAULT_FRAME_RATE; + String frameRateString = xmlParser.getAttributeValue(TTP, "frameRate"); + if (frameRateString != null) { + frameRate = Integer.parseInt(frameRateString); + } + + float frameRateMultiplier = 1; + String frameRateMultiplierString = xmlParser.getAttributeValue(TTP, "frameRateMultiplier"); + if (frameRateMultiplierString != null) { + String[] parts = frameRateMultiplierString.split(" "); + if (parts.length != 2) { + throw new SubtitleDecoderException("frameRateMultiplier doesn't have 2 parts"); + } + float numerator = Integer.parseInt(parts[0]); + float denominator = Integer.parseInt(parts[1]); + frameRateMultiplier = numerator / denominator; + } + + int subFrameRate = DEFAULT_FRAME_AND_TICK_RATE.subFrameRate; + String subFrameRateString = xmlParser.getAttributeValue(TTP, "subFrameRate"); + if (subFrameRateString != null) { + subFrameRate = Integer.parseInt(subFrameRateString); + } + + int tickRate = DEFAULT_FRAME_AND_TICK_RATE.tickRate; + String tickRateString = xmlParser.getAttributeValue(TTP, "tickRate"); + if (tickRateString != null) { + tickRate = Integer.parseInt(tickRateString); + } + return new FrameAndTickRate(frameRate * frameRateMultiplier, subFrameRate, tickRate); + } + + private Map parseHeader(XmlPullParser xmlParser, + Map globalStyles, Map globalRegions) + throws IOException, XmlPullParserException { + do { + xmlParser.next(); + if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) { + String parentStyleId = XmlPullParserUtil.getAttributeValue(xmlParser, ATTR_STYLE); + TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle()); + if (parentStyleId != null) { + for (String id : parseStyleIds(parentStyleId)) { + style.chain(globalStyles.get(id)); + } + } + if (style.getId() != null) { + globalStyles.put(style.getId(), style); + } + } else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) { + Pair ttmlRegionInfo = parseRegionAttributes(xmlParser); + if (ttmlRegionInfo != null) { + globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second); + } + } + } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD)); + return globalStyles; + } + + /** + * Parses a region declaration. Supports origin and extent definition but only when defined in + * terms of percentage of the viewport. Regions that do not correctly declare origin are ignored. + */ + private Pair parseRegionAttributes(XmlPullParser xmlParser) { + String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); + String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); + String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); + if (regionOrigin == null || regionId == null) { + return null; + } + float position = Cue.DIMEN_UNSET; + float line = Cue.DIMEN_UNSET; + Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); + if (originMatcher.matches()) { + try { + position = Float.parseFloat(originMatcher.group(1)) / 100.f; + line = Float.parseFloat(originMatcher.group(2)) / 100.f; + } catch (NumberFormatException e) { + position = Cue.DIMEN_UNSET; + } + } + float width = Cue.DIMEN_UNSET; + if (regionExtent != null) { + Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); + if (extentMatcher.matches()) { + try { + width = Float.parseFloat(extentMatcher.group(1)) / 100.f; + } catch (NumberFormatException e) { + e.getStackTrace(); + } + } + } + return position != Cue.DIMEN_UNSET ? new Pair<>(regionId, new TtmlRegion(position, line, + Cue.LINE_TYPE_FRACTION, width)) : null; + } + + private String[] parseStyleIds(String parentStyleIds) { + return parentStyleIds.split("\\s+"); + } + + private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) { + int attributeCount = parser.getAttributeCount(); + for (int i = 0; i < attributeCount; i++) { + String attributeValue = parser.getAttributeValue(i); + switch (parser.getAttributeName(i)) { + case TtmlNode.ATTR_ID: + if (TtmlNode.TAG_STYLE.equals(parser.getName())) { + style = createIfNull(style).setId(attributeValue); + } + break; + case TtmlNode.ATTR_TTS_BACKGROUND_COLOR: + style = createIfNull(style); + try { + style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue)); + } catch (IllegalArgumentException e) { + e.getStackTrace(); + } + break; + case TtmlNode.ATTR_TTS_COLOR: + style = createIfNull(style); + try { + style.setFontColor(ColorParser.parseTtmlColor(attributeValue)); + } catch (IllegalArgumentException e) { + e.getStackTrace(); + } + break; + case TtmlNode.ATTR_TTS_FONT_FAMILY: + style = createIfNull(style).setFontFamily(attributeValue); + break; + case TtmlNode.ATTR_TTS_FONT_SIZE: + try { + style = createIfNull(style); + parseFontSize(attributeValue, style); + } catch (SubtitleDecoderException e) { + e.getStackTrace(); + } + break; + case TtmlNode.ATTR_TTS_FONT_WEIGHT: + style = createIfNull(style).setBold( + TtmlNode.BOLD.equalsIgnoreCase(attributeValue)); + break; + case TtmlNode.ATTR_TTS_FONT_STYLE: + style = createIfNull(style).setItalic( + TtmlNode.ITALIC.equalsIgnoreCase(attributeValue)); + break; + case TtmlNode.ATTR_TTS_TEXT_ALIGN: + switch (Util.toLowerInvariant(attributeValue)) { + case TtmlNode.LEFT: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL); + break; + case TtmlNode.START: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL); + break; + case TtmlNode.RIGHT: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE); + break; + case TtmlNode.END: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE); + break; + case TtmlNode.CENTER: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_CENTER); + break; + } + break; + case TtmlNode.ATTR_TTS_TEXT_DECORATION: + switch (Util.toLowerInvariant(attributeValue)) { + case TtmlNode.LINETHROUGH: + style = createIfNull(style).setLinethrough(true); + break; + case TtmlNode.NO_LINETHROUGH: + style = createIfNull(style).setLinethrough(false); + break; + case TtmlNode.UNDERLINE: + style = createIfNull(style).setUnderline(true); + break; + case TtmlNode.NO_UNDERLINE: + style = createIfNull(style).setUnderline(false); + break; + } + break; + default: + // ignore + break; + } + } + return style; + } + + private TtmlStyle createIfNull(TtmlStyle style) { + return style == null ? new TtmlStyle() : style; + } + + private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent, + Map regionMap, FrameAndTickRate frameAndTickRate) + throws SubtitleDecoderException { + long duration = C.TIME_UNSET; + long startTime = C.TIME_UNSET; + long endTime = C.TIME_UNSET; + String regionId = TtmlNode.ANONYMOUS_REGION_ID; + String[] styleIds = null; + int attributeCount = parser.getAttributeCount(); + TtmlStyle style = parseStyleAttributes(parser, null); + for (int i = 0; i < attributeCount; i++) { + String attr = parser.getAttributeName(i); + String value = parser.getAttributeValue(i); + switch (attr) { + case ATTR_BEGIN: + startTime = parseTimeExpression(value, frameAndTickRate); + break; + case ATTR_END: + endTime = parseTimeExpression(value, frameAndTickRate); + break; + case ATTR_DURATION: + duration = parseTimeExpression(value, frameAndTickRate); + break; + case ATTR_STYLE: + // IDREFS: potentially multiple space delimited ids + String[] ids = parseStyleIds(value); + if (ids.length > 0) { + styleIds = ids; + } + break; + case ATTR_REGION: + if (regionMap.containsKey(value)) { + // If the region has not been correctly declared or does not define a position, we use + // the anonymous region. + regionId = value; + } + break; + default: + // Do nothing. + break; + } + } + if (parent != null && parent.startTimeUs != C.TIME_UNSET) { + if (startTime != C.TIME_UNSET) { + startTime += parent.startTimeUs; + } + if (endTime != C.TIME_UNSET) { + endTime += parent.startTimeUs; + } + } + if (endTime == C.TIME_UNSET) { + if (duration != C.TIME_UNSET) { + // Infer the end time from the duration. + endTime = startTime + duration; + } else if (parent != null && parent.endTimeUs != C.TIME_UNSET) { + // If the end time remains unspecified, then it should be inherited from the parent. + endTime = parent.endTimeUs; + } + } + return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId); + } + + private static boolean isSupportedTag(String tag) { + return tag.equals(TtmlNode.TAG_TT) + || tag.equals(TtmlNode.TAG_HEAD) + || tag.equals(TtmlNode.TAG_BODY) + || tag.equals(TtmlNode.TAG_DIV) + || tag.equals(TtmlNode.TAG_P) + || tag.equals(TtmlNode.TAG_SPAN) + || tag.equals(TtmlNode.TAG_BR) + || tag.equals(TtmlNode.TAG_STYLE) + || tag.equals(TtmlNode.TAG_STYLING) + || tag.equals(TtmlNode.TAG_LAYOUT) + || tag.equals(TtmlNode.TAG_REGION) + || tag.equals(TtmlNode.TAG_METADATA) + || tag.equals(TtmlNode.TAG_SMPTE_IMAGE) + || tag.equals(TtmlNode.TAG_SMPTE_DATA) + || tag.equals(TtmlNode.TAG_SMPTE_INFORMATION); + } + + private static void parseFontSize(String expression, TtmlStyle out) throws + SubtitleDecoderException { + String[] expressions = expression.split("\\s+"); + Matcher matcher; + if (expressions.length == 1) { + matcher = FONT_SIZE.matcher(expression); + } else if (expressions.length == 2){ + matcher = FONT_SIZE.matcher(expressions[1]); + } else { + throw new SubtitleDecoderException("Invalid number of entries for fontSize: " + + expressions.length + "."); + } + + if (matcher.matches()) { + String unit = matcher.group(3); + switch (unit) { + case "px": + out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PIXEL); + break; + case "em": + out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_EM); + break; + case "%": + out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PERCENT); + break; + default: + throw new SubtitleDecoderException("Invalid unit for fontSize: '" + unit + "'."); + } + out.setFontSize(Float.valueOf(matcher.group(1))); + } else { + throw new SubtitleDecoderException("Invalid expression for fontSize: '" + expression + "'."); + } + } + + /** + * Parses a time expression, returning the parsed timestamp. + *

    + * For the format of a time expression, see: + * timeExpression + * + * @param time A string that includes the time expression. + * @param frameAndTickRate The effective frame and tick rates of the stream. + * @return The parsed timestamp in microseconds. + * @throws SubtitleDecoderException If the given string does not contain a valid time expression. + */ + private static long parseTimeExpression(String time, FrameAndTickRate frameAndTickRate) + throws SubtitleDecoderException { + Matcher matcher = CLOCK_TIME.matcher(time); + if (matcher.matches()) { + String hours = matcher.group(1); + double durationSeconds = Long.parseLong(hours) * 3600; + String minutes = matcher.group(2); + durationSeconds += Long.parseLong(minutes) * 60; + String seconds = matcher.group(3); + durationSeconds += Long.parseLong(seconds); + String fraction = matcher.group(4); + durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0; + String frames = matcher.group(5); + durationSeconds += (frames != null) + ? Long.parseLong(frames) / frameAndTickRate.effectiveFrameRate : 0; + String subframes = matcher.group(6); + durationSeconds += (subframes != null) + ? ((double) Long.parseLong(subframes)) / frameAndTickRate.subFrameRate + / frameAndTickRate.effectiveFrameRate + : 0; + return (long) (durationSeconds * C.MICROS_PER_SECOND); + } + matcher = OFFSET_TIME.matcher(time); + if (matcher.matches()) { + String timeValue = matcher.group(1); + double offsetSeconds = Double.parseDouble(timeValue); + String unit = matcher.group(2); + switch (unit) { + case "h": + offsetSeconds *= 3600; + break; + case "m": + offsetSeconds *= 60; + break; + case "s": + // Do nothing. + break; + case "ms": + offsetSeconds /= 1000; + break; + case "f": + offsetSeconds /= frameAndTickRate.effectiveFrameRate; + break; + case "t": + offsetSeconds /= frameAndTickRate.tickRate; + break; + } + return (long) (offsetSeconds * C.MICROS_PER_SECOND); + } + throw new SubtitleDecoderException("Malformed time expression: " + time); + } + + private static final class FrameAndTickRate { + final float effectiveFrameRate; + final int subFrameRate; + final int tickRate; + + FrameAndTickRate(float effectiveFrameRate, int subFrameRate, int tickRate) { + this.effectiveFrameRate = effectiveFrameRate; + this.subFrameRate = subFrameRate; + this.tickRate = tickRate; + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlNode.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlNode.java new file mode 100644 index 0000000..e8e4567 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlNode.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.ttml; + +import android.text.SpannableStringBuilder; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * A package internal representation of TTML node. + */ +/* package */ final class TtmlNode { + + public static final String TAG_TT = "tt"; + public static final String TAG_HEAD = "head"; + public static final String TAG_BODY = "body"; + public static final String TAG_DIV = "div"; + public static final String TAG_P = "p"; + public static final String TAG_SPAN = "span"; + public static final String TAG_BR = "br"; + public static final String TAG_STYLE = "style"; + public static final String TAG_STYLING = "styling"; + public static final String TAG_LAYOUT = "layout"; + public static final String TAG_REGION = "region"; + public static final String TAG_METADATA = "metadata"; + public static final String TAG_SMPTE_IMAGE = "smpte:image"; + public static final String TAG_SMPTE_DATA = "smpte:data"; + public static final String TAG_SMPTE_INFORMATION = "smpte:information"; + + public static final String ANONYMOUS_REGION_ID = ""; + public static final String ATTR_ID = "id"; + public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor"; + public static final String ATTR_TTS_EXTENT = "extent"; + public static final String ATTR_TTS_FONT_STYLE = "fontStyle"; + public static final String ATTR_TTS_FONT_SIZE = "fontSize"; + public static final String ATTR_TTS_FONT_FAMILY = "fontFamily"; + public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight"; + public static final String ATTR_TTS_COLOR = "color"; + public static final String ATTR_TTS_ORIGIN = "origin"; + public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration"; + public static final String ATTR_TTS_TEXT_ALIGN = "textAlign"; + + public static final String LINETHROUGH = "linethrough"; + public static final String NO_LINETHROUGH = "nolinethrough"; + public static final String UNDERLINE = "underline"; + public static final String NO_UNDERLINE = "nounderline"; + public static final String ITALIC = "italic"; + public static final String BOLD = "bold"; + + public static final String LEFT = "left"; + public static final String CENTER = "center"; + public static final String RIGHT = "right"; + public static final String START = "start"; + public static final String END = "end"; + + public final String tag; + public final String text; + public final boolean isTextNode; + public final long startTimeUs; + public final long endTimeUs; + public final TtmlStyle style; + public final String regionId; + + private final String[] styleIds; + private final HashMap nodeStartsByRegion; + private final HashMap nodeEndsByRegion; + + private List children; + + public static TtmlNode buildTextNode(String text) { + return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), C.TIME_UNSET, + C.TIME_UNSET, null, null, ANONYMOUS_REGION_ID); + } + + public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs, + TtmlStyle style, String[] styleIds, String regionId) { + return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId); + } + + private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs, + TtmlStyle style, String[] styleIds, String regionId) { + this.tag = tag; + this.text = text; + this.style = style; + this.styleIds = styleIds; + this.isTextNode = text != null; + this.startTimeUs = startTimeUs; + this.endTimeUs = endTimeUs; + this.regionId = Assertions.checkNotNull(regionId); + nodeStartsByRegion = new HashMap<>(); + nodeEndsByRegion = new HashMap<>(); + } + + public boolean isActive(long timeUs) { + return (startTimeUs == C.TIME_UNSET && endTimeUs == C.TIME_UNSET) + || (startTimeUs <= timeUs && endTimeUs == C.TIME_UNSET) + || (startTimeUs == C.TIME_UNSET && timeUs < endTimeUs) + || (startTimeUs <= timeUs && timeUs < endTimeUs); + } + + public void addChild(TtmlNode child) { + if (children == null) { + children = new ArrayList<>(); + } + children.add(child); + } + + public TtmlNode getChild(int index) { + if (children == null) { + throw new IndexOutOfBoundsException(); + } + return children.get(index); + } + + public int getChildCount() { + return children == null ? 0 : children.size(); + } + + public long[] getEventTimesUs() { + TreeSet eventTimeSet = new TreeSet<>(); + getEventTimes(eventTimeSet, false); + long[] eventTimes = new long[eventTimeSet.size()]; + int i = 0; + for (long eventTimeUs : eventTimeSet) { + eventTimes[i++] = eventTimeUs; + } + return eventTimes; + } + + private void getEventTimes(TreeSet out, boolean descendsPNode) { + boolean isPNode = TAG_P.equals(tag); + if (descendsPNode || isPNode) { + if (startTimeUs != C.TIME_UNSET) { + out.add(startTimeUs); + } + if (endTimeUs != C.TIME_UNSET) { + out.add(endTimeUs); + } + } + if (children == null) { + return; + } + for (int i = 0; i < children.size(); i++) { + children.get(i).getEventTimes(out, descendsPNode || isPNode); + } + } + + public String[] getStyleIds() { + return styleIds; + } + + public List getCues(long timeUs, Map globalStyles, + Map regionMap) { + TreeMap regionOutputs = new TreeMap<>(); + traverseForText(timeUs, false, regionId, regionOutputs); + traverseForStyle(globalStyles, regionOutputs); + List cues = new ArrayList<>(); + for (Entry entry : regionOutputs.entrySet()) { + TtmlRegion region = regionMap.get(entry.getKey()); + cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, region.lineType, + Cue.TYPE_UNSET, region.position, Cue.TYPE_UNSET, region.width)); + } + return cues; + } + + private void traverseForText(long timeUs, boolean descendsPNode, + String inheritedRegion, Map regionOutputs) { + nodeStartsByRegion.clear(); + nodeEndsByRegion.clear(); + String resolvedRegionId = regionId; + if (ANONYMOUS_REGION_ID.equals(resolvedRegionId)) { + resolvedRegionId = inheritedRegion; + } + if (isTextNode && descendsPNode) { + getRegionOutput(resolvedRegionId, regionOutputs).append(text); + } else if (TAG_BR.equals(tag) && descendsPNode) { + getRegionOutput(resolvedRegionId, regionOutputs).append('\n'); + } else if (TAG_METADATA.equals(tag)) { + // Do nothing. + } else if (isActive(timeUs)) { + boolean isPNode = TAG_P.equals(tag); + for (Entry entry : regionOutputs.entrySet()) { + nodeStartsByRegion.put(entry.getKey(), entry.getValue().length()); + } + for (int i = 0; i < getChildCount(); i++) { + getChild(i).traverseForText(timeUs, descendsPNode || isPNode, resolvedRegionId, + regionOutputs); + } + if (isPNode) { + TtmlRenderUtil.endParagraph(getRegionOutput(resolvedRegionId, regionOutputs)); + } + for (Entry entry : regionOutputs.entrySet()) { + nodeEndsByRegion.put(entry.getKey(), entry.getValue().length()); + } + } + } + + private static SpannableStringBuilder getRegionOutput(String resolvedRegionId, + Map regionOutputs) { + if (!regionOutputs.containsKey(resolvedRegionId)) { + regionOutputs.put(resolvedRegionId, new SpannableStringBuilder()); + } + return regionOutputs.get(resolvedRegionId); + } + + private void traverseForStyle(Map globalStyles, + Map regionOutputs) { + for (Entry entry : nodeEndsByRegion.entrySet()) { + String regionId = entry.getKey(); + int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0; + applyStyleToOutput(globalStyles, regionOutputs.get(regionId), start, entry.getValue()); + for (int i = 0; i < getChildCount(); ++i) { + getChild(i).traverseForStyle(globalStyles, regionOutputs); + } + } + } + + private void applyStyleToOutput(Map globalStyles, + SpannableStringBuilder regionOutput, int start, int end) { + if (start != end) { + TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles); + if (resolvedStyle != null) { + TtmlRenderUtil.applyStylesToSpan(regionOutput, start, end, resolvedStyle); + } + } + } + + private SpannableStringBuilder cleanUpText(SpannableStringBuilder builder) { + // Having joined the text elements, we need to do some final cleanup on the result. + // 1. Collapse multiple consecutive spaces into a single space. + int builderLength = builder.length(); + for (int i = 0; i < builderLength; i++) { + if (builder.charAt(i) == ' ') { + int j = i + 1; + while (j < builder.length() && builder.charAt(j) == ' ') { + j++; + } + int spacesToDelete = j - (i + 1); + if (spacesToDelete > 0) { + builder.delete(i, i + spacesToDelete); + builderLength -= spacesToDelete; + } + } + } + // 2. Remove any spaces from the start of each line. + if (builderLength > 0 && builder.charAt(0) == ' ') { + builder.delete(0, 1); + builderLength--; + } + for (int i = 0; i < builderLength - 1; i++) { + if (builder.charAt(i) == '\n' && builder.charAt(i + 1) == ' ') { + builder.delete(i + 1, i + 2); + builderLength--; + } + } + // 3. Remove any spaces from the end of each line. + if (builderLength > 0 && builder.charAt(builderLength - 1) == ' ') { + builder.delete(builderLength - 1, builderLength); + builderLength--; + } + for (int i = 0; i < builderLength - 1; i++) { + if (builder.charAt(i) == ' ' && builder.charAt(i + 1) == '\n') { + builder.delete(i, i + 1); + builderLength--; + } + } + // 4. Trim a trailing newline, if there is one. + if (builderLength > 0 && builder.charAt(builderLength - 1) == '\n') { + builder.delete(builderLength - 1, builderLength); + /*builderLength--;*/ + } + return builder; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlRegion.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlRegion.java new file mode 100644 index 0000000..f7b1491 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlRegion.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.ttml; + +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; + +/** + * Represents a TTML Region. + */ +/* package */ final class TtmlRegion { + + public final float position; + public final float line; + @Cue.LineType + public final int lineType; + public final float width; + + public TtmlRegion() { + this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); + } + + public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) { + this.position = position; + this.line = line; + this.lineType = lineType; + this.width = width; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlRenderUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlRenderUtil.java new file mode 100644 index 0000000..9e78d03 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlRenderUtil.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.ttml; + +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.AlignmentSpan; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; +import android.text.style.UnderlineSpan; +import java.util.Map; + +/** + * Package internal utility class to render styled TtmlNodes. + */ +/* package */ final class TtmlRenderUtil { + + public static TtmlStyle resolveStyle(TtmlStyle style, String[] styleIds, + Map globalStyles) { + if (style == null && styleIds == null) { + // No styles at all. + return null; + } else if (style == null && styleIds.length == 1) { + // Only one single referential style present. + return globalStyles.get(styleIds[0]); + } else if (style == null && styleIds.length > 1) { + // Only multiple referential styles present. + TtmlStyle chainedStyle = new TtmlStyle(); + for (String id : styleIds) { + chainedStyle.chain(globalStyles.get(id)); + } + return chainedStyle; + } else if (style != null && styleIds != null && styleIds.length == 1) { + // Merge a single referential style into inline style. + return style.chain(globalStyles.get(styleIds[0])); + } else if (style != null && styleIds != null && styleIds.length > 1) { + // Merge multiple referential styles into inline style. + for (String id : styleIds) { + style.chain(globalStyles.get(id)); + } + return style; + } + // Only inline styles available. + return style; + } + + public static void applyStylesToSpan(SpannableStringBuilder builder, + int start, int end, TtmlStyle style) { + + if (style.getStyle() != TtmlStyle.UNSPECIFIED) { + builder.setSpan(new StyleSpan(style.getStyle()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.isLinethrough()) { + builder.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.isUnderline()) { + builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.hasFontColor()) { + builder.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.hasBackgroundColor()) { + builder.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.getFontFamily() != null) { + builder.setSpan(new TypefaceSpan(style.getFontFamily()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.getTextAlign() != null) { + builder.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + switch (style.getFontSizeUnit()) { + case TtmlStyle.FONT_SIZE_UNIT_PIXEL: + builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.FONT_SIZE_UNIT_EM: + builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.FONT_SIZE_UNIT_PERCENT: + builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.UNSPECIFIED: + // Do nothing. + break; + } + } + + /** + * Called when the end of a paragraph is encountered. Adds a newline if there are one or more + * non-space characters since the previous newline. + * + * @param builder The builder. + */ + /* package */ static void endParagraph(SpannableStringBuilder builder) { + int position = builder.length() - 1; + while (position >= 0 && builder.charAt(position) == ' ') { + position--; + } + if (position >= 0 && builder.charAt(position) != '\n') { + builder.append('\n'); + } + } + + /** + * Applies the appropriate space policy to the given text element. + * + * @param in The text element to which the policy should be applied. + * @return The result of applying the policy to the text element. + */ + /* package */ static String applyTextElementSpacePolicy(String in) { + // Removes carriage return followed by line feed. See: http://www.w3.org/TR/xml/#sec-line-ends + String out = in.replaceAll("\r\n", "\n"); + // Apply suppress-at-line-break="auto" and + // white-space-treatment="ignore-if-surrounding-linefeed" + out = out.replaceAll(" *\n *", "\n"); + // Apply linefeed-treatment="treat-as-space" + out = out.replaceAll("\n", " "); + // Apply white-space-collapse="true" + out = out.replaceAll("[ \t\\x0B\f\r]+", " "); + return out; + } + + private TtmlRenderUtil() {} + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlStyle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlStyle.java new file mode 100644 index 0000000..b899ad7 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlStyle.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.ttml; + +import android.graphics.Typeface; +import android.support.annotation.IntDef; +import android.text.Layout; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Style object of a TtmlNode + */ +/* package */ final class TtmlStyle { + + public static final int UNSPECIFIED = -1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, + STYLE_BOLD_ITALIC}) + public @interface StyleFlags {} + public static final int STYLE_NORMAL = Typeface.NORMAL; + public static final int STYLE_BOLD = Typeface.BOLD; + public static final int STYLE_ITALIC = Typeface.ITALIC; + public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) + public @interface FontSizeUnit {} + public static final int FONT_SIZE_UNIT_PIXEL = 1; + public static final int FONT_SIZE_UNIT_EM = 2; + public static final int FONT_SIZE_UNIT_PERCENT = 3; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, OFF, ON}) + private @interface OptionalBoolean {} + private static final int OFF = 0; + private static final int ON = 1; + + private String fontFamily; + private int fontColor; + private boolean hasFontColor; + private int backgroundColor; + private boolean hasBackgroundColor; + @OptionalBoolean private int linethrough; + @OptionalBoolean private int underline; + @OptionalBoolean private int bold; + @OptionalBoolean private int italic; + @FontSizeUnit private int fontSizeUnit; + private float fontSize; + private String id; + private TtmlStyle inheritableStyle; + private Layout.Alignment textAlign; + + public TtmlStyle() { + linethrough = UNSPECIFIED; + underline = UNSPECIFIED; + bold = UNSPECIFIED; + italic = UNSPECIFIED; + fontSizeUnit = UNSPECIFIED; + } + + /** + * Returns the style or {@link #UNSPECIFIED} when no style information is given. + * + * @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD} + * or {@link #STYLE_BOLD_ITALIC}. + */ + @StyleFlags public int getStyle() { + if (bold == UNSPECIFIED && italic == UNSPECIFIED) { + return UNSPECIFIED; + } + return (bold == ON ? STYLE_BOLD : STYLE_NORMAL) + | (italic == ON ? STYLE_ITALIC : STYLE_NORMAL); + } + + public boolean isLinethrough() { + return linethrough == ON; + } + + public TtmlStyle setLinethrough(boolean linethrough) { + Assertions.checkState(inheritableStyle == null); + this.linethrough = linethrough ? ON : OFF; + return this; + } + + public boolean isUnderline() { + return underline == ON; + } + + public TtmlStyle setUnderline(boolean underline) { + Assertions.checkState(inheritableStyle == null); + this.underline = underline ? ON : OFF; + return this; + } + + public TtmlStyle setBold(boolean bold) { + Assertions.checkState(inheritableStyle == null); + this.bold = bold ? ON : OFF; + return this; + } + + public TtmlStyle setItalic(boolean italic) { + Assertions.checkState(inheritableStyle == null); + this.italic = italic ? ON : OFF; + return this; + } + + public String getFontFamily() { + return fontFamily; + } + + public TtmlStyle setFontFamily(String fontFamily) { + Assertions.checkState(inheritableStyle == null); + this.fontFamily = fontFamily; + return this; + } + + public int getFontColor() { + if (!hasFontColor) { + throw new IllegalStateException("Font color has not been defined."); + } + return fontColor; + } + + public TtmlStyle setFontColor(int fontColor) { + Assertions.checkState(inheritableStyle == null); + this.fontColor = fontColor; + hasFontColor = true; + return this; + } + + public boolean hasFontColor() { + return hasFontColor; + } + + public int getBackgroundColor() { + if (!hasBackgroundColor) { + throw new IllegalStateException("Background color has not been defined."); + } + return backgroundColor; + } + + public TtmlStyle setBackgroundColor(int backgroundColor) { + this.backgroundColor = backgroundColor; + hasBackgroundColor = true; + return this; + } + + public boolean hasBackgroundColor() { + return hasBackgroundColor; + } + + /** + * Inherits from an ancestor style. Properties like tts:backgroundColor which + * are not inheritable are not inherited as well as properties which are already set locally + * are never overridden. + * + * @param ancestor the ancestor style to inherit from + */ + public TtmlStyle inherit(TtmlStyle ancestor) { + return inherit(ancestor, false); + } + + /** + * Chains this style to referential style. Local properties which are already set + * are never overridden. + * + * @param ancestor the referential style to inherit from + */ + public TtmlStyle chain(TtmlStyle ancestor) { + return inherit(ancestor, true); + } + + private TtmlStyle inherit(TtmlStyle ancestor, boolean chaining) { + if (ancestor != null) { + if (!hasFontColor && ancestor.hasFontColor) { + setFontColor(ancestor.fontColor); + } + if (bold == UNSPECIFIED) { + bold = ancestor.bold; + } + if (italic == UNSPECIFIED) { + italic = ancestor.italic; + } + if (fontFamily == null) { + fontFamily = ancestor.fontFamily; + } + if (linethrough == UNSPECIFIED) { + linethrough = ancestor.linethrough; + } + if (underline == UNSPECIFIED) { + underline = ancestor.underline; + } + if (textAlign == null) { + textAlign = ancestor.textAlign; + } + if (fontSizeUnit == UNSPECIFIED) { + fontSizeUnit = ancestor.fontSizeUnit; + fontSize = ancestor.fontSize; + } + // attributes not inherited as of http://www.w3.org/TR/ttml1/ + if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) { + setBackgroundColor(ancestor.backgroundColor); + } + } + return this; + } + + public TtmlStyle setId(String id) { + this.id = id; + return this; + } + + public String getId() { + return id; + } + + public Layout.Alignment getTextAlign() { + return textAlign; + } + + public TtmlStyle setTextAlign(Layout.Alignment textAlign) { + this.textAlign = textAlign; + return this; + } + + public TtmlStyle setFontSize(float fontSize) { + this.fontSize = fontSize; + return this; + } + + public TtmlStyle setFontSizeUnit(int fontSizeUnit) { + this.fontSizeUnit = fontSizeUnit; + return this; + } + + @FontSizeUnit public int getFontSizeUnit() { + return fontSizeUnit; + } + + public float getFontSize() { + return fontSize; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlSubtitle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlSubtitle.java new file mode 100644 index 0000000..e3b255d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/ttml/TtmlSubtitle.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.ttml; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * A representation of a TTML subtitle. + */ +/* package */ final class TtmlSubtitle implements Subtitle { + + private final TtmlNode root; + private final long[] eventTimesUs; + private final Map globalStyles; + private final Map regionMap; + + public TtmlSubtitle(TtmlNode root, Map globalStyles, + Map regionMap) { + this.root = root; + this.regionMap = regionMap; + this.globalStyles = globalStyles != null + ? Collections.unmodifiableMap(globalStyles) : Collections.emptyMap(); + this.eventTimesUs = root.getEventTimesUs(); + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + int index = Util.binarySearchCeil(eventTimesUs, timeUs, false, false); + return index < eventTimesUs.length ? index : C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return eventTimesUs.length; + } + + @Override + public long getEventTime(int index) { + return eventTimesUs[index]; + } + + /* @VisibleForTesting */ + /* package */ TtmlNode getRoot() { + return root; + } + + @Override + public List getCues(long timeUs) { + return root.getCues(timeUs, globalStyles, regionMap); + } + + /* @VisibleForTesting */ + /* package */ Map getGlobalStyles() { + return globalStyles; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/tx3g/Tx3gDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/tx3g/Tx3gDecoder.java new file mode 100644 index 0000000..46414a8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.tx3g; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; +import android.text.style.UnderlineSpan; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SimpleSubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoderException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.nio.charset.Charset; +import java.util.List; + +/** + * A {@link SimpleSubtitleDecoder} for tx3g. + *

    + * Currently supports parsing of a single text track with embedded styles. + */ +public final class Tx3gDecoder extends SimpleSubtitleDecoder { + + private static final char BOM_UTF16_BE = '\uFEFF'; + private static final char BOM_UTF16_LE = '\uFFFE'; + + private static final int TYPE_STYL = Util.getIntegerCodeForString("styl"); + private static final int TYPE_TBOX = Util.getIntegerCodeForString("tbox"); + private static final String TX3G_SERIF = "Serif"; + + private static final int SIZE_ATOM_HEADER = 8; + private static final int SIZE_SHORT = 2; + private static final int SIZE_BOM_UTF16 = 2; + private static final int SIZE_STYLE_RECORD = 12; + + private static final int FONT_FACE_BOLD = 0x0001; + private static final int FONT_FACE_ITALIC = 0x0002; + private static final int FONT_FACE_UNDERLINE = 0x0004; + + private static final int SPAN_PRIORITY_LOW = (0xFF << Spanned.SPAN_PRIORITY_SHIFT); + private static final int SPAN_PRIORITY_HIGH = (0x00 << Spanned.SPAN_PRIORITY_SHIFT); + + private static final int DEFAULT_FONT_FACE = 0; + private static final int DEFAULT_COLOR = Color.WHITE; + private static final String DEFAULT_FONT_FAMILY = C.SANS_SERIF_NAME; + private static final float DEFAULT_VERTICAL_PLACEMENT = 0.85f; + + private final ParsableByteArray parsableByteArray; + private boolean customVerticalPlacement; + private int defaultFontFace; + private int defaultColorRgba; + private String defaultFontFamily; + private float defaultVerticalPlacement; + private int calculatedVideoTrackHeight; + + /** + * Sets up a new {@link Tx3gDecoder} with default values. + * + * @param initializationData Sample description atom ('stsd') data with default subtitle styles. + */ + public Tx3gDecoder(List initializationData) { + super("Tx3gDecoder"); + parsableByteArray = new ParsableByteArray(); + decodeInitializationData(initializationData); + } + + private void decodeInitializationData(List initializationData) { + if (initializationData != null && initializationData.size() == 1 + && (initializationData.get(0).length == 48 || initializationData.get(0).length == 53)) { + byte[] initializationBytes = initializationData.get(0); + defaultFontFace = initializationBytes[24]; + defaultColorRgba = ((initializationBytes[26] & 0xFF) << 24) + | ((initializationBytes[27] & 0xFF) << 16) + | ((initializationBytes[28] & 0xFF) << 8) + | (initializationBytes[29] & 0xFF); + String fontFamily = new String(initializationBytes, 43, initializationBytes.length - 43); + defaultFontFamily = TX3G_SERIF.equals(fontFamily) ? C.SERIF_NAME : C.SANS_SERIF_NAME; + //font size (initializationBytes[25]) is 5% of video height + calculatedVideoTrackHeight = 20 * initializationBytes[25]; + customVerticalPlacement = (initializationBytes[0] & 0x20) != 0; + if (customVerticalPlacement) { + int requestedVerticalPlacement = ((initializationBytes[10] & 0xFF) << 8) + | (initializationBytes[11] & 0xFF); + defaultVerticalPlacement = (float) requestedVerticalPlacement / calculatedVideoTrackHeight; + defaultVerticalPlacement = Util.constrainValue(defaultVerticalPlacement, 0.0f, 0.95f); + } else { + defaultVerticalPlacement = DEFAULT_VERTICAL_PLACEMENT; + } + } else { + defaultFontFace = DEFAULT_FONT_FACE; + defaultColorRgba = DEFAULT_COLOR; + defaultFontFamily = DEFAULT_FONT_FAMILY; + customVerticalPlacement = false; + defaultVerticalPlacement = DEFAULT_VERTICAL_PLACEMENT; + } + } + + @Override + protected Subtitle decode(byte[] bytes, int length, boolean reset) + throws SubtitleDecoderException { + parsableByteArray.reset(bytes, length); + String cueTextString = readSubtitleText(parsableByteArray); + if (cueTextString.isEmpty()) { + return Tx3gSubtitle.EMPTY; + } + // Attach default styles. + SpannableStringBuilder cueText = new SpannableStringBuilder(cueTextString); + attachFontFace(cueText, defaultFontFace, DEFAULT_FONT_FACE, 0, cueText.length(), + SPAN_PRIORITY_LOW); + attachColor(cueText, defaultColorRgba, DEFAULT_COLOR, 0, cueText.length(), + SPAN_PRIORITY_LOW); + attachFontFamily(cueText, defaultFontFamily, DEFAULT_FONT_FAMILY, 0, cueText.length(), + SPAN_PRIORITY_LOW); + float verticalPlacement = defaultVerticalPlacement; + // Find and attach additional styles. + while (parsableByteArray.bytesLeft() >= SIZE_ATOM_HEADER) { + int position = parsableByteArray.getPosition(); + int atomSize = parsableByteArray.readInt(); + int atomType = parsableByteArray.readInt(); + if (atomType == TYPE_STYL) { + assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT); + int styleRecordCount = parsableByteArray.readUnsignedShort(); + for (int i = 0; i < styleRecordCount; i++) { + applyStyleRecord(parsableByteArray, cueText); + } + } else if (atomType == TYPE_TBOX && customVerticalPlacement) { + assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT); + int requestedVerticalPlacement = parsableByteArray.readUnsignedShort(); + verticalPlacement = (float) requestedVerticalPlacement / calculatedVideoTrackHeight; + verticalPlacement = Util.constrainValue(verticalPlacement, 0.0f, 0.95f); + } + parsableByteArray.setPosition(position + atomSize); + } + return new Tx3gSubtitle(new Cue(cueText, null, verticalPlacement, Cue.LINE_TYPE_FRACTION, + Cue.ANCHOR_TYPE_START, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET)); + } + + private static String readSubtitleText(ParsableByteArray parsableByteArray) + throws SubtitleDecoderException { + assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT); + int textLength = parsableByteArray.readUnsignedShort(); + if (textLength == 0) { + return ""; + } + if (parsableByteArray.bytesLeft() >= SIZE_BOM_UTF16) { + char firstChar = parsableByteArray.peekChar(); + if (firstChar == BOM_UTF16_BE || firstChar == BOM_UTF16_LE) { + return parsableByteArray.readString(textLength, Charset.forName(C.UTF16_NAME)); + } + } + return parsableByteArray.readString(textLength, Charset.forName(C.UTF8_NAME)); + } + + private void applyStyleRecord(ParsableByteArray parsableByteArray, + SpannableStringBuilder cueText) throws SubtitleDecoderException { + assertTrue(parsableByteArray.bytesLeft() >= SIZE_STYLE_RECORD); + int start = parsableByteArray.readUnsignedShort(); + int end = parsableByteArray.readUnsignedShort(); + parsableByteArray.skipBytes(2); // font identifier + int fontFace = parsableByteArray.readUnsignedByte(); + parsableByteArray.skipBytes(1); // font size + int colorRgba = parsableByteArray.readInt(); + attachFontFace(cueText, fontFace, defaultFontFace, start, end, SPAN_PRIORITY_HIGH); + attachColor(cueText, colorRgba, defaultColorRgba, start, end, SPAN_PRIORITY_HIGH); + } + + private static void attachFontFace(SpannableStringBuilder cueText, int fontFace, + int defaultFontFace, int start, int end, int spanPriority) { + if (fontFace != defaultFontFace) { + final int flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority; + boolean isBold = (fontFace & FONT_FACE_BOLD) != 0; + boolean isItalic = (fontFace & FONT_FACE_ITALIC) != 0; + if (isBold) { + if (isItalic) { + cueText.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flags); + } else { + cueText.setSpan(new StyleSpan(Typeface.BOLD), start, end, flags); + } + } else if (isItalic) { + cueText.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flags); + } + boolean isUnderlined = (fontFace & FONT_FACE_UNDERLINE) != 0; + if (isUnderlined) { + cueText.setSpan(new UnderlineSpan(), start, end, flags); + } + if (!isUnderlined && !isBold && !isItalic) { + cueText.setSpan(new StyleSpan(Typeface.NORMAL), start, end, flags); + } + } + } + + private static void attachColor(SpannableStringBuilder cueText, int colorRgba, + int defaultColorRgba, int start, int end, int spanPriority) { + if (colorRgba != defaultColorRgba) { + int colorArgb = ((colorRgba & 0xFF) << 24) | (colorRgba >>> 8); + cueText.setSpan(new ForegroundColorSpan(colorArgb), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority); + } + } + + @SuppressWarnings("ReferenceEquality") + private static void attachFontFamily(SpannableStringBuilder cueText, String fontFamily, + String defaultFontFamily, int start, int end, int spanPriority) { + if (fontFamily != defaultFontFamily) { + cueText.setSpan(new TypefaceSpan(fontFamily), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority); + } + } + + private static void assertTrue(boolean checkValue) throws SubtitleDecoderException { + if (!checkValue) { + throw new SubtitleDecoderException("Unexpected subtitle format."); + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/tx3g/Tx3gSubtitle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/tx3g/Tx3gSubtitle.java new file mode 100644 index 0000000..5ef1bf8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/tx3g/Tx3gSubtitle.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.tx3g; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.Collections; +import java.util.List; + +/** + * A representation of a tx3g subtitle. + */ +/* package */ final class Tx3gSubtitle implements Subtitle { + + public static final Tx3gSubtitle EMPTY = new Tx3gSubtitle(); + + private final List cues; + + public Tx3gSubtitle(Cue cue) { + this.cues = Collections.singletonList(cue); + } + + private Tx3gSubtitle() { + this.cues = Collections.emptyList(); + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + return timeUs < 0 ? 0 : C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return 1; + } + + @Override + public long getEventTime(int index) { + Assertions.checkArgument(index == 0); + return 0; + } + + @Override + public List getCues(long timeUs) { + return timeUs >= 0 ? cues : Collections.emptyList(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/CssParser.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/CssParser.java new file mode 100644 index 0000000..f9aa50c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/CssParser.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ColorParser; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provides a CSS parser for STYLE blocks in Webvtt files. Supports only a subset of the CSS + * features. + */ +/* package */ final class CssParser { + + private static final String PROPERTY_BGCOLOR = "background-color"; + private static final String PROPERTY_FONT_FAMILY = "font-family"; + private static final String PROPERTY_FONT_WEIGHT = "font-weight"; + private static final String PROPERTY_TEXT_DECORATION = "text-decoration"; + private static final String VALUE_BOLD = "bold"; + private static final String VALUE_UNDERLINE = "underline"; + private static final String BLOCK_START = "{"; + private static final String BLOCK_END = "}"; + private static final String PROPERTY_FONT_STYLE = "font-style"; + private static final String VALUE_ITALIC = "italic"; + + private static final Pattern VOICE_NAME_PATTERN = Pattern.compile("\\[voice=\"([^\"]*)\"\\]"); + + // Temporary utility data structures. + private final ParsableByteArray styleInput; + private final StringBuilder stringBuilder; + + public CssParser() { + styleInput = new ParsableByteArray(); + stringBuilder = new StringBuilder(); + } + + /** + * Takes a CSS style block and consumes up to the first empty line found. Attempts to parse the + * contents of the style block and returns a {@link WebvttCssStyle} instance if successful, or + * {@code null} otherwise. + * + * @param input The input from which the style block should be read. + * @return A {@link WebvttCssStyle} that represents the parsed block. + */ + public WebvttCssStyle parseBlock(ParsableByteArray input) { + stringBuilder.setLength(0); + int initialInputPosition = input.getPosition(); + skipStyleBlock(input); + styleInput.reset(input.data, input.getPosition()); + styleInput.setPosition(initialInputPosition); + String selector = parseSelector(styleInput, stringBuilder); + if (selector == null || !BLOCK_START.equals(parseNextToken(styleInput, stringBuilder))) { + return null; + } + WebvttCssStyle style = new WebvttCssStyle(); + applySelectorToStyle(style, selector); + String token = null; + boolean blockEndFound = false; + while (!blockEndFound) { + int position = styleInput.getPosition(); + token = parseNextToken(styleInput, stringBuilder); + blockEndFound = token == null || BLOCK_END.equals(token); + if (!blockEndFound) { + styleInput.setPosition(position); + parseStyleDeclaration(styleInput, style, stringBuilder); + } + } + return BLOCK_END.equals(token) ? style : null; // Check that the style block ended correctly. + } + + /** + * Returns a string containing the selector. The input is expected to have the form + * {@code ::cue(tag#id.class1.class2[voice="someone"]}, where every element is optional. + * + * @param input From which the selector is obtained. + * @return A string containing the target, empty string if the selector is universal + * (targets all cues) or null if an error was encountered. + */ + private static String parseSelector(ParsableByteArray input, StringBuilder stringBuilder) { + skipWhitespaceAndComments(input); + if (input.bytesLeft() < 5) { + return null; + } + String cueSelector = input.readString(5); + if (!"::cue".equals(cueSelector)) { + return null; + } + int position = input.getPosition(); + String token = parseNextToken(input, stringBuilder); + if (token == null) { + return null; + } + if (BLOCK_START.equals(token)) { + input.setPosition(position); + return ""; + } + String target = null; + if ("(".equals(token)) { + target = readCueTarget(input); + } + token = parseNextToken(input, stringBuilder); + if (!")".equals(token) || token == null) { + return null; + } + return target; + } + + /** + * Reads the contents of ::cue() and returns it as a string. + */ + private static String readCueTarget(ParsableByteArray input) { + int position = input.getPosition(); + int limit = input.limit(); + boolean cueTargetEndFound = false; + while (position < limit && !cueTargetEndFound) { + char c = (char) input.data[position++]; + cueTargetEndFound = c == ')'; + } + return input.readString(--position - input.getPosition()).trim(); + // --offset to return ')' to the input. + } + + private static void parseStyleDeclaration(ParsableByteArray input, WebvttCssStyle style, + StringBuilder stringBuilder) { + skipWhitespaceAndComments(input); + String property = parseIdentifier(input, stringBuilder); + if ("".equals(property)) { + return; + } + if (!":".equals(parseNextToken(input, stringBuilder))) { + return; + } + skipWhitespaceAndComments(input); + String value = parsePropertyValue(input, stringBuilder); + if (value == null || "".equals(value)) { + return; + } + int position = input.getPosition(); + String token = parseNextToken(input, stringBuilder); + if (";".equals(token)) { + // The style declaration is well formed. + } else if (BLOCK_END.equals(token)) { + // The style declaration is well formed and we can go on, but the closing bracket had to be + // fed back. + input.setPosition(position); + } else { + // The style declaration is not well formed. + return; + } + // At this point we have a presumably valid declaration, we need to parse it and fill the style. + if ("color".equals(property)) { + style.setFontColor(ColorParser.parseCssColor(value)); + } else if (PROPERTY_BGCOLOR.equals(property)) { + style.setBackgroundColor(ColorParser.parseCssColor(value)); + } else if (PROPERTY_TEXT_DECORATION.equals(property)) { + if (VALUE_UNDERLINE.equals(value)) { + style.setUnderline(true); + } + } else if (PROPERTY_FONT_FAMILY.equals(property)) { + style.setFontFamily(value); + } else if (PROPERTY_FONT_WEIGHT.equals(property)) { + if (VALUE_BOLD.equals(value)) { + style.setBold(true); + } + } else if (PROPERTY_FONT_STYLE.equals(property)) { + if (VALUE_ITALIC.equals(value)) { + style.setItalic(true); + } + } + // TODO: Fill remaining supported styles. + } + + // Visible for testing. + /* package */ static void skipWhitespaceAndComments(ParsableByteArray input) { + boolean skipping = true; + while (input.bytesLeft() > 0 && skipping) { + skipping = maybeSkipWhitespace(input) || maybeSkipComment(input); + } + } + + // Visible for testing. + /* package */ static String parseNextToken(ParsableByteArray input, StringBuilder stringBuilder) { + skipWhitespaceAndComments(input); + if (input.bytesLeft() == 0) { + return null; + } + String identifier = parseIdentifier(input, stringBuilder); + if (!"".equals(identifier)) { + return identifier; + } + // We found a delimiter. + return "" + (char) input.readUnsignedByte(); + } + + private static boolean maybeSkipWhitespace(ParsableByteArray input) { + switch(peekCharAtPosition(input, input.getPosition())) { + case '\t': + case '\r': + case '\n': + case '\f': + case ' ': + input.skipBytes(1); + return true; + default: + return false; + } + } + + // Visible for testing. + /* package */ static void skipStyleBlock(ParsableByteArray input) { + // The style block cannot contain empty lines, so we assume the input ends when a empty line + // is found. + String line; + do { + line = input.readLine(); + } while (!TextUtils.isEmpty(line)); + } + + private static char peekCharAtPosition(ParsableByteArray input, int position) { + return (char) input.data[position]; + } + + private static String parsePropertyValue(ParsableByteArray input, StringBuilder stringBuilder) { + StringBuilder expressionBuilder = new StringBuilder(); + String token; + int position; + boolean expressionEndFound = false; + // TODO: Add support for "Strings in quotes with spaces". + while (!expressionEndFound) { + position = input.getPosition(); + token = parseNextToken(input, stringBuilder); + if (token == null) { + // Syntax error. + return null; + } + if (BLOCK_END.equals(token) || ";".equals(token)) { + input.setPosition(position); + expressionEndFound = true; + } else { + expressionBuilder.append(token); + } + } + return expressionBuilder.toString(); + } + + private static boolean maybeSkipComment(ParsableByteArray input) { + int position = input.getPosition(); + int limit = input.limit(); + byte[] data = input.data; + if (position + 2 <= limit && data[position++] == '/' && data[position++] == '*') { + while (position + 1 < limit) { + char skippedChar = (char) data[position++]; + if (skippedChar == '*') { + if (((char) data[position]) == '/') { + position++; + limit = position; + } + } + } + input.skipBytes(limit - input.getPosition()); + return true; + } + return false; + } + + private static String parseIdentifier(ParsableByteArray input, StringBuilder stringBuilder) { + stringBuilder.setLength(0); + int position = input.getPosition(); + int limit = input.limit(); + boolean identifierEndFound = false; + while (position < limit && !identifierEndFound) { + char c = (char) input.data[position]; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '#' + || c == '-' || c == '.' || c == '_') { + position++; + stringBuilder.append(c); + } else { + identifierEndFound = true; + } + } + input.skipBytes(position - input.getPosition()); + return stringBuilder.toString(); + } + + /** + * Sets the target of a {@link WebvttCssStyle} by splitting a selector of the form + * {@code ::cue(tag#id.class1.class2[voice="someone"]}, where every element is optional. + */ + private void applySelectorToStyle(WebvttCssStyle style, String selector) { + if ("".equals(selector)) { + return; // Universal selector. + } + int voiceStartIndex = selector.indexOf('['); + if (voiceStartIndex != -1) { + Matcher matcher = VOICE_NAME_PATTERN.matcher(selector.substring(voiceStartIndex)); + if (matcher.matches()) { + style.setTargetVoice(matcher.group(1)); + } + selector = selector.substring(0, voiceStartIndex); + } + String[] classDivision = selector.split("\\."); + String tagAndIdDivision = classDivision[0]; + int idPrefixIndex = tagAndIdDivision.indexOf('#'); + if (idPrefixIndex != -1) { + style.setTargetTagName(tagAndIdDivision.substring(0, idPrefixIndex)); + style.setTargetId(tagAndIdDivision.substring(idPrefixIndex + 1)); // We discard the '#'. + } else { + style.setTargetTagName(tagAndIdDivision); + } + if (classDivision.length > 1) { + style.setTargetClasses(Arrays.copyOfRange(classDivision, 1, classDivision.length)); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/Mp4WebvttDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/Mp4WebvttDecoder.java new file mode 100644 index 0000000..68e9b3d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/Mp4WebvttDecoder.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SimpleSubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoderException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A {@link SimpleSubtitleDecoder} for Webvtt embedded in a Mp4 container file. + */ +public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder { + + private static final int BOX_HEADER_SIZE = 8; + + private static final int TYPE_payl = Util.getIntegerCodeForString("payl"); + private static final int TYPE_sttg = Util.getIntegerCodeForString("sttg"); + private static final int TYPE_vttc = Util.getIntegerCodeForString("vttc"); + + private final ParsableByteArray sampleData; + private final WebvttCue.Builder builder; + + public Mp4WebvttDecoder() { + super("Mp4WebvttDecoder"); + sampleData = new ParsableByteArray(); + builder = new WebvttCue.Builder(); + } + + @Override + protected Mp4WebvttSubtitle decode(byte[] bytes, int length, boolean reset) + throws SubtitleDecoderException { + // Webvtt in Mp4 samples have boxes inside of them, so we have to do a traditional box parsing: + // first 4 bytes size and then 4 bytes type. + sampleData.reset(bytes, length); + List resultingCueList = new ArrayList<>(); + while (sampleData.bytesLeft() > 0) { + if (sampleData.bytesLeft() < BOX_HEADER_SIZE) { + throw new SubtitleDecoderException("Incomplete Mp4Webvtt Top Level box header found."); + } + int boxSize = sampleData.readInt(); + int boxType = sampleData.readInt(); + if (boxType == TYPE_vttc) { + resultingCueList.add(parseVttCueBox(sampleData, builder, boxSize - BOX_HEADER_SIZE)); + } else { + // Peers of the VTTCueBox are still not supported and are skipped. + sampleData.skipBytes(boxSize - BOX_HEADER_SIZE); + } + } + return new Mp4WebvttSubtitle(resultingCueList); + } + + private static Cue parseVttCueBox(ParsableByteArray sampleData, WebvttCue.Builder builder, + int remainingCueBoxBytes) throws SubtitleDecoderException { + builder.reset(); + while (remainingCueBoxBytes > 0) { + if (remainingCueBoxBytes < BOX_HEADER_SIZE) { + throw new SubtitleDecoderException("Incomplete vtt cue box header found."); + } + int boxSize = sampleData.readInt(); + int boxType = sampleData.readInt(); + remainingCueBoxBytes -= BOX_HEADER_SIZE; + int payloadLength = boxSize - BOX_HEADER_SIZE; + String boxPayload = new String(sampleData.data, sampleData.getPosition(), payloadLength); + sampleData.skipBytes(payloadLength); + remainingCueBoxBytes -= payloadLength; + if (boxType == TYPE_sttg) { + WebvttCueParser.parseCueSettingsList(boxPayload, builder); + } else if (boxType == TYPE_payl) { + WebvttCueParser.parseCueText(null, boxPayload.trim(), builder, + Collections.emptyList()); + } else { + // Other VTTCueBox children are still not supported and are ignored. + } + } + return builder.build(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java new file mode 100644 index 0000000..dda0b9c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.Collections; +import java.util.List; + +/** + * Representation of a Webvtt subtitle embedded in a MP4 container file. + */ +/* package */ final class Mp4WebvttSubtitle implements Subtitle { + + private final List cues; + + public Mp4WebvttSubtitle(List cueList) { + cues = Collections.unmodifiableList(cueList); + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + return timeUs < 0 ? 0 : C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return 1; + } + + @Override + public long getEventTime(int index) { + Assertions.checkArgument(index == 0); + return 0; + } + + @Override + public List getCues(long timeUs) { + return timeUs >= 0 ? cues : Collections.emptyList(); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCssStyle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCssStyle.java new file mode 100644 index 0000000..758a368 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCssStyle.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import android.graphics.Typeface; +import android.support.annotation.IntDef; +import android.text.Layout; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Style object of a Css style block in a Webvtt file. + * + * @see W3C specification - Apply + * CSS properties + */ +/* package */ final class WebvttCssStyle { + + public static final int UNSPECIFIED = -1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, + STYLE_BOLD_ITALIC}) + public @interface StyleFlags {} + public static final int STYLE_NORMAL = Typeface.NORMAL; + public static final int STYLE_BOLD = Typeface.BOLD; + public static final int STYLE_ITALIC = Typeface.ITALIC; + public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) + public @interface FontSizeUnit {} + public static final int FONT_SIZE_UNIT_PIXEL = 1; + public static final int FONT_SIZE_UNIT_EM = 2; + public static final int FONT_SIZE_UNIT_PERCENT = 3; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, OFF, ON}) + private @interface OptionalBoolean {} + private static final int OFF = 0; + private static final int ON = 1; + + // Selector properties. + private String targetId; + private String targetTag; + private List targetClasses; + private String targetVoice; + + // Style properties. + private String fontFamily; + private int fontColor; + private boolean hasFontColor; + private int backgroundColor; + private boolean hasBackgroundColor; + @OptionalBoolean private int linethrough; + @OptionalBoolean private int underline; + @OptionalBoolean private int bold; + @OptionalBoolean private int italic; + @FontSizeUnit private int fontSizeUnit; + private float fontSize; + private Layout.Alignment textAlign; + + public WebvttCssStyle() { + reset(); + } + + public void reset() { + targetId = ""; + targetTag = ""; + targetClasses = Collections.emptyList(); + targetVoice = ""; + fontFamily = null; + hasFontColor = false; + hasBackgroundColor = false; + linethrough = UNSPECIFIED; + underline = UNSPECIFIED; + bold = UNSPECIFIED; + italic = UNSPECIFIED; + fontSizeUnit = UNSPECIFIED; + textAlign = null; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public void setTargetTagName(String targetTag) { + this.targetTag = targetTag; + } + + public void setTargetClasses(String[] targetClasses) { + this.targetClasses = Arrays.asList(targetClasses); + } + + public void setTargetVoice(String targetVoice) { + this.targetVoice = targetVoice; + } + + /** + * Returns a value in a score system compliant with the CSS Specificity rules. + * + * @see CSS Cascading + * + * The score works as follows: + *

      + *
    • Id match adds 0x40000000 to the score. + *
    • Each class and voice match adds 4 to the score. + *
    • Tag matching adds 2 to the score. + *
    • Universal selector matching scores 1. + *
    + * + * @param id The id of the cue if present, {@code null} otherwise. + * @param tag Name of the tag, {@code null} if it refers to the entire cue. + * @param classes An array containing the classes the tag belongs to. Must not be null. + * @param voice Annotated voice if present, {@code null} otherwise. + * @return The score of the match, zero if there is no match. + */ + public int getSpecificityScore(String id, String tag, String[] classes, String voice) { + if (targetId.isEmpty() && targetTag.isEmpty() && targetClasses.isEmpty() + && targetVoice.isEmpty()) { + // The selector is universal. It matches with the minimum score if and only if the given + // element is a whole cue. + return tag.isEmpty() ? 1 : 0; + } + int score = 0; + score = updateScoreForMatch(score, targetId, id, 0x40000000); + score = updateScoreForMatch(score, targetTag, tag, 2); + score = updateScoreForMatch(score, targetVoice, voice, 4); + if (score == -1 || !Arrays.asList(classes).containsAll(targetClasses)) { + return 0; + } else { + score += targetClasses.size() * 4; + } + return score; + } + + /** + * Returns the style or {@link #UNSPECIFIED} when no style information is given. + * + * @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD} + * or {@link #STYLE_BOLD_ITALIC}. + */ + @StyleFlags public int getStyle() { + if (bold == UNSPECIFIED && italic == UNSPECIFIED) { + return UNSPECIFIED; + } + return (bold == ON ? STYLE_BOLD : STYLE_NORMAL) + | (italic == ON ? STYLE_ITALIC : STYLE_NORMAL); + } + + public boolean isLinethrough() { + return linethrough == ON; + } + + public WebvttCssStyle setLinethrough(boolean linethrough) { + this.linethrough = linethrough ? ON : OFF; + return this; + } + + public boolean isUnderline() { + return underline == ON; + } + + public WebvttCssStyle setUnderline(boolean underline) { + this.underline = underline ? ON : OFF; + return this; + } + public WebvttCssStyle setBold(boolean bold) { + this.bold = bold ? ON : OFF; + return this; + } + + public WebvttCssStyle setItalic(boolean italic) { + this.italic = italic ? ON : OFF; + return this; + } + + public String getFontFamily() { + return fontFamily; + } + + public WebvttCssStyle setFontFamily(String fontFamily) { + this.fontFamily = Util.toLowerInvariant(fontFamily); + return this; + } + + public int getFontColor() { + if (!hasFontColor) { + throw new IllegalStateException("Font color not defined"); + } + return fontColor; + } + + public WebvttCssStyle setFontColor(int color) { + this.fontColor = color; + hasFontColor = true; + return this; + } + + public boolean hasFontColor() { + return hasFontColor; + } + + public int getBackgroundColor() { + if (!hasBackgroundColor) { + throw new IllegalStateException("Background color not defined."); + } + return backgroundColor; + } + + public WebvttCssStyle setBackgroundColor(int backgroundColor) { + this.backgroundColor = backgroundColor; + hasBackgroundColor = true; + return this; + } + + public boolean hasBackgroundColor() { + return hasBackgroundColor; + } + + public Layout.Alignment getTextAlign() { + return textAlign; + } + + public WebvttCssStyle setTextAlign(Layout.Alignment textAlign) { + this.textAlign = textAlign; + return this; + } + + public WebvttCssStyle setFontSize(float fontSize) { + this.fontSize = fontSize; + return this; + } + + public WebvttCssStyle setFontSizeUnit(short unit) { + this.fontSizeUnit = unit; + return this; + } + + @FontSizeUnit public int getFontSizeUnit() { + return fontSizeUnit; + } + + public float getFontSize() { + return fontSize; + } + + public void cascadeFrom(WebvttCssStyle style) { + if (style.hasFontColor) { + setFontColor(style.fontColor); + } + if (style.bold != UNSPECIFIED) { + bold = style.bold; + } + if (style.italic != UNSPECIFIED) { + italic = style.italic; + } + if (style.fontFamily != null) { + fontFamily = style.fontFamily; + } + if (linethrough == UNSPECIFIED) { + linethrough = style.linethrough; + } + if (underline == UNSPECIFIED) { + underline = style.underline; + } + if (textAlign == null) { + textAlign = style.textAlign; + } + if (fontSizeUnit == UNSPECIFIED) { + fontSizeUnit = style.fontSizeUnit; + fontSize = style.fontSize; + } + if (style.hasBackgroundColor) { + setBackgroundColor(style.backgroundColor); + } + } + + private static int updateScoreForMatch(int currentScore, String target, String actual, + int score) { + if (target.isEmpty() || currentScore == -1) { + return currentScore; + } + return target.equals(actual) ? currentScore + score : -1; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCue.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCue.java new file mode 100644 index 0000000..3f99c24 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCue.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import android.text.Layout.Alignment; +import android.text.SpannableStringBuilder; +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; + +/** + * A representation of a WebVTT cue. + */ +/* package */ final class WebvttCue extends Cue { + + public final long startTime; + public final long endTime; + + public WebvttCue(CharSequence text) { + this(0, 0, text); + } + + public WebvttCue(long startTime, long endTime, CharSequence text) { + this(startTime, endTime, text, null, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, + Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); + } + + public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment, + float line, @Cue.LineType int lineType, @Cue.AnchorType int lineAnchor, float position, + @Cue.AnchorType int positionAnchor, float width) { + super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width); + this.startTime = startTime; + this.endTime = endTime; + } + + /** + * Returns whether or not this cue should be placed in the default position and rolled-up with + * the other "normal" cues. + * + * @return Whether this cue should be placed in the default position. + */ + public boolean isNormalCue() { + return (line == DIMEN_UNSET && position == DIMEN_UNSET); + } + + /** + * Builder for WebVTT cues. + */ + @SuppressWarnings("hiding") + public static final class Builder { + + private static final String TAG = "WebvttCueBuilder"; + + private long startTime; + private long endTime; + private SpannableStringBuilder text; + private Alignment textAlignment; + private float line; + private int lineType; + private int lineAnchor; + private float position; + private int positionAnchor; + private float width; + + // Initialization methods + + public Builder() { + reset(); + } + + public void reset() { + startTime = 0; + endTime = 0; + text = null; + textAlignment = null; + line = Cue.DIMEN_UNSET; + lineType = Cue.TYPE_UNSET; + lineAnchor = Cue.TYPE_UNSET; + position = Cue.DIMEN_UNSET; + positionAnchor = Cue.TYPE_UNSET; + width = Cue.DIMEN_UNSET; + } + + // Construction methods. + + public WebvttCue build() { + if (position != Cue.DIMEN_UNSET && positionAnchor == Cue.TYPE_UNSET) { + derivePositionAnchorFromAlignment(); + } + return new WebvttCue(startTime, endTime, text, textAlignment, line, lineType, lineAnchor, + position, positionAnchor, width); + } + + public Builder setStartTime(long time) { + startTime = time; + return this; + } + + public Builder setEndTime(long time) { + endTime = time; + return this; + } + + public Builder setText(SpannableStringBuilder aText) { + text = aText; + return this; + } + + public Builder setTextAlignment(Alignment textAlignment) { + this.textAlignment = textAlignment; + return this; + } + + public Builder setLine(float line) { + this.line = line; + return this; + } + + public Builder setLineType(int lineType) { + this.lineType = lineType; + return this; + } + + public Builder setLineAnchor(int lineAnchor) { + this.lineAnchor = lineAnchor; + return this; + } + + public Builder setPosition(float position) { + this.position = position; + return this; + } + + public Builder setPositionAnchor(int positionAnchor) { + this.positionAnchor = positionAnchor; + return this; + } + + public Builder setWidth(float width) { + this.width = width; + return this; + } + + private Builder derivePositionAnchorFromAlignment() { + if (textAlignment == null) { + positionAnchor = Cue.TYPE_UNSET; + } else { + switch (textAlignment) { + case ALIGN_NORMAL: + positionAnchor = Cue.ANCHOR_TYPE_START; + break; + case ALIGN_CENTER: + positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + break; + case ALIGN_OPPOSITE: + positionAnchor = Cue.ANCHOR_TYPE_END; + break; + default: + Log.w(TAG, "Unrecognized alignment: " + textAlignment); + positionAnchor = Cue.ANCHOR_TYPE_START; + break; + } + } + return this; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCueParser.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCueParser.java new file mode 100644 index 0000000..fa46ccb --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttCueParser.java @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import android.graphics.Typeface; +import android.support.annotation.NonNull; +import android.text.Layout.Alignment; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.AlignmentSpan; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; +import android.text.style.UnderlineSpan; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parser for WebVTT cues. (https://w3c.github.io/webvtt/#cues) + */ +/* package */ final class WebvttCueParser { + + public static final Pattern CUE_HEADER_PATTERN = Pattern + .compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$"); + + private static final Pattern CUE_SETTING_PATTERN = Pattern.compile("(\\S+?):(\\S+)"); + + private static final char CHAR_LESS_THAN = '<'; + private static final char CHAR_GREATER_THAN = '>'; + private static final char CHAR_SLASH = '/'; + private static final char CHAR_AMPERSAND = '&'; + private static final char CHAR_SEMI_COLON = ';'; + private static final char CHAR_SPACE = ' '; + + private static final String ENTITY_LESS_THAN = "lt"; + private static final String ENTITY_GREATER_THAN = "gt"; + private static final String ENTITY_AMPERSAND = "amp"; + private static final String ENTITY_NON_BREAK_SPACE = "nbsp"; + + private static final String TAG_BOLD = "b"; + private static final String TAG_ITALIC = "i"; + private static final String TAG_UNDERLINE = "u"; + private static final String TAG_CLASS = "c"; + private static final String TAG_VOICE = "v"; + private static final String TAG_LANG = "lang"; + + private static final int STYLE_BOLD = Typeface.BOLD; + private static final int STYLE_ITALIC = Typeface.ITALIC; + + private static final String TAG = "WebvttCueParser"; + + private final StringBuilder textBuilder; + + public WebvttCueParser() { + textBuilder = new StringBuilder(); + } + + /** + * Parses the next valid WebVTT cue in a parsable array, including timestamps, settings and text. + * + * @param webvttData Parsable WebVTT file data. + * @param builder Builder for WebVTT Cues. + * @param styles List of styles defined by the CSS style blocks preceeding the cues. + * @return Whether a valid Cue was found. + */ + /* package */ boolean parseCue(ParsableByteArray webvttData, WebvttCue.Builder builder, + List styles) { + String firstLine = webvttData.readLine(); + Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(firstLine); + if (cueHeaderMatcher.matches()) { + // We have found the timestamps in the first line. No id present. + return parseCue(null, cueHeaderMatcher, webvttData, builder, textBuilder, styles); + } else { + // The first line is not the timestamps, but could be the cue id. + String secondLine = webvttData.readLine(); + cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine); + if (cueHeaderMatcher.matches()) { + // We can do the rest of the parsing, including the id. + return parseCue(firstLine.trim(), cueHeaderMatcher, webvttData, builder, textBuilder, + styles); + } + } + return false; + } + + /** + * Parses a string containing a list of cue settings. + * + * @param cueSettingsList String containing the settings for a given cue. + * @param builder The {@link WebvttCue.Builder} where incremental construction takes place. + */ + /* package */ static void parseCueSettingsList(String cueSettingsList, + WebvttCue.Builder builder) { + // Parse the cue settings list. + Matcher cueSettingMatcher = CUE_SETTING_PATTERN.matcher(cueSettingsList); + while (cueSettingMatcher.find()) { + String name = cueSettingMatcher.group(1); + String value = cueSettingMatcher.group(2); + try { + if ("line".equals(name)) { + parseLineAttribute(value, builder); + } else if ("align".equals(name)) { + builder.setTextAlignment(parseTextAlignment(value)); + } else if ("position".equals(name)) { + parsePositionAttribute(value, builder); + } else if ("size".equals(name)) { + builder.setWidth(WebvttParserUtil.parsePercentage(value)); + } + } catch (NumberFormatException e) { + e.getStackTrace(); + } + } + } + + /** + * Parses the text payload of a WebVTT Cue and applies modifications on {@link WebvttCue.Builder}. + * + * @param id Id of the cue, {@code null} if it is not present. + * @param markup The markup text to be parsed. + * @param styles List of styles defined by the CSS style blocks preceeding the cues. + * @param builder Output builder. + */ + /* package */ static void parseCueText(String id, String markup, WebvttCue.Builder builder, + List styles) { + SpannableStringBuilder spannedText = new SpannableStringBuilder(); + Stack startTagStack = new Stack<>(); + List scratchStyleMatches = new ArrayList<>(); + int pos = 0; + while (pos < markup.length()) { + char curr = markup.charAt(pos); + switch (curr) { + case CHAR_LESS_THAN: + if (pos + 1 >= markup.length()) { + pos++; + break; // avoid ArrayOutOfBoundsException + } + int ltPos = pos; + boolean isClosingTag = markup.charAt(ltPos + 1) == CHAR_SLASH; + pos = findEndOfTag(markup, ltPos + 1); + boolean isVoidTag = markup.charAt(pos - 2) == CHAR_SLASH; + String fullTagExpression = markup.substring(ltPos + (isClosingTag ? 2 : 1), + isVoidTag ? pos - 2 : pos - 1); + String tagName = getTagName(fullTagExpression); + if (tagName == null || !isSupportedTag(tagName)) { + continue; + } + if (isClosingTag) { + StartTag startTag; + do { + if (startTagStack.isEmpty()) { + break; + } + startTag = startTagStack.pop(); + applySpansForTag(id, startTag, spannedText, styles, scratchStyleMatches); + } while(!startTag.name.equals(tagName)); + } else if (!isVoidTag) { + startTagStack.push(StartTag.buildStartTag(fullTagExpression, spannedText.length())); + } + break; + case CHAR_AMPERSAND: + int semiColonEndIndex = markup.indexOf(CHAR_SEMI_COLON, pos + 1); + int spaceEndIndex = markup.indexOf(CHAR_SPACE, pos + 1); + int entityEndIndex = semiColonEndIndex == -1 ? spaceEndIndex + : (spaceEndIndex == -1 ? semiColonEndIndex + : Math.min(semiColonEndIndex, spaceEndIndex)); + if (entityEndIndex != -1) { + applyEntity(markup.substring(pos + 1, entityEndIndex), spannedText); + if (entityEndIndex == spaceEndIndex) { + spannedText.append(" "); + } + pos = entityEndIndex + 1; + } else { + spannedText.append(curr); + pos++; + } + break; + default: + spannedText.append(curr); + pos++; + break; + } + } + // apply unclosed tags + while (!startTagStack.isEmpty()) { + applySpansForTag(id, startTagStack.pop(), spannedText, styles, scratchStyleMatches); + } + applySpansForTag(id, StartTag.buildWholeCueVirtualTag(), spannedText, styles, + scratchStyleMatches); + builder.setText(spannedText); + } + + private static boolean parseCue(String id, Matcher cueHeaderMatcher, ParsableByteArray webvttData, + WebvttCue.Builder builder, StringBuilder textBuilder, List styles) { + try { + // Parse the cue start and end times. + builder.setStartTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1))) + .setEndTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(2))); + } catch (NumberFormatException e) { + return false; + } + + parseCueSettingsList(cueHeaderMatcher.group(3), builder); + + // Parse the cue text. + textBuilder.setLength(0); + String line; + while ((line = webvttData.readLine()) != null && !line.isEmpty()) { + if (textBuilder.length() > 0) { + textBuilder.append("\n"); + } + textBuilder.append(line.trim()); + } + parseCueText(id, textBuilder.toString(), builder, styles); + return true; + } + + // Internal methods + + private static void parseLineAttribute(String s, WebvttCue.Builder builder) + throws NumberFormatException { + int commaIndex = s.indexOf(','); + if (commaIndex != -1) { + builder.setLineAnchor(parsePositionAnchor(s.substring(commaIndex + 1))); + s = s.substring(0, commaIndex); + } else { + builder.setLineAnchor(Cue.TYPE_UNSET); + } + if (s.endsWith("%")) { + builder.setLine(WebvttParserUtil.parsePercentage(s)).setLineType(Cue.LINE_TYPE_FRACTION); + } else { + int lineNumber = Integer.parseInt(s); + if (lineNumber < 0) { + // WebVTT defines line -1 as last visible row when lineAnchor is ANCHOR_TYPE_START, where-as + // Cue defines it to be the first row that's not visible. + lineNumber--; + } + builder.setLine(lineNumber).setLineType(Cue.LINE_TYPE_NUMBER); + } + } + + private static void parsePositionAttribute(String s, WebvttCue.Builder builder) + throws NumberFormatException { + int commaIndex = s.indexOf(','); + if (commaIndex != -1) { + builder.setPositionAnchor(parsePositionAnchor(s.substring(commaIndex + 1))); + s = s.substring(0, commaIndex); + } else { + builder.setPositionAnchor(Cue.TYPE_UNSET); + } + builder.setPosition(WebvttParserUtil.parsePercentage(s)); + } + + private static int parsePositionAnchor(String s) { + switch (s) { + case "start": + return Cue.ANCHOR_TYPE_START; + case "center": + case "middle": + return Cue.ANCHOR_TYPE_MIDDLE; + case "end": + return Cue.ANCHOR_TYPE_END; + default: + return Cue.TYPE_UNSET; + } + } + + private static Alignment parseTextAlignment(String s) { + switch (s) { + case "start": + case "left": + return Alignment.ALIGN_NORMAL; + case "center": + case "middle": + return Alignment.ALIGN_CENTER; + case "end": + case "right": + return Alignment.ALIGN_OPPOSITE; + default: + return null; + } + } + + /** + * Find end of tag (>). The position returned is the position of the > plus one (exclusive). + * + * @param markup The WebVTT cue markup to be parsed. + * @param startPos The position from where to start searching for the end of tag. + * @return The position of the end of tag plus 1 (one). + */ + private static int findEndOfTag(String markup, int startPos) { + int index = markup.indexOf(CHAR_GREATER_THAN, startPos); + return index == -1 ? markup.length() : index + 1; + } + + private static void applyEntity(String entity, SpannableStringBuilder spannedText) { + switch (entity) { + case ENTITY_LESS_THAN: + spannedText.append('<'); + break; + case ENTITY_GREATER_THAN: + spannedText.append('>'); + break; + case ENTITY_NON_BREAK_SPACE: + spannedText.append(' '); + break; + case ENTITY_AMPERSAND: + spannedText.append('&'); + break; + default: + break; + } + } + + private static boolean isSupportedTag(String tagName) { + switch (tagName) { + case TAG_BOLD: + case TAG_CLASS: + case TAG_ITALIC: + case TAG_LANG: + case TAG_UNDERLINE: + case TAG_VOICE: + return true; + default: + return false; + } + } + + private static void applySpansForTag(String cueId, StartTag startTag, SpannableStringBuilder text, + List styles, List scratchStyleMatches) { + int start = startTag.position; + int end = text.length(); + switch(startTag.name) { + case TAG_BOLD: + text.setSpan(new StyleSpan(STYLE_BOLD), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TAG_ITALIC: + text.setSpan(new StyleSpan(STYLE_ITALIC), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TAG_UNDERLINE: + text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TAG_CLASS: + case TAG_LANG: + case TAG_VOICE: + case "": // Case of the "whole cue" virtual tag. + break; + default: + return; + } + scratchStyleMatches.clear(); + getApplicableStyles(styles, cueId, startTag, scratchStyleMatches); + int styleMatchesCount = scratchStyleMatches.size(); + for (int i = 0; i < styleMatchesCount; i++) { + applyStyleToText(text, scratchStyleMatches.get(i).style, start, end); + } + } + + private static void applyStyleToText(SpannableStringBuilder spannedText, WebvttCssStyle style, + int start, int end) { + if (style == null) { + return; + } + if (style.getStyle() != WebvttCssStyle.UNSPECIFIED) { + spannedText.setSpan(new StyleSpan(style.getStyle()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.isLinethrough()) { + spannedText.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.isUnderline()) { + spannedText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.hasFontColor()) { + spannedText.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.hasBackgroundColor()) { + spannedText.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.getFontFamily() != null) { + spannedText.setSpan(new TypefaceSpan(style.getFontFamily()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.getTextAlign() != null) { + spannedText.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + switch (style.getFontSizeUnit()) { + case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL: + spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.FONT_SIZE_UNIT_EM: + spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT: + spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.UNSPECIFIED: + // Do nothing. + break; + } + } + + /** + * Returns the tag name for the given tag contents. + * + * @param tagExpression Characters between &lt: and &gt; of a start or end tag. + * @return The name of tag. + */ + private static String getTagName(String tagExpression) { + tagExpression = tagExpression.trim(); + if (tagExpression.isEmpty()) { + return null; + } + return tagExpression.split("[ \\.]")[0]; + } + + private static void getApplicableStyles(List declaredStyles, String id, + StartTag tag, List output) { + int styleCount = declaredStyles.size(); + for (int i = 0; i < styleCount; i++) { + WebvttCssStyle style = declaredStyles.get(i); + int score = style.getSpecificityScore(id, tag.name, tag.classes, tag.voice); + if (score > 0) { + output.add(new StyleMatch(score, style)); + } + } + Collections.sort(output); + } + + private static final class StyleMatch implements Comparable { + + public final int score; + public final WebvttCssStyle style; + + public StyleMatch(int score, WebvttCssStyle style) { + this.score = score; + this.style = style; + } + + @Override + public int compareTo(@NonNull StyleMatch another) { + return this.score - another.score; + } + + } + + private static final class StartTag { + + private static final String[] NO_CLASSES = new String[0]; + + public final String name; + public final int position; + public final String voice; + public final String[] classes; + + private StartTag(String name, int position, String voice, String[] classes) { + this.position = position; + this.name = name; + this.voice = voice; + this.classes = classes; + } + + public static StartTag buildStartTag(String fullTagExpression, int position) { + fullTagExpression = fullTagExpression.trim(); + if (fullTagExpression.isEmpty()) { + return null; + } + int voiceStartIndex = fullTagExpression.indexOf(" "); + String voice; + if (voiceStartIndex == -1) { + voice = ""; + } else { + voice = fullTagExpression.substring(voiceStartIndex).trim(); + fullTagExpression = fullTagExpression.substring(0, voiceStartIndex); + } + String[] nameAndClasses = fullTagExpression.split("\\."); + String name = nameAndClasses[0]; + String[] classes; + if (nameAndClasses.length > 1) { + classes = Arrays.copyOfRange(nameAndClasses, 1, nameAndClasses.length); + } else { + classes = NO_CLASSES; + } + return new StartTag(name, position, voice, classes); + } + + public static StartTag buildWholeCueVirtualTag() { + return new StartTag("", 0, "", new String[0]); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttDecoder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttDecoder.java new file mode 100644 index 0000000..f2fd724 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttDecoder.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SimpleSubtitleDecoder; +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoderException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link SimpleSubtitleDecoder} for WebVTT. + *

    + * @see WebVTT specification + */ +public final class WebvttDecoder extends SimpleSubtitleDecoder { + + private static final int EVENT_NONE = -1; + private static final int EVENT_END_OF_FILE = 0; + private static final int EVENT_COMMENT = 1; + private static final int EVENT_STYLE_BLOCK = 2; + private static final int EVENT_CUE = 3; + + private static final String COMMENT_START = "NOTE"; + private static final String STYLE_START = "STYLE"; + + private final WebvttCueParser cueParser; + private final ParsableByteArray parsableWebvttData; + private final WebvttCue.Builder webvttCueBuilder; + private final CssParser cssParser; + private final List definedStyles; + + public WebvttDecoder() { + super("WebvttDecoder"); + cueParser = new WebvttCueParser(); + parsableWebvttData = new ParsableByteArray(); + webvttCueBuilder = new WebvttCue.Builder(); + cssParser = new CssParser(); + definedStyles = new ArrayList<>(); + } + + @Override + protected WebvttSubtitle decode(byte[] bytes, int length, boolean reset) + throws SubtitleDecoderException { + parsableWebvttData.reset(bytes, length); + // Initialization for consistent starting state. + webvttCueBuilder.reset(); + definedStyles.clear(); + + // Validate the first line of the header, and skip the remainder. + WebvttParserUtil.validateWebvttHeaderLine(parsableWebvttData); + while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {} + + int event; + ArrayList subtitles = new ArrayList<>(); + while ((event = getNextEvent(parsableWebvttData)) != EVENT_END_OF_FILE) { + if (event == EVENT_COMMENT) { + skipComment(parsableWebvttData); + } else if (event == EVENT_STYLE_BLOCK) { + if (!subtitles.isEmpty()) { + throw new SubtitleDecoderException("A style block was found after the first cue."); + } + parsableWebvttData.readLine(); // Consume the "STYLE" header. + WebvttCssStyle styleBlock = cssParser.parseBlock(parsableWebvttData); + if (styleBlock != null) { + definedStyles.add(styleBlock); + } + } else if (event == EVENT_CUE) { + if (cueParser.parseCue(parsableWebvttData, webvttCueBuilder, definedStyles)) { + subtitles.add(webvttCueBuilder.build()); + webvttCueBuilder.reset(); + } + } + } + return new WebvttSubtitle(subtitles); + } + + /** + * Positions the input right before the next event, and returns the kind of event found. Does not + * consume any data from such event, if any. + * + * @return The kind of event found. + */ + private static int getNextEvent(ParsableByteArray parsableWebvttData) { + int foundEvent = EVENT_NONE; + int currentInputPosition = 0; + while (foundEvent == EVENT_NONE) { + currentInputPosition = parsableWebvttData.getPosition(); + String line = parsableWebvttData.readLine(); + if (line == null) { + foundEvent = EVENT_END_OF_FILE; + } else if (STYLE_START.equals(line)) { + foundEvent = EVENT_STYLE_BLOCK; + } else if (COMMENT_START.startsWith(line)) { + foundEvent = EVENT_COMMENT; + } else { + foundEvent = EVENT_CUE; + } + } + parsableWebvttData.setPosition(currentInputPosition); + return foundEvent; + } + + private static void skipComment(ParsableByteArray parsableWebvttData) { + while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {} + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttParserUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttParserUtil.java new file mode 100644 index 0000000..4c5d1ed --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttParserUtil.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import com.tangxiaolv.telegramgallery.exoplayer2.text.SubtitleDecoderException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility methods for parsing WebVTT data. + */ +public final class WebvttParserUtil { + + private static final Pattern COMMENT = Pattern.compile("^NOTE((\u0020|\u0009).*)?$"); + private static final Pattern HEADER = Pattern.compile("^\uFEFF?WEBVTT((\u0020|\u0009).*)?$"); + + private WebvttParserUtil() {} + + /** + * Reads and validates the first line of a WebVTT file. + * + * @param input The input from which the line should be read. + * @throws SubtitleDecoderException If the line isn't the start of a valid WebVTT file. + */ + public static void validateWebvttHeaderLine(ParsableByteArray input) + throws SubtitleDecoderException { + String line = input.readLine(); + if (line == null || !HEADER.matcher(line).matches()) { + throw new SubtitleDecoderException("Expected WEBVTT. Got " + line); + } + } + + /** + * Parses a WebVTT timestamp. + * + * @param timestamp The timestamp string. + * @return The parsed timestamp in microseconds. + * @throws NumberFormatException If the timestamp could not be parsed. + */ + public static long parseTimestampUs(String timestamp) throws NumberFormatException { + long value = 0; + String[] parts = timestamp.split("\\.", 2); + String[] subparts = parts[0].split(":"); + for (String subpart : subparts) { + value = value * 60 + Long.parseLong(subpart); + } + return (value * 1000 + Long.parseLong(parts[1])) * 1000; + } + + /** + * Parses a percentage string. + * + * @param s The percentage string. + * @return The parsed value, where 1.0 represents 100%. + * @throws NumberFormatException If the percentage could not be parsed. + */ + public static float parsePercentage(String s) throws NumberFormatException { + if (!s.endsWith("%")) { + throw new NumberFormatException("Percentages must end with %"); + } + return Float.parseFloat(s.substring(0, s.length() - 1)) / 100; + } + + /** + * Reads lines up to and including the next WebVTT cue header. + * + * @param input The input from which lines should be read. + * @return A {@link Matcher} for the WebVTT cue header, or null if the end of the input was + * reached without a cue header being found. In the case that a cue header is found, groups 1, + * 2 and 3 of the returned matcher contain the start time, end time and settings list. + */ + public static Matcher findNextCueHeader(ParsableByteArray input) { + String line; + while ((line = input.readLine()) != null) { + if (COMMENT.matcher(line).matches()) { + // Skip until the end of the comment block. + while ((line = input.readLine()) != null && !line.isEmpty()) {} + } else { + Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(line); + if (cueHeaderMatcher.matches()) { + return cueHeaderMatcher; + } + } + } + return null; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttSubtitle.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttSubtitle.java new file mode 100644 index 0000000..c848fed --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/text/webvtt/WebvttSubtitle.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.text.webvtt; + +import android.text.SpannableStringBuilder; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Subtitle; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A representation of a WebVTT subtitle. + */ +/* package */ final class WebvttSubtitle implements Subtitle { + + private final List cues; + private final int numCues; + private final long[] cueTimesUs; + private final long[] sortedCueTimesUs; + + /** + * @param cues A list of the cues in this subtitle. + */ + public WebvttSubtitle(List cues) { + this.cues = cues; + numCues = cues.size(); + cueTimesUs = new long[2 * numCues]; + for (int cueIndex = 0; cueIndex < numCues; cueIndex++) { + WebvttCue cue = cues.get(cueIndex); + int arrayIndex = cueIndex * 2; + cueTimesUs[arrayIndex] = cue.startTime; + cueTimesUs[arrayIndex + 1] = cue.endTime; + } + sortedCueTimesUs = Arrays.copyOf(cueTimesUs, cueTimesUs.length); + Arrays.sort(sortedCueTimesUs); + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + int index = Util.binarySearchCeil(sortedCueTimesUs, timeUs, false, false); + return index < sortedCueTimesUs.length ? index : C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return sortedCueTimesUs.length; + } + + @Override + public long getEventTime(int index) { + Assertions.checkArgument(index >= 0); + Assertions.checkArgument(index < sortedCueTimesUs.length); + return sortedCueTimesUs[index]; + } + + @Override + public List getCues(long timeUs) { + ArrayList list = null; + WebvttCue firstNormalCue = null; + SpannableStringBuilder normalCueTextBuilder = null; + + for (int i = 0; i < numCues; i++) { + if ((cueTimesUs[i * 2] <= timeUs) && (timeUs < cueTimesUs[i * 2 + 1])) { + if (list == null) { + list = new ArrayList<>(); + } + WebvttCue cue = cues.get(i); + if (cue.isNormalCue()) { + // we want to merge all of the normal cues into a single cue to ensure they are drawn + // correctly (i.e. don't overlap) and to emulate roll-up, but only if there are multiple + // normal cues, otherwise we can just append the single normal cue + if (firstNormalCue == null) { + firstNormalCue = cue; + } else if (normalCueTextBuilder == null) { + normalCueTextBuilder = new SpannableStringBuilder(); + normalCueTextBuilder.append(firstNormalCue.text).append("\n").append(cue.text); + } else { + normalCueTextBuilder.append("\n").append(cue.text); + } + } else { + list.add(cue); + } + } + } + if (normalCueTextBuilder != null) { + // there were multiple normal cues, so create a new cue with all of the text + list.add(new WebvttCue(normalCueTextBuilder)); + } else if (firstNormalCue != null) { + // there was only a single normal cue, so just add it to the list + list.add(firstNormalCue); + } + + if (list != null) { + return list; + } else { + return Collections.emptyList(); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/AdaptiveTrackSelection.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/AdaptiveTrackSelection.java new file mode 100644 index 0000000..5e4bb47 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/AdaptiveTrackSelection.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.MediaChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.BandwidthMeter; +import java.util.List; + +/** + * A bandwidth based adaptive {@link TrackSelection}, whose selected track is updated to be the one + * of highest quality given the current network conditions and the state of the buffer. + */ +public class AdaptiveTrackSelection extends BaseTrackSelection { + + /** + * Factory for {@link AdaptiveTrackSelection} instances. + */ + public static final class Factory implements TrackSelection.Factory { + + private final BandwidthMeter bandwidthMeter; + private final int maxInitialBitrate; + private final int minDurationForQualityIncreaseMs; + private final int maxDurationForQualityDecreaseMs; + private final int minDurationToRetainAfterDiscardMs; + private final float bandwidthFraction; + + /** + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + */ + public Factory(BandwidthMeter bandwidthMeter) { + this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE, + DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, + DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, + DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION); + } + + /** + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + * @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed + * when a bandwidth estimate is unavailable. + * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for + * the selected track to switch to one of higher quality. + * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for + * the selected track to switch to one of lower quality. + * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher + * quality, the selection may indicate that media already buffered at the lower quality can + * be discarded to speed up the switch. This is the minimum duration of media that must be + * retained at the lower quality. + * @param bandwidthFraction The fraction of the available bandwidth that the selection should + * consider available for use. Setting to a value less than 1 is recommended to account + * for inaccuracies in the bandwidth estimator. + */ + public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate, + int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs, + int minDurationToRetainAfterDiscardMs, float bandwidthFraction) { + this.bandwidthMeter = bandwidthMeter; + this.maxInitialBitrate = maxInitialBitrate; + this.minDurationForQualityIncreaseMs = minDurationForQualityIncreaseMs; + this.maxDurationForQualityDecreaseMs = maxDurationForQualityDecreaseMs; + this.minDurationToRetainAfterDiscardMs = minDurationToRetainAfterDiscardMs; + this.bandwidthFraction = bandwidthFraction; + } + + @Override + public AdaptiveTrackSelection createTrackSelection(TrackGroup group, int... tracks) { + return new AdaptiveTrackSelection(group, tracks, bandwidthMeter, maxInitialBitrate, + minDurationForQualityIncreaseMs, maxDurationForQualityDecreaseMs, + minDurationToRetainAfterDiscardMs, bandwidthFraction); + } + + } + + public static final int DEFAULT_MAX_INITIAL_BITRATE = 800000; + public static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000; + public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000; + public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000; + public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f; + + private final BandwidthMeter bandwidthMeter; + private final int maxInitialBitrate; + private final long minDurationForQualityIncreaseUs; + private final long maxDurationForQualityDecreaseUs; + private final long minDurationToRetainAfterDiscardUs; + private final float bandwidthFraction; + + private int selectedIndex; + private int reason; + + /** + * @param group The {@link TrackGroup}. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * empty. May be in any order. + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + */ + public AdaptiveTrackSelection(TrackGroup group, int[] tracks, + BandwidthMeter bandwidthMeter) { + this (group, tracks, bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE, + DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, + DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, + DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION); + } + + /** + * @param group The {@link TrackGroup}. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * empty. May be in any order. + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + * @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed when a + * bandwidth estimate is unavailable. + * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for the + * selected track to switch to one of higher quality. + * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for the + * selected track to switch to one of lower quality. + * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher + * quality, the selection may indicate that media already buffered at the lower quality can + * be discarded to speed up the switch. This is the minimum duration of media that must be + * retained at the lower quality. + * @param bandwidthFraction The fraction of the available bandwidth that the selection should + * consider available for use. Setting to a value less than 1 is recommended to account + * for inaccuracies in the bandwidth estimator. + */ + public AdaptiveTrackSelection(TrackGroup group, int[] tracks, BandwidthMeter bandwidthMeter, + int maxInitialBitrate, long minDurationForQualityIncreaseMs, + long maxDurationForQualityDecreaseMs, long minDurationToRetainAfterDiscardMs, + float bandwidthFraction) { + super(group, tracks); + this.bandwidthMeter = bandwidthMeter; + this.maxInitialBitrate = maxInitialBitrate; + this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L; + this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L; + this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L; + this.bandwidthFraction = bandwidthFraction; + selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE); + reason = C.SELECTION_REASON_INITIAL; + } + + @Override + public void updateSelectedTrack(long bufferedDurationUs) { + long nowMs = SystemClock.elapsedRealtime(); + // Get the current and ideal selections. + int currentSelectedIndex = selectedIndex; + Format currentFormat = getSelectedFormat(); + int idealSelectedIndex = determineIdealSelectedIndex(nowMs); + Format idealFormat = getFormat(idealSelectedIndex); + // Assume we can switch to the ideal selection. + selectedIndex = idealSelectedIndex; + // Revert back to the current selection if conditions are not suitable for switching. + if (currentFormat != null && !isBlacklisted(selectedIndex, nowMs)) { + if (idealFormat.bitrate > currentFormat.bitrate + && bufferedDurationUs < minDurationForQualityIncreaseUs) { + // The ideal track is a higher quality, but we have insufficient buffer to safely switch + // up. Defer switching up for now. + selectedIndex = currentSelectedIndex; + } else if (idealFormat.bitrate < currentFormat.bitrate + && bufferedDurationUs >= maxDurationForQualityDecreaseUs) { + // The ideal track is a lower quality, but we have sufficient buffer to defer switching + // down for now. + selectedIndex = currentSelectedIndex; + } + } + // If we adapted, update the trigger. + if (selectedIndex != currentSelectedIndex) { + reason = C.SELECTION_REASON_ADAPTIVE; + } + } + + @Override + public int getSelectedIndex() { + return selectedIndex; + } + + @Override + public int getSelectionReason() { + return reason; + } + + @Override + public Object getSelectionData() { + return null; + } + + @Override + public int evaluateQueueSize(long playbackPositionUs, List queue) { + if (queue.isEmpty()) { + return 0; + } + int queueSize = queue.size(); + long bufferedDurationUs = queue.get(queueSize - 1).endTimeUs - playbackPositionUs; + if (bufferedDurationUs < minDurationToRetainAfterDiscardUs) { + return queueSize; + } + int idealSelectedIndex = determineIdealSelectedIndex(SystemClock.elapsedRealtime()); + Format idealFormat = getFormat(idealSelectedIndex); + // If the chunks contain video, discard from the first SD chunk beyond + // minDurationToRetainAfterDiscardUs whose resolution and bitrate are both lower than the ideal + // track. + for (int i = 0; i < queueSize; i++) { + MediaChunk chunk = queue.get(i); + Format format = chunk.trackFormat; + long durationBeforeThisChunkUs = chunk.startTimeUs - playbackPositionUs; + if (durationBeforeThisChunkUs >= minDurationToRetainAfterDiscardUs + && format.bitrate < idealFormat.bitrate + && format.height != Format.NO_VALUE && format.height < 720 + && format.width != Format.NO_VALUE && format.width < 1280 + && format.height < idealFormat.height) { + return i; + } + } + return queueSize; + } + + /** + * Computes the ideal selected index ignoring buffer health. + * + * @param nowMs The current time in the timebase of {@link SystemClock#elapsedRealtime()}, or + * {@link Long#MIN_VALUE} to ignore blacklisting. + */ + private int determineIdealSelectedIndex(long nowMs) { + long bitrateEstimate = bandwidthMeter.getBitrateEstimate(); + long effectiveBitrate = bitrateEstimate == BandwidthMeter.NO_ESTIMATE + ? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction); + int lowestBitrateNonBlacklistedIndex = 0; + for (int i = 0; i < length; i++) { + if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) { + Format format = getFormat(i); + if (format.bitrate <= effectiveBitrate) { + return i; + } else { + lowestBitrateNonBlacklistedIndex = i; + } + } + } + return lowestBitrateNonBlacklistedIndex; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/BaseTrackSelection.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/BaseTrackSelection.java new file mode 100644 index 0000000..50913e4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/BaseTrackSelection.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.MediaChunk; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * An abstract base class suitable for most {@link TrackSelection} implementations. + */ +public abstract class BaseTrackSelection implements TrackSelection { + + /** + * The selected {@link TrackGroup}. + */ + protected final TrackGroup group; + /** + * The number of selected tracks within the {@link TrackGroup}. Always greater than zero. + */ + protected final int length; + /** + * The indices of the selected tracks in {@link #group}, in order of decreasing bandwidth. + */ + protected final int[] tracks; + + /** + * The {@link Format}s of the selected tracks, in order of decreasing bandwidth. + */ + private final Format[] formats; + /** + * Selected track blacklist timestamps, in order of decreasing bandwidth. + */ + private final long[] blacklistUntilTimes; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + */ + public BaseTrackSelection(TrackGroup group, int... tracks) { + Assertions.checkState(tracks.length > 0); + this.group = Assertions.checkNotNull(group); + this.length = tracks.length; + // Set the formats, sorted in order of decreasing bandwidth. + formats = new Format[length]; + for (int i = 0; i < tracks.length; i++) { + formats[i] = group.getFormat(tracks[i]); + } + Arrays.sort(formats, new DecreasingBandwidthComparator()); + // Set the format indices in the same order. + this.tracks = new int[length]; + for (int i = 0; i < length; i++) { + this.tracks[i] = group.indexOf(formats[i]); + } + blacklistUntilTimes = new long[length]; + } + + @Override + public final TrackGroup getTrackGroup() { + return group; + } + + @Override + public final int length() { + return tracks.length; + } + + @Override + public final Format getFormat(int index) { + return formats[index]; + } + + @Override + public final int getIndexInTrackGroup(int index) { + return tracks[index]; + } + + @Override + public final int indexOf(Format format) { + for (int i = 0; i < length; i++) { + if (formats[i] == format) { + return i; + } + } + return C.INDEX_UNSET; + } + + @Override + public final int indexOf(int indexInTrackGroup) { + for (int i = 0; i < length; i++) { + if (tracks[i] == indexInTrackGroup) { + return i; + } + } + return C.INDEX_UNSET; + } + + @Override + public final Format getSelectedFormat() { + return formats[getSelectedIndex()]; + } + + @Override + public final int getSelectedIndexInTrackGroup() { + return tracks[getSelectedIndex()]; + } + + @Override + public int evaluateQueueSize(long playbackPositionUs, List queue) { + return queue.size(); + } + + @Override + public final boolean blacklist(int index, long blacklistDurationMs) { + long nowMs = SystemClock.elapsedRealtime(); + boolean canBlacklist = isBlacklisted(index, nowMs); + for (int i = 0; i < length && !canBlacklist; i++) { + canBlacklist = i != index && !isBlacklisted(i, nowMs); + } + if (!canBlacklist) { + return false; + } + blacklistUntilTimes[index] = Math.max(blacklistUntilTimes[index], nowMs + blacklistDurationMs); + return true; + } + + /** + * Returns whether the track at the specified index in the selection is blacklisted. + * + * @param index The index of the track in the selection. + * @param nowMs The current time in the timebase of {@link SystemClock#elapsedRealtime()}. + */ + protected final boolean isBlacklisted(int index, long nowMs) { + return blacklistUntilTimes[index] > nowMs; + } + + // Object overrides. + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = 31 * System.identityHashCode(group) + Arrays.hashCode(tracks); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BaseTrackSelection other = (BaseTrackSelection) obj; + return group == other.group && Arrays.equals(tracks, other.tracks); + } + + /** + * Sorts {@link Format} objects in order of decreasing bandwidth. + */ + private static final class DecreasingBandwidthComparator implements Comparator { + + @Override + public int compare(Format a, Format b) { + return b.bitrate - a.bitrate; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/DefaultTrackSelector.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/DefaultTrackSelector.java new file mode 100644 index 0000000..a11b9ff --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/DefaultTrackSelector.java @@ -0,0 +1,979 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import android.content.Context; +import android.graphics.Point; +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.RendererCapabilities; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A {@link MappingTrackSelector} that allows configuration of common parameters. It is safe to call + * the methods of this class from the application thread. See {@link Parameters#Parameters()} for + * default selection parameters. + */ +public class DefaultTrackSelector extends MappingTrackSelector { + + /** + * Holder for available configurations for the {@link DefaultTrackSelector}. + */ + public static final class Parameters { + + // Audio. + public final String preferredAudioLanguage; + + // Text. + public final String preferredTextLanguage; + + // Video. + public final boolean allowMixedMimeAdaptiveness; + public final boolean allowNonSeamlessAdaptiveness; + public final int maxVideoWidth; + public final int maxVideoHeight; + public final int maxVideoBitrate; + public final boolean exceedVideoConstraintsIfNecessary; + public final boolean exceedRendererCapabilitiesIfNecessary; + public final int viewportWidth; + public final int viewportHeight; + public final boolean orientationMayChange; + + /** + * Constructor with default selection parameters: + *

      + *
    • No preferred audio language is set.
    • + *
    • No preferred text language is set.
    • + *
    • Adaptation between different mime types is not allowed.
    • + *
    • Non seamless adaptation is allowed.
    • + *
    • No max limit for video width/height.
    • + *
    • No max video bitrate.
    • + *
    • Video constraints are exceeded if no supported selection can be made otherwise.
    • + *
    • Renderer capabilities are exceeded if no supported selection can be made.
    • + *
    • No viewport width/height constraints are set.
    • + *
    + */ + public Parameters() { + this(null, null, false, true, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, true, + true, Integer.MAX_VALUE, Integer.MAX_VALUE, true); + } + + /** + * @param preferredAudioLanguage The preferred language for audio, as well as for forced text + * tracks as defined by RFC 5646. {@code null} to select the default track, or first track + * if there's no default. + * @param preferredTextLanguage The preferred language for text tracks as defined by RFC 5646. + * {@code null} to select the default track, or first track if there's no default. + * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. + * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. + * @param maxVideoWidth Maximum allowed video width. + * @param maxVideoHeight Maximum allowed video height. + * @param maxVideoBitrate Maximum allowed video bitrate. + * @param exceedVideoConstraintsIfNecessary Whether to exceed video constraints when no + * selection can be made otherwise. + * @param exceedRendererCapabilitiesIfNecessary Whether to exceed renderer capabilities when no + * selection can be made otherwise. + * @param viewportWidth Viewport width in pixels. + * @param viewportHeight Viewport height in pixels. + * @param orientationMayChange Whether orientation may change during playback. + */ + public Parameters(String preferredAudioLanguage, String preferredTextLanguage, + boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness, + int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, + boolean exceedVideoConstraintsIfNecessary, boolean exceedRendererCapabilitiesIfNecessary, + int viewportWidth, int viewportHeight, boolean orientationMayChange) { + this.preferredAudioLanguage = preferredAudioLanguage; + this.preferredTextLanguage = preferredTextLanguage; + this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; + this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; + this.maxVideoWidth = maxVideoWidth; + this.maxVideoHeight = maxVideoHeight; + this.maxVideoBitrate = maxVideoBitrate; + this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; + this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; + this.viewportWidth = viewportWidth; + this.viewportHeight = viewportHeight; + this.orientationMayChange = orientationMayChange; + } + + /** + * Returns a {@link Parameters} instance with the provided preferred language for audio and + * forced text tracks. + * + * @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to + * select the default track, or first track if there's no default. + * @return A {@link Parameters} instance with the provided preferred language for audio and + * forced text tracks. + */ + public Parameters withPreferredAudioLanguage(String preferredAudioLanguage) { + preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); + if (TextUtils.equals(preferredAudioLanguage, this.preferredAudioLanguage)) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided preferred language for text tracks. + * + * @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to + * select the default track, or no track if there's no default. + * @return A {@link Parameters} instance with the provided preferred language for text tracks. + */ + public Parameters withPreferredTextLanguage(String preferredTextLanguage) { + preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); + if (TextUtils.equals(preferredTextLanguage, this.preferredTextLanguage)) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided mixed mime adaptiveness allowance. + * + * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. + * @return A {@link Parameters} instance with the provided mixed mime adaptiveness allowance. + */ + public Parameters withAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { + if (allowMixedMimeAdaptiveness == this.allowMixedMimeAdaptiveness) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided seamless adaptiveness allowance. + * + * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. + * @return A {@link Parameters} instance with the provided seamless adaptiveness allowance. + */ + public Parameters withAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { + if (allowNonSeamlessAdaptiveness == this.allowNonSeamlessAdaptiveness) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided max video size. + * + * @param maxVideoWidth The max video width. + * @param maxVideoHeight The max video width. + * @return A {@link Parameters} instance with the provided max video size. + */ + public Parameters withMaxVideoSize(int maxVideoWidth, int maxVideoHeight) { + if (maxVideoWidth == this.maxVideoWidth && maxVideoHeight == this.maxVideoHeight) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided max video bitrate. + * + * @param maxVideoBitrate The max video bitrate. + * @return A {@link Parameters} instance with the provided max video bitrate. + */ + public Parameters withMaxVideoBitrate(int maxVideoBitrate) { + if (maxVideoBitrate == this.maxVideoBitrate) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Equivalent to {@code withMaxVideoSize(1279, 719)}. + * + * @return A {@link Parameters} instance with maximum standard definition as maximum video size. + */ + public Parameters withMaxVideoSizeSd() { + return withMaxVideoSize(1279, 719); + } + + /** + * Equivalent to {@code withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}. + * + * @return A {@link Parameters} instance without video size constraints. + */ + public Parameters withoutVideoSizeConstraints() { + return withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * Returns a {@link Parameters} instance with the provided + * {@code exceedVideoConstraintsIfNecessary} value. + * + * @param exceedVideoConstraintsIfNecessary Whether to exceed video constraints when no + * selection can be made otherwise. + * @return A {@link Parameters} instance with the provided + * {@code exceedVideoConstraintsIfNecessary} value. + */ + public Parameters withExceedVideoConstraintsIfNecessary( + boolean exceedVideoConstraintsIfNecessary) { + if (exceedVideoConstraintsIfNecessary == this.exceedVideoConstraintsIfNecessary) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided + * {@code exceedRendererCapabilitiesIfNecessary} value. + * + * @param exceedRendererCapabilitiesIfNecessary Whether to exceed renderer capabilities when no + * selection can be made otherwise. + * @return A {@link Parameters} instance with the provided + * {@code exceedRendererCapabilitiesIfNecessary} value. + */ + public Parameters withExceedRendererCapabilitiesIfNecessary( + boolean exceedRendererCapabilitiesIfNecessary) { + if (exceedRendererCapabilitiesIfNecessary == this.exceedRendererCapabilitiesIfNecessary) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided viewport size. + * + * @param viewportWidth Viewport width in pixels. + * @param viewportHeight Viewport height in pixels. + * @param orientationMayChange Whether orientation may change during playback. + * @return A {@link Parameters} instance with the provided viewport size. + */ + public Parameters withViewportSize(int viewportWidth, int viewportHeight, + boolean orientationMayChange) { + if (viewportWidth == this.viewportWidth && viewportHeight == this.viewportHeight + && orientationMayChange == this.orientationMayChange) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, + viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance where the viewport size is obtained from the provided + * {@link Context}. + * + * @param context The context to obtain the viewport size from. + * @param orientationMayChange Whether orientation may change during playback. + * @return A {@link Parameters} instance where the viewport size is obtained from the provided + * {@link Context}. + */ + public Parameters withViewportSizeFromContext(Context context, boolean orientationMayChange) { + // Assume the viewport is fullscreen. + Point viewportSize = Util.getPhysicalDisplaySize(context); + return withViewportSize(viewportSize.x, viewportSize.y, orientationMayChange); + } + + /** + * Equivalent to {@code withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}. + * + * @return A {@link Parameters} instance without viewport size constraints. + */ + public Parameters withoutViewportSizeConstraints() { + return withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Parameters other = (Parameters) obj; + return allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness + && allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness + && maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight + && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary + && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary + && orientationMayChange == other.orientationMayChange + && viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight + && maxVideoBitrate == other.maxVideoBitrate + && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) + && TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage); + } + + @Override + public int hashCode() { + int result = preferredAudioLanguage.hashCode(); + result = 31 * result + preferredTextLanguage.hashCode(); + result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0); + result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0); + result = 31 * result + maxVideoWidth; + result = 31 * result + maxVideoHeight; + result = 31 * result + maxVideoBitrate; + result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); + result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); + result = 31 * result + (orientationMayChange ? 1 : 0); + result = 31 * result + viewportWidth; + result = 31 * result + viewportHeight; + return result; + } + + } + + /** + * If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the + * corresponding viewport dimension, then the video is considered as filling the viewport (in that + * dimension). + */ + private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f; + private static final int[] NO_TRACKS = new int[0]; + private static final int WITHIN_RENDERER_CAPABILITIES_BONUS = 1000; + + private final TrackSelection.Factory adaptiveTrackSelectionFactory; + private final AtomicReference paramsReference; + + /** + * Constructs an instance that does not support adaptive tracks. + */ + public DefaultTrackSelector() { + this(null); + } + + /** + * Constructs an instance that uses a factory to create adaptive track selections. + * + * @param adaptiveTrackSelectionFactory A factory for adaptive {@link TrackSelection}s, or null if + * the selector should not support adaptive tracks. + */ + public DefaultTrackSelector(TrackSelection.Factory adaptiveTrackSelectionFactory) { + this.adaptiveTrackSelectionFactory = adaptiveTrackSelectionFactory; + paramsReference = new AtomicReference<>(new Parameters()); + } + + /** + * Atomically sets the provided parameters for track selection. + * + * @param params The parameters for track selection. + */ + public void setParameters(Parameters params) { + Assertions.checkNotNull(params); + if (!paramsReference.getAndSet(params).equals(params)) { + invalidate(); + } + } + + /** + * Gets the current selection parameters. + * + * @return The current selection parameters. + */ + public Parameters getParameters() { + return paramsReference.get(); + } + + // MappingTrackSelector implementation. + + @Override + protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, + TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + throws ExoPlaybackException { + // Make a track selection for each renderer. + int rendererCount = rendererCapabilities.length; + TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCount]; + Parameters params = paramsReference.get(); + boolean videoTrackAndRendererPresent = false; + + for (int i = 0; i < rendererCount; i++) { + if (C.TRACK_TYPE_VIDEO == rendererCapabilities[i].getTrackType()) { + rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i], + rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth, + params.maxVideoHeight, params.maxVideoBitrate, params.allowNonSeamlessAdaptiveness, + params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight, + params.orientationMayChange, adaptiveTrackSelectionFactory, + params.exceedVideoConstraintsIfNecessary, params.exceedRendererCapabilitiesIfNecessary); + videoTrackAndRendererPresent |= rendererTrackGroupArrays[i].length > 0; + } + } + + for (int i = 0; i < rendererCount; i++) { + switch (rendererCapabilities[i].getTrackType()) { + case C.TRACK_TYPE_VIDEO: + // Already done. Do nothing. + break; + case C.TRACK_TYPE_AUDIO: + rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], + rendererFormatSupports[i], params.preferredAudioLanguage, + params.exceedRendererCapabilitiesIfNecessary, params.allowMixedMimeAdaptiveness, + videoTrackAndRendererPresent ? null : adaptiveTrackSelectionFactory); + break; + case C.TRACK_TYPE_TEXT: + rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], + rendererFormatSupports[i], params.preferredTextLanguage, + params.preferredAudioLanguage, params.exceedRendererCapabilitiesIfNecessary); + break; + default: + rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(), + rendererTrackGroupArrays[i], rendererFormatSupports[i], + params.exceedRendererCapabilitiesIfNecessary); + break; + } + } + return rendererTrackSelections; + } + + // Video track selection implementation. + + protected TrackSelection selectVideoTrack(RendererCapabilities rendererCapabilities, + TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, + int maxVideoBitrate, boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, + int viewportWidth, int viewportHeight, boolean orientationMayChange, + TrackSelection.Factory adaptiveTrackSelectionFactory, boolean exceedConstraintsIfNecessary, + boolean exceedRendererCapabilitiesIfNecessary) throws ExoPlaybackException { + TrackSelection selection = null; + if (adaptiveTrackSelectionFactory != null) { + selection = selectAdaptiveVideoTrack(rendererCapabilities, groups, formatSupport, + maxVideoWidth, maxVideoHeight, maxVideoBitrate, allowNonSeamlessAdaptiveness, + allowMixedMimeAdaptiveness, viewportWidth, viewportHeight, + orientationMayChange, adaptiveTrackSelectionFactory); + } + if (selection == null) { + selection = selectFixedVideoTrack(groups, formatSupport, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, viewportWidth, viewportHeight, orientationMayChange, + exceedConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary); + } + return selection; + } + + private static TrackSelection selectAdaptiveVideoTrack(RendererCapabilities rendererCapabilities, + TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, + int maxVideoBitrate, boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, + int viewportWidth, int viewportHeight, boolean orientationMayChange, + TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { + int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness + ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) + : RendererCapabilities.ADAPTIVE_SEAMLESS; + boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness + && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0; + for (int i = 0; i < groups.length; i++) { + TrackGroup group = groups.get(i); + int[] adaptiveTracks = getAdaptiveVideoTracksForGroup(group, formatSupport[i], + allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, viewportWidth, viewportHeight, orientationMayChange); + if (adaptiveTracks.length > 0) { + return adaptiveTrackSelectionFactory.createTrackSelection(group, adaptiveTracks); + } + } + return null; + } + + private static int[] getAdaptiveVideoTracksForGroup(TrackGroup group, int[] formatSupport, + boolean allowMixedMimeTypes, int requiredAdaptiveSupport, int maxVideoWidth, + int maxVideoHeight, int maxVideoBitrate, int viewportWidth, int viewportHeight, + boolean orientationMayChange) { + if (group.length < 2) { + return NO_TRACKS; + } + + List selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth, + viewportHeight, orientationMayChange); + if (selectedTrackIndices.size() < 2) { + return NO_TRACKS; + } + + String selectedMimeType = null; + if (!allowMixedMimeTypes) { + // Select the mime type for which we have the most adaptive tracks. + HashSet seenMimeTypes = new HashSet<>(); + int selectedMimeTypeTrackCount = 0; + for (int i = 0; i < selectedTrackIndices.size(); i++) { + int trackIndex = selectedTrackIndices.get(i); + String sampleMimeType = group.getFormat(trackIndex).sampleMimeType; + if (seenMimeTypes.add(sampleMimeType)) { + int countForMimeType = getAdaptiveVideoTrackCountForMimeType(group, formatSupport, + requiredAdaptiveSupport, sampleMimeType, maxVideoWidth, maxVideoHeight, + maxVideoBitrate, selectedTrackIndices); + if (countForMimeType > selectedMimeTypeTrackCount) { + selectedMimeType = sampleMimeType; + selectedMimeTypeTrackCount = countForMimeType; + } + } + } + } + + // Filter by the selected mime type. + filterAdaptiveVideoTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport, + selectedMimeType, maxVideoWidth, maxVideoHeight, maxVideoBitrate, selectedTrackIndices); + + return selectedTrackIndices.size() < 2 ? NO_TRACKS : Util.toArray(selectedTrackIndices); + } + + private static int getAdaptiveVideoTrackCountForMimeType(TrackGroup group, int[] formatSupport, + int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight, + int maxVideoBitrate, List selectedTrackIndices) { + int adaptiveTrackCount = 0; + for (int i = 0; i < selectedTrackIndices.size(); i++) { + int trackIndex = selectedTrackIndices.get(i); + if (isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, + formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, + maxVideoBitrate)) { + adaptiveTrackCount++; + } + } + return adaptiveTrackCount; + } + + private static void filterAdaptiveVideoTrackCountForMimeType(TrackGroup group, + int[] formatSupport, int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, + int maxVideoHeight, int maxVideoBitrate, List selectedTrackIndices) { + for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { + int trackIndex = selectedTrackIndices.get(i); + if (!isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, + formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, + maxVideoBitrate)) { + selectedTrackIndices.remove(i); + } + } + } + + private static boolean isSupportedAdaptiveVideoTrack(Format format, String mimeType, + int formatSupport, int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight, + int maxVideoBitrate) { + return isSupported(formatSupport, false) && ((formatSupport & requiredAdaptiveSupport) != 0) + && (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType)) + && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth) + && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight) + && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxVideoBitrate); + } + + private static TrackSelection selectFixedVideoTrack(TrackGroupArray groups, + int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, + int viewportWidth, int viewportHeight, boolean orientationMayChange, + boolean exceedConstraintsIfNecessary, boolean exceedRendererCapabilitiesIfNecessary) { + TrackGroup selectedGroup = null; + int selectedTrackIndex = 0; + int selectedTrackScore = 0; + int selectedBitrate = Format.NO_VALUE; + int selectedPixelCount = Format.NO_VALUE; + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup trackGroup = groups.get(groupIndex); + List selectedTrackIndices = getViewportFilteredTrackIndices(trackGroup, + viewportWidth, viewportHeight, orientationMayChange); + int[] trackFormatSupport = formatSupport[groupIndex]; + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { + Format format = trackGroup.getFormat(trackIndex); + boolean isWithinConstraints = selectedTrackIndices.contains(trackIndex) + && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth) + && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight) + && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxVideoBitrate); + if (!isWithinConstraints && !exceedConstraintsIfNecessary) { + // Track should not be selected. + continue; + } + int trackScore = isWithinConstraints ? 2 : 1; + if (isSupported(trackFormatSupport[trackIndex], false)) { + trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; + } + boolean selectTrack = trackScore > selectedTrackScore; + if (trackScore == selectedTrackScore) { + // Use the pixel count as a tie breaker (or bitrate if pixel counts are tied). If we're + // within constraints prefer a higher pixel count (or bitrate), else prefer a lower + // count (or bitrate). If still tied then prefer the first track (i.e. the one that's + // already selected). + int comparisonResult; + int formatPixelCount = format.getPixelCount(); + if (formatPixelCount != selectedPixelCount) { + comparisonResult = compareFormatValues(format.getPixelCount(), selectedPixelCount); + } else { + comparisonResult = compareFormatValues(format.bitrate, selectedBitrate); + } + selectTrack = isWithinConstraints ? comparisonResult > 0 : comparisonResult < 0; + } + if (selectTrack) { + selectedGroup = trackGroup; + selectedTrackIndex = trackIndex; + selectedTrackScore = trackScore; + selectedBitrate = format.bitrate; + selectedPixelCount = format.getPixelCount(); + } + } + } + } + return selectedGroup == null ? null + : new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + /** + * Compares two format values for order. A known value is considered greater than + * {@link Format#NO_VALUE}. + * + * @param first The first value. + * @param second The second value. + * @return A negative integer if the first value is less than the second. Zero if they are equal. + * A positive integer if the first value is greater than the second. + */ + private static int compareFormatValues(int first, int second) { + return first == Format.NO_VALUE ? (second == Format.NO_VALUE ? 0 : -1) + : (second == Format.NO_VALUE ? 1 : (first - second)); + } + + // Audio track selection implementation. + + protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport, + String preferredAudioLanguage, boolean exceedRendererCapabilitiesIfNecessary, + boolean allowMixedMimeAdaptiveness, TrackSelection.Factory adaptiveTrackSelectionFactory) { + int selectedGroupIndex = C.INDEX_UNSET; + int selectedTrackIndex = C.INDEX_UNSET; + int selectedTrackScore = 0; + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup trackGroup = groups.get(groupIndex); + int[] trackFormatSupport = formatSupport[groupIndex]; + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { + Format format = trackGroup.getFormat(trackIndex); + int trackScore = getAudioTrackScore(trackFormatSupport[trackIndex], + preferredAudioLanguage, format); + if (trackScore > selectedTrackScore) { + selectedGroupIndex = groupIndex; + selectedTrackIndex = trackIndex; + selectedTrackScore = trackScore; + } + } + } + } + + if (selectedGroupIndex == C.INDEX_UNSET) { + return null; + } + + TrackGroup selectedGroup = groups.get(selectedGroupIndex); + if (adaptiveTrackSelectionFactory != null) { + // If the group of the track with the highest score allows it, try to enable adaptation. + int[] adaptiveTracks = getAdaptiveAudioTracks(selectedGroup, + formatSupport[selectedGroupIndex], allowMixedMimeAdaptiveness); + if (adaptiveTracks.length > 0) { + return adaptiveTrackSelectionFactory.createTrackSelection(selectedGroup, + adaptiveTracks); + } + } + return new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + private static int getAudioTrackScore(int formatSupport, String preferredLanguage, + Format format) { + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + int trackScore; + if (formatHasLanguage(format, preferredLanguage)) { + if (isDefault) { + trackScore = 4; + } else { + trackScore = 3; + } + } else if (isDefault) { + trackScore = 2; + } else { + trackScore = 1; + } + if (isSupported(formatSupport, false)) { + trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; + } + return trackScore; + } + + private static int[] getAdaptiveAudioTracks(TrackGroup group, int[] formatSupport, + boolean allowMixedMimeTypes) { + int selectedConfigurationTrackCount = 0; + AudioConfigurationTuple selectedConfiguration = null; + HashSet seenConfigurationTuples = new HashSet<>(); + for (int i = 0; i < group.length; i++) { + Format format = group.getFormat(i); + AudioConfigurationTuple configuration = new AudioConfigurationTuple( + format.channelCount, format.sampleRate, + allowMixedMimeTypes ? null : format.sampleMimeType); + if (seenConfigurationTuples.add(configuration)) { + int configurationCount = getAdaptiveAudioTrackCount(group, formatSupport, configuration); + if (configurationCount > selectedConfigurationTrackCount) { + selectedConfiguration = configuration; + selectedConfigurationTrackCount = configurationCount; + } + } + } + + if (selectedConfigurationTrackCount > 1) { + int[] adaptiveIndices = new int[selectedConfigurationTrackCount]; + int index = 0; + for (int i = 0; i < group.length; i++) { + if (isSupportedAdaptiveAudioTrack(group.getFormat(i), formatSupport[i], + selectedConfiguration)) { + adaptiveIndices[index++] = i; + } + } + return adaptiveIndices; + } + return NO_TRACKS; + } + + private static int getAdaptiveAudioTrackCount(TrackGroup group, int[] formatSupport, + AudioConfigurationTuple configuration) { + int count = 0; + for (int i = 0; i < group.length; i++) { + if (isSupportedAdaptiveAudioTrack(group.getFormat(i), formatSupport[i], configuration)) { + count++; + } + } + return count; + } + + private static boolean isSupportedAdaptiveAudioTrack(Format format, int formatSupport, + AudioConfigurationTuple configuration) { + return isSupported(formatSupport, false) && format.channelCount == configuration.channelCount + && format.sampleRate == configuration.sampleRate + && (configuration.mimeType == null + || TextUtils.equals(configuration.mimeType, format.sampleMimeType)); + } + + // Text track selection implementation. + + protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport, + String preferredTextLanguage, String preferredAudioLanguage, + boolean exceedRendererCapabilitiesIfNecessary) { + TrackGroup selectedGroup = null; + int selectedTrackIndex = 0; + int selectedTrackScore = 0; + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup trackGroup = groups.get(groupIndex); + int[] trackFormatSupport = formatSupport[groupIndex]; + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { + Format format = trackGroup.getFormat(trackIndex); + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + boolean isForced = (format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0; + int trackScore; + if (formatHasLanguage(format, preferredTextLanguage)) { + if (isDefault) { + trackScore = 6; + } else if (!isForced) { + // Prefer non-forced to forced if a preferred text language has been specified. Where + // both are provided the non-forced track will usually contain the forced subtitles as + // a subset. + trackScore = 5; + } else { + trackScore = 4; + } + } else if (isDefault) { + trackScore = 3; + } else if (isForced) { + if (formatHasLanguage(format, preferredAudioLanguage)) { + trackScore = 2; + } else { + trackScore = 1; + } + } else { + // Track should not be selected. + continue; + } + if (isSupported(trackFormatSupport[trackIndex], false)) { + trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; + } + if (trackScore > selectedTrackScore) { + selectedGroup = trackGroup; + selectedTrackIndex = trackIndex; + selectedTrackScore = trackScore; + } + } + } + } + return selectedGroup == null ? null + : new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + // General track selection methods. + + protected TrackSelection selectOtherTrack(int trackType, TrackGroupArray groups, + int[][] formatSupport, boolean exceedRendererCapabilitiesIfNecessary) { + TrackGroup selectedGroup = null; + int selectedTrackIndex = 0; + int selectedTrackScore = 0; + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup trackGroup = groups.get(groupIndex); + int[] trackFormatSupport = formatSupport[groupIndex]; + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { + Format format = trackGroup.getFormat(trackIndex); + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + int trackScore = isDefault ? 2 : 1; + if (isSupported(trackFormatSupport[trackIndex], false)) { + trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; + } + if (trackScore > selectedTrackScore) { + selectedGroup = trackGroup; + selectedTrackIndex = trackIndex; + selectedTrackScore = trackScore; + } + } + } + } + return selectedGroup == null ? null + : new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + protected static boolean isSupported(int formatSupport, boolean allowExceedsCapabilities) { + int maskedSupport = formatSupport & RendererCapabilities.FORMAT_SUPPORT_MASK; + return maskedSupport == RendererCapabilities.FORMAT_HANDLED || (allowExceedsCapabilities + && maskedSupport == RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES); + } + + protected static boolean formatHasLanguage(Format format, String language) { + return TextUtils.equals(language, Util.normalizeLanguageCode(format.language)); + } + + // Viewport size util methods. + + private static List getViewportFilteredTrackIndices(TrackGroup group, int viewportWidth, + int viewportHeight, boolean orientationMayChange) { + // Initially include all indices. + ArrayList selectedTrackIndices = new ArrayList<>(group.length); + for (int i = 0; i < group.length; i++) { + selectedTrackIndices.add(i); + } + + if (viewportWidth == Integer.MAX_VALUE || viewportHeight == Integer.MAX_VALUE) { + // Viewport dimensions not set. Return the full set of indices. + return selectedTrackIndices; + } + + int maxVideoPixelsToRetain = Integer.MAX_VALUE; + for (int i = 0; i < group.length; i++) { + Format format = group.getFormat(i); + // Keep track of the number of pixels of the selected format whose resolution is the + // smallest to exceed the maximum size at which it can be displayed within the viewport. + // We'll discard formats of higher resolution. + if (format.width > 0 && format.height > 0) { + Point maxVideoSizeInViewport = getMaxVideoSizeInViewport(orientationMayChange, + viewportWidth, viewportHeight, format.width, format.height); + int videoPixels = format.width * format.height; + if (format.width >= (int) (maxVideoSizeInViewport.x * FRACTION_TO_CONSIDER_FULLSCREEN) + && format.height >= (int) (maxVideoSizeInViewport.y * FRACTION_TO_CONSIDER_FULLSCREEN) + && videoPixels < maxVideoPixelsToRetain) { + maxVideoPixelsToRetain = videoPixels; + } + } + } + + // Filter out formats that exceed maxVideoPixelsToRetain. These formats have an unnecessarily + // high resolution given the size at which the video will be displayed within the viewport. Also + // filter out formats with unknown dimensions, since we have some whose dimensions are known. + if (maxVideoPixelsToRetain != Integer.MAX_VALUE) { + for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { + Format format = group.getFormat(selectedTrackIndices.get(i)); + int pixelCount = format.getPixelCount(); + if (pixelCount == Format.NO_VALUE || pixelCount > maxVideoPixelsToRetain) { + selectedTrackIndices.remove(i); + } + } + } + + return selectedTrackIndices; + } + + /** + * Given viewport dimensions and video dimensions, computes the maximum size of the video as it + * will be rendered to fit inside of the viewport. + */ + private static Point getMaxVideoSizeInViewport(boolean orientationMayChange, int viewportWidth, + int viewportHeight, int videoWidth, int videoHeight) { + if (orientationMayChange && (videoWidth > videoHeight) != (viewportWidth > viewportHeight)) { + // Rotation is allowed, and the video will be larger in the rotated viewport. + int tempViewportWidth = viewportWidth; + viewportWidth = viewportHeight; + viewportHeight = tempViewportWidth; + } + + if (videoWidth * viewportHeight >= videoHeight * viewportWidth) { + // Horizontal letter-boxing along top and bottom. + return new Point(viewportWidth, Util.ceilDivide(viewportWidth * videoHeight, videoWidth)); + } else { + // Vertical letter-boxing along edges. + return new Point(Util.ceilDivide(viewportHeight * videoWidth, videoHeight), viewportHeight); + } + } + + private static final class AudioConfigurationTuple { + + public final int channelCount; + public final int sampleRate; + public final String mimeType; + + public AudioConfigurationTuple(int channelCount, int sampleRate, String mimeType) { + this.channelCount = channelCount; + this.sampleRate = sampleRate; + this.mimeType = mimeType; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + AudioConfigurationTuple other = (AudioConfigurationTuple) obj; + return channelCount == other.channelCount && sampleRate == other.sampleRate + && TextUtils.equals(mimeType, other.mimeType); + } + + @Override + public int hashCode() { + int result = channelCount; + result = 31 * result + sampleRate; + result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0); + return result; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/FixedTrackSelection.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/FixedTrackSelection.java new file mode 100644 index 0000000..7baaf70 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/FixedTrackSelection.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * A {@link TrackSelection} consisting of a single track. + */ +public final class FixedTrackSelection extends BaseTrackSelection { + + /** + * Factory for {@link FixedTrackSelection} instances. + */ + public static final class Factory implements TrackSelection.Factory { + + private final int reason; + private final Object data; + + public Factory() { + this.reason = C.SELECTION_REASON_UNKNOWN; + this.data = null; + } + + /** + * @param reason A reason for the track selection. + * @param data Optional data associated with the track selection. + */ + public Factory(int reason, Object data) { + this.reason = reason; + this.data = data; + } + + @Override + public FixedTrackSelection createTrackSelection(TrackGroup group, int... tracks) { + Assertions.checkArgument(tracks.length == 1); + return new FixedTrackSelection(group, tracks[0], reason, data); + } + + } + + private final int reason; + private final Object data; + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param track The index of the selected track within the {@link TrackGroup}. + */ + public FixedTrackSelection(TrackGroup group, int track) { + this(group, track, C.SELECTION_REASON_UNKNOWN, null); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param track The index of the selected track within the {@link TrackGroup}. + * @param reason A reason for the track selection. + * @param data Optional data associated with the track selection. + */ + public FixedTrackSelection(TrackGroup group, int track, int reason, Object data) { + super(group, track); + this.reason = reason; + this.data = data; + } + + @Override + public void updateSelectedTrack(long bufferedDurationUs) { + // Do nothing. + } + + @Override + public int getSelectedIndex() { + return 0; + } + + @Override + public int getSelectionReason() { + return reason; + } + + @Override + public Object getSelectionData() { + return data; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/MappingTrackSelector.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/MappingTrackSelector.java new file mode 100644 index 0000000..9784f8c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/MappingTrackSelector.java @@ -0,0 +1,733 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import android.content.Context; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.RendererCapabilities; +import com.tangxiaolv.telegramgallery.exoplayer2.RendererConfiguration; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s + * and renderers, and then from that mapping create a {@link TrackSelection} for each renderer. + */ +public abstract class MappingTrackSelector extends TrackSelector { + + /** + * A track selection override. + */ + public static final class SelectionOverride { + + public final TrackSelection.Factory factory; + public final int groupIndex; + public final int[] tracks; + public final int length; + + /** + * @param factory A factory for creating selections from this override. + * @param groupIndex The overriding group index. + * @param tracks The overriding track indices within the group. + */ + public SelectionOverride(TrackSelection.Factory factory, int groupIndex, int... tracks) { + this.factory = factory; + this.groupIndex = groupIndex; + this.tracks = tracks; + this.length = tracks.length; + } + + /** + * Creates an selection from this override. + * + * @param groups The groups whose selection is being overridden. + * @return The selection. + */ + public TrackSelection createTrackSelection(TrackGroupArray groups) { + return factory.createTrackSelection(groups.get(groupIndex), tracks); + } + + /** + * Returns whether this override contains the specified track index. + */ + public boolean containsTrack(int track) { + for (int overrideTrack : tracks) { + if (overrideTrack == track) { + return true; + } + } + return false; + } + + } + + private final SparseArray> selectionOverrides; + private final SparseBooleanArray rendererDisabledFlags; + private int tunnelingAudioSessionId; + + private MappedTrackInfo currentMappedTrackInfo; + + public MappingTrackSelector() { + selectionOverrides = new SparseArray<>(); + rendererDisabledFlags = new SparseBooleanArray(); + tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; + } + + /** + * Returns the mapping information associated with the current track selections, or null if no + * selection is currently active. + */ + public final MappedTrackInfo getCurrentMappedTrackInfo() { + return currentMappedTrackInfo; + } + + /** + * Sets whether the renderer at the specified index is disabled. + * + * @param rendererIndex The renderer index. + * @param disabled Whether the renderer is disabled. + */ + public final void setRendererDisabled(int rendererIndex, boolean disabled) { + if (rendererDisabledFlags.get(rendererIndex) == disabled) { + // The disabled flag is unchanged. + return; + } + rendererDisabledFlags.put(rendererIndex, disabled); + invalidate(); + } + + /** + * Returns whether the renderer is disabled. + * + * @param rendererIndex The renderer index. + * @return Whether the renderer is disabled. + */ + public final boolean getRendererDisabled(int rendererIndex) { + return rendererDisabledFlags.get(rendererIndex); + } + + /** + * Overrides the track selection for the renderer at a specified index. + *

    + * When the {@link TrackGroupArray} available to the renderer at the specified index matches the + * one provided, the override is applied. When the {@link TrackGroupArray} does not match, the + * override has no effect. The override replaces any previous override for the renderer and the + * provided {@link TrackGroupArray}. + *

    + * Passing a {@code null} override will explicitly disable the renderer. To remove overrides use + * {@link #clearSelectionOverride(int, TrackGroupArray)}, {@link #clearSelectionOverrides(int)} + * or {@link #clearSelectionOverrides()}. + * + * @param rendererIndex The renderer index. + * @param groups The {@link TrackGroupArray} for which the override should be applied. + * @param override The override. + */ + public final void setSelectionOverride(int rendererIndex, TrackGroupArray groups, + SelectionOverride override) { + Map overrides = selectionOverrides.get(rendererIndex); + if (overrides == null) { + overrides = new HashMap<>(); + selectionOverrides.put(rendererIndex, overrides); + } + if (overrides.containsKey(groups) && Util.areEqual(overrides.get(groups), override)) { + // The override is unchanged. + return; + } + overrides.put(groups, override); + invalidate(); + } + + /** + * Returns whether there is an override for the specified renderer and {@link TrackGroupArray}. + * + * @param rendererIndex The renderer index. + * @param groups The {@link TrackGroupArray}. + * @return Whether there is an override. + */ + public final boolean hasSelectionOverride(int rendererIndex, TrackGroupArray groups) { + Map overrides = selectionOverrides.get(rendererIndex); + return overrides != null && overrides.containsKey(groups); + } + + /** + * Returns the override for the specified renderer and {@link TrackGroupArray}. + * + * @param rendererIndex The renderer index. + * @param groups The {@link TrackGroupArray}. + * @return The override, or null if no override exists. + */ + public final SelectionOverride getSelectionOverride(int rendererIndex, TrackGroupArray groups) { + Map overrides = selectionOverrides.get(rendererIndex); + return overrides != null ? overrides.get(groups) : null; + } + + /** + * Clears a track selection override for the specified renderer and {@link TrackGroupArray}. + * + * @param rendererIndex The renderer index. + * @param groups The {@link TrackGroupArray} for which the override should be cleared. + */ + public final void clearSelectionOverride(int rendererIndex, TrackGroupArray groups) { + Map overrides = selectionOverrides.get(rendererIndex); + if (overrides == null || !overrides.containsKey(groups)) { + // Nothing to clear. + return; + } + overrides.remove(groups); + if (overrides.isEmpty()) { + selectionOverrides.remove(rendererIndex); + } + invalidate(); + } + + /** + * Clears all track selection override for the specified renderer. + * + * @param rendererIndex The renderer index. + */ + public final void clearSelectionOverrides(int rendererIndex) { + Map overrides = selectionOverrides.get(rendererIndex); + if (overrides == null || overrides.isEmpty()) { + // Nothing to clear. + return; + } + selectionOverrides.remove(rendererIndex); + invalidate(); + } + + /** + * Clears all track selection overrides. + */ + public final void clearSelectionOverrides() { + if (selectionOverrides.size() == 0) { + // Nothing to clear. + return; + } + selectionOverrides.clear(); + invalidate(); + } + + /** + * Enables or disables tunneling. To enable tunneling, pass an audio session id to use when in + * tunneling mode. Session ids can be generated using + * {@link C#generateAudioSessionIdV21(Context)}. To disable tunneling pass + * {@link C#AUDIO_SESSION_ID_UNSET}. Tunneling will only be activated if it's both enabled and + * supported by the audio and video renderers for the selected tracks. + * + * @param tunnelingAudioSessionId The audio session id to use when tunneling, or + * {@link C#AUDIO_SESSION_ID_UNSET} to disable tunneling. + */ + public void setTunnelingAudioSessionId(int tunnelingAudioSessionId) { + if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { + this.tunnelingAudioSessionId = tunnelingAudioSessionId; + invalidate(); + } + } + + // TrackSelector implementation. + + @Override + public final TrackSelectorResult selectTracks(RendererCapabilities[] rendererCapabilities, + TrackGroupArray trackGroups) throws ExoPlaybackException { + // Structures into which data will be written during the selection. The extra item at the end + // of each array is to store data associated with track groups that cannot be associated with + // any renderer. + int[] rendererTrackGroupCounts = new int[rendererCapabilities.length + 1]; + TrackGroup[][] rendererTrackGroups = new TrackGroup[rendererCapabilities.length + 1][]; + int[][][] rendererFormatSupports = new int[rendererCapabilities.length + 1][][]; + for (int i = 0; i < rendererTrackGroups.length; i++) { + rendererTrackGroups[i] = new TrackGroup[trackGroups.length]; + rendererFormatSupports[i] = new int[trackGroups.length][]; + } + + // Determine the extent to which each renderer supports mixed mimeType adaptation. + int[] mixedMimeTypeAdaptationSupport = getMixedMimeTypeAdaptationSupport(rendererCapabilities); + + // Associate each track group to a preferred renderer, and evaluate the support that the + // renderer provides for each track in the group. + for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { + TrackGroup group = trackGroups.get(groupIndex); + // Associate the group to a preferred renderer. + int rendererIndex = findRenderer(rendererCapabilities, group); + // Evaluate the support that the renderer provides for each track in the group. + int[] rendererFormatSupport = rendererIndex == rendererCapabilities.length + ? new int[group.length] : getFormatSupport(rendererCapabilities[rendererIndex], group); + // Stash the results. + int rendererTrackGroupCount = rendererTrackGroupCounts[rendererIndex]; + rendererTrackGroups[rendererIndex][rendererTrackGroupCount] = group; + rendererFormatSupports[rendererIndex][rendererTrackGroupCount] = rendererFormatSupport; + rendererTrackGroupCounts[rendererIndex]++; + } + + // Create a track group array for each renderer, and trim each rendererFormatSupports entry. + TrackGroupArray[] rendererTrackGroupArrays = new TrackGroupArray[rendererCapabilities.length]; + int[] rendererTrackTypes = new int[rendererCapabilities.length]; + for (int i = 0; i < rendererCapabilities.length; i++) { + int rendererTrackGroupCount = rendererTrackGroupCounts[i]; + rendererTrackGroupArrays[i] = new TrackGroupArray( + Arrays.copyOf(rendererTrackGroups[i], rendererTrackGroupCount)); + rendererFormatSupports[i] = Arrays.copyOf(rendererFormatSupports[i], rendererTrackGroupCount); + rendererTrackTypes[i] = rendererCapabilities[i].getTrackType(); + } + + // Create a track group array for track groups not associated with a renderer. + int unassociatedTrackGroupCount = rendererTrackGroupCounts[rendererCapabilities.length]; + TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(Arrays.copyOf( + rendererTrackGroups[rendererCapabilities.length], unassociatedTrackGroupCount)); + + TrackSelection[] trackSelections = selectTracks(rendererCapabilities, rendererTrackGroupArrays, + rendererFormatSupports); + + // Apply track disabling and overriding. + for (int i = 0; i < rendererCapabilities.length; i++) { + if (rendererDisabledFlags.get(i)) { + trackSelections[i] = null; + } else { + TrackGroupArray rendererTrackGroup = rendererTrackGroupArrays[i]; + Map overrides = selectionOverrides.get(i); + SelectionOverride override = overrides == null ? null : overrides.get(rendererTrackGroup); + if (override != null) { + trackSelections[i] = override.createTrackSelection(rendererTrackGroup); + } + } + } + + // Package up the track information and selections. + MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes, + rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports, + unassociatedTrackGroupArray); + + // Initialize the renderer configurations to the default configuration for all renderers with + // selections, and null otherwise. + RendererConfiguration[] rendererConfigurations = + new RendererConfiguration[rendererCapabilities.length]; + for (int i = 0; i < rendererCapabilities.length; i++) { + rendererConfigurations[i] = trackSelections[i] != null ? RendererConfiguration.DEFAULT : null; + } + // Configure audio and video renderers to use tunneling if appropriate. + maybeConfigureRenderersForTunneling(rendererCapabilities, rendererTrackGroupArrays, + rendererFormatSupports, rendererConfigurations, trackSelections, tunnelingAudioSessionId); + + return new TrackSelectorResult(trackGroups, new TrackSelectionArray(trackSelections), + mappedTrackInfo, rendererConfigurations); + } + + @Override + public final void onSelectionActivated(Object info) { + currentMappedTrackInfo = (MappedTrackInfo) info; + } + + /** + * Given an array of renderers and a set of {@link TrackGroup}s mapped to each of them, provides a + * {@link TrackSelection} per renderer. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which + * {@link TrackSelection}s are to be generated. + * @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry + * corresponds to the renderer of equal index in {@code renderers}. + * @param rendererFormatSupports Maps every available track to a specific level of support as + * defined by the renderer {@code FORMAT_*} constants. + * @throws ExoPlaybackException If an error occurs while selecting the tracks. + */ + protected abstract TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, + TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + throws ExoPlaybackException; + + /** + * Finds the renderer to which the provided {@link TrackGroup} should be associated. + *

    + * A {@link TrackGroup} is associated to a renderer that reports + * {@link RendererCapabilities#FORMAT_HANDLED} support for one or more of the tracks in the group, + * or {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES} if no such renderer exists, or + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} if again no such renderer exists. In + * the case that two or more renderers report the same level of support, the renderer with the + * lowest index is associated. + *

    + * If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the + * tracks in the group, then {@code renderers.length} is returned to indicate that no association + * was made. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers. + * @param group The {@link TrackGroup} whose associated renderer is to be found. + * @return The index of the associated renderer, or {@code renderers.length} if no + * association was made. + * @throws ExoPlaybackException If an error occurs finding a renderer. + */ + private static int findRenderer(RendererCapabilities[] rendererCapabilities, TrackGroup group) + throws ExoPlaybackException { + int bestRendererIndex = rendererCapabilities.length; + int bestFormatSupportLevel = RendererCapabilities.FORMAT_UNSUPPORTED_TYPE; + for (int rendererIndex = 0; rendererIndex < rendererCapabilities.length; rendererIndex++) { + RendererCapabilities rendererCapability = rendererCapabilities[rendererIndex]; + for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { + int formatSupportLevel = rendererCapability.supportsFormat(group.getFormat(trackIndex)) + & RendererCapabilities.FORMAT_SUPPORT_MASK; + if (formatSupportLevel > bestFormatSupportLevel) { + bestRendererIndex = rendererIndex; + bestFormatSupportLevel = formatSupportLevel; + if (bestFormatSupportLevel == RendererCapabilities.FORMAT_HANDLED) { + // We can't do better. + return bestRendererIndex; + } + } + } + } + return bestRendererIndex; + } + + /** + * Calls {@link RendererCapabilities#supportsFormat} for each track in the specified + * {@link TrackGroup}, returning the results in an array. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderer. + * @param group The {@link TrackGroup} to evaluate. + * @return An array containing the result of calling + * {@link RendererCapabilities#supportsFormat} for each track in the group. + * @throws ExoPlaybackException If an error occurs determining the format support. + */ + private static int[] getFormatSupport(RendererCapabilities rendererCapabilities, TrackGroup group) + throws ExoPlaybackException { + int[] formatSupport = new int[group.length]; + for (int i = 0; i < group.length; i++) { + formatSupport[i] = rendererCapabilities.supportsFormat(group.getFormat(i)); + } + return formatSupport; + } + + /** + * Calls {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer, + * returning the results in an array. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers. + * @return An array containing the result of calling + * {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. + * @throws ExoPlaybackException If an error occurs determining the adaptation support. + */ + private static int[] getMixedMimeTypeAdaptationSupport( + RendererCapabilities[] rendererCapabilities) throws ExoPlaybackException { + int[] mixedMimeTypeAdaptationSupport = new int[rendererCapabilities.length]; + for (int i = 0; i < mixedMimeTypeAdaptationSupport.length; i++) { + mixedMimeTypeAdaptationSupport[i] = rendererCapabilities[i].supportsMixedMimeTypeAdaptation(); + } + return mixedMimeTypeAdaptationSupport; + } + + /** + * Determines whether tunneling should be enabled, replacing {@link RendererConfiguration}s in + * {@code rendererConfigurations} with configurations that enable tunneling on the appropriate + * renderers if so. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which + * {@link TrackSelection}s are to be generated. + * @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry + * corresponds to the renderer of equal index in {@code renderers}. + * @param rendererFormatSupports Maps every available track to a specific level of support as + * defined by the renderer {@code FORMAT_*} constants. + * @param rendererConfigurations The renderer configurations. Configurations may be replaced with + * ones that enable tunneling as a result of this call. + * @param trackSelections The renderer track selections. + * @param tunnelingAudioSessionId The audio session id to use when tunneling, or + * {@link C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. + */ + private static void maybeConfigureRenderersForTunneling( + RendererCapabilities[] rendererCapabilities, TrackGroupArray[] rendererTrackGroupArrays, + int[][][] rendererFormatSupports, RendererConfiguration[] rendererConfigurations, + TrackSelection[] trackSelections, int tunnelingAudioSessionId) { + if (tunnelingAudioSessionId == C.AUDIO_SESSION_ID_UNSET) { + return; + } + // Check whether we can enable tunneling. To enable tunneling we require exactly one audio and + // one video renderer to support tunneling and have a selection. + int tunnelingAudioRendererIndex = -1; + int tunnelingVideoRendererIndex = -1; + boolean enableTunneling = true; + for (int i = 0; i < rendererCapabilities.length; i++) { + int rendererType = rendererCapabilities[i].getTrackType(); + TrackSelection trackSelection = trackSelections[i]; + if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO) + && trackSelection != null) { + if (rendererSupportsTunneling(rendererFormatSupports[i], rendererTrackGroupArrays[i], + trackSelection)) { + if (rendererType == C.TRACK_TYPE_AUDIO) { + if (tunnelingAudioRendererIndex != -1) { + enableTunneling = false; + break; + } else { + tunnelingAudioRendererIndex = i; + } + } else { + if (tunnelingVideoRendererIndex != -1) { + enableTunneling = false; + break; + } else { + tunnelingVideoRendererIndex = i; + } + } + } + } + } + enableTunneling &= tunnelingAudioRendererIndex != -1 && tunnelingVideoRendererIndex != -1; + if (enableTunneling) { + RendererConfiguration tunnelingRendererConfiguration = + new RendererConfiguration(tunnelingAudioSessionId); + rendererConfigurations[tunnelingAudioRendererIndex] = tunnelingRendererConfiguration; + rendererConfigurations[tunnelingVideoRendererIndex] = tunnelingRendererConfiguration; + } + } + + /** + * Returns whether a renderer supports tunneling for a {@link TrackSelection}. + * + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each + * track, indexed by group index and track index (in that order). + * @param trackGroups The {@link TrackGroupArray}s for the renderer. + * @param selection The track selection. + * @return Whether the renderer supports tunneling for the {@link TrackSelection}. + */ + private static boolean rendererSupportsTunneling(int[][] formatSupport, + TrackGroupArray trackGroups, TrackSelection selection) { + if (selection == null) { + return false; + } + int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); + for (int i = 0; i < selection.length(); i++) { + int trackFormatSupport = formatSupport[trackGroupIndex][selection.getIndexInTrackGroup(i)]; + if ((trackFormatSupport & RendererCapabilities.TUNNELING_SUPPORT_MASK) + != RendererCapabilities.TUNNELING_SUPPORTED) { + return false; + } + } + return true; + } + + /** + * Provides track information for each renderer. + */ + public static final class MappedTrackInfo { + + /** + * The renderer does not have any associated tracks. + */ + public static final int RENDERER_SUPPORT_NO_TRACKS = 0; + /** + * The renderer has associated tracks, but all are of unsupported types. + */ + public static final int RENDERER_SUPPORT_UNSUPPORTED_TRACKS = 1; + /** + * The renderer has associated tracks and at least one is of a supported type, but all of the + * tracks whose types are supported exceed the renderer's capabilities. + */ + public static final int RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS = 2; + /** + * The renderer has associated tracks and can play at least one of them. + */ + public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 3; + + /** + * The number of renderers to which tracks are mapped. + */ + public final int length; + + private final int[] rendererTrackTypes; + private final TrackGroupArray[] trackGroups; + private final int[] mixedMimeTypeAdaptiveSupport; + private final int[][][] formatSupport; + private final TrackGroupArray unassociatedTrackGroups; + + /** + * @param rendererTrackTypes The track type supported by each renderer. + * @param trackGroups The {@link TrackGroupArray}s for each renderer. + * @param mixedMimeTypeAdaptiveSupport The result of + * {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each + * track, indexed by renderer index, group index and track index (in that order). + * @param unassociatedTrackGroups Contains {@link TrackGroup}s not associated with any renderer. + */ + /* package */ MappedTrackInfo(int[] rendererTrackTypes, + TrackGroupArray[] trackGroups, int[] mixedMimeTypeAdaptiveSupport, + int[][][] formatSupport, TrackGroupArray unassociatedTrackGroups) { + this.rendererTrackTypes = rendererTrackTypes; + this.trackGroups = trackGroups; + this.formatSupport = formatSupport; + this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport; + this.unassociatedTrackGroups = unassociatedTrackGroups; + this.length = trackGroups.length; + } + + /** + * Returns the array of {@link TrackGroup}s associated to the renderer at a specified index. + * + * @param rendererIndex The renderer index. + * @return The corresponding {@link TrackGroup}s. + */ + public TrackGroupArray getTrackGroups(int rendererIndex) { + return trackGroups[rendererIndex]; + } + + /** + * Returns the extent to which a renderer can support playback of the tracks associated to it. + * + * @param rendererIndex The renderer index. + * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, + * {@link #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS}, + * {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. + */ + public int getRendererSupport(int rendererIndex) { + int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS; + int[][] rendererFormatSupport = formatSupport[rendererIndex]; + for (int i = 0; i < rendererFormatSupport.length; i++) { + for (int j = 0; j < rendererFormatSupport[i].length; j++) { + int trackRendererSupport; + switch (rendererFormatSupport[i][j] & RendererCapabilities.FORMAT_SUPPORT_MASK) { + case RendererCapabilities.FORMAT_HANDLED: + return RENDERER_SUPPORT_PLAYABLE_TRACKS; + case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES: + trackRendererSupport = RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS; + break; + default: + trackRendererSupport = RENDERER_SUPPORT_UNSUPPORTED_TRACKS; + break; + } + bestRendererSupport = Math.max(bestRendererSupport, trackRendererSupport); + } + } + return bestRendererSupport; + } + + /** + * Returns the best level of support obtained from {@link #getRendererSupport(int)} for all + * renderers of the specified track type. If no renderers exist for the specified type then + * {@link #RENDERER_SUPPORT_NO_TRACKS} is returned. + * + * @param trackType The track type. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, + * {@link #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS}, + * {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. + */ + public int getTrackTypeRendererSupport(int trackType) { + int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS; + for (int i = 0; i < length; i++) { + if (rendererTrackTypes[i] == trackType) { + bestRendererSupport = Math.max(bestRendererSupport, getRendererSupport(i)); + } + } + return bestRendererSupport; + } + + /** + * Returns the extent to which the format of an individual track is supported by the renderer. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the group to which the track belongs. + * @param trackIndex The index of the track within the group. + * @return One of {@link RendererCapabilities#FORMAT_HANDLED}, + * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}, + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} and + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE}. + */ + public int getTrackFormatSupport(int rendererIndex, int groupIndex, int trackIndex) { + return formatSupport[rendererIndex][groupIndex][trackIndex] + & RendererCapabilities.FORMAT_SUPPORT_MASK; + } + + /** + * Returns the extent to which the renderer supports adaptation between supported tracks in a + * specified {@link TrackGroup}. + *

    + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_HANDLED} are always considered. + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} or + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} are never considered. + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES} are considered only if + * {@code includeCapabilitiesExceededTracks} is set to {@code true}. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the group. + * @param includeCapabilitiesExceededTracks True if formats that exceed the capabilities of the + * renderer should be included when determining support. False otherwise. + * @return One of {@link RendererCapabilities#ADAPTIVE_SEAMLESS}, + * {@link RendererCapabilities#ADAPTIVE_NOT_SEAMLESS} and + * {@link RendererCapabilities#ADAPTIVE_NOT_SUPPORTED}. + */ + public int getAdaptiveSupport(int rendererIndex, int groupIndex, + boolean includeCapabilitiesExceededTracks) { + int trackCount = trackGroups[rendererIndex].get(groupIndex).length; + // Iterate over the tracks in the group, recording the indices of those to consider. + int[] trackIndices = new int[trackCount]; + int trackIndexCount = 0; + for (int i = 0; i < trackCount; i++) { + int fixedSupport = getTrackFormatSupport(rendererIndex, groupIndex, i); + if (fixedSupport == RendererCapabilities.FORMAT_HANDLED + || (includeCapabilitiesExceededTracks + && fixedSupport == RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES)) { + trackIndices[trackIndexCount++] = i; + } + } + trackIndices = Arrays.copyOf(trackIndices, trackIndexCount); + return getAdaptiveSupport(rendererIndex, groupIndex, trackIndices); + } + + /** + * Returns the extent to which the renderer supports adaptation between specified tracks within + * a {@link TrackGroup}. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the group. + * @return One of {@link RendererCapabilities#ADAPTIVE_SEAMLESS}, + * {@link RendererCapabilities#ADAPTIVE_NOT_SEAMLESS} and + * {@link RendererCapabilities#ADAPTIVE_NOT_SUPPORTED}. + */ + public int getAdaptiveSupport(int rendererIndex, int groupIndex, int[] trackIndices) { + int handledTrackCount = 0; + int adaptiveSupport = RendererCapabilities.ADAPTIVE_SEAMLESS; + boolean multipleMimeTypes = false; + String firstSampleMimeType = null; + for (int i = 0; i < trackIndices.length; i++) { + int trackIndex = trackIndices[i]; + String sampleMimeType = trackGroups[rendererIndex].get(groupIndex).getFormat(trackIndex) + .sampleMimeType; + if (handledTrackCount++ == 0) { + firstSampleMimeType = sampleMimeType; + } else { + multipleMimeTypes |= !Util.areEqual(firstSampleMimeType, sampleMimeType); + } + adaptiveSupport = Math.min(adaptiveSupport, formatSupport[rendererIndex][groupIndex][i] + & RendererCapabilities.ADAPTIVE_SUPPORT_MASK); + } + return multipleMimeTypes + ? Math.min(adaptiveSupport, mixedMimeTypeAdaptiveSupport[rendererIndex]) + : adaptiveSupport; + } + + /** + * Returns the {@link TrackGroup}s not associated with any renderer. + */ + public TrackGroupArray getUnassociatedTrackGroups() { + return unassociatedTrackGroups; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/RandomTrackSelection.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/RandomTrackSelection.java new file mode 100644 index 0000000..cb3893b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/RandomTrackSelection.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import java.util.Random; + +/** + * A {@link TrackSelection} whose selected track is updated randomly. + */ +public final class RandomTrackSelection extends BaseTrackSelection { + + /** + * Factory for {@link RandomTrackSelection} instances. + */ + public static final class Factory implements TrackSelection.Factory { + + private final Random random; + + public Factory() { + random = new Random(); + } + + /** + * @param seed A seed for the {@link Random} instance used by the factory. + */ + public Factory(int seed) { + random = new Random(seed); + } + + @Override + public RandomTrackSelection createTrackSelection(TrackGroup group, int... tracks) { + return new RandomTrackSelection(group, tracks, random); + } + + } + + private final Random random; + + private int selectedIndex; + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + */ + public RandomTrackSelection(TrackGroup group, int... tracks) { + super(group, tracks); + random = new Random(); + selectedIndex = random.nextInt(length); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @param seed A seed for the {@link Random} instance used to update the selected track. + */ + public RandomTrackSelection(TrackGroup group, int[] tracks, long seed) { + this(group, tracks, new Random(seed)); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @param random A source of random numbers. + */ + public RandomTrackSelection(TrackGroup group, int[] tracks, Random random) { + super(group, tracks); + this.random = random; + selectedIndex = random.nextInt(length); + } + + @Override + public void updateSelectedTrack(long bufferedDurationUs) { + // Count the number of non-blacklisted formats. + long nowMs = SystemClock.elapsedRealtime(); + int nonBlacklistedFormatCount = 0; + for (int i = 0; i < length; i++) { + if (!isBlacklisted(i, nowMs)) { + nonBlacklistedFormatCount++; + } + } + + selectedIndex = random.nextInt(nonBlacklistedFormatCount); + if (nonBlacklistedFormatCount != length) { + // Adjust the format index to account for blacklisted formats. + nonBlacklistedFormatCount = 0; + for (int i = 0; i < length; i++) { + if (!isBlacklisted(i, nowMs) && selectedIndex == nonBlacklistedFormatCount++) { + selectedIndex = i; + return; + } + } + } + } + + @Override + public int getSelectedIndex() { + return selectedIndex; + } + + @Override + public int getSelectionReason() { + return C.SELECTION_REASON_ADAPTIVE; + } + + @Override + public Object getSelectionData() { + return null; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelection.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelection.java new file mode 100644 index 0000000..b697dc3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelection.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroup; +import com.tangxiaolv.telegramgallery.exoplayer2.source.chunk.MediaChunk; +import java.util.List; + +/** + * A track selection consisting of a static subset of selected tracks belonging to a + * {@link TrackGroup}, and a possibly varying individual selected track from the subset. + *

    + * Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual selected + * track may change as a result of calling {@link #updateSelectedTrack(long)}. + */ +public interface TrackSelection { + + /** + * Factory for {@link TrackSelection} instances. + */ + interface Factory { + + /** + * Creates a new selection. + * + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @return The created selection. + */ + TrackSelection createTrackSelection(TrackGroup group, int... tracks); + + } + + /** + * Returns the {@link TrackGroup} to which the selected tracks belong. + */ + TrackGroup getTrackGroup(); + + // Static subset of selected tracks. + + /** + * Returns the number of tracks in the selection. + */ + int length(); + + /** + * Returns the format of the track at a given index in the selection. + * + * @param index The index in the selection. + * @return The format of the selected track. + */ + Format getFormat(int index); + + /** + * Returns the index in the track group of the track at a given index in the selection. + * + * @param index The index in the selection. + * @return The index of the selected track. + */ + int getIndexInTrackGroup(int index); + + /** + * Returns the index in the selection of the track with the specified format. + * + * @param format The format. + * @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified + * format is not part of the selection. + */ + int indexOf(Format format); + + /** + * Returns the index in the selection of the track with the specified index in the track group. + * + * @param indexInTrackGroup The index in the track group. + * @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified + * index is not part of the selection. + */ + int indexOf(int indexInTrackGroup); + + // Individual selected track. + + /** + * Returns the {@link Format} of the individual selected track. + */ + Format getSelectedFormat(); + + /** + * Returns the index in the track group of the individual selected track. + */ + int getSelectedIndexInTrackGroup(); + + /** + * Returns the index of the selected track. + */ + int getSelectedIndex(); + + /** + * Returns the reason for the current track selection. + */ + int getSelectionReason(); + + /** + * Returns optional data associated with the current track selection. + */ + Object getSelectionData(); + + // Adaptation. + + /** + * Updates the selected track. + * + * @param bufferedDurationUs The duration of media currently buffered in microseconds. + */ + void updateSelectedTrack(long bufferedDurationUs); + + /** + * May be called periodically by sources that load media in discrete {@link MediaChunk}s and + * support discarding of buffered chunks in order to re-buffer using a different selected track. + * Returns the number of chunks that should be retained in the queue. + *

    + * To avoid excessive re-buffering, implementations should normally return the size of the queue. + * An example of a case where a smaller value may be returned is if network conditions have + * improved dramatically, allowing chunks to be discarded and re-buffered in a track of + * significantly higher quality. Discarding chunks may allow faster switching to a higher quality + * track in this case. + * + * @param playbackPositionUs The current playback position in microseconds. + * @param queue The queue of buffered {@link MediaChunk}s. Must not be modified. + * @return The number of chunks to retain in the queue. + */ + int evaluateQueueSize(long playbackPositionUs, List queue); + + /** + * Attempts to blacklist the track at the specified index in the selection, making it ineligible + * for selection by calls to {@link #updateSelectedTrack(long)} for the specified period of time. + * Blacklisting will fail if all other tracks are currently blacklisted. If blacklisting the + * currently selected track, note that it will remain selected until the next call to + * {@link #updateSelectedTrack(long)}. + * + * @param index The index of the track in the selection. + * @param blacklistDurationMs The duration of time for which the track should be blacklisted, in + * milliseconds. + * @return Whether blacklisting was successful. + */ + boolean blacklist(int index, long blacklistDurationMs); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelectionArray.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelectionArray.java new file mode 100644 index 0000000..5765a01 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelectionArray.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import java.util.Arrays; + +/** + * The result of a {@link TrackSelector} operation. + */ +public final class TrackSelectionArray { + + /** + * The number of selections in the result. Greater than or equal to zero. + */ + public final int length; + + private final TrackSelection[] trackSelections; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param trackSelections The selections. Must not be null, but may contain null elements. + */ + public TrackSelectionArray(TrackSelection... trackSelections) { + this.trackSelections = trackSelections; + this.length = trackSelections.length; + } + + /** + * Returns the selection at a given index. + * + * @param index The index of the selection. + * @return The selection. + */ + public TrackSelection get(int index) { + return trackSelections[index]; + } + + /** + * Returns the selections in a newly allocated array. + */ + public TrackSelection[] getAll() { + return trackSelections.clone(); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + Arrays.hashCode(trackSelections); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackSelectionArray other = (TrackSelectionArray) obj; + return Arrays.equals(trackSelections, other.trackSelections); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelector.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelector.java new file mode 100644 index 0000000..d539f91 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelector.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.RendererCapabilities; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; + +/** Selects tracks to be consumed by available renderers. */ +public abstract class TrackSelector { + + /** + * Notified when previous selections by a {@link TrackSelector} are no longer valid. + */ + public interface InvalidationListener { + + /** + * Called by a {@link TrackSelector} when previous selections are no longer valid. + */ + void onTrackSelectionsInvalidated(); + + } + + private InvalidationListener listener; + + /** + * Initializes the selector. + * + * @param listener A listener for the selector. + */ + public final void init(InvalidationListener listener) { + this.listener = listener; + } + + /** + * Performs a track selection for renderers. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which tracks + * are to be selected. + * @param trackGroups The available track groups. + * @return A {@link TrackSelectorResult} describing the track selections. + * @throws ExoPlaybackException If an error occurs selecting tracks. + */ + public abstract TrackSelectorResult selectTracks(RendererCapabilities[] rendererCapabilities, + TrackGroupArray trackGroups) throws ExoPlaybackException; + + /** + * Called when a {@link TrackSelectorResult} previously generated by + * {@link #selectTracks(RendererCapabilities[], TrackGroupArray)} is activated. + * + * @param info The value of {@link TrackSelectorResult#info} in the activated result. + */ + public abstract void onSelectionActivated(Object info); + + /** + * Invalidates all previously generated track selections. + */ + protected final void invalidate() { + if (listener != null) { + listener.onTrackSelectionsInvalidated(); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelectorResult.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelectorResult.java new file mode 100644 index 0000000..31eb98b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/trackselection/TrackSelectorResult.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.trackselection; + +import com.tangxiaolv.telegramgallery.exoplayer2.RendererConfiguration; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * The result of a {@link TrackSelector} operation. + */ +public final class TrackSelectorResult { + + /** + * The groups provided to the {@link TrackSelector}. + */ + public final TrackGroupArray groups; + /** + * A {@link TrackSelectionArray} containing the selection for each renderer. + */ + public final TrackSelectionArray selections; + /** + * An opaque object that will be returned to {@link TrackSelector#onSelectionActivated(Object)} + * should the selections be activated. + */ + public final Object info; + /** + * A {@link RendererConfiguration} for each renderer, to be used with the selections. + */ + public final RendererConfiguration[] rendererConfigurations; + + /** + * @param groups The groups provided to the {@link TrackSelector}. + * @param selections A {@link TrackSelectionArray} containing the selection for each renderer. + * @param info An opaque object that will be returned to + * {@link TrackSelector#onSelectionActivated(Object)} should the selections be activated. + * @param rendererConfigurations A {@link RendererConfiguration} for each renderer, to be used + * with the selections. + */ + public TrackSelectorResult(TrackGroupArray groups, TrackSelectionArray selections, Object info, + RendererConfiguration[] rendererConfigurations) { + this.groups = groups; + this.selections = selections; + this.info = info; + this.rendererConfigurations = rendererConfigurations; + } + + /** + * Returns whether this result is equivalent to {@code other} for all renderers. + * + * @param other The other {@link TrackSelectorResult}. May be null, in which case {@code false} + * will be returned in all cases. + * @return Whether this result is equivalent to {@code other} for all renderers. + */ + public boolean isEquivalent(TrackSelectorResult other) { + if (other == null) { + return false; + } + for (int i = 0; i < selections.length; i++) { + if (!isEquivalent(other, i)) { + return false; + } + } + return true; + } + + /** + * Returns whether this result is equivalent to {@code other} for the renderer at the given index. + * The results are equivalent if they have equal track selections and configurations for the + * renderer. + * + * @param other The other {@link TrackSelectorResult}. May be null, in which case {@code false} + * will be returned in all cases. + * @param index The renderer index to check for equivalence. + * @return Whether this result is equivalent to {@code other} for all renderers. + */ + public boolean isEquivalent(TrackSelectorResult other, int index) { + if (other == null) { + return false; + } + return Util.areEqual(selections.get(index), other.selections.get(index)) + && Util.areEqual(rendererConfigurations[index], other.rendererConfigurations[index]); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/AspectRatioFrameLayout.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/AspectRatioFrameLayout.java new file mode 100644 index 0000000..b5270c2 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/AspectRatioFrameLayout.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.ui; + +import android.content.Context; +import android.graphics.Matrix; +import android.support.annotation.IntDef; +import android.util.AttributeSet; +import android.view.TextureView; +import android.view.View; +import android.widget.FrameLayout; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A {@link FrameLayout} that resizes itself to match a specified aspect ratio. + */ +public class AspectRatioFrameLayout extends FrameLayout { + + /** + * Resize modes for {@link AspectRatioFrameLayout}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL}) + public @interface ResizeMode {} + + /** + * Either the width or height is decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIT = 0; + /** + * The width is fixed and the height is increased or decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIXED_WIDTH = 1; + /** + * The height is fixed and the width is increased or decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIXED_HEIGHT = 2; + /** + * The specified aspect ratio is ignored. + */ + public static final int RESIZE_MODE_FILL = 3; + + /** + * The {@link FrameLayout} will not resize itself if the fractional difference between its natural + * aspect ratio and the requested aspect ratio falls below this threshold. + *

    + * This tolerance allows the view to occupy the whole of the screen when the requested aspect + * ratio is very close, but not exactly equal to, the aspect ratio of the screen. This may reduce + * the number of view layers that need to be composited by the underlying system, which can help + * to reduce power consumption. + */ + private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f; + + private float videoAspectRatio; + private int resizeMode; + private boolean drawingReady; + private int rotation; + private Matrix matrix = new Matrix(); + + public AspectRatioFrameLayout(Context context) { + this(context, null); + } + + public AspectRatioFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + resizeMode = RESIZE_MODE_FIT; + } + + public void setDrawingReady(boolean value) { + if (drawingReady == value) { + return; + } + drawingReady = value; + } + + public boolean isDrawingReady() { + return drawingReady; + } + + /** + * Set the aspect ratio that this view should satisfy. + * + * @param widthHeightRatio The width to height ratio. + */ + public void setAspectRatio(float widthHeightRatio, int rotation) { + if (this.videoAspectRatio != widthHeightRatio || this.rotation != rotation) { + this.videoAspectRatio = widthHeightRatio; + this.rotation = rotation; + requestLayout(); + } + } + + public float getAspectRatio() { + return videoAspectRatio; + } + + public int getVideoRotation() { + return rotation; + } + + /** + * Sets the resize mode. + * + * @param resizeMode The resize mode. + */ + public void setResizeMode(@ResizeMode int resizeMode) { + if (this.resizeMode != resizeMode) { + this.resizeMode = resizeMode; + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (resizeMode == RESIZE_MODE_FILL || videoAspectRatio <= 0) { + // Aspect ratio not set. + return; + } + + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + float viewAspectRatio = (float) width / height; + float aspectDeformation = videoAspectRatio / viewAspectRatio - 1; + if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) { + // We're within the allowed tolerance. + return; + } + + switch (resizeMode) { + case RESIZE_MODE_FIXED_WIDTH: + height = (int) (width / videoAspectRatio); + break; + case RESIZE_MODE_FIXED_HEIGHT: + width = (int) (height * videoAspectRatio); + break; + default: + if (aspectDeformation > 0) { + height = (int) (width / videoAspectRatio); + } else { + width = (int) (height * videoAspectRatio); + } + break; + } + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View child = getChildAt(a); + if (child instanceof TextureView) { + matrix.reset(); + int px = getWidth() / 2; + int py = getHeight() / 2; + matrix.postRotate(rotation, px, py); + if (rotation == 90 || rotation == 270) { + float ratio = (float) getHeight() / getWidth(); + matrix.postScale(1 / ratio, ratio, px, py); + } + ((TextureView) child).setTransform(matrix); + break; + } + } + } +} \ No newline at end of file diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/DebugTextViewHelper.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/DebugTextViewHelper.java new file mode 100644 index 0000000..a0d0666 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/DebugTextViewHelper.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.ui; + +import android.widget.TextView; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.PlaybackParameters; +import com.tangxiaolv.telegramgallery.exoplayer2.SimpleExoPlayer; +import com.tangxiaolv.telegramgallery.exoplayer2.Timeline; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderCounters; +import com.tangxiaolv.telegramgallery.exoplayer2.source.TrackGroupArray; +import com.tangxiaolv.telegramgallery.exoplayer2.trackselection.TrackSelectionArray; + +/** + * A helper class for periodically updating a {@link TextView} with debug information obtained from + * a {@link SimpleExoPlayer}. + */ +public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListener { + + private static final int REFRESH_INTERVAL_MS = 1000; + + private final SimpleExoPlayer player; + private final TextView textView; + + private boolean started; + + /** + * @param player The {@link SimpleExoPlayer} from which debug information should be obtained. + * @param textView The {@link TextView} that should be updated to display the information. + */ + public DebugTextViewHelper(SimpleExoPlayer player, TextView textView) { + this.player = player; + this.textView = textView; + } + + /** + * Starts periodic updates of the {@link TextView}. Must be called from the application's main + * thread. + */ + public void start() { + if (started) { + return; + } + started = true; + player.addListener(this); + updateAndPost(); + } + + /** + * Stops periodic updates of the {@link TextView}. Must be called from the application's main + * thread. + */ + public void stop() { + if (!started) { + return; + } + started = false; + player.removeListener(this); + textView.removeCallbacks(this); + } + + // ExoPlayer.EventListener implementation. + + @Override + public void onLoadingChanged(boolean isLoading) { + // Do nothing. + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + updateAndPost(); + } + + @Override + public void onPositionDiscontinuity() { + updateAndPost(); + } + + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + // Do nothing. + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + // Do nothing. + } + + @Override + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { + // Do nothing. + } + + // Runnable implementation. + + @Override + public void run() { + updateAndPost(); + } + + // Private methods. + + private void updateAndPost() { + textView.setText(getPlayerStateString() + getPlayerWindowIndexString() + getVideoString() + + getAudioString()); + textView.removeCallbacks(this); + textView.postDelayed(this, REFRESH_INTERVAL_MS); + } + + private String getPlayerStateString() { + String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:"; + switch (player.getPlaybackState()) { + case ExoPlayer.STATE_BUFFERING: + text += "buffering"; + break; + case ExoPlayer.STATE_ENDED: + text += "ended"; + break; + case ExoPlayer.STATE_IDLE: + text += "idle"; + break; + case ExoPlayer.STATE_READY: + text += "ready"; + break; + default: + text += "unknown"; + break; + } + return text; + } + + private String getPlayerWindowIndexString() { + return " window:" + player.getCurrentWindowIndex(); + } + + private String getVideoString() { + Format format = player.getVideoFormat(); + if (format == null) { + return ""; + } + return "\n" + format.sampleMimeType + "(id:" + format.id + " r:" + format.width + "x" + + format.height + getDecoderCountersBufferCountString(player.getVideoDecoderCounters()) + + ")"; + } + + private String getAudioString() { + Format format = player.getAudioFormat(); + if (format == null) { + return ""; + } + return "\n" + format.sampleMimeType + "(id:" + format.id + " hz:" + format.sampleRate + " ch:" + + format.channelCount + + getDecoderCountersBufferCountString(player.getAudioDecoderCounters()) + ")"; + } + + private static String getDecoderCountersBufferCountString(DecoderCounters counters) { + if (counters == null) { + return ""; + } + counters.ensureUpdated(); + return " rb:" + counters.renderedOutputBufferCount + + " sb:" + counters.skippedOutputBufferCount + + " db:" + counters.droppedOutputBufferCount + + " mcdb:" + counters.maxConsecutiveDroppedOutputBufferCount; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/SubtitlePainter.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/SubtitlePainter.java new file mode 100644 index 0000000..15fc632 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/SubtitlePainter.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.ui; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Join; +import android.graphics.Paint.Style; +import android.graphics.Rect; +import android.graphics.RectF; +import android.text.Layout.Alignment; +import android.text.SpannableStringBuilder; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.RelativeSizeSpan; +import android.util.DisplayMetrics; +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.text.CaptionStyleCompat; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +/** + * Paints subtitle {@link Cue}s. + */ +/* package */ final class SubtitlePainter { + + private static final String TAG = "SubtitlePainter"; + + /** + * Ratio of inner padding to font size. + */ + private static final float INNER_PADDING_RATIO = 0.125f; + + /** + * Temporary rectangle used for computing line bounds. + */ + private final RectF lineBounds = new RectF(); + + // Styled dimensions. + private final float cornerRadius; + private final float outlineWidth; + private final float shadowRadius; + private final float shadowOffset; + private final float spacingMult; + private final float spacingAdd; + + private final TextPaint textPaint; + private final Paint paint; + + // Previous input variables. + private CharSequence cueText; + private Alignment cueTextAlignment; + private Bitmap cueBitmap; + private float cueLine; + @Cue.LineType + private int cueLineType; + @Cue.AnchorType + private int cueLineAnchor; + private float cuePosition; + @Cue.AnchorType + private int cuePositionAnchor; + private float cueSize; + private float cueBitmapHeight; + private boolean applyEmbeddedStyles; + private boolean applyEmbeddedFontSizes; + private int foregroundColor; + private int backgroundColor; + private int windowColor; + private int edgeColor; + @CaptionStyleCompat.EdgeType + private int edgeType; + private float textSizePx; + private float bottomPaddingFraction; + private int parentLeft; + private int parentTop; + private int parentRight; + private int parentBottom; + + // Derived drawing variables. + private StaticLayout textLayout; + private int textLeft; + private int textTop; + private int textPaddingX; + private Rect bitmapRect; + + @SuppressWarnings("ResourceType") + public SubtitlePainter(Context context) { + int[] viewAttr = {android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; + TypedArray styledAttributes = context.obtainStyledAttributes(null, viewAttr, 0, 0); + spacingAdd = styledAttributes.getDimensionPixelSize(0, 0); + spacingMult = styledAttributes.getFloat(1, 1); + styledAttributes.recycle(); + + Resources resources = context.getResources(); + DisplayMetrics displayMetrics = resources.getDisplayMetrics(); + int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); + cornerRadius = twoDpInPx; + outlineWidth = twoDpInPx; + shadowRadius = twoDpInPx; + shadowOffset = twoDpInPx; + + textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setSubpixelText(true); + + paint = new Paint(); + paint.setAntiAlias(true); + paint.setStyle(Style.FILL); + } + + /** + * Draws the provided {@link Cue} into a canvas with the specified styling. + *

    + * A call to this method is able to use cached results of calculations made during the previous + * call, and so an instance of this class is able to optimize repeated calls to this method in + * which the same parameters are passed. + * + * @param cue The cue to draw. + * @param applyEmbeddedStyles Whether styling embedded within the cue should be applied. + * @param applyEmbeddedFontSizes If {@code applyEmbeddedStyles} is true, defines whether font + * sizes embedded within the cue should be applied. Otherwise, it is ignored. + * @param style The style to use when drawing the cue text. + * @param textSizePx The text size to use when drawing the cue text, in pixels. + * @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is + * {@link Cue#DIMEN_UNSET}, as a fraction of the viewport height + * @param canvas The canvas into which to draw. + * @param cueBoxLeft The left position of the enclosing cue box. + * @param cueBoxTop The top position of the enclosing cue box. + * @param cueBoxRight The right position of the enclosing cue box. + * @param cueBoxBottom The bottom position of the enclosing cue box. + */ + public void draw(Cue cue, boolean applyEmbeddedStyles, boolean applyEmbeddedFontSizes, + CaptionStyleCompat style, float textSizePx, float bottomPaddingFraction, Canvas canvas, + int cueBoxLeft, int cueBoxTop, int cueBoxRight, int cueBoxBottom) { + boolean isTextCue = cue.bitmap == null; + int windowColor = Color.BLACK; + if (isTextCue) { + if (TextUtils.isEmpty(cue.text)) { + // Nothing to draw. + return; + } + windowColor = (cue.windowColorSet && applyEmbeddedStyles) + ? cue.windowColor : style.windowColor; + } + if (areCharSequencesEqual(this.cueText, cue.text) + && Util.areEqual(this.cueTextAlignment, cue.textAlignment) + && this.cueBitmap == cue.bitmap + && this.cueLine == cue.line + && this.cueLineType == cue.lineType + && Util.areEqual(this.cueLineAnchor, cue.lineAnchor) + && this.cuePosition == cue.position + && Util.areEqual(this.cuePositionAnchor, cue.positionAnchor) + && this.cueSize == cue.size + && this.cueBitmapHeight == cue.bitmapHeight + && this.applyEmbeddedStyles == applyEmbeddedStyles + && this.applyEmbeddedFontSizes == applyEmbeddedFontSizes + && this.foregroundColor == style.foregroundColor + && this.backgroundColor == style.backgroundColor + && this.windowColor == windowColor + && this.edgeType == style.edgeType + && this.edgeColor == style.edgeColor + && Util.areEqual(this.textPaint.getTypeface(), style.typeface) + && this.textSizePx == textSizePx + && this.bottomPaddingFraction == bottomPaddingFraction + && this.parentLeft == cueBoxLeft + && this.parentTop == cueBoxTop + && this.parentRight == cueBoxRight + && this.parentBottom == cueBoxBottom) { + // We can use the cached layout. + drawLayout(canvas, isTextCue); + return; + } + + this.cueText = cue.text; + this.cueTextAlignment = cue.textAlignment; + this.cueBitmap = cue.bitmap; + this.cueLine = cue.line; + this.cueLineType = cue.lineType; + this.cueLineAnchor = cue.lineAnchor; + this.cuePosition = cue.position; + this.cuePositionAnchor = cue.positionAnchor; + this.cueSize = cue.size; + this.cueBitmapHeight = cue.bitmapHeight; + this.applyEmbeddedStyles = applyEmbeddedStyles; + this.applyEmbeddedFontSizes = applyEmbeddedFontSizes; + this.foregroundColor = style.foregroundColor; + this.backgroundColor = style.backgroundColor; + this.windowColor = windowColor; + this.edgeType = style.edgeType; + this.edgeColor = style.edgeColor; + this.textPaint.setTypeface(style.typeface); + this.textSizePx = textSizePx; + this.bottomPaddingFraction = bottomPaddingFraction; + this.parentLeft = cueBoxLeft; + this.parentTop = cueBoxTop; + this.parentRight = cueBoxRight; + this.parentBottom = cueBoxBottom; + + if (isTextCue) { + setupTextLayout(); + } else { + setupBitmapLayout(); + } + drawLayout(canvas, isTextCue); + } + + private void setupTextLayout() { + int parentWidth = parentRight - parentLeft; + int parentHeight = parentBottom - parentTop; + + textPaint.setTextSize(textSizePx); + int textPaddingX = (int) (textSizePx * INNER_PADDING_RATIO + 0.5f); + + int availableWidth = parentWidth - textPaddingX * 2; + if (cueSize != Cue.DIMEN_UNSET) { + availableWidth = (int) (availableWidth * cueSize); + } + if (availableWidth <= 0) { + Log.w(TAG, "Skipped drawing subtitle cue (insufficient space)"); + return; + } + + // Remove embedded styling or font size if requested. + CharSequence cueText; + if (applyEmbeddedFontSizes && applyEmbeddedStyles) { + cueText = this.cueText; + } else if (!applyEmbeddedStyles) { + cueText = this.cueText.toString(); // Equivalent to erasing all spans. + } else { + SpannableStringBuilder newCueText = new SpannableStringBuilder(this.cueText); + int cueLength = newCueText.length(); + AbsoluteSizeSpan[] absSpans = newCueText.getSpans(0, cueLength, AbsoluteSizeSpan.class); + RelativeSizeSpan[] relSpans = newCueText.getSpans(0, cueLength, RelativeSizeSpan.class); + for (AbsoluteSizeSpan absSpan : absSpans) { + newCueText.removeSpan(absSpan); + } + for (RelativeSizeSpan relSpan : relSpans) { + newCueText.removeSpan(relSpan); + } + cueText = newCueText; + } + + Alignment textAlignment = cueTextAlignment == null ? Alignment.ALIGN_CENTER : cueTextAlignment; + textLayout = new StaticLayout(cueText, textPaint, availableWidth, textAlignment, spacingMult, + spacingAdd, true); + int textHeight = textLayout.getHeight(); + int textWidth = 0; + int lineCount = textLayout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + textWidth = Math.max((int) Math.ceil(textLayout.getLineWidth(i)), textWidth); + } + if (cueSize != Cue.DIMEN_UNSET && textWidth < availableWidth) { + textWidth = availableWidth; + } + textWidth += textPaddingX * 2; + + int textLeft; + int textRight; + if (cuePosition != Cue.DIMEN_UNSET) { + int anchorPosition = Math.round(parentWidth * cuePosition) + parentLeft; + textLeft = cuePositionAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textWidth + : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textWidth) / 2 + : anchorPosition; + textLeft = Math.max(textLeft, parentLeft); + textRight = Math.min(textLeft + textWidth, parentRight); + } else { + textLeft = (parentWidth - textWidth) / 2; + textRight = textLeft + textWidth; + } + + textWidth = textRight - textLeft; + if (textWidth <= 0) { + Log.w(TAG, "Skipped drawing subtitle cue (invalid horizontal positioning)"); + return; + } + + int textTop; + if (cueLine != Cue.DIMEN_UNSET) { + int anchorPosition; + if (cueLineType == Cue.LINE_TYPE_FRACTION) { + anchorPosition = Math.round(parentHeight * cueLine) + parentTop; + } else { + // cueLineType == Cue.LINE_TYPE_NUMBER + int firstLineHeight = textLayout.getLineBottom(0) - textLayout.getLineTop(0); + if (cueLine >= 0) { + anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop; + } else { + anchorPosition = Math.round((cueLine + 1) * firstLineHeight) + parentBottom; + } + } + textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight + : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textHeight) / 2 + : anchorPosition; + if (textTop + textHeight > parentBottom) { + textTop = parentBottom - textHeight; + } else if (textTop < parentTop) { + textTop = parentTop; + } + } else { + textTop = parentBottom - textHeight - (int) (parentHeight * bottomPaddingFraction); + } + + // Update the derived drawing variables. + this.textLayout = new StaticLayout(cueText, textPaint, textWidth, textAlignment, spacingMult, + spacingAdd, true); + this.textLeft = textLeft; + this.textTop = textTop; + this.textPaddingX = textPaddingX; + } + + private void setupBitmapLayout() { + int parentWidth = parentRight - parentLeft; + int parentHeight = parentBottom - parentTop; + float anchorX = parentLeft + (parentWidth * cuePosition); + float anchorY = parentTop + (parentHeight * cueLine); + int width = Math.round(parentWidth * cueSize); + int height = cueBitmapHeight != Cue.DIMEN_UNSET ? Math.round(parentHeight * cueBitmapHeight) + : Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth())); + int x = Math.round(cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width) + : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX); + int y = Math.round(cuePositionAnchor == Cue.ANCHOR_TYPE_END ? (anchorY - height) + : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorY - (height / 2)) : anchorY); + bitmapRect = new Rect(x, y, x + width, y + height); + } + + private void drawLayout(Canvas canvas, boolean isTextCue) { + if (isTextCue) { + drawTextLayout(canvas); + } else { + drawBitmapLayout(canvas); + } + } + + private void drawTextLayout(Canvas canvas) { + StaticLayout layout = textLayout; + if (layout == null) { + // Nothing to draw. + return; + } + + int saveCount = canvas.save(); + canvas.translate(textLeft, textTop); + + if (Color.alpha(windowColor) > 0) { + paint.setColor(windowColor); + canvas.drawRect(-textPaddingX, 0, layout.getWidth() + textPaddingX, layout.getHeight(), + paint); + } + + if (Color.alpha(backgroundColor) > 0) { + paint.setColor(backgroundColor); + float previousBottom = layout.getLineTop(0); + int lineCount = layout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + lineBounds.left = layout.getLineLeft(i) - textPaddingX; + lineBounds.right = layout.getLineRight(i) + textPaddingX; + lineBounds.top = previousBottom; + lineBounds.bottom = layout.getLineBottom(i); + previousBottom = lineBounds.bottom; + canvas.drawRoundRect(lineBounds, cornerRadius, cornerRadius, paint); + } + } + + if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { + textPaint.setStrokeJoin(Join.ROUND); + textPaint.setStrokeWidth(outlineWidth); + textPaint.setColor(edgeColor); + textPaint.setStyle(Style.FILL_AND_STROKE); + layout.draw(canvas); + } else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { + textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor); + } else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED + || edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { + boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; + int colorUp = raised ? Color.WHITE : edgeColor; + int colorDown = raised ? edgeColor : Color.WHITE; + float offset = shadowRadius / 2f; + textPaint.setColor(foregroundColor); + textPaint.setStyle(Style.FILL); + textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp); + layout.draw(canvas); + textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown); + } + + textPaint.setColor(foregroundColor); + textPaint.setStyle(Style.FILL); + layout.draw(canvas); + textPaint.setShadowLayer(0, 0, 0, 0); + + canvas.restoreToCount(saveCount); + } + + private void drawBitmapLayout(Canvas canvas) { + canvas.drawBitmap(cueBitmap, null, bitmapRect, null); + } + + /** + * This method is used instead of {@link TextUtils#equals(CharSequence, CharSequence)} because the + * latter only checks the text of each sequence, and does not check for equality of styling that + * may be embedded within the {@link CharSequence}s. + */ + private static boolean areCharSequencesEqual(CharSequence first, CharSequence second) { + // Some CharSequence implementations don't perform a cheap referential equality check in their + // equals methods, so we perform one explicitly here. + return first == second || (first != null && first.equals(second)); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/SubtitleView.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/SubtitleView.java new file mode 100644 index 0000000..fd51dda --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/SubtitleView.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.view.accessibility.CaptioningManager; +import com.tangxiaolv.telegramgallery.exoplayer2.text.CaptionStyleCompat; +import com.tangxiaolv.telegramgallery.exoplayer2.text.Cue; +import com.tangxiaolv.telegramgallery.exoplayer2.text.TextRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.List; + +/** + * A view for displaying subtitle {@link Cue}s. + */ +public final class SubtitleView extends View implements TextRenderer.Output { + + /** + * The default fractional text size. + * + * @see #setFractionalTextSize(float, boolean) + */ + public static final float DEFAULT_TEXT_SIZE_FRACTION = 0.0533f; + + /** + * The default bottom padding to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, as a + * fraction of the viewport height. + * + * @see #setBottomPaddingFraction(float) + */ + public static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f; + + private static final int FRACTIONAL = 0; + private static final int FRACTIONAL_IGNORE_PADDING = 1; + private static final int ABSOLUTE = 2; + + private final List painters; + + private List cues; + private int textSizeType; + private float textSize; + private boolean applyEmbeddedStyles; + private boolean applyEmbeddedFontSizes; + private CaptionStyleCompat style; + private float bottomPaddingFraction; + + public SubtitleView(Context context) { + this(context, null); + } + + public SubtitleView(Context context, AttributeSet attrs) { + super(context, attrs); + painters = new ArrayList<>(); + textSizeType = FRACTIONAL; + textSize = DEFAULT_TEXT_SIZE_FRACTION; + applyEmbeddedStyles = true; + applyEmbeddedFontSizes = true; + style = CaptionStyleCompat.DEFAULT; + bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION; + } + + @Override + public void onCues(List cues) { + setCues(cues); + } + + /** + * Sets the cues to be displayed by the view. + * + * @param cues The cues to display. + */ + public void setCues(List cues) { + if (this.cues == cues) { + return; + } + this.cues = cues; + // Ensure we have sufficient painters. + int cueCount = (cues == null) ? 0 : cues.size(); + while (painters.size() < cueCount) { + painters.add(new SubtitlePainter(getContext())); + } + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Set the text size to a given unit and value. + *

    + * See {@link TypedValue} for the possible dimension units. + * + * @param unit The desired dimension unit. + * @param size The desired size in the given units. + */ + public void setFixedTextSize(int unit, float size) { + Context context = getContext(); + Resources resources; + if (context == null) { + resources = Resources.getSystem(); + } else { + resources = context.getResources(); + } + setTextSize(ABSOLUTE, TypedValue.applyDimension(unit, size, resources.getDisplayMetrics())); + } + + /** + * Sets the text size to one derived from {@link CaptioningManager#getFontScale()}, or to a + * default size before API level 19. + */ + public void setUserDefaultTextSize() { + float fontScale = Util.SDK_INT >= 19 && !isInEditMode() ? getUserCaptionFontScaleV19() : 1f; + setFractionalTextSize(DEFAULT_TEXT_SIZE_FRACTION * fontScale); + } + + /** + * Sets the text size to be a fraction of the view's remaining height after its top and bottom + * padding have been subtracted. + *

    + * Equivalent to {@code #setFractionalTextSize(fractionOfHeight, false)}. + * + * @param fractionOfHeight A fraction between 0 and 1. + */ + public void setFractionalTextSize(float fractionOfHeight) { + setFractionalTextSize(fractionOfHeight, false); + } + + /** + * Sets the text size to be a fraction of the height of this view. + * + * @param fractionOfHeight A fraction between 0 and 1. + * @param ignorePadding Set to true if {@code fractionOfHeight} should be interpreted as a + * fraction of this view's height ignoring any top and bottom padding. Set to false if + * {@code fractionOfHeight} should be interpreted as a fraction of this view's remaining + * height after the top and bottom padding has been subtracted. + */ + public void setFractionalTextSize(float fractionOfHeight, boolean ignorePadding) { + setTextSize(ignorePadding ? FRACTIONAL_IGNORE_PADDING : FRACTIONAL, fractionOfHeight); + } + + private void setTextSize(int textSizeType, float textSize) { + if (this.textSizeType == textSizeType && this.textSize == textSize) { + return; + } + this.textSizeType = textSizeType; + this.textSize = textSize; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets whether styling embedded within the cues should be applied. Enabled by default. + * Overrides any setting made with {@link SubtitleView#setApplyEmbeddedFontSizes}. + * + * @param applyEmbeddedStyles Whether styling embedded within the cues should be applied. + */ + public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) { + if (this.applyEmbeddedStyles == applyEmbeddedStyles + && this.applyEmbeddedFontSizes == applyEmbeddedStyles) { + return; + } + this.applyEmbeddedStyles = applyEmbeddedStyles; + this.applyEmbeddedFontSizes = applyEmbeddedStyles; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets whether font sizes embedded within the cues should be applied. Enabled by default. + * Only takes effect if {@link SubtitleView#setApplyEmbeddedStyles} is set to true. + * + * @param applyEmbeddedFontSizes Whether font sizes embedded within the cues should be applied. + */ + public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) { + if (this.applyEmbeddedFontSizes == applyEmbeddedFontSizes) { + return; + } + this.applyEmbeddedFontSizes = applyEmbeddedFontSizes; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets the caption style to be equivalent to the one returned by + * {@link CaptioningManager#getUserStyle()}, or to a default style before API level 19. + */ + public void setUserDefaultStyle() { + setStyle(Util.SDK_INT >= 19 && !isInEditMode() + ? getUserCaptionStyleV19() : CaptionStyleCompat.DEFAULT); + } + + /** + * Sets the caption style. + * + * @param style A style for the view. + */ + public void setStyle(CaptionStyleCompat style) { + if (this.style == style) { + return; + } + this.style = style; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, + * as a fraction of the view's remaining height after its top and bottom padding have been + * subtracted. + *

    + * Note that this padding is applied in addition to any standard view padding. + * + * @param bottomPaddingFraction The bottom padding fraction. + */ + public void setBottomPaddingFraction(float bottomPaddingFraction) { + if (this.bottomPaddingFraction == bottomPaddingFraction) { + return; + } + this.bottomPaddingFraction = bottomPaddingFraction; + // Invalidate to trigger drawing. + invalidate(); + } + + @Override + public void dispatchDraw(Canvas canvas) { + int cueCount = (cues == null) ? 0 : cues.size(); + int rawTop = getTop(); + int rawBottom = getBottom(); + + // Calculate the bounds after padding is taken into account. + int left = getLeft() + getPaddingLeft(); + int top = rawTop + getPaddingTop(); + int right = getRight() + getPaddingRight(); + int bottom = rawBottom - getPaddingBottom(); + if (bottom <= top || right <= left) { + // No space to draw subtitles. + return; + } + + float textSizePx = textSizeType == ABSOLUTE ? textSize + : textSize * (textSizeType == FRACTIONAL ? (bottom - top) : (rawBottom - rawTop)); + if (textSizePx <= 0) { + // Text has no height. + return; + } + + for (int i = 0; i < cueCount; i++) { + painters.get(i).draw(cues.get(i), applyEmbeddedStyles, applyEmbeddedFontSizes, style, + textSizePx, bottomPaddingFraction, canvas, left, top, right, bottom); + } + } + + @TargetApi(19) + private float getUserCaptionFontScaleV19() { + CaptioningManager captioningManager = + (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); + return captioningManager.getFontScale(); + } + + @TargetApi(19) + private CaptionStyleCompat getUserCaptionStyleV19() { + CaptioningManager captioningManager = + (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); + return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/TimeBar.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/TimeBar.java new file mode 100644 index 0000000..6cc7b7e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/ui/TimeBar.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.ui; + +import android.support.annotation.Nullable; +import android.view.View; + +/** + * Interface for time bar views that can display a playback position, buffered position, duration + * and ad markers, and that have a listener for scrubbing (seeking) events. + */ +public interface TimeBar { + + /** + * @see View#isEnabled() + */ + void setEnabled(boolean enabled); + + /** + * Sets the listener for the scrubbing events. + * + * @param listener The listener for scrubbing events. + */ + void setListener(OnScrubListener listener); + + /** + * Sets the position increment for key presses and accessibility actions, in milliseconds. + *

    + * Clears any increment specified in a preceding call to {@link #setKeyCountIncrement(int)}. + * + * @param time The time increment, in milliseconds. + */ + void setKeyTimeIncrement(long time); + + /** + * Sets the position increment for key presses and accessibility actions, as a number of + * increments that divide the duration of the media. For example, passing 20 will cause key + * presses to increment/decrement the position by 1/20th of the duration (if known). + *

    + * Clears any increment specified in a preceding call to {@link #setKeyTimeIncrement(long)}. + * + * @param count The number of increments that divide the duration of the media. + */ + void setKeyCountIncrement(int count); + + /** + * Sets the current position. + * + * @param position The current position to show, in milliseconds. + */ + void setPosition(long position); + + /** + * Sets the buffered position. + * + * @param bufferedPosition The current buffered position to show, in milliseconds. + */ + void setBufferedPosition(long bufferedPosition); + + /** + * Sets the duration. + * + * @param duration The duration to show, in milliseconds. + */ + void setDuration(long duration); + + /** + * Sets the times of ad breaks. + * + * @param adBreakTimesMs An array where the first {@code adBreakCount} elements are the times of + * ad breaks in milliseconds. May be {@code null} if there are no ad breaks. + * @param adBreakCount The number of ad breaks. + */ + void setAdBreakTimesMs(@Nullable long[] adBreakTimesMs, int adBreakCount); + + /** + * Listener for scrubbing events. + */ + interface OnScrubListener { + + /** + * Called when the user starts moving the scrubber. + * + * @param timeBar The time bar. + */ + void onScrubStart(TimeBar timeBar); + + /** + * Called when the user moves the scrubber. + * + * @param timeBar The time bar. + * @param position The position of the scrubber, in milliseconds. + */ + void onScrubMove(TimeBar timeBar, long position); + + /** + * Called when the user stops moving the scrubber. + * + * @param timeBar The time bar. + * @param position The position of the scrubber, in milliseconds. + * @param canceled Whether scrubbing was canceled. + */ + void onScrubStop(TimeBar timeBar, long position, boolean canceled); + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Allocation.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Allocation.java new file mode 100644 index 0000000..3f9a92d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Allocation.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +/** + * An allocation within a byte array. + *

    + * The allocation's length is obtained by calling {@link Allocator#getIndividualAllocationLength()} + * on the {@link Allocator} from which it was obtained. + */ +public final class Allocation { + + /** + * The array containing the allocated space. The allocated space might not be at the start of the + * array, and so {@link #translateOffset(int)} method must be used when indexing into it. + */ + public final byte[] data; + + private final int offset; + + /** + * @param data The array containing the allocated space. + * @param offset The offset of the allocated space within the array. + */ + public Allocation(byte[] data, int offset) { + this.data = data; + this.offset = offset; + } + + /** + * Translates a zero-based offset into the allocation to the corresponding {@link #data} offset. + * + * @param offset The zero-based offset to translate. + * @return The corresponding offset in {@link #data}. + */ + public int translateOffset(int offset) { + return this.offset + offset; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Allocator.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Allocator.java new file mode 100644 index 0000000..42842f6 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Allocator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +/** + * A source of allocations. + */ +public interface Allocator { + + /** + * Obtain an {@link Allocation}. + *

    + * When the caller has finished with the {@link Allocation}, it should be returned by calling + * {@link #release(Allocation)}. + * + * @return The {@link Allocation}. + */ + Allocation allocate(); + + /** + * Releases an {@link Allocation} back to the allocator. + * + * @param allocation The {@link Allocation} being released. + */ + void release(Allocation allocation); + + /** + * Releases an array of {@link Allocation}s back to the allocator. + * + * @param allocations The array of {@link Allocation}s being released. + */ + void release(Allocation[] allocations); + + /** + * Hints to the allocator that it should make a best effort to release any excess + * {@link Allocation}s. + */ + void trim(); + + /** + * Returns the total number of bytes currently allocated. + */ + int getTotalBytesAllocated(); + + /** + * Returns the length of each individual {@link Allocation}. + */ + int getIndividualAllocationLength(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/AssetDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/AssetDataSource.java new file mode 100644 index 0000000..94c4afe --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/AssetDataSource.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.content.Context; +import android.content.res.AssetManager; +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@link DataSource} for reading from a local asset. + */ +public final class AssetDataSource implements DataSource { + + /** + * Thrown when an {@link IOException} is encountered reading a local asset. + */ + public static final class AssetDataSourceException extends IOException { + + public AssetDataSourceException(IOException cause) { + super(cause); + } + + } + + private final AssetManager assetManager; + private final TransferListener listener; + + private Uri uri; + private InputStream inputStream; + private long bytesRemaining; + private boolean opened; + + /** + * @param context A context. + */ + public AssetDataSource(Context context) { + this(context, null); + } + + /** + * @param context A context. + * @param listener An optional listener. + */ + public AssetDataSource(Context context, TransferListener listener) { + this.assetManager = context.getAssets(); + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws AssetDataSourceException { + try { + uri = dataSpec.uri; + String path = uri.getPath(); + if (path.startsWith("/android_asset/")) { + path = path.substring(15); + } else if (path.startsWith("/")) { + path = path.substring(1); + } + inputStream = assetManager.open(path, AssetManager.ACCESS_RANDOM); + long skipped = inputStream.skip(dataSpec.position); + if (skipped < dataSpec.position) { + // assetManager.open() returns an AssetInputStream, whose skip() implementation only skips + // fewer bytes than requested if the skip is beyond the end of the asset's data. + throw new EOFException(); + } + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = dataSpec.length; + } else { + bytesRemaining = inputStream.available(); + if (bytesRemaining == Integer.MAX_VALUE) { + // assetManager.open() returns an AssetInputStream, whose available() implementation + // returns Integer.MAX_VALUE if the remaining length is greater than (or equal to) + // Integer.MAX_VALUE. We don't know the true length in this case, so treat as unbounded. + bytesRemaining = C.LENGTH_UNSET; + } + } + } catch (IOException e) { + throw new AssetDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws AssetDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + + int bytesRead; + try { + int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength + : (int) Math.min(bytesRemaining, readLength); + bytesRead = inputStream.read(buffer, offset, bytesToRead); + } catch (IOException e) { + throw new AssetDataSourceException(e); + } + + if (bytesRead == -1) { + if (bytesRemaining != C.LENGTH_UNSET) { + // End of stream reached having not read sufficient data. + throw new AssetDataSourceException(new EOFException()); + } + return C.RESULT_END_OF_INPUT; + } + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; + } + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + return bytesRead; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws AssetDataSourceException { + uri = null; + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + throw new AssetDataSourceException(e); + } finally { + inputStream = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/BandwidthMeter.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/BandwidthMeter.java new file mode 100644 index 0000000..720a94b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/BandwidthMeter.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +/** + * Provides estimates of the currently available bandwidth. + */ +public interface BandwidthMeter { + + /** + * A listener of {@link BandwidthMeter} events. + */ + interface EventListener { + + /** + * Called periodically to indicate that bytes have been transferred. + *

    + * Note: The estimated bitrate is typically derived from more information than just + * {@code bytes} and {@code elapsedMs}. + * + * @param elapsedMs The time taken to transfer the bytes, in milliseconds. + * @param bytes The number of bytes transferred. + * @param bitrate The estimated bitrate in bits/sec, or {@link #NO_ESTIMATE} if an estimate is + * not available. + */ + void onBandwidthSample(int elapsedMs, long bytes, long bitrate); + + } + + /** + * Indicates no bandwidth estimate is available. + */ + long NO_ESTIMATE = -1; + + /** + * Returns the estimated bandwidth in bits/sec, or {@link #NO_ESTIMATE} if an estimate is not + * available. + */ + long getBitrateEstimate(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ByteArrayDataSink.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ByteArrayDataSink.java new file mode 100644 index 0000000..ff215c9 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ByteArrayDataSink.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * A {@link DataSink} for writing to a byte array. + */ +public final class ByteArrayDataSink implements DataSink { + + private ByteArrayOutputStream stream; + + @Override + public void open(DataSpec dataSpec) throws IOException { + if (dataSpec.length == C.LENGTH_UNSET) { + stream = new ByteArrayOutputStream(); + } else { + Assertions.checkArgument(dataSpec.length <= Integer.MAX_VALUE); + stream = new ByteArrayOutputStream((int) dataSpec.length); + } + } + + @Override + public void close() throws IOException { + stream.close(); + } + + @Override + public void write(byte[] buffer, int offset, int length) throws IOException { + stream.write(buffer, offset, length); + } + + /** + * Returns the data written to the sink since the last call to {@link #open(DataSpec)}, or null if + * {@link #open(DataSpec)} has never been called. + */ + public byte[] getData() { + return stream == null ? null : stream.toByteArray(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ByteArrayDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ByteArrayDataSource.java new file mode 100644 index 0000000..142c062 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ByteArrayDataSource.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * A {@link DataSource} for reading from a byte array. + */ +public final class ByteArrayDataSource implements DataSource { + + private final byte[] data; + + private Uri uri; + private int readPosition; + private int bytesRemaining; + + /** + * @param data The data to be read. + */ + public ByteArrayDataSource(byte[] data) { + Assertions.checkNotNull(data); + Assertions.checkArgument(data.length > 0); + this.data = data; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + uri = dataSpec.uri; + readPosition = (int) dataSpec.position; + bytesRemaining = (int) ((dataSpec.length == C.LENGTH_UNSET) + ? (data.length - dataSpec.position) : dataSpec.length); + if (bytesRemaining <= 0 || readPosition + bytesRemaining > data.length) { + throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length + + "], length: " + data.length); + } + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + + readLength = Math.min(readLength, bytesRemaining); + System.arraycopy(data, readPosition, buffer, offset, readLength); + readPosition += readLength; + bytesRemaining -= readLength; + return readLength; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws IOException { + uri = null; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ContentDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ContentDataSource.java new file mode 100644 index 0000000..a9e17d1 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ContentDataSource.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@link DataSource} for reading from a content URI. + */ +public final class ContentDataSource implements DataSource { + + /** + * Thrown when an {@link IOException} is encountered reading from a content URI. + */ + public static class ContentDataSourceException extends IOException { + + public ContentDataSourceException(IOException cause) { + super(cause); + } + + } + + private final ContentResolver resolver; + private final TransferListener listener; + + private Uri uri; + private AssetFileDescriptor assetFileDescriptor; + private InputStream inputStream; + private long bytesRemaining; + private boolean opened; + + /** + * @param context A context. + */ + public ContentDataSource(Context context) { + this(context, null); + } + + /** + * @param context A context. + * @param listener An optional listener. + */ + public ContentDataSource(Context context, TransferListener listener) { + this.resolver = context.getContentResolver(); + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws ContentDataSourceException { + try { + uri = dataSpec.uri; + assetFileDescriptor = resolver.openAssetFileDescriptor(uri, "r"); + inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); + long skipped = inputStream.skip(dataSpec.position); + if (skipped < dataSpec.position) { + // We expect the skip to be satisfied in full. If it isn't then we're probably trying to + // skip beyond the end of the data. + throw new EOFException(); + } + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = dataSpec.length; + } else { + bytesRemaining = inputStream.available(); + if (bytesRemaining == 0) { + // FileInputStream.available() returns 0 if the remaining length cannot be determined, or + // if it's greater than Integer.MAX_VALUE. We don't know the true length in either case, + // so treat as unbounded. + bytesRemaining = C.LENGTH_UNSET; + } + } + } catch (IOException e) { + throw new ContentDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws ContentDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + + int bytesRead; + try { + int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength + : (int) Math.min(bytesRemaining, readLength); + bytesRead = inputStream.read(buffer, offset, bytesToRead); + } catch (IOException e) { + throw new ContentDataSourceException(e); + } + + if (bytesRead == -1) { + if (bytesRemaining != C.LENGTH_UNSET) { + // End of stream reached having not read sufficient data. + throw new ContentDataSourceException(new EOFException()); + } + return C.RESULT_END_OF_INPUT; + } + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; + } + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + return bytesRead; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws ContentDataSourceException { + uri = null; + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + throw new ContentDataSourceException(e); + } finally { + inputStream = null; + try { + if (assetFileDescriptor != null) { + assetFileDescriptor.close(); + } + } catch (IOException e) { + throw new ContentDataSourceException(e); + } finally { + assetFileDescriptor = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSink.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSink.java new file mode 100644 index 0000000..97ffa2f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSink.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import java.io.IOException; + +/** + * A component to which streams of data can be written. + */ +public interface DataSink { + + /** + * A factory for {@link DataSink} instances. + */ + interface Factory { + + /** + * Creates a {@link DataSink} instance. + */ + DataSink createDataSink(); + + } + + /** + * Opens the sink to consume the specified data. + * + * @param dataSpec Defines the data to be consumed. + * @throws IOException If an error occurs opening the sink. + */ + void open(DataSpec dataSpec) throws IOException; + + /** + * Consumes the provided data. + * + * @param buffer The buffer from which data should be consumed. + * @param offset The offset of the data to consume in {@code buffer}. + * @param length The length of the data to consume, in bytes. + * @throws IOException If an error occurs writing to the sink. + */ + void write(byte[] buffer, int offset, int length) throws IOException; + + /** + * Closes the sink. + * + * @throws IOException If an error occurs closing the sink. + */ + void close() throws IOException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSource.java new file mode 100644 index 0000000..3901b04 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSource.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.IOException; + +/** + * A component from which streams of data can be read. + */ +public interface DataSource { + + /** + * A factory for {@link DataSource} instances. + */ + interface Factory { + + /** + * Creates a {@link DataSource} instance. + */ + DataSource createDataSource(); + + } + + /** + * Opens the source to read the specified data. + *

    + * Note: If an {@link IOException} is thrown, callers must still call {@link #close()} to ensure + * that any partial effects of the invocation are cleaned up. + * + * @param dataSpec Defines the data to be read. + * @throws IOException If an error occurs opening the source. {@link DataSourceException} can be + * thrown or used as a cause of the thrown exception to specify the reason of the error. + * @return The number of bytes that can be read from the opened source. For unbounded requests + * (i.e. requests where {@link DataSpec#length} equals {@link C#LENGTH_UNSET}) this value + * is the resolved length of the request, or {@link C#LENGTH_UNSET} if the length is still + * unresolved. For all other requests, the value returned will be equal to the request's + * {@link DataSpec#length}. + */ + long open(DataSpec dataSpec) throws IOException; + + /** + * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at + * index {@code offset}. + *

    + * If {@code length} is zero then 0 is returned. Otherwise, if no data is available because the + * end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned. + * Otherwise, the call will block until at least one byte of data has been read and the number of + * bytes read is returned. + * + * @param buffer The buffer into which the read data should be stored. + * @param offset The start offset into {@code buffer} at which data should be written. + * @param readLength The maximum number of bytes to read. + * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if no data is available + * because the end of the opened range has been reached. + * @throws IOException If an error occurs reading from the source. + */ + int read(byte[] buffer, int offset, int readLength) throws IOException; + + /** + * When the source is open, returns the {@link Uri} from which data is being read. The returned + * {@link Uri} will be identical to the one passed {@link #open(DataSpec)} in the {@link DataSpec} + * unless redirection has occurred. If redirection has occurred, the {@link Uri} after redirection + * is returned. + * + * @return The {@link Uri} from which data is being read, or null if the source is not open. + */ + Uri getUri(); + + /** + * Closes the source. + *

    + * Note: This method must be called even if the corresponding call to {@link #open(DataSpec)} + * threw an {@link IOException}. See {@link #open(DataSpec)} for more details. + * + * @throws IOException If an error occurs closing the source. + */ + void close() throws IOException; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSourceException.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSourceException.java new file mode 100644 index 0000000..5361d5d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSourceException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import java.io.IOException; + +/** + * Used to specify reason of a DataSource error. + */ +public final class DataSourceException extends IOException { + + public static final int POSITION_OUT_OF_RANGE = 0; + + /** + * The reason of this {@link DataSourceException}. It can only be {@link #POSITION_OUT_OF_RANGE}. + */ + public final int reason; + + /** + * Constructs a DataSourceException. + * + * @param reason Reason of the error. It can only be {@link #POSITION_OUT_OF_RANGE}. + */ + public DataSourceException(int reason) { + this.reason = reason; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSourceInputStream.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSourceInputStream.java new file mode 100644 index 0000000..b19cb8f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSourceInputStream.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.support.annotation.NonNull; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; +import java.io.InputStream; + +/** + * Allows data corresponding to a given {@link DataSpec} to be read from a {@link DataSource} and + * consumed through an {@link InputStream}. + */ +public final class DataSourceInputStream extends InputStream { + + private final DataSource dataSource; + private final DataSpec dataSpec; + private final byte[] singleByteArray; + + private boolean opened = false; + private boolean closed = false; + private long totalBytesRead; + + /** + * @param dataSource The {@link DataSource} from which the data should be read. + * @param dataSpec The {@link DataSpec} defining the data to be read from {@code dataSource}. + */ + public DataSourceInputStream(DataSource dataSource, DataSpec dataSpec) { + this.dataSource = dataSource; + this.dataSpec = dataSpec; + singleByteArray = new byte[1]; + } + + /** + * Returns the total number of bytes that have been read or skipped. + */ + public long bytesRead() { + return totalBytesRead; + } + + /** + * Optional call to open the underlying {@link DataSource}. + *

    + * Calling this method does nothing if the {@link DataSource} is already open. Calling this + * method is optional, since the read and skip methods will automatically open the underlying + * {@link DataSource} if it's not open already. + * + * @throws IOException If an error occurs opening the {@link DataSource}. + */ + public void open() throws IOException { + checkOpened(); + } + + @Override + public int read() throws IOException { + int length = read(singleByteArray); + return length == -1 ? -1 : (singleByteArray[0] & 0xFF); + } + + @Override + public int read(@NonNull byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } + + @Override + public int read(@NonNull byte[] buffer, int offset, int length) throws IOException { + Assertions.checkState(!closed); + checkOpened(); + int bytesRead = dataSource.read(buffer, offset, length); + if (bytesRead == C.RESULT_END_OF_INPUT) { + return -1; + } else { + totalBytesRead += bytesRead; + return bytesRead; + } + } + + @Override + public void close() throws IOException { + if (!closed) { + dataSource.close(); + closed = true; + } + } + + private void checkOpened() throws IOException { + if (!opened) { + dataSource.open(dataSpec); + opened = true; + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSpec.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSpec.java new file mode 100644 index 0000000..5c4c779 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DataSpec.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import android.support.annotation.IntDef; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; + +/** + * Defines a region of data. + */ +public final class DataSpec { + + /** + * The flags that apply to any request for data. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_ALLOW_GZIP, FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}) + public @interface Flags {} + /** + * Permits an underlying network stack to request that the server use gzip compression. + *

    + * Should not typically be set if the data being requested is already compressed (e.g. most audio + * and video requests). May be set when requesting other data. + *

    + * When a {@link DataSource} is used to request data with this flag set, and if the + * {@link DataSource} does make a network request, then the value returned from + * {@link DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from + * {@link DataSource#read(byte[], int, int)} will be the decompressed data. + */ + public static final int FLAG_ALLOW_GZIP = 1 << 0; + + /** + * Permits content to be cached even if its length can not be resolved. + */ + public static final int FLAG_ALLOW_CACHING_UNKNOWN_LENGTH = 1 << 1; + + /** + * The source from which data should be read. + */ + public final Uri uri; + /** + * Body for a POST request, null otherwise. + */ + public final byte[] postBody; + /** + * The absolute position of the data in the full stream. + */ + public final long absoluteStreamPosition; + /** + * The position of the data when read from {@link #uri}. + *

    + * Always equal to {@link #absoluteStreamPosition} unless the {@link #uri} defines the location + * of a subset of the underyling data. + */ + public final long position; + /** + * The length of the data, or {@link C#LENGTH_UNSET}. + */ + public final long length; + /** + * A key that uniquely identifies the original stream. Used for cache indexing. May be null if the + * {@link DataSpec} is not intended to be used in conjunction with a cache. + */ + public final String key; + /** + * Request flags. Currently {@link #FLAG_ALLOW_GZIP} and + * {@link #FLAG_ALLOW_CACHING_UNKNOWN_LENGTH} are the only supported flags. + */ + @Flags public final int flags; + + /** + * Construct a {@link DataSpec} for the given uri and with {@link #key} set to null. + * + * @param uri {@link #uri}. + */ + public DataSpec(Uri uri) { + this(uri, 0); + } + + /** + * Construct a {@link DataSpec} for the given uri and with {@link #key} set to null. + * + * @param uri {@link #uri}. + * @param flags {@link #flags}. + */ + public DataSpec(Uri uri, @Flags int flags) { + this(uri, 0, C.LENGTH_UNSET, null, flags); + } + + /** + * Construct a {@link DataSpec} where {@link #position} equals {@link #absoluteStreamPosition}. + * + * @param uri {@link #uri}. + * @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}. + * @param length {@link #length}. + * @param key {@link #key}. + */ + public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key) { + this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, 0); + } + + /** + * Construct a {@link DataSpec} where {@link #position} equals {@link #absoluteStreamPosition}. + * + * @param uri {@link #uri}. + * @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}. + * @param length {@link #length}. + * @param key {@link #key}. + * @param flags {@link #flags}. + */ + public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, @Flags int flags) { + this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, flags); + } + + /** + * Construct a {@link DataSpec} where {@link #position} may differ from + * {@link #absoluteStreamPosition}. + * + * @param uri {@link #uri}. + * @param absoluteStreamPosition {@link #absoluteStreamPosition}. + * @param position {@link #position}. + * @param length {@link #length}. + * @param key {@link #key}. + * @param flags {@link #flags}. + */ + public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length, String key, + @Flags int flags) { + this(uri, null, absoluteStreamPosition, position, length, key, flags); + } + + /** + * Construct a {@link DataSpec} where {@link #position} may differ from + * {@link #absoluteStreamPosition}. + * + * @param uri {@link #uri}. + * @param postBody {@link #postBody}. + * @param absoluteStreamPosition {@link #absoluteStreamPosition}. + * @param position {@link #position}. + * @param length {@link #length}. + * @param key {@link #key}. + * @param flags {@link #flags}. + */ + public DataSpec(Uri uri, byte[] postBody, long absoluteStreamPosition, long position, long length, + String key, @Flags int flags) { + Assertions.checkArgument(absoluteStreamPosition >= 0); + Assertions.checkArgument(position >= 0); + Assertions.checkArgument(length > 0 || length == C.LENGTH_UNSET); + this.uri = uri; + this.postBody = postBody; + this.absoluteStreamPosition = absoluteStreamPosition; + this.position = position; + this.length = length; + this.key = key; + this.flags = flags; + } + + /** + * Returns whether the given flag is set. + * + * @param flag Flag to be checked if it is set. + */ + public boolean isFlagSet(@Flags int flag) { + return (this.flags & flag) == flag; + } + + @Override + public String toString() { + return "DataSpec[" + uri + ", " + Arrays.toString(postBody) + ", " + absoluteStreamPosition + + ", " + position + ", " + length + ", " + key + ", " + flags + "]"; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultAllocator.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultAllocator.java new file mode 100644 index 0000000..064e5bd --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultAllocator.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * Default implementation of {@link Allocator}. + */ +public final class DefaultAllocator implements Allocator { + + private static final int AVAILABLE_EXTRA_CAPACITY = 100; + + private final boolean trimOnReset; + private final int individualAllocationSize; + private final byte[] initialAllocationBlock; + private final Allocation[] singleAllocationReleaseHolder; + + private int targetBufferSize; + private int allocatedCount; + private int availableCount; + private Allocation[] availableAllocations; + + /** + * Constructs an instance without creating any {@link Allocation}s up front. + * + * @param trimOnReset Whether memory is freed when the allocator is reset. Should be true unless + * the allocator will be re-used by multiple player instances. + * @param individualAllocationSize The length of each individual {@link Allocation}. + */ + public DefaultAllocator(boolean trimOnReset, int individualAllocationSize) { + this(trimOnReset, individualAllocationSize, 0); + } + + /** + * Constructs an instance with some {@link Allocation}s created up front. + *

    + * Note: {@link Allocation}s created up front will never be discarded by {@link #trim()}. + * + * @param trimOnReset Whether memory is freed when the allocator is reset. Should be true unless + * the allocator will be re-used by multiple player instances. + * @param individualAllocationSize The length of each individual {@link Allocation}. + * @param initialAllocationCount The number of allocations to create up front. + */ + public DefaultAllocator(boolean trimOnReset, int individualAllocationSize, + int initialAllocationCount) { + Assertions.checkArgument(individualAllocationSize > 0); + Assertions.checkArgument(initialAllocationCount >= 0); + this.trimOnReset = trimOnReset; + this.individualAllocationSize = individualAllocationSize; + this.availableCount = initialAllocationCount; + this.availableAllocations = new Allocation[initialAllocationCount + AVAILABLE_EXTRA_CAPACITY]; + if (initialAllocationCount > 0) { + initialAllocationBlock = new byte[initialAllocationCount * individualAllocationSize]; + for (int i = 0; i < initialAllocationCount; i++) { + int allocationOffset = i * individualAllocationSize; + availableAllocations[i] = new Allocation(initialAllocationBlock, allocationOffset); + } + } else { + initialAllocationBlock = null; + } + singleAllocationReleaseHolder = new Allocation[1]; + } + + public synchronized void reset() { + if (trimOnReset) { + setTargetBufferSize(0); + } + } + + public synchronized void setTargetBufferSize(int targetBufferSize) { + boolean targetBufferSizeReduced = targetBufferSize < this.targetBufferSize; + this.targetBufferSize = targetBufferSize; + if (targetBufferSizeReduced) { + trim(); + } + } + + @Override + public synchronized Allocation allocate() { + allocatedCount++; + Allocation allocation; + if (availableCount > 0) { + allocation = availableAllocations[--availableCount]; + availableAllocations[availableCount] = null; + } else { + allocation = new Allocation(new byte[individualAllocationSize], 0); + } + return allocation; + } + + @Override + public synchronized void release(Allocation allocation) { + singleAllocationReleaseHolder[0] = allocation; + release(singleAllocationReleaseHolder); + } + + @Override + public synchronized void release(Allocation[] allocations) { + if (availableCount + allocations.length >= availableAllocations.length) { + availableAllocations = Arrays.copyOf(availableAllocations, + Math.max(availableAllocations.length * 2, availableCount + allocations.length)); + } + for (Allocation allocation : allocations) { + // Weak sanity check that the allocation probably originated from this pool. + Assertions.checkArgument(allocation.data == initialAllocationBlock + || allocation.data.length == individualAllocationSize); + availableAllocations[availableCount++] = allocation; + } + allocatedCount -= allocations.length; + // Wake up threads waiting for the allocated size to drop. + notifyAll(); + } + + @Override + public synchronized void trim() { + int targetAllocationCount = Util.ceilDivide(targetBufferSize, individualAllocationSize); + int targetAvailableCount = Math.max(0, targetAllocationCount - allocatedCount); + if (targetAvailableCount >= availableCount) { + // We're already at or below the target. + return; + } + + if (initialAllocationBlock != null) { + // Some allocations are backed by an initial block. We need to make sure that we hold onto all + // such allocations. Re-order the available allocations so that the ones backed by the initial + // block come first. + int lowIndex = 0; + int highIndex = availableCount - 1; + while (lowIndex <= highIndex) { + Allocation lowAllocation = availableAllocations[lowIndex]; + if (lowAllocation.data == initialAllocationBlock) { + lowIndex++; + } else { + Allocation highAllocation = availableAllocations[highIndex]; + if (highAllocation.data != initialAllocationBlock) { + highIndex--; + } else { + availableAllocations[lowIndex++] = highAllocation; + availableAllocations[highIndex--] = lowAllocation; + } + } + } + // lowIndex is the index of the first allocation not backed by an initial block. + targetAvailableCount = Math.max(targetAvailableCount, lowIndex); + if (targetAvailableCount >= availableCount) { + // We're already at or below the target. + return; + } + } + + // Discard allocations beyond the target. + Arrays.fill(availableAllocations, targetAvailableCount, availableCount, null); + availableCount = targetAvailableCount; + } + + @Override + public synchronized int getTotalBytesAllocated() { + return allocatedCount * individualAllocationSize; + } + + @Override + public int getIndividualAllocationLength() { + return individualAllocationSize; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultBandwidthMeter.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultBandwidthMeter.java new file mode 100644 index 0000000..04a3a60 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.os.Handler; +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.SlidingPercentile; + +/** + * Estimates bandwidth by listening to data transfers. The bandwidth estimate is calculated using + * a {@link SlidingPercentile} and is updated each time a transfer ends. + */ +public final class DefaultBandwidthMeter implements BandwidthMeter, TransferListener { + + /** + * The default maximum weight for the sliding window. + */ + public static final int DEFAULT_MAX_WEIGHT = 2000; + + private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; + private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; + + private final Handler eventHandler; + private final EventListener eventListener; + private final SlidingPercentile slidingPercentile; + + private int streamCount; + private long sampleStartTimeMs; + private long sampleBytesTransferred; + + private long totalElapsedTimeMs; + private long totalBytesTransferred; + private long bitrateEstimate; + + public DefaultBandwidthMeter() { + this(null, null); + } + + public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) { + this(eventHandler, eventListener, DEFAULT_MAX_WEIGHT); + } + + public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) { + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.slidingPercentile = new SlidingPercentile(maxWeight); + bitrateEstimate = NO_ESTIMATE; + } + + @Override + public synchronized long getBitrateEstimate() { + return bitrateEstimate; + } + + @Override + public synchronized void onTransferStart(Object source, DataSpec dataSpec) { + if (streamCount == 0) { + sampleStartTimeMs = SystemClock.elapsedRealtime(); + } + streamCount++; + } + + @Override + public synchronized void onBytesTransferred(Object source, int bytes) { + sampleBytesTransferred += bytes; + } + + @Override + public synchronized void onTransferEnd(Object source) { + Assertions.checkState(streamCount > 0); + long nowMs = SystemClock.elapsedRealtime(); + int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); + totalElapsedTimeMs += sampleElapsedTimeMs; + totalBytesTransferred += sampleBytesTransferred; + if (sampleElapsedTimeMs > 0) { + float bitsPerSecond = (sampleBytesTransferred * 8000) / sampleElapsedTimeMs; + slidingPercentile.addSample((int) Math.sqrt(sampleBytesTransferred), bitsPerSecond); + if (totalElapsedTimeMs >= ELAPSED_MILLIS_FOR_ESTIMATE + || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) { + float bitrateEstimateFloat = slidingPercentile.getPercentile(0.5f); + bitrateEstimate = Float.isNaN(bitrateEstimateFloat) ? NO_ESTIMATE + : (long) bitrateEstimateFloat; + } + } + notifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); + if (--streamCount > 0) { + sampleStartTimeMs = nowMs; + } + sampleBytesTransferred = 0; + } + + private void notifyBandwidthSample(final int elapsedMs, final long bytes, final long bitrate) { + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onBandwidthSample(elapsedMs, bytes, bitrate); + } + }); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultDataSource.java new file mode 100644 index 0000000..b3ce52f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultDataSource.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.content.Context; +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A {@link DataSource} that supports multiple URI schemes. The supported schemes are: + * + *
      + *
    • file: For fetching data from a local file (e.g. file:///path/to/media/media.mp4, or just + * /path/to/media/media.mp4 because the implementation assumes that a URI without a scheme is a + * local file URI). + *
    • asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4). + *
    • content: For fetching data from a content URI (e.g. content://authority/path/123). + *
    • http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4), if + * constructed using {@link #DefaultDataSource(Context, TransferListener, String, boolean)}, or + * any other schemes supported by a base data source if constructed using + * {@link #DefaultDataSource(Context, TransferListener, DataSource)}. + *
    + */ +public final class DefaultDataSource implements DataSource { + + private static final String SCHEME_ASSET = "asset"; + private static final String SCHEME_CONTENT = "content"; + + private final DataSource baseDataSource; + private final DataSource fileDataSource; + private final DataSource assetDataSource; + private final DataSource contentDataSource; + + private DataSource dataSource; + + /** + * Constructs a new instance, optionally configured to follow cross-protocol redirects. + * + * @param context A context. + * @param listener An optional listener. + * @param userAgent The User-Agent string that should be used when requesting remote data. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled when fetching remote data. + */ + public DefaultDataSource(Context context, TransferListener listener, + String userAgent, boolean allowCrossProtocolRedirects) { + this(context, listener, userAgent, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, allowCrossProtocolRedirects); + } + + /** + * Constructs a new instance, optionally configured to follow cross-protocol redirects. + * + * @param context A context. + * @param listener An optional listener. + * @param userAgent The User-Agent string that should be used when requesting remote data. + * @param connectTimeoutMillis The connection timeout that should be used when requesting remote + * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param readTimeoutMillis The read timeout that should be used when requesting remote data, + * in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled when fetching remote data. + */ + public DefaultDataSource(Context context, TransferListener listener, + String userAgent, int connectTimeoutMillis, int readTimeoutMillis, + boolean allowCrossProtocolRedirects) { + this(context, listener, + new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis, + readTimeoutMillis, allowCrossProtocolRedirects, null)); + } + + /** + * Constructs a new instance that delegates to a provided {@link DataSource} for URI schemes other + * than file, asset and content. + * + * @param context A context. + * @param listener An optional listener. + * @param baseDataSource A {@link DataSource} to use for URI schemes other than file, asset and + * content. This {@link DataSource} should normally support at least http(s). + */ + public DefaultDataSource(Context context, TransferListener listener, + DataSource baseDataSource) { + this.baseDataSource = Assertions.checkNotNull(baseDataSource); + this.fileDataSource = new FileDataSource(listener); + this.assetDataSource = new AssetDataSource(context, listener); + this.contentDataSource = new ContentDataSource(context, listener); + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + Assertions.checkState(dataSource == null); + // Choose the correct source for the scheme. + String scheme = dataSpec.uri.getScheme(); + if (Util.isLocalFileUri(dataSpec.uri)) { + if (dataSpec.uri.getPath().startsWith("/android_asset/")) { + dataSource = assetDataSource; + } else { + dataSource = fileDataSource; + } + } else if (SCHEME_ASSET.equals(scheme)) { + dataSource = assetDataSource; + } else if (SCHEME_CONTENT.equals(scheme)) { + dataSource = contentDataSource; + } else { + dataSource = baseDataSource; + } + // Open the source and return. + return dataSource.open(dataSpec); + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + return dataSource.read(buffer, offset, readLength); + } + + @Override + public Uri getUri() { + return dataSource == null ? null : dataSource.getUri(); + } + + @Override + public void close() throws IOException { + if (dataSource != null) { + try { + dataSource.close(); + } finally { + dataSource = null; + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultDataSourceFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultDataSourceFactory.java new file mode 100644 index 0000000..3c93416 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultDataSourceFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.content.Context; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource.Factory; + +/** + * A {@link Factory} that produces {@link DefaultDataSource} instances that delegate to + * {@link DefaultHttpDataSource}s for non-file/asset/content URIs. + */ +public final class DefaultDataSourceFactory implements Factory { + + private final Context context; + private final TransferListener listener; + private final DataSource.Factory baseDataSourceFactory; + + /** + * @param context A context. + * @param userAgent The User-Agent string that should be used. + */ + public DefaultDataSourceFactory(Context context, String userAgent) { + this(context, userAgent, null); + } + + /** + * @param context A context. + * @param userAgent The User-Agent string that should be used. + * @param listener An optional listener. + */ + public DefaultDataSourceFactory(Context context, String userAgent, + TransferListener listener) { + this(context, listener, new DefaultHttpDataSourceFactory(userAgent, listener)); + } + + /** + * @param context A context. + * @param listener An optional listener. + * @param baseDataSourceFactory A {@link Factory} to be used to create a base {@link DataSource} + * for {@link DefaultDataSource}. + * @see DefaultDataSource#DefaultDataSource(Context, TransferListener, DataSource) + */ + public DefaultDataSourceFactory(Context context, TransferListener listener, + DataSource.Factory baseDataSourceFactory) { + this.context = context.getApplicationContext(); + this.listener = listener; + this.baseDataSourceFactory = baseDataSourceFactory; + } + + @Override + public DefaultDataSource createDataSource() { + return new DefaultDataSource(context, listener, baseDataSourceFactory.createDataSource()); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultHttpDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultHttpDataSource.java new file mode 100644 index 0000000..258e508 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultHttpDataSource.java @@ -0,0 +1,644 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Predicate; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.NoRouteToHostException; +import java.net.ProtocolException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}. + *

    + * By default this implementation will not follow cross-protocol redirects (i.e. redirects from + * HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the + * {@link #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean, + * RequestProperties)} constructor and passing {@code true} as the second last argument. + */ +public class DefaultHttpDataSource implements HttpDataSource { + + /** + * The default connection timeout, in milliseconds. + */ + public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000; + /** + * The default read timeout, in milliseconds. + */ + public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000; + + private static final String TAG = "DefaultHttpDataSource"; + private static final int MAX_REDIRECTS = 20; // Same limit as okhttp. + private static final long MAX_BYTES_TO_DRAIN = 2048; + private static final Pattern CONTENT_RANGE_HEADER = + Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$"); + private static final AtomicReference skipBufferReference = new AtomicReference<>(); + + private final boolean allowCrossProtocolRedirects; + private final int connectTimeoutMillis; + private final int readTimeoutMillis; + private final String userAgent; + private final Predicate contentTypePredicate; + private final RequestProperties defaultRequestProperties; + private final RequestProperties requestProperties; + private final TransferListener listener; + + private DataSpec dataSpec; + private HttpURLConnection connection; + private InputStream inputStream; + private boolean opened; + + private long bytesToSkip; + private long bytesToRead; + + private long bytesSkipped; + private long bytesRead; + + /** + * @param userAgent The User-Agent string that should be used. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the + * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from + * {@link #open(DataSpec)}. + */ + public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate) { + this(userAgent, contentTypePredicate, null); + } + + /** + * @param userAgent The User-Agent string that should be used. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the + * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from + * {@link #open(DataSpec)}. + * @param listener An optional listener. + */ + public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate, + TransferListener listener) { + this(userAgent, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS, + DEFAULT_READ_TIMEOUT_MILLIS); + } + + /** + * @param userAgent The User-Agent string that should be used. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the + * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from + * {@link #open(DataSpec)}. + * @param listener An optional listener. + * @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is + * interpreted as an infinite timeout. + * @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted + * as an infinite timeout. + */ + public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate, + TransferListener listener, int connectTimeoutMillis, + int readTimeoutMillis) { + this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false, + null); + } + + /** + * @param userAgent The User-Agent string that should be used. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the + * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from + * {@link #open(DataSpec)}. + * @param listener An optional listener. + * @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is + * interpreted as an infinite timeout. Pass {@link #DEFAULT_CONNECT_TIMEOUT_MILLIS} to use + * the default value. + * @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted + * as an infinite timeout. Pass {@link #DEFAULT_READ_TIMEOUT_MILLIS} to use the default value. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled. + * @param defaultRequestProperties The default request properties to be sent to the server as + * HTTP headers or {@code null} if not required. + */ + public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate, + TransferListener listener, int connectTimeoutMillis, + int readTimeoutMillis, boolean allowCrossProtocolRedirects, + RequestProperties defaultRequestProperties) { + this.userAgent = Assertions.checkNotEmpty(userAgent); + this.contentTypePredicate = contentTypePredicate; + this.listener = listener; + this.requestProperties = new RequestProperties(); + this.connectTimeoutMillis = connectTimeoutMillis; + this.readTimeoutMillis = readTimeoutMillis; + this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; + this.defaultRequestProperties = defaultRequestProperties; + } + + @Override + public Uri getUri() { + return connection == null ? null : Uri.parse(connection.getURL().toString()); + } + + @Override + public Map> getResponseHeaders() { + return connection == null ? null : connection.getHeaderFields(); + } + + @Override + public void setRequestProperty(String name, String value) { + Assertions.checkNotNull(name); + Assertions.checkNotNull(value); + requestProperties.set(name, value); + } + + @Override + public void clearRequestProperty(String name) { + Assertions.checkNotNull(name); + requestProperties.remove(name); + } + + @Override + public void clearAllRequestProperties() { + requestProperties.clear(); + } + + @Override + public long open(DataSpec dataSpec) throws HttpDataSourceException { + this.dataSpec = dataSpec; + this.bytesRead = 0; + this.bytesSkipped = 0; + try { + connection = makeConnection(dataSpec); + } catch (IOException e) { + throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e, + dataSpec, HttpDataSourceException.TYPE_OPEN); + } + + int responseCode; + try { + responseCode = connection.getResponseCode(); + } catch (IOException e) { + closeConnectionQuietly(); + throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e, + dataSpec, HttpDataSourceException.TYPE_OPEN); + } + + // Check for a valid response code. + if (responseCode < 200 || responseCode > 299) { + Map> headers = connection.getHeaderFields(); + closeConnectionQuietly(); + InvalidResponseCodeException exception = + new InvalidResponseCodeException(responseCode, headers, dataSpec); + if (responseCode == 416) { + exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); + } + throw exception; + } + + // Check for a valid content type. + String contentType = connection.getContentType(); + if (contentTypePredicate != null && !contentTypePredicate.evaluate(contentType)) { + closeConnectionQuietly(); + throw new InvalidContentTypeException(contentType, dataSpec); + } + + // If we requested a range starting from a non-zero position and received a 200 rather than a + // 206, then the server does not support partial requests. We'll need to manually skip to the + // requested position. + bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; + + // Determine the length of the data to be read, after skipping. + if (!dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_GZIP)) { + if (dataSpec.length != C.LENGTH_UNSET) { + bytesToRead = dataSpec.length; + } else { + long contentLength = getContentLength(connection); + bytesToRead = contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) + : C.LENGTH_UNSET; + } + } else { + // Gzip is enabled. If the server opts to use gzip then the content length in the response + // will be that of the compressed data, which isn't what we want. Furthermore, there isn't a + // reliable way to determine whether the gzip was used or not. Always use the dataSpec length + // in this case. + bytesToRead = dataSpec.length; + } + + try { + inputStream = connection.getInputStream(); + } catch (IOException e) { + closeConnectionQuietly(); + throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_OPEN); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + + return bytesToRead; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException { + try { + skipInternal(); + return readInternal(buffer, offset, readLength); + } catch (IOException e) { + throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_READ); + } + } + + @Override + public void close() throws HttpDataSourceException { + try { + if (inputStream != null) { + maybeTerminateInputStream(connection, bytesRemaining()); + try { + inputStream.close(); + } catch (IOException e) { + throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_CLOSE); + } + } + } finally { + inputStream = null; + closeConnectionQuietly(); + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + + /** + * Returns the current connection, or null if the source is not currently opened. + * + * @return The current open connection, or null. + */ + protected final HttpURLConnection getConnection() { + return connection; + } + + /** + * Returns the number of bytes that have been skipped since the most recent call to + * {@link #open(DataSpec)}. + * + * @return The number of bytes skipped. + */ + protected final long bytesSkipped() { + return bytesSkipped; + } + + /** + * Returns the number of bytes that have been read since the most recent call to + * {@link #open(DataSpec)}. + * + * @return The number of bytes read. + */ + protected final long bytesRead() { + return bytesRead; + } + + /** + * Returns the number of bytes that are still to be read for the current {@link DataSpec}. + *

    + * If the total length of the data being read is known, then this length minus {@code bytesRead()} + * is returned. If the total length is unknown, {@link C#LENGTH_UNSET} is returned. + * + * @return The remaining length, or {@link C#LENGTH_UNSET}. + */ + protected final long bytesRemaining() { + return bytesToRead == C.LENGTH_UNSET ? bytesToRead : bytesToRead - bytesRead; + } + + /** + * Establishes a connection, following redirects to do so where permitted. + */ + private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException { + URL url = new URL(dataSpec.uri.toString()); + byte[] postBody = dataSpec.postBody; + long position = dataSpec.position; + long length = dataSpec.length; + boolean allowGzip = dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_GZIP); + + if (!allowCrossProtocolRedirects) { + // HttpURLConnection disallows cross-protocol redirects, but otherwise performs redirection + // automatically. This is the behavior we want, so use it. + return makeConnection(url, postBody, position, length, allowGzip, true /* followRedirects */); + } + + // We need to handle redirects ourselves to allow cross-protocol redirects. + int redirectCount = 0; + while (redirectCount++ <= MAX_REDIRECTS) { + HttpURLConnection connection = makeConnection( + url, postBody, position, length, allowGzip, false /* followRedirects */); + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_MULT_CHOICE + || responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_MOVED_TEMP + || responseCode == HttpURLConnection.HTTP_SEE_OTHER + || (postBody == null + && (responseCode == 307 /* HTTP_TEMP_REDIRECT */ + || responseCode == 308 /* HTTP_PERM_REDIRECT */))) { + // For 300, 301, 302, and 303 POST requests follow the redirect and are transformed into + // GET requests. For 307 and 308 POST requests are not redirected. + postBody = null; + String location = connection.getHeaderField("Location"); + connection.disconnect(); + url = handleRedirect(url, location); + } else { + return connection; + } + } + + // If we get here we've been redirected more times than are permitted. + throw new NoRouteToHostException("Too many redirects: " + redirectCount); + } + + /** + * Configures a connection and opens it. + * + * @param url The url to connect to. + * @param postBody The body data for a POST request. + * @param position The byte offset of the requested data. + * @param length The length of the requested data, or {@link C#LENGTH_UNSET}. + * @param allowGzip Whether to allow the use of gzip. + * @param followRedirects Whether to follow redirects. + */ + private HttpURLConnection makeConnection(URL url, byte[] postBody, long position, + long length, boolean allowGzip, boolean followRedirects) throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(connectTimeoutMillis); + connection.setReadTimeout(readTimeoutMillis); + if (defaultRequestProperties != null) { + for (Map.Entry property : defaultRequestProperties.getSnapshot().entrySet()) { + connection.setRequestProperty(property.getKey(), property.getValue()); + } + } + for (Map.Entry property : requestProperties.getSnapshot().entrySet()) { + connection.setRequestProperty(property.getKey(), property.getValue()); + } + if (!(position == 0 && length == C.LENGTH_UNSET)) { + String rangeRequest = "bytes=" + position + "-"; + if (length != C.LENGTH_UNSET) { + rangeRequest += (position + length - 1); + } + connection.setRequestProperty("Range", rangeRequest); + } + connection.setRequestProperty("User-Agent", userAgent); + if (!allowGzip) { + connection.setRequestProperty("Accept-Encoding", "identity"); + } + connection.setInstanceFollowRedirects(followRedirects); + connection.setDoOutput(postBody != null); + if (postBody != null) { + connection.setRequestMethod("POST"); + if (postBody.length == 0) { + connection.connect(); + } else { + connection.setFixedLengthStreamingMode(postBody.length); + connection.connect(); + OutputStream os = connection.getOutputStream(); + os.write(postBody); + os.close(); + } + } else { + connection.connect(); + } + return connection; + } + + /** + * Handles a redirect. + * + * @param originalUrl The original URL. + * @param location The Location header in the response. + * @return The next URL. + * @throws IOException If redirection isn't possible. + */ + private static URL handleRedirect(URL originalUrl, String location) throws IOException { + if (location == null) { + throw new ProtocolException("Null location redirect"); + } + // Form the new url. + URL url = new URL(originalUrl, location); + // Check that the protocol of the new url is supported. + String protocol = url.getProtocol(); + if (!"https".equals(protocol) && !"http".equals(protocol)) { + throw new ProtocolException("Unsupported protocol redirect: " + protocol); + } + // Currently this method is only called if allowCrossProtocolRedirects is true, and so the code + // below isn't required. If we ever decide to handle redirects ourselves when cross-protocol + // redirects are disabled, we'll need to uncomment this block of code. + // if (!allowCrossProtocolRedirects && !protocol.equals(originalUrl.getProtocol())) { + // throw new ProtocolException("Disallowed cross-protocol redirect (" + // + originalUrl.getProtocol() + " to " + protocol + ")"); + // } + return url; + } + + /** + * Attempts to extract the length of the content from the response headers of an open connection. + * + * @param connection The open connection. + * @return The extracted length, or {@link C#LENGTH_UNSET}. + */ + private static long getContentLength(HttpURLConnection connection) { + long contentLength = C.LENGTH_UNSET; + String contentLengthHeader = connection.getHeaderField("Content-Length"); + if (!TextUtils.isEmpty(contentLengthHeader)) { + try { + contentLength = Long.parseLong(contentLengthHeader); + } catch (NumberFormatException e) { + e.getStackTrace(); + } + } + String contentRangeHeader = connection.getHeaderField("Content-Range"); + if (!TextUtils.isEmpty(contentRangeHeader)) { + Matcher matcher = CONTENT_RANGE_HEADER.matcher(contentRangeHeader); + if (matcher.find()) { + try { + long contentLengthFromRange = + Long.parseLong(matcher.group(2)) - Long.parseLong(matcher.group(1)) + 1; + if (contentLength < 0) { + // Some proxy servers strip the Content-Length header. Fall back to the length + // calculated here in this case. + contentLength = contentLengthFromRange; + } else if (contentLength != contentLengthFromRange) { + // If there is a discrepancy between the Content-Length and Content-Range headers, + // assume the one with the larger value is correct. We have seen cases where carrier + // change one of them to reduce the size of a request, but it is unlikely anybody would + // increase it. + contentLength = Math.max(contentLength, contentLengthFromRange); + } + } catch (NumberFormatException e) { + e.getStackTrace(); + } + } + } + return contentLength; + } + + /** + * Skips any bytes that need skipping. Else does nothing. + *

    + * This implementation is based roughly on {@code libcore.io.Streams.skipByReading()}. + * + * @throws InterruptedIOException If the thread is interrupted during the operation. + * @throws EOFException If the end of the input stream is reached before the bytes are skipped. + */ + private void skipInternal() throws IOException { + if (bytesSkipped == bytesToSkip) { + return; + } + + // Acquire the shared skip buffer. + byte[] skipBuffer = skipBufferReference.getAndSet(null); + if (skipBuffer == null) { + skipBuffer = new byte[4096]; + } + + while (bytesSkipped != bytesToSkip) { + int readLength = (int) Math.min(bytesToSkip - bytesSkipped, skipBuffer.length); + int read = inputStream.read(skipBuffer, 0, readLength); + if (Thread.interrupted()) { + throw new InterruptedIOException(); + } + if (read == -1) { + throw new EOFException(); + } + bytesSkipped += read; + if (listener != null) { + listener.onBytesTransferred(this, read); + } + } + + // Release the shared skip buffer. + skipBufferReference.set(skipBuffer); + } + + /** + * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at + * index {@code offset}. + *

    + * This method blocks until at least one byte of data can be read, the end of the opened range is + * detected, or an exception is thrown. + * + * @param buffer The buffer into which the read data should be stored. + * @param offset The start offset into {@code buffer} at which data should be written. + * @param readLength The maximum number of bytes to read. + * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened + * range is reached. + * @throws IOException If an error occurs reading from the source. + */ + private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { + if (readLength == 0) { + return 0; + } + if (bytesToRead != C.LENGTH_UNSET) { + long bytesRemaining = bytesToRead - bytesRead; + if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + readLength = (int) Math.min(readLength, bytesRemaining); + } + + int read = inputStream.read(buffer, offset, readLength); + if (read == -1) { + if (bytesToRead != C.LENGTH_UNSET) { + // End of stream reached having not read sufficient data. + throw new EOFException(); + } + return C.RESULT_END_OF_INPUT; + } + + bytesRead += read; + if (listener != null) { + listener.onBytesTransferred(this, read); + } + return read; + } + + /** + * On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can + * block for a long time if the stream has a lot of data remaining. Call this method before + * closing the input stream to make a best effort to cause the input stream to encounter an + * unexpected end of input, working around this issue. On other platform API levels, the method + * does nothing. + * + * @param connection The connection whose {@link InputStream} should be terminated. + * @param bytesRemaining The number of bytes remaining to be read from the input stream if its + * length is known. {@link C#LENGTH_UNSET} otherwise. + */ + private static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) { + if (Util.SDK_INT != 19 && Util.SDK_INT != 20) { + return; + } + + try { + InputStream inputStream = connection.getInputStream(); + if (bytesRemaining == C.LENGTH_UNSET) { + // If the input stream has already ended, do nothing. The socket may be re-used. + if (inputStream.read() == -1) { + return; + } + } else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) { + // There isn't much data left. Prefer to allow it to drain, which may allow the socket to be + // re-used. + return; + } + String className = inputStream.getClass().getName(); + if (className.equals("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream") + || className.equals( + "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream")) { + Class superclass = inputStream.getClass().getSuperclass(); + Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput"); + unexpectedEndOfInput.setAccessible(true); + unexpectedEndOfInput.invoke(inputStream); + } + } catch (Exception e) { + // If an IOException then the connection didn't ever have an input stream, or it was closed + // already. If another type of exception then something went wrong, most likely the device + // isn't using okhttp. + e.getStackTrace(); + } + } + + + /** + * Closes the current connection quietly, if there is one. + */ + private void closeConnectionQuietly() { + if (connection != null) { + try { + connection.disconnect(); + } catch (Exception e) { + e.getStackTrace(); + } + connection = null; + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultHttpDataSourceFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultHttpDataSourceFactory.java new file mode 100644 index 0000000..f1b4264 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DefaultHttpDataSourceFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource.BaseFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.HttpDataSource.Factory; + +/** A {@link Factory} that produces {@link DefaultHttpDataSource} instances. */ +public final class DefaultHttpDataSourceFactory extends BaseFactory { + + private final String userAgent; + private final TransferListener listener; + private final int connectTimeoutMillis; + private final int readTimeoutMillis; + private final boolean allowCrossProtocolRedirects; + + /** + * Constructs a DefaultHttpDataSourceFactory. Sets {@link + * DefaultHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout, {@link + * DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout and disables + * cross-protocol redirects. + * + * @param userAgent The User-Agent string that should be used. + */ + public DefaultHttpDataSourceFactory(String userAgent) { + this(userAgent, null); + } + + /** + * Constructs a DefaultHttpDataSourceFactory. Sets {@link + * DefaultHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout, {@link + * DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout and disables + * cross-protocol redirects. + * + * @param userAgent The User-Agent string that should be used. + * @param listener An optional listener. + * @see #DefaultHttpDataSourceFactory(String, TransferListener, int, int, boolean) + */ + public DefaultHttpDataSourceFactory( + String userAgent, TransferListener listener) { + this(userAgent, listener, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, false); + } + + /** + * @param userAgent The User-Agent string that should be used. + * @param listener An optional listener. + * @param connectTimeoutMillis The connection timeout that should be used when requesting remote + * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param readTimeoutMillis The read timeout that should be used when requesting remote data, in + * milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled. + */ + public DefaultHttpDataSourceFactory(String userAgent, + TransferListener listener, int connectTimeoutMillis, + int readTimeoutMillis, boolean allowCrossProtocolRedirects) { + this.userAgent = userAgent; + this.listener = listener; + this.connectTimeoutMillis = connectTimeoutMillis; + this.readTimeoutMillis = readTimeoutMillis; + this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; + } + + @Override + protected DefaultHttpDataSource createDataSourceInternal( + HttpDataSource.RequestProperties defaultRequestProperties) { + return new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis, + readTimeoutMillis, allowCrossProtocolRedirects, defaultRequestProperties); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DummyDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DummyDataSource.java new file mode 100644 index 0000000..5ec5f37 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/DummyDataSource.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import java.io.IOException; + +/** + * A dummy DataSource which provides no data. {@link #open(DataSpec)} throws {@link IOException}. + */ +public final class DummyDataSource implements DataSource { + + public static final DummyDataSource INSTANCE = new DummyDataSource(); + + /** A factory that that produces {@link DummyDataSource}. */ + public static final Factory FACTORY = new Factory() { + @Override + public DataSource createDataSource() { + return new DummyDataSource(); + } + }; + + private DummyDataSource() {} + + @Override + public long open(DataSpec dataSpec) throws IOException { + throw new IOException("Dummy source"); + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Uri getUri() { + return null; + } + + @Override + public void close() throws IOException { + // do nothing. + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/FileDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/FileDataSource.java new file mode 100644 index 0000000..df57fa8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/FileDataSource.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.EOFException; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * A {@link DataSource} for reading local files. + */ +public final class FileDataSource implements DataSource { + + /** + * Thrown when IOException is encountered during local file read operation. + */ + public static class FileDataSourceException extends IOException { + + public FileDataSourceException(IOException cause) { + super(cause); + } + + } + + private final TransferListener listener; + + private RandomAccessFile file; + private Uri uri; + private long bytesRemaining; + private boolean opened; + + public FileDataSource() { + this(null); + } + + /** + * @param listener An optional listener. + */ + public FileDataSource(TransferListener listener) { + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws FileDataSourceException { + try { + uri = dataSpec.uri; + file = new RandomAccessFile(dataSpec.uri.getPath(), "r"); + file.seek(dataSpec.position); + bytesRemaining = dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position + : dataSpec.length; + if (bytesRemaining < 0) { + throw new EOFException(); + } + } catch (IOException e) { + throw new FileDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws FileDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } else { + int bytesRead; + try { + bytesRead = file.read(buffer, offset, (int) Math.min(bytesRemaining, readLength)); + } catch (IOException e) { + throw new FileDataSourceException(e); + } + + if (bytesRead > 0) { + bytesRemaining -= bytesRead; + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + } + + return bytesRead; + } + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws FileDataSourceException { + uri = null; + try { + if (file != null) { + file.close(); + } + } catch (IOException e) { + throw new FileDataSourceException(e); + } finally { + file = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/FileDataSourceFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/FileDataSourceFactory.java new file mode 100644 index 0000000..11c235f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/FileDataSourceFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +/** + * A {@link DataSource.Factory} that produces {@link FileDataSource}. + */ +public final class FileDataSourceFactory implements DataSource.Factory { + + private final TransferListener listener; + + public FileDataSourceFactory() { + this(null); + } + + public FileDataSourceFactory(TransferListener listener) { + this.listener = listener; + } + + @Override + public DataSource createDataSource() { + return new FileDataSource(listener); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/HttpDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/HttpDataSource.java new file mode 100644 index 0000000..da59332 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/HttpDataSource.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.support.annotation.IntDef; +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Predicate; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An HTTP {@link DataSource}. + */ +public interface HttpDataSource extends DataSource { + + /** + * A factory for {@link HttpDataSource} instances. + */ + interface Factory extends DataSource.Factory { + + @Override + HttpDataSource createDataSource(); + + /** + * Gets the default request properties used by all {@link HttpDataSource}s created by the + * factory. Changes to the properties will be reflected in any future requests made by + * {@link HttpDataSource}s created by the factory. + * + * @return The default request properties of the factory. + */ + RequestProperties getDefaultRequestProperties(); + + /** + * Sets a default request header for {@link HttpDataSource} instances created by the factory. + * + * @deprecated Use {@link #getDefaultRequestProperties} instead. + * @param name The name of the header field. + * @param value The value of the field. + */ + @Deprecated + void setDefaultRequestProperty(String name, String value); + + /** + * Clears a default request header for {@link HttpDataSource} instances created by the factory. + * + * @deprecated Use {@link #getDefaultRequestProperties} instead. + * @param name The name of the header field. + */ + @Deprecated + void clearDefaultRequestProperty(String name); + + /** + * Clears all default request headers for all {@link HttpDataSource} instances created by the + * factory. + * + * @deprecated Use {@link #getDefaultRequestProperties} instead. + */ + @Deprecated + void clearAllDefaultRequestProperties(); + + } + + /** + * Stores HTTP request properties (aka HTTP headers) and provides methods to modify the headers + * in a thread safe way to avoid the potential of creating snapshots of an inconsistent or + * unintended state. + */ + final class RequestProperties { + + private final Map requestProperties; + private Map requestPropertiesSnapshot; + + public RequestProperties() { + requestProperties = new HashMap<>(); + } + + /** + * Sets the specified property {@code value} for the specified {@code name}. If a property for + * this name previously existed, the old value is replaced by the specified value. + * + * @param name The name of the request property. + * @param value The value of the request property. + */ + public synchronized void set(String name, String value) { + requestPropertiesSnapshot = null; + requestProperties.put(name, value); + } + + /** + * Sets the keys and values contained in the map. If a property previously existed, the old + * value is replaced by the specified value. If a property previously existed and is not in the + * map, the property is left unchanged. + * + * @param properties The request properties. + */ + public synchronized void set(Map properties) { + requestPropertiesSnapshot = null; + requestProperties.putAll(properties); + } + + /** + * Removes all properties previously existing and sets the keys and values of the map. + * + * @param properties The request properties. + */ + public synchronized void clearAndSet(Map properties) { + requestPropertiesSnapshot = null; + requestProperties.clear(); + requestProperties.putAll(properties); + } + + /** + * Removes a request property by name. + * + * @param name The name of the request property to remove. + */ + public synchronized void remove(String name) { + requestPropertiesSnapshot = null; + requestProperties.remove(name); + } + + /** + * Clears all request properties. + */ + public synchronized void clear() { + requestPropertiesSnapshot = null; + requestProperties.clear(); + } + + /** + * Gets a snapshot of the request properties. + * + * @return A snapshot of the request properties. + */ + public synchronized Map getSnapshot() { + if (requestPropertiesSnapshot == null) { + requestPropertiesSnapshot = Collections.unmodifiableMap(new HashMap<>(requestProperties)); + } + return requestPropertiesSnapshot; + } + + } + + /** + * Base implementation of {@link Factory} that sets default request properties. + */ + abstract class BaseFactory implements Factory { + + private final RequestProperties defaultRequestProperties; + + public BaseFactory() { + defaultRequestProperties = new RequestProperties(); + } + + @Override + public final HttpDataSource createDataSource() { + return createDataSourceInternal(defaultRequestProperties); + } + + @Override + public final RequestProperties getDefaultRequestProperties() { + return defaultRequestProperties; + } + + @Deprecated + @Override + public final void setDefaultRequestProperty(String name, String value) { + defaultRequestProperties.set(name, value); + } + + @Deprecated + @Override + public final void clearDefaultRequestProperty(String name) { + defaultRequestProperties.remove(name); + } + + @Deprecated + @Override + public final void clearAllDefaultRequestProperties() { + defaultRequestProperties.clear(); + } + + /** + * Called by {@link #createDataSource()} to create a {@link HttpDataSource} instance. + * + * @param defaultRequestProperties The default {@code RequestProperties} to be used by the + * {@link HttpDataSource} instance. + * @return A {@link HttpDataSource} instance. + */ + protected abstract HttpDataSource createDataSourceInternal(RequestProperties + defaultRequestProperties); + + } + + /** + * A {@link Predicate} that rejects content types often used for pay-walls. + */ + Predicate REJECT_PAYWALL_TYPES = new Predicate() { + + @Override + public boolean evaluate(String contentType) { + contentType = Util.toLowerInvariant(contentType); + return !TextUtils.isEmpty(contentType) + && (!contentType.contains("text") || contentType.contains("text/vtt")) + && !contentType.contains("html") && !contentType.contains("xml"); + } + + }; + + /** + * Thrown when an error is encountered when trying to read from a {@link HttpDataSource}. + */ + class HttpDataSourceException extends IOException { + + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_OPEN, TYPE_READ, TYPE_CLOSE}) + public @interface Type {} + public static final int TYPE_OPEN = 1; + public static final int TYPE_READ = 2; + public static final int TYPE_CLOSE = 3; + + @Type public final int type; + + /** + * The {@link DataSpec} associated with the current connection. + */ + public final DataSpec dataSpec; + + public HttpDataSourceException(DataSpec dataSpec, @Type int type) { + super(); + this.dataSpec = dataSpec; + this.type = type; + } + + public HttpDataSourceException(String message, DataSpec dataSpec, @Type int type) { + super(message); + this.dataSpec = dataSpec; + this.type = type; + } + + public HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type) { + super(cause); + this.dataSpec = dataSpec; + this.type = type; + } + + public HttpDataSourceException(String message, IOException cause, DataSpec dataSpec, + @Type int type) { + super(message, cause); + this.dataSpec = dataSpec; + this.type = type; + } + + } + + /** + * Thrown when the content type is invalid. + */ + final class InvalidContentTypeException extends HttpDataSourceException { + + public final String contentType; + + public InvalidContentTypeException(String contentType, DataSpec dataSpec) { + super("Invalid content type: " + contentType, dataSpec, TYPE_OPEN); + this.contentType = contentType; + } + + } + + /** + * Thrown when an attempt to open a connection results in a response code not in the 2xx range. + */ + final class InvalidResponseCodeException extends HttpDataSourceException { + + /** + * The response code that was outside of the 2xx range. + */ + public final int responseCode; + + /** + * An unmodifiable map of the response header fields and values. + */ + public final Map> headerFields; + + public InvalidResponseCodeException(int responseCode, Map> headerFields, + DataSpec dataSpec) { + super("Response code: " + responseCode, dataSpec, TYPE_OPEN); + this.responseCode = responseCode; + this.headerFields = headerFields; + } + + } + + @Override + long open(DataSpec dataSpec) throws HttpDataSourceException; + + @Override + void close() throws HttpDataSourceException; + + @Override + int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException; + + /** + * Sets the value of a request header. The value will be used for subsequent connections + * established by the source. + * + * @param name The name of the header field. + * @param value The value of the field. + */ + void setRequestProperty(String name, String value); + + /** + * Clears the value of a request header. The change will apply to subsequent connections + * established by the source. + * + * @param name The name of the header field. + */ + void clearRequestProperty(String name); + + /** + * Clears all request headers that were set by {@link #setRequestProperty(String, String)}. + */ + void clearAllRequestProperties(); + + /** + * Returns the headers provided in the response, or {@code null} if response headers are + * unavailable. + */ + Map> getResponseHeaders(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Loader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Loader.java new file mode 100644 index 0000000..91d2979 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/Loader.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.annotation.SuppressLint; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TraceUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.concurrent.ExecutorService; + +/** + * Manages the background loading of {@link Loadable}s. + */ +public final class Loader implements LoaderErrorThrower { + + /** + * Thrown when an unexpected exception is encountered during loading. + */ + public static final class UnexpectedLoaderException extends IOException { + + public UnexpectedLoaderException(Exception cause) { + super("Unexpected " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), cause); + } + + } + + /** + * An object that can be loaded using a {@link Loader}. + */ + public interface Loadable { + + /** + * Cancels the load. + */ + void cancelLoad(); + + /** + * Returns whether the load has been canceled. + */ + boolean isLoadCanceled(); + + /** + * Performs the load, returning on completion or cancellation. + * + * @throws IOException + * @throws InterruptedException + */ + void load() throws IOException, InterruptedException; + + } + + /** + * A callback to be notified of {@link Loader} events. + */ + public interface Callback { + + /** + * Called when a load has completed. + *

    + * Note: There is guaranteed to be a memory barrier between {@link Loadable#load()} exiting and + * this callback being called. + * + * @param loadable The loadable whose load has completed. + * @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load ended. + * @param loadDurationMs The duration of the load. + */ + void onLoadCompleted(T loadable, long elapsedRealtimeMs, long loadDurationMs); + + /** + * Called when a load has been canceled. + *

    + * Note: If the {@link Loader} has not been released then there is guaranteed to be a memory + * barrier between {@link Loadable#load()} exiting and this callback being called. If the + * {@link Loader} has been released then this callback may be called before + * {@link Loadable#load()} exits. + * + * @param loadable The loadable whose load has been canceled. + * @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load was canceled. + * @param loadDurationMs The duration of the load up to the point at which it was canceled. + * @param released True if the load was canceled because the {@link Loader} was released. False + * otherwise. + */ + void onLoadCanceled(T loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released); + + /** + * Called when a load encounters an error. + *

    + * Note: There is guaranteed to be a memory barrier between {@link Loadable#load()} exiting and + * this callback being called. + * + * @param loadable The loadable whose load has encountered an error. + * @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the error occurred. + * @param loadDurationMs The duration of the load up to the point at which the error occurred. + * @param error The load error. + * @return The desired retry action. One of {@link Loader#RETRY}, + * {@link Loader#RETRY_RESET_ERROR_COUNT}, {@link Loader#DONT_RETRY} and + * {@link Loader#DONT_RETRY_FATAL}. + */ + int onLoadError(T loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error); + + } + + public static final int RETRY = 0; + public static final int RETRY_RESET_ERROR_COUNT = 1; + public static final int DONT_RETRY = 2; + public static final int DONT_RETRY_FATAL = 3; + + private static final int MSG_START = 0; + private static final int MSG_CANCEL = 1; + private static final int MSG_END_OF_SOURCE = 2; + private static final int MSG_IO_EXCEPTION = 3; + private static final int MSG_FATAL_ERROR = 4; + + private final ExecutorService downloadExecutorService; + + private LoadTask currentTask; + private IOException fatalError; + + /** + * @param threadName A name for the loader's thread. + */ + public Loader(String threadName) { + this.downloadExecutorService = Util.newSingleThreadExecutor(threadName); + } + + /** + * Starts loading a {@link Loadable}. + *

    + * The calling thread must be a {@link Looper} thread, which is the thread on which the + * {@link Callback} will be called. + * + * @param The type of the loadable. + * @param loadable The {@link Loadable} to load. + * @param callback A callback to called when the load ends. + * @param defaultMinRetryCount The minimum number of times the load must be retried before + * {@link #maybeThrowError()} will propagate an error. + * @throws IllegalStateException If the calling thread does not have an associated {@link Looper}. + * @return {@link SystemClock#elapsedRealtime} when the load started. + */ + public long startLoading(T loadable, Callback callback, + int defaultMinRetryCount) { + Looper looper = Looper.myLooper(); + Assertions.checkState(looper != null); + long startTimeMs = SystemClock.elapsedRealtime(); + new LoadTask<>(looper, loadable, callback, defaultMinRetryCount, startTimeMs).start(0); + return startTimeMs; + } + + /** + * Returns whether the {@link Loader} is currently loading a {@link Loadable}. + */ + public boolean isLoading() { + return currentTask != null; + } + + /** + * Cancels the current load. This method should only be called when a load is in progress. + */ + public void cancelLoading() { + currentTask.cancel(false); + } + + /** + * Releases the {@link Loader}. This method should be called when the {@link Loader} is no longer + * required. + */ + public void release() { + release(null); + } + + /** + * Releases the {@link Loader}, running {@code postLoadAction} on its thread. This method should + * be called when the {@link Loader} is no longer required. + * + * @param postLoadAction A {@link Runnable} to run on the loader's thread when + * {@link Loadable#load()} is no longer running. + */ + public void release(Runnable postLoadAction) { + if (currentTask != null) { + currentTask.cancel(true); + } + if (postLoadAction != null) { + downloadExecutorService.execute(postLoadAction); + } + downloadExecutorService.shutdown(); + } + + // LoaderErrorThrower implementation. + + @Override + public void maybeThrowError() throws IOException { + maybeThrowError(Integer.MIN_VALUE); + } + + @Override + public void maybeThrowError(int minRetryCount) throws IOException { + if (fatalError != null) { + throw fatalError; + } else if (currentTask != null) { + currentTask.maybeThrowError(minRetryCount == Integer.MIN_VALUE + ? currentTask.defaultMinRetryCount : minRetryCount); + } + } + + // Internal classes. + + @SuppressLint("HandlerLeak") + private final class LoadTask extends Handler implements Runnable { + + private static final String TAG = "LoadTask"; + + private final T loadable; + private final Loader.Callback callback; + public final int defaultMinRetryCount; + private final long startTimeMs; + + private IOException currentError; + private int errorCount; + + private volatile Thread executorThread; + private volatile boolean released; + + public LoadTask(Looper looper, T loadable, Loader.Callback callback, + int defaultMinRetryCount, long startTimeMs) { + super(looper); + this.loadable = loadable; + this.callback = callback; + this.defaultMinRetryCount = defaultMinRetryCount; + this.startTimeMs = startTimeMs; + } + + public void maybeThrowError(int minRetryCount) throws IOException { + if (currentError != null && errorCount > minRetryCount) { + throw currentError; + } + } + + public void start(long delayMillis) { + Assertions.checkState(currentTask == null); + currentTask = this; + if (delayMillis > 0) { + sendEmptyMessageDelayed(MSG_START, delayMillis); + } else { + execute(); + } + } + + public void cancel(boolean released) { + this.released = released; + currentError = null; + if (hasMessages(MSG_START)) { + removeMessages(MSG_START); + if (!released) { + sendEmptyMessage(MSG_CANCEL); + } + } else { + loadable.cancelLoad(); + if (executorThread != null) { + executorThread.interrupt(); + } + } + if (released) { + finish(); + long nowMs = SystemClock.elapsedRealtime(); + callback.onLoadCanceled(loadable, nowMs, nowMs - startTimeMs, true); + } + } + + @Override + public void run() { + try { + executorThread = Thread.currentThread(); + if (!loadable.isLoadCanceled()) { + TraceUtil.beginSection("load:" + loadable.getClass().getSimpleName()); + try { + loadable.load(); + } finally { + TraceUtil.endSection(); + } + } + if (!released) { + sendEmptyMessage(MSG_END_OF_SOURCE); + } + } catch (IOException e) { + if (!released) { + obtainMessage(MSG_IO_EXCEPTION, e).sendToTarget(); + } + } catch (InterruptedException e) { + // The load was canceled. + Assertions.checkState(loadable.isLoadCanceled()); + if (!released) { + sendEmptyMessage(MSG_END_OF_SOURCE); + } + } catch (Exception e) { + // This should never happen, but handle it anyway. + if (!released) { + obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); + } + } catch (Error e) { + // We'd hope that the platform would kill the process if an Error is thrown here, but the + // executor may catch the error (b/20616433). Throw it here, but also pass and throw it from + // the handler thread so that the process dies even if the executor behaves in this way. + if (!released) { + obtainMessage(MSG_FATAL_ERROR, e).sendToTarget(); + } + throw e; + } + } + + @Override + public void handleMessage(Message msg) { + if (released) { + return; + } + if (msg.what == MSG_START) { + execute(); + return; + } + if (msg.what == MSG_FATAL_ERROR) { + throw (Error) msg.obj; + } + finish(); + long nowMs = SystemClock.elapsedRealtime(); + long durationMs = nowMs - startTimeMs; + if (loadable.isLoadCanceled()) { + callback.onLoadCanceled(loadable, nowMs, durationMs, false); + return; + } + switch (msg.what) { + case MSG_CANCEL: + callback.onLoadCanceled(loadable, nowMs, durationMs, false); + break; + case MSG_END_OF_SOURCE: + callback.onLoadCompleted(loadable, nowMs, durationMs); + break; + case MSG_IO_EXCEPTION: + currentError = (IOException) msg.obj; + int retryAction = callback.onLoadError(loadable, nowMs, durationMs, currentError); + if (retryAction == DONT_RETRY_FATAL) { + fatalError = currentError; + } else if (retryAction != DONT_RETRY) { + errorCount = retryAction == RETRY_RESET_ERROR_COUNT ? 1 : errorCount + 1; + start(getRetryDelayMillis()); + } + break; + } + } + + private void execute() { + currentError = null; + downloadExecutorService.execute(currentTask); + } + + private void finish() { + currentTask = null; + } + + private long getRetryDelayMillis() { + return Math.min((errorCount - 1) * 1000, 5000); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/LoaderErrorThrower.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/LoaderErrorThrower.java new file mode 100644 index 0000000..fba4d85 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/LoaderErrorThrower.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader.Loadable; +import java.io.IOException; + +/** + * Conditionally throws errors affecting a {@link Loader}. + */ +public interface LoaderErrorThrower { + + /** + * Throws a fatal error, or a non-fatal error if loading is currently backed off and the current + * {@link Loadable} has incurred a number of errors greater than the {@link Loader}s default + * minimum number of retries. Else does nothing. + * + * @throws IOException The error. + */ + void maybeThrowError() throws IOException; + + /** + * Throws a fatal error, or a non-fatal error if loading is currently backed off and the current + * {@link Loadable} has incurred a number of errors greater than the specified minimum number + * of retries. Else does nothing. + * + * @param minRetryCount A minimum retry count that must be exceeded for a non-fatal error to be + * thrown. Should be non-negative. + * @throws IOException The error. + */ + void maybeThrowError(int minRetryCount) throws IOException; + + /** + * A {@link LoaderErrorThrower} that never throws. + */ + final class Dummy implements LoaderErrorThrower { + + @Override + public void maybeThrowError() throws IOException { + // Do nothing. + } + + @Override + public void maybeThrowError(int minRetryCount) throws IOException { + // Do nothing. + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ParsingLoadable.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ParsingLoadable.java new file mode 100644 index 0000000..174c943 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/ParsingLoadable.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.Loader.Loadable; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@link Loadable} for objects that can be parsed from binary data using a {@link Parser}. + * + * @param The type of the object being loaded. + */ +public final class ParsingLoadable implements Loadable { + + /** + * Parses an object from loaded data. + */ + public interface Parser { + + /** + * Parses an object from a response. + * + * @param uri The source {@link Uri} of the response, after any redirection. + * @param inputStream An {@link InputStream} from which the response data can be read. + * @return The parsed object. + * @throws ParserException If an error occurs parsing the data. + * @throws IOException If an error occurs reading data from the stream. + */ + T parse(Uri uri, InputStream inputStream) throws IOException; + + } + + /** + * The {@link DataSpec} that defines the data to be loaded. + */ + public final DataSpec dataSpec; + /** + * The type of the data. One of the {@code DATA_TYPE_*} constants defined in {@link C}. For + * reporting only. + */ + public final int type; + + private final DataSource dataSource; + private final Parser parser; + + private volatile T result; + private volatile boolean isCanceled; + private volatile long bytesLoaded; + + /** + * @param dataSource A {@link DataSource} to use when loading the data. + * @param uri The {@link Uri} from which the object should be loaded. + * @param type See {@link #type}. + * @param parser Parses the object from the response. + */ + public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser parser) { + this.dataSource = dataSource; + this.dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP); + this.type = type; + this.parser = parser; + } + + /** + * Returns the loaded object, or null if an object has not been loaded. + */ + public final T getResult() { + return result; + } + + /** + * Returns the number of bytes loaded. In the case that the network response was compressed, the + * value returned is the size of the data after decompression. + * + * @return The number of bytes loaded. + */ + public long bytesLoaded() { + return bytesLoaded; + } + + @Override + public final void cancelLoad() { + // We don't actually cancel anything, but we need to record the cancellation so that + // isLoadCanceled can return the correct value. + isCanceled = true; + } + + @Override + public final boolean isLoadCanceled() { + return isCanceled; + } + + @Override + public final void load() throws IOException, InterruptedException { + DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); + try { + inputStream.open(); + result = parser.parse(dataSource.getUri(), inputStream); + } finally { + bytesLoaded = inputStream.bytesRead(); + Util.closeQuietly(inputStream); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/PriorityDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/PriorityDataSource.java new file mode 100644 index 0000000..8b61558 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/PriorityDataSource.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.PriorityTaskManager; +import java.io.IOException; + +/** + * A {@link DataSource} that can be used as part of a task registered with a + * {@link PriorityTaskManager}. + *

    + * Calls to {@link #open(DataSpec)} and {@link #read(byte[], int, int)} are allowed to proceed only + * if there are no higher priority tasks registered to the {@link PriorityTaskManager}. If there + * exists a higher priority task then {@link PriorityTaskManager.PriorityTooLowException} is thrown. + *

    + * Instances of this class are intended to be used as parts of (possibly larger) tasks that are + * registered with the {@link PriorityTaskManager}, and hence do not register as tasks + * themselves. + */ +public final class PriorityDataSource implements DataSource { + + private final DataSource upstream; + private final PriorityTaskManager priorityTaskManager; + private final int priority; + + /** + * @param upstream The upstream {@link DataSource}. + * @param priorityTaskManager The priority manager to which the task is registered. + * @param priority The priority of the task. + */ + public PriorityDataSource(DataSource upstream, PriorityTaskManager priorityTaskManager, + int priority) { + this.upstream = Assertions.checkNotNull(upstream); + this.priorityTaskManager = Assertions.checkNotNull(priorityTaskManager); + this.priority = priority; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + priorityTaskManager.proceedOrThrow(priority); + return upstream.open(dataSpec); + } + + @Override + public int read(byte[] buffer, int offset, int max) throws IOException { + priorityTaskManager.proceedOrThrow(priority); + return upstream.read(buffer, offset, max); + } + + @Override + public Uri getUri() { + return upstream.getUri(); + } + + @Override + public void close() throws IOException { + upstream.close(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/PriorityDataSourceFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/PriorityDataSourceFactory.java new file mode 100644 index 0000000..48fc7f8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/PriorityDataSourceFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource.Factory; +import com.tangxiaolv.telegramgallery.exoplayer2.util.PriorityTaskManager; + +/** + * A {@link DataSource.Factory} that produces {@link PriorityDataSource} instances. + */ +public final class PriorityDataSourceFactory implements Factory { + + private final Factory upstreamFactory; + private final PriorityTaskManager priorityTaskManager; + private final int priority; + + /** + * @param upstreamFactory A {@link DataSource.Factory} to be used to create an upstream {@link + * DataSource} for {@link PriorityDataSource}. + * @param priorityTaskManager The priority manager to which PriorityDataSource task is registered. + * @param priority The priority of PriorityDataSource task. + */ + public PriorityDataSourceFactory(Factory upstreamFactory, PriorityTaskManager priorityTaskManager, + int priority) { + this.upstreamFactory = upstreamFactory; + this.priorityTaskManager = priorityTaskManager; + this.priority = priority; + } + + @Override + public PriorityDataSource createDataSource() { + return new PriorityDataSource(upstreamFactory.createDataSource(), priorityTaskManager, + priority); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/RawResourceDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/RawResourceDataSource.java new file mode 100644 index 0000000..a2cd7f4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/RawResourceDataSource.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.content.res.Resources; +import android.net.Uri; +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@link DataSource} for reading a raw resource inside the APK. + *

    + * URIs supported by this source are of the form {@code rawresource:///rawResourceId}, where + * rawResourceId is the integer identifier of a raw resource. {@link #buildRawResourceUri(int)} can + * be used to build {@link Uri}s in this format. + */ +public final class RawResourceDataSource implements DataSource { + + /** + * Thrown when an {@link IOException} is encountered reading from a raw resource. + */ + public static class RawResourceDataSourceException extends IOException { + public RawResourceDataSourceException(String message) { + super(message); + } + + public RawResourceDataSourceException(IOException e) { + super(e); + } + } + + /** + * Builds a {@link Uri} for the specified raw resource identifier. + * + * @param rawResourceId A raw resource identifier (i.e. a constant defined in {@code R.raw}). + * @return The corresponding {@link Uri}. + */ + public static Uri buildRawResourceUri(int rawResourceId) { + return Uri.parse(RAW_RESOURCE_SCHEME + ":///" + rawResourceId); + } + + private static final String RAW_RESOURCE_SCHEME = "rawresource"; + + private final Resources resources; + private final TransferListener listener; + + private Uri uri; + private AssetFileDescriptor assetFileDescriptor; + private InputStream inputStream; + private long bytesRemaining; + private boolean opened; + + /** + * @param context A context. + */ + public RawResourceDataSource(Context context) { + this(context, null); + } + + /** + * @param context A context. + * @param listener An optional listener. + */ + public RawResourceDataSource(Context context, + TransferListener listener) { + this.resources = context.getResources(); + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws RawResourceDataSourceException { + try { + uri = dataSpec.uri; + if (!TextUtils.equals(RAW_RESOURCE_SCHEME, uri.getScheme())) { + throw new RawResourceDataSourceException("URI must use scheme " + RAW_RESOURCE_SCHEME); + } + + int resourceId; + try { + resourceId = Integer.parseInt(uri.getLastPathSegment()); + } catch (NumberFormatException e) { + throw new RawResourceDataSourceException("Resource identifier must be an integer."); + } + + assetFileDescriptor = resources.openRawResourceFd(resourceId); + inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); + inputStream.skip(assetFileDescriptor.getStartOffset()); + long skipped = inputStream.skip(dataSpec.position); + if (skipped < dataSpec.position) { + // We expect the skip to be satisfied in full. If it isn't then we're probably trying to + // skip beyond the end of the data. + throw new EOFException(); + } + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = dataSpec.length; + } else { + long assetFileDescriptorLength = assetFileDescriptor.getLength(); + // If the length is UNKNOWN_LENGTH then the asset extends to the end of the file. + bytesRemaining = assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH + ? C.LENGTH_UNSET : (assetFileDescriptorLength - dataSpec.position); + } + } catch (IOException e) { + throw new RawResourceDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws RawResourceDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + + int bytesRead; + try { + int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength + : (int) Math.min(bytesRemaining, readLength); + bytesRead = inputStream.read(buffer, offset, bytesToRead); + } catch (IOException e) { + throw new RawResourceDataSourceException(e); + } + + if (bytesRead == -1) { + if (bytesRemaining != C.LENGTH_UNSET) { + // End of stream reached having not read sufficient data. + throw new RawResourceDataSourceException(new EOFException()); + } + return C.RESULT_END_OF_INPUT; + } + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; + } + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + return bytesRead; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws RawResourceDataSourceException { + uri = null; + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + throw new RawResourceDataSourceException(e); + } finally { + inputStream = null; + try { + if (assetFileDescriptor != null) { + assetFileDescriptor.close(); + } + } catch (IOException e) { + throw new RawResourceDataSourceException(e); + } finally { + assetFileDescriptor = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/TeeDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/TeeDataSource.java new file mode 100644 index 0000000..74feb27 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/TeeDataSource.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Tees data into a {@link DataSink} as the data is read. + */ +public final class TeeDataSource implements DataSource { + + private final DataSource upstream; + private final DataSink dataSink; + + /** + * @param upstream The upstream {@link DataSource}. + * @param dataSink The {@link DataSink} into which data is written. + */ + public TeeDataSource(DataSource upstream, DataSink dataSink) { + this.upstream = Assertions.checkNotNull(upstream); + this.dataSink = Assertions.checkNotNull(dataSink); + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + long dataLength = upstream.open(dataSpec); + if (dataSpec.length == C.LENGTH_UNSET && dataLength != C.LENGTH_UNSET) { + // Reconstruct dataSpec in order to provide the resolved length to the sink. + dataSpec = new DataSpec(dataSpec.uri, dataSpec.absoluteStreamPosition, dataSpec.position, + dataLength, dataSpec.key, dataSpec.flags); + } + dataSink.open(dataSpec); + return dataLength; + } + + @Override + public int read(byte[] buffer, int offset, int max) throws IOException { + int num = upstream.read(buffer, offset, max); + if (num > 0) { + // TODO: Consider continuing even if disk writes fail. + dataSink.write(buffer, offset, num); + } + return num; + } + + @Override + public Uri getUri() { + return upstream.getUri(); + } + + @Override + public void close() throws IOException { + try { + upstream.close(); + } finally { + dataSink.close(); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/TransferListener.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/TransferListener.java new file mode 100644 index 0000000..d28364a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/TransferListener.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +/** + * A listener of data transfer events. + */ +public interface TransferListener { + + /** + * Called when a transfer starts. + * + * @param source The source performing the transfer. + * @param dataSpec Describes the data being transferred. + */ + void onTransferStart(S source, DataSpec dataSpec); + + /** + * Called incrementally during a transfer. + * + * @param source The source performing the transfer. + * @param bytesTransferred The number of bytes transferred since the previous call to this + * method (or if the first call, since the transfer was started). + */ + void onBytesTransferred(S source, int bytesTransferred); + + /** + * Called when a transfer ends. + * + * @param source The source performing the transfer. + */ + void onTransferEnd(S source); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/UdpDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/UdpDataSource.java new file mode 100644 index 0000000..c235878 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/UdpDataSource.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MulticastSocket; +import java.net.SocketException; + +/** + * A UDP {@link DataSource}. + */ +public final class UdpDataSource implements DataSource { + + /** + * Thrown when an error is encountered when trying to read from a {@link UdpDataSource}. + */ + public static final class UdpDataSourceException extends IOException { + + public UdpDataSourceException(IOException cause) { + super(cause); + } + + } + + /** + * The default maximum datagram packet size, in bytes. + */ + public static final int DEFAULT_MAX_PACKET_SIZE = 2000; + + /** + * The default socket timeout, in milliseconds. + */ + public static final int DEAFULT_SOCKET_TIMEOUT_MILLIS = 8 * 1000; + + private final TransferListener listener; + private final int socketTimeoutMillis; + private final byte[] packetBuffer; + private final DatagramPacket packet; + + private Uri uri; + private DatagramSocket socket; + private MulticastSocket multicastSocket; + private InetAddress address; + private InetSocketAddress socketAddress; + private boolean opened; + + private int packetRemaining; + + /** + * @param listener An optional listener. + */ + public UdpDataSource(TransferListener listener) { + this(listener, DEFAULT_MAX_PACKET_SIZE); + } + + /** + * @param listener An optional listener. + * @param maxPacketSize The maximum datagram packet size, in bytes. + */ + public UdpDataSource(TransferListener listener, int maxPacketSize) { + this(listener, maxPacketSize, DEAFULT_SOCKET_TIMEOUT_MILLIS); + } + + /** + * @param listener An optional listener. + * @param maxPacketSize The maximum datagram packet size, in bytes. + * @param socketTimeoutMillis The socket timeout in milliseconds. A timeout of zero is interpreted + * as an infinite timeout. + */ + public UdpDataSource(TransferListener listener, int maxPacketSize, + int socketTimeoutMillis) { + this.listener = listener; + this.socketTimeoutMillis = socketTimeoutMillis; + packetBuffer = new byte[maxPacketSize]; + packet = new DatagramPacket(packetBuffer, 0, maxPacketSize); + } + + @Override + public long open(DataSpec dataSpec) throws UdpDataSourceException { + uri = dataSpec.uri; + String host = uri.getHost(); + int port = uri.getPort(); + + try { + address = InetAddress.getByName(host); + socketAddress = new InetSocketAddress(address, port); + if (address.isMulticastAddress()) { + multicastSocket = new MulticastSocket(socketAddress); + multicastSocket.joinGroup(address); + socket = multicastSocket; + } else { + socket = new DatagramSocket(socketAddress); + } + } catch (IOException e) { + throw new UdpDataSourceException(e); + } + + try { + socket.setSoTimeout(socketTimeoutMillis); + } catch (SocketException e) { + throw new UdpDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + return C.LENGTH_UNSET; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws UdpDataSourceException { + if (readLength == 0) { + return 0; + } + + if (packetRemaining == 0) { + // We've read all of the data from the current packet. Get another. + try { + socket.receive(packet); + } catch (IOException e) { + throw new UdpDataSourceException(e); + } + packetRemaining = packet.getLength(); + if (listener != null) { + listener.onBytesTransferred(this, packetRemaining); + } + } + + int packetOffset = packet.getLength() - packetRemaining; + int bytesToRead = Math.min(packetRemaining, readLength); + System.arraycopy(packetBuffer, packetOffset, buffer, offset, bytesToRead); + packetRemaining -= bytesToRead; + return bytesToRead; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() { + uri = null; + if (multicastSocket != null) { + try { + multicastSocket.leaveGroup(address); + } catch (IOException e) { + // Do nothing. + e.getStackTrace(); + } + multicastSocket = null; + } + if (socket != null) { + socket.close(); + socket = null; + } + address = null; + socketAddress = null; + packetRemaining = 0; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/Cache.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/Cache.java new file mode 100644 index 0000000..fad3d23 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/Cache.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import java.io.File; +import java.io.IOException; +import java.util.NavigableSet; +import java.util.Set; + +/** + * An interface for cache. + */ +public interface Cache { + + /** + * Listener of {@link Cache} events. + */ + interface Listener { + + /** + * Called when a {@link CacheSpan} is added to the cache. + * + * @param cache The source of the event. + * @param span The added {@link CacheSpan}. + */ + void onSpanAdded(Cache cache, CacheSpan span); + + /** + * Called when a {@link CacheSpan} is removed from the cache. + * + * @param cache The source of the event. + * @param span The removed {@link CacheSpan}. + */ + void onSpanRemoved(Cache cache, CacheSpan span); + + /** + * Called when an existing {@link CacheSpan} is accessed, causing it to be replaced. The new + * {@link CacheSpan} is guaranteed to represent the same data as the one it replaces, however + * {@link CacheSpan#file} and {@link CacheSpan#lastAccessTimestamp} may have changed. + *

    + * Note that for span replacement, {@link #onSpanAdded(Cache, CacheSpan)} and + * {@link #onSpanRemoved(Cache, CacheSpan)} are not called in addition to this method. + * + * @param cache The source of the event. + * @param oldSpan The old {@link CacheSpan}, which has been removed from the cache. + * @param newSpan The new {@link CacheSpan}, which has been added to the cache. + */ + void onSpanTouched(Cache cache, CacheSpan oldSpan, CacheSpan newSpan); + + } + + /** + * Thrown when an error is encountered when writing data. + */ + class CacheException extends IOException { + + public CacheException(String message) { + super(message); + } + + public CacheException(IOException cause) { + super(cause); + } + + } + + /** + * Registers a listener to listen for changes to a given key. + *

    + * No guarantees are made about the thread or threads on which the listener is called, but it is + * guaranteed that listener methods will be called in a serial fashion (i.e. one at a time) and in + * the same order as events occurred. + * + * @param key The key to listen to. + * @param listener The listener to add. + * @return The current spans for the key. + */ + NavigableSet addListener(String key, Listener listener); + + /** + * Unregisters a listener. + * + * @param key The key to stop listening to. + * @param listener The listener to remove. + */ + void removeListener(String key, Listener listener); + + /** + * Returns the cached spans for a given cache key. + * + * @param key The key for which spans should be returned. + * @return The spans for the key. May be null if there are no such spans. + */ + NavigableSet getCachedSpans(String key); + + /** + * Returns all keys in the cache. + * + * @return All the keys in the cache. + */ + Set getKeys(); + + /** + * Returns the total disk space in bytes used by the cache. + * + * @return The total disk space in bytes. + */ + long getCacheSpace(); + + /** + * A caller should invoke this method when they require data from a given position for a given + * key. + *

    + * If there is a cache entry that overlaps the position, then the returned {@link CacheSpan} + * defines the file in which the data is stored. {@link CacheSpan#isCached} is true. The caller + * may read from the cache file, but does not acquire any locks. + *

    + * If there is no cache entry overlapping {@code offset}, then the returned {@link CacheSpan} + * defines a hole in the cache starting at {@code position} into which the caller may write as it + * obtains the data from some other source. The returned {@link CacheSpan} serves as a lock. + * Whilst the caller holds the lock it may write data into the hole. It may split data into + * multiple files. When the caller has finished writing a file it should commit it to the cache + * by calling {@link #commitFile(File)}. When the caller has finished writing, it must release + * the lock by calling {@link #releaseHoleSpan}. + * + * @param key The key of the data being requested. + * @param position The position of the data being requested. + * @return The {@link CacheSpan}. + * @throws InterruptedException + */ + CacheSpan startReadWrite(String key, long position) throws InterruptedException, CacheException; + + /** + * Same as {@link #startReadWrite(String, long)}. However, if the cache entry is locked, then + * instead of blocking, this method will return null as the {@link CacheSpan}. + * + * @param key The key of the data being requested. + * @param position The position of the data being requested. + * @return The {@link CacheSpan}. Or null if the cache entry is locked. + */ + CacheSpan startReadWriteNonBlocking(String key, long position) throws CacheException; + + /** + * Obtains a cache file into which data can be written. Must only be called when holding a + * corresponding hole {@link CacheSpan} obtained from {@link #startReadWrite(String, long)}. + * + * @param key The cache key for the data. + * @param position The starting position of the data. + * @param maxLength The maximum length of the data to be written. Used only to ensure that there + * is enough space in the cache. + * @return The file into which data should be written. + */ + File startFile(String key, long position, long maxLength) throws CacheException; + + /** + * Commits a file into the cache. Must only be called when holding a corresponding hole + * {@link CacheSpan} obtained from {@link #startReadWrite(String, long)} + * + * @param file A newly written cache file. + */ + void commitFile(File file) throws CacheException; + + /** + * Releases a {@link CacheSpan} obtained from {@link #startReadWrite(String, long)} which + * corresponded to a hole in the cache. + * + * @param holeSpan The {@link CacheSpan} being released. + */ + void releaseHoleSpan(CacheSpan holeSpan); + + /** + * Removes a cached {@link CacheSpan} from the cache, deleting the underlying file. + * + * @param span The {@link CacheSpan} to remove. + */ + void removeSpan(CacheSpan span) throws CacheException; + + /** + * Queries if a range is entirely available in the cache. + * + * @param key The cache key for the data. + * @param position The starting position of the data. + * @param length The length of the data. + * @return true if the data is available in the Cache otherwise false; + */ + boolean isCached(String key, long position, long length); + + /** + * Returns the length of the cached data block starting from the {@code position} to the block end + * up to {@code length} bytes. If the {@code position} isn't cached then -(the length of the gap + * to the next cached data up to {@code length} bytes) is returned. + * + * @param key The cache key for the data. + * @param position The starting position of the data. + * @param length The maximum length of the data to be returned. + * @return the length of the cached or not cached data block length. + */ + long getCachedBytes(String key, long position, long length); + + /** + * Sets the content length for the given key. + * + * @param key The cache key for the data. + * @param length The length of the data. + */ + void setContentLength(String key, long length) throws CacheException; + + /** + * Returns the content length for the given key if one set, or {@link + * com.tangxiaolv.telegramgallery.exoplayer2.C#LENGTH_UNSET} otherwise. + * + * @param key The cache key for the data. + */ + long getContentLength(String key); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSink.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSink.java new file mode 100644 index 0000000..bf9fbfe --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSink.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSink; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache.Cache.CacheException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ReusableBufferedOutputStream; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Writes data into a cache. + */ +public final class CacheDataSink implements DataSink { + + /** Default buffer size. */ + public static final int DEFAULT_BUFFER_SIZE = 20480; + + private final Cache cache; + private final long maxCacheFileSize; + private final int bufferSize; + + private DataSpec dataSpec; + private File file; + private OutputStream outputStream; + private FileOutputStream underlyingFileOutputStream; + private long outputStreamBytesWritten; + private long dataSpecBytesWritten; + private ReusableBufferedOutputStream bufferedOutputStream; + + /** + * Thrown when IOException is encountered when writing data into sink. + */ + public static class CacheDataSinkException extends CacheException { + + public CacheDataSinkException(IOException cause) { + super(cause); + } + + } + + /** + * Constructs a CacheDataSink using the {@link #DEFAULT_BUFFER_SIZE}. + * + * @param cache The cache into which data should be written. + * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for + * a {@link DataSpec} whose size exceeds this value, then the data will be fragmented into + * multiple cache files. + */ + public CacheDataSink(Cache cache, long maxCacheFileSize) { + this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE); + } + + /** + * @param cache The cache into which data should be written. + * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for + * a {@link DataSpec} whose size exceeds this value, then the data will be fragmented into + * multiple cache files. + * @param bufferSize The buffer size in bytes for writing to a cache file. A zero or negative + * value disables buffering. + */ + public CacheDataSink(Cache cache, long maxCacheFileSize, int bufferSize) { + this.cache = Assertions.checkNotNull(cache); + this.maxCacheFileSize = maxCacheFileSize; + this.bufferSize = bufferSize; + } + + @Override + public void open(DataSpec dataSpec) throws CacheDataSinkException { + if (dataSpec.length == C.LENGTH_UNSET + && !dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)) { + this.dataSpec = null; + return; + } + this.dataSpec = dataSpec; + dataSpecBytesWritten = 0; + try { + openNextOutputStream(); + } catch (IOException e) { + throw new CacheDataSinkException(e); + } + } + + @Override + public void write(byte[] buffer, int offset, int length) throws CacheDataSinkException { + if (dataSpec == null) { + return; + } + try { + int bytesWritten = 0; + while (bytesWritten < length) { + if (outputStreamBytesWritten == maxCacheFileSize) { + closeCurrentOutputStream(); + openNextOutputStream(); + } + int bytesToWrite = (int) Math.min(length - bytesWritten, + maxCacheFileSize - outputStreamBytesWritten); + outputStream.write(buffer, offset + bytesWritten, bytesToWrite); + bytesWritten += bytesToWrite; + outputStreamBytesWritten += bytesToWrite; + dataSpecBytesWritten += bytesToWrite; + } + } catch (IOException e) { + throw new CacheDataSinkException(e); + } + } + + @Override + public void close() throws CacheDataSinkException { + if (dataSpec == null) { + return; + } + try { + closeCurrentOutputStream(); + } catch (IOException e) { + throw new CacheDataSinkException(e); + } + } + + private void openNextOutputStream() throws IOException { + long maxLength = dataSpec.length == C.LENGTH_UNSET ? maxCacheFileSize + : Math.min(dataSpec.length - dataSpecBytesWritten, maxCacheFileSize); + file = cache.startFile(dataSpec.key, dataSpec.absoluteStreamPosition + dataSpecBytesWritten, + maxLength); + underlyingFileOutputStream = new FileOutputStream(file); + if (bufferSize > 0) { + if (bufferedOutputStream == null) { + bufferedOutputStream = new ReusableBufferedOutputStream(underlyingFileOutputStream, + bufferSize); + } else { + bufferedOutputStream.reset(underlyingFileOutputStream); + } + outputStream = bufferedOutputStream; + } else { + outputStream = underlyingFileOutputStream; + } + outputStreamBytesWritten = 0; + } + + @SuppressWarnings("ThrowFromFinallyBlock") + private void closeCurrentOutputStream() throws IOException { + if (outputStream == null) { + return; + } + + boolean success = false; + try { + outputStream.flush(); + underlyingFileOutputStream.getFD().sync(); + success = true; + } finally { + Util.closeQuietly(outputStream); + outputStream = null; + File fileToCommit = file; + file = null; + if (success) { + cache.commitFile(fileToCommit); + } else { + fileToCommit.delete(); + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSinkFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSinkFactory.java new file mode 100644 index 0000000..db83797 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSinkFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSink; + +/** + * A {@link DataSink.Factory} that produces {@link CacheDataSink}. + */ +public final class CacheDataSinkFactory implements DataSink.Factory { + + private final Cache cache; + private final long maxCacheFileSize; + private final int bufferSize; + + /** + * @see CacheDataSink#CacheDataSink(Cache, long) + */ + public CacheDataSinkFactory(Cache cache, long maxCacheFileSize) { + this(cache, maxCacheFileSize, CacheDataSink.DEFAULT_BUFFER_SIZE); + } + + /** + * @see CacheDataSink#CacheDataSink(Cache, long, int) + */ + public CacheDataSinkFactory(Cache cache, long maxCacheFileSize, int bufferSize) { + this.cache = cache; + this.maxCacheFileSize = maxCacheFileSize; + this.bufferSize = bufferSize; + } + + @Override + public DataSink createDataSink() { + return new CacheDataSink(cache, maxCacheFileSize, bufferSize); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSource.java new file mode 100644 index 0000000..0e45a43 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSource.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import android.net.Uri; +import android.support.annotation.IntDef; +import android.support.annotation.Nullable; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSink; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSourceException; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.FileDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.TeeDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache.Cache.CacheException; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A {@link DataSource} that reads and writes a {@link Cache}. Requests are fulfilled from the cache + * when possible. When data is not cached it is requested from an upstream {@link DataSource} and + * written into the cache. + */ +public final class CacheDataSource implements DataSource { + + /** + * Default maximum single cache file size. + * + * @see #CacheDataSource(Cache, DataSource, int) + * @see #CacheDataSource(Cache, DataSource, int, long) + */ + public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024; + + /** + * Flags controlling the cache's behavior. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR, + FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}) + public @interface Flags {} + /** + * A flag indicating whether we will block reads if the cache key is locked. If this flag is + * set, then we will read from upstream if the cache key is locked. + */ + public static final int FLAG_BLOCK_ON_CACHE = 1 << 0; + + /** + * A flag indicating whether the cache is bypassed following any cache related error. If set + * then cache related exceptions may be thrown for one cycle of open, read and close calls. + * Subsequent cycles of these calls will then bypass the cache. + */ + public static final int FLAG_IGNORE_CACHE_ON_ERROR = 1 << 1; + + /** + * A flag indicating that the cache should be bypassed for requests whose lengths are unset. + */ + public static final int FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS = 1 << 2; + + /** + * Listener of {@link CacheDataSource} events. + */ + public interface EventListener { + + /** + * Called when bytes have been read from the cache. + * + * @param cacheSizeBytes Current cache size in bytes. + * @param cachedBytesRead Total bytes read from the cache since this method was last called. + */ + void onCachedBytesRead(long cacheSizeBytes, long cachedBytesRead); + + } + + private final Cache cache; + private final DataSource cacheReadDataSource; + private final DataSource cacheWriteDataSource; + private final DataSource upstreamDataSource; + @Nullable private final EventListener eventListener; + + private final boolean blockOnCache; + private final boolean ignoreCacheOnError; + private final boolean ignoreCacheForUnsetLengthRequests; + + private DataSource currentDataSource; + private boolean currentRequestUnbounded; + private Uri uri; + private int flags; + private String key; + private long readPosition; + private long bytesRemaining; + private CacheSpan lockedSpan; + private boolean seenCacheError; + private boolean currentRequestIgnoresCache; + private long totalCachedBytesRead; + + /** + * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}. + */ + public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) { + this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE); + } + + /** + * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache. The sink is configured to fragment data such that no single + * cache file is greater than maxCacheFileSize bytes. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link + * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. + * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size + * exceeds this value, then the data will be fragmented into multiple cache files. The + * finer-grained this is the finer-grained the eviction policy can be. + */ + public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags, + long maxCacheFileSize) { + this(cache, upstream, new FileDataSource(), new CacheDataSink(cache, maxCacheFileSize), + flags, null); + } + + /** + * Constructs an instance with arbitrary {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache. One use of this constructor is to allow data to be transformed + * before it is written to disk. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. + * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is + * accessed read-only. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link + * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. + * @param eventListener An optional {@link EventListener} to receive events. + */ + public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, + DataSink cacheWriteDataSink, @Flags int flags, @Nullable EventListener eventListener) { + this.cache = cache; + this.cacheReadDataSource = cacheReadDataSource; + this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; + this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0; + this.ignoreCacheForUnsetLengthRequests = + (flags & FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS) != 0; + this.upstreamDataSource = upstream; + if (cacheWriteDataSink != null) { + this.cacheWriteDataSource = new TeeDataSource(upstream, cacheWriteDataSink); + } else { + this.cacheWriteDataSource = null; + } + this.eventListener = eventListener; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + try { + uri = dataSpec.uri; + flags = dataSpec.flags; + key = CacheUtil.getKey(dataSpec); + readPosition = dataSpec.position; + currentRequestIgnoresCache = (ignoreCacheOnError && seenCacheError) + || (dataSpec.length == C.LENGTH_UNSET && ignoreCacheForUnsetLengthRequests); + if (dataSpec.length != C.LENGTH_UNSET || currentRequestIgnoresCache) { + bytesRemaining = dataSpec.length; + } else { + bytesRemaining = cache.getContentLength(key); + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= dataSpec.position; + if (bytesRemaining <= 0) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } + } + } + openNextSource(true); + return bytesRemaining; + } catch (IOException e) { + handleBeforeThrow(e); + throw e; + } + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + if (readLength == 0) { + return 0; + } + if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + try { + int bytesRead = currentDataSource.read(buffer, offset, readLength); + if (bytesRead >= 0) { + if (currentDataSource == cacheReadDataSource) { + totalCachedBytesRead += bytesRead; + } + readPosition += bytesRead; + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; + } + } else { + if (currentRequestUnbounded) { + // We only do unbounded requests to upstream and only when we don't know the actual stream + // length. So we reached the end of stream. + setContentLength(readPosition); + bytesRemaining = 0; + } + closeCurrentSource(); + if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) { + if (openNextSource(false)) { + return read(buffer, offset, readLength); + } + } + } + return bytesRead; + } catch (IOException e) { + handleBeforeThrow(e); + throw e; + } + } + + @Override + public Uri getUri() { + return currentDataSource == upstreamDataSource ? currentDataSource.getUri() : uri; + } + + @Override + public void close() throws IOException { + uri = null; + notifyBytesRead(); + try { + closeCurrentSource(); + } catch (IOException e) { + handleBeforeThrow(e); + throw e; + } + } + + /** + * Opens the next source. If the cache contains data spanning the current read position then + * {@link #cacheReadDataSource} is opened to read from it. Else {@link #upstreamDataSource} is + * opened to read from the upstream source and write into the cache. + * @param initial Whether it is the initial open call. + */ + private boolean openNextSource(boolean initial) throws IOException { + DataSpec dataSpec; + CacheSpan span; + if (currentRequestIgnoresCache) { + span = null; + } else if (blockOnCache) { + try { + span = cache.startReadWrite(key, readPosition); + } catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } else { + span = cache.startReadWriteNonBlocking(key, readPosition); + } + + if (span == null) { + // The data is locked in the cache, or we're ignoring the cache. Bypass the cache and read + // from upstream. + currentDataSource = upstreamDataSource; + dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key, flags); + } else if (span.isCached) { + // Data is cached, read from cache. + Uri fileUri = Uri.fromFile(span.file); + long filePosition = readPosition - span.position; + long length = span.length - filePosition; + if (bytesRemaining != C.LENGTH_UNSET) { + length = Math.min(length, bytesRemaining); + } + dataSpec = new DataSpec(fileUri, readPosition, filePosition, length, key, flags); + currentDataSource = cacheReadDataSource; + } else { + // Data is not cached, and data is not locked, read from upstream with cache backing. + long length; + if (span.isOpenEnded()) { + length = bytesRemaining; + } else { + length = span.length; + if (bytesRemaining != C.LENGTH_UNSET) { + length = Math.min(length, bytesRemaining); + } + } + dataSpec = new DataSpec(uri, readPosition, length, key, flags); + if (cacheWriteDataSource != null) { + currentDataSource = cacheWriteDataSource; + lockedSpan = span; + } else { + currentDataSource = upstreamDataSource; + cache.releaseHoleSpan(span); + } + } + + currentRequestUnbounded = dataSpec.length == C.LENGTH_UNSET; + boolean successful = false; + long currentBytesRemaining = 0; + try { + currentBytesRemaining = currentDataSource.open(dataSpec); + successful = true; + } catch (IOException e) { + // if this isn't the initial open call (we had read some bytes) and an unbounded range request + // failed because of POSITION_OUT_OF_RANGE then mute the exception. We are trying to find the + // end of the stream. + if (!initial && currentRequestUnbounded) { + Throwable cause = e; + while (cause != null) { + if (cause instanceof DataSourceException) { + int reason = ((DataSourceException) cause).reason; + if (reason == DataSourceException.POSITION_OUT_OF_RANGE) { + e = null; + break; + } + } + cause = cause.getCause(); + } + } + if (e != null) { + throw e; + } + } + + // If we did an unbounded request (which means it's to upstream and + // bytesRemaining == C.LENGTH_UNSET) and got a resolved length from open() request + if (currentRequestUnbounded && currentBytesRemaining != C.LENGTH_UNSET) { + bytesRemaining = currentBytesRemaining; + setContentLength(dataSpec.position + bytesRemaining); + } + return successful; + } + + private void setContentLength(long length) throws IOException { + // If writing into cache + if (currentDataSource == cacheWriteDataSource) { + cache.setContentLength(key, length); + } + } + + private void closeCurrentSource() throws IOException { + if (currentDataSource == null) { + return; + } + try { + currentDataSource.close(); + currentDataSource = null; + currentRequestUnbounded = false; + } finally { + if (lockedSpan != null) { + cache.releaseHoleSpan(lockedSpan); + lockedSpan = null; + } + } + } + + private void handleBeforeThrow(IOException exception) { + if (currentDataSource == cacheReadDataSource || exception instanceof CacheException) { + seenCacheError = true; + } + } + + private void notifyBytesRead() { + if (eventListener != null && totalCachedBytesRead > 0) { + eventListener.onCachedBytesRead(cache.getCacheSpace(), totalCachedBytesRead); + totalCachedBytesRead = 0; + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSourceFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSourceFactory.java new file mode 100644 index 0000000..ecd3d78 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheDataSourceFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSink; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource.Factory; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.FileDataSourceFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache.CacheDataSource.EventListener; + +/** + * A {@link DataSource.Factory} that produces {@link CacheDataSource}. + */ +public final class CacheDataSourceFactory implements DataSource.Factory { + + private final Cache cache; + private final DataSource.Factory upstreamFactory; + private final DataSource.Factory cacheReadDataSourceFactory; + private final DataSink.Factory cacheWriteDataSinkFactory; + private final int flags; + private final EventListener eventListener; + + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource, int) + */ + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags) { + this(cache, upstreamFactory, flags, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE); + } + + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long) + */ + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags, + long maxCacheFileSize) { + this(cache, upstreamFactory, new FileDataSourceFactory(), + new CacheDataSinkFactory(cache, maxCacheFileSize), flags, null); + } + + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource, DataSource, DataSink, int, + * EventListener) + */ + public CacheDataSourceFactory(Cache cache, Factory upstreamFactory, + Factory cacheReadDataSourceFactory, + DataSink.Factory cacheWriteDataSinkFactory, int flags, EventListener eventListener) { + this.cache = cache; + this.upstreamFactory = upstreamFactory; + this.cacheReadDataSourceFactory = cacheReadDataSourceFactory; + this.cacheWriteDataSinkFactory = cacheWriteDataSinkFactory; + this.flags = flags; + this.eventListener = eventListener; + } + + @Override + public CacheDataSource createDataSource() { + return new CacheDataSource(cache, upstreamFactory.createDataSource(), + cacheReadDataSourceFactory.createDataSource(), + cacheWriteDataSinkFactory != null ? cacheWriteDataSinkFactory.createDataSink() : null, + flags, eventListener); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheEvictor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheEvictor.java new file mode 100644 index 0000000..dffd68d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheEvictor.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +/** + * Evicts data from a {@link Cache}. Implementations should call {@link Cache#removeSpan(CacheSpan)} + * to evict cache entries based on their eviction policies. + */ +public interface CacheEvictor extends Cache.Listener { + + /** + * Called when cache has been initialized. + */ + void onCacheInitialized(); + + /** + * Called when a writer starts writing to the cache. + * + * @param cache The source of the event. + * @param key The key being written. + * @param position The starting position of the data being written. + * @param maxLength The maximum length of the data being written. + */ + void onStartFile(Cache cache, String key, long position, long maxLength); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheSpan.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheSpan.java new file mode 100644 index 0000000..5d8351b --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheSpan.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import android.support.annotation.NonNull; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.io.File; + +/** + * Defines a span of data that may or may not be cached (as indicated by {@link #isCached}). + */ +public class CacheSpan implements Comparable { + + /** + * The cache key that uniquely identifies the original stream. + */ + public final String key; + /** + * The position of the {@link CacheSpan} in the original stream. + */ + public final long position; + /** + * The length of the {@link CacheSpan}, or {@link C#LENGTH_UNSET} if this is an open-ended hole. + */ + public final long length; + /** + * Whether the {@link CacheSpan} is cached. + */ + public final boolean isCached; + /** + * The file corresponding to this {@link CacheSpan}, or null if {@link #isCached} is false. + */ + public final File file; + /** + * The last access timestamp, or {@link C#TIME_UNSET} if {@link #isCached} is false. + */ + public final long lastAccessTimestamp; + + /** + * Creates a hole CacheSpan which isn't cached, has no last access time and no file associated. + * + * @param key The cache key that uniquely identifies the original stream. + * @param position The position of the {@link CacheSpan} in the original stream. + * @param length The length of the {@link CacheSpan}, or {@link C#LENGTH_UNSET} if this is an + * open-ended hole. + */ + public CacheSpan(String key, long position, long length) { + this(key, position, length, C.TIME_UNSET, null); + } + + /** + * Creates a CacheSpan. + * + * @param key The cache key that uniquely identifies the original stream. + * @param position The position of the {@link CacheSpan} in the original stream. + * @param length The length of the {@link CacheSpan}, or {@link C#LENGTH_UNSET} if this is an + * open-ended hole. + * @param lastAccessTimestamp The last access timestamp, or {@link C#TIME_UNSET} if + * {@link #isCached} is false. + * @param file The file corresponding to this {@link CacheSpan}, or null if it's a hole. + */ + public CacheSpan(String key, long position, long length, long lastAccessTimestamp, File file) { + this.key = key; + this.position = position; + this.length = length; + this.isCached = file != null; + this.file = file; + this.lastAccessTimestamp = lastAccessTimestamp; + } + + /** + * Returns whether this is an open-ended {@link CacheSpan}. + */ + public boolean isOpenEnded() { + return length == C.LENGTH_UNSET; + } + + /** + * Returns whether this is a hole {@link CacheSpan}. + */ + public boolean isHoleSpan() { + return !isCached; + } + + @Override + public int compareTo(@NonNull CacheSpan another) { + if (!key.equals(another.key)) { + return key.compareTo(another.key); + } + long startOffsetDiff = position - another.position; + return startOffsetDiff == 0 ? 0 : ((startOffsetDiff < 0) ? -1 : 1); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheUtil.java new file mode 100644 index 0000000..296bf87 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CacheUtil.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.PriorityTaskManager; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.IOException; +import java.util.NavigableSet; + +/** + * Caching related utility methods. + */ +public final class CacheUtil { + + /** Holds the counters used during caching. */ + public static class CachingCounters { + /** Total number of already cached bytes. */ + public long alreadyCachedBytes; + /** + * Total number of downloaded bytes. + * + *

    {@link #getCached(DataSpec, Cache, CachingCounters)} sets it to the count of the missing + * bytes or to {@link C#LENGTH_UNSET} if {@code dataSpec} is unbounded and content length isn't + * available in the {@code cache}. + */ + public long downloadedBytes; + } + + /** + * Generates a cache key out of the given {@link Uri}. + * + * @param uri Uri of a content which the requested key is for. + */ + public static String generateKey(Uri uri) { + return uri.toString(); + } + + /** + * Returns the {@code dataSpec.key} if not null, otherwise generates a cache key out of {@code + * dataSpec.uri} + * + * @param dataSpec Defines a content which the requested key is for. + */ + public static String getKey(DataSpec dataSpec) { + return dataSpec.key != null ? dataSpec.key : generateKey(dataSpec.uri); + } + + /** + * Returns already cached and missing bytes in the {@cache} for the data defined by {@code + * dataSpec}. + * + * @param dataSpec Defines the data to be checked. + * @param cache A {@link Cache} which has the data. + * @param counters The counters to be set. If null a new {@link CachingCounters} is created and + * used. + * @return The used {@link CachingCounters} instance. + */ + public static CachingCounters getCached(DataSpec dataSpec, Cache cache, + CachingCounters counters) { + try { + return internalCache(dataSpec, cache, null, null, null, 0, counters); + } catch (IOException | InterruptedException e) { + throw new IllegalStateException(e); + } + } + + /** + * Caches the data defined by {@code dataSpec} while skipping already cached data. + * + * @param dataSpec Defines the data to be cached. + * @param cache A {@link Cache} to store the data. + * @param dataSource A {@link CacheDataSource} that works on the {@code cache}. + * @param buffer The buffer to be used while caching. + * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with + * caching. + * @param priority The priority of this task. Used with {@code priorityTaskManager}. + * @param counters The counters to be set during caching. If not null its values reset to + * zero before using. If null a new {@link CachingCounters} is created and used. + * @return The used {@link CachingCounters} instance. + * @throws IOException If an error occurs reading from the source. + * @throws InterruptedException If the thread was interrupted. + */ + public static CachingCounters cache(DataSpec dataSpec, Cache cache, CacheDataSource dataSource, + byte[] buffer, PriorityTaskManager priorityTaskManager, int priority, + CachingCounters counters) throws IOException, InterruptedException { + Assertions.checkNotNull(dataSource); + Assertions.checkNotNull(buffer); + return internalCache(dataSpec, cache, dataSource, buffer, priorityTaskManager, priority, + counters); + } + + /** + * Caches the data defined by {@code dataSpec} while skipping already cached data. If {@code + * dataSource} or {@code buffer} is null performs a dry run. + * + * @param dataSpec Defines the data to be cached. + * @param cache A {@link Cache} to store the data. + * @param dataSource A {@link CacheDataSource} that works on the {@code cache}. If null a dry run + * is performed. + * @param buffer The buffer to be used while caching. If null a dry run is performed. + * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with + * caching. + * @param priority The priority of this task. Used with {@code priorityTaskManager}. + * @param counters The counters to be set during caching. If not null its values reset to + * zero before using. If null a new {@link CachingCounters} is created and used. + * @return The used {@link CachingCounters} instance. + * @throws IOException If not dry run and an error occurs reading from the source. + * @throws InterruptedException If not dry run and the thread was interrupted. + */ + private static CachingCounters internalCache(DataSpec dataSpec, Cache cache, + CacheDataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, + int priority, CachingCounters counters) throws IOException, InterruptedException { + long start = dataSpec.position; + long left = dataSpec.length; + String key = getKey(dataSpec); + if (left == C.LENGTH_UNSET) { + left = cache.getContentLength(key); + if (left == C.LENGTH_UNSET) { + left = Long.MAX_VALUE; + } + } + if (counters == null) { + counters = new CachingCounters(); + } else { + counters.alreadyCachedBytes = 0; + counters.downloadedBytes = 0; + } + while (left > 0) { + long blockLength = cache.getCachedBytes(key, start, left); + // Skip already cached data + if (blockLength > 0) { + counters.alreadyCachedBytes += blockLength; + } else { + // There is a hole in the cache which is at least "-blockLength" long. + blockLength = -blockLength; + if (dataSource != null && buffer != null) { + DataSpec subDataSpec = new DataSpec(dataSpec.uri, start, + blockLength == Long.MAX_VALUE ? C.LENGTH_UNSET : blockLength, key); + long read = readAndDiscard(subDataSpec, dataSource, buffer, priorityTaskManager, + priority); + counters.downloadedBytes += read; + if (read < blockLength) { + // Reached end of data. + break; + } + } else if (blockLength == Long.MAX_VALUE) { + counters.downloadedBytes = C.LENGTH_UNSET; + break; + } else { + counters.downloadedBytes += blockLength; + } + } + start += blockLength; + if (left != Long.MAX_VALUE) { + left -= blockLength; + } + } + return counters; + } + + /** + * Reads and discards all data specified by the {@code dataSpec}. + * + * @param dataSpec Defines the data to be read. + * @param dataSource The {@link DataSource} to read the data from. + * @param buffer The buffer to be used while downloading. + * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with + * caching. + * @param priority The priority of this task. + * @return Number of read bytes, or 0 if no data is available because the end of the opened range + * has been reached. + */ + private static long readAndDiscard(DataSpec dataSpec, DataSource dataSource, byte[] buffer, + PriorityTaskManager priorityTaskManager, int priority) + throws IOException, InterruptedException { + while (true) { + if (priorityTaskManager != null) { + // Wait for any other thread with higher priority to finish its job. + priorityTaskManager.proceed(priority); + } + try { + dataSource.open(dataSpec); + long totalRead = 0; + while (true) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + int read = dataSource.read(buffer, 0, buffer.length); + if (read == C.RESULT_END_OF_INPUT) { + return totalRead; + } + totalRead += read; + } + } catch (PriorityTaskManager.PriorityTooLowException exception) { + // catch and try again + exception.getStackTrace(); + } finally { + Util.closeQuietly(dataSource); + } + } + } + + /** Removes all of the data in the {@code cache} pointed by the {@code key}. */ + public static void remove(Cache cache, String key) { + NavigableSet cachedSpans = cache.getCachedSpans(key); + if (cachedSpans == null) { + return; + } + for (CacheSpan cachedSpan : cachedSpans) { + try { + cache.removeSpan(cachedSpan); + } catch (Cache.CacheException e) { + // do nothing + e.getStackTrace(); + } + } + } + + private CacheUtil() {} + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedContent.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedContent.java new file mode 100644 index 0000000..2fbcf1e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedContent.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache.Cache.CacheException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.TreeSet; + +/** + * Defines the cached content for a single stream. + */ +/*package*/ final class CachedContent { + + /** + * The cache file id that uniquely identifies the original stream. + */ + public final int id; + /** + * The cache key that uniquely identifies the original stream. + */ + public final String key; + /** + * The cached spans of this content. + */ + private final TreeSet cachedSpans; + /** + * The length of the original stream, or {@link C#LENGTH_UNSET} if the length is unknown. + */ + private long length; + + /** + * Reads an instance from a {@link DataInputStream}. + * + * @param input Input stream containing values needed to initialize CachedContent instance. + * @throws IOException If an error occurs during reading values. + */ + public CachedContent(DataInputStream input) throws IOException { + this(input.readInt(), input.readUTF(), input.readLong()); + } + + /** + * Creates a CachedContent. + * + * @param id The cache file id. + * @param key The cache stream key. + * @param length The length of the original stream. + */ + public CachedContent(int id, String key, long length) { + this.id = id; + this.key = key; + this.length = length; + this.cachedSpans = new TreeSet<>(); + } + + /** + * Writes the instance to a {@link DataOutputStream}. + * + * @param output Output stream to store the values. + * @throws IOException If an error occurs during writing values to output. + */ + public void writeToStream(DataOutputStream output) throws IOException { + output.writeInt(id); + output.writeUTF(key); + output.writeLong(length); + } + + /** Returns the length of the content. */ + public long getLength() { + return length; + } + + /** Sets the length of the content. */ + public void setLength(long length) { + this.length = length; + } + + /** Adds the given {@link SimpleCacheSpan} which contains a part of the content. */ + public void addSpan(SimpleCacheSpan span) { + cachedSpans.add(span); + } + + /** Returns a set of all {@link SimpleCacheSpan}s. */ + public TreeSet getSpans() { + return cachedSpans; + } + + /** + * Returns the span containing the position. If there isn't one, it returns a hole span + * which defines the maximum extents of the hole in the cache. + */ + public SimpleCacheSpan getSpan(long position) { + SimpleCacheSpan lookupSpan = SimpleCacheSpan.createLookup(key, position); + SimpleCacheSpan floorSpan = cachedSpans.floor(lookupSpan); + if (floorSpan != null && floorSpan.position + floorSpan.length > position) { + return floorSpan; + } + SimpleCacheSpan ceilSpan = cachedSpans.ceiling(lookupSpan); + return ceilSpan == null ? SimpleCacheSpan.createOpenHole(key, position) + : SimpleCacheSpan.createClosedHole(key, position, ceilSpan.position - position); + } + + /** + * Returns the length of the cached data block starting from the {@code position} to the block end + * up to {@code length} bytes. If the {@code position} isn't cached then -(the length of the gap + * to the next cached data up to {@code length} bytes) is returned. + * + * @param position The starting position of the data. + * @param length The maximum length of the data to be returned. + * @return the length of the cached or not cached data block length. + */ + public long getCachedBytes(long position, long length) { + SimpleCacheSpan span = getSpan(position); + if (span.isHoleSpan()) { + // We don't have a span covering the start of the queried region. + return -Math.min(span.isOpenEnded() ? Long.MAX_VALUE : span.length, length); + } + long queryEndPosition = position + length; + long currentEndPosition = span.position + span.length; + if (currentEndPosition < queryEndPosition) { + for (SimpleCacheSpan next : cachedSpans.tailSet(span, false)) { + if (next.position > currentEndPosition) { + // There's a hole in the cache within the queried region. + break; + } + // We expect currentEndPosition to always equal (next.position + next.length), but + // perform a max check anyway to guard against the existence of overlapping spans. + currentEndPosition = Math.max(currentEndPosition, next.position + next.length); + if (currentEndPosition >= queryEndPosition) { + // We've found spans covering the queried region. + break; + } + } + } + return Math.min(currentEndPosition - position, length); + } + + /** + * Copies the given span with an updated last access time. Passed span becomes invalid after this + * call. + * + * @param cacheSpan Span to be copied and updated. + * @return a span with the updated last access time. + * @throws CacheException If renaming of the underlying span file failed. + */ + public SimpleCacheSpan touch(SimpleCacheSpan cacheSpan) throws CacheException { + // Remove the old span from the in-memory representation. + Assertions.checkState(cachedSpans.remove(cacheSpan)); + // Obtain a new span with updated last access timestamp. + SimpleCacheSpan newCacheSpan = cacheSpan.copyWithUpdatedLastAccessTime(id); + // Rename the cache file + if (!cacheSpan.file.renameTo(newCacheSpan.file)) { + throw new CacheException("Renaming of " + cacheSpan.file + " to " + newCacheSpan.file + + " failed."); + } + // Add the updated span back into the in-memory representation. + cachedSpans.add(newCacheSpan); + return newCacheSpan; + } + + /** Returns whether there are any spans cached. */ + public boolean isEmpty() { + return cachedSpans.isEmpty(); + } + + /** Removes the given span from cache. */ + public boolean removeSpan(CacheSpan span) { + if (cachedSpans.remove(span)) { + span.file.delete(); + return true; + } + return false; + } + + /** Calculates a hash code for the header of this {@code CachedContent}. */ + public int headerHashCode() { + int result = id; + result = 31 * result + key.hashCode(); + result = 31 * result + (int) (length ^ (length >>> 32)); + return result; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedContentIndex.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedContentIndex.java new file mode 100644 index 0000000..5326a53 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedContentIndex.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import android.util.SparseArray; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache.Cache.CacheException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.AtomicFile; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ReusableBufferedOutputStream; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Random; +import java.util.Set; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * This class maintains the index of cached content. + */ +/*package*/ final class CachedContentIndex { + + public static final String FILE_NAME = "cached_content_index.exi"; + + private static final int VERSION = 1; + + private static final int FLAG_ENCRYPTED_INDEX = 1; + + private static final String TAG = "CachedContentIndex"; + + private final HashMap keyToContent; + private final SparseArray idToKey; + private final AtomicFile atomicFile; + private final Cipher cipher; + private final SecretKeySpec secretKeySpec; + private boolean changed; + private ReusableBufferedOutputStream bufferedOutputStream; + + /** + * Creates a CachedContentIndex which works on the index file in the given cacheDir. + * + * @param cacheDir Directory where the index file is kept. + */ + public CachedContentIndex(File cacheDir) { + this(cacheDir, null); + } + + /** + * Creates a CachedContentIndex which works on the index file in the given cacheDir. + * + * @param cacheDir Directory where the index file is kept. + * @param secretKey If not null, cache keys will be stored encrypted on filesystem using AES/CBC. + * The key must be 16 bytes long. + */ + public CachedContentIndex(File cacheDir, byte[] secretKey) { + if (secretKey != null) { + Assertions.checkArgument(secretKey.length == 16); + try { + cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + secretKeySpec = new SecretKeySpec(secretKey, "AES"); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalStateException(e); // Should never happen. + } + } else { + cipher = null; + secretKeySpec = null; + } + keyToContent = new HashMap<>(); + idToKey = new SparseArray<>(); + atomicFile = new AtomicFile(new File(cacheDir, FILE_NAME)); + } + + /** Loads the index file. */ + public void load() { + Assertions.checkState(!changed); + if (!readFile()) { + atomicFile.delete(); + keyToContent.clear(); + idToKey.clear(); + } + } + + /** Stores the index data to index file if there is a change. */ + public void store() throws CacheException { + if (!changed) { + return; + } + writeFile(); + changed = false; + } + + /** + * Adds the given key to the index if it isn't there already. + * + * @param key The cache key that uniquely identifies the original stream. + * @return A new or existing CachedContent instance with the given key. + */ + public CachedContent add(String key) { + CachedContent cachedContent = keyToContent.get(key); + if (cachedContent == null) { + cachedContent = addNew(key, C.LENGTH_UNSET); + } + return cachedContent; + } + + /** Returns a CachedContent instance with the given key or null if there isn't one. */ + public CachedContent get(String key) { + return keyToContent.get(key); + } + + /** + * Returns a Collection of all CachedContent instances in the index. The collection is backed by + * the {@code keyToContent} map, so changes to the map are reflected in the collection, and + * vice-versa. If the map is modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), the results of the iteration are + * undefined. + */ + public Collection getAll() { + return keyToContent.values(); + } + + /** Returns an existing or new id assigned to the given key. */ + public int assignIdForKey(String key) { + return add(key).id; + } + + /** Returns the key which has the given id assigned. */ + public String getKeyForId(int id) { + return idToKey.get(id); + } + + /** + * Removes {@link CachedContent} with the given key from index. It shouldn't contain any spans. + * + * @throws IllegalStateException If {@link CachedContent} isn't empty. + */ + public void removeEmpty(String key) { + CachedContent cachedContent = keyToContent.remove(key); + if (cachedContent != null) { + Assertions.checkState(cachedContent.isEmpty()); + idToKey.remove(cachedContent.id); + changed = true; + } + } + + /** Removes empty {@link CachedContent} instances from index. */ + public void removeEmpty() { + LinkedList cachedContentToBeRemoved = new LinkedList<>(); + for (CachedContent cachedContent : keyToContent.values()) { + if (cachedContent.isEmpty()) { + cachedContentToBeRemoved.add(cachedContent.key); + } + } + for (String key : cachedContentToBeRemoved) { + removeEmpty(key); + } + } + + /** + * Returns a set of all content keys. The set is backed by the {@code keyToContent} map, so + * changes to the map are reflected in the set, and vice-versa. If the map is modified while an + * iteration over the set is in progress (except through the iterator's own remove operation), the + * results of the iteration are undefined. + */ + public Set getKeys() { + return keyToContent.keySet(); + } + + /** + * Sets the content length for the given key. A new {@link CachedContent} is added if there isn't + * one already with the given key. + */ + public void setContentLength(String key, long length) { + CachedContent cachedContent = get(key); + if (cachedContent != null) { + if (cachedContent.getLength() != length) { + cachedContent.setLength(length); + changed = true; + } + } else { + addNew(key, length); + } + } + + /** + * Returns the content length for the given key if one set, or {@link + * com.tangxiaolv.telegramgallery.exoplayer2.C#LENGTH_UNSET} otherwise. + */ + public long getContentLength(String key) { + CachedContent cachedContent = get(key); + return cachedContent == null ? C.LENGTH_UNSET : cachedContent.getLength(); + } + + private boolean readFile() { + DataInputStream input = null; + try { + InputStream inputStream = new BufferedInputStream(atomicFile.openRead()); + input = new DataInputStream(inputStream); + int version = input.readInt(); + if (version != VERSION) { + // Currently there is no other version + return false; + } + + int flags = input.readInt(); + if ((flags & FLAG_ENCRYPTED_INDEX) != 0) { + if (cipher == null) { + return false; + } + byte[] initializationVector = new byte[16]; + input.readFully(initializationVector); + IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector); + try { + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalStateException(e); + } + input = new DataInputStream(new CipherInputStream(inputStream, cipher)); + } else { + if (cipher != null) { + changed = true; // Force index to be rewritten encrypted after read. + } + } + + int count = input.readInt(); + int hashCode = 0; + for (int i = 0; i < count; i++) { + CachedContent cachedContent = new CachedContent(input); + add(cachedContent); + hashCode += cachedContent.headerHashCode(); + } + if (input.readInt() != hashCode) { + return false; + } + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + return false; + } finally { + if (input != null) { + Util.closeQuietly(input); + } + } + return true; + } + + private void writeFile() throws CacheException { + DataOutputStream output = null; + try { + OutputStream outputStream = atomicFile.startWrite(); + if (bufferedOutputStream == null) { + bufferedOutputStream = new ReusableBufferedOutputStream(outputStream); + } else { + bufferedOutputStream.reset(outputStream); + } + output = new DataOutputStream(bufferedOutputStream); + output.writeInt(VERSION); + + int flags = cipher != null ? FLAG_ENCRYPTED_INDEX : 0; + output.writeInt(flags); + + if (cipher != null) { + byte[] initializationVector = new byte[16]; + new Random().nextBytes(initializationVector); + output.write(initializationVector); + IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector); + try { + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalStateException(e); // Should never happen. + } + output.flush(); + output = new DataOutputStream(new CipherOutputStream(bufferedOutputStream, cipher)); + } + + output.writeInt(keyToContent.size()); + int hashCode = 0; + for (CachedContent cachedContent : keyToContent.values()) { + cachedContent.writeToStream(output); + hashCode += cachedContent.headerHashCode(); + } + output.writeInt(hashCode); + atomicFile.endWrite(output); + // Avoid calling close twice. Duplicate CipherOutputStream.close calls did + // not used to be no-ops: https://android-review.googlesource.com/#/c/272799/ + output = null; + } catch (IOException e) { + throw new CacheException(e); + } finally { + Util.closeQuietly(output); + } + } + + private void add(CachedContent cachedContent) { + keyToContent.put(cachedContent.key, cachedContent); + idToKey.put(cachedContent.id, cachedContent.key); + } + + /** Adds the given CachedContent to the index. */ + /*package*/ void addNew(CachedContent cachedContent) { + add(cachedContent); + changed = true; + } + + private CachedContent addNew(String key, long length) { + int id = getNewId(idToKey); + CachedContent cachedContent = new CachedContent(id, key, length); + addNew(cachedContent); + return cachedContent; + } + + /** + * Returns an id which isn't used in the given array. If the maximum id in the array is smaller + * than {@link java.lang.Integer#MAX_VALUE} it just returns the next bigger integer. Otherwise it + * returns the smallest unused non-negative integer. + */ + //@VisibleForTesting + public static int getNewId(SparseArray idToKey) { + int size = idToKey.size(); + int id = size == 0 ? 0 : (idToKey.keyAt(size - 1) + 1); + if (id < 0) { // In case if we pass max int value. + // TODO optimization: defragmentation or binary search? + for (id = 0; id < size; id++) { + if (id != idToKey.keyAt(id)) { + break; + } + } + } + return id; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedRegionTracker.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedRegionTracker.java new file mode 100644 index 0000000..2d41e9c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/CachedRegionTracker.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import android.support.annotation.NonNull; +import android.util.Log; +import com.tangxiaolv.telegramgallery.exoplayer2.extractor.ChunkIndex; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.TreeSet; + +/** + * Utility class for efficiently tracking regions of data that are stored in a {@link Cache} + * for a given cache key. + */ +public final class CachedRegionTracker implements Cache.Listener { + + private static final String TAG = "CachedRegionTracker"; + + public static final int NOT_CACHED = -1; + public static final int CACHED_TO_END = -2; + + private final Cache cache; + private final String cacheKey; + private final ChunkIndex chunkIndex; + + private final TreeSet regions; + private final Region lookupRegion; + + public CachedRegionTracker(Cache cache, String cacheKey, ChunkIndex chunkIndex) { + this.cache = cache; + this.cacheKey = cacheKey; + this.chunkIndex = chunkIndex; + this.regions = new TreeSet<>(); + this.lookupRegion = new Region(0, 0); + + synchronized (this) { + NavigableSet cacheSpans = cache.addListener(cacheKey, this); + if (cacheSpans != null) { + // Merge the spans into regions. mergeSpan is more efficient when merging from high to low, + // which is why a descending iterator is used here. + Iterator spanIterator = cacheSpans.descendingIterator(); + while (spanIterator.hasNext()) { + CacheSpan span = spanIterator.next(); + mergeSpan(span); + } + } + } + } + + public void release() { + cache.removeListener(cacheKey, this); + } + + /** + * When provided with a byte offset, this method locates the cached region within which the + * offset falls, and returns the approximate end position in milliseconds of that region. If the + * byte offset does not fall within a cached region then {@link #NOT_CACHED} is returned. + * If the cached region extends to the end of the stream, {@link #CACHED_TO_END} is returned. + * + * @param byteOffset The byte offset in the underlying stream. + * @return The end position of the corresponding cache region, {@link #NOT_CACHED}, or + * {@link #CACHED_TO_END}. + */ + public synchronized int getRegionEndTimeMs(long byteOffset) { + lookupRegion.startOffset = byteOffset; + Region floorRegion = regions.floor(lookupRegion); + if (floorRegion == null || byteOffset > floorRegion.endOffset + || floorRegion.endOffsetIndex == -1) { + return NOT_CACHED; + } + int index = floorRegion.endOffsetIndex; + if (index == chunkIndex.length - 1 + && floorRegion.endOffset == (chunkIndex.offsets[index] + chunkIndex.sizes[index])) { + return CACHED_TO_END; + } + long segmentFractionUs = (chunkIndex.durationsUs[index] + * (floorRegion.endOffset - chunkIndex.offsets[index])) / chunkIndex.sizes[index]; + return (int) ((chunkIndex.timesUs[index] + segmentFractionUs) / 1000); + } + + @Override + public synchronized void onSpanAdded(Cache cache, CacheSpan span) { + mergeSpan(span); + } + + @Override + public synchronized void onSpanRemoved(Cache cache, CacheSpan span) { + Region removedRegion = new Region(span.position, span.position + span.length); + + // Look up a region this span falls into. + Region floorRegion = regions.floor(removedRegion); + if (floorRegion == null) { + Log.e(TAG, "Removed a span we were not aware of"); + return; + } + + // Remove it. + regions.remove(floorRegion); + + // Add new floor and ceiling regions, if necessary. + if (floorRegion.startOffset < removedRegion.startOffset) { + Region newFloorRegion = new Region(floorRegion.startOffset, removedRegion.startOffset); + + int index = Arrays.binarySearch(chunkIndex.offsets, newFloorRegion.endOffset); + newFloorRegion.endOffsetIndex = index < 0 ? -index - 2 : index; + regions.add(newFloorRegion); + } + + if (floorRegion.endOffset > removedRegion.endOffset) { + Region newCeilingRegion = new Region(removedRegion.endOffset + 1, floorRegion.endOffset); + newCeilingRegion.endOffsetIndex = floorRegion.endOffsetIndex; + regions.add(newCeilingRegion); + } + } + + @Override + public void onSpanTouched(Cache cache, CacheSpan oldSpan, CacheSpan newSpan) { + // Do nothing. + } + + private void mergeSpan(CacheSpan span) { + Region newRegion = new Region(span.position, span.position + span.length); + Region floorRegion = regions.floor(newRegion); + Region ceilingRegion = regions.ceiling(newRegion); + boolean floorConnects = regionsConnect(floorRegion, newRegion); + boolean ceilingConnects = regionsConnect(newRegion, ceilingRegion); + + if (ceilingConnects) { + if (floorConnects) { + // Extend floorRegion to cover both newRegion and ceilingRegion. + floorRegion.endOffset = ceilingRegion.endOffset; + floorRegion.endOffsetIndex = ceilingRegion.endOffsetIndex; + } else { + // Extend newRegion to cover ceilingRegion. Add it. + newRegion.endOffset = ceilingRegion.endOffset; + newRegion.endOffsetIndex = ceilingRegion.endOffsetIndex; + regions.add(newRegion); + } + regions.remove(ceilingRegion); + } else if (floorConnects) { + // Extend floorRegion to the right to cover newRegion. + floorRegion.endOffset = newRegion.endOffset; + int index = floorRegion.endOffsetIndex; + while (index < chunkIndex.length - 1 + && (chunkIndex.offsets[index + 1] <= floorRegion.endOffset)) { + index++; + } + floorRegion.endOffsetIndex = index; + } else { + // This is a new region. + int index = Arrays.binarySearch(chunkIndex.offsets, newRegion.endOffset); + newRegion.endOffsetIndex = index < 0 ? -index - 2 : index; + regions.add(newRegion); + } + } + + private boolean regionsConnect(Region lower, Region upper) { + return lower != null && upper != null && lower.endOffset == upper.startOffset; + } + + private static class Region implements Comparable { + + /** + * The first byte of the region (inclusive). + */ + public long startOffset; + /** + * End offset of the region (exclusive). + */ + public long endOffset; + /** + * The index in chunkIndex that contains the end offset. May be -1 if the end offset comes + * before the start of the first media chunk (i.e. if the end offset is within the stream + * header). + */ + public int endOffsetIndex; + + public Region(long position, long endOffset) { + this.startOffset = position; + this.endOffset = endOffset; + } + + @Override + public int compareTo(@NonNull Region another) { + return startOffset < another.startOffset ? -1 + : startOffset == another.startOffset ? 0 : 1; + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java new file mode 100644 index 0000000..02da5a3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache.Cache.CacheException; +import java.util.Comparator; +import java.util.TreeSet; + +/** + * Evicts least recently used cache files first. + */ +public final class LeastRecentlyUsedCacheEvictor implements CacheEvictor, Comparator { + + private final long maxBytes; + private final TreeSet leastRecentlyUsed; + + private long currentSize; + + public LeastRecentlyUsedCacheEvictor(long maxBytes) { + this.maxBytes = maxBytes; + this.leastRecentlyUsed = new TreeSet<>(this); + } + + @Override + public void onCacheInitialized() { + // Do nothing. + } + + @Override + public void onStartFile(Cache cache, String key, long position, long maxLength) { + evictCache(cache, maxLength); + } + + @Override + public void onSpanAdded(Cache cache, CacheSpan span) { + leastRecentlyUsed.add(span); + currentSize += span.length; + evictCache(cache, 0); + } + + @Override + public void onSpanRemoved(Cache cache, CacheSpan span) { + leastRecentlyUsed.remove(span); + currentSize -= span.length; + } + + @Override + public void onSpanTouched(Cache cache, CacheSpan oldSpan, CacheSpan newSpan) { + onSpanRemoved(cache, oldSpan); + onSpanAdded(cache, newSpan); + } + + @Override + public int compare(CacheSpan lhs, CacheSpan rhs) { + long lastAccessTimestampDelta = lhs.lastAccessTimestamp - rhs.lastAccessTimestamp; + if (lastAccessTimestampDelta == 0) { + // Use the standard compareTo method as a tie-break. + return lhs.compareTo(rhs); + } + return lhs.lastAccessTimestamp < rhs.lastAccessTimestamp ? -1 : 1; + } + + private void evictCache(Cache cache, long requiredSpace) { + while (currentSize + requiredSpace > maxBytes) { + try { + cache.removeSpan(leastRecentlyUsed.first()); + } catch (CacheException e) { + // do nothing. + } + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/NoOpCacheEvictor.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/NoOpCacheEvictor.java new file mode 100644 index 0000000..bd11df0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/NoOpCacheEvictor.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + + +/** + * Evictor that doesn't ever evict cache files. + * + * Warning: Using this evictor might have unforeseeable consequences if cache + * size is not managed elsewhere. + */ +public final class NoOpCacheEvictor implements CacheEvictor { + + @Override + public void onCacheInitialized() { + // Do nothing. + } + + @Override + public void onStartFile(Cache cache, String key, long position, long maxLength) { + // Do nothing. + } + + @Override + public void onSpanAdded(Cache cache, CacheSpan span) { + // Do nothing. + } + + @Override + public void onSpanRemoved(Cache cache, CacheSpan span) { + // Do nothing. + } + + @Override + public void onSpanTouched(Cache cache, CacheSpan oldSpan, CacheSpan newSpan) { + // Do nothing. + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/SimpleCache.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/SimpleCache.java new file mode 100644 index 0000000..77707c3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/SimpleCache.java @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import android.os.ConditionVariable; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.NavigableSet; +import java.util.Set; +import java.util.TreeSet; + +/** + * A {@link Cache} implementation that maintains an in-memory representation. + */ +public final class SimpleCache implements Cache { + + private final File cacheDir; + private final CacheEvictor evictor; + private final HashMap lockedSpans; + private final CachedContentIndex index; + private final HashMap> listeners; + private long totalSpace = 0; + private CacheException initializationException; + + /** + * Constructs the cache. The cache will delete any unrecognized files from the directory. Hence + * the directory cannot be used to store other files. + * + * @param cacheDir A dedicated cache directory. + * @param evictor The evictor to be used. + */ + public SimpleCache(File cacheDir, CacheEvictor evictor) { + this(cacheDir, evictor, null); + } + + /** + * Constructs the cache. The cache will delete any unrecognized files from the directory. Hence + * the directory cannot be used to store other files. + * + * @param cacheDir A dedicated cache directory. + * @param evictor The evictor to be used. + * @param secretKey If not null, cache keys will be stored encrypted on filesystem using AES/CBC. + * The key must be 16 bytes long. + */ + public SimpleCache(File cacheDir, CacheEvictor evictor, byte[] secretKey) { + this.cacheDir = cacheDir; + this.evictor = evictor; + this.lockedSpans = new HashMap<>(); + this.index = new CachedContentIndex(cacheDir, secretKey); + this.listeners = new HashMap<>(); + // Start cache initialization. + final ConditionVariable conditionVariable = new ConditionVariable(); + new Thread("SimpleCache.initialize()") { + @Override + public void run() { + synchronized (SimpleCache.this) { + conditionVariable.open(); + try { + initialize(); + } catch (CacheException e) { + initializationException = e; + } + SimpleCache.this.evictor.onCacheInitialized(); + } + } + }.start(); + conditionVariable.block(); + } + + @Override + public synchronized NavigableSet addListener(String key, Listener listener) { + ArrayList listenersForKey = listeners.get(key); + if (listenersForKey == null) { + listenersForKey = new ArrayList<>(); + listeners.put(key, listenersForKey); + } + listenersForKey.add(listener); + return getCachedSpans(key); + } + + @Override + public synchronized void removeListener(String key, Listener listener) { + ArrayList listenersForKey = listeners.get(key); + if (listenersForKey != null) { + listenersForKey.remove(listener); + if (listenersForKey.isEmpty()) { + listeners.remove(key); + } + } + } + + @Override + public synchronized NavigableSet getCachedSpans(String key) { + CachedContent cachedContent = index.get(key); + return cachedContent == null ? null : new TreeSet(cachedContent.getSpans()); + } + + @Override + public synchronized Set getKeys() { + return new HashSet<>(index.getKeys()); + } + + @Override + public synchronized long getCacheSpace() { + return totalSpace; + } + + @Override + public synchronized SimpleCacheSpan startReadWrite(String key, long position) + throws InterruptedException, CacheException { + while (true) { + SimpleCacheSpan span = startReadWriteNonBlocking(key, position); + if (span != null) { + return span; + } else { + // Write case, lock not available. We'll be woken up when a locked span is released (if the + // released lock is for the requested key then we'll be able to make progress) or when a + // span is added to the cache (if the span is for the requested key and covers the requested + // position, then we'll become a read and be able to make progress). + wait(); + } + } + } + + @Override + public synchronized SimpleCacheSpan startReadWriteNonBlocking(String key, long position) + throws CacheException { + if (initializationException != null) { + throw initializationException; + } + + SimpleCacheSpan cacheSpan = getSpan(key, position); + + // Read case. + if (cacheSpan.isCached) { + // Obtain a new span with updated last access timestamp. + SimpleCacheSpan newCacheSpan = index.get(key).touch(cacheSpan); + notifySpanTouched(cacheSpan, newCacheSpan); + return newCacheSpan; + } + + // Write case, lock available. + if (!lockedSpans.containsKey(key)) { + lockedSpans.put(key, cacheSpan); + return cacheSpan; + } + + // Write case, lock not available. + return null; + } + + @Override + public synchronized File startFile(String key, long position, long maxLength) + throws CacheException { + Assertions.checkState(lockedSpans.containsKey(key)); + if (!cacheDir.exists()) { + // For some reason the cache directory doesn't exist. Make a best effort to create it. + removeStaleSpansAndCachedContents(); + cacheDir.mkdirs(); + } + evictor.onStartFile(this, key, position, maxLength); + return SimpleCacheSpan.getCacheFile(cacheDir, index.assignIdForKey(key), position, + System.currentTimeMillis()); + } + + @Override + public synchronized void commitFile(File file) throws CacheException { + SimpleCacheSpan span = SimpleCacheSpan.createCacheEntry(file, index); + Assertions.checkState(span != null); + Assertions.checkState(lockedSpans.containsKey(span.key)); + // If the file doesn't exist, don't add it to the in-memory representation. + if (!file.exists()) { + return; + } + // If the file has length 0, delete it and don't add it to the in-memory representation. + if (file.length() == 0) { + file.delete(); + return; + } + // Check if the span conflicts with the set content length + Long length = getContentLength(span.key); + if (length != C.LENGTH_UNSET) { + Assertions.checkState((span.position + span.length) <= length); + } + addSpan(span); + index.store(); + notifyAll(); + } + + @Override + public synchronized void releaseHoleSpan(CacheSpan holeSpan) { + Assertions.checkState(holeSpan == lockedSpans.remove(holeSpan.key)); + notifyAll(); + } + + /** + * Returns the cache {@link SimpleCacheSpan} corresponding to the provided lookup {@link + * SimpleCacheSpan}. + * + *

    If the lookup position is contained by an existing entry in the cache, then the returned + * {@link SimpleCacheSpan} defines the file in which the data is stored. If the lookup position is + * not contained by an existing entry, then the returned {@link SimpleCacheSpan} defines the + * maximum extents of the hole in the cache. + * + * @param key The key of the span being requested. + * @param position The position of the span being requested. + * @return The corresponding cache {@link SimpleCacheSpan}. + */ + private SimpleCacheSpan getSpan(String key, long position) throws CacheException { + CachedContent cachedContent = index.get(key); + if (cachedContent == null) { + return SimpleCacheSpan.createOpenHole(key, position); + } + while (true) { + SimpleCacheSpan span = cachedContent.getSpan(position); + if (span.isCached && !span.file.exists()) { + // The file has been deleted from under us. It's likely that other files will have been + // deleted too, so scan the whole in-memory representation. + removeStaleSpansAndCachedContents(); + continue; + } + return span; + } + } + + /** + * Ensures that the cache's in-memory representation has been initialized. + */ + private void initialize() throws CacheException { + if (!cacheDir.exists()) { + cacheDir.mkdirs(); + return; + } + + index.load(); + + File[] files = cacheDir.listFiles(); + if (files == null) { + return; + } + for (File file : files) { + if (file.getName().equals(CachedContentIndex.FILE_NAME)) { + continue; + } + SimpleCacheSpan span = file.length() > 0 + ? SimpleCacheSpan.createCacheEntry(file, index) : null; + if (span != null) { + addSpan(span); + } else { + file.delete(); + } + } + + index.removeEmpty(); + index.store(); + } + + /** + * Adds a cached span to the in-memory representation. + * + * @param span The span to be added. + */ + private void addSpan(SimpleCacheSpan span) { + index.add(span.key).addSpan(span); + totalSpace += span.length; + notifySpanAdded(span); + } + + private void removeSpan(CacheSpan span, boolean removeEmptyCachedContent) throws CacheException { + CachedContent cachedContent = index.get(span.key); + Assertions.checkState(cachedContent.removeSpan(span)); + totalSpace -= span.length; + if (removeEmptyCachedContent && cachedContent.isEmpty()) { + index.removeEmpty(cachedContent.key); + index.store(); + } + notifySpanRemoved(span); + } + + @Override + public synchronized void removeSpan(CacheSpan span) throws CacheException { + removeSpan(span, true); + } + + /** + * Scans all of the cached spans in the in-memory representation, removing any for which files + * no longer exist. + */ + private void removeStaleSpansAndCachedContents() throws CacheException { + LinkedList spansToBeRemoved = new LinkedList<>(); + for (CachedContent cachedContent : index.getAll()) { + for (CacheSpan span : cachedContent.getSpans()) { + if (!span.file.exists()) { + spansToBeRemoved.add(span); + } + } + } + for (CacheSpan span : spansToBeRemoved) { + // Remove span but not CachedContent to prevent multiple index.store() calls. + removeSpan(span, false); + } + index.removeEmpty(); + index.store(); + } + + private void notifySpanRemoved(CacheSpan span) { + ArrayList keyListeners = listeners.get(span.key); + if (keyListeners != null) { + for (int i = keyListeners.size() - 1; i >= 0; i--) { + keyListeners.get(i).onSpanRemoved(this, span); + } + } + evictor.onSpanRemoved(this, span); + } + + private void notifySpanAdded(SimpleCacheSpan span) { + ArrayList keyListeners = listeners.get(span.key); + if (keyListeners != null) { + for (int i = keyListeners.size() - 1; i >= 0; i--) { + keyListeners.get(i).onSpanAdded(this, span); + } + } + evictor.onSpanAdded(this, span); + } + + private void notifySpanTouched(SimpleCacheSpan oldSpan, CacheSpan newSpan) { + ArrayList keyListeners = listeners.get(oldSpan.key); + if (keyListeners != null) { + for (int i = keyListeners.size() - 1; i >= 0; i--) { + keyListeners.get(i).onSpanTouched(this, oldSpan, newSpan); + } + } + evictor.onSpanTouched(this, oldSpan, newSpan); + } + + @Override + public synchronized boolean isCached(String key, long position, long length) { + CachedContent cachedContent = index.get(key); + return cachedContent != null && cachedContent.getCachedBytes(position, length) >= length; + } + + @Override + public synchronized long getCachedBytes(String key, long position, long length) { + CachedContent cachedContent = index.get(key); + return cachedContent != null ? cachedContent.getCachedBytes(position, length) : -length; + } + + @Override + public synchronized void setContentLength(String key, long length) throws CacheException { + index.setContentLength(key, length); + index.store(); + } + + @Override + public synchronized long getContentLength(String key) { + return index.getContentLength(key); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/SimpleCacheSpan.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/SimpleCacheSpan.java new file mode 100644 index 0000000..f3cf021 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/cache/SimpleCacheSpan.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.cache; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class stores span metadata in filename. + */ +/*package*/ final class SimpleCacheSpan extends CacheSpan { + + private static final String SUFFIX = ".v3.exo"; + private static final Pattern CACHE_FILE_PATTERN_V1 = Pattern.compile( + "^(.+)\\.(\\d+)\\.(\\d+)\\.v1\\.exo$", Pattern.DOTALL); + private static final Pattern CACHE_FILE_PATTERN_V2 = Pattern.compile( + "^(.+)\\.(\\d+)\\.(\\d+)\\.v2\\.exo$", Pattern.DOTALL); + private static final Pattern CACHE_FILE_PATTERN_V3 = Pattern.compile( + "^(\\d+)\\.(\\d+)\\.(\\d+)\\.v3\\.exo$", Pattern.DOTALL); + + public static File getCacheFile(File cacheDir, int id, long position, + long lastAccessTimestamp) { + return new File(cacheDir, id + "." + position + "." + lastAccessTimestamp + SUFFIX); + } + + public static SimpleCacheSpan createLookup(String key, long position) { + return new SimpleCacheSpan(key, position, C.LENGTH_UNSET, C.TIME_UNSET, null); + } + + public static SimpleCacheSpan createOpenHole(String key, long position) { + return new SimpleCacheSpan(key, position, C.LENGTH_UNSET, C.TIME_UNSET, null); + } + + public static SimpleCacheSpan createClosedHole(String key, long position, long length) { + return new SimpleCacheSpan(key, position, length, C.TIME_UNSET, null); + } + + /** + * Creates a cache span from an underlying cache file. Upgrades the file if necessary. + * + * @param file The cache file. + * @param index Cached content index. + * @return The span, or null if the file name is not correctly formatted, or if the id is not + * present in the content index. + */ + public static SimpleCacheSpan createCacheEntry(File file, CachedContentIndex index) { + String name = file.getName(); + if (!name.endsWith(SUFFIX)) { + file = upgradeFile(file, index); + if (file == null) { + return null; + } + name = file.getName(); + } + + Matcher matcher = CACHE_FILE_PATTERN_V3.matcher(name); + if (!matcher.matches()) { + return null; + } + long length = file.length(); + int id = Integer.parseInt(matcher.group(1)); + String key = index.getKeyForId(id); + return key == null ? null : new SimpleCacheSpan(key, Long.parseLong(matcher.group(2)), length, + Long.parseLong(matcher.group(3)), file); + } + + private static File upgradeFile(File file, CachedContentIndex index) { + String key; + String filename = file.getName(); + Matcher matcher = CACHE_FILE_PATTERN_V2.matcher(filename); + if (matcher.matches()) { + key = Util.unescapeFileName(matcher.group(1)); + if (key == null) { + return null; + } + } else { + matcher = CACHE_FILE_PATTERN_V1.matcher(filename); + if (!matcher.matches()) { + return null; + } + key = matcher.group(1); // Keys were not escaped in version 1. + } + + File newCacheFile = getCacheFile(file.getParentFile(), index.assignIdForKey(key), + Long.parseLong(matcher.group(2)), Long.parseLong(matcher.group(3))); + if (!file.renameTo(newCacheFile)) { + return null; + } + return newCacheFile; + } + + private SimpleCacheSpan(String key, long position, long length, long lastAccessTimestamp, + File file) { + super(key, position, length, lastAccessTimestamp, file); + } + + /** + * Returns a copy of this CacheSpan whose last access time stamp is set to current time. This + * doesn't copy or change the underlying cache file. + * + * @param id The cache file id. + * @return A {@link SimpleCacheSpan} with updated last access time stamp. + * @throws IllegalStateException If called on a non-cached span (i.e. {@link #isCached} is false). + */ + public SimpleCacheSpan copyWithUpdatedLastAccessTime(int id) { + Assertions.checkState(isCached); + long now = System.currentTimeMillis(); + File newCacheFile = getCacheFile(file.getParentFile(), id, position, now); + return new SimpleCacheSpan(key, position, length, now, newCacheFile); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesCipherDataSink.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesCipherDataSink.java new file mode 100644 index 0000000..8a8f863 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesCipherDataSink.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.crypto; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSink; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import java.io.IOException; +import javax.crypto.Cipher; + +/** + * A wrapping {@link DataSink} that encrypts the data being consumed. + */ +public final class AesCipherDataSink implements DataSink { + + private final DataSink wrappedDataSink; + private final byte[] secretKey; + private final byte[] scratch; + + private AesFlushingCipher cipher; + + /** + * Create an instance whose {@code write} methods have the side effect of overwriting the input + * {@code data}. Use this constructor for maximum efficiency in the case that there is no + * requirement for the input data arrays to remain unchanged. + * + * @param secretKey The key data. + * @param wrappedDataSink The wrapped {@link DataSink}. + */ + public AesCipherDataSink(byte[] secretKey, DataSink wrappedDataSink) { + this(secretKey, wrappedDataSink, null); + } + + /** + * Create an instance whose {@code write} methods are free of side effects. Use this constructor + * when the input data arrays are required to remain unchanged. + * + * @param secretKey The key data. + * @param wrappedDataSink The wrapped {@link DataSink}. + * @param scratch Scratch space. Data is decrypted into this array before being written to the + * wrapped {@link DataSink}. It should be of appropriate size for the expected writes. If a + * write is larger than the size of this array the write will still succeed, but multiple + * cipher calls will be required to complete the operation. + */ + public AesCipherDataSink(byte[] secretKey, DataSink wrappedDataSink, byte[] scratch) { + this.wrappedDataSink = wrappedDataSink; + this.secretKey = secretKey; + this.scratch = scratch; + } + + @Override + public void open(DataSpec dataSpec) throws IOException { + wrappedDataSink.open(dataSpec); + long nonce = CryptoUtil.getFNV64Hash(dataSpec.key); + cipher = new AesFlushingCipher(Cipher.ENCRYPT_MODE, secretKey, nonce, + dataSpec.absoluteStreamPosition); + } + + @Override + public void write(byte[] data, int offset, int length) throws IOException { + if (scratch == null) { + // In-place mode. Writes over the input data. + cipher.updateInPlace(data, offset, length); + wrappedDataSink.write(data, offset, length); + } else { + // Use scratch space. The original data remains intact. + int bytesProcessed = 0; + while (bytesProcessed < length) { + int bytesToProcess = Math.min(length - bytesProcessed, scratch.length); + cipher.update(data, offset + bytesProcessed, bytesToProcess, scratch, 0); + wrappedDataSink.write(scratch, 0, bytesToProcess); + bytesProcessed += bytesToProcess; + } + } + } + + @Override + public void close() throws IOException { + cipher = null; + wrappedDataSink.close(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesCipherDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesCipherDataSource.java new file mode 100644 index 0000000..a5f4247 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesCipherDataSource.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.crypto; + +import android.net.Uri; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import java.io.IOException; +import javax.crypto.Cipher; + +/** + * A {@link DataSource} that decrypts the data read from an upstream source. + */ +public final class AesCipherDataSource implements DataSource { + + private final DataSource upstream; + private final byte[] secretKey; + + private AesFlushingCipher cipher; + + public AesCipherDataSource(byte[] secretKey, DataSource upstream) { + this.upstream = upstream; + this.secretKey = secretKey; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + long dataLength = upstream.open(dataSpec); + long nonce = CryptoUtil.getFNV64Hash(dataSpec.key); + cipher = new AesFlushingCipher(Cipher.DECRYPT_MODE, secretKey, nonce, + dataSpec.absoluteStreamPosition); + return dataLength; + } + + @Override + public int read(byte[] data, int offset, int readLength) throws IOException { + if (readLength == 0) { + return 0; + } + int read = upstream.read(data, offset, readLength); + if (read == C.RESULT_END_OF_INPUT) { + return C.RESULT_END_OF_INPUT; + } + cipher.updateInPlace(data, offset, read); + return read; + } + + @Override + public void close() throws IOException { + cipher = null; + upstream.close(); + } + + @Override + public Uri getUri() { + return upstream.getUri(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesFlushingCipher.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesFlushingCipher.java new file mode 100644 index 0000000..fbb1f9e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/AesFlushingCipher.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.crypto; + +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * A flushing variant of a AES/CTR/NoPadding {@link Cipher}. + * + * Unlike a regular {@link Cipher}, the update methods of this class are guaranteed to process all + * of the bytes input (and hence output the same number of bytes). + */ +public final class AesFlushingCipher { + + private final Cipher cipher; + private final int blockSize; + private final byte[] zerosBlock; + private final byte[] flushedBlock; + + private int pendingXorBytes; + + public AesFlushingCipher(int mode, byte[] secretKey, long nonce, long offset) { + try { + cipher = Cipher.getInstance("AES/CTR/NoPadding"); + blockSize = cipher.getBlockSize(); + zerosBlock = new byte[blockSize]; + flushedBlock = new byte[blockSize]; + long counter = offset / blockSize; + int startPadding = (int) (offset % blockSize); + cipher.init(mode, new SecretKeySpec(secretKey, cipher.getAlgorithm().split("/")[0]), + new IvParameterSpec(getInitializationVector(nonce, counter))); + if (startPadding != 0) { + updateInPlace(new byte[startPadding], 0, startPadding); + } + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException e) { + // Should never happen. + throw new RuntimeException(e); + } + } + + public void updateInPlace(byte[] data, int offset, int length) { + update(data, offset, length, data, offset); + } + + public void update(byte[] in, int inOffset, int length, byte[] out, int outOffset) { + // If we previously flushed the cipher by inputting zeros up to a block boundary, then we need + // to manually transform the data that actually ended the block. See the comment below for more + // details. + while (pendingXorBytes > 0) { + out[outOffset] = (byte) (in[inOffset] ^ flushedBlock[blockSize - pendingXorBytes]); + outOffset++; + inOffset++; + pendingXorBytes--; + length--; + if (length == 0) { + return; + } + } + + // Do the bulk of the update. + int written = nonFlushingUpdate(in, inOffset, length, out, outOffset); + if (length == written) { + return; + } + + // We need to finish the block to flush out the remaining bytes. We do so by inputting zeros, + // so that the corresponding bytes output by the cipher are those that would have been XORed + // against the real end-of-block data to transform it. We store these bytes so that we can + // perform the transformation manually in the case of a subsequent call to this method with + // the real data. + int bytesToFlush = length - written; + Assertions.checkState(bytesToFlush < blockSize); + outOffset += written; + pendingXorBytes = blockSize - bytesToFlush; + written = nonFlushingUpdate(zerosBlock, 0, pendingXorBytes, flushedBlock, 0); + Assertions.checkState(written == blockSize); + // The first part of xorBytes contains the flushed data, which we copy out. The remainder + // contains the bytes that will be needed for manual transformation in a subsequent call. + for (int i = 0; i < bytesToFlush; i++) { + out[outOffset++] = flushedBlock[i]; + } + } + + private int nonFlushingUpdate(byte[] in, int inOffset, int length, byte[] out, int outOffset) { + try { + return cipher.update(in, inOffset, length, out, outOffset); + } catch (ShortBufferException e) { + // Should never happen. + throw new RuntimeException(e); + } + } + + private byte[] getInitializationVector(long nonce, long counter) { + return ByteBuffer.allocate(16).putLong(nonce).putLong(counter).array(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/CryptoUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/CryptoUtil.java new file mode 100644 index 0000000..94846e3 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/upstream/crypto/CryptoUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.upstream.crypto; + +/** + * Utility functions for the crypto package. + */ +/* package */ final class CryptoUtil { + + private CryptoUtil() {} + + /** + * Returns the hash value of the input as a long using the 64 bit FNV-1a hash function. The hash + * values produced by this function are less likely to collide than those produced by + * {@link #hashCode()}. + */ + public static long getFNV64Hash(String input) { + if (input == null) { + return 0; + } + + long hash = 0; + for (int i = 0; i < input.length(); i++) { + hash ^= input.charAt(i); + // This is equivalent to hash *= 0x100000001b3 (the FNV magic prime number). + hash += (hash << 1) + (hash << 4) + (hash << 5) + (hash << 7) + (hash << 8) + (hash << 40); + } + return hash; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Assertions.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Assertions.java new file mode 100644 index 0000000..2170220 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Assertions.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.os.Looper; +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayerLibraryInfo; + +/** + * Provides methods for asserting the truth of expressions and properties. + */ +public final class Assertions { + + private Assertions() {} + + /** + * Throws {@link IllegalArgumentException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. + * @throws IllegalArgumentException If {@code expression} is false. + */ + public static void checkArgument(boolean expression) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Throws {@link IllegalArgumentException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. + * @param errorMessage The exception message if an exception is thrown. The message is converted + * to a {@link String} using {@link String#valueOf(Object)}. + * @throws IllegalArgumentException If {@code expression} is false. + */ + public static void checkArgument(boolean expression, Object errorMessage) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Throws {@link IndexOutOfBoundsException} if {@code index} falls outside the specified bounds. + * + * @param index The index to test. + * @param start The start of the allowed range (inclusive). + * @param limit The end of the allowed range (exclusive). + * @return The {@code index} that was validated. + * @throws IndexOutOfBoundsException If {@code index} falls outside the specified bounds. + */ + public static int checkIndex(int index, int start, int limit) { + if (index < start || index >= limit) { + throw new IndexOutOfBoundsException(); + } + return index; + } + + /** + * Throws {@link IllegalStateException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. + * @throws IllegalStateException If {@code expression} is false. + */ + public static void checkState(boolean expression) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { + throw new IllegalStateException(); + } + } + + /** + * Throws {@link IllegalStateException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. + * @param errorMessage The exception message if an exception is thrown. The message is converted + * to a {@link String} using {@link String#valueOf(Object)}. + * @throws IllegalStateException If {@code expression} is false. + */ + public static void checkState(boolean expression, Object errorMessage) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Throws {@link NullPointerException} if {@code reference} is null. + * + * @param The type of the reference. + * @param reference The reference. + * @return The non-null reference that was validated. + * @throws NullPointerException If {@code reference} is null. + */ + public static T checkNotNull(T reference) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Throws {@link NullPointerException} if {@code reference} is null. + * + * @param The type of the reference. + * @param reference The reference. + * @param errorMessage The exception message to use if the check fails. The message is converted + * to a string using {@link String#valueOf(Object)}. + * @return The non-null reference that was validated. + * @throws NullPointerException If {@code reference} is null. + */ + public static T checkNotNull(T reference, Object errorMessage) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** + * Throws {@link IllegalArgumentException} if {@code string} is null or zero length. + * + * @param string The string to check. + * @return The non-null, non-empty string that was validated. + * @throws IllegalArgumentException If {@code string} is null or 0-length. + */ + public static String checkNotEmpty(String string) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && TextUtils.isEmpty(string)) { + throw new IllegalArgumentException(); + } + return string; + } + + /** + * Throws {@link IllegalArgumentException} if {@code string} is null or zero length. + * + * @param string The string to check. + * @param errorMessage The exception message to use if the check fails. The message is converted + * to a string using {@link String#valueOf(Object)}. + * @return The non-null, non-empty string that was validated. + * @throws IllegalArgumentException If {@code string} is null or 0-length. + */ + public static String checkNotEmpty(String string, Object errorMessage) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && TextUtils.isEmpty(string)) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + return string; + } + + /** + * Throws {@link IllegalStateException} if the calling thread is not the application's main + * thread. + * + * @throws IllegalStateException If the calling thread is not the application's main thread. + */ + public static void checkMainThread() { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && Looper.myLooper() != Looper.getMainLooper()) { + throw new IllegalStateException("Not in applications main thread"); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/AtomicFile.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/AtomicFile.java new file mode 100644 index 0000000..a2515e4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/AtomicFile.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.support.annotation.NonNull; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A helper class for performing atomic operations on a file by creating a backup file until a write + * has successfully completed. + * + *

    Atomic file guarantees file integrity by ensuring that a file has been completely written and + * sync'd to disk before removing its backup. As long as the backup file exists, the original file + * is considered to be invalid (left over from a previous attempt to write the file). + * + *

    Atomic file does not confer any file locking semantics. Do not use this class when the file + * may be accessed or modified concurrently by multiple threads or processes. The caller is + * responsible for ensuring appropriate mutual exclusion invariants whenever it accesses the file. + */ +public final class AtomicFile { + + private static final String TAG = "AtomicFile"; + + private final File baseName; + private final File backupName; + + /** + * Create a new AtomicFile for a file located at the given File path. The secondary backup file + * will be the same file path with ".bak" appended. + */ + public AtomicFile(File baseName) { + this.baseName = baseName; + backupName = new File(baseName.getPath() + ".bak"); + } + + /** Delete the atomic file. This deletes both the base and backup files. */ + public void delete() { + baseName.delete(); + backupName.delete(); + } + + /** + * Start a new write operation on the file. This returns an {@link OutputStream} to which you can + * write the new file data. If the whole data is written successfully you must call + * {@link #endWrite(OutputStream)}. On failure you should call {@link OutputStream#close()} + * only to free up resources used by it. + * + *

    Example usage: + * + *

    +   *   DataOutputStream dataOutput = null;
    +   *   try {
    +   *     OutputStream outputStream = atomicFile.startWrite();
    +   *     dataOutput = new DataOutputStream(outputStream); // Wrapper stream
    +   *     dataOutput.write(data1);
    +   *     dataOutput.write(data2);
    +   *     atomicFile.endWrite(dataOutput); // Pass wrapper stream
    +   *   } finally{
    +   *     if (dataOutput != null) {
    +   *       dataOutput.close();
    +   *     }
    +   *   }
    +   * 
    + * + *

    Note that if another thread is currently performing a write, this will simply replace + * whatever that thread is writing with the new file being written by this thread, and when the + * other thread finishes the write the new write operation will no longer be safe (or will be + * lost). You must do your own threading protection for access to AtomicFile. + */ + public OutputStream startWrite() throws IOException { + // Rename the current file so it may be used as a backup during the next read + if (baseName.exists()) { + if (backupName.exists()) { + baseName.delete(); + } + } + OutputStream str; + try { + str = new AtomicFileOutputStream(baseName); + } catch (FileNotFoundException e) { + File parent = baseName.getParentFile(); + if (!parent.mkdirs()) { + throw new IOException("Couldn't create directory " + baseName); + } + try { + str = new AtomicFileOutputStream(baseName); + } catch (FileNotFoundException e2) { + throw new IOException("Couldn't create " + baseName); + } + } + return str; + } + + /** + * Call when you have successfully finished writing to the stream returned by {@link + * #startWrite()}. This will close, sync, and commit the new data. The next attempt to read the + * atomic file will return the new file stream. + * + * @param str Outer-most wrapper OutputStream used to write to the stream returned by {@link + * #startWrite()}. + * @see #startWrite() + */ + public void endWrite(OutputStream str) throws IOException { + str.close(); + // If close() throws exception, the next line is skipped. + backupName.delete(); + } + + /** + * Open the atomic file for reading. If there previously was an incomplete write, this will roll + * back to the last good data before opening for read. + * + *

    Note that if another thread is currently performing a write, this will incorrectly consider + * it to be in the state of a bad write and roll back, causing the new data currently being + * written to be dropped. You must do your own threading protection for access to AtomicFile. + */ + public InputStream openRead() throws FileNotFoundException { + restoreBackup(); + return new FileInputStream(baseName); + } + + private void restoreBackup() { + if (backupName.exists()) { + baseName.delete(); + backupName.renameTo(baseName); + } + } + + private static final class AtomicFileOutputStream extends OutputStream { + + private final FileOutputStream fileOutputStream; + private boolean closed = false; + + public AtomicFileOutputStream(File file) throws FileNotFoundException { + fileOutputStream = new FileOutputStream(file); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + flush(); + try { + fileOutputStream.getFD().sync(); + } catch (IOException e) { + e.getStackTrace(); + } + fileOutputStream.close(); + } + + @Override + public void flush() throws IOException { + fileOutputStream.flush(); + } + + @Override + public void write(int b) throws IOException { + fileOutputStream.write(b); + } + + @Override + public void write(@NonNull byte[] b) throws IOException { + fileOutputStream.write(b); + } + + @Override + public void write(@NonNull byte[] b, int off, int len) throws IOException { + fileOutputStream.write(b, off, len); + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Clock.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Clock.java new file mode 100644 index 0000000..18087fa --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Clock.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +/** + * An interface through which system clocks can be read. The {@link SystemClock} implementation + * must be used for all non-test cases. + */ +public interface Clock { + + /** + * Returns {@link android.os.SystemClock#elapsedRealtime}. + * + * @return Elapsed milliseconds since boot. + */ + long elapsedRealtime(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/CodecSpecificDataUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/CodecSpecificDataUtil.java new file mode 100644 index 0000000..4f5e0e4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/CodecSpecificDataUtil.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.util.Pair; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import java.util.ArrayList; +import java.util.List; + +/** + * Provides static utility methods for manipulating various types of codec specific data. + */ +public final class CodecSpecificDataUtil { + + private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; + + private static final int AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY = 0xF; + + private static final int[] AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE = new int[] { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 + }; + + private static final int AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID = -1; + /** + * In the channel configurations below, indicates a single channel element; (A, B) indicates a + * channel pair element; and [A] indicates a low-frequency effects element. + * The speaker mapping short forms used are: + * - FC: front center + * - BC: back center + * - FL/FR: front left/right + * - FCL/FCR: front center left/right + * - FTL/FTR: front top left/right + * - SL/SR: back surround left/right + * - BL/BR: back left/right + * - LFE: low frequency effects + */ + private static final int[] AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE = + new int[] { + 0, + 1, /* mono: */ + 2, /* stereo: (FL, FR) */ + 3, /* 3.0: , (FL, FR) */ + 4, /* 4.0: , (FL, FR), */ + 5, /* 5.0 back: , (FL, FR), (SL, SR) */ + 6, /* 5.1 back: , (FL, FR), (SL, SR), , [LFE] */ + 8, /* 7.1 wide back: , (FCL, FCR), (FL, FR), (SL, SR), [LFE] */ + AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID, + AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID, + AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID, + 7, /* 6.1: , (FL, FR), (SL, SR), , [LFE] */ + 8, /* 7.1: , (FL, FR), (SL, SR), (BL, BR), [LFE] */ + AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID, + 8, /* 7.1 top: , (FL, FR), (SL, SR), [LFE], (FTL, FTR) */ + AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID + }; + + // Advanced Audio Coding Low-Complexity profile. + private static final int AUDIO_OBJECT_TYPE_AAC_LC = 2; + // Spectral Band Replication. + private static final int AUDIO_OBJECT_TYPE_SBR = 5; + // Error Resilient Bit-Sliced Arithmetic Coding. + private static final int AUDIO_OBJECT_TYPE_ER_BSAC = 22; + // Parametric Stereo. + private static final int AUDIO_OBJECT_TYPE_PS = 29; + // Escape code for extended audio object types. + private static final int AUDIO_OBJECT_TYPE_ESCAPE = 31; + + private CodecSpecificDataUtil() {} + + /** + * Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 + * + * @param audioSpecificConfig The AudioSpecificConfig to parse. + * @return A pair consisting of the sample rate in Hz and the channel count. + */ + public static Pair parseAacAudioSpecificConfig(byte[] audioSpecificConfig) { + ParsableBitArray bitArray = new ParsableBitArray(audioSpecificConfig); + int audioObjectType = getAacAudioObjectType(bitArray); + int sampleRate = getAacSamplingFrequency(bitArray); + int channelConfiguration = bitArray.readBits(4); + if (audioObjectType == AUDIO_OBJECT_TYPE_SBR || audioObjectType == AUDIO_OBJECT_TYPE_PS) { + // For an AAC bitstream using spectral band replication (SBR) or parametric stereo (PS) with + // explicit signaling, we return the extension sampling frequency as the sample rate of the + // content; this is identical to the sample rate of the decoded output but may differ from + // the sample rate set above. + // Use the extensionSamplingFrequencyIndex. + sampleRate = getAacSamplingFrequency(bitArray); + audioObjectType = getAacAudioObjectType(bitArray); + if (audioObjectType == AUDIO_OBJECT_TYPE_ER_BSAC) { + // Use the extensionChannelConfiguration. + channelConfiguration = bitArray.readBits(4); + } + } + int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[channelConfiguration]; + Assertions.checkArgument(channelCount != AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID); + return Pair.create(sampleRate, channelCount); + } + + /** + * Builds a simple HE-AAC LC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 + * + * @param sampleRate The sample rate in Hz. + * @param numChannels The number of channels. + * @return The AudioSpecificConfig. + */ + public static byte[] buildAacLcAudioSpecificConfig(int sampleRate, int numChannels) { + int sampleRateIndex = C.INDEX_UNSET; + for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE.length; ++i) { + if (sampleRate == AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[i]) { + sampleRateIndex = i; + } + } + int channelConfig = C.INDEX_UNSET; + for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE.length; ++i) { + if (numChannels == AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[i]) { + channelConfig = i; + } + } + if (sampleRate == C.INDEX_UNSET || channelConfig == C.INDEX_UNSET) { + throw new IllegalArgumentException("Invalid sample rate or number of channels: " + + sampleRate + ", " + numChannels); + } + return buildAacAudioSpecificConfig(AUDIO_OBJECT_TYPE_AAC_LC, sampleRateIndex, channelConfig); + } + + /** + * Builds a simple AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 + * + * @param audioObjectType The audio object type. + * @param sampleRateIndex The sample rate index. + * @param channelConfig The channel configuration. + * @return The AudioSpecificConfig. + */ + public static byte[] buildAacAudioSpecificConfig(int audioObjectType, int sampleRateIndex, + int channelConfig) { + byte[] specificConfig = new byte[2]; + specificConfig[0] = (byte) (((audioObjectType << 3) & 0xF8) | ((sampleRateIndex >> 1) & 0x07)); + specificConfig[1] = (byte) (((sampleRateIndex << 7) & 0x80) | ((channelConfig << 3) & 0x78)); + return specificConfig; + } + + /** + * Constructs a NAL unit consisting of the NAL start code followed by the specified data. + * + * @param data An array containing the data that should follow the NAL start code. + * @param offset The start offset into {@code data}. + * @param length The number of bytes to copy from {@code data} + * @return The constructed NAL unit. + */ + public static byte[] buildNalUnit(byte[] data, int offset, int length) { + byte[] nalUnit = new byte[length + NAL_START_CODE.length]; + System.arraycopy(NAL_START_CODE, 0, nalUnit, 0, NAL_START_CODE.length); + System.arraycopy(data, offset, nalUnit, NAL_START_CODE.length, length); + return nalUnit; + } + + /** + * Splits an array of NAL units. + *

    + * If the input consists of NAL start code delimited units, then the returned array consists of + * the split NAL units, each of which is still prefixed with the NAL start code. For any other + * input, null is returned. + * + * @param data An array of data. + * @return The individual NAL units, or null if the input did not consist of NAL start code + * delimited units. + */ + public static byte[][] splitNalUnits(byte[] data) { + if (!isNalStartCode(data, 0)) { + // data does not consist of NAL start code delimited units. + return null; + } + List starts = new ArrayList<>(); + int nalUnitIndex = 0; + do { + starts.add(nalUnitIndex); + nalUnitIndex = findNalStartCode(data, nalUnitIndex + NAL_START_CODE.length); + } while (nalUnitIndex != C.INDEX_UNSET); + byte[][] split = new byte[starts.size()][]; + for (int i = 0; i < starts.size(); i++) { + int startIndex = starts.get(i); + int endIndex = i < starts.size() - 1 ? starts.get(i + 1) : data.length; + byte[] nal = new byte[endIndex - startIndex]; + System.arraycopy(data, startIndex, nal, 0, nal.length); + split[i] = nal; + } + return split; + } + + /** + * Finds the next occurrence of the NAL start code from a given index. + * + * @param data The data in which to search. + * @param index The first index to test. + * @return The index of the first byte of the found start code, or {@link C#INDEX_UNSET}. + */ + private static int findNalStartCode(byte[] data, int index) { + int endIndex = data.length - NAL_START_CODE.length; + for (int i = index; i <= endIndex; i++) { + if (isNalStartCode(data, i)) { + return i; + } + } + return C.INDEX_UNSET; + } + + /** + * Tests whether there exists a NAL start code at a given index. + * + * @param data The data. + * @param index The index to test. + * @return Whether there exists a start code that begins at {@code index}. + */ + private static boolean isNalStartCode(byte[] data, int index) { + if (data.length - index <= NAL_START_CODE.length) { + return false; + } + for (int j = 0; j < NAL_START_CODE.length; j++) { + if (data[index + j] != NAL_START_CODE[j]) { + return false; + } + } + return true; + } + + /** + * Returns the AAC audio object type as specified in 14496-3 (2005) Table 1.14. + * + * @param bitArray The bit array containing the audio specific configuration. + * @return The audio object type. + */ + private static int getAacAudioObjectType(ParsableBitArray bitArray) { + int audioObjectType = bitArray.readBits(5); + if (audioObjectType == AUDIO_OBJECT_TYPE_ESCAPE) { + audioObjectType = 32 + bitArray.readBits(6); + } + return audioObjectType; + } + + /** + * Returns the AAC sampling frequency (or extension sampling frequency) as specified in 14496-3 + * (2005) Table 1.13. + * + * @param bitArray The bit array containing the audio specific configuration. + * @return The sampling frequency. + */ + private static int getAacSamplingFrequency(ParsableBitArray bitArray) { + int samplingFrequency; + int frequencyIndex = bitArray.readBits(4); + if (frequencyIndex == AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY) { + samplingFrequency = bitArray.readBits(24); + } else { + Assertions.checkArgument(frequencyIndex < 13); + samplingFrequency = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; + } + return samplingFrequency; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ColorParser.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ColorParser.java new file mode 100644 index 0000000..3d4bea9 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ColorParser.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.text.TextUtils; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parser for color expressions found in styling formats, e.g. TTML and CSS. + * + * @see WebVTT CSS Styling + * @see Timed Text Markup Language 2 (TTML2) - 10.3.5 + **/ +public final class ColorParser { + + private static final String RGB = "rgb"; + private static final String RGBA = "rgba"; + + private static final Pattern RGB_PATTERN = Pattern.compile( + "^rgb\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$"); + + private static final Pattern RGBA_PATTERN_INT_ALPHA = Pattern.compile( + "^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$"); + + private static final Pattern RGBA_PATTERN_FLOAT_ALPHA = Pattern.compile( + "^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d*\\.?\\d*?)\\)$"); + + private static final Map COLOR_MAP; + + /** + * Parses a TTML color expression. + * + * @param colorExpression The color expression. + * @return The parsed ARGB color. + */ + public static int parseTtmlColor(String colorExpression) { + return parseColorInternal(colorExpression, false); + } + + /** + * Parses a CSS color expression. + * + * @param colorExpression The color expression. + * @return The parsed ARGB color. + */ + public static int parseCssColor(String colorExpression) { + return parseColorInternal(colorExpression, true); + } + + private static int parseColorInternal(String colorExpression, boolean alphaHasFloatFormat) { + Assertions.checkArgument(!TextUtils.isEmpty(colorExpression)); + colorExpression = colorExpression.replace(" ", ""); + if (colorExpression.charAt(0) == '#') { + // Parse using Long to avoid failure when colorExpression is greater than #7FFFFFFF. + int color = (int) Long.parseLong(colorExpression.substring(1), 16); + if (colorExpression.length() == 7) { + // Set the alpha value + color |= 0xFF000000; + } else if (colorExpression.length() == 9) { + // We have #RRGGBBAA, but we need #AARRGGBB + color = ((color & 0xFF) << 24) | (color >>> 8); + } else { + throw new IllegalArgumentException(); + } + return color; + } else if (colorExpression.startsWith(RGBA)) { + Matcher matcher = (alphaHasFloatFormat ? RGBA_PATTERN_FLOAT_ALPHA : RGBA_PATTERN_INT_ALPHA) + .matcher(colorExpression); + if (matcher.matches()) { + return argb( + alphaHasFloatFormat ? (int) (255 * Float.parseFloat(matcher.group(4))) + : Integer.parseInt(matcher.group(4), 10), + Integer.parseInt(matcher.group(1), 10), + Integer.parseInt(matcher.group(2), 10), + Integer.parseInt(matcher.group(3), 10) + ); + } + } else if (colorExpression.startsWith(RGB)) { + Matcher matcher = RGB_PATTERN.matcher(colorExpression); + if (matcher.matches()) { + return rgb( + Integer.parseInt(matcher.group(1), 10), + Integer.parseInt(matcher.group(2), 10), + Integer.parseInt(matcher.group(3), 10) + ); + } + } else { + // we use our own color map + Integer color = COLOR_MAP.get(Util.toLowerInvariant(colorExpression)); + if (color != null) { + return color; + } + } + throw new IllegalArgumentException(); + } + + private static int argb(int alpha, int red, int green, int blue) { + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + private static int rgb(int red, int green, int blue) { + return argb(0xFF, red, green, blue); + } + + static { + COLOR_MAP = new HashMap<>(); + COLOR_MAP.put("aliceblue", 0xFFF0F8FF); + COLOR_MAP.put("antiquewhite", 0xFFFAEBD7); + COLOR_MAP.put("aqua", 0xFF00FFFF); + COLOR_MAP.put("aquamarine", 0xFF7FFFD4); + COLOR_MAP.put("azure", 0xFFF0FFFF); + COLOR_MAP.put("beige", 0xFFF5F5DC); + COLOR_MAP.put("bisque", 0xFFFFE4C4); + COLOR_MAP.put("black", 0xFF000000); + COLOR_MAP.put("blanchedalmond", 0xFFFFEBCD); + COLOR_MAP.put("blue", 0xFF0000FF); + COLOR_MAP.put("blueviolet", 0xFF8A2BE2); + COLOR_MAP.put("brown", 0xFFA52A2A); + COLOR_MAP.put("burlywood", 0xFFDEB887); + COLOR_MAP.put("cadetblue", 0xFF5F9EA0); + COLOR_MAP.put("chartreuse", 0xFF7FFF00); + COLOR_MAP.put("chocolate", 0xFFD2691E); + COLOR_MAP.put("coral", 0xFFFF7F50); + COLOR_MAP.put("cornflowerblue", 0xFF6495ED); + COLOR_MAP.put("cornsilk", 0xFFFFF8DC); + COLOR_MAP.put("crimson", 0xFFDC143C); + COLOR_MAP.put("cyan", 0xFF00FFFF); + COLOR_MAP.put("darkblue", 0xFF00008B); + COLOR_MAP.put("darkcyan", 0xFF008B8B); + COLOR_MAP.put("darkgoldenrod", 0xFFB8860B); + COLOR_MAP.put("darkgray", 0xFFA9A9A9); + COLOR_MAP.put("darkgreen", 0xFF006400); + COLOR_MAP.put("darkgrey", 0xFFA9A9A9); + COLOR_MAP.put("darkkhaki", 0xFFBDB76B); + COLOR_MAP.put("darkmagenta", 0xFF8B008B); + COLOR_MAP.put("darkolivegreen", 0xFF556B2F); + COLOR_MAP.put("darkorange", 0xFFFF8C00); + COLOR_MAP.put("darkorchid", 0xFF9932CC); + COLOR_MAP.put("darkred", 0xFF8B0000); + COLOR_MAP.put("darksalmon", 0xFFE9967A); + COLOR_MAP.put("darkseagreen", 0xFF8FBC8F); + COLOR_MAP.put("darkslateblue", 0xFF483D8B); + COLOR_MAP.put("darkslategray", 0xFF2F4F4F); + COLOR_MAP.put("darkslategrey", 0xFF2F4F4F); + COLOR_MAP.put("darkturquoise", 0xFF00CED1); + COLOR_MAP.put("darkviolet", 0xFF9400D3); + COLOR_MAP.put("deeppink", 0xFFFF1493); + COLOR_MAP.put("deepskyblue", 0xFF00BFFF); + COLOR_MAP.put("dimgray", 0xFF696969); + COLOR_MAP.put("dimgrey", 0xFF696969); + COLOR_MAP.put("dodgerblue", 0xFF1E90FF); + COLOR_MAP.put("firebrick", 0xFFB22222); + COLOR_MAP.put("floralwhite", 0xFFFFFAF0); + COLOR_MAP.put("forestgreen", 0xFF228B22); + COLOR_MAP.put("fuchsia", 0xFFFF00FF); + COLOR_MAP.put("gainsboro", 0xFFDCDCDC); + COLOR_MAP.put("ghostwhite", 0xFFF8F8FF); + COLOR_MAP.put("gold", 0xFFFFD700); + COLOR_MAP.put("goldenrod", 0xFFDAA520); + COLOR_MAP.put("gray", 0xFF808080); + COLOR_MAP.put("green", 0xFF008000); + COLOR_MAP.put("greenyellow", 0xFFADFF2F); + COLOR_MAP.put("grey", 0xFF808080); + COLOR_MAP.put("honeydew", 0xFFF0FFF0); + COLOR_MAP.put("hotpink", 0xFFFF69B4); + COLOR_MAP.put("indianred", 0xFFCD5C5C); + COLOR_MAP.put("indigo", 0xFF4B0082); + COLOR_MAP.put("ivory", 0xFFFFFFF0); + COLOR_MAP.put("khaki", 0xFFF0E68C); + COLOR_MAP.put("lavender", 0xFFE6E6FA); + COLOR_MAP.put("lavenderblush", 0xFFFFF0F5); + COLOR_MAP.put("lawngreen", 0xFF7CFC00); + COLOR_MAP.put("lemonchiffon", 0xFFFFFACD); + COLOR_MAP.put("lightblue", 0xFFADD8E6); + COLOR_MAP.put("lightcoral", 0xFFF08080); + COLOR_MAP.put("lightcyan", 0xFFE0FFFF); + COLOR_MAP.put("lightgoldenrodyellow", 0xFFFAFAD2); + COLOR_MAP.put("lightgray", 0xFFD3D3D3); + COLOR_MAP.put("lightgreen", 0xFF90EE90); + COLOR_MAP.put("lightgrey", 0xFFD3D3D3); + COLOR_MAP.put("lightpink", 0xFFFFB6C1); + COLOR_MAP.put("lightsalmon", 0xFFFFA07A); + COLOR_MAP.put("lightseagreen", 0xFF20B2AA); + COLOR_MAP.put("lightskyblue", 0xFF87CEFA); + COLOR_MAP.put("lightslategray", 0xFF778899); + COLOR_MAP.put("lightslategrey", 0xFF778899); + COLOR_MAP.put("lightsteelblue", 0xFFB0C4DE); + COLOR_MAP.put("lightyellow", 0xFFFFFFE0); + COLOR_MAP.put("lime", 0xFF00FF00); + COLOR_MAP.put("limegreen", 0xFF32CD32); + COLOR_MAP.put("linen", 0xFFFAF0E6); + COLOR_MAP.put("magenta", 0xFFFF00FF); + COLOR_MAP.put("maroon", 0xFF800000); + COLOR_MAP.put("mediumaquamarine", 0xFF66CDAA); + COLOR_MAP.put("mediumblue", 0xFF0000CD); + COLOR_MAP.put("mediumorchid", 0xFFBA55D3); + COLOR_MAP.put("mediumpurple", 0xFF9370DB); + COLOR_MAP.put("mediumseagreen", 0xFF3CB371); + COLOR_MAP.put("mediumslateblue", 0xFF7B68EE); + COLOR_MAP.put("mediumspringgreen", 0xFF00FA9A); + COLOR_MAP.put("mediumturquoise", 0xFF48D1CC); + COLOR_MAP.put("mediumvioletred", 0xFFC71585); + COLOR_MAP.put("midnightblue", 0xFF191970); + COLOR_MAP.put("mintcream", 0xFFF5FFFA); + COLOR_MAP.put("mistyrose", 0xFFFFE4E1); + COLOR_MAP.put("moccasin", 0xFFFFE4B5); + COLOR_MAP.put("navajowhite", 0xFFFFDEAD); + COLOR_MAP.put("navy", 0xFF000080); + COLOR_MAP.put("oldlace", 0xFFFDF5E6); + COLOR_MAP.put("olive", 0xFF808000); + COLOR_MAP.put("olivedrab", 0xFF6B8E23); + COLOR_MAP.put("orange", 0xFFFFA500); + COLOR_MAP.put("orangered", 0xFFFF4500); + COLOR_MAP.put("orchid", 0xFFDA70D6); + COLOR_MAP.put("palegoldenrod", 0xFFEEE8AA); + COLOR_MAP.put("palegreen", 0xFF98FB98); + COLOR_MAP.put("paleturquoise", 0xFFAFEEEE); + COLOR_MAP.put("palevioletred", 0xFFDB7093); + COLOR_MAP.put("papayawhip", 0xFFFFEFD5); + COLOR_MAP.put("peachpuff", 0xFFFFDAB9); + COLOR_MAP.put("peru", 0xFFCD853F); + COLOR_MAP.put("pink", 0xFFFFC0CB); + COLOR_MAP.put("plum", 0xFFDDA0DD); + COLOR_MAP.put("powderblue", 0xFFB0E0E6); + COLOR_MAP.put("purple", 0xFF800080); + COLOR_MAP.put("rebeccapurple", 0xFF663399); + COLOR_MAP.put("red", 0xFFFF0000); + COLOR_MAP.put("rosybrown", 0xFFBC8F8F); + COLOR_MAP.put("royalblue", 0xFF4169E1); + COLOR_MAP.put("saddlebrown", 0xFF8B4513); + COLOR_MAP.put("salmon", 0xFFFA8072); + COLOR_MAP.put("sandybrown", 0xFFF4A460); + COLOR_MAP.put("seagreen", 0xFF2E8B57); + COLOR_MAP.put("seashell", 0xFFFFF5EE); + COLOR_MAP.put("sienna", 0xFFA0522D); + COLOR_MAP.put("silver", 0xFFC0C0C0); + COLOR_MAP.put("skyblue", 0xFF87CEEB); + COLOR_MAP.put("slateblue", 0xFF6A5ACD); + COLOR_MAP.put("slategray", 0xFF708090); + COLOR_MAP.put("slategrey", 0xFF708090); + COLOR_MAP.put("snow", 0xFFFFFAFA); + COLOR_MAP.put("springgreen", 0xFF00FF7F); + COLOR_MAP.put("steelblue", 0xFF4682B4); + COLOR_MAP.put("tan", 0xFFD2B48C); + COLOR_MAP.put("teal", 0xFF008080); + COLOR_MAP.put("thistle", 0xFFD8BFD8); + COLOR_MAP.put("tomato", 0xFFFF6347); + COLOR_MAP.put("transparent", 0x00000000); + COLOR_MAP.put("turquoise", 0xFF40E0D0); + COLOR_MAP.put("violet", 0xFFEE82EE); + COLOR_MAP.put("wheat", 0xFFF5DEB3); + COLOR_MAP.put("white", 0xFFFFFFFF); + COLOR_MAP.put("whitesmoke", 0xFFF5F5F5); + COLOR_MAP.put("yellow", 0xFFFFFF00); + COLOR_MAP.put("yellowgreen", 0xFF9ACD32); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ConditionVariable.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ConditionVariable.java new file mode 100644 index 0000000..6b400f4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ConditionVariable.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +/** + * A condition variable whose {@link #open()} and {@link #close()} methods return whether they + * resulted in a change of state. + */ +public final class ConditionVariable { + + private boolean isOpen; + + /** + * Opens the condition and releases all threads that are blocked. + * + * @return True if the condition variable was opened. False if it was already open. + */ + public synchronized boolean open() { + if (isOpen) { + return false; + } + isOpen = true; + notifyAll(); + return true; + } + + /** + * Closes the condition. + * + * @return True if the condition variable was closed. False if it was already closed. + */ + public synchronized boolean close() { + boolean wasOpen = isOpen; + isOpen = false; + return wasOpen; + } + + /** + * Blocks until the condition is opened. + * + * @throws InterruptedException If the thread is interrupted. + */ + public synchronized void block() throws InterruptedException { + while (!isOpen) { + wait(); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/FlacStreamInfo.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/FlacStreamInfo.java new file mode 100644 index 0000000..f2bcf75 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/FlacStreamInfo.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +/** + * Holder for FLAC stream info. + */ +public final class FlacStreamInfo { + + public final int minBlockSize; + public final int maxBlockSize; + public final int minFrameSize; + public final int maxFrameSize; + public final int sampleRate; + public final int channels; + public final int bitsPerSample; + public final long totalSamples; + + /** + * Constructs a FlacStreamInfo parsing the given binary FLAC stream info metadata structure. + * + * @param data An array holding FLAC stream info metadata structure + * @param offset Offset of the structure in the array + * @see FLAC format + * METADATA_BLOCK_STREAMINFO + */ + public FlacStreamInfo(byte[] data, int offset) { + ParsableBitArray scratch = new ParsableBitArray(data); + scratch.setPosition(offset * 8); + this.minBlockSize = scratch.readBits(16); + this.maxBlockSize = scratch.readBits(16); + this.minFrameSize = scratch.readBits(24); + this.maxFrameSize = scratch.readBits(24); + this.sampleRate = scratch.readBits(20); + this.channels = scratch.readBits(3) + 1; + this.bitsPerSample = scratch.readBits(5) + 1; + this.totalSamples = scratch.readBits(36); + // Remaining 16 bytes is md5 value + } + + public FlacStreamInfo(int minBlockSize, int maxBlockSize, int minFrameSize, int maxFrameSize, + int sampleRate, int channels, int bitsPerSample, long totalSamples) { + this.minBlockSize = minBlockSize; + this.maxBlockSize = maxBlockSize; + this.minFrameSize = minFrameSize; + this.maxFrameSize = maxFrameSize; + this.sampleRate = sampleRate; + this.channels = channels; + this.bitsPerSample = bitsPerSample; + this.totalSamples = totalSamples; + } + + public int maxDecodedFrameSize() { + return maxBlockSize * channels * 2; + } + + public int bitRate() { + return bitsPerSample * sampleRate; + } + + public long durationUs() { + return (totalSamples * 1000000L) / sampleRate; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/LibraryLoader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/LibraryLoader.java new file mode 100644 index 0000000..218ba8d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/LibraryLoader.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +/** + * Configurable loader for native libraries. + */ +public final class LibraryLoader { + + private String[] nativeLibraries; + private boolean loadAttempted; + private boolean isAvailable; + + /** + * @param libraries The names of the libraries to load. + */ + public LibraryLoader(String... libraries) { + nativeLibraries = libraries; + } + + /** + * Overrides the names of the libraries to load. Must be called before any call to + * {@link #isAvailable()}. + */ + public synchronized void setLibraries(String... libraries) { + Assertions.checkState(!loadAttempted, "Cannot set libraries after loading"); + nativeLibraries = libraries; + } + + /** + * Returns whether the underlying libraries are available, loading them if necessary. + */ + public synchronized boolean isAvailable() { + if (loadAttempted) { + return isAvailable; + } + loadAttempted = true; + try { + for (String lib : nativeLibraries) { + System.loadLibrary(lib); + } + isAvailable = true; + } catch (UnsatisfiedLinkError exception) { + // Do nothing. + exception.getStackTrace(); + } + return isAvailable; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/LongArray.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/LongArray.java new file mode 100644 index 0000000..047e4af --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/LongArray.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import java.util.Arrays; + +/** + * An append-only, auto-growing {@code long[]}. + */ +public final class LongArray { + + private static final int DEFAULT_INITIAL_CAPACITY = 32; + + private int size; + private long[] values; + + public LongArray() { + this(DEFAULT_INITIAL_CAPACITY); + } + + /** + * @param initialCapacity The initial capacity of the array. + */ + public LongArray(int initialCapacity) { + values = new long[initialCapacity]; + } + + /** + * Appends a value. + * + * @param value The value to append. + */ + public void add(long value) { + if (size == values.length) { + values = Arrays.copyOf(values, size * 2); + } + values[size++] = value; + } + + /** + * Returns the value at a specified index. + * + * @param index The index. + * @return The corresponding value. + * @throws IndexOutOfBoundsException If the index is less than zero, or greater than or equal to + * {@link #size()}. + */ + public long get(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Invalid index " + index + ", size is " + size); + } + return values[index]; + } + + /** + * Returns the current size of the array. + */ + public int size() { + return size; + } + + /** + * Copies the current values into a newly allocated primitive array. + * + * @return The primitive array containing the copied values. + */ + public long[] toArray() { + return Arrays.copyOf(values, size); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/MediaClock.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/MediaClock.java new file mode 100644 index 0000000..9ce9e20 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/MediaClock.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import com.tangxiaolv.telegramgallery.exoplayer2.PlaybackParameters; + +/** + * Tracks the progression of media time. + */ +public interface MediaClock { + + /** + * Returns the current media position in microseconds. + */ + long getPositionUs(); + + /** + * Attempts to set the playback parameters and returns the active playback parameters, which may + * differ from those passed in. + * + * @param playbackParameters The playback parameters. + * @return The active playback parameters. + */ + PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters); + + /** + * Returns the active playback parameters. + */ + PlaybackParameters getPlaybackParameters(); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/MimeTypes.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/MimeTypes.java new file mode 100644 index 0000000..7bbde60 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/MimeTypes.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.text.TextUtils; +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +/** + * Defines common MIME types and helper methods. + */ +public final class MimeTypes { + + public static final String BASE_TYPE_VIDEO = "video"; + public static final String BASE_TYPE_AUDIO = "audio"; + public static final String BASE_TYPE_TEXT = "text"; + public static final String BASE_TYPE_APPLICATION = "application"; + + public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4"; + public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm"; + public static final String VIDEO_H263 = BASE_TYPE_VIDEO + "/3gpp"; + public static final String VIDEO_H264 = BASE_TYPE_VIDEO + "/avc"; + public static final String VIDEO_H265 = BASE_TYPE_VIDEO + "/hevc"; + public static final String VIDEO_VP8 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp8"; + public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9"; + public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es"; + public static final String VIDEO_MPEG2 = BASE_TYPE_VIDEO + "/mpeg2"; + public static final String VIDEO_VC1 = BASE_TYPE_VIDEO + "/wvc1"; + public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown"; + + public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; + public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; + public static final String AUDIO_WEBM = BASE_TYPE_AUDIO + "/webm"; + public static final String AUDIO_MPEG = BASE_TYPE_AUDIO + "/mpeg"; + public static final String AUDIO_MPEG_L1 = BASE_TYPE_AUDIO + "/mpeg-L1"; + public static final String AUDIO_MPEG_L2 = BASE_TYPE_AUDIO + "/mpeg-L2"; + public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw"; + public static final String AUDIO_ALAW = BASE_TYPE_AUDIO + "/g711-alaw"; + public static final String AUDIO_ULAW = BASE_TYPE_AUDIO + "/g711-mlaw"; + public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; + public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3"; + public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd"; + public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts"; + public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd"; + public static final String AUDIO_DTS_EXPRESS = BASE_TYPE_AUDIO + "/vnd.dts.hd;profile=lbr"; + public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis"; + public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus"; + public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp"; + public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb"; + public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/x-flac"; + public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac"; + + public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt"; + + public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4"; + public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm"; + public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL"; + public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3"; + public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608"; + public static final String APPLICATION_CEA708 = BASE_TYPE_APPLICATION + "/cea-708"; + public static final String APPLICATION_SUBRIP = BASE_TYPE_APPLICATION + "/x-subrip"; + public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml"; + public static final String APPLICATION_TX3G = BASE_TYPE_APPLICATION + "/x-quicktime-tx3g"; + public static final String APPLICATION_MP4VTT = BASE_TYPE_APPLICATION + "/x-mp4-vtt"; + public static final String APPLICATION_MP4CEA608 = BASE_TYPE_APPLICATION + "/x-mp4-cea-608"; + public static final String APPLICATION_RAWCC = BASE_TYPE_APPLICATION + "/x-rawcc"; + public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub"; + public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs"; + public static final String APPLICATION_SCTE35 = BASE_TYPE_APPLICATION + "/x-scte35"; + public static final String APPLICATION_CAMERA_MOTION = BASE_TYPE_APPLICATION + "/x-camera-motion"; + public static final String APPLICATION_EMSG = BASE_TYPE_APPLICATION + "/x-emsg"; + public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs"; + + private MimeTypes() {} + + /** + * Whether the top-level type of {@code mimeType} is audio. + * + * @param mimeType The mimeType to test. + * @return Whether the top level type is audio. + */ + public static boolean isAudio(String mimeType) { + return BASE_TYPE_AUDIO.equals(getTopLevelType(mimeType)); + } + + /** + * Whether the top-level type of {@code mimeType} is video. + * + * @param mimeType The mimeType to test. + * @return Whether the top level type is video. + */ + public static boolean isVideo(String mimeType) { + return BASE_TYPE_VIDEO.equals(getTopLevelType(mimeType)); + } + + /** + * Whether the top-level type of {@code mimeType} is text. + * + * @param mimeType The mimeType to test. + * @return Whether the top level type is text. + */ + public static boolean isText(String mimeType) { + return BASE_TYPE_TEXT.equals(getTopLevelType(mimeType)); + } + + /** + * Whether the top-level type of {@code mimeType} is application. + * + * @param mimeType The mimeType to test. + * @return Whether the top level type is application. + */ + public static boolean isApplication(String mimeType) { + return BASE_TYPE_APPLICATION.equals(getTopLevelType(mimeType)); + } + + + /** + * Derives a video sample mimeType from a codecs attribute. + * + * @param codecs The codecs attribute. + * @return The derived video mimeType, or null if it could not be derived. + */ + public static String getVideoMediaMimeType(String codecs) { + if (codecs == null) { + return null; + } + String[] codecList = codecs.split(","); + for (String codec : codecList) { + String mimeType = getMediaMimeType(codec); + if (mimeType != null && isVideo(mimeType)) { + return mimeType; + } + } + return null; + } + + /** + * Derives a audio sample mimeType from a codecs attribute. + * + * @param codecs The codecs attribute. + * @return The derived audio mimeType, or null if it could not be derived. + */ + public static String getAudioMediaMimeType(String codecs) { + if (codecs == null) { + return null; + } + String[] codecList = codecs.split(","); + for (String codec : codecList) { + String mimeType = getMediaMimeType(codec); + if (mimeType != null && isAudio(mimeType)) { + return mimeType; + } + } + return null; + } + + /** + * Derives a mimeType from a codec identifier, as defined in RFC 6381. + * + * @param codec The codec identifier to derive. + * @return The mimeType, or null if it could not be derived. + */ + public static String getMediaMimeType(String codec) { + if (codec == null) { + return null; + } + codec = codec.trim(); + if (codec.startsWith("avc1") || codec.startsWith("avc3")) { + return MimeTypes.VIDEO_H264; + } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { + return MimeTypes.VIDEO_H265; + } else if (codec.startsWith("vp9")) { + return MimeTypes.VIDEO_VP9; + } else if (codec.startsWith("vp8")) { + return MimeTypes.VIDEO_VP8; + } else if (codec.startsWith("mp4a")) { + return MimeTypes.AUDIO_AAC; + } else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) { + return MimeTypes.AUDIO_AC3; + } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { + return MimeTypes.AUDIO_E_AC3; + } else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) { + return MimeTypes.AUDIO_DTS; + } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { + return MimeTypes.AUDIO_DTS_HD; + } else if (codec.startsWith("opus")) { + return MimeTypes.AUDIO_OPUS; + } else if (codec.startsWith("vorbis")) { + return MimeTypes.AUDIO_VORBIS; + } + return null; + } + + /** + * Returns the {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified mime type. + * {@link C#TRACK_TYPE_UNKNOWN} if the mime type is not known or the mapping cannot be + * established. + * + * @param mimeType The mimeType. + * @return The {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified mime type. + */ + public static int getTrackType(String mimeType) { + if (TextUtils.isEmpty(mimeType)) { + return C.TRACK_TYPE_UNKNOWN; + } else if (isAudio(mimeType)) { + return C.TRACK_TYPE_AUDIO; + } else if (isVideo(mimeType)) { + return C.TRACK_TYPE_VIDEO; + } else if (isText(mimeType) || APPLICATION_CEA608.equals(mimeType) + || APPLICATION_CEA708.equals(mimeType) || APPLICATION_MP4CEA608.equals(mimeType) + || APPLICATION_SUBRIP.equals(mimeType) || APPLICATION_TTML.equals(mimeType) + || APPLICATION_TX3G.equals(mimeType) || APPLICATION_MP4VTT.equals(mimeType) + || APPLICATION_RAWCC.equals(mimeType) || APPLICATION_VOBSUB.equals(mimeType) + || APPLICATION_PGS.equals(mimeType) || APPLICATION_DVBSUBS.equals(mimeType)) { + return C.TRACK_TYPE_TEXT; + } else if (APPLICATION_ID3.equals(mimeType) + || APPLICATION_EMSG.equals(mimeType) + || APPLICATION_SCTE35.equals(mimeType) + || APPLICATION_CAMERA_MOTION.equals(mimeType)) { + return C.TRACK_TYPE_METADATA; + } else { + return C.TRACK_TYPE_UNKNOWN; + } + } + + /** + * Equivalent to {@code getTrackType(getMediaMimeType(codec))}. + * + * @param codec The codec. + * @return The {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified codec. + */ + public static int getTrackTypeOfCodec(String codec) { + return getTrackType(getMediaMimeType(codec)); + } + + /** + * Returns the top-level type of {@code mimeType}. + * + * @param mimeType The mimeType whose top-level type is required. + * @return The top-level type, or null if the mimeType is null. + */ + private static String getTopLevelType(String mimeType) { + if (mimeType == null) { + return null; + } + int indexOfSlash = mimeType.indexOf('/'); + if (indexOfSlash == -1) { + throw new IllegalArgumentException("Invalid mime type: " + mimeType); + } + return mimeType.substring(0, indexOfSlash); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/NalUnitUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/NalUnitUtil.java new file mode 100644 index 0000000..6397257 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/NalUnitUtil.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.util.Log; +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * Utility methods for handling H.264/AVC and H.265/HEVC NAL units. + */ +public final class NalUnitUtil { + + private static final String TAG = "NalUnitUtil"; + + /** + * Holds data parsed from a sequence parameter set NAL unit. + */ + public static final class SpsData { + + public final int seqParameterSetId; + public final int width; + public final int height; + public final float pixelWidthAspectRatio; + public final boolean separateColorPlaneFlag; + public final boolean frameMbsOnlyFlag; + public final int frameNumLength; + public final int picOrderCountType; + public final int picOrderCntLsbLength; + public final boolean deltaPicOrderAlwaysZeroFlag; + + public SpsData(int seqParameterSetId, int width, int height, float pixelWidthAspectRatio, + boolean separateColorPlaneFlag, boolean frameMbsOnlyFlag, int frameNumLength, + int picOrderCountType, int picOrderCntLsbLength, boolean deltaPicOrderAlwaysZeroFlag) { + this.seqParameterSetId = seqParameterSetId; + this.width = width; + this.height = height; + this.pixelWidthAspectRatio = pixelWidthAspectRatio; + this.separateColorPlaneFlag = separateColorPlaneFlag; + this.frameMbsOnlyFlag = frameMbsOnlyFlag; + this.frameNumLength = frameNumLength; + this.picOrderCountType = picOrderCountType; + this.picOrderCntLsbLength = picOrderCntLsbLength; + this.deltaPicOrderAlwaysZeroFlag = deltaPicOrderAlwaysZeroFlag; + } + + } + + /** + * Holds data parsed from a picture parameter set NAL unit. + */ + public static final class PpsData { + + public final int picParameterSetId; + public final int seqParameterSetId; + public final boolean bottomFieldPicOrderInFramePresentFlag; + + public PpsData(int picParameterSetId, int seqParameterSetId, + boolean bottomFieldPicOrderInFramePresentFlag) { + this.picParameterSetId = picParameterSetId; + this.seqParameterSetId = seqParameterSetId; + this.bottomFieldPicOrderInFramePresentFlag = bottomFieldPicOrderInFramePresentFlag; + } + + } + + /** Four initial bytes that must prefix NAL units for decoding. */ + public static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; + + /** Value for aspect_ratio_idc indicating an extended aspect ratio, in H.264 and H.265 SPSs. */ + public static final int EXTENDED_SAR = 0xFF; + /** Aspect ratios indexed by aspect_ratio_idc, in H.264 and H.265 SPSs. */ + public static final float[] ASPECT_RATIO_IDC_VALUES = new float[] { + 1f /* Unspecified. Assume square */, + 1f, + 12f / 11f, + 10f / 11f, + 16f / 11f, + 40f / 33f, + 24f / 11f, + 20f / 11f, + 32f / 11f, + 80f / 33f, + 18f / 11f, + 15f / 11f, + 64f / 33f, + 160f / 99f, + 4f / 3f, + 3f / 2f, + 2f + }; + + private static final int H264_NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information + private static final int H264_NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set + private static final int H265_NAL_UNIT_TYPE_PREFIX_SEI = 39; + + private static final Object scratchEscapePositionsLock = new Object(); + + /** + * Temporary store for positions of escape codes in {@link #unescapeStream(byte[], int)}. Guarded + * by {@link #scratchEscapePositionsLock}. + */ + private static int[] scratchEscapePositions = new int[10]; + + /** + * Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with + * [0, 0]. The unescaped data is returned in-place, with the return value indicating its length. + *

    + * Executions of this method are mutually exclusive, so it should not be called with very large + * buffers. + * + * @param data The data to unescape. + * @param limit The limit (exclusive) of the data to unescape. + * @return The length of the unescaped data. + */ + public static int unescapeStream(byte[] data, int limit) { + synchronized (scratchEscapePositionsLock) { + int position = 0; + int scratchEscapeCount = 0; + while (position < limit) { + position = findNextUnescapeIndex(data, position, limit); + if (position < limit) { + if (scratchEscapePositions.length <= scratchEscapeCount) { + // Grow scratchEscapePositions to hold a larger number of positions. + scratchEscapePositions = Arrays.copyOf(scratchEscapePositions, + scratchEscapePositions.length * 2); + } + scratchEscapePositions[scratchEscapeCount++] = position; + position += 3; + } + } + + int unescapedLength = limit - scratchEscapeCount; + int escapedPosition = 0; // The position being read from. + int unescapedPosition = 0; // The position being written to. + for (int i = 0; i < scratchEscapeCount; i++) { + int nextEscapePosition = scratchEscapePositions[i]; + int copyLength = nextEscapePosition - escapedPosition; + System.arraycopy(data, escapedPosition, data, unescapedPosition, copyLength); + unescapedPosition += copyLength; + data[unescapedPosition++] = 0; + data[unescapedPosition++] = 0; + escapedPosition += copyLength + 3; + } + + int remainingLength = unescapedLength - unescapedPosition; + System.arraycopy(data, escapedPosition, data, unescapedPosition, remainingLength); + return unescapedLength; + } + } + + /** + * Discards data from the buffer up to the first SPS, where {@code data.position()} is interpreted + * as the length of the buffer. + *

    + * When the method returns, {@code data.position()} will contain the new length of the buffer. If + * the buffer is not empty it is guaranteed to start with an SPS. + * + * @param data Buffer containing start code delimited NAL units. + */ + public static void discardToSps(ByteBuffer data) { + int length = data.position(); + int consecutiveZeros = 0; + int offset = 0; + while (offset + 1 < length) { + int value = data.get(offset) & 0xFF; + if (consecutiveZeros == 3) { + if (value == 1 && (data.get(offset + 1) & 0x1F) == H264_NAL_UNIT_TYPE_SPS) { + // Copy from this NAL unit onwards to the start of the buffer. + ByteBuffer offsetData = data.duplicate(); + offsetData.position(offset - 3); + offsetData.limit(length); + data.position(0); + data.put(offsetData); + return; + } + } else if (value == 0) { + consecutiveZeros++; + } + if (value != 0) { + consecutiveZeros = 0; + } + offset++; + } + // Empty the buffer if the SPS NAL unit was not found. + data.clear(); + } + + /** + * Returns whether the NAL unit with the specified header contains supplemental enhancement + * information. + * + * @param mimeType The sample MIME type. + * @param nalUnitHeaderFirstByte The first byte of nal_unit(). + * @return Whether the NAL unit with the specified header is an SEI NAL unit. + */ + public static boolean isNalUnitSei(String mimeType, byte nalUnitHeaderFirstByte) { + return (MimeTypes.VIDEO_H264.equals(mimeType) + && (nalUnitHeaderFirstByte & 0x1F) == H264_NAL_UNIT_TYPE_SEI) + || (MimeTypes.VIDEO_H265.equals(mimeType) + && ((nalUnitHeaderFirstByte & 0x7E) >> 1) == H265_NAL_UNIT_TYPE_PREFIX_SEI); + } + + /** + * Returns the type of the NAL unit in {@code data} that starts at {@code offset}. + * + * @param data The data to search. + * @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and + * {@code data.length - 3} (exclusive). + * @return The type of the unit. + */ + public static int getNalUnitType(byte[] data, int offset) { + return data[offset + 3] & 0x1F; + } + + /** + * Returns the type of the H.265 NAL unit in {@code data} that starts at {@code offset}. + * + * @param data The data to search. + * @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and + * {@code data.length - 3} (exclusive). + * @return The type of the unit. + */ + public static int getH265NalUnitType(byte[] data, int offset) { + return (data[offset + 3] & 0x7E) >> 1; + } + + /** + * Parses an SPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection + * 7.3.2.1.1. + * + * @param nalData A buffer containing escaped SPS data. + * @param nalOffset The offset of the NAL unit header in {@code nalData}. + * @param nalLimit The limit of the NAL unit in {@code nalData}. + * @return A parsed representation of the SPS data. + */ + public static SpsData parseSpsNalUnit(byte[] nalData, int nalOffset, int nalLimit) { + ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit); + data.skipBits(8); // nal_unit + int profileIdc = data.readBits(8); + data.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8) + int seqParameterSetId = data.readUnsignedExpGolombCodedInt(); + + int chromaFormatIdc = 1; // Default is 4:2:0 + boolean separateColorPlaneFlag = false; + if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244 + || profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118 + || profileIdc == 128 || profileIdc == 138) { + chromaFormatIdc = data.readUnsignedExpGolombCodedInt(); + if (chromaFormatIdc == 3) { + separateColorPlaneFlag = data.readBit(); + } + data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8 + data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8 + data.skipBits(1); // qpprime_y_zero_transform_bypass_flag + boolean seqScalingMatrixPresentFlag = data.readBit(); + if (seqScalingMatrixPresentFlag) { + int limit = (chromaFormatIdc != 3) ? 8 : 12; + for (int i = 0; i < limit; i++) { + boolean seqScalingListPresentFlag = data.readBit(); + if (seqScalingListPresentFlag) { + skipScalingList(data, i < 6 ? 16 : 64); + } + } + } + } + + int frameNumLength = data.readUnsignedExpGolombCodedInt() + 4; // log2_max_frame_num_minus4 + 4 + int picOrderCntType = data.readUnsignedExpGolombCodedInt(); + int picOrderCntLsbLength = 0; + boolean deltaPicOrderAlwaysZeroFlag = false; + if (picOrderCntType == 0) { + // log2_max_pic_order_cnt_lsb_minus4 + 4 + picOrderCntLsbLength = data.readUnsignedExpGolombCodedInt() + 4; + } else if (picOrderCntType == 1) { + deltaPicOrderAlwaysZeroFlag = data.readBit(); // delta_pic_order_always_zero_flag + data.readSignedExpGolombCodedInt(); // offset_for_non_ref_pic + data.readSignedExpGolombCodedInt(); // offset_for_top_to_bottom_field + long numRefFramesInPicOrderCntCycle = data.readUnsignedExpGolombCodedInt(); + for (int i = 0; i < numRefFramesInPicOrderCntCycle; i++) { + data.readUnsignedExpGolombCodedInt(); // offset_for_ref_frame[i] + } + } + data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames + data.skipBits(1); // gaps_in_frame_num_value_allowed_flag + + int picWidthInMbs = data.readUnsignedExpGolombCodedInt() + 1; + int picHeightInMapUnits = data.readUnsignedExpGolombCodedInt() + 1; + boolean frameMbsOnlyFlag = data.readBit(); + int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits; + if (!frameMbsOnlyFlag) { + data.skipBits(1); // mb_adaptive_frame_field_flag + } + + data.skipBits(1); // direct_8x8_inference_flag + int frameWidth = picWidthInMbs * 16; + int frameHeight = frameHeightInMbs * 16; + boolean frameCroppingFlag = data.readBit(); + if (frameCroppingFlag) { + int frameCropLeftOffset = data.readUnsignedExpGolombCodedInt(); + int frameCropRightOffset = data.readUnsignedExpGolombCodedInt(); + int frameCropTopOffset = data.readUnsignedExpGolombCodedInt(); + int frameCropBottomOffset = data.readUnsignedExpGolombCodedInt(); + int cropUnitX; + int cropUnitY; + if (chromaFormatIdc == 0) { + cropUnitX = 1; + cropUnitY = 2 - (frameMbsOnlyFlag ? 1 : 0); + } else { + int subWidthC = (chromaFormatIdc == 3) ? 1 : 2; + int subHeightC = (chromaFormatIdc == 1) ? 2 : 1; + cropUnitX = subWidthC; + cropUnitY = subHeightC * (2 - (frameMbsOnlyFlag ? 1 : 0)); + } + frameWidth -= (frameCropLeftOffset + frameCropRightOffset) * cropUnitX; + frameHeight -= (frameCropTopOffset + frameCropBottomOffset) * cropUnitY; + } + + float pixelWidthHeightRatio = 1; + boolean vuiParametersPresentFlag = data.readBit(); + if (vuiParametersPresentFlag) { + boolean aspectRatioInfoPresentFlag = data.readBit(); + if (aspectRatioInfoPresentFlag) { + int aspectRatioIdc = data.readBits(8); + if (aspectRatioIdc == NalUnitUtil.EXTENDED_SAR) { + int sarWidth = data.readBits(16); + int sarHeight = data.readBits(16); + if (sarWidth != 0 && sarHeight != 0) { + pixelWidthHeightRatio = (float) sarWidth / sarHeight; + } + } else if (aspectRatioIdc < NalUnitUtil.ASPECT_RATIO_IDC_VALUES.length) { + pixelWidthHeightRatio = NalUnitUtil.ASPECT_RATIO_IDC_VALUES[aspectRatioIdc]; + } else { + Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc); + } + } + } + + return new SpsData(seqParameterSetId, frameWidth, frameHeight, pixelWidthHeightRatio, + separateColorPlaneFlag, frameMbsOnlyFlag, frameNumLength, picOrderCntType, + picOrderCntLsbLength, deltaPicOrderAlwaysZeroFlag); + } + + /** + * Parses a PPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection + * 7.3.2.2. + * + * @param nalData A buffer containing escaped PPS data. + * @param nalOffset The offset of the NAL unit header in {@code nalData}. + * @param nalLimit The limit of the NAL unit in {@code nalData}. + * @return A parsed representation of the PPS data. + */ + public static PpsData parsePpsNalUnit(byte[] nalData, int nalOffset, int nalLimit) { + ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit); + data.skipBits(8); // nal_unit + int picParameterSetId = data.readUnsignedExpGolombCodedInt(); + int seqParameterSetId = data.readUnsignedExpGolombCodedInt(); + data.skipBits(1); // entropy_coding_mode_flag + boolean bottomFieldPicOrderInFramePresentFlag = data.readBit(); + return new PpsData(picParameterSetId, seqParameterSetId, bottomFieldPicOrderInFramePresentFlag); + } + + /** + * Finds the first NAL unit in {@code data}. + *

    + * If {@code prefixFlags} is null then the first three bytes of a NAL unit must be entirely + * contained within the part of the array being searched in order for it to be found. + *

    + * When {@code prefixFlags} is non-null, this method supports finding NAL units whose first four + * bytes span {@code data} arrays passed to successive calls. To use this feature, pass the same + * {@code prefixFlags} parameter to successive calls. State maintained in this parameter enables + * the detection of such NAL units. Note that when using this feature, the return value may be 3, + * 2 or 1 less than {@code startOffset}, to indicate a NAL unit starting 3, 2 or 1 bytes before + * the first byte in the current array. + * + * @param data The data to search. + * @param startOffset The offset (inclusive) in the data to start the search. + * @param endOffset The offset (exclusive) in the data to end the search. + * @param prefixFlags A boolean array whose first three elements are used to store the state + * required to detect NAL units where the NAL unit prefix spans array boundaries. The array + * must be at least 3 elements long. + * @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found. + */ + public static int findNalUnit(byte[] data, int startOffset, int endOffset, + boolean[] prefixFlags) { + int length = endOffset - startOffset; + + Assertions.checkState(length >= 0); + if (length == 0) { + return endOffset; + } + + if (prefixFlags != null) { + if (prefixFlags[0]) { + clearPrefixFlags(prefixFlags); + return startOffset - 3; + } else if (length > 1 && prefixFlags[1] && data[startOffset] == 1) { + clearPrefixFlags(prefixFlags); + return startOffset - 2; + } else if (length > 2 && prefixFlags[2] && data[startOffset] == 0 + && data[startOffset + 1] == 1) { + clearPrefixFlags(prefixFlags); + return startOffset - 1; + } + } + + int limit = endOffset - 1; + // We're looking for the NAL unit start code prefix 0x000001. The value of i tracks the index of + // the third byte. + for (int i = startOffset + 2; i < limit; i += 3) { + if ((data[i] & 0xFE) != 0) { + // There isn't a NAL prefix here, or at the next two positions. Do nothing and let the + // loop advance the index by three. + } else if (data[i - 2] == 0 && data[i - 1] == 0 && data[i] == 1) { + if (prefixFlags != null) { + clearPrefixFlags(prefixFlags); + } + return i - 2; + } else { + // There isn't a NAL prefix here, but there might be at the next position. We should + // only skip forward by one. The loop will skip forward by three, so subtract two here. + i -= 2; + } + } + + if (prefixFlags != null) { + // True if the last three bytes in the data seen so far are {0,0,1}. + prefixFlags[0] = length > 2 + ? (data[endOffset - 3] == 0 && data[endOffset - 2] == 0 && data[endOffset - 1] == 1) + : length == 2 ? (prefixFlags[2] && data[endOffset - 2] == 0 && data[endOffset - 1] == 1) + : (prefixFlags[1] && data[endOffset - 1] == 1); + // True if the last two bytes in the data seen so far are {0,0}. + prefixFlags[1] = length > 1 ? data[endOffset - 2] == 0 && data[endOffset - 1] == 0 + : prefixFlags[2] && data[endOffset - 1] == 0; + // True if the last byte in the data seen so far is {0}. + prefixFlags[2] = data[endOffset - 1] == 0; + } + + return endOffset; + } + + /** + * Clears prefix flags, as used by {@link #findNalUnit(byte[], int, int, boolean[])}. + * + * @param prefixFlags The flags to clear. + */ + public static void clearPrefixFlags(boolean[] prefixFlags) { + prefixFlags[0] = false; + prefixFlags[1] = false; + prefixFlags[2] = false; + } + + private static int findNextUnescapeIndex(byte[] bytes, int offset, int limit) { + for (int i = offset; i < limit - 2; i++) { + if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x03) { + return i; + } + } + return limit; + } + + private static void skipScalingList(ParsableNalUnitBitArray bitArray, int size) { + int lastScale = 8; + int nextScale = 8; + for (int i = 0; i < size; i++) { + if (nextScale != 0) { + int deltaScale = bitArray.readSignedExpGolombCodedInt(); + nextScale = (lastScale + deltaScale + 256) % 256; + } + lastScale = (nextScale == 0) ? lastScale : nextScale; + } + } + + private NalUnitUtil() { + // Prevent instantiation. + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableBitArray.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableBitArray.java new file mode 100644 index 0000000..9d75762 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableBitArray.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +/** + * Wraps a byte array, providing methods that allow it to be read as a bitstream. + */ +public final class ParsableBitArray { + + public byte[] data; + + // The offset within the data, stored as the current byte offset, and the bit offset within that + // byte (from 0 to 7). + private int byteOffset; + private int bitOffset; + private int byteLimit; + + /** + * Creates a new instance that initially has no backing data. + */ + public ParsableBitArray() {} + + /** + * Creates a new instance that wraps an existing array. + * + * @param data The data to wrap. + */ + public ParsableBitArray(byte[] data) { + this(data, data.length); + } + + /** + * Creates a new instance that wraps an existing array. + * + * @param data The data to wrap. + * @param limit The limit in bytes. + */ + public ParsableBitArray(byte[] data, int limit) { + this.data = data; + byteLimit = limit; + } + + /** + * Updates the instance to wrap {@code data}, and resets the position to zero. + * + * @param data The array to wrap. + */ + public void reset(byte[] data) { + reset(data, data.length); + } + + /** + * Updates the instance to wrap {@code data}, and resets the position to zero. + * + * @param data The array to wrap. + * @param limit The limit in bytes. + */ + public void reset(byte[] data, int limit) { + this.data = data; + byteOffset = 0; + bitOffset = 0; + byteLimit = limit; + } + + /** + * Returns the number of bits yet to be read. + */ + public int bitsLeft() { + return (byteLimit - byteOffset) * 8 - bitOffset; + } + + /** + * Returns the current bit offset. + */ + public int getPosition() { + return byteOffset * 8 + bitOffset; + } + + /** + * Returns the current byte offset. Must only be called when the position is byte aligned. + * + * @throws IllegalStateException If the position isn't byte aligned. + */ + public int getBytePosition() { + Assertions.checkState(bitOffset == 0); + return byteOffset; + } + + /** + * Sets the current bit offset. + * + * @param position The position to set. + */ + public void setPosition(int position) { + byteOffset = position / 8; + bitOffset = position - (byteOffset * 8); + assertValidOffset(); + } + + /** + * Skips bits and moves current reading position forward. + * + * @param n The number of bits to skip. + */ + public void skipBits(int n) { + byteOffset += (n / 8); + bitOffset += (n % 8); + if (bitOffset > 7) { + byteOffset++; + bitOffset -= 8; + } + assertValidOffset(); + } + + /** + * Reads a single bit. + * + * @return Whether the bit is set. + */ + public boolean readBit() { + return readBits(1) == 1; + } + + /** + * Reads up to 32 bits. + * + * @param numBits The number of bits to read. + * @return An integer whose bottom n bits hold the read data. + */ + public int readBits(int numBits) { + if (numBits == 0) { + return 0; + } + + int returnValue = 0; + + // Read as many whole bytes as we can. + int wholeBytes = (numBits / 8); + for (int i = 0; i < wholeBytes; i++) { + int byteValue; + if (bitOffset != 0) { + byteValue = ((data[byteOffset] & 0xFF) << bitOffset) + | ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset)); + } else { + byteValue = data[byteOffset]; + } + numBits -= 8; + returnValue |= (byteValue & 0xFF) << numBits; + byteOffset++; + } + + // Read any remaining bits. + if (numBits > 0) { + int nextBit = bitOffset + numBits; + byte writeMask = (byte) (0xFF >> (8 - numBits)); + + if (nextBit > 8) { + // Combine bits from current byte and next byte. + returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8) + | ((data[byteOffset + 1] & 0xFF) >> (16 - nextBit))) & writeMask)); + byteOffset++; + } else { + // Bits to be read only within current byte. + returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask); + if (nextBit == 8) { + byteOffset++; + } + } + + bitOffset = nextBit % 8; + } + + assertValidOffset(); + return returnValue; + } + + /** + * Aligns the position to the next byte boundary. Does nothing if the position is already aligned. + */ + public void byteAlign() { + if (bitOffset == 0) { + return; + } + bitOffset = 0; + byteOffset++; + assertValidOffset(); + } + + /** + * Reads the next {@code length} bytes into {@code buffer}. Must only be called when the position + * is byte aligned. + * + * @see System#arraycopy(Object, int, Object, int, int) + * @param buffer The array into which the read data should be written. + * @param offset The offset in {@code buffer} at which the read data should be written. + * @param length The number of bytes to read. + * @throws IllegalStateException If the position isn't byte aligned. + */ + public void readBytes(byte[] buffer, int offset, int length) { + Assertions.checkState(bitOffset == 0); + System.arraycopy(data, byteOffset, buffer, offset, length); + byteOffset += length; + assertValidOffset(); + } + + /** + * Skips the next {@code length} bytes. Must only be called when the position is byte aligned. + * + * @param length The number of bytes to read. + * @throws IllegalStateException If the position isn't byte aligned. + */ + public void skipBytes(int length) { + Assertions.checkState(bitOffset == 0); + byteOffset += length; + assertValidOffset(); + } + + private void assertValidOffset() { + // It is fine for position to be at the end of the array, but no further. + Assertions.checkState(byteOffset >= 0 + && (bitOffset >= 0 && bitOffset < 8) + && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableByteArray.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableByteArray.java new file mode 100644 index 0000000..35a6556 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableByteArray.java @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * Wraps a byte array, providing a set of methods for parsing data from it. Numerical values are + * parsed with the assumption that their constituent bytes are in big endian order. + */ +public final class ParsableByteArray { + + public byte[] data; + + private int position; + private int limit; + + /** + * Creates a new instance that initially has no backing data. + */ + public ParsableByteArray() {} + + /** + * Creates a new instance with {@code limit} bytes and sets the limit. + * + * @param limit The limit to set. + */ + public ParsableByteArray(int limit) { + this.data = new byte[limit]; + this.limit = limit; + } + + /** + * Creates a new instance wrapping {@code data}, and sets the limit to {@code data.length}. + * + * @param data The array to wrap. + */ + public ParsableByteArray(byte[] data) { + this.data = data; + limit = data.length; + } + + /** + * Creates a new instance that wraps an existing array. + * + * @param data The data to wrap. + * @param limit The limit to set. + */ + public ParsableByteArray(byte[] data, int limit) { + this.data = data; + this.limit = limit; + } + + /** + * Resets the position to zero and the limit to the specified value. If the limit exceeds the + * capacity, {@code data} is replaced with a new array of sufficient size. + * + * @param limit The limit to set. + */ + public void reset(int limit) { + reset(capacity() < limit ? new byte[limit] : data, limit); + } + + /** + * Updates the instance to wrap {@code data}, and resets the position to zero. + * + * @param data The array to wrap. + * @param limit The limit to set. + */ + public void reset(byte[] data, int limit) { + this.data = data; + this.limit = limit; + position = 0; + } + + /** + * Sets the position and limit to zero. + */ + public void reset() { + position = 0; + limit = 0; + } + + /** + * Returns the number of bytes yet to be read. + */ + public int bytesLeft() { + return limit - position; + } + + /** + * Returns the limit. + */ + public int limit() { + return limit; + } + + /** + * Sets the limit. + * + * @param limit The limit to set. + */ + public void setLimit(int limit) { + Assertions.checkArgument(limit >= 0 && limit <= data.length); + this.limit = limit; + } + + /** + * Returns the current offset in the array, in bytes. + */ + public int getPosition() { + return position; + } + + /** + * Returns the capacity of the array, which may be larger than the limit. + */ + public int capacity() { + return data == null ? 0 : data.length; + } + + /** + * Sets the reading offset in the array. + * + * @param position Byte offset in the array from which to read. + * @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the + * array. + */ + public void setPosition(int position) { + // It is fine for position to be at the end of the array. + Assertions.checkArgument(position >= 0 && position <= limit); + this.position = position; + } + + /** + * Moves the reading offset by {@code bytes}. + * + * @param bytes The number of bytes to skip. + * @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the + * array. + */ + public void skipBytes(int bytes) { + setPosition(position + bytes); + } + + /** + * Reads the next {@code length} bytes into {@code bitArray}, and resets the position of + * {@code bitArray} to zero. + * + * @param bitArray The {@link ParsableBitArray} into which the bytes should be read. + * @param length The number of bytes to write. + */ + public void readBytes(ParsableBitArray bitArray, int length) { + readBytes(bitArray.data, 0, length); + bitArray.setPosition(0); + } + + /** + * Reads the next {@code length} bytes into {@code buffer} at {@code offset}. + * + * @see System#arraycopy(Object, int, Object, int, int) + * @param buffer The array into which the read data should be written. + * @param offset The offset in {@code buffer} at which the read data should be written. + * @param length The number of bytes to read. + */ + public void readBytes(byte[] buffer, int offset, int length) { + System.arraycopy(data, position, buffer, offset, length); + position += length; + } + + /** + * Reads the next {@code length} bytes into {@code buffer}. + * + * @see ByteBuffer#put(byte[], int, int) + * @param buffer The {@link ByteBuffer} into which the read data should be written. + * @param length The number of bytes to read. + */ + public void readBytes(ByteBuffer buffer, int length) { + buffer.put(data, position, length); + position += length; + } + + /** + * Peeks at the next byte as an unsigned value. + */ + public int peekUnsignedByte() { + return (data[position] & 0xFF); + } + + /** + * Peeks at the next char. + */ + public char peekChar() { + return (char) ((data[position] & 0xFF) << 8 + | (data[position + 1] & 0xFF)); + } + + /** + * Reads the next byte as an unsigned value. + */ + public int readUnsignedByte() { + return (data[position++] & 0xFF); + } + + /** + * Reads the next two bytes as an unsigned value. + */ + public int readUnsignedShort() { + return (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF); + } + + /** + * Reads the next two bytes as an unsigned value. + */ + public int readLittleEndianUnsignedShort() { + return (data[position++] & 0xFF) | (data[position++] & 0xFF) << 8; + } + + /** + * Reads the next two bytes as an signed value. + */ + public short readShort() { + return (short) ((data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF)); + } + + /** + * Reads the next two bytes as a signed value. + */ + public short readLittleEndianShort() { + return (short) ((data[position++] & 0xFF) | (data[position++] & 0xFF) << 8); + } + + /** + * Reads the next three bytes as an unsigned value. + */ + public int readUnsignedInt24() { + return (data[position++] & 0xFF) << 16 + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF); + } + + /** + * Reads the next three bytes as a signed value in little endian order. + */ + public int readLittleEndianInt24() { + return (data[position++] & 0xFF) + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF) << 16; + } + + /** + * Reads the next three bytes as an unsigned value in little endian order. + */ + public int readLittleEndianUnsignedInt24() { + return (data[position++] & 0xFF) + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF) << 16; + } + + /** + * Reads the next four bytes as an unsigned value. + */ + public long readUnsignedInt() { + return (data[position++] & 0xFFL) << 24 + | (data[position++] & 0xFFL) << 16 + | (data[position++] & 0xFFL) << 8 + | (data[position++] & 0xFFL); + } + + /** + * Reads the next four bytes as an unsigned value in little endian order. + */ + public long readLittleEndianUnsignedInt() { + return (data[position++] & 0xFFL) + | (data[position++] & 0xFFL) << 8 + | (data[position++] & 0xFFL) << 16 + | (data[position++] & 0xFFL) << 24; + } + + /** + * Reads the next four bytes as a signed value + */ + public int readInt() { + return (data[position++] & 0xFF) << 24 + | (data[position++] & 0xFF) << 16 + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF); + } + + /** + * Reads the next four bytes as an signed value in little endian order. + */ + public int readLittleEndianInt() { + return (data[position++] & 0xFF) + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF) << 16 + | (data[position++] & 0xFF) << 24; + } + + /** + * Reads the next eight bytes as a signed value. + */ + public long readLong() { + return (data[position++] & 0xFFL) << 56 + | (data[position++] & 0xFFL) << 48 + | (data[position++] & 0xFFL) << 40 + | (data[position++] & 0xFFL) << 32 + | (data[position++] & 0xFFL) << 24 + | (data[position++] & 0xFFL) << 16 + | (data[position++] & 0xFFL) << 8 + | (data[position++] & 0xFFL); + } + + /** + * Reads the next eight bytes as a signed value in little endian order. + */ + public long readLittleEndianLong() { + return (data[position++] & 0xFFL) + | (data[position++] & 0xFFL) << 8 + | (data[position++] & 0xFFL) << 16 + | (data[position++] & 0xFFL) << 24 + | (data[position++] & 0xFFL) << 32 + | (data[position++] & 0xFFL) << 40 + | (data[position++] & 0xFFL) << 48 + | (data[position++] & 0xFFL) << 56; + } + + /** + * Reads the next four bytes, returning the integer portion of the fixed point 16.16 integer. + */ + public int readUnsignedFixedPoint1616() { + int result = (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF); + position += 2; // Skip the non-integer portion. + return result; + } + + /** + * Reads a Synchsafe integer. + *

    + * Synchsafe integers keep the highest bit of every byte zeroed. A 32 bit synchsafe integer can + * store 28 bits of information. + * + * @return The parsed value. + */ + public int readSynchSafeInt() { + int b1 = readUnsignedByte(); + int b2 = readUnsignedByte(); + int b3 = readUnsignedByte(); + int b4 = readUnsignedByte(); + return (b1 << 21) | (b2 << 14) | (b3 << 7) | b4; + } + + /** + * Reads the next four bytes as an unsigned integer into an integer, if the top bit is a zero. + * + * @throws IllegalStateException Thrown if the top bit of the input data is set. + */ + public int readUnsignedIntToInt() { + int result = readInt(); + if (result < 0) { + throw new IllegalStateException("Top bit not zero: " + result); + } + return result; + } + + /** + * Reads the next four bytes as a little endian unsigned integer into an integer, if the top bit + * is a zero. + * + * @throws IllegalStateException Thrown if the top bit of the input data is set. + */ + public int readLittleEndianUnsignedIntToInt() { + int result = readLittleEndianInt(); + if (result < 0) { + throw new IllegalStateException("Top bit not zero: " + result); + } + return result; + } + + /** + * Reads the next eight bytes as an unsigned long into a long, if the top bit is a zero. + * + * @throws IllegalStateException Thrown if the top bit of the input data is set. + */ + public long readUnsignedLongToLong() { + long result = readLong(); + if (result < 0) { + throw new IllegalStateException("Top bit not zero: " + result); + } + return result; + } + + /** + * Reads the next four bytes as a 32-bit floating point value. + */ + public float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads the next eight bytes as a 64-bit floating point value. + */ + public double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + /** + * Reads the next {@code length} bytes as UTF-8 characters. + * + * @param length The number of bytes to read. + * @return The string encoded by the bytes. + */ + public String readString(int length) { + return readString(length, Charset.defaultCharset()); + } + + /** + * Reads the next {@code length} bytes as characters in the specified {@link Charset}. + * + * @param length The number of bytes to read. + * @param charset The character set of the encoded characters. + * @return The string encoded by the bytes in the specified character set. + */ + public String readString(int length, Charset charset) { + String result = new String(data, position, length, charset); + position += length; + return result; + } + + /** + * Reads the next {@code length} bytes as UTF-8 characters. A terminating NUL byte is discarded, + * if present. + * + * @param length The number of bytes to read. + * @return The string, not including any terminating NUL byte. + */ + public String readNullTerminatedString(int length) { + if (length == 0) { + return ""; + } + int stringLength = length; + int lastIndex = position + length - 1; + if (lastIndex < limit && data[lastIndex] == 0) { + stringLength--; + } + String result = new String(data, position, stringLength); + position += length; + return result; + } + + /** + * Reads up to the next NUL byte (or the limit) as UTF-8 characters. + * + * @return The string not including any terminating NUL byte, or null if the end of the data has + * already been reached. + */ + public String readNullTerminatedString() { + if (bytesLeft() == 0) { + return null; + } + int stringLimit = position; + while (stringLimit < limit && data[stringLimit] != 0) { + stringLimit++; + } + String string = new String(data, position, stringLimit - position); + position = stringLimit; + if (position < limit) { + position++; + } + return string; + } + + /** + * Reads a line of text. + *

    + * A line is considered to be terminated by any one of a carriage return ('\r'), a line feed + * ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). The system's default + * charset (UTF-8) is used. + * + * @return The line not including any line-termination characters, or null if the end of the data + * has already been reached. + */ + public String readLine() { + if (bytesLeft() == 0) { + return null; + } + int lineLimit = position; + while (lineLimit < limit && !Util.isLinebreak(data[lineLimit])) { + lineLimit++; + } + if (lineLimit - position >= 3 && data[position] == (byte) 0xEF + && data[position + 1] == (byte) 0xBB && data[position + 2] == (byte) 0xBF) { + // There's a byte order mark at the start of the line. Discard it. + position += 3; + } + String line = new String(data, position, lineLimit - position); + position = lineLimit; + if (position == limit) { + return line; + } + if (data[position] == '\r') { + position++; + if (position == limit) { + return line; + } + } + if (data[position] == '\n') { + position++; + } + return line; + } + + /** + * Reads a long value encoded by UTF-8 encoding + * + * @throws NumberFormatException if there is a problem with decoding + * @return Decoded long value + */ + public long readUtf8EncodedLong() { + int length = 0; + long value = data[position]; + // find the high most 0 bit + for (int j = 7; j >= 0; j--) { + if ((value & (1 << j)) == 0) { + if (j < 6) { + value &= (1 << j) - 1; + length = 7 - j; + } else if (j == 7) { + length = 1; + } + break; + } + } + if (length == 0) { + throw new NumberFormatException("Invalid UTF-8 sequence first byte: " + value); + } + for (int i = 1; i < length; i++) { + int x = data[position + i]; + if ((x & 0xC0) != 0x80) { // if the high most 0 bit not 7th + throw new NumberFormatException("Invalid UTF-8 sequence continuation byte: " + value); + } + value = (value << 6) | (x & 0x3F); + } + position += length; + return value; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableNalUnitBitArray.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableNalUnitBitArray.java new file mode 100644 index 0000000..a1311f0 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ParsableNalUnitBitArray.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +/** + * Wraps a byte array, providing methods that allow it to be read as a NAL unit bitstream. + *

    + * Whenever the byte sequence [0, 0, 3] appears in the wrapped byte array, it is treated as [0, 0] + * for all reading/skipping operations, which makes the bitstream appear to be unescaped. + */ +public final class ParsableNalUnitBitArray { + + private byte[] data; + private int byteLimit; + + // The byte offset is never equal to the offset of the 3rd byte in a subsequence [0, 0, 3]. + private int byteOffset; + private int bitOffset; + + /** + * @param data The data to wrap. + * @param offset The byte offset in {@code data} to start reading from. + * @param limit The byte offset of the end of the bitstream in {@code data}. + */ + public ParsableNalUnitBitArray(byte[] data, int offset, int limit) { + reset(data, offset, limit); + } + + /** + * Resets the wrapped data, limit and offset. + * + * @param data The data to wrap. + * @param offset The byte offset in {@code data} to start reading from. + * @param limit The byte offset of the end of the bitstream in {@code data}. + */ + public void reset(byte[] data, int offset, int limit) { + this.data = data; + byteOffset = offset; + byteLimit = limit; + bitOffset = 0; + assertValidOffset(); + } + + /** + * Skips bits and moves current reading position forward. + * + * @param n The number of bits to skip. + */ + public void skipBits(int n) { + int oldByteOffset = byteOffset; + byteOffset += (n / 8); + bitOffset += (n % 8); + if (bitOffset > 7) { + byteOffset++; + bitOffset -= 8; + } + for (int i = oldByteOffset + 1; i <= byteOffset; i++) { + if (shouldSkipByte(i)) { + // Skip the byte and move forward to check three bytes ahead. + byteOffset++; + i += 2; + } + } + assertValidOffset(); + } + + /** + * Returns whether it's possible to read {@code n} bits starting from the current offset. The + * offset is not modified. + * + * @param n The number of bits. + * @return Whether it is possible to read {@code n} bits. + */ + public boolean canReadBits(int n) { + int oldByteOffset = byteOffset; + int newByteOffset = byteOffset + (n / 8); + int newBitOffset = bitOffset + (n % 8); + if (newBitOffset > 7) { + newByteOffset++; + newBitOffset -= 8; + } + for (int i = oldByteOffset + 1; i <= newByteOffset && newByteOffset < byteLimit; i++) { + if (shouldSkipByte(i)) { + // Skip the byte and move forward to check three bytes ahead. + newByteOffset++; + i += 2; + } + } + return newByteOffset < byteLimit || (newByteOffset == byteLimit && newBitOffset == 0); + } + + /** + * Reads a single bit. + * + * @return Whether the bit is set. + */ + public boolean readBit() { + return readBits(1) == 1; + } + + /** + * Reads up to 32 bits. + * + * @param numBits The number of bits to read. + * @return An integer whose bottom n bits hold the read data. + */ + public int readBits(int numBits) { + if (numBits == 0) { + return 0; + } + + int returnValue = 0; + + // Read as many whole bytes as we can. + int wholeBytes = (numBits / 8); + for (int i = 0; i < wholeBytes; i++) { + int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1; + int byteValue; + if (bitOffset != 0) { + byteValue = ((data[byteOffset] & 0xFF) << bitOffset) + | ((data[nextByteOffset] & 0xFF) >>> (8 - bitOffset)); + } else { + byteValue = data[byteOffset]; + } + numBits -= 8; + returnValue |= (byteValue & 0xFF) << numBits; + byteOffset = nextByteOffset; + } + + // Read any remaining bits. + if (numBits > 0) { + int nextBit = bitOffset + numBits; + byte writeMask = (byte) (0xFF >> (8 - numBits)); + int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1; + + if (nextBit > 8) { + // Combine bits from current byte and next byte. + returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8) + | ((data[nextByteOffset] & 0xFF) >> (16 - nextBit))) & writeMask)); + byteOffset = nextByteOffset; + } else { + // Bits to be read only within current byte. + returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask); + if (nextBit == 8) { + byteOffset = nextByteOffset; + } + } + + bitOffset = nextBit % 8; + } + + assertValidOffset(); + return returnValue; + } + + /** + * Returns whether it is possible to read an Exp-Golomb-coded integer starting from the current + * offset. The offset is not modified. + * + * @return Whether it is possible to read an Exp-Golomb-coded integer. + */ + public boolean canReadExpGolombCodedNum() { + int initialByteOffset = byteOffset; + int initialBitOffset = bitOffset; + int leadingZeros = 0; + while (byteOffset < byteLimit && !readBit()) { + leadingZeros++; + } + boolean hitLimit = byteOffset == byteLimit; + byteOffset = initialByteOffset; + bitOffset = initialBitOffset; + return !hitLimit && canReadBits(leadingZeros * 2 + 1); + } + + /** + * Reads an unsigned Exp-Golomb-coded format integer. + * + * @return The value of the parsed Exp-Golomb-coded integer. + */ + public int readUnsignedExpGolombCodedInt() { + return readExpGolombCodeNum(); + } + + /** + * Reads an signed Exp-Golomb-coded format integer. + * + * @return The value of the parsed Exp-Golomb-coded integer. + */ + public int readSignedExpGolombCodedInt() { + int codeNum = readExpGolombCodeNum(); + return ((codeNum % 2) == 0 ? -1 : 1) * ((codeNum + 1) / 2); + } + + private int readExpGolombCodeNum() { + int leadingZeros = 0; + while (!readBit()) { + leadingZeros++; + } + return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0); + } + + private boolean shouldSkipByte(int offset) { + return 2 <= offset && offset < byteLimit && data[offset] == (byte) 0x03 + && data[offset - 2] == (byte) 0x00 && data[offset - 1] == (byte) 0x00; + } + + private void assertValidOffset() { + // It is fine for position to be at the end of the array, but no further. + Assertions.checkState(byteOffset >= 0 + && (bitOffset >= 0 && bitOffset < 8) + && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Predicate.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Predicate.java new file mode 100644 index 0000000..73cf931 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Predicate.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +/** + * Determines a true of false value for a given input. + * + * @param The input type of the predicate. + */ +public interface Predicate { + + /** + * Evaluates an input. + * + * @param input The input to evaluate. + * @return The evaluated result. + */ + boolean evaluate(T input); + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/PriorityTaskManager.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/PriorityTaskManager.java new file mode 100644 index 0000000..f05a901 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/PriorityTaskManager.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import java.io.IOException; +import java.util.Collections; +import java.util.PriorityQueue; + +/** + * Allows tasks with associated priorities to control how they proceed relative to one another. + *

    + * A task should call {@link #add(int)} to register with the manager and {@link #remove(int)} to + * unregister. A registered task will prevent tasks of lower priority from proceeding, and should + * call {@link #proceed(int)}, {@link #proceedNonBlocking(int)} or {@link #proceedOrThrow(int)} each + * time it wishes to check whether it is itself allowed to proceed. + */ +public final class PriorityTaskManager { + + /** + * Thrown when task attempts to proceed when another registered task has a higher priority. + */ + public static class PriorityTooLowException extends IOException { + + public PriorityTooLowException(int priority, int highestPriority) { + super("Priority too low [priority=" + priority + ", highest=" + highestPriority + "]"); + } + + } + + private final Object lock = new Object(); + + // Guarded by lock. + private final PriorityQueue queue; + private int highestPriority; + + public PriorityTaskManager() { + queue = new PriorityQueue<>(10, Collections.reverseOrder()); + highestPriority = Integer.MIN_VALUE; + } + + /** + * Register a new task. The task must call {@link #remove(int)} when done. + * + * @param priority The priority of the task. Larger values indicate higher priorities. + */ + public void add(int priority) { + synchronized (lock) { + queue.add(priority); + highestPriority = Math.max(highestPriority, priority); + } + } + + /** + * Blocks until the task is allowed to proceed. + * + * @param priority The priority of the task. + * @throws InterruptedException If the thread is interrupted. + */ + public void proceed(int priority) throws InterruptedException { + synchronized (lock) { + while (highestPriority != priority) { + lock.wait(); + } + } + } + + /** + * A non-blocking variant of {@link #proceed(int)}. + * + * @param priority The priority of the task. + * @return Whether the task is allowed to proceed. + */ + public boolean proceedNonBlocking(int priority) { + synchronized (lock) { + return highestPriority == priority; + } + } + + /** + * A throwing variant of {@link #proceed(int)}. + * + * @param priority The priority of the task. + * @throws PriorityTooLowException If the task is not allowed to proceed. + */ + public void proceedOrThrow(int priority) throws PriorityTooLowException { + synchronized (lock) { + if (highestPriority != priority) { + throw new PriorityTooLowException(priority, highestPriority); + } + } + } + + /** + * Unregister a task. + * + * @param priority The priority of the task. + */ + public void remove(int priority) { + synchronized (lock) { + queue.remove(priority); + highestPriority = queue.isEmpty() ? Integer.MIN_VALUE : queue.peek(); + lock.notifyAll(); + } + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ReusableBufferedOutputStream.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ReusableBufferedOutputStream.java new file mode 100644 index 0000000..eda7533 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/ReusableBufferedOutputStream.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * This is a subclass of {@link BufferedOutputStream} with a {@link #reset(OutputStream)} method + * that allows an instance to be re-used with another underlying output stream. + */ +public final class ReusableBufferedOutputStream extends BufferedOutputStream { + + private boolean closed; + + public ReusableBufferedOutputStream(OutputStream out) { + super(out); + } + + public ReusableBufferedOutputStream(OutputStream out, int size) { + super(out, size); + } + + @Override + public void close() throws IOException { + closed = true; + + Throwable thrown = null; + try { + flush(); + } catch (Throwable e) { + thrown = e; + } + try { + out.close(); + } catch (Throwable e) { + if (thrown == null) { + thrown = e; + } + } + if (thrown != null) { + Util.sneakyThrow(thrown); + } + } + + /** + * Resets this stream and uses the given output stream for writing. This stream must be closed + * before resetting. + * + * @param out New output stream to be used for writing. + * @throws IllegalStateException If the stream isn't closed. + */ + public void reset(OutputStream out) { + Assertions.checkState(closed); + this.out = out; + count = 0; + closed = false; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/SlidingPercentile.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/SlidingPercentile.java new file mode 100644 index 0000000..bb16aa8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/SlidingPercentile.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Calculate any percentile over a sliding window of weighted values. A maximum weight is + * configured. Once the total weight of the values reaches the maximum weight, the oldest value is + * reduced in weight until it reaches zero and is removed. This maintains a constant total weight, + * equal to the maximum allowed, at the steady state. + *

    + * This class can be used for bandwidth estimation based on a sliding window of past transfer rate + * observations. This is an alternative to sliding mean and exponential averaging which suffer from + * susceptibility to outliers and slow adaptation to step functions. + * + * @see Wiki: Moving average + * @see Wiki: Selection algorithm + */ +public class SlidingPercentile { + + // Orderings. + private static final Comparator INDEX_COMPARATOR = new Comparator() { + @Override + public int compare(Sample a, Sample b) { + return a.index - b.index; + } + }; + + private static final Comparator VALUE_COMPARATOR = new Comparator() { + @Override + public int compare(Sample a, Sample b) { + return a.value < b.value ? -1 : b.value < a.value ? 1 : 0; + } + }; + + private static final int SORT_ORDER_NONE = -1; + private static final int SORT_ORDER_BY_VALUE = 0; + private static final int SORT_ORDER_BY_INDEX = 1; + + private static final int MAX_RECYCLED_SAMPLES = 5; + + private final int maxWeight; + private final ArrayList samples; + + private final Sample[] recycledSamples; + + private int currentSortOrder; + private int nextSampleIndex; + private int totalWeight; + private int recycledSampleCount; + + /** + * @param maxWeight The maximum weight. + */ + public SlidingPercentile(int maxWeight) { + this.maxWeight = maxWeight; + recycledSamples = new Sample[MAX_RECYCLED_SAMPLES]; + samples = new ArrayList<>(); + currentSortOrder = SORT_ORDER_NONE; + } + + /** + * Adds a new weighted value. + * + * @param weight The weight of the new observation. + * @param value The value of the new observation. + */ + public void addSample(int weight, float value) { + ensureSortedByIndex(); + + Sample newSample = recycledSampleCount > 0 ? recycledSamples[--recycledSampleCount] + : new Sample(); + newSample.index = nextSampleIndex++; + newSample.weight = weight; + newSample.value = value; + samples.add(newSample); + totalWeight += weight; + + while (totalWeight > maxWeight) { + int excessWeight = totalWeight - maxWeight; + Sample oldestSample = samples.get(0); + if (oldestSample.weight <= excessWeight) { + totalWeight -= oldestSample.weight; + samples.remove(0); + if (recycledSampleCount < MAX_RECYCLED_SAMPLES) { + recycledSamples[recycledSampleCount++] = oldestSample; + } + } else { + oldestSample.weight -= excessWeight; + totalWeight -= excessWeight; + } + } + } + + /** + * Computes a percentile by integration. + * + * @param percentile The desired percentile, expressed as a fraction in the range (0,1]. + * @return The requested percentile value or {@link Float#NaN} if no samples have been added. + */ + public float getPercentile(float percentile) { + ensureSortedByValue(); + float desiredWeight = percentile * totalWeight; + int accumulatedWeight = 0; + for (int i = 0; i < samples.size(); i++) { + Sample currentSample = samples.get(i); + accumulatedWeight += currentSample.weight; + if (accumulatedWeight >= desiredWeight) { + return currentSample.value; + } + } + // Clamp to maximum value or NaN if no values. + return samples.isEmpty() ? Float.NaN : samples.get(samples.size() - 1).value; + } + + /** + * Sorts the samples by index. + */ + private void ensureSortedByIndex() { + if (currentSortOrder != SORT_ORDER_BY_INDEX) { + Collections.sort(samples, INDEX_COMPARATOR); + currentSortOrder = SORT_ORDER_BY_INDEX; + } + } + + /** + * Sorts the samples by value. + */ + private void ensureSortedByValue() { + if (currentSortOrder != SORT_ORDER_BY_VALUE) { + Collections.sort(samples, VALUE_COMPARATOR); + currentSortOrder = SORT_ORDER_BY_VALUE; + } + } + + private static class Sample { + + public int index; + public int weight; + public float value; + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/StandaloneMediaClock.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/StandaloneMediaClock.java new file mode 100644 index 0000000..1fd73c1 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/StandaloneMediaClock.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.os.SystemClock; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.PlaybackParameters; + +/** + * A {@link MediaClock} whose position advances with real time based on the playback parameters when + * started. + */ +public final class StandaloneMediaClock implements MediaClock { + + private boolean started; + private long baseUs; + private long baseElapsedMs; + private PlaybackParameters playbackParameters; + + /** + * Creates a new standalone media clock. + */ + public StandaloneMediaClock() { + playbackParameters = PlaybackParameters.DEFAULT; + } + + /** + * Starts the clock. Does nothing if the clock is already started. + */ + public void start() { + if (!started) { + baseElapsedMs = SystemClock.elapsedRealtime(); + started = true; + } + } + + /** + * Stops the clock. Does nothing if the clock is already stopped. + */ + public void stop() { + if (started) { + setPositionUs(getPositionUs()); + started = false; + } + } + + /** + * Sets the clock's position. + * + * @param positionUs The position to set in microseconds. + */ + public void setPositionUs(long positionUs) { + baseUs = positionUs; + if (started) { + baseElapsedMs = SystemClock.elapsedRealtime(); + } + } + + /** + * Synchronizes this clock with the current state of {@code clock}. + * + * @param clock The clock with which to synchronize. + */ + public void synchronize(MediaClock clock) { + setPositionUs(clock.getPositionUs()); + playbackParameters = clock.getPlaybackParameters(); + } + + @Override + public long getPositionUs() { + long positionUs = baseUs; + if (started) { + long elapsedSinceBaseMs = SystemClock.elapsedRealtime() - baseElapsedMs; + if (playbackParameters.speed == 1f) { + positionUs += C.msToUs(elapsedSinceBaseMs); + } else { + positionUs += playbackParameters.getSpeedAdjustedDurationUs(elapsedSinceBaseMs); + } + } + return positionUs; + } + + @Override + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + // Store the current position as the new base, in case the playback speed has changed. + if (started) { + setPositionUs(getPositionUs()); + } + this.playbackParameters = playbackParameters; + return playbackParameters; + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return playbackParameters; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/SystemClock.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/SystemClock.java new file mode 100644 index 0000000..acdd63e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/SystemClock.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +/** + * The standard implementation of {@link Clock}. + */ +public final class SystemClock implements Clock { + + @Override + public long elapsedRealtime() { + return android.os.SystemClock.elapsedRealtime(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/TimestampAdjuster.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/TimestampAdjuster.java new file mode 100644 index 0000000..5a8d58c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/TimestampAdjuster.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +/** + * Offsets timestamps according to an initial sample timestamp offset. MPEG-2 TS timestamps scaling + * and adjustment is supported, taking into account timestamp rollover. + */ +public final class TimestampAdjuster { + + /** + * A special {@code firstSampleTimestampUs} value indicating that presentation timestamps should + * not be offset. + */ + public static final long DO_NOT_OFFSET = Long.MAX_VALUE; + + /** + * The value one greater than the largest representable (33 bit) MPEG-2 TS presentation timestamp. + */ + private static final long MAX_PTS_PLUS_ONE = 0x200000000L; + + private long firstSampleTimestampUs; + private long timestampOffsetUs; + + // Volatile to allow isInitialized to be called on a different thread to adjustSampleTimestamp. + private volatile long lastSampleTimestamp; + + /** + * @param firstSampleTimestampUs See {@link #setFirstSampleTimestampUs(long)}. + */ + public TimestampAdjuster(long firstSampleTimestampUs) { + lastSampleTimestamp = C.TIME_UNSET; + setFirstSampleTimestampUs(firstSampleTimestampUs); + } + + /** + * Sets the desired result of the first call to {@link #adjustSampleTimestamp(long)}. Can only be + * called before any timestamps have been adjusted. + * + * @param firstSampleTimestampUs The first adjusted sample timestamp in microseconds, or + * {@link #DO_NOT_OFFSET} if presentation timestamps should not be offset. + */ + public synchronized void setFirstSampleTimestampUs(long firstSampleTimestampUs) { + Assertions.checkState(lastSampleTimestamp == C.TIME_UNSET); + this.firstSampleTimestampUs = firstSampleTimestampUs; + } + + /** + * Returns the first adjusted sample timestamp in microseconds. + * + * @return The first adjusted sample timestamp in microseconds. + */ + public long getFirstSampleTimestampUs() { + return firstSampleTimestampUs; + } + + /** + * Returns the last adjusted timestamp. If no timestamp has been adjusted, returns + * {@code firstSampleTimestampUs} as provided to the constructor. If this value is + * {@link #DO_NOT_OFFSET}, returns {@link C#TIME_UNSET}. + * + * @return The last adjusted timestamp. If not present, {@code firstSampleTimestampUs} is + * returned unless equal to {@link #DO_NOT_OFFSET}, in which case {@link C#TIME_UNSET} is + * returned. + */ + public long getLastAdjustedTimestampUs() { + return lastSampleTimestamp != C.TIME_UNSET ? lastSampleTimestamp + : firstSampleTimestampUs != DO_NOT_OFFSET ? firstSampleTimestampUs : C.TIME_UNSET; + } + + /** + * Returns the offset between the input of {@link #adjustSampleTimestamp(long)} and its output. + * If {@link #DO_NOT_OFFSET} was provided to the constructor, 0 is returned. If the timestamp + * adjuster is yet not initialized, {@link C#TIME_UNSET} is returned. + * + * @return The offset between {@link #adjustSampleTimestamp(long)}'s input and output. + * {@link C#TIME_UNSET} if the adjuster is not yet initialized and 0 if timestamps should not + * be offset. + */ + public long getTimestampOffsetUs() { + return firstSampleTimestampUs == DO_NOT_OFFSET ? 0 + : lastSampleTimestamp == C.TIME_UNSET ? C.TIME_UNSET : timestampOffsetUs; + } + + /** + * Resets the instance to its initial state. + */ + public void reset() { + lastSampleTimestamp = C.TIME_UNSET; + } + + /** + * Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound. + * + * @param pts The MPEG-2 TS presentation timestamp. + * @return The adjusted timestamp in microseconds. + */ + public long adjustTsTimestamp(long pts) { + if (pts == C.TIME_UNSET) { + return C.TIME_UNSET; + } + if (lastSampleTimestamp != C.TIME_UNSET) { + // The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1), + // and we need to snap to the one closest to lastSampleTimestamp. + long lastPts = usToPts(lastSampleTimestamp); + long closestWrapCount = (lastPts + (MAX_PTS_PLUS_ONE / 2)) / MAX_PTS_PLUS_ONE; + long ptsWrapBelow = pts + (MAX_PTS_PLUS_ONE * (closestWrapCount - 1)); + long ptsWrapAbove = pts + (MAX_PTS_PLUS_ONE * closestWrapCount); + pts = Math.abs(ptsWrapBelow - lastPts) < Math.abs(ptsWrapAbove - lastPts) + ? ptsWrapBelow : ptsWrapAbove; + } + return adjustSampleTimestamp(ptsToUs(pts)); + } + + /** + * Offsets a sample timestamp in microseconds. + * + * @param timeUs The timestamp of a sample to adjust. + * @return The adjusted timestamp in microseconds. + */ + public long adjustSampleTimestamp(long timeUs) { + if (timeUs == C.TIME_UNSET) { + return C.TIME_UNSET; + } + // Record the adjusted PTS to adjust for wraparound next time. + if (lastSampleTimestamp != C.TIME_UNSET) { + lastSampleTimestamp = timeUs; + } else { + if (firstSampleTimestampUs != DO_NOT_OFFSET) { + // Calculate the timestamp offset. + timestampOffsetUs = firstSampleTimestampUs - timeUs; + } + synchronized (this) { + lastSampleTimestamp = timeUs; + // Notify threads waiting for this adjuster to be initialized. + notifyAll(); + } + } + return timeUs + timestampOffsetUs; + } + + /** + * Blocks the calling thread until this adjuster is initialized. + * + * @throws InterruptedException If the thread was interrupted. + */ + public synchronized void waitUntilInitialized() throws InterruptedException { + while (lastSampleTimestamp == C.TIME_UNSET) { + wait(); + } + } + + /** + * Converts a value in MPEG-2 timestamp units to the corresponding value in microseconds. + * + * @param pts A value in MPEG-2 timestamp units. + * @return The corresponding value in microseconds. + */ + public static long ptsToUs(long pts) { + return (pts * C.MICROS_PER_SECOND) / 90000; + } + + /** + * Converts a value in microseconds to the corresponding values in MPEG-2 timestamp units. + * + * @param us A value in microseconds. + * @return The corresponding value in MPEG-2 timestamp units. + */ + public static long usToPts(long us) { + return (us * 90000) / C.MICROS_PER_SECOND; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/TraceUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/TraceUtil.java new file mode 100644 index 0000000..45f937a --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/TraceUtil.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.annotation.TargetApi; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayerLibraryInfo; + +/** + * Calls through to {@link android.os.Trace} methods on supported API levels. + */ +public final class TraceUtil { + + private TraceUtil() {} + + /** + * Writes a trace message to indicate that a given section of code has begun. + * + * @see android.os.Trace#beginSection(String) + * @param sectionName The name of the code section to appear in the trace. This may be at most 127 + * Unicode code units long. + */ + public static void beginSection(String sectionName) { + if (ExoPlayerLibraryInfo.TRACE_ENABLED && Util.SDK_INT >= 18) { + beginSectionV18(sectionName); + } + } + + /** + * Writes a trace message to indicate that a given section of code has ended. + * + * @see android.os.Trace#endSection() + */ + public static void endSection() { + if (ExoPlayerLibraryInfo.TRACE_ENABLED && Util.SDK_INT >= 18) { + endSectionV18(); + } + } + + @TargetApi(18) + private static void beginSectionV18(String sectionName) { + android.os.Trace.beginSection(sectionName); + } + + @TargetApi(18) + private static void endSectionV18() { + android.os.Trace.endSection(); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/UriUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/UriUtil.java new file mode 100644 index 0000000..6d4bd37 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/UriUtil.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.net.Uri; +import android.text.TextUtils; + +/** + * Utility methods for manipulating URIs. + */ +public final class UriUtil { + + /** + * The length of arrays returned by {@link #getUriIndices(String)}. + */ + private static final int INDEX_COUNT = 4; + /** + * An index into an array returned by {@link #getUriIndices(String)}. + *

    + * The value at this position in the array is the index of the ':' after the scheme. Equals -1 if + * the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1), + * including when the URI has no scheme. + */ + private static final int SCHEME_COLON = 0; + /** + * An index into an array returned by {@link #getUriIndices(String)}. + *

    + * The value at this position in the array is the index of the path part. Equals (schemeColon + 1) + * if no authority part, (schemeColon + 3) if the authority part consists of just "//", and + * (query) if no path part. The characters starting at this index can be "//" only if the + * authority part is non-empty (in this case the double-slash means the first segment is empty). + */ + private static final int PATH = 1; + /** + * An index into an array returned by {@link #getUriIndices(String)}. + *

    + * The value at this position in the array is the index of the query part, including the '?' + * before the query. Equals fragment if no query part, and (fragment - 1) if the query part is a + * single '?' with no data. + */ + private static final int QUERY = 2; + /** + * An index into an array returned by {@link #getUriIndices(String)}. + *

    + * The value at this position in the array is the index of the fragment part, including the '#' + * before the fragment. Equal to the length of the URI if no fragment part, and (length - 1) if + * the fragment part is a single '#' with no data. + */ + private static final int FRAGMENT = 3; + + private UriUtil() {} + + /** + * Like {@link #resolve(String, String)}, but returns a {@link Uri} instead of a {@link String}. + * + * @param baseUri The base URI. + * @param referenceUri The reference URI to resolve. + */ + public static Uri resolveToUri(String baseUri, String referenceUri) { + return Uri.parse(resolve(baseUri, referenceUri)); + } + + /** + * Performs relative resolution of a {@code referenceUri} with respect to a {@code baseUri}. + *

    + * The resolution is performed as specified by RFC-3986. + * + * @param baseUri The base URI. + * @param referenceUri The reference URI to resolve. + */ + public static String resolve(String baseUri, String referenceUri) { + StringBuilder uri = new StringBuilder(); + + // Map null onto empty string, to make the following logic simpler. + baseUri = baseUri == null ? "" : baseUri; + referenceUri = referenceUri == null ? "" : referenceUri; + + int[] refIndices = getUriIndices(referenceUri); + if (refIndices[SCHEME_COLON] != -1) { + // The reference is absolute. The target Uri is the reference. + uri.append(referenceUri); + removeDotSegments(uri, refIndices[PATH], refIndices[QUERY]); + return uri.toString(); + } + + int[] baseIndices = getUriIndices(baseUri); + if (refIndices[FRAGMENT] == 0) { + // The reference is empty or contains just the fragment part, then the target Uri is the + // concatenation of the base Uri without its fragment, and the reference. + return uri.append(baseUri, 0, baseIndices[FRAGMENT]).append(referenceUri).toString(); + } + + if (refIndices[QUERY] == 0) { + // The reference starts with the query part. The target is the base up to (but excluding) the + // query, plus the reference. + return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString(); + } + + if (refIndices[PATH] != 0) { + // The reference has authority. The target is the base scheme plus the reference. + int baseLimit = baseIndices[SCHEME_COLON] + 1; + uri.append(baseUri, 0, baseLimit).append(referenceUri); + return removeDotSegments(uri, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY]); + } + + if (referenceUri.charAt(refIndices[PATH]) == '/') { + // The reference path is rooted. The target is the base scheme and authority (if any), plus + // the reference. + uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY]); + } + + // The target Uri is the concatenation of the base Uri up to (but excluding) the last segment, + // and the reference. This can be split into 2 cases: + if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH] + && baseIndices[PATH] == baseIndices[QUERY]) { + // Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is + // needed after the authority, before appending the reference. + uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] + 1); + } else { + // Case 2: Otherwise, find the last '/' in the base hier-part and append the reference after + // it. If base hier-part has no '/', it could only mean that it is completely empty or + // contains only one segment, in which case the whole hier-part is excluded and the reference + // is appended right after the base scheme colon without an added '/'. + int lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1); + int baseLimit = lastSlashIndex == -1 ? baseIndices[PATH] : lastSlashIndex + 1; + uri.append(baseUri, 0, baseLimit).append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY]); + } + } + + /** + * Removes dot segments from the path of a URI. + * + * @param uri A {@link StringBuilder} containing the URI. + * @param offset The index of the start of the path in {@code uri}. + * @param limit The limit (exclusive) of the path in {@code uri}. + */ + private static String removeDotSegments(StringBuilder uri, int offset, int limit) { + if (offset >= limit) { + // Nothing to do. + return uri.toString(); + } + if (uri.charAt(offset) == '/') { + // If the path starts with a /, always retain it. + offset++; + } + // The first character of the current path segment. + int segmentStart = offset; + int i = offset; + while (i <= limit) { + int nextSegmentStart; + if (i == limit) { + nextSegmentStart = i; + } else if (uri.charAt(i) == '/') { + nextSegmentStart = i + 1; + } else { + i++; + continue; + } + // We've encountered the end of a segment or the end of the path. If the final segment was + // "." or "..", remove the appropriate segments of the path. + if (i == segmentStart + 1 && uri.charAt(segmentStart) == '.') { + // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". + uri.delete(segmentStart, nextSegmentStart); + limit -= nextSegmentStart - segmentStart; + i = segmentStart; + } else if (i == segmentStart + 2 && uri.charAt(segmentStart) == '.' + && uri.charAt(segmentStart + 1) == '.') { + // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". + int prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1; + int removeFrom = prevSegmentStart > offset ? prevSegmentStart : offset; + uri.delete(removeFrom, nextSegmentStart); + limit -= nextSegmentStart - removeFrom; + segmentStart = prevSegmentStart; + i = prevSegmentStart; + } else { + i++; + segmentStart = i; + } + } + return uri.toString(); + } + + /** + * Calculates indices of the constituent components of a URI. + * + * @param uriString The URI as a string. + * @return The corresponding indices. + */ + private static int[] getUriIndices(String uriString) { + int[] indices = new int[INDEX_COUNT]; + if (TextUtils.isEmpty(uriString)) { + indices[SCHEME_COLON] = -1; + return indices; + } + + // Determine outer structure from right to left. + // Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + int length = uriString.length(); + int fragmentIndex = uriString.indexOf('#'); + if (fragmentIndex == -1) { + fragmentIndex = length; + } + int queryIndex = uriString.indexOf('?'); + if (queryIndex == -1 || queryIndex > fragmentIndex) { + // '#' before '?': '?' is within the fragment. + queryIndex = fragmentIndex; + } + // Slashes are allowed only in hier-part so any colon after the first slash is part of the + // hier-part, not the scheme colon separator. + int schemeIndexLimit = uriString.indexOf('/'); + if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) { + schemeIndexLimit = queryIndex; + } + int schemeIndex = uriString.indexOf(':'); + if (schemeIndex > schemeIndexLimit) { + // '/' before ':' + schemeIndex = -1; + } + + // Determine hier-part structure: hier-part = "//" authority path / path + // This block can also cope with schemeIndex == -1. + boolean hasAuthority = schemeIndex + 2 < queryIndex + && uriString.charAt(schemeIndex + 1) == '/' + && uriString.charAt(schemeIndex + 2) == '/'; + int pathIndex; + if (hasAuthority) { + pathIndex = uriString.indexOf('/', schemeIndex + 3); // find first '/' after "://" + if (pathIndex == -1 || pathIndex > queryIndex) { + pathIndex = queryIndex; + } + } else { + pathIndex = schemeIndex + 1; + } + + indices[SCHEME_COLON] = schemeIndex; + indices[PATH] = pathIndex; + indices[QUERY] = queryIndex; + indices[FRAGMENT] = fragmentIndex; + return indices; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Util.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Util.java new file mode 100644 index 0000000..1d12e23 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/Util.java @@ -0,0 +1,1181 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import android.Manifest.permission; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Point; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; +import android.view.Display; +import android.view.WindowManager; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlayerLibraryInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Formatter; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Miscellaneous utility methods. + */ +public final class Util { + + /** + * Like {@link android.os.Build.VERSION#SDK_INT}, but in a place where it can be conveniently + * overridden for local testing. + */ + public static final int SDK_INT = + (Build.VERSION.SDK_INT == 25 && Build.VERSION.CODENAME.charAt(0) == 'O') ? 26 + : Build.VERSION.SDK_INT; + + /** + * Like {@link Build#DEVICE}, but in a place where it can be conveniently overridden for local + * testing. + */ + public static final String DEVICE = Build.DEVICE; + + /** + * Like {@link Build#MANUFACTURER}, but in a place where it can be conveniently overridden for + * local testing. + */ + public static final String MANUFACTURER = Build.MANUFACTURER; + + /** + * Like {@link Build#MODEL}, but in a place where it can be conveniently overridden for local + * testing. + */ + public static final String MODEL = Build.MODEL; + + /** + * A concise description of the device that it can be useful to log for debugging purposes. + */ + public static final String DEVICE_DEBUG_INFO = DEVICE + ", " + MODEL + ", " + MANUFACTURER + ", " + + SDK_INT; + + private static final String TAG = "Util"; + private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile( + "(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]" + + "(\\d\\d):(\\d\\d):(\\d\\d)([\\.,](\\d+))?" + + "([Zz]|((\\+|\\-)(\\d\\d):?(\\d\\d)))?"); + private static final Pattern XS_DURATION_PATTERN = + Pattern.compile("^(-)?P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?" + + "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"); + private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})"); + + private Util() {} + + /** + * Converts the entirety of an {@link InputStream} to a byte array. + * + * @param inputStream the {@link InputStream} to be read. The input stream is not closed by this + * method. + * @return a byte array containing all of the inputStream's bytes. + * @throws IOException if an error occurs reading from the stream. + */ + public static byte[] toByteArray(InputStream inputStream) throws IOException { + byte[] buffer = new byte[1024 * 4]; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return outputStream.toByteArray(); + } + + /** + * Checks whether it's necessary to request the {@link permission#READ_EXTERNAL_STORAGE} + * permission read the specified {@link Uri}s, requesting the permission if necessary. + * + * @param activity The host activity for checking and requesting the permission. + * @param uris {@link Uri}s that may require {@link permission#READ_EXTERNAL_STORAGE} to read. + * @return Whether a permission request was made. + */ + @TargetApi(23) + public static boolean maybeRequestReadExternalStoragePermission(Activity activity, Uri... uris) { + if (Util.SDK_INT < 23) { + return false; + } + for (Uri uri : uris) { + if (Util.isLocalFileUri(uri)) { + if (activity.checkSelfPermission(permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0); + return true; + } + break; + } + } + return false; + } + + /** + * Returns true if the URI is a path to a local file or a reference to a local file. + * + * @param uri The uri to test. + */ + public static boolean isLocalFileUri(Uri uri) { + String scheme = uri.getScheme(); + return TextUtils.isEmpty(scheme) || scheme.equals("file"); + } + + /** + * Tests two objects for {@link Object#equals(Object)} equality, handling the case where one or + * both may be null. + * + * @param o1 The first object. + * @param o2 The second object. + * @return {@code o1 == null ? o2 == null : o1.equals(o2)}. + */ + public static boolean areEqual(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + + /** + * Tests whether an {@code items} array contains an object equal to {@code item}, according to + * {@link Object#equals(Object)}. + *

    + * If {@code item} is null then true is returned if and only if {@code items} contains null. + * + * @param items The array of items to search. + * @param item The item to search for. + * @return True if the array contains an object equal to the item being searched for. + */ + public static boolean contains(Object[] items, Object item) { + for (Object arrayItem : items) { + if (Util.areEqual(arrayItem, item)) { + return true; + } + } + return false; + } + + /** + * Instantiates a new single threaded executor whose thread has the specified name. + * + * @param threadName The name of the thread. + * @return The executor. + */ + public static ExecutorService newSingleThreadExecutor(final String threadName) { + return Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(@NonNull Runnable r) { + return new Thread(r, threadName); + } + }); + } + + /** + * Closes a {@link DataSource}, suppressing any {@link IOException} that may occur. + * + * @param dataSource The {@link DataSource} to close. + */ + public static void closeQuietly(DataSource dataSource) { + try { + if (dataSource != null) { + dataSource.close(); + } + } catch (IOException e) { + // Ignore. + e.getStackTrace(); + } + } + + /** + * Closes a {@link Closeable}, suppressing any {@link IOException} that may occur. Both {@link + * java.io.OutputStream} and {@link InputStream} are {@code Closeable}. + * + * @param closeable The {@link Closeable} to close. + */ + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException e) { + // Ignore. + e.getStackTrace(); + } + } + + /** + * Returns a normalized RFC 5646 language code. + * + * @param language A possibly non-normalized RFC 5646 language code. + * @return The normalized code, or null if the input was null. + */ + public static String normalizeLanguageCode(String language) { + return language == null ? null : new Locale(language).getLanguage(); + } + + /** + * Returns a new byte array containing the code points of a {@link String} encoded using UTF-8. + * + * @param value The {@link String} whose bytes should be obtained. + * @return The code points encoding using UTF-8. + */ + public static byte[] getUtf8Bytes(String value) { + return value.getBytes(Charset.defaultCharset()); // UTF-8 is the default on Android. + } + + /** + * Returns whether the given character is a carriage return ('\r') or a line feed ('\n'). + * + * @param c The character. + * @return Whether the given character is a linebreak. + */ + public static boolean isLinebreak(int c) { + return c == '\n' || c == '\r'; + } + + /** + * Converts text to lower case using {@link Locale#US}. + * + * @param text The text to convert. + * @return The lower case text, or null if {@code text} is null. + */ + public static String toLowerInvariant(String text) { + return text == null ? null : text.toLowerCase(Locale.US); + } + + /** + * Divides a {@code numerator} by a {@code denominator}, returning the ceiled result. + * + * @param numerator The numerator to divide. + * @param denominator The denominator to divide by. + * @return The ceiled result of the division. + */ + public static int ceilDivide(int numerator, int denominator) { + return (numerator + denominator - 1) / denominator; + } + + /** + * Divides a {@code numerator} by a {@code denominator}, returning the ceiled result. + * + * @param numerator The numerator to divide. + * @param denominator The denominator to divide by. + * @return The ceiled result of the division. + */ + public static long ceilDivide(long numerator, long denominator) { + return (numerator + denominator - 1) / denominator; + } + + /** + * Constrains a value to the specified bounds. + * + * @param value The value to constrain. + * @param min The lower bound. + * @param max The upper bound. + * @return The constrained value {@code Math.max(min, Math.min(value, max))}. + */ + public static int constrainValue(int value, int min, int max) { + return Math.max(min, Math.min(value, max)); + } + + /** + * Constrains a value to the specified bounds. + * + * @param value The value to constrain. + * @param min The lower bound. + * @param max The upper bound. + * @return The constrained value {@code Math.max(min, Math.min(value, max))}. + */ + public static long constrainValue(long value, long min, long max) { + return Math.max(min, Math.min(value, max)); + } + + /** + * Constrains a value to the specified bounds. + * + * @param value The value to constrain. + * @param min The lower bound. + * @param max The upper bound. + * @return The constrained value {@code Math.max(min, Math.min(value, max))}. + */ + public static float constrainValue(float value, float min, float max) { + return Math.max(min, Math.min(value, max)); + } + + /** + * Returns the index of the largest element in {@code array} that is less than (or optionally + * equal to) a specified {@code value}. + *

    + * The search is performed using a binary search algorithm, so the array must be sorted. If the + * array contains multiple elements equal to {@code value} and {@code inclusive} is true, the + * index of the first one will be returned. + * + * @param array The array to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the array, whether to return the corresponding + * index. If false then the returned index corresponds to the largest element strictly less + * than the value. + * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than + * the smallest element in the array. If false then -1 will be returned. + * @return The index of the largest element in {@code array} that is less than (or optionally + * equal to) {@code value}. + */ + public static int binarySearchFloor(int[] array, int value, boolean inclusive, + boolean stayInBounds) { + int index = Arrays.binarySearch(array, value); + if (index < 0) { + index = -(index + 2); + } else { + while ((--index) >= 0 && array[index] == value) {} + if (inclusive) { + index++; + } + } + return stayInBounds ? Math.max(0, index) : index; + } + + /** + * Returns the index of the largest element in {@code array} that is less than (or optionally + * equal to) a specified {@code value}. + *

    + * The search is performed using a binary search algorithm, so the array must be sorted. If the + * array contains multiple elements equal to {@code value} and {@code inclusive} is true, the + * index of the first one will be returned. + * + * @param array The array to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the array, whether to return the corresponding + * index. If false then the returned index corresponds to the largest element strictly less + * than the value. + * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than + * the smallest element in the array. If false then -1 will be returned. + * @return The index of the largest element in {@code array} that is less than (or optionally + * equal to) {@code value}. + */ + public static int binarySearchFloor(long[] array, long value, boolean inclusive, + boolean stayInBounds) { + int index = Arrays.binarySearch(array, value); + if (index < 0) { + index = -(index + 2); + } else { + while ((--index) >= 0 && array[index] == value) {} + if (inclusive) { + index++; + } + } + return stayInBounds ? Math.max(0, index) : index; + } + + /** + * Returns the index of the smallest element in {@code array} that is greater than (or optionally + * equal to) a specified {@code value}. + *

    + * The search is performed using a binary search algorithm, so the array must be sorted. If + * the array contains multiple elements equal to {@code value} and {@code inclusive} is true, the + * index of the last one will be returned. + * + * @param array The array to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the array, whether to return the corresponding + * index. If false then the returned index corresponds to the smallest element strictly + * greater than the value. + * @param stayInBounds If true, then {@code (a.length - 1)} will be returned in the case that the + * value is greater than the largest element in the array. If false then {@code a.length} will + * be returned. + * @return The index of the smallest element in {@code array} that is greater than (or optionally + * equal to) {@code value}. + */ + public static int binarySearchCeil(long[] array, long value, boolean inclusive, + boolean stayInBounds) { + int index = Arrays.binarySearch(array, value); + if (index < 0) { + index = ~index; + } else { + while ((++index) < array.length && array[index] == value) {} + if (inclusive) { + index--; + } + } + return stayInBounds ? Math.min(array.length - 1, index) : index; + } + + /** + * Returns the index of the largest element in {@code list} that is less than (or optionally equal + * to) a specified {@code value}. + *

    + * The search is performed using a binary search algorithm, so the list must be sorted. If the + * list contains multiple elements equal to {@code value} and {@code inclusive} is true, the + * index of the first one will be returned. + * + * @param The type of values being searched. + * @param list The list to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the list, whether to return the corresponding + * index. If false then the returned index corresponds to the largest element strictly less + * than the value. + * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than + * the smallest element in the list. If false then -1 will be returned. + * @return The index of the largest element in {@code list} that is less than (or optionally equal + * to) {@code value}. + */ + public static int binarySearchFloor(List> list, T value, + boolean inclusive, boolean stayInBounds) { + int index = Collections.binarySearch(list, value); + if (index < 0) { + index = -(index + 2); + } else { + while ((--index) >= 0 && list.get(index).compareTo(value) == 0) {} + if (inclusive) { + index++; + } + } + return stayInBounds ? Math.max(0, index) : index; + } + + /** + * Returns the index of the smallest element in {@code list} that is greater than (or optionally + * equal to) a specified value. + *

    + * The search is performed using a binary search algorithm, so the list must be sorted. If the + * list contains multiple elements equal to {@code value} and {@code inclusive} is true, the + * index of the last one will be returned. + * + * @param The type of values being searched. + * @param list The list to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the list, whether to return the corresponding + * index. If false then the returned index corresponds to the smallest element strictly + * greater than the value. + * @param stayInBounds If true, then {@code (list.size() - 1)} will be returned in the case that + * the value is greater than the largest element in the list. If false then + * {@code list.size()} will be returned. + * @return The index of the smallest element in {@code list} that is greater than (or optionally + * equal to) {@code value}. + */ + public static int binarySearchCeil(List> list, T value, + boolean inclusive, boolean stayInBounds) { + int index = Collections.binarySearch(list, value); + if (index < 0) { + index = ~index; + } else { + int listSize = list.size(); + while ((++index) < listSize && list.get(index).compareTo(value) == 0) {} + if (inclusive) { + index--; + } + } + return stayInBounds ? Math.min(list.size() - 1, index) : index; + } + + /** + * Parses an xs:duration attribute value, returning the parsed duration in milliseconds. + * + * @param value The attribute value to decode. + * @return The parsed duration in milliseconds. + */ + public static long parseXsDuration(String value) { + Matcher matcher = XS_DURATION_PATTERN.matcher(value); + if (matcher.matches()) { + boolean negated = !TextUtils.isEmpty(matcher.group(1)); + // Durations containing years and months aren't completely defined. We assume there are + // 30.4368 days in a month, and 365.242 days in a year. + String years = matcher.group(3); + double durationSeconds = (years != null) ? Double.parseDouble(years) * 31556908 : 0; + String months = matcher.group(5); + durationSeconds += (months != null) ? Double.parseDouble(months) * 2629739 : 0; + String days = matcher.group(7); + durationSeconds += (days != null) ? Double.parseDouble(days) * 86400 : 0; + String hours = matcher.group(10); + durationSeconds += (hours != null) ? Double.parseDouble(hours) * 3600 : 0; + String minutes = matcher.group(12); + durationSeconds += (minutes != null) ? Double.parseDouble(minutes) * 60 : 0; + String seconds = matcher.group(14); + durationSeconds += (seconds != null) ? Double.parseDouble(seconds) : 0; + long durationMillis = (long) (durationSeconds * 1000); + return negated ? -durationMillis : durationMillis; + } else { + return (long) (Double.parseDouble(value) * 3600 * 1000); + } + } + + /** + * Parses an xs:dateTime attribute value, returning the parsed timestamp in milliseconds since + * the epoch. + * + * @param value The attribute value to decode. + * @return The parsed timestamp in milliseconds since the epoch. + * @throws ParserException if an error occurs parsing the dateTime attribute value. + */ + public static long parseXsDateTime(String value) throws ParserException { + Matcher matcher = XS_DATE_TIME_PATTERN.matcher(value); + if (!matcher.matches()) { + throw new ParserException("Invalid date/time format: " + value); + } + + int timezoneShift; + if (matcher.group(9) == null) { + // No time zone specified. + timezoneShift = 0; + } else if (matcher.group(9).equalsIgnoreCase("Z")) { + timezoneShift = 0; + } else { + timezoneShift = ((Integer.parseInt(matcher.group(12)) * 60 + + Integer.parseInt(matcher.group(13)))); + if (matcher.group(11).equals("-")) { + timezoneShift *= -1; + } + } + + Calendar dateTime = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + + dateTime.clear(); + // Note: The month value is 0-based, hence the -1 on group(2) + dateTime.set(Integer.parseInt(matcher.group(1)), + Integer.parseInt(matcher.group(2)) - 1, + Integer.parseInt(matcher.group(3)), + Integer.parseInt(matcher.group(4)), + Integer.parseInt(matcher.group(5)), + Integer.parseInt(matcher.group(6))); + if (!TextUtils.isEmpty(matcher.group(8))) { + final BigDecimal bd = new BigDecimal("0." + matcher.group(8)); + // we care only for milliseconds, so movePointRight(3) + dateTime.set(Calendar.MILLISECOND, bd.movePointRight(3).intValue()); + } + + long time = dateTime.getTimeInMillis(); + if (timezoneShift != 0) { + time -= timezoneShift * 60000; + } + + return time; + } + + /** + * Scales a large timestamp. + *

    + * Logically, scaling consists of a multiplication followed by a division. The actual operations + * performed are designed to minimize the probability of overflow. + * + * @param timestamp The timestamp to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + * @return The scaled timestamp. + */ + public static long scaleLargeTimestamp(long timestamp, long multiplier, long divisor) { + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = divisor / multiplier; + return timestamp / divisionFactor; + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = multiplier / divisor; + return timestamp * multiplicationFactor; + } else { + double multiplicationFactor = (double) multiplier / divisor; + return (long) (timestamp * multiplicationFactor); + } + } + + /** + * Applies {@link #scaleLargeTimestamp(long, long, long)} to a list of unscaled timestamps. + * + * @param timestamps The timestamps to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + * @return The scaled timestamps. + */ + public static long[] scaleLargeTimestamps(List timestamps, long multiplier, long divisor) { + long[] scaledTimestamps = new long[timestamps.size()]; + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = divisor / multiplier; + for (int i = 0; i < scaledTimestamps.length; i++) { + scaledTimestamps[i] = timestamps.get(i) / divisionFactor; + } + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = multiplier / divisor; + for (int i = 0; i < scaledTimestamps.length; i++) { + scaledTimestamps[i] = timestamps.get(i) * multiplicationFactor; + } + } else { + double multiplicationFactor = (double) multiplier / divisor; + for (int i = 0; i < scaledTimestamps.length; i++) { + scaledTimestamps[i] = (long) (timestamps.get(i) * multiplicationFactor); + } + } + return scaledTimestamps; + } + + /** + * Applies {@link #scaleLargeTimestamp(long, long, long)} to an array of unscaled timestamps. + * + * @param timestamps The timestamps to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + */ + public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) { + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = divisor / multiplier; + for (int i = 0; i < timestamps.length; i++) { + timestamps[i] /= divisionFactor; + } + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = multiplier / divisor; + for (int i = 0; i < timestamps.length; i++) { + timestamps[i] *= multiplicationFactor; + } + } else { + double multiplicationFactor = (double) multiplier / divisor; + for (int i = 0; i < timestamps.length; i++) { + timestamps[i] = (long) (timestamps[i] * multiplicationFactor); + } + } + } + + /** + * Converts a list of integers to a primitive array. + * + * @param list A list of integers. + * @return The list in array form, or null if the input list was null. + */ + public static int[] toArray(List list) { + if (list == null) { + return null; + } + int length = list.size(); + int[] intArray = new int[length]; + for (int i = 0; i < length; i++) { + intArray[i] = list.get(i); + } + return intArray; + } + + /** + * Given a {@link DataSpec} and a number of bytes already loaded, returns a {@link DataSpec} + * that represents the remainder of the data. + * + * @param dataSpec The original {@link DataSpec}. + * @param bytesLoaded The number of bytes already loaded. + * @return A {@link DataSpec} that represents the remainder of the data. + */ + public static DataSpec getRemainderDataSpec(DataSpec dataSpec, int bytesLoaded) { + if (bytesLoaded == 0) { + return dataSpec; + } else { + long remainingLength = dataSpec.length == C.LENGTH_UNSET ? C.LENGTH_UNSET + : dataSpec.length - bytesLoaded; + return new DataSpec(dataSpec.uri, dataSpec.position + bytesLoaded, remainingLength, + dataSpec.key, dataSpec.flags); + } + } + + /** + * Returns the integer equal to the big-endian concatenation of the characters in {@code string} + * as bytes. The string must be no more than four characters long. + * + * @param string A string no more than four characters long. + */ + public static int getIntegerCodeForString(String string) { + int length = string.length(); + Assertions.checkArgument(length <= 4); + int result = 0; + for (int i = 0; i < length; i++) { + result <<= 8; + result |= string.charAt(i); + } + return result; + } + + /** + * Returns a byte array containing values parsed from the hex string provided. + * + * @param hexString The hex string to convert to bytes. + * @return A byte array containing values parsed from the hex string provided. + */ + public static byte[] getBytesFromHexString(String hexString) { + byte[] data = new byte[hexString.length() / 2]; + for (int i = 0; i < data.length; i++) { + int stringOffset = i * 2; + data[i] = (byte) ((Character.digit(hexString.charAt(stringOffset), 16) << 4) + + Character.digit(hexString.charAt(stringOffset + 1), 16)); + } + return data; + } + + /** + * Returns a string with comma delimited simple names of each object's class. + * + * @param objects The objects whose simple class names should be comma delimited and returned. + * @return A string with comma delimited simple names of each object's class. + */ + public static String getCommaDelimitedSimpleClassNames(Object[] objects) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < objects.length; i++) { + stringBuilder.append(objects[i].getClass().getSimpleName()); + if (i < objects.length - 1) { + stringBuilder.append(", "); + } + } + return stringBuilder.toString(); + } + + /** + * Returns a user agent string based on the given application name and the library version. + * + * @param context A valid context of the calling application. + * @param applicationName String that will be prefix'ed to the generated user agent. + * @return A user agent string generated using the applicationName and the library version. + */ + public static String getUserAgent(Context context, String applicationName) { + String versionName; + try { + String packageName = context.getPackageName(); + PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); + versionName = info.versionName; + } catch (NameNotFoundException e) { + versionName = "?"; + } + return applicationName + "/" + versionName + " (Linux;Android " + Build.VERSION.RELEASE + + ") " + ExoPlayerLibraryInfo.VERSION_SLASHY; + } + + /** + * Converts a sample bit depth to a corresponding PCM encoding constant. + * + * @param bitDepth The bit depth. Supported values are 8, 16, 24 and 32. + * @return The corresponding encoding. One of {@link C#ENCODING_PCM_8BIT}, + * {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and + * {@link C#ENCODING_PCM_32BIT}. If the bit depth is unsupported then + * {@link C#ENCODING_INVALID} is returned. + */ + @C.PcmEncoding + public static int getPcmEncoding(int bitDepth) { + switch (bitDepth) { + case 8: + return C.ENCODING_PCM_8BIT; + case 16: + return C.ENCODING_PCM_16BIT; + case 24: + return C.ENCODING_PCM_24BIT; + case 32: + return C.ENCODING_PCM_32BIT; + default: + return C.ENCODING_INVALID; + } + } + + /** + * Returns the frame size for audio with {@code channelCount} channels in the specified encoding. + * + * @param pcmEncoding The encoding of the audio data. + * @param channelCount The channel count. + * @return The size of one audio frame in bytes. + */ + public static int getPcmFrameSize(@C.PcmEncoding int pcmEncoding, int channelCount) { + switch (pcmEncoding) { + case C.ENCODING_PCM_8BIT: + return channelCount; + case C.ENCODING_PCM_16BIT: + return channelCount * 2; + case C.ENCODING_PCM_24BIT: + return channelCount * 3; + case C.ENCODING_PCM_32BIT: + return channelCount * 4; + default: + throw new IllegalArgumentException(); + } + } + + /** + * Makes a best guess to infer the type from a {@link Uri}. + * + * @param uri The {@link Uri}. + * @return The content type. + */ + @C.ContentType + public static int inferContentType(Uri uri) { + String path = uri.getPath(); + return path == null ? C.TYPE_OTHER : inferContentType(path); + } + + /** + * Makes a best guess to infer the type from a file name. + * + * @param fileName Name of the file. It can include the path of the file. + * @return The content type. + */ + @C.ContentType + public static int inferContentType(String fileName) { + fileName = fileName.toLowerCase(); + if (fileName.endsWith(".mpd")) { + return C.TYPE_DASH; + } else if (fileName.endsWith(".m3u8")) { + return C.TYPE_HLS; + } else if (fileName.endsWith(".ism") || fileName.endsWith(".isml") + || fileName.endsWith(".ism/manifest") || fileName.endsWith(".isml/manifest")) { + return C.TYPE_SS; + } else { + return C.TYPE_OTHER; + } + } + + /** + * Returns the specified millisecond time formatted as a string. + * + * @param builder The builder that {@code formatter} will write to. + * @param formatter The formatter. + * @param timeMs The time to format as a string, in milliseconds. + * @return The time formatted as a string. + */ + public static String getStringForTime(StringBuilder builder, Formatter formatter, long timeMs) { + if (timeMs == C.TIME_UNSET) { + timeMs = 0; + } + long totalSeconds = (timeMs + 500) / 1000; + long seconds = totalSeconds % 60; + long minutes = (totalSeconds / 60) % 60; + long hours = totalSeconds / 3600; + builder.setLength(0); + return hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString() + : formatter.format("%02d:%02d", minutes, seconds).toString(); + } + + /** + * Maps a {@link C} {@code TRACK_TYPE_*} constant to the corresponding {@link C} + * {@code DEFAULT_*_BUFFER_SIZE} constant. + * + * @param trackType The track type. + * @return The corresponding default buffer size in bytes. + */ + public static int getDefaultBufferSize(int trackType) { + switch (trackType) { + case C.TRACK_TYPE_DEFAULT: + return C.DEFAULT_MUXED_BUFFER_SIZE; + case C.TRACK_TYPE_AUDIO: + return C.DEFAULT_AUDIO_BUFFER_SIZE; + case C.TRACK_TYPE_VIDEO: + return C.DEFAULT_VIDEO_BUFFER_SIZE; + case C.TRACK_TYPE_TEXT: + return C.DEFAULT_TEXT_BUFFER_SIZE; + case C.TRACK_TYPE_METADATA: + return C.DEFAULT_METADATA_BUFFER_SIZE; + default: + throw new IllegalStateException(); + } + } + + /** + * Escapes a string so that it's safe for use as a file or directory name on at least FAT32 + * filesystems. FAT32 is the most restrictive of all filesystems still commonly used today. + * + *

    For simplicity, this only handles common characters known to be illegal on FAT32: + * <, >, :, ", /, \, |, ?, and *. % is also escaped since it is used as the escape + * character. Escaping is performed in a consistent way so that no collisions occur and + * {@link #unescapeFileName(String)} can be used to retrieve the original file name. + * + * @param fileName File name to be escaped. + * @return An escaped file name which will be safe for use on at least FAT32 filesystems. + */ + public static String escapeFileName(String fileName) { + int length = fileName.length(); + int charactersToEscapeCount = 0; + for (int i = 0; i < length; i++) { + if (shouldEscapeCharacter(fileName.charAt(i))) { + charactersToEscapeCount++; + } + } + if (charactersToEscapeCount == 0) { + return fileName; + } + + int i = 0; + StringBuilder builder = new StringBuilder(length + charactersToEscapeCount * 2); + while (charactersToEscapeCount > 0) { + char c = fileName.charAt(i++); + if (shouldEscapeCharacter(c)) { + builder.append('%').append(Integer.toHexString(c)); + charactersToEscapeCount--; + } else { + builder.append(c); + } + } + if (i < length) { + builder.append(fileName, i, length); + } + return builder.toString(); + } + + private static boolean shouldEscapeCharacter(char c) { + switch (c) { + case '<': + case '>': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '?': + case '*': + case '%': + return true; + default: + return false; + } + } + + /** + * Unescapes an escaped file or directory name back to its original value. + * + *

    See {@link #escapeFileName(String)} for more information. + * + * @param fileName File name to be unescaped. + * @return The original value of the file name before it was escaped, or null if the escaped + * fileName seems invalid. + */ + public static String unescapeFileName(String fileName) { + int length = fileName.length(); + int percentCharacterCount = 0; + for (int i = 0; i < length; i++) { + if (fileName.charAt(i) == '%') { + percentCharacterCount++; + } + } + if (percentCharacterCount == 0) { + return fileName; + } + + int expectedLength = length - percentCharacterCount * 2; + StringBuilder builder = new StringBuilder(expectedLength); + Matcher matcher = ESCAPED_CHARACTER_PATTERN.matcher(fileName); + int endOfLastMatch = 0; + while (percentCharacterCount > 0 && matcher.find()) { + char unescapedCharacter = (char) Integer.parseInt(matcher.group(1), 16); + builder.append(fileName, endOfLastMatch, matcher.start()).append(unescapedCharacter); + endOfLastMatch = matcher.end(); + percentCharacterCount--; + } + if (endOfLastMatch < length) { + builder.append(fileName, endOfLastMatch, length); + } + if (builder.length() != expectedLength) { + return null; + } + return builder.toString(); + } + + /** + * A hacky method that always throws {@code t} even if {@code t} is a checked exception, + * and is not declared to be thrown. + */ + public static void sneakyThrow(Throwable t) { + Util.sneakyThrowInternal(t); + } + + @SuppressWarnings("unchecked") + private static void sneakyThrowInternal(Throwable t) throws T { + throw (T) t; + } + + /** Recursively deletes a directory and its content. */ + public static void recursiveDelete(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) { + for (File child : fileOrDirectory.listFiles()) { + recursiveDelete(child); + } + } + fileOrDirectory.delete(); + } + + /** Creates an empty directory in the directory returned by {@link Context#getCacheDir()}. */ + public static File createTempDirectory(Context context, String prefix) throws IOException { + File tempFile = File.createTempFile(prefix, null, context.getCacheDir()); + tempFile.delete(); // Delete the temp file. + tempFile.mkdir(); // Create a directory with the same name. + return tempFile; + } + + /** + * Returns the result of updating a CRC with the specified bytes in a "most significant bit first" + * order. + * + * @param bytes Array containing the bytes to update the crc value with. + * @param start The index to the first byte in the byte range to update the crc with. + * @param end The index after the last byte in the byte range to update the crc with. + * @param initialValue The initial value for the crc calculation. + * @return The result of updating the initial value with the specified bytes. + */ + public static int crc(byte[] bytes, int start, int end, int initialValue) { + for (int i = start; i < end; i++) { + initialValue = (initialValue << 8) + ^ CRC32_BYTES_MSBF[((initialValue >>> 24) ^ (bytes[i] & 0xFF)) & 0xFF]; + } + return initialValue; + } + + /** + * Gets the physical size of the default display, in pixels. + * + * @param context Any context. + * @return The physical display size, in pixels. + */ + public static Point getPhysicalDisplaySize(Context context) { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + return getPhysicalDisplaySize(context, windowManager.getDefaultDisplay()); + } + + /** + * Gets the physical size of the specified display, in pixels. + * + * @param context Any context. + * @param display The display whose size is to be returned. + * @return The physical display size, in pixels. + */ + public static Point getPhysicalDisplaySize(Context context, Display display) { + if (Util.SDK_INT < 25 && display.getDisplayId() == Display.DEFAULT_DISPLAY) { + // Before API 25 the Display object does not provide a working way to identify Android TVs + // that can show 4k resolution in a SurfaceView, so check for supported devices here. + if ("Sony".equals(Util.MANUFACTURER) && Util.MODEL.startsWith("BRAVIA") + && context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) { + return new Point(3840, 2160); + } else if ("NVIDIA".equals(Util.MANUFACTURER) && Util.MODEL.contains("SHIELD")) { + // Attempt to read sys.display-size. + String sysDisplaySize = null; + try { + Class systemProperties = Class.forName("android.os.SystemProperties"); + Method getMethod = systemProperties.getMethod("get", String.class); + sysDisplaySize = (String) getMethod.invoke(systemProperties, "sys.display-size"); + } catch (Exception e) { + Log.e(TAG, "Failed to read sys.display-size", e); + } + // If we managed to read sys.display-size, attempt to parse it. + if (!TextUtils.isEmpty(sysDisplaySize)) { + try { + String[] sysDisplaySizeParts = sysDisplaySize.trim().split("x"); + if (sysDisplaySizeParts.length == 2) { + int width = Integer.parseInt(sysDisplaySizeParts[0]); + int height = Integer.parseInt(sysDisplaySizeParts[1]); + if (width > 0 && height > 0) { + return new Point(width, height); + } + } + } catch (NumberFormatException e) { + // Do nothing. + } + Log.e(TAG, "Invalid sys.display-size: " + sysDisplaySize); + } + } + } + + Point displaySize = new Point(); + if (Util.SDK_INT >= 23) { + getDisplaySizeV23(display, displaySize); + } else if (Util.SDK_INT >= 17) { + getDisplaySizeV17(display, displaySize); + } else if (Util.SDK_INT >= 16) { + getDisplaySizeV16(display, displaySize); + } else { + getDisplaySizeV9(display, displaySize); + } + return displaySize; + } + + @TargetApi(23) + private static void getDisplaySizeV23(Display display, Point outSize) { + Display.Mode mode = display.getMode(); + outSize.x = mode.getPhysicalWidth(); + outSize.y = mode.getPhysicalHeight(); + } + + @TargetApi(17) + private static void getDisplaySizeV17(Display display, Point outSize) { + display.getRealSize(outSize); + } + + @TargetApi(16) + private static void getDisplaySizeV16(Display display, Point outSize) { + display.getSize(outSize); + } + + @SuppressWarnings("deprecation") + private static void getDisplaySizeV9(Display display, Point outSize) { + outSize.x = display.getWidth(); + outSize.y = display.getHeight(); + } + + /** + * Allows the CRC calculation to be done byte by byte instead of bit per bit being the order + * "most significant bit first". + */ + private static final int[] CRC32_BYTES_MSBF = { + 0X00000000, 0X04C11DB7, 0X09823B6E, 0X0D4326D9, 0X130476DC, 0X17C56B6B, 0X1A864DB2, + 0X1E475005, 0X2608EDB8, 0X22C9F00F, 0X2F8AD6D6, 0X2B4BCB61, 0X350C9B64, 0X31CD86D3, + 0X3C8EA00A, 0X384FBDBD, 0X4C11DB70, 0X48D0C6C7, 0X4593E01E, 0X4152FDA9, 0X5F15ADAC, + 0X5BD4B01B, 0X569796C2, 0X52568B75, 0X6A1936C8, 0X6ED82B7F, 0X639B0DA6, 0X675A1011, + 0X791D4014, 0X7DDC5DA3, 0X709F7B7A, 0X745E66CD, 0X9823B6E0, 0X9CE2AB57, 0X91A18D8E, + 0X95609039, 0X8B27C03C, 0X8FE6DD8B, 0X82A5FB52, 0X8664E6E5, 0XBE2B5B58, 0XBAEA46EF, + 0XB7A96036, 0XB3687D81, 0XAD2F2D84, 0XA9EE3033, 0XA4AD16EA, 0XA06C0B5D, 0XD4326D90, + 0XD0F37027, 0XDDB056FE, 0XD9714B49, 0XC7361B4C, 0XC3F706FB, 0XCEB42022, 0XCA753D95, + 0XF23A8028, 0XF6FB9D9F, 0XFBB8BB46, 0XFF79A6F1, 0XE13EF6F4, 0XE5FFEB43, 0XE8BCCD9A, + 0XEC7DD02D, 0X34867077, 0X30476DC0, 0X3D044B19, 0X39C556AE, 0X278206AB, 0X23431B1C, + 0X2E003DC5, 0X2AC12072, 0X128E9DCF, 0X164F8078, 0X1B0CA6A1, 0X1FCDBB16, 0X018AEB13, + 0X054BF6A4, 0X0808D07D, 0X0CC9CDCA, 0X7897AB07, 0X7C56B6B0, 0X71159069, 0X75D48DDE, + 0X6B93DDDB, 0X6F52C06C, 0X6211E6B5, 0X66D0FB02, 0X5E9F46BF, 0X5A5E5B08, 0X571D7DD1, + 0X53DC6066, 0X4D9B3063, 0X495A2DD4, 0X44190B0D, 0X40D816BA, 0XACA5C697, 0XA864DB20, + 0XA527FDF9, 0XA1E6E04E, 0XBFA1B04B, 0XBB60ADFC, 0XB6238B25, 0XB2E29692, 0X8AAD2B2F, + 0X8E6C3698, 0X832F1041, 0X87EE0DF6, 0X99A95DF3, 0X9D684044, 0X902B669D, 0X94EA7B2A, + 0XE0B41DE7, 0XE4750050, 0XE9362689, 0XEDF73B3E, 0XF3B06B3B, 0XF771768C, 0XFA325055, + 0XFEF34DE2, 0XC6BCF05F, 0XC27DEDE8, 0XCF3ECB31, 0XCBFFD686, 0XD5B88683, 0XD1799B34, + 0XDC3ABDED, 0XD8FBA05A, 0X690CE0EE, 0X6DCDFD59, 0X608EDB80, 0X644FC637, 0X7A089632, + 0X7EC98B85, 0X738AAD5C, 0X774BB0EB, 0X4F040D56, 0X4BC510E1, 0X46863638, 0X42472B8F, + 0X5C007B8A, 0X58C1663D, 0X558240E4, 0X51435D53, 0X251D3B9E, 0X21DC2629, 0X2C9F00F0, + 0X285E1D47, 0X36194D42, 0X32D850F5, 0X3F9B762C, 0X3B5A6B9B, 0X0315D626, 0X07D4CB91, + 0X0A97ED48, 0X0E56F0FF, 0X1011A0FA, 0X14D0BD4D, 0X19939B94, 0X1D528623, 0XF12F560E, + 0XF5EE4BB9, 0XF8AD6D60, 0XFC6C70D7, 0XE22B20D2, 0XE6EA3D65, 0XEBA91BBC, 0XEF68060B, + 0XD727BBB6, 0XD3E6A601, 0XDEA580D8, 0XDA649D6F, 0XC423CD6A, 0XC0E2D0DD, 0XCDA1F604, + 0XC960EBB3, 0XBD3E8D7E, 0XB9FF90C9, 0XB4BCB610, 0XB07DABA7, 0XAE3AFBA2, 0XAAFBE615, + 0XA7B8C0CC, 0XA379DD7B, 0X9B3660C6, 0X9FF77D71, 0X92B45BA8, 0X9675461F, 0X8832161A, + 0X8CF30BAD, 0X81B02D74, 0X857130C3, 0X5D8A9099, 0X594B8D2E, 0X5408ABF7, 0X50C9B640, + 0X4E8EE645, 0X4A4FFBF2, 0X470CDD2B, 0X43CDC09C, 0X7B827D21, 0X7F436096, 0X7200464F, + 0X76C15BF8, 0X68860BFD, 0X6C47164A, 0X61043093, 0X65C52D24, 0X119B4BE9, 0X155A565E, + 0X18197087, 0X1CD86D30, 0X029F3D35, 0X065E2082, 0X0B1D065B, 0X0FDC1BEC, 0X3793A651, + 0X3352BBE6, 0X3E119D3F, 0X3AD08088, 0X2497D08D, 0X2056CD3A, 0X2D15EBE3, 0X29D4F654, + 0XC5A92679, 0XC1683BCE, 0XCC2B1D17, 0XC8EA00A0, 0XD6AD50A5, 0XD26C4D12, 0XDF2F6BCB, + 0XDBEE767C, 0XE3A1CBC1, 0XE760D676, 0XEA23F0AF, 0XEEE2ED18, 0XF0A5BD1D, 0XF464A0AA, + 0XF9278673, 0XFDE69BC4, 0X89B8FD09, 0X8D79E0BE, 0X803AC667, 0X84FBDBD0, 0X9ABC8BD5, + 0X9E7D9662, 0X933EB0BB, 0X97FFAD0C, 0XAFB010B1, 0XAB710D06, 0XA6322BDF, 0XA2F33668, + 0XBCB4666D, 0XB8757BDA, 0XB5365D03, 0XB1F740B4 + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/XmlPullParserUtil.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/XmlPullParserUtil.java new file mode 100644 index 0000000..a072cb9 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/util/XmlPullParserUtil.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.util; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * {@link XmlPullParser} utility methods. + */ +public final class XmlPullParserUtil { + + private XmlPullParserUtil() {} + + /** + * Returns whether the current event is an end tag with the specified name. + * + * @param xpp The {@link XmlPullParser} to query. + * @param name The specified name. + * @return Whether the current event is an end tag with the specified name. + * @throws XmlPullParserException If an error occurs querying the parser. + */ + public static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException { + return isEndTag(xpp) && xpp.getName().equals(name); + } + + /** + * Returns whether the current event is an end tag. + * + * @param xpp The {@link XmlPullParser} to query. + * @return Whether the current event is an end tag. + * @throws XmlPullParserException If an error occurs querying the parser. + */ + public static boolean isEndTag(XmlPullParser xpp) throws XmlPullParserException { + return xpp.getEventType() == XmlPullParser.END_TAG; + } + + /** + * Returns whether the current event is a start tag with the specified name. + * + * @param xpp The {@link XmlPullParser} to query. + * @param name The specified name. + * @return Whether the current event is a start tag with the specified name. + * @throws XmlPullParserException If an error occurs querying the parser. + */ + public static boolean isStartTag(XmlPullParser xpp, String name) + throws XmlPullParserException { + return isStartTag(xpp) && xpp.getName().equals(name); + } + + /** + * Returns whether the current event is a start tag. + * + * @param xpp The {@link XmlPullParser} to query. + * @return Whether the current event is a start tag. + * @throws XmlPullParserException If an error occurs querying the parser. + */ + public static boolean isStartTag(XmlPullParser xpp) throws XmlPullParserException { + return xpp.getEventType() == XmlPullParser.START_TAG; + } + + /** + * Returns the value of an attribute of the current start tag. + * + * @param xpp The {@link XmlPullParser} to query. + * @param attributeName The name of the attribute. + * @return The value of the attribute, or null if the current event is not a start tag or if no + * no such attribute was found. + */ + public static String getAttributeValue(XmlPullParser xpp, String attributeName) { + int attributeCount = xpp.getAttributeCount(); + for (int i = 0; i < attributeCount; i++) { + if (attributeName.equals(xpp.getAttributeName(i))) { + return xpp.getAttributeValue(i); + } + } + return null; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/AvcConfig.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/AvcConfig.java new file mode 100644 index 0000000..7c6940f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/AvcConfig.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.video; + +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.CodecSpecificDataUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil.SpsData; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.List; + +/** + * AVC configuration data. + */ +public final class AvcConfig { + + public final List initializationData; + public final int nalUnitLengthFieldLength; + public final int width; + public final int height; + public final float pixelWidthAspectRatio; + + /** + * Parses AVC configuration data. + * + * @param data A {@link ParsableByteArray}, whose position is set to the start of the AVC + * configuration data to parse. + * @return A parsed representation of the HEVC configuration data. + * @throws ParserException If an error occurred parsing the data. + */ + public static AvcConfig parse(ParsableByteArray data) throws ParserException { + try { + data.skipBytes(4); // Skip to the AVCDecoderConfigurationRecord (defined in 14496-15) + int nalUnitLengthFieldLength = (data.readUnsignedByte() & 0x3) + 1; + if (nalUnitLengthFieldLength == 3) { + throw new IllegalStateException(); + } + List initializationData = new ArrayList<>(); + int numSequenceParameterSets = data.readUnsignedByte() & 0x1F; + for (int j = 0; j < numSequenceParameterSets; j++) { + initializationData.add(buildNalUnitForChild(data)); + } + int numPictureParameterSets = data.readUnsignedByte(); + for (int j = 0; j < numPictureParameterSets; j++) { + initializationData.add(buildNalUnitForChild(data)); + } + + int width = Format.NO_VALUE; + int height = Format.NO_VALUE; + float pixelWidthAspectRatio = 1; + if (numSequenceParameterSets > 0) { + byte[] sps = initializationData.get(0); + SpsData spsData = NalUnitUtil.parseSpsNalUnit(initializationData.get(0), + nalUnitLengthFieldLength, sps.length); + width = spsData.width; + height = spsData.height; + pixelWidthAspectRatio = spsData.pixelWidthAspectRatio; + } + return new AvcConfig(initializationData, nalUnitLengthFieldLength, width, height, + pixelWidthAspectRatio); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing AVC config", e); + } + } + + private AvcConfig(List initializationData, int nalUnitLengthFieldLength, + int width, int height, float pixelWidthAspectRatio) { + this.initializationData = initializationData; + this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + this.width = width; + this.height = height; + this.pixelWidthAspectRatio = pixelWidthAspectRatio; + } + + private static byte[] buildNalUnitForChild(ParsableByteArray data) { + int length = data.readUnsignedShort(); + int offset = data.getPosition(); + data.skipBytes(length); + return CodecSpecificDataUtil.buildNalUnit(data.data, offset, length); + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/ColorInfo.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/ColorInfo.java new file mode 100644 index 0000000..b51b5cc --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/ColorInfo.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.video; + +import android.os.Parcel; +import android.os.Parcelable; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import java.util.Arrays; + +/** + * Stores color info. + */ +public final class ColorInfo implements Parcelable { + + /** + * The color space of the video. Valid values are {@link C#COLOR_SPACE_BT601}, {@link + * C#COLOR_SPACE_BT709}, {@link C#COLOR_SPACE_BT2020} or {@link Format#NO_VALUE} if unknown. + */ + @C.ColorSpace + public final int colorSpace; + + /** + * The color range of the video. Valid values are {@link C#COLOR_RANGE_LIMITED}, {@link + * C#COLOR_RANGE_FULL} or {@link Format#NO_VALUE} if unknown. + */ + @C.ColorRange + public final int colorRange; + + /** + * The color transfer characteristicks of the video. Valid values are {@link + * C#COLOR_TRANSFER_HLG}, {@link C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link + * Format#NO_VALUE} if unknown. + */ + @C.ColorTransfer + public final int colorTransfer; + + /** + * HdrStaticInfo as defined in CTA-861.3. + */ + public final byte[] hdrStaticInfo; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * Constructs the ColorInfo. + * + * @param colorSpace The color space of the video. + * @param colorRange The color range of the video. + * @param colorTransfer The color transfer characteristics of the video. + * @param hdrStaticInfo HdrStaticInfo as defined in CTA-861.3. + */ + public ColorInfo(@C.ColorSpace int colorSpace, @C.ColorRange int colorRange, + @C.ColorTransfer int colorTransfer, byte[] hdrStaticInfo) { + this.colorSpace = colorSpace; + this.colorRange = colorRange; + this.colorTransfer = colorTransfer; + this.hdrStaticInfo = hdrStaticInfo; + } + + @SuppressWarnings("ResourceType") + /* package */ ColorInfo(Parcel in) { + colorSpace = in.readInt(); + colorRange = in.readInt(); + colorTransfer = in.readInt(); + boolean hasHdrStaticInfo = in.readInt() != 0; + hdrStaticInfo = hasHdrStaticInfo ? in.createByteArray() : null; + } + + // Parcelable implementation. + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ColorInfo other = (ColorInfo) obj; + if (colorSpace != other.colorSpace || colorRange != other.colorRange + || colorTransfer != other.colorTransfer + || !Arrays.equals(hdrStaticInfo, other.hdrStaticInfo)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "ColorInfo(" + colorSpace + ", " + colorRange + ", " + colorTransfer + + ", " + (hdrStaticInfo != null) + ")"; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + colorSpace; + result = 31 * result + colorRange; + result = 31 * result + colorTransfer; + result = 31 * result + Arrays.hashCode(hdrStaticInfo); + hashCode = result; + } + return hashCode; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(colorSpace); + dest.writeInt(colorRange); + dest.writeInt(colorTransfer); + dest.writeInt(hdrStaticInfo != null ? 1 : 0); + if (hdrStaticInfo != null) { + dest.writeByteArray(hdrStaticInfo); + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public ColorInfo createFromParcel(Parcel in) { + return new ColorInfo(in); + } + + @Override + public ColorInfo[] newArray(int size) { + return new ColorInfo[0]; + } + }; + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/HevcConfig.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/HevcConfig.java new file mode 100644 index 0000000..c755caa --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/HevcConfig.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.video; + +import com.tangxiaolv.telegramgallery.exoplayer2.ParserException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.NalUnitUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.ParsableByteArray; +import java.util.Collections; +import java.util.List; + +/** + * HEVC configuration data. + */ +public final class HevcConfig { + + public final List initializationData; + public final int nalUnitLengthFieldLength; + + /** + * Parses HEVC configuration data. + * + * @param data A {@link ParsableByteArray}, whose position is set to the start of the HEVC + * configuration data to parse. + * @return A parsed representation of the HEVC configuration data. + * @throws ParserException If an error occurred parsing the data. + */ + public static HevcConfig parse(ParsableByteArray data) throws ParserException { + try { + data.skipBytes(21); // Skip to the NAL unit length size field. + int lengthSizeMinusOne = data.readUnsignedByte() & 0x03; + + // Calculate the combined size of all VPS/SPS/PPS bitstreams. + int numberOfArrays = data.readUnsignedByte(); + int csdLength = 0; + int csdStartPosition = data.getPosition(); + for (int i = 0; i < numberOfArrays; i++) { + data.skipBytes(1); // completeness (1), nal_unit_type (7) + int numberOfNalUnits = data.readUnsignedShort(); + for (int j = 0; j < numberOfNalUnits; j++) { + int nalUnitLength = data.readUnsignedShort(); + csdLength += 4 + nalUnitLength; // Start code and NAL unit. + data.skipBytes(nalUnitLength); + } + } + + // Concatenate the codec-specific data into a single buffer. + data.setPosition(csdStartPosition); + byte[] buffer = new byte[csdLength]; + int bufferPosition = 0; + for (int i = 0; i < numberOfArrays; i++) { + data.skipBytes(1); // completeness (1), nal_unit_type (7) + int numberOfNalUnits = data.readUnsignedShort(); + for (int j = 0; j < numberOfNalUnits; j++) { + int nalUnitLength = data.readUnsignedShort(); + System.arraycopy(NalUnitUtil.NAL_START_CODE, 0, buffer, bufferPosition, + NalUnitUtil.NAL_START_CODE.length); + bufferPosition += NalUnitUtil.NAL_START_CODE.length; + System + .arraycopy(data.data, data.getPosition(), buffer, bufferPosition, nalUnitLength); + bufferPosition += nalUnitLength; + data.skipBytes(nalUnitLength); + } + } + + List initializationData = csdLength == 0 ? null : Collections.singletonList(buffer); + return new HevcConfig(initializationData, lengthSizeMinusOne + 1); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing HEVC config", e); + } + } + + private HevcConfig(List initializationData, int nalUnitLengthFieldLength) { + this.initializationData = initializationData; + this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/MediaCodecVideoRenderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/MediaCodecVideoRenderer.java new file mode 100644 index 0000000..b2cb04c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/MediaCodecVideoRenderer.java @@ -0,0 +1,866 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.video; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Point; +import android.media.MediaCodec; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCrypto; +import android.media.MediaFormat; +import android.os.Handler; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.view.Surface; +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.ExoPlaybackException; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderInputBuffer; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmInitData; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.DrmSessionManager; +import com.tangxiaolv.telegramgallery.exoplayer2.drm.FrameworkMediaCrypto; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecInfo; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecRenderer; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecSelector; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import com.tangxiaolv.telegramgallery.exoplayer2.util.MimeTypes; +import com.tangxiaolv.telegramgallery.exoplayer2.util.TraceUtil; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; +import com.tangxiaolv.telegramgallery.exoplayer2.video.VideoRendererEventListener.EventDispatcher; +import java.nio.ByteBuffer; + +/** + * Decodes and renders video using {@link MediaCodec}. + */ +@TargetApi(16) +public class MediaCodecVideoRenderer extends MediaCodecRenderer { + + private static final String TAG = "MediaCodecVideoRenderer"; + private static final String KEY_CROP_LEFT = "crop-left"; + private static final String KEY_CROP_RIGHT = "crop-right"; + private static final String KEY_CROP_BOTTOM = "crop-bottom"; + private static final String KEY_CROP_TOP = "crop-top"; + + // Long edge length in pixels for standard video formats, in decreasing in order. + private static final int[] STANDARD_LONG_EDGE_VIDEO_PX = new int[] { + 1920, 1600, 1440, 1280, 960, 854, 640, 540, 480}; + + private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper; + private final EventDispatcher eventDispatcher; + private final long allowedJoiningTimeMs; + private final int maxDroppedFramesToNotify; + private final boolean deviceNeedsAutoFrcWorkaround; + + private Format[] streamFormats; + private CodecMaxValues codecMaxValues; + + private Surface surface; + @C.VideoScalingMode + private int scalingMode; + private boolean renderedFirstFrame; + private long joiningDeadlineMs; + private long droppedFrameAccumulationStartTimeMs; + private int droppedFrames; + private int consecutiveDroppedFrameCount; + + private int pendingRotationDegrees; + private float pendingPixelWidthHeightRatio; + private int currentWidth; + private int currentHeight; + private int currentUnappliedRotationDegrees; + private float currentPixelWidthHeightRatio; + private int reportedWidth; + private int reportedHeight; + private int reportedUnappliedRotationDegrees; + private float reportedPixelWidthHeightRatio; + + private boolean tunneling; + private int tunnelingAudioSessionId; + /* package */ OnFrameRenderedListenerV23 tunnelingOnFrameRenderedListener; + + /** + * @param context A context. + * @param mediaCodecSelector A decoder selector. + */ + public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector) { + this(context, mediaCodecSelector, 0); + } + + /** + * @param context A context. + * @param mediaCodecSelector A decoder selector. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. + */ + public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, + long allowedJoiningTimeMs) { + this(context, mediaCodecSelector, allowedJoiningTimeMs, null, null, -1); + } + + /** + * @param context A context. + * @param mediaCodecSelector A decoder selector. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between + * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. + */ + public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, + long allowedJoiningTimeMs, Handler eventHandler, VideoRendererEventListener eventListener, + int maxDroppedFrameCountToNotify) { + this(context, mediaCodecSelector, allowedJoiningTimeMs, null, false, eventHandler, + eventListener, maxDroppedFrameCountToNotify); + } + + /** + * @param context A context. + * @param mediaCodecSelector A decoder selector. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. + * @param drmSessionManager For use with encrypted content. May be null if support for encrypted + * content is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between + * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. + */ + public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, + long allowedJoiningTimeMs, DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys, Handler eventHandler, + VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { + super(C.TRACK_TYPE_VIDEO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); + this.allowedJoiningTimeMs = allowedJoiningTimeMs; + this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; + frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context); + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround(); + joiningDeadlineMs = C.TIME_UNSET; + currentWidth = Format.NO_VALUE; + currentHeight = Format.NO_VALUE; + currentPixelWidthHeightRatio = Format.NO_VALUE; + pendingPixelWidthHeightRatio = Format.NO_VALUE; + scalingMode = C.VIDEO_SCALING_MODE_DEFAULT; + clearReportedVideoSize(); + } + + @Override + protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) + throws DecoderQueryException { + String mimeType = format.sampleMimeType; + if (!MimeTypes.isVideo(mimeType)) { + return FORMAT_UNSUPPORTED_TYPE; + } + boolean requiresSecureDecryption = false; + DrmInitData drmInitData = format.drmInitData; + if (drmInitData != null) { + for (int i = 0; i < drmInitData.schemeDataCount; i++) { + requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption; + } + } + MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, + requiresSecureDecryption); + if (decoderInfo == null) { + return FORMAT_UNSUPPORTED_SUBTYPE; + } + + boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs); + if (decoderCapable && format.width > 0 && format.height > 0) { + if (Util.SDK_INT >= 21) { + decoderCapable = decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height, + format.frameRate); + } else { + decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize(); + } + } + + int adaptiveSupport = decoderInfo.adaptive ? ADAPTIVE_SEAMLESS : ADAPTIVE_NOT_SEAMLESS; + int tunnelingSupport = decoderInfo.tunneling ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; + int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; + return adaptiveSupport | tunnelingSupport | formatSupport; + } + + @Override + protected void onEnabled(boolean joining) throws ExoPlaybackException { + super.onEnabled(joining); + tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; + tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET; + eventDispatcher.enabled(decoderCounters); + frameReleaseTimeHelper.enable(); + } + + @Override + protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + streamFormats = formats; + super.onStreamChanged(formats); + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { + super.onPositionReset(positionUs, joining); + clearRenderedFirstFrame(); + consecutiveDroppedFrameCount = 0; + if (joining) { + setJoiningDeadlineMs(); + } else { + joiningDeadlineMs = C.TIME_UNSET; + } + } + + @Override + public boolean isReady() { + if ((renderedFirstFrame || super.shouldInitCodec()) && super.isReady()) { + // Ready. If we were joining then we've now joined, so clear the joining deadline. + joiningDeadlineMs = C.TIME_UNSET; + return true; + } else if (joiningDeadlineMs == C.TIME_UNSET) { + // Not joining. + return false; + } else if (SystemClock.elapsedRealtime() < joiningDeadlineMs) { + // Joining and still within the joining deadline. + return true; + } else { + // The joining deadline has been exceeded. Give up and clear the deadline. + joiningDeadlineMs = C.TIME_UNSET; + return false; + } + } + + @Override + protected void onStarted() { + super.onStarted(); + droppedFrames = 0; + droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); + joiningDeadlineMs = C.TIME_UNSET; + } + + @Override + protected void onStopped() { + maybeNotifyDroppedFrames(); + super.onStopped(); + } + + @Override + protected void onDisabled() { + currentWidth = Format.NO_VALUE; + currentHeight = Format.NO_VALUE; + currentPixelWidthHeightRatio = Format.NO_VALUE; + pendingPixelWidthHeightRatio = Format.NO_VALUE; + clearReportedVideoSize(); + clearRenderedFirstFrame(); + frameReleaseTimeHelper.disable(); + tunnelingOnFrameRenderedListener = null; + try { + super.onDisabled(); + } finally { + decoderCounters.ensureUpdated(); + eventDispatcher.disabled(decoderCounters); + } + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + if (messageType == C.MSG_SET_SURFACE) { + setSurface((Surface) message); + } else if (messageType == C.MSG_SET_SCALING_MODE) { + scalingMode = (Integer) message; + MediaCodec codec = getCodec(); + if (codec != null) { + setVideoScalingMode(codec, scalingMode); + } + } else { + super.handleMessage(messageType, message); + } + } + + private void setSurface(Surface surface) throws ExoPlaybackException { + // We only need to update the codec if the surface has changed. + if (this.surface != surface) { + this.surface = surface; + int state = getState(); + if (state == STATE_ENABLED || state == STATE_STARTED) { + MediaCodec codec = getCodec(); + if (Util.SDK_INT >= 23 && codec != null && surface != null) { + setOutputSurfaceV23(codec, surface); + } else { + releaseCodec(); + maybeInitCodec(); + } + } + if (surface != null) { + // If we know the video size, report it again immediately. + maybeRenotifyVideoSizeChanged(); + // We haven't rendered to the new surface yet. + clearRenderedFirstFrame(); + if (state == STATE_STARTED) { + setJoiningDeadlineMs(); + } + } else { + // The surface has been removed. + clearReportedVideoSize(); + clearRenderedFirstFrame(); + } + } else if (surface != null) { + // The surface is unchanged and non-null. If we know the video size and/or have already + // rendered to the surface, report these again immediately. + maybeRenotifyVideoSizeChanged(); + maybeRenotifyRenderedFirstFrame(); + } + } + + @Override + protected boolean shouldInitCodec() { + return super.shouldInitCodec() && surface != null && surface.isValid(); + } + + @Override + protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, + MediaCrypto crypto) throws DecoderQueryException { + codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats); + MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround, + tunnelingAudioSessionId); + codec.configure(mediaFormat, surface, crypto, 0); + if (Util.SDK_INT >= 23 && tunneling) { + tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); + } + } + + @Override + protected void onCodecInitialized(String name, long initializedTimestampMs, + long initializationDurationMs) { + eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); + } + + @Override + protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + super.onInputFormatChanged(newFormat); + eventDispatcher.inputFormatChanged(newFormat); + pendingPixelWidthHeightRatio = getPixelWidthHeightRatio(newFormat); + pendingRotationDegrees = getRotationDegrees(newFormat); + } + + @Override + protected void onQueueInputBuffer(DecoderInputBuffer buffer) { + if (Util.SDK_INT < 23 && tunneling) { + maybeNotifyRenderedFirstFrame(); + } + } + + @Override + protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) { + boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT) + && outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM) + && outputFormat.containsKey(KEY_CROP_TOP); + currentWidth = hasCrop + ? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1 + : outputFormat.getInteger(MediaFormat.KEY_WIDTH); + currentHeight = hasCrop + ? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1 + : outputFormat.getInteger(MediaFormat.KEY_HEIGHT); + currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio; + if (Util.SDK_INT >= 21) { + // On API level 21 and above the decoder applies the rotation when rendering to the surface. + // Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need + // to flip the width, height and pixel aspect ratio to reflect the rotation that was applied. + if (pendingRotationDegrees == 90 || pendingRotationDegrees == 270) { + int rotatedHeight = currentWidth; + currentWidth = currentHeight; + currentHeight = rotatedHeight; + currentPixelWidthHeightRatio = 1 / currentPixelWidthHeightRatio; + } + } else { + // On API level 20 and below the decoder does not apply the rotation. + currentUnappliedRotationDegrees = pendingRotationDegrees; + } + // Must be applied each time the output format changes. + setVideoScalingMode(codec, scalingMode); + } + + @Override + protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, + Format oldFormat, Format newFormat) { + return areAdaptationCompatible(oldFormat, newFormat) + && newFormat.width <= codecMaxValues.width && newFormat.height <= codecMaxValues.height + && newFormat.maxInputSize <= codecMaxValues.inputSize + && (codecIsAdaptive + || (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height)); + } + + @Override + protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, + ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, + boolean shouldSkip) { + if (shouldSkip) { + skipOutputBuffer(codec, bufferIndex); + return true; + } + + if (!renderedFirstFrame) { + if (Util.SDK_INT >= 21) { + renderOutputBufferV21(codec, bufferIndex, System.nanoTime()); + } else { + renderOutputBuffer(codec, bufferIndex); + } + return true; + } + + if (getState() != STATE_STARTED) { + return false; + } + + // Compute how many microseconds it is until the buffer's presentation time. + long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs; + long earlyUs = bufferPresentationTimeUs - positionUs - elapsedSinceStartOfLoopUs; + + // Compute the buffer's desired release time in nanoseconds. + long systemTimeNs = System.nanoTime(); + long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000); + + // Apply a timestamp adjustment, if there is one. + long adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime( + bufferPresentationTimeUs, unadjustedFrameReleaseTimeNs); + earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000; + + if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) { + // We're more than 30ms late rendering the frame. + dropOutputBuffer(codec, bufferIndex); + return true; + } + + if (Util.SDK_INT >= 21) { + // Let the underlying framework time the release. + if (earlyUs < 50000) { + renderOutputBufferV21(codec, bufferIndex, adjustedReleaseTimeNs); + return true; + } + } else { + // We need to time the release ourselves. + if (earlyUs < 30000) { + if (earlyUs > 11000) { + // We're a little too early to render the frame. Sleep until the frame can be rendered. + // Note: The 11ms threshold was chosen fairly arbitrarily. + try { + // Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms. + Thread.sleep((earlyUs - 10000) / 1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + renderOutputBuffer(codec, bufferIndex); + return true; + } + } + + // We're either not playing, or it's not time to render the frame yet. + return false; + } + + /** + * Returns whether the buffer being processed should be dropped. + * + * @param earlyUs The time until the buffer should be presented in microseconds. A negative value + * indicates that the buffer is late. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. + */ + protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) { + // Drop the frame if we're more than 30ms late rendering the frame. + return earlyUs < -30000; + } + + private void skipOutputBuffer(MediaCodec codec, int bufferIndex) { + TraceUtil.beginSection("skipVideoBuffer"); + codec.releaseOutputBuffer(bufferIndex, false); + TraceUtil.endSection(); + decoderCounters.skippedOutputBufferCount++; + } + + private void dropOutputBuffer(MediaCodec codec, int bufferIndex) { + TraceUtil.beginSection("dropVideoBuffer"); + codec.releaseOutputBuffer(bufferIndex, false); + TraceUtil.endSection(); + decoderCounters.droppedOutputBufferCount++; + droppedFrames++; + consecutiveDroppedFrameCount++; + decoderCounters.maxConsecutiveDroppedOutputBufferCount = Math.max(consecutiveDroppedFrameCount, + decoderCounters.maxConsecutiveDroppedOutputBufferCount); + if (droppedFrames == maxDroppedFramesToNotify) { + maybeNotifyDroppedFrames(); + } + } + + private void renderOutputBuffer(MediaCodec codec, int bufferIndex) { + maybeNotifyVideoSizeChanged(); + TraceUtil.beginSection("releaseOutputBuffer"); + codec.releaseOutputBuffer(bufferIndex, true); + TraceUtil.endSection(); + decoderCounters.renderedOutputBufferCount++; + consecutiveDroppedFrameCount = 0; + maybeNotifyRenderedFirstFrame(); + } + + @TargetApi(21) + private void renderOutputBufferV21(MediaCodec codec, int bufferIndex, long releaseTimeNs) { + maybeNotifyVideoSizeChanged(); + TraceUtil.beginSection("releaseOutputBuffer"); + codec.releaseOutputBuffer(bufferIndex, releaseTimeNs); + TraceUtil.endSection(); + decoderCounters.renderedOutputBufferCount++; + consecutiveDroppedFrameCount = 0; + maybeNotifyRenderedFirstFrame(); + } + + private void setJoiningDeadlineMs() { + joiningDeadlineMs = allowedJoiningTimeMs > 0 + ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; + } + + private void clearRenderedFirstFrame() { + renderedFirstFrame = false; + // The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for + // non-tunneled playback, onQueueInputBuffer for tunneled playback prior to API level 23, and + // OnFrameRenderedListenerV23.onFrameRenderedListener for tunneled playback on API level 23 and + // above. + if (Util.SDK_INT >= 23 && tunneling) { + MediaCodec codec = getCodec(); + // If codec is null then the listener will be instantiated in configureCodec. + if (codec != null) { + tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); + } + } + } + + /* package */ void maybeNotifyRenderedFirstFrame() { + if (!renderedFirstFrame) { + renderedFirstFrame = true; + eventDispatcher.renderedFirstFrame(surface); + } + } + + private void maybeRenotifyRenderedFirstFrame() { + if (renderedFirstFrame) { + eventDispatcher.renderedFirstFrame(surface); + } + } + + private void clearReportedVideoSize() { + reportedWidth = Format.NO_VALUE; + reportedHeight = Format.NO_VALUE; + reportedPixelWidthHeightRatio = Format.NO_VALUE; + reportedUnappliedRotationDegrees = Format.NO_VALUE; + } + + private void maybeNotifyVideoSizeChanged() { + if (reportedWidth != currentWidth || reportedHeight != currentHeight + || reportedUnappliedRotationDegrees != currentUnappliedRotationDegrees + || reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) { + eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, + currentPixelWidthHeightRatio); + reportedWidth = currentWidth; + reportedHeight = currentHeight; + reportedUnappliedRotationDegrees = currentUnappliedRotationDegrees; + reportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; + } + } + + private void maybeRenotifyVideoSizeChanged() { + if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) { + eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, + currentPixelWidthHeightRatio); + } + } + + private void maybeNotifyDroppedFrames() { + if (droppedFrames > 0) { + long now = SystemClock.elapsedRealtime(); + long elapsedMs = now - droppedFrameAccumulationStartTimeMs; + eventDispatcher.droppedFrames(droppedFrames, elapsedMs); + droppedFrames = 0; + droppedFrameAccumulationStartTimeMs = now; + } + } + + @SuppressLint("InlinedApi") + private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues, + boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) { + MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16(); + // Set the maximum adaptive video dimensions. + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, codecMaxValues.width); + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, codecMaxValues.height); + // Set the maximum input size. + if (codecMaxValues.inputSize != Format.NO_VALUE) { + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, codecMaxValues.inputSize); + } + // Set FRC workaround. + if (deviceNeedsAutoFrcWorkaround) { + frameworkMediaFormat.setInteger("auto-frc", 0); + } + // Configure tunneling if enabled. + if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { + configureTunnelingV21(frameworkMediaFormat, tunnelingAudioSessionId); + } + return frameworkMediaFormat; + } + + @TargetApi(23) + private static void setOutputSurfaceV23(MediaCodec codec, Surface surface) { + codec.setOutputSurface(surface); + } + + @TargetApi(21) + private static void configureTunnelingV21(MediaFormat mediaFormat, int tunnelingAudioSessionId) { + mediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true); + mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId); + } + + /** + * Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way + * that will allow possible adaptation to other compatible formats in {@code streamFormats}. + * + * @param codecInfo Information about the {@link MediaCodec} being configured. + * @param format The format for which the codec is being configured. + * @param streamFormats The possible stream formats. + * @return Suitable {@link CodecMaxValues}. + * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. + */ + private static CodecMaxValues getCodecMaxValues(MediaCodecInfo codecInfo, Format format, + Format[] streamFormats) throws DecoderQueryException { + int maxWidth = format.width; + int maxHeight = format.height; + int maxInputSize = getMaxInputSize(format); + if (streamFormats.length == 1) { + // The single entry in streamFormats must correspond to the format for which the codec is + // being configured. + return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); + } + boolean haveUnknownDimensions = false; + for (Format streamFormat : streamFormats) { + if (areAdaptationCompatible(format, streamFormat)) { + haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE + || streamFormat.height == Format.NO_VALUE); + maxWidth = Math.max(maxWidth, streamFormat.width); + maxHeight = Math.max(maxHeight, streamFormat.height); + maxInputSize = Math.max(maxInputSize, getMaxInputSize(streamFormat)); + } + } + if (haveUnknownDimensions) { + Point codecMaxSize = getCodecMaxSize(codecInfo, format); + if (codecMaxSize != null) { + maxWidth = Math.max(maxWidth, codecMaxSize.x); + maxHeight = Math.max(maxHeight, codecMaxSize.y); + maxInputSize = Math.max(maxInputSize, + getMaxInputSize(format.sampleMimeType, maxWidth, maxHeight)); + } + } + return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); + } + + /** + * Returns a maximum video size to use when configuring a codec for {@code format} in a way + * that will allow possible adaptation to other compatible formats that are expected to have the + * same aspect ratio, but whose sizes are unknown. + * + * @param codecInfo Information about the {@link MediaCodec} being configured. + * @param format The format for which the codec is being configured. + * @return The maximum video size to use, or null if the size of {@code format} should be used. + * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. + */ + private static Point getCodecMaxSize(MediaCodecInfo codecInfo, Format format) + throws DecoderQueryException { + boolean isVerticalVideo = format.height > format.width; + int formatLongEdgePx = isVerticalVideo ? format.height : format.width; + int formatShortEdgePx = isVerticalVideo ? format.width : format.height; + float aspectRatio = (float) formatShortEdgePx / formatLongEdgePx; + for (int longEdgePx : STANDARD_LONG_EDGE_VIDEO_PX) { + int shortEdgePx = (int) (longEdgePx * aspectRatio); + if (longEdgePx <= formatLongEdgePx || shortEdgePx <= formatShortEdgePx) { + // Don't return a size not larger than the format for which the codec is being configured. + return null; + } else if (Util.SDK_INT >= 21) { + Point alignedSize = codecInfo.alignVideoSizeV21(isVerticalVideo ? shortEdgePx : longEdgePx, + isVerticalVideo ? longEdgePx : shortEdgePx); + float frameRate = format.frameRate; + if (codecInfo.isVideoSizeAndRateSupportedV21(alignedSize.x, alignedSize.y, frameRate)) { + return alignedSize; + } + } else { + // Conservatively assume the codec requires 16px width and height alignment. + longEdgePx = Util.ceilDivide(longEdgePx, 16) * 16; + shortEdgePx = Util.ceilDivide(shortEdgePx, 16) * 16; + if (longEdgePx * shortEdgePx <= MediaCodecUtil.maxH264DecodableFrameSize()) { + return new Point(isVerticalVideo ? shortEdgePx : longEdgePx, + isVerticalVideo ? longEdgePx : shortEdgePx); + } + } + } + return null; + } + + /** + * Returns a maximum input size for a given format. + * + * @param format The format. + * @return A maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be + * determined. + */ + private static int getMaxInputSize(Format format) { + if (format.maxInputSize != Format.NO_VALUE) { + // The format defines an explicit maximum input size. + return format.maxInputSize; + } + return getMaxInputSize(format.sampleMimeType, format.width, format.height); + } + + /** + * Returns a maximum input size for a given mime type, width and height. + * + * @param sampleMimeType The format mime type. + * @param width The width in pixels. + * @param height The height in pixels. + * @return A maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be + * determined. + */ + private static int getMaxInputSize(String sampleMimeType, int width, int height) { + if (width == Format.NO_VALUE || height == Format.NO_VALUE) { + // We can't infer a maximum input size without video dimensions. + return Format.NO_VALUE; + } + + // Attempt to infer a maximum input size from the format. + int maxPixels; + int minCompressionRatio; + switch (sampleMimeType) { + case MimeTypes.VIDEO_H263: + case MimeTypes.VIDEO_MP4V: + maxPixels = width * height; + minCompressionRatio = 2; + break; + case MimeTypes.VIDEO_H264: + if ("BRAVIA 4K 2015".equals(Util.MODEL)) { + // The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video + // maximum input size, so use the default value. + return Format.NO_VALUE; + } + // Round up width/height to an integer number of macroblocks. + maxPixels = Util.ceilDivide(width, 16) * Util.ceilDivide(height, 16) * 16 * 16; + minCompressionRatio = 2; + break; + case MimeTypes.VIDEO_VP8: + // VPX does not specify a ratio so use the values from the platform's SoftVPX.cpp. + maxPixels = width * height; + minCompressionRatio = 2; + break; + case MimeTypes.VIDEO_H265: + case MimeTypes.VIDEO_VP9: + maxPixels = width * height; + minCompressionRatio = 4; + break; + default: + // Leave the default max input size. + return Format.NO_VALUE; + } + // Estimate the maximum input size assuming three channel 4:2:0 subsampled input frames. + return (maxPixels * 3) / (2 * minCompressionRatio); + } + + private static void setVideoScalingMode(MediaCodec codec, int scalingMode) { + codec.setVideoScalingMode(scalingMode); + } + + /** + * Returns whether the device is known to enable frame-rate conversion logic that negatively + * impacts ExoPlayer. + *

    + * If true is returned then we explicitly disable the feature. + * + * @return True if the device is known to enable frame-rate conversion logic that negatively + * impacts ExoPlayer. False otherwise. + */ + private static boolean deviceNeedsAutoFrcWorkaround() { + // nVidia Shield prior to M tries to adjust the playback rate to better map the frame-rate of + // content to the refresh rate of the display. For example playback of 23.976fps content is + // adjusted to play at 1.001x speed when the output display is 60Hz. Unfortunately the + // implementation causes ExoPlayer's reported playback position to drift out of sync. Captions + // also lose sync [Internal: b/26453592]. + return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER); + } + + /** + * Returns whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation + * between two {@link Format}s. + * + * @param first The first format. + * @param second The second format. + * @return Whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation + * between two {@link Format}s. + */ + private static boolean areAdaptationCompatible(Format first, Format second) { + return first.sampleMimeType.equals(second.sampleMimeType) + && getRotationDegrees(first) == getRotationDegrees(second); + } + + private static float getPixelWidthHeightRatio(Format format) { + return format.pixelWidthHeightRatio == Format.NO_VALUE ? 1 : format.pixelWidthHeightRatio; + } + + private static int getRotationDegrees(Format format) { + return format.rotationDegrees == Format.NO_VALUE ? 0 : format.rotationDegrees; + } + + private static final class CodecMaxValues { + + public final int width; + public final int height; + public final int inputSize; + + public CodecMaxValues(int width, int height, int inputSize) { + this.width = width; + this.height = height; + this.inputSize = inputSize; + } + + } + + @TargetApi(23) + private final class OnFrameRenderedListenerV23 implements MediaCodec.OnFrameRenderedListener { + + private OnFrameRenderedListenerV23(MediaCodec codec) { + codec.setOnFrameRenderedListener(this, new Handler()); + } + + @Override + public void onFrameRendered(@NonNull MediaCodec codec, long presentationTimeUs, long nanoTime) { + if (this != tunnelingOnFrameRenderedListener) { + // Stale event. + return; + } + maybeNotifyRenderedFirstFrame(); + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/VideoFrameReleaseTimeHelper.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/VideoFrameReleaseTimeHelper.java new file mode 100644 index 0000000..a2c935c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/VideoFrameReleaseTimeHelper.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.video; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.view.Choreographer; +import android.view.Choreographer.FrameCallback; +import android.view.WindowManager; +import com.tangxiaolv.telegramgallery.exoplayer2.C; + +/** + * Makes a best effort to adjust frame release timestamps for a smoother visual result. + */ +@TargetApi(16) +public final class VideoFrameReleaseTimeHelper { + + private static final long CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500; + private static final long MAX_ALLOWED_DRIFT_NS = 20000000; + + private static final long VSYNC_OFFSET_PERCENTAGE = 80; + private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6; + + private final VSyncSampler vsyncSampler; + private final boolean useDefaultDisplayVsync; + private final long vsyncDurationNs; + private final long vsyncOffsetNs; + + private long lastFramePresentationTimeUs; + private long adjustedLastFrameTimeNs; + private long pendingAdjustedFrameTimeNs; + + private boolean haveSync; + private long syncUnadjustedReleaseTimeNs; + private long syncFramePresentationTimeNs; + private long frameCount; + + /** + * Constructs an instance that smoothes frame release timestamps but does not align them with + * the default display's vsync signal. + */ + public VideoFrameReleaseTimeHelper() { + this(-1 /* Value unused */, false); + } + + /** + * Constructs an instance that smoothes frame release timestamps and aligns them with the default + * display's vsync signal. + * + * @param context A context from which information about the default display can be retrieved. + */ + public VideoFrameReleaseTimeHelper(Context context) { + this(getDefaultDisplayRefreshRate(context), true); + } + + private VideoFrameReleaseTimeHelper(double defaultDisplayRefreshRate, + boolean useDefaultDisplayVsync) { + this.useDefaultDisplayVsync = useDefaultDisplayVsync; + if (useDefaultDisplayVsync) { + vsyncSampler = VSyncSampler.getInstance(); + vsyncDurationNs = (long) (C.NANOS_PER_SECOND / defaultDisplayRefreshRate); + vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100; + } else { + vsyncSampler = null; + vsyncDurationNs = -1; // Value unused. + vsyncOffsetNs = -1; // Value unused. + } + } + + /** + * Enables the helper. + */ + public void enable() { + haveSync = false; + if (useDefaultDisplayVsync) { + vsyncSampler.addObserver(); + } + } + + /** + * Disables the helper. + */ + public void disable() { + if (useDefaultDisplayVsync) { + vsyncSampler.removeObserver(); + } + } + + /** + * Adjusts a frame release timestamp. + * + * @param framePresentationTimeUs The frame's presentation time, in microseconds. + * @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in + * the same time base as {@link System#nanoTime()}. + * @return The adjusted frame release timestamp, in nanoseconds and in the same time base as + * {@link System#nanoTime()}. + */ + public long adjustReleaseTime(long framePresentationTimeUs, long unadjustedReleaseTimeNs) { + long framePresentationTimeNs = framePresentationTimeUs * 1000; + + // Until we know better, the adjustment will be a no-op. + long adjustedFrameTimeNs = framePresentationTimeNs; + long adjustedReleaseTimeNs = unadjustedReleaseTimeNs; + + if (haveSync) { + // See if we've advanced to the next frame. + if (framePresentationTimeUs != lastFramePresentationTimeUs) { + frameCount++; + adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs; + } + if (frameCount >= MIN_FRAMES_FOR_ADJUSTMENT) { + // We're synced and have waited the required number of frames to apply an adjustment. + // Calculate the average frame time across all the frames we've seen since the last sync. + // This will typically give us a frame rate at a finer granularity than the frame times + // themselves (which often only have millisecond granularity). + long averageFrameDurationNs = (framePresentationTimeNs - syncFramePresentationTimeNs) + / frameCount; + // Project the adjusted frame time forward using the average. + long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameDurationNs; + + if (isDriftTooLarge(candidateAdjustedFrameTimeNs, unadjustedReleaseTimeNs)) { + haveSync = false; + } else { + adjustedFrameTimeNs = candidateAdjustedFrameTimeNs; + adjustedReleaseTimeNs = syncUnadjustedReleaseTimeNs + adjustedFrameTimeNs + - syncFramePresentationTimeNs; + } + } else { + // We're synced but haven't waited the required number of frames to apply an adjustment. + // Check drift anyway. + if (isDriftTooLarge(framePresentationTimeNs, unadjustedReleaseTimeNs)) { + haveSync = false; + } + } + } + + // If we need to sync, do so now. + if (!haveSync) { + syncFramePresentationTimeNs = framePresentationTimeNs; + syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs; + frameCount = 0; + haveSync = true; + onSynced(); + } + + lastFramePresentationTimeUs = framePresentationTimeUs; + pendingAdjustedFrameTimeNs = adjustedFrameTimeNs; + + if (vsyncSampler == null || vsyncSampler.sampledVsyncTimeNs == 0) { + return adjustedReleaseTimeNs; + } + + // Find the timestamp of the closest vsync. This is the vsync that we're targeting. + long snappedTimeNs = closestVsync(adjustedReleaseTimeNs, + vsyncSampler.sampledVsyncTimeNs, vsyncDurationNs); + // Apply an offset so that we release before the target vsync, but after the previous one. + return snappedTimeNs - vsyncOffsetNs; + } + + protected void onSynced() { + // Do nothing. + } + + private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) { + long elapsedFrameTimeNs = frameTimeNs - syncFramePresentationTimeNs; + long elapsedReleaseTimeNs = releaseTimeNs - syncUnadjustedReleaseTimeNs; + return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) > MAX_ALLOWED_DRIFT_NS; + } + + private static long closestVsync(long releaseTime, long sampledVsyncTime, long vsyncDuration) { + long vsyncCount = (releaseTime - sampledVsyncTime) / vsyncDuration; + long snappedTimeNs = sampledVsyncTime + (vsyncDuration * vsyncCount); + long snappedBeforeNs; + long snappedAfterNs; + if (releaseTime <= snappedTimeNs) { + snappedBeforeNs = snappedTimeNs - vsyncDuration; + snappedAfterNs = snappedTimeNs; + } else { + snappedBeforeNs = snappedTimeNs; + snappedAfterNs = snappedTimeNs + vsyncDuration; + } + long snappedAfterDiff = snappedAfterNs - releaseTime; + long snappedBeforeDiff = releaseTime - snappedBeforeNs; + return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs; + } + + private static float getDefaultDisplayRefreshRate(Context context) { + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + return manager.getDefaultDisplay().getRefreshRate(); + } + + /** + * Samples display vsync timestamps. A single instance using a single {@link Choreographer} is + * shared by all {@link VideoFrameReleaseTimeHelper} instances. This is done to avoid a resource + * leak in the platform on API levels prior to 23. See [Internal: b/12455729]. + */ + private static final class VSyncSampler implements FrameCallback, Handler.Callback { + + public volatile long sampledVsyncTimeNs; + + private static final int CREATE_CHOREOGRAPHER = 0; + private static final int MSG_ADD_OBSERVER = 1; + private static final int MSG_REMOVE_OBSERVER = 2; + + private static final VSyncSampler INSTANCE = new VSyncSampler(); + + private final Handler handler; + private final HandlerThread choreographerOwnerThread; + private Choreographer choreographer; + private int observerCount; + + public static VSyncSampler getInstance() { + return INSTANCE; + } + + private VSyncSampler() { + choreographerOwnerThread = new HandlerThread("ChoreographerOwner:Handler"); + choreographerOwnerThread.start(); + handler = new Handler(choreographerOwnerThread.getLooper(), this); + handler.sendEmptyMessage(CREATE_CHOREOGRAPHER); + } + + /** + * Notifies the sampler that a {@link VideoFrameReleaseTimeHelper} is observing + * {@link #sampledVsyncTimeNs}, and hence that the value should be periodically updated. + */ + public void addObserver() { + handler.sendEmptyMessage(MSG_ADD_OBSERVER); + } + + /** + * Notifies the sampler that a {@link VideoFrameReleaseTimeHelper} is no longer observing + * {@link #sampledVsyncTimeNs}. + */ + public void removeObserver() { + handler.sendEmptyMessage(MSG_REMOVE_OBSERVER); + } + + @Override + public void doFrame(long vsyncTimeNs) { + sampledVsyncTimeNs = vsyncTimeNs; + choreographer.postFrameCallbackDelayed(this, CHOREOGRAPHER_SAMPLE_DELAY_MILLIS); + } + + @Override + public boolean handleMessage(Message message) { + switch (message.what) { + case CREATE_CHOREOGRAPHER: { + createChoreographerInstanceInternal(); + return true; + } + case MSG_ADD_OBSERVER: { + addObserverInternal(); + return true; + } + case MSG_REMOVE_OBSERVER: { + removeObserverInternal(); + return true; + } + default: { + return false; + } + } + } + + private void createChoreographerInstanceInternal() { + choreographer = Choreographer.getInstance(); + } + + private void addObserverInternal() { + observerCount++; + if (observerCount == 1) { + choreographer.postFrameCallback(this); + } + } + + private void removeObserverInternal() { + observerCount--; + if (observerCount == 0) { + choreographer.removeFrameCallback(this); + sampledVsyncTimeNs = 0; + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/VideoRendererEventListener.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/VideoRendererEventListener.java new file mode 100644 index 0000000..bcd237f --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/exoplayer2/video/VideoRendererEventListener.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tangxiaolv.telegramgallery.exoplayer2.video; + +import android.os.Handler; +import android.os.SystemClock; +import android.view.Surface; +import android.view.TextureView; +import com.tangxiaolv.telegramgallery.exoplayer2.Format; +import com.tangxiaolv.telegramgallery.exoplayer2.Renderer; +import com.tangxiaolv.telegramgallery.exoplayer2.decoder.DecoderCounters; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; + +/** + * Listener of video {@link Renderer} events. + */ +public interface VideoRendererEventListener { + + /** + * Called when the renderer is enabled. + * + * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it + * remains enabled. + */ + void onVideoEnabled(DecoderCounters counters); + + /** + * Called when a decoder is created. + * + * @param decoderName The decoder that was created. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. + * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. + */ + void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, + long initializationDurationMs); + + /** + * Called when the format of the media being consumed by the renderer changes. + * + * @param format The new format. + */ + void onVideoInputFormatChanged(Format format); + + /** + * Called to report the number of frames dropped by the renderer. Dropped frames are reported + * whenever the renderer is stopped having dropped frames, and optionally, whenever the count + * reaches a specified threshold whilst the renderer is started. + * + * @param count The number of dropped frames. + * @param elapsedMs The duration in milliseconds over which the frames were dropped. This + * duration is timed from when the renderer was started or from when dropped frames were + * last reported (whichever was more recent), and not from when the first of the reported + * drops occurred. + */ + void onDroppedFrames(int count, long elapsedMs); + + /** + * Called before a frame is rendered for the first time since setting the surface, and each time + * there's a change in the size, rotation or pixel aspect ratio of the video being rendered. + * + * @param width The video width in pixels. + * @param height The video height in pixels. + * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise + * rotation in degrees that the application should apply for the video for it to be rendered + * in the correct orientation. This value will always be zero on API levels 21 and above, + * since the renderer will apply all necessary rotations internally. On earlier API levels + * this is not possible. Applications that use {@link TextureView} can apply the rotation by + * calling {@link TextureView#setTransform}. Applications that do not expect to encounter + * rotated videos can safely ignore this parameter. + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case + * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * content. + */ + void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, + float pixelWidthHeightRatio); + + /** + * Called when a frame is rendered for the first time since setting the surface, and when a frame + * is rendered for the first time since the renderer was reset. + * + * @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if + * the renderer renders to something that isn't a {@link Surface}. + */ + void onRenderedFirstFrame(Surface surface); + + /** + * Called when the renderer is disabled. + * + * @param counters {@link DecoderCounters} that were updated by the renderer. + */ + void onVideoDisabled(DecoderCounters counters); + + /** + * Dispatches events to a {@link VideoRendererEventListener}. + */ + final class EventDispatcher { + + private final Handler handler; + private final VideoRendererEventListener listener; + + /** + * @param handler A handler for dispatching events, or null if creating a dummy instance. + * @param listener The listener to which events should be dispatched, or null if creating a + * dummy instance. + */ + public EventDispatcher(Handler handler, VideoRendererEventListener listener) { + this.handler = listener != null ? Assertions.checkNotNull(handler) : null; + this.listener = listener; + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoEnabled(DecoderCounters)}. + */ + public void enabled(final DecoderCounters decoderCounters) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onVideoEnabled(decoderCounters); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoDecoderInitialized(String, long, long)}. + */ + public void decoderInitialized(final String decoderName, + final long initializedTimestampMs, final long initializationDurationMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onVideoDecoderInitialized(decoderName, initializedTimestampMs, + initializationDurationMs); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format)}. + */ + public void inputFormatChanged(final Format format) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onVideoInputFormatChanged(format); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onDroppedFrames(int, long)}. + */ + public void droppedFrames(final int droppedFrameCount, final long elapsedMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onDroppedFrames(droppedFrameCount, elapsedMs); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoSizeChanged(int, int, int, float)}. + */ + public void videoSizeChanged(final int width, final int height, + final int unappliedRotationDegrees, final float pixelWidthHeightRatio) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, + pixelWidthHeightRatio); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onRenderedFirstFrame(Surface)}. + */ + public void renderedFirstFrame(final Surface surface) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onRenderedFirstFrame(surface); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoDisabled(DecoderCounters)}. + */ + public void disabled(final DecoderCounters counters) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + counters.ensureUpdated(); + listener.onVideoDisabled(counters); + } + }); + } + } + + } + +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/EncryptedFileDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/EncryptedFileDataSource.java new file mode 100644 index 0000000..524280c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/EncryptedFileDataSource.java @@ -0,0 +1,127 @@ +package com.tangxiaolv.telegramgallery.secretmedia; + +import android.net.Uri; + +import com.tangxiaolv.telegramgallery.exoplayer2.C; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.TransferListener; + +import java.io.IOException; +import java.io.RandomAccessFile; + +public final class EncryptedFileDataSource implements DataSource { + + /** + * Thrown when IOException is encountered during local file read operation. + */ + public static class EncryptedFileDataSourceException extends IOException { + + public EncryptedFileDataSourceException(IOException cause) { + super(cause); + } + + } + + private final TransferListener listener; + + private RandomAccessFile file; + private Uri uri; + private long bytesRemaining; + private boolean opened; + private byte[] key = new byte[32]; + private byte[] iv = new byte[16]; + private int fileOffset; + + public EncryptedFileDataSource() { + this(null); + } + + public EncryptedFileDataSource(TransferListener listener) { + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws EncryptedFileDataSourceException { + /*try { + uri = dataSpec.uri; + File path = new File(dataSpec.uri.getPath()); + String name = path.getName(); + File keyPath = new File(FileLoader.getInternalCacheDir(), name + ".key"); + RandomAccessFile keyFile = new RandomAccessFile(keyPath, "r"); + keyFile.read(key); + keyFile.read(iv); + keyFile.close(); + + file = new RandomAccessFile(path, "r"); + file.seek(dataSpec.position); + fileOffset = (int) dataSpec.position; + bytesRemaining = dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position : dataSpec.length; + if (bytesRemaining < 0) { + throw new EOFException(); + } + } catch (IOException e) { + throw new EncryptedFileDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + }*/ + + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws EncryptedFileDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } else { + int bytesRead; + try { + bytesRead = file.read(buffer, offset, (int) Math.min(bytesRemaining, readLength)); + //Utilities.aesCtrDecryptionByteArray(buffer, key, iv, offset, bytesRead, fileOffset); + fileOffset += bytesRead; + } catch (IOException e) { + throw new EncryptedFileDataSourceException(e); + } + + if (bytesRead > 0) { + bytesRemaining -= bytesRead; + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + } + + return bytesRead; + } + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws EncryptedFileDataSourceException { + uri = null; + fileOffset = 0; + try { + if (file != null) { + file.close(); + } + } catch (IOException e) { + throw new EncryptedFileDataSourceException(e); + } finally { + file = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/EncryptedFileInputStream.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/EncryptedFileInputStream.java new file mode 100644 index 0000000..a927f60 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/EncryptedFileInputStream.java @@ -0,0 +1,46 @@ +package com.tangxiaolv.telegramgallery.secretmedia; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class EncryptedFileInputStream extends FileInputStream { + + private byte[] key = new byte[32]; + private byte[] iv = new byte[16]; + private int fileOffset; + + public EncryptedFileInputStream(File file, File keyFile) throws Exception { + super(file); + + RandomAccessFile randomAccessFile = new RandomAccessFile(keyFile, "r"); + randomAccessFile.read(key, 0, 32); + randomAccessFile.read(iv, 0, 16); + randomAccessFile.close(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int result = super.read(b, off, len); + //Utilities.aesCtrDecryptionByteArray(b, key, iv, off, len, fileOffset); + fileOffset += len; + return result; + } + + @Override + public long skip(long n) throws IOException { + fileOffset += n; + return super.skip(n); + } + + public static void decryptBytesWithKeyFile(byte[] bytes, int offset, int length, File keyFile) throws Exception { + byte[] key = new byte[32]; + byte[] iv = new byte[16]; + RandomAccessFile randomAccessFile = new RandomAccessFile(keyFile, "r"); + randomAccessFile.read(key, 0, 32); + randomAccessFile.read(iv, 0, 16); + randomAccessFile.close(); + //Utilities.aesCtrDecryptionByteArray(bytes, key, iv, offset, length, 0); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/ExtendedDefaultDataSource.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/ExtendedDefaultDataSource.java new file mode 100644 index 0000000..1951f90 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/ExtendedDefaultDataSource.java @@ -0,0 +1,131 @@ +package com.tangxiaolv.telegramgallery.secretmedia; + +import android.content.Context; +import android.net.Uri; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.AssetDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.ContentDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSpec; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DefaultHttpDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.FileDataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.TransferListener; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Assertions; +import com.tangxiaolv.telegramgallery.exoplayer2.util.Util; + +import java.io.IOException; + +public final class ExtendedDefaultDataSource implements DataSource { + + private static final String SCHEME_ASSET = "asset"; + private static final String SCHEME_CONTENT = "content"; + + private final DataSource baseDataSource; + private final DataSource fileDataSource; + private final DataSource encryptedFileDataSource; + private final DataSource assetDataSource; + private final DataSource contentDataSource; + + private DataSource dataSource; + + /** + * Constructs a new instance, optionally configured to follow cross-protocol redirects. + * + * @param context A context. + * @param listener An optional listener. + * @param userAgent The User-Agent string that should be used when requesting remote data. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled when fetching remote data. + */ + public ExtendedDefaultDataSource(Context context, TransferListener listener, + String userAgent, boolean allowCrossProtocolRedirects) { + this(context, listener, userAgent, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, allowCrossProtocolRedirects); + } + + /** + * Constructs a new instance, optionally configured to follow cross-protocol redirects. + * + * @param context A context. + * @param listener An optional listener. + * @param userAgent The User-Agent string that should be used when requesting remote data. + * @param connectTimeoutMillis The connection timeout that should be used when requesting remote + * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param readTimeoutMillis The read timeout that should be used when requesting remote data, + * in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled when fetching remote data. + */ + public ExtendedDefaultDataSource(Context context, TransferListener listener, + String userAgent, int connectTimeoutMillis, int readTimeoutMillis, + boolean allowCrossProtocolRedirects) { + this(context, listener, + new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis, + readTimeoutMillis, allowCrossProtocolRedirects, null)); + } + + /** + * Constructs a new instance that delegates to a provided {@link DataSource} for URI schemes other + * than file, asset and content. + * + * @param context A context. + * @param listener An optional listener. + * @param baseDataSource A {@link DataSource} to use for URI schemes other than file, asset and + * content. This {@link DataSource} should normally support at least http(s). + */ + public ExtendedDefaultDataSource(Context context, TransferListener listener, + DataSource baseDataSource) { + this.baseDataSource = Assertions.checkNotNull(baseDataSource); + this.fileDataSource = new FileDataSource(listener); + this.encryptedFileDataSource = new EncryptedFileDataSource(listener); + this.assetDataSource = new AssetDataSource(context, listener); + this.contentDataSource = new ContentDataSource(context, listener); + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + Assertions.checkState(dataSource == null); + // Choose the correct source for the scheme. + String scheme = dataSpec.uri.getScheme(); + if (Util.isLocalFileUri(dataSpec.uri)) { + if (dataSpec.uri.getPath().startsWith("/android_asset/")) { + dataSource = assetDataSource; + } else { + if (dataSpec.uri.getPath().endsWith(".enc")) { + dataSource = encryptedFileDataSource; + } else { + dataSource = fileDataSource; + } + } + } else if (SCHEME_ASSET.equals(scheme)) { + dataSource = assetDataSource; + } else if (SCHEME_CONTENT.equals(scheme)) { + dataSource = contentDataSource; + } else { + dataSource = baseDataSource; + } + // Open the source and return. + return dataSource.open(dataSpec); + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + return dataSource.read(buffer, offset, readLength); + } + + @Override + public Uri getUri() { + return dataSource == null ? null : dataSource.getUri(); + } + + @Override + public void close() throws IOException { + if (dataSource != null) { + try { + dataSource.close(); + } finally { + dataSource = null; + } + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/ExtendedDefaultDataSourceFactory.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/ExtendedDefaultDataSourceFactory.java new file mode 100644 index 0000000..bf9c9c8 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/secretmedia/ExtendedDefaultDataSourceFactory.java @@ -0,0 +1,51 @@ +package com.tangxiaolv.telegramgallery.secretmedia; + +import android.content.Context; + +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DataSource; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.tangxiaolv.telegramgallery.exoplayer2.upstream.TransferListener; + +public final class ExtendedDefaultDataSourceFactory implements DataSource.Factory { + + private final Context context; + private final TransferListener listener; + private final DataSource.Factory baseDataSourceFactory; + + /** + * @param context A context. + * @param userAgent The User-Agent string that should be used. + */ + public ExtendedDefaultDataSourceFactory(Context context, String userAgent) { + this(context, userAgent, null); + } + + /** + * @param context A context. + * @param userAgent The User-Agent string that should be used. + * @param listener An optional listener. + */ + public ExtendedDefaultDataSourceFactory(Context context, String userAgent, + TransferListener listener) { + this(context, listener, new DefaultHttpDataSourceFactory(userAgent, listener)); + } + + /** + * @param context A context. + * @param listener An optional listener. + * @param baseDataSourceFactory A {@link DataSource.Factory} to be used to create a base {@link DataSource} + * for {@link DefaultDataSource}. + * @see DefaultDataSource#DefaultDataSource(Context, TransferListener, DataSource) + */ + public ExtendedDefaultDataSourceFactory(Context context, TransferListener listener, + DataSource.Factory baseDataSourceFactory) { + this.context = context.getApplicationContext(); + this.listener = listener; + this.baseDataSourceFactory = baseDataSourceFactory; + } + + @Override + public ExtendedDefaultDataSource createDataSource() { + return new ExtendedDefaultDataSource(context, listener, baseDataSourceFactory.createDataSource()); + } +} \ No newline at end of file diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/AbstractSerializedData.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/AbstractSerializedData.java similarity index 96% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/AbstractSerializedData.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/AbstractSerializedData.java index 3567ced..5cb7d3c 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/AbstractSerializedData.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/AbstractSerializedData.java @@ -1,4 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; + public abstract class AbstractSerializedData { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/BotInlineResult.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/BotInlineResult.java new file mode 100644 index 0000000..82e5da9 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/BotInlineResult.java @@ -0,0 +1,153 @@ +package com.tangxiaolv.telegramgallery.tl; + +public class BotInlineResult extends TLObject { + public int flags; + public String id; + public String type; + public String title; + public String description; + public String url; + public String thumb_url; + public String content_url; + public String content_type; + public int w; + public int h; + public int duration; + public Photo photo; + public Document document; + public long query_id; + + public static BotInlineResult TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + BotInlineResult result = null; + switch (constructor) { + case 0x9bebaeb9: + result = new TL_botInlineResult(); + break; + case 0x17db940b: + result = new TL_botInlineMediaResult(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in BotInlineResult", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + + public static class TL_botInlineResult extends BotInlineResult { + public static int constructor = 0x9bebaeb9; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + id = stream.readString(exception); + type = stream.readString(exception); + if ((flags & 2) != 0) { + title = stream.readString(exception); + } + if ((flags & 4) != 0) { + description = stream.readString(exception); + } + if ((flags & 8) != 0) { + url = stream.readString(exception); + } + if ((flags & 16) != 0) { + thumb_url = stream.readString(exception); + } + if ((flags & 32) != 0) { + content_url = stream.readString(exception); + } + if ((flags & 32) != 0) { + content_type = stream.readString(exception); + } + if ((flags & 64) != 0) { + w = stream.readInt32(exception); + } + if ((flags & 64) != 0) { + h = stream.readInt32(exception); + } + if ((flags & 128) != 0) { + duration = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeString(id); + stream.writeString(type); + if ((flags & 2) != 0) { + stream.writeString(title); + } + if ((flags & 4) != 0) { + stream.writeString(description); + } + if ((flags & 8) != 0) { + stream.writeString(url); + } + if ((flags & 16) != 0) { + stream.writeString(thumb_url); + } + if ((flags & 32) != 0) { + stream.writeString(content_url); + } + if ((flags & 32) != 0) { + stream.writeString(content_type); + } + if ((flags & 64) != 0) { + stream.writeInt32(w); + } + if ((flags & 64) != 0) { + stream.writeInt32(h); + } + if ((flags & 128) != 0) { + stream.writeInt32(duration); + } + } + } + + public static class TL_botInlineMediaResult extends BotInlineResult { + public static int constructor = 0x17db940b; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + id = stream.readString(exception); + type = stream.readString(exception); + if ((flags & 1) != 0) { + photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 2) != 0) { + document = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 4) != 0) { + title = stream.readString(exception); + } + if ((flags & 8) != 0) { + description = stream.readString(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeString(id); + stream.writeString(type); + if ((flags & 1) != 0) { + photo.serializeToStream(stream); + } + if ((flags & 2) != 0) { + document.serializeToStream(stream); + } + if ((flags & 4) != 0) { + stream.writeString(title); + } + if ((flags & 8) != 0) { + stream.writeString(description); + } + } + } +} + diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/Document.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/Document.java similarity index 76% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/Document.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/Document.java index 32fca5d..2f12fdb 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/Document.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/Document.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; import java.util.ArrayList; @@ -12,16 +12,19 @@ public class Document extends TLObject { public String mime_type; public int size; public PhotoSize thumb; + public int version; public int dc_id; public byte[] key; public byte[] iv; public String caption; public ArrayList attributes = new ArrayList<>(); - public static Document TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + public static Document TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Document result = null; switch (constructor) { + case 0x87232bc7: + result = new TL_document(); + break; case 0x55555556: result = new TL_documentEncrypted_old(); break; @@ -35,12 +38,11 @@ public static Document TLdeserialize(AbstractSerializedData stream, int construc result = new TL_documentEncrypted(); break; case 0xf9a39f4f: - result = new TL_document(); + result = new TL_document_layer53(); break; } if (result == null && exception) { - throw new RuntimeException( - String.format("can't parse magic %x in Document", constructor)); + throw new RuntimeException(String.format("can't parse magic %x in Document", constructor)); } if (result != null) { result.readParams(stream, exception); @@ -51,6 +53,7 @@ public static Document TLdeserialize(AbstractSerializedData stream, int construc public static class TL_documentEncrypted_old extends TL_document { public static int constructor = 0x55555556; + public void readParams(AbstractSerializedData stream, boolean exception) { id = stream.readInt64(exception); access_hash = stream.readInt64(exception); @@ -84,6 +87,7 @@ public void serializeToStream(AbstractSerializedData stream) { public static class TL_document_old extends TL_document { public static int constructor = 0x9efc6326; + public void readParams(AbstractSerializedData stream, boolean exception) { id = stream.readInt64(exception); access_hash = stream.readInt64(exception); @@ -113,6 +117,7 @@ public void serializeToStream(AbstractSerializedData stream) { public static class TL_documentEmpty extends Document { public static int constructor = 0x36f8c871; + public void readParams(AbstractSerializedData stream, boolean exception) { id = stream.readInt64(exception); } @@ -126,11 +131,12 @@ public void serializeToStream(AbstractSerializedData stream) { public static class TL_documentEncrypted extends Document { public static int constructor = 0x55555558; + public void readParams(AbstractSerializedData stream, boolean exception) { id = stream.readInt64(exception); access_hash = stream.readInt64(exception); date = stream.readInt32(exception); - int startReadPosiition = stream.getPosition(); // TODO remove this hack after some time + int startReadPosiition = stream.getPosition(); //TODO remove this hack after some time try { mime_type = stream.readString(true); } catch (Exception e) { @@ -151,8 +157,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } int count = stream.readInt32(exception); for (int a = 0; a < count; a++) { - DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, - stream.readInt32(exception), exception); + DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); if (object == null) { return; } @@ -182,9 +187,57 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_document extends Document { + public static class TL_document_layer53 extends TL_document { public static int constructor = 0xf9a39f4f; + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + date = stream.readInt32(exception); + mime_type = stream.readString(exception); + size = stream.readInt32(exception); + thumb = PhotoSize.TLdeserialize(stream, stream.readInt32(exception), exception); + dc_id = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + attributes.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeInt64(access_hash); + stream.writeInt32(date); + stream.writeString(mime_type); + stream.writeInt32(size); + thumb.serializeToStream(stream); + stream.writeInt32(dc_id); + stream.writeInt32(0x1cb5c415); + int count = attributes.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + attributes.get(a).serializeToStream(stream); + } + } + } + + public static class TL_document extends Document { + public static int constructor = 0x87232bc7; + + public void readParams(AbstractSerializedData stream, boolean exception) { id = stream.readInt64(exception); access_hash = stream.readInt64(exception); @@ -193,6 +246,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { size = stream.readInt32(exception); thumb = PhotoSize.TLdeserialize(stream, stream.readInt32(exception), exception); dc_id = stream.readInt32(exception); + version = stream.readInt32(exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -202,8 +256,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } int count = stream.readInt32(exception); for (int a = 0; a < count; a++) { - DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, - stream.readInt32(exception), exception); + DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); if (object == null) { return; } @@ -220,6 +273,7 @@ public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(size); thumb.serializeToStream(stream); stream.writeInt32(dc_id); + stream.writeInt32(version); stream.writeInt32(0x1cb5c415); int count = attributes.size(); stream.writeInt32(count); diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/DocumentAttribute.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/DocumentAttribute.java similarity index 98% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/DocumentAttribute.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/DocumentAttribute.java index 886f0dd..dd51318 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/DocumentAttribute.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/DocumentAttribute.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class DocumentAttribute extends TLObject { public int w; @@ -15,7 +15,7 @@ public class DocumentAttribute extends TLObject { public String file_name; public static DocumentAttribute TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { DocumentAttribute result = null; switch (constructor) { case 0x11b58939: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/FileLocation.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/FileLocation.java similarity index 96% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/FileLocation.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/FileLocation.java index 0e711c2..3da41f3 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/FileLocation.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/FileLocation.java @@ -1,5 +1,6 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; + public class FileLocation extends TLObject { public int dc_id; @@ -10,7 +11,7 @@ public class FileLocation extends TLObject { public byte[] iv; public static FileLocation TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { FileLocation result = null; switch (constructor) { case 0x53d69076: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/GeoPoint.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/GeoPoint.java similarity index 93% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/GeoPoint.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/GeoPoint.java index b92ba75..4d3a7ca 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/GeoPoint.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/GeoPoint.java @@ -1,12 +1,12 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class GeoPoint extends TLObject { public double _long; public double lat; public static GeoPoint TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { GeoPoint result = null; switch (constructor) { case 0x1117dd5f: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputEncryptedFile.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputEncryptedFile.java similarity index 96% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputEncryptedFile.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputEncryptedFile.java index da8ebe6..6954a2f 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputEncryptedFile.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputEncryptedFile.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class InputEncryptedFile extends TLObject { public long id; @@ -9,7 +9,7 @@ public class InputEncryptedFile extends TLObject { public String md5_checksum; public static InputEncryptedFile TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { InputEncryptedFile result = null; switch (constructor) { case 0x5a17b5e5: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputFile.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputFile.java similarity index 95% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputFile.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputFile.java index 3e666da..ae0722f 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputFile.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputFile.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class InputFile extends TLObject { public long id; @@ -8,7 +8,7 @@ public class InputFile extends TLObject { public String md5_checksum; public static InputFile TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { InputFile result = null; switch (constructor) { case 0xfa4f0bb5: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputFileLocation.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputFileLocation.java similarity index 95% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputFileLocation.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputFileLocation.java index 6526e07..30ae6e5 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputFileLocation.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputFileLocation.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class InputFileLocation extends TLObject { public long id; @@ -9,7 +9,7 @@ public class InputFileLocation extends TLObject { public long secret; public static InputFileLocation TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { InputFileLocation result = null; switch (constructor) { case 0xf5235d55: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputStickerSet.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputStickerSet.java similarity index 95% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputStickerSet.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputStickerSet.java index bcb0114..ce069ed 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/InputStickerSet.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/InputStickerSet.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class InputStickerSet extends TLObject { public long id; @@ -7,7 +7,7 @@ public class InputStickerSet extends TLObject { public String short_name; public static InputStickerSet TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { InputStickerSet result = null; switch (constructor) { case 0xffb62b95: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/NativeByteBuffer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/NativeByteBuffer.java similarity index 99% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/NativeByteBuffer.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/NativeByteBuffer.java index d2fa18d..ba95ef9 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/NativeByteBuffer.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/NativeByteBuffer.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; import java.nio.ByteBuffer; import java.nio.ByteOrder; diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/Photo.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/Photo.java similarity index 98% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/Photo.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/Photo.java index 5b473de..58b2eeb 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/Photo.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/Photo.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; import java.util.ArrayList; @@ -13,7 +13,7 @@ public class Photo extends TLObject { public ArrayList sizes = new ArrayList<>(); public static Photo TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { Photo result = null; switch (constructor) { case 0x22b56751: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/PhotoSize.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/PhotoSize.java similarity index 97% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/PhotoSize.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/PhotoSize.java index d7ac5cb..cf311f9 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/PhotoSize.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/PhotoSize.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class PhotoSize extends TLObject { public String type; @@ -10,7 +10,7 @@ public class PhotoSize extends TLObject { public byte[] bytes; public static PhotoSize TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { PhotoSize result = null; switch (constructor) { case 0x77bfb61b: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/TLObject.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/TLObject.java similarity index 91% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/TLObject.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/TLObject.java index 622b64c..ec8a760 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/TLObject.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/TLObject.java @@ -6,7 +6,7 @@ * Copyright Nikolai Kudashov, 2013-2016. */ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class TLObject { @@ -31,7 +31,7 @@ public void serializeToStream(AbstractSerializedData stream) { } public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { return null; } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/TL_upload_file.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/TL_upload_file.java similarity index 91% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/TL_upload_file.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/TL_upload_file.java index 671cb93..9f54e99 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/TL_upload_file.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/TL_upload_file.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class TL_upload_file extends TLObject { public static int constructor = 0x96a18d5; @@ -9,7 +9,7 @@ public class TL_upload_file extends TLObject { public NativeByteBuffer bytes; public static TL_upload_file TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { if (TL_upload_file.constructor != constructor) { if (exception) { throw new RuntimeException( diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/storage_FileType.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/storage_FileType.java similarity index 98% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/storage_FileType.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/storage_FileType.java index d401a58..a1c8faf 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/TL/storage_FileType.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/tl/storage_FileType.java @@ -1,10 +1,10 @@ -package com.tangxiaolv.telegramgallery.TL; +package com.tangxiaolv.telegramgallery.tl; public class storage_FileType extends TLObject { public static storage_FileType TLdeserialize(AbstractSerializedData stream, int constructor, - boolean exception) { + boolean exception) { storage_FileType result = null; switch (constructor) { case 0xaa963b05: diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/AndroidUtilities.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/AndroidUtilities.java similarity index 94% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/AndroidUtilities.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/AndroidUtilities.java index 73e56cb..244ee4e 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/AndroidUtilities.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/AndroidUtilities.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.Utils; +package com.tangxiaolv.telegramgallery.utils; import android.annotation.SuppressLint; import android.content.ContentUris; @@ -8,8 +8,10 @@ import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -33,7 +35,6 @@ import android.widget.Toast; import com.tangxiaolv.telegramgallery.Gallery; -import com.tangxiaolv.telegramgallery.R; import java.io.File; import java.lang.reflect.Field; @@ -112,6 +113,12 @@ public static void showToast(String text) { toast.show(); } + public static void cancelToast() { + if (toast != null) { + toast.cancel(); + } + } + public static void showKeyboard(View view) { if (view == null) { return; @@ -256,7 +263,7 @@ public static void cancelRunOnUIThread(Runnable runnable) { public static boolean isTablet() { if (isTablet == null) { - isTablet = Gallery.applicationContext.getResources().getBoolean(R.bool.isTablet); + isTablet = false; } return isTablet; } @@ -591,4 +598,40 @@ public static boolean isRTL() { return directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC; } + + public static void setRectToRect(Matrix matrix, RectF src, RectF dst, int rotation, Matrix.ScaleToFit align) { + float tx, sx; + float ty, sy; + if (rotation == 90 || rotation == 270) { + sx = dst.height() / src.width(); + sy = dst.width() / src.height(); + } else { + sx = dst.width() / src.width(); + sy = dst.height() / src.height(); + } + if (align != Matrix.ScaleToFit.FILL) { + if (sx > sy) { + sx = sy; + } else { + sy = sx; + } + } + tx = -src.left * sx; + ty = -src.top * sy; + + matrix.setTranslate(dst.left, dst.top); + if (rotation == 90) { + matrix.preRotate(90); + matrix.preTranslate(0, -dst.width()); + } else if (rotation == 180) { + matrix.preRotate(180); + matrix.preTranslate(-dst.width(), -dst.height()); + } else if (rotation == 270) { + matrix.preRotate(270); + matrix.preTranslate(-dst.height(), 0); + } + + matrix.preScale(sx, sy); + matrix.preTranslate(tx, ty); + } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/Bitmaps.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Bitmaps.java similarity index 99% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/Bitmaps.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Bitmaps.java index bff0529..c356bea 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/Bitmaps.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Bitmaps.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Utils; +package com.tangxiaolv.telegramgallery.utils; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -214,6 +214,7 @@ public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int he canvas.setBitmap(null); } catch (Exception e) { //don't promt, this will crash on 2.x + e.getStackTrace(); } return bitmap; } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Constants.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Constants.java new file mode 100644 index 0000000..616822d --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Constants.java @@ -0,0 +1,14 @@ +package com.tangxiaolv.telegramgallery.utils; + +public class Constants { + + public static boolean DARK_THEME = false; + //失败 + public static int STATE_FAIL = -1; + //成功 + public static int STATE_SUCCESS = 0; + //原图过大 + public static int STATE_IMAGE_SIZE_OUT = 1; + //视频时间过长 + public static int STATE_VIDEO_TIME_OUT = 2; +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/FileLoadOperation.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLoadOperation.java similarity index 84% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/FileLoadOperation.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLoadOperation.java index cde1a7c..42bb071 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/FileLoadOperation.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLoadOperation.java @@ -1,9 +1,9 @@ -package com.tangxiaolv.telegramgallery.Utils; +package com.tangxiaolv.telegramgallery.utils; -import com.tangxiaolv.telegramgallery.TL.Document; -import com.tangxiaolv.telegramgallery.TL.FileLocation; -import com.tangxiaolv.telegramgallery.TL.InputFileLocation; -import com.tangxiaolv.telegramgallery.TL.TL_upload_file; +import com.tangxiaolv.telegramgallery.tl.Document; +import com.tangxiaolv.telegramgallery.tl.FileLocation; +import com.tangxiaolv.telegramgallery.tl.InputFileLocation; +import com.tangxiaolv.telegramgallery.tl.TL_upload_file; import java.io.File; import java.io.RandomAccessFile; @@ -56,10 +56,13 @@ private static class RequestInfo { private File storePath; private File tempPath; private boolean isForceRequest; + private boolean started; public interface FileLoadOperationDelegate { void didFinishLoadingFile(FileLoadOperation operation, File finalFile); + void didFailedLoadingFile(FileLoadOperation operation, int state); + void didChangedLoadProgress(FileLoadOperation operation, float progress); } @@ -159,61 +162,49 @@ public void setPaths(File store, File temp) { tempPath = temp; } - public void start() { + public boolean wasStarted() { + return started; + } + + public boolean start() { if (state != stateIdle) { - return; + return false; } - currentDownloadChunkSize = totalBytesCount >= bigFileSizeFrom ? downloadChunkSizeBig : downloadChunkSize; - currentMaxDownloadRequests = totalBytesCount >= bigFileSizeFrom ? maxDownloadRequestsBig : maxDownloadRequests; - requestInfos = new ArrayList<>(currentMaxDownloadRequests); - delayedRequestInfos = new ArrayList<>(currentMaxDownloadRequests - 1); - state = stateDownloading; if (location == null) { - cleanup(); - Utilities.stageQueue.postRunnable(new Runnable() { - @Override - public void run() { - delegate.didFailedLoadingFile(FileLoadOperation.this, 0); - } - }); - return; + onFail(true, 0); + return false; } + String fileNameFinal; String fileNameTemp; String fileNameIv = null; if (location.volume_id != 0 && location.local_id != 0) { + if (datacenter_id == Integer.MIN_VALUE || location.volume_id == Integer.MIN_VALUE || datacenter_id == 0) { + onFail(true, 0); + return false; + } + fileNameTemp = location.volume_id + "_" + location.local_id + ".temp"; fileNameFinal = location.volume_id + "_" + location.local_id + "." + ext; if (key != null) { fileNameIv = location.volume_id + "_" + location.local_id + ".iv"; } - if (datacenter_id == Integer.MIN_VALUE || location.volume_id == Integer.MIN_VALUE || datacenter_id == 0) { - cleanup(); - Utilities.stageQueue.postRunnable(new Runnable() { - @Override - public void run() { - delegate.didFailedLoadingFile(FileLoadOperation.this, 0); - } - }); - return; - } } else { + if (datacenter_id == 0 || location.id == 0) { + onFail(true, 0); + return false; + } fileNameTemp = datacenter_id + "_" + location.id + ".temp"; fileNameFinal = datacenter_id + "_" + location.id + ext; if (key != null) { fileNameIv = datacenter_id + "_" + location.id + ".iv"; } - if (datacenter_id == 0 || location.id == 0) { - cleanup(); - Utilities.stageQueue.postRunnable(new Runnable() { - @Override - public void run() { - delegate.didFailedLoadingFile(FileLoadOperation.this, 0); - } - }); - return; - } } + currentDownloadChunkSize = totalBytesCount >= bigFileSizeFrom ? downloadChunkSizeBig : downloadChunkSize; + currentMaxDownloadRequests = totalBytesCount >= bigFileSizeFrom ? maxDownloadRequestsBig : maxDownloadRequests; + requestInfos = new ArrayList<>(currentMaxDownloadRequests); + delayedRequestInfos = new ArrayList<>(currentMaxDownloadRequests - 1); + state = stateDownloading; cacheFileFinal = new File(storePath, fileNameFinal); boolean exist = cacheFileFinal.exists(); @@ -223,23 +214,31 @@ public void run() { if (!cacheFileFinal.exists()) { cacheFileTemp = new File(tempPath, fileNameTemp); + boolean newKeyGenerated = false; + if (cacheFileTemp.exists()) { - downloadedBytes = (int) cacheFileTemp.length(); - nextDownloadOffset = downloadedBytes = downloadedBytes / currentDownloadChunkSize * currentDownloadChunkSize; + if (newKeyGenerated) { + cacheFileTemp.delete(); + } else { + downloadedBytes = (int) cacheFileTemp.length(); + nextDownloadOffset = downloadedBytes = downloadedBytes / currentDownloadChunkSize * currentDownloadChunkSize; + } } if (fileNameIv != null) { cacheIvTemp = new File(tempPath, fileNameIv); try { fiv = new RandomAccessFile(cacheIvTemp, "rws"); - long len = cacheIvTemp.length(); - if (len > 0 && len % 32 == 0) { - fiv.read(iv, 0, 32); - } else { - downloadedBytes = 0; + if (!newKeyGenerated) { + long len = cacheIvTemp.length(); + if (len > 0 && len % 32 == 0) { + fiv.read(iv, 0, 32); + } else { + downloadedBytes = 0; + } } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); downloadedBytes = 0; } } @@ -249,37 +248,49 @@ public void run() { fileOutputStream.seek(downloadedBytes); } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } if (fileOutputStream == null) { - cleanup(); - Utilities.stageQueue.postRunnable(new Runnable() { - @Override - public void run() { - delegate.didFailedLoadingFile(FileLoadOperation.this, 0); - } - }); - return; + onFail(true, 0); + return false; } + started = true; Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { if (totalBytesCount != 0 && downloadedBytes == totalBytesCount) { try { - onFinishLoadingFile(); + onFinishLoadingFile(false); } catch (Exception e) { - delegate.didFailedLoadingFile(FileLoadOperation.this, 0); + onFail(true, 0); } } } }); } else { + started = true; try { - onFinishLoadingFile(); + onFinishLoadingFile(false); } catch (Exception e) { - delegate.didFailedLoadingFile(FileLoadOperation.this, 0); + onFail(true, 0); } } + return true; + } + + private void onFail(boolean thread, final int reason) { + cleanup(); + state = stateFailed; + if (thread) { + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + delegate.didFailedLoadingFile(FileLoadOperation.this, reason); + } + }); + } else { + delegate.didFailedLoadingFile(FileLoadOperation.this, reason); + } } public void cancel() { @@ -336,7 +347,7 @@ private void cleanup() { } } - private void onFinishLoadingFile() throws Exception { + private void onFinishLoadingFile(final boolean increment) throws Exception { if (state != stateDownloading) { return; } @@ -356,9 +367,9 @@ private void onFinishLoadingFile() throws Exception { @Override public void run() { try { - onFinishLoadingFile(); + onFinishLoadingFile(increment); } catch (Exception e) { - delegate.didFailedLoadingFile(FileLoadOperation.this, 0); + onFail(false, 0); } } }, 200); diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/FileLoader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLoader.java similarity index 66% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/FileLoader.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLoader.java index 8049120..592dd8f 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/FileLoader.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLoader.java @@ -1,11 +1,14 @@ -package com.tangxiaolv.telegramgallery.Utils; +package com.tangxiaolv.telegramgallery.utils; import com.tangxiaolv.telegramgallery.DispatchQueue; -import com.tangxiaolv.telegramgallery.TL.Document; -import com.tangxiaolv.telegramgallery.TL.DocumentAttribute; -import com.tangxiaolv.telegramgallery.TL.FileLocation; -import com.tangxiaolv.telegramgallery.TL.PhotoSize; -import com.tangxiaolv.telegramgallery.TL.TLObject; +import com.tangxiaolv.telegramgallery.Gallery; +import com.tangxiaolv.telegramgallery.tl.Document; +import com.tangxiaolv.telegramgallery.tl.DocumentAttribute; +import com.tangxiaolv.telegramgallery.tl.FileLocation; +import com.tangxiaolv.telegramgallery.tl.InputEncryptedFile; +import com.tangxiaolv.telegramgallery.tl.InputFile; +import com.tangxiaolv.telegramgallery.tl.PhotoSize; +import com.tangxiaolv.telegramgallery.tl.TLObject; import java.io.File; import java.util.ArrayList; @@ -17,6 +20,12 @@ public class FileLoader { public interface FileLoaderDelegate { + void fileUploadProgressChanged(String location, float progress, boolean isEncrypted); + + void fileDidUploaded(String location, InputFile inputFile, InputEncryptedFile inputEncryptedFile, byte[] key, byte[] iv, long totalFileSize); + + void fileDidFailedUpload(String location, boolean isEncrypted); + void fileDidLoaded(String location, File finalFile, int type); void fileDidFailedLoad(String location, int state); @@ -80,7 +89,8 @@ public File getDirectory(int type) { dir.mkdirs(); } } catch (Exception e) { - // don't promt + //don't promt + e.getStackTrace(); } return dir; } @@ -97,8 +107,7 @@ public void cancelLoadFile(FileLocation location, String ext) { cancelLoadFile(null, location, ext); } - private void cancelLoadFile(final Document document, final FileLocation location, - final String locationExt) { + private void cancelLoadFile(final Document document, final FileLocation location, final String locationExt) { if (location == null && document == null) { return; } @@ -117,9 +126,13 @@ public void run() { FileLoadOperation operation = loadOperationPaths.remove(fileName); if (operation != null) { if (location != null) { - photoLoadOperationQueue.remove(operation); + if (!photoLoadOperationQueue.remove(operation)) { + currentPhotoLoadOperationsCount--; + } } else { - loadOperationQueue.remove(operation); + if (!loadOperationQueue.remove(operation)) { + currentLoadOperationsCount--; + } } operation.cancel(); } @@ -140,29 +153,37 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } return result[0]; } - public void loadFile(PhotoSize photo, String ext, boolean cacheOnly) { - loadFile(null, photo.location, ext, photo.size, false, - cacheOnly || (photo != null && photo.size == 0 || photo.location.key != null)); + public void loadFile(PhotoSize photo, String ext, int cacheType) { + if (cacheType == 0 && (photo != null && photo.size == 0 || photo.location.key != null)) { + cacheType = 1; + } + loadFile(null, photo.location, ext, photo.size, false, cacheType); + } + + public void loadFile(Document document, boolean force, int cacheType) { + if (cacheType == 0 && (document != null && document.key != null)) { + cacheType = 1; + } + loadFile(document, null, null, 0, force, cacheType); } - public void loadFile(Document document, boolean force, boolean cacheOnly) { - loadFile(document, null, null, 0, force, - cacheOnly || document != null && document.key != null); + public void loadFile(boolean force, int cacheType) { + loadFile(null, null, null, 0, force, cacheType); } - public void loadFile(FileLocation location, String ext, int size, boolean cacheOnly) { - loadFile(null, location, ext, size, true, - cacheOnly || size == 0 || (location != null && location.key != null)); + public void loadFile(FileLocation location, String ext, int size, int cacheType) { + if (cacheType == 0 && (size == 0 || location != null && location.key != null)) { + cacheType = 1; + } + loadFile(null, location, ext, size, true, cacheType); } - private void loadFile(final Document document, final FileLocation location, - final String locationExt, final int locationSize, final boolean force, - final boolean cacheOnly) { + private void loadFile(final Document document, final FileLocation location, final String locationExt, final int locationSize, final boolean force, final int cacheType) { fileLoaderQueue.postRunnable(new Runnable() { @Override public void run() { @@ -180,18 +201,14 @@ public void run() { operation = loadOperationPaths.get(fileName); if (operation != null) { if (force) { + operation.setForceRequest(true); LinkedList downloadQueue; - if (location != null) { - downloadQueue = photoLoadOperationQueue; - } else { - downloadQueue = loadOperationQueue; - } + downloadQueue = loadOperationQueue; if (downloadQueue != null) { int index = downloadQueue.indexOf(operation); - if (index != -1) { + if (index > 0) { downloadQueue.remove(index); downloadQueue.add(0, operation); - operation.setForceRequest(true); } } } @@ -207,16 +224,16 @@ public void run() { type = MEDIA_DIR_IMAGE; } else if (document != null) { operation = new FileLoadOperation(document); + type = MEDIA_DIR_DOCUMENT; } - if (!cacheOnly) { + if (cacheType == 0) { storeDir = getDirectory(type); } operation.setPaths(storeDir, tempDir); final String finalFileName = fileName; final int finalType = type; - loadOperationPaths.put(fileName, operation); - operation.setDelegate(new FileLoadOperation.FileLoadOperationDelegate() { + FileLoadOperation.FileLoadOperationDelegate fileLoadOperationDelegate = new FileLoadOperation.FileLoadOperationDelegate() { @Override public void didFinishLoadingFile(FileLoadOperation operation, File finalFile) { if (delegate != null) { @@ -226,26 +243,58 @@ public void didFinishLoadingFile(FileLoadOperation operation, File finalFile) { } @Override - public void didFailedLoadingFile(FileLoadOperation operation, int canceled) { + public void didFailedLoadingFile(FileLoadOperation operation, int reason) { checkDownloadQueue(document, location, finalFileName); if (delegate != null) { - delegate.fileDidFailedLoad(finalFileName, canceled); + delegate.fileDidFailedLoad(finalFileName, reason); } } @Override - public void didChangedLoadProgress(FileLoadOperation operation, - float progress) { + public void didChangedLoadProgress(FileLoadOperation operation, float progress) { if (delegate != null) { delegate.fileLoadProgressChanged(finalFileName, progress); } } - }); + }; + operation.setDelegate(fileLoadOperationDelegate); + + /*if (location != null) { + operation = new FileLoadOperation(location.dc_id, location.volume_id, location.volume_id, location.secret, location.local_id, location.key, location.iv, locationExt != null ? locationExt : "jpg", 0, locationSize, !cacheOnly ? getDirectory(type) : tempDir, tempDir, fileLoadOperationDelegate); + } else if (document != null) { + String ext = FileLoader.getDocumentFileName(document); + int idx; + if (ext == null || (idx = ext.lastIndexOf('.')) == -1) { + ext = ""; + } else { + ext = ext.substring(idx + 1); + } + if (ext.length() <= 0) { + if (document.mime_type != null) { + switch (document.mime_type) { + case "video/mp4": + ext = "mp4"; + break; + case "audio/ogg": + ext = "ogg"; + break; + default: + ext = ""; + break; + } + } else { + ext = ""; + } + } + operation = new FileLoadOperation(document.dc_id, document.id, 0, document.access_hash, 0, document.key, document.iv, ext, document.version, document.size, !cacheOnly ? getDirectory(type) : tempDir, tempDir, fileLoadOperationDelegate); + }*/ + loadOperationPaths.put(fileName, operation); int maxCount = force ? 3 : 1; if (type == MEDIA_DIR_AUDIO) { if (currentAudioLoadOperationsCount < maxCount) { - currentAudioLoadOperationsCount++; - operation.start(); + if (operation.start()) { + currentAudioLoadOperationsCount++; + } } else { if (force) { audioLoadOperationQueue.add(0, operation); @@ -255,8 +304,9 @@ public void didChangedLoadProgress(FileLoadOperation operation, } } else if (location != null) { if (currentPhotoLoadOperationsCount < maxCount) { - currentPhotoLoadOperationsCount++; - operation.start(); + if (operation.start()) { + currentPhotoLoadOperationsCount++; + } } else { if (force) { photoLoadOperationQueue.add(0, operation); @@ -266,8 +316,9 @@ public void didChangedLoadProgress(FileLoadOperation operation, } } else { if (currentLoadOperationsCount < maxCount) { - currentLoadOperationsCount++; - operation.start(); + if (operation.start()) { + currentLoadOperationsCount++; + } } else { if (force) { loadOperationQueue.add(0, operation); @@ -280,38 +331,28 @@ public void didChangedLoadProgress(FileLoadOperation operation, }); } - private void checkDownloadQueue(final Document document, final FileLocation location, - final String arg1) { + private void checkDownloadQueue(final Document document, final FileLocation location, final String arg1) { fileLoaderQueue.postRunnable(new Runnable() { @Override public void run() { - loadOperationPaths.remove(arg1); - FileLoadOperation operation; - if (location != null) { - currentPhotoLoadOperationsCount--; - if (!photoLoadOperationQueue.isEmpty()) { - operation = photoLoadOperationQueue.get(0); - int maxCount = operation.isForceRequest() ? 3 : 1; - if (currentPhotoLoadOperationsCount < maxCount) { - operation = photoLoadOperationQueue.poll(); - if (operation != null) { - currentPhotoLoadOperationsCount++; - operation.start(); - } - } + FileLoadOperation operation = loadOperationPaths.remove(arg1); + if (operation != null) { + if (operation.wasStarted()) { + currentLoadOperationsCount--; + } else { + loadOperationQueue.remove(operation); } - } else { - currentLoadOperationsCount--; - if (!loadOperationQueue.isEmpty()) { - operation = loadOperationQueue.get(0); - int maxCount = operation.isForceRequest() ? 3 : 1; - if (currentLoadOperationsCount < maxCount) { - operation = loadOperationQueue.poll(); - if (operation != null) { - currentLoadOperationsCount++; - operation.start(); - } + } + while (!loadOperationQueue.isEmpty()) { + operation = loadOperationQueue.get(0); + int maxCount = operation.isForceRequest() ? 3 : 1; + if (currentLoadOperationsCount < maxCount) { + operation = loadOperationQueue.poll(); + if (operation != null && operation.start()) { + currentLoadOperationsCount++; } + } else { + break; } } } @@ -344,18 +385,14 @@ public static File getPathToAttach(TLObject attach, String ext, boolean forceCac } } else if (attach instanceof PhotoSize) { PhotoSize photoSize = (PhotoSize) attach; - if (photoSize.location == null || photoSize.location.key != null - || photoSize.location.volume_id == Integer.MIN_VALUE - && photoSize.location.local_id < 0 - || photoSize.size < 0) { + if (photoSize.location == null || photoSize.location.key != null || photoSize.location.volume_id == Integer.MIN_VALUE && photoSize.location.local_id < 0 || photoSize.size < 0) { dir = getInstance().getDirectory(MEDIA_DIR_CACHE); } else { dir = getInstance().getDirectory(MEDIA_DIR_IMAGE); } } else if (attach instanceof FileLocation) { FileLocation fileLocation = (FileLocation) attach; - if (fileLocation.key != null || fileLocation.volume_id == Integer.MIN_VALUE - && fileLocation.local_id < 0) { + if (fileLocation.key != null || fileLocation.volume_id == Integer.MIN_VALUE && fileLocation.local_id < 0) { dir = getInstance().getDirectory(MEDIA_DIR_CACHE); } else { dir = getInstance().getDirectory(MEDIA_DIR_IMAGE); @@ -372,8 +409,7 @@ public static PhotoSize getClosestPhotoSizeWithSize(ArrayList sizes, return getClosestPhotoSizeWithSize(sizes, side, false); } - public static PhotoSize getClosestPhotoSizeWithSize(ArrayList sizes, int side, - boolean byMinSide) { + public static PhotoSize getClosestPhotoSizeWithSize(ArrayList sizes, int side, boolean byMinSide) { if (sizes == null || sizes.isEmpty()) { return null; } @@ -386,21 +422,13 @@ public static PhotoSize getClosestPhotoSizeWithSize(ArrayList sizes, } if (byMinSide) { int currentSide = obj.h >= obj.w ? obj.w : obj.h; - if (closestObject == null - || side > 100 && closestObject.location != null - && closestObject.location.dc_id == Integer.MIN_VALUE - || obj instanceof PhotoSize.TL_photoCachedSize - || side > lastSide && lastSide < currentSide) { + if (closestObject == null || side > 100 && closestObject.location != null && closestObject.location.dc_id == Integer.MIN_VALUE || obj instanceof PhotoSize.TL_photoCachedSize || side > lastSide && lastSide < currentSide) { closestObject = obj; lastSide = currentSide; } } else { int currentSide = obj.w >= obj.h ? obj.w : obj.h; - if (closestObject == null - || side > 100 && closestObject.location != null - && closestObject.location.dc_id == Integer.MIN_VALUE - || obj instanceof PhotoSize.TL_photoCachedSize - || currentSide <= side && lastSide < currentSide) { + if (closestObject == null || side > 100 && closestObject.location != null && closestObject.location.dc_id == Integer.MIN_VALUE || obj instanceof PhotoSize.TL_photoCachedSize || currentSide <= side && lastSide < currentSide) { closestObject = obj; lastSide = currentSide; } @@ -433,6 +461,18 @@ public static String getDocumentFileName(Document document) { return ""; } + public static String getExtensionByMime(String mime) { + int index; + if ((index = mime.indexOf('/')) != -1) { + return mime.substring(index + 1); + } + return ""; + } + + public static File getInternalCacheDir() { + return Gallery.applicationContext.getCacheDir(); + } + public static String getDocumentExtension(Document document) { String fileName = getDocumentFileName(document); int idx = fileName.lastIndexOf('.'); @@ -484,18 +524,25 @@ public static String getAttachFileName(TLObject attach, String ext) { docExt = ""; } } - if (docExt.length() > 1) { - return document.dc_id + "_" + document.id + docExt; + if (document.version == 0) { + if (docExt.length() > 1) { + return document.dc_id + "_" + document.id + docExt; + } else { + return document.dc_id + "_" + document.id; + } } else { - return document.dc_id + "_" + document.id; + if (docExt.length() > 1) { + return document.dc_id + "_" + document.id + "_" + document.version + docExt; + } else { + return document.dc_id + "_" + document.id + "_" + document.version; + } } } else if (attach instanceof PhotoSize) { PhotoSize photo = (PhotoSize) attach; if (photo.location == null || photo.location instanceof FileLocation.TL_fileLocationUnavailable) { return ""; } - return photo.location.volume_id + "_" + photo.location.local_id + "." - + (ext != null ? ext : "jpg"); + return photo.location.volume_id + "_" + photo.location.local_id + "." + (ext != null ? ext : "jpg"); } else if (attach instanceof FileLocation) { if (attach instanceof FileLocation.TL_fileLocationUnavailable) { return ""; @@ -515,13 +562,30 @@ public void deleteFiles(final ArrayList files, final int type) { public void run() { for (int a = 0; a < files.size(); a++) { File file = files.get(a); - if (file.exists()) { + File encrypted = new File(file.getAbsolutePath() + ".enc"); + if (encrypted.exists()) { + try { + if (!encrypted.delete()) { + encrypted.deleteOnExit(); + } + } catch (Exception e) { + FileLog.e(e); + } + try { + File key = new File(FileLoader.getInternalCacheDir(), file.getName() + ".enc.key"); + if (!key.delete()) { + key.deleteOnExit(); + } + } catch (Exception e) { + FileLog.e(e); + } + } else if (file.exists()) { try { if (!file.delete()) { file.deleteOnExit(); } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } try { @@ -532,11 +596,11 @@ public void run() { } } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } if (type == 2) { - ImageLoader.getInstance().clearMemory(); + GalleryImageLoader.getInstance().clearMemory(); } } }); diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLog.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLog.java new file mode 100644 index 0000000..40ee0bf --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/FileLog.java @@ -0,0 +1,41 @@ +package com.tangxiaolv.telegramgallery.utils; + +import android.util.Log; + +public class FileLog { + + private static volatile FileLog Instance = null; + + public static FileLog getInstance() { + FileLog localInstance = Instance; + if (localInstance == null) { + synchronized (FileLog.class) { + localInstance = Instance; + if (localInstance == null) { + Instance = localInstance = new FileLog(); + } + } + } + return localInstance; + } + + public static void e(final String message, final Throwable exception) { + Log.e("gallery", message, exception); + } + + public static void e(final String message) { + Log.e("gallery", message); + } + + public static void e(final Throwable e) { + e.printStackTrace(); + } + + public static void d(final String message) { + Log.d("gallery", message); + } + + public static void w(final String message) { + Log.w("gallery", message); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/ImageLoader.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/GalleryImageLoader.java similarity index 68% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/ImageLoader.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/GalleryImageLoader.java index 8a931b9..94abf6f 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/ImageLoader.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/GalleryImageLoader.java @@ -1,7 +1,10 @@ -package com.tangxiaolv.telegramgallery.Utils; +package com.tangxiaolv.telegramgallery.utils; import android.app.ActivityManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; @@ -13,21 +16,23 @@ import android.os.Build; import android.os.Environment; import android.provider.MediaStore; -import android.util.Log; import com.tangxiaolv.telegramgallery.AnimatedFileDrawable; import com.tangxiaolv.telegramgallery.DispatchQueue; import com.tangxiaolv.telegramgallery.Gallery; import com.tangxiaolv.telegramgallery.ImageReceiver; -import com.tangxiaolv.telegramgallery.LruCache; -import com.tangxiaolv.telegramgallery.TL.Document; -import com.tangxiaolv.telegramgallery.TL.FileLocation; -import com.tangxiaolv.telegramgallery.TL.PhotoSize; -import com.tangxiaolv.telegramgallery.TL.TLObject; +import com.tangxiaolv.telegramgallery.tl.Document; +import com.tangxiaolv.telegramgallery.tl.FileLocation; +import com.tangxiaolv.telegramgallery.tl.InputEncryptedFile; +import com.tangxiaolv.telegramgallery.tl.InputFile; +import com.tangxiaolv.telegramgallery.tl.PhotoSize; +import com.tangxiaolv.telegramgallery.tl.TLObject; +import com.tangxiaolv.telegramgallery.secretmedia.EncryptedFileInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -46,7 +51,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class ImageLoader { +public class GalleryImageLoader { private HashMap bitmapUseCounts = new HashMap<>(); private LruCache memCache; @@ -66,6 +71,12 @@ public class ImageLoader { private static byte[] bytesThumb; private static byte[] header = new byte[12]; private static byte[] headerThumb = new byte[12]; + private int currentHttpTasksCount = 0; + + private LinkedList httpFileLoadTasks = new LinkedList<>(); + private HashMap httpFileLoadTasksByKeys = new HashMap<>(); + private HashMap retryHttpsTasks = new HashMap<>(); + private int currentHttpFileLoadTasksCount = 0; private String ignoreRemoval = null; @@ -86,8 +97,10 @@ private class HttpFileTask extends AsyncTask { private String url; private File tempFile; private String ext; + private int fileSize; private RandomAccessFile fileOutputStream = null; private boolean canRetry = true; + private long lastProgressTime; public HttpFileTask(String url, File tempFile, String ext) { this.url = url; @@ -95,6 +108,25 @@ public HttpFileTask(String url, File tempFile, String ext) { this.ext = ext; } + private void reportProgress(final float progress) { + long currentTime = System.currentTimeMillis(); + if (progress == 1 || lastProgressTime == 0 || lastProgressTime < currentTime - 500) { + lastProgressTime = currentTime; + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + fileProgresses.put(url, progress); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileLoadProgressChanged, url, progress); + } + }); + } + }); + } + } + protected Boolean doInBackground(Void... voids) { InputStream httpConnectionStream = null; boolean done = false; @@ -103,26 +135,22 @@ protected Boolean doInBackground(Void... voids) { try { URL downloadUrl = new URL(url); httpConnection = downloadUrl.openConnection(); - httpConnection.addRequestProperty("User-Agent", - "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36"); - httpConnection.addRequestProperty("Referer", "google.com"); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1"); + //httpConnection.addRequestProperty("Referer", "google.com"); httpConnection.setConnectTimeout(5000); httpConnection.setReadTimeout(5000); if (httpConnection instanceof HttpURLConnection) { HttpURLConnection httpURLConnection = (HttpURLConnection) httpConnection; httpURLConnection.setInstanceFollowRedirects(true); int status = httpURLConnection.getResponseCode(); - if (status == HttpURLConnection.HTTP_MOVED_TEMP - || status == HttpURLConnection.HTTP_MOVED_PERM - || status == HttpURLConnection.HTTP_SEE_OTHER) { + if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) { String newUrl = httpURLConnection.getHeaderField("Location"); String cookies = httpURLConnection.getHeaderField("Set-Cookie"); downloadUrl = new URL(newUrl); httpConnection = downloadUrl.openConnection(); httpConnection.setRequestProperty("Cookie", cookies); - httpConnection.addRequestProperty("User-Agent", - "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36"); - httpConnection.addRequestProperty("Referer", "google.com"); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1"); + //httpConnection.addRequestProperty("Referer", "google.com"); } } httpConnection.connect(); @@ -132,27 +160,48 @@ protected Boolean doInBackground(Void... voids) { } catch (Throwable e) { if (e instanceof UnknownHostException) { canRetry = false; + } else if (e instanceof SocketException) { + if (e.getMessage() != null && e.getMessage().contains("ECONNRESET")) { + canRetry = false; + } + } else if (e instanceof FileNotFoundException) { + canRetry = false; } - e.printStackTrace(); + FileLog.e(e); } if (canRetry) { try { if (httpConnection != null && httpConnection instanceof HttpURLConnection) { int code = ((HttpURLConnection) httpConnection).getResponseCode(); - if (code != HttpURLConnection.HTTP_OK - && code != HttpURLConnection.HTTP_ACCEPTED - && code != HttpURLConnection.HTTP_NOT_MODIFIED) { + if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) { canRetry = false; } } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); + } + if (httpConnection != null) { + try { + Map> headerFields = httpConnection.getHeaderFields(); + if (headerFields != null) { + List values = headerFields.get("content-Length"); + if (values != null && !values.isEmpty()) { + String length = (String) values.get(0); + if (length != null) { + fileSize = Utilities.parseInt(length); + } + } + } + } catch (Exception e) { + FileLog.e(e); + } } if (httpConnectionStream != null) { try { - byte[] data = new byte[1024 * 4]; + byte[] data = new byte[1024 * 32]; + int totalLoaded = 0; while (true) { if (isCancelled()) { break; @@ -161,19 +210,26 @@ protected Boolean doInBackground(Void... voids) { int read = httpConnectionStream.read(data); if (read > 0) { fileOutputStream.write(data, 0, read); + totalLoaded += read; + if (fileSize > 0) { + reportProgress(totalLoaded / (float) fileSize); + } } else if (read == -1) { done = true; + if (fileSize != 0) { + reportProgress(1.0f); + } break; } else { break; } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); break; } } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } } @@ -183,7 +239,7 @@ protected Boolean doInBackground(Void... voids) { fileOutputStream = null; } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } try { @@ -191,7 +247,7 @@ protected Boolean doInBackground(Void... voids) { httpConnectionStream.close(); } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } } @@ -200,10 +256,12 @@ protected Boolean doInBackground(Void... voids) { @Override protected void onPostExecute(Boolean result) { + runHttpFileLoadTasks(this, result ? 2 : 1); } @Override protected void onCancelled() { + runHttpFileLoadTasks(this, 2); } } @@ -232,9 +290,7 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.FileLoadProgressChanged, cacheImage.url, - progress); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileLoadProgressChanged, cacheImage.url, progress); } }); } @@ -250,9 +306,8 @@ protected Boolean doInBackground(Void... voids) { try { URL downloadUrl = new URL(cacheImage.httpUrl); httpConnection = downloadUrl.openConnection(); - httpConnection.addRequestProperty("User-Agent", - "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36"); - httpConnection.addRequestProperty("Referer", "google.com"); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1"); + //httpConnection.addRequestProperty("Referer", "google.com"); httpConnection.setConnectTimeout(5000); httpConnection.setReadTimeout(5000); if (httpConnection instanceof HttpURLConnection) { @@ -270,8 +325,10 @@ protected Boolean doInBackground(Void... voids) { if (e.getMessage() != null && e.getMessage().contains("ECONNRESET")) { canRetry = false; } + } else if (e instanceof FileNotFoundException) { + canRetry = false; } - e.printStackTrace(); + FileLog.e(e); } } @@ -279,14 +336,12 @@ protected Boolean doInBackground(Void... voids) { try { if (httpConnection != null && httpConnection instanceof HttpURLConnection) { int code = ((HttpURLConnection) httpConnection).getResponseCode(); - if (code != HttpURLConnection.HTTP_OK - && code != HttpURLConnection.HTTP_ACCEPTED - && code != HttpURLConnection.HTTP_NOT_MODIFIED) { + if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) { canRetry = false; } } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } if (imageSize == 0 && httpConnection != null) { try { @@ -301,7 +356,7 @@ protected Boolean doInBackground(Void... voids) { } } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } @@ -331,12 +386,12 @@ protected Boolean doInBackground(Void... voids) { break; } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); break; } } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } } } @@ -347,7 +402,7 @@ protected Boolean doInBackground(Void... voids) { fileOutputStream = null; } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } try { @@ -355,7 +410,7 @@ protected Boolean doInBackground(Void... voids) { httpConnectionStream.close(); } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } if (done) { @@ -384,20 +439,30 @@ public void run() { @Override public void run() { if (result) { - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.FileDidLoaded, cacheImage.url); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidLoaded, cacheImage.url); } else { - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.FileDidFailedLoad, cacheImage.url, 2); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, cacheImage.url, 2); } } }); } }); + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + runHttpTasks(true); + } + }); } @Override protected void onCancelled() { + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + runHttpTasks(true); + } + }); Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -405,8 +470,7 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.FileDidFailedLoad, cacheImage.url, 1); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, cacheImage.url, 1); } }); } @@ -449,31 +513,24 @@ public void run() { return; } final String key = thumbLocation.volume_id + "_" + thumbLocation.local_id; - File thumbFile = new File( - FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), - "q_" + key + ".jpg"); + File thumbFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + key + ".jpg"); if (thumbFile.exists() || !originalPath.exists()) { removeTask(); return; } - int size = Math.min(180, - Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - / 4); + int size = Math.min(180, Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / 4); Bitmap originalBitmap = null; if (mediaType == FileLoader.MEDIA_DIR_IMAGE) { - originalBitmap = ImageLoader.loadBitmap(originalPath.toString(), null, size, - size, false); + originalBitmap = GalleryImageLoader.loadBitmap(originalPath.toString(), null, size, size, false); } else if (mediaType == FileLoader.MEDIA_DIR_VIDEO) { - originalBitmap = ThumbnailUtils.createVideoThumbnail(originalPath.toString(), - MediaStore.Video.Thumbnails.MINI_KIND); + originalBitmap = ThumbnailUtils.createVideoThumbnail(originalPath.toString(), MediaStore.Video.Thumbnails.MINI_KIND); } else if (mediaType == FileLoader.MEDIA_DIR_DOCUMENT) { String path = originalPath.toString().toLowerCase(); - if (!path.endsWith(".jpg") && !path.endsWith(".jpeg") && !path.endsWith(".png") - && !path.endsWith(".gif")) { + if (!path.endsWith(".jpg") && !path.endsWith(".jpeg") && !path.endsWith(".png") && !path.endsWith(".gif")) { removeTask(); return; } - originalBitmap = ImageLoader.loadBitmap(path, null, size, size, false); + originalBitmap = GalleryImageLoader.loadBitmap(path, null, size, size, false); } if (originalBitmap == null) { removeTask(); @@ -487,18 +544,19 @@ public void run() { return; } float scaleFactor = Math.min((float) w / size, (float) h / size); - Bitmap scaledBitmap = Bitmaps.createScaledBitmap(originalBitmap, - (int) (w / scaleFactor), (int) (h / scaleFactor), true); + Bitmap scaledBitmap = Bitmaps.createScaledBitmap(originalBitmap, (int) (w / scaleFactor), (int) (h / scaleFactor), true); if (scaledBitmap != originalBitmap) { originalBitmap.recycle(); } originalBitmap = scaledBitmap; - FileOutputStream stream = new FileOutputStream(thumbFile); - originalBitmap.compress(Bitmap.CompressFormat.JPEG, 60, stream); + FileOutputStream stream = null; try { - stream.close(); - } catch (Exception e) { - e.printStackTrace(); + stream = new FileOutputStream(thumbFile); + originalBitmap.compress(Bitmap.CompressFormat.JPEG, 60, stream); + } finally { + if (null != stream) { + stream.close(); + } } final BitmapDrawable bitmapDrawable = new BitmapDrawable(originalBitmap); AndroidUtilities.runOnUIThread(new Runnable() { @@ -510,19 +568,22 @@ public void run() { if (filter != null) { kf += "@" + filter; } - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.messageThumbGenerated, bitmapDrawable, kf); - /* - * BitmapDrawable old = memCache.get(kf); if (old != null) { Bitmap image = - * old.getBitmap(); if (runtimeHack != null) { - * runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight()); } if - * (!image.isRecycled()) { image.recycle(); } } - */ + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageThumbGenerated, bitmapDrawable, kf); + /*BitmapDrawable old = memCache.get(kf); + if (old != null) { + Bitmap image = old.getBitmap(); + if (runtimeHack != null) { + runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight()); + } + if (!image.isRecycled()) { + image.recycle(); + } + }*/ memCache.put(kf, bitmapDrawable); } }); } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); removeTask(); } } @@ -555,9 +616,7 @@ public void run() { return; } } - AnimatedFileDrawable fileDrawable = new AnimatedFileDrawable( - cacheImage.finalFilePath, - cacheImage.filter != null && cacheImage.filter.equals("d")); + AnimatedFileDrawable fileDrawable = new AnimatedFileDrawable(cacheImage.finalFilePath, cacheImage.filter != null && cacheImage.filter.equals("d")); Thread.interrupted(); onPostExecute(fileDrawable); } else { @@ -565,6 +624,7 @@ public void run() { boolean mediaIsVideo = false; Bitmap image = null; File cacheFileFinal = cacheImage.finalFilePath; + boolean inEncryptedFile = cacheImage.encryptionKeyPath != null && cacheFileFinal != null && cacheFileFinal.getAbsolutePath().endsWith(".enc"); boolean canDeleteFile = true; boolean useNativeWebpLoaded = false; @@ -586,13 +646,13 @@ public void run() { } randomAccessFile.close(); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } finally { if (randomAccessFile != null) { try { randomAccessFile.close(); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } } @@ -627,27 +687,36 @@ public void run() { if (useNativeWebpLoaded) { RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r"); - ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, - 0, cacheFileFinal.length()); + ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length()); BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; - image = Bitmaps.createBitmap(bmOptions.outWidth, bmOptions.outHeight, - Bitmap.Config.ARGB_8888); + Utilities.loadWebpImage(null, buffer, buffer.limit(), bmOptions, true); + image = Bitmaps.createBitmap(bmOptions.outWidth, bmOptions.outHeight, Bitmap.Config.ARGB_8888); + + Utilities.loadWebpImage(image, buffer, buffer.limit(), null, !opts.inPurgeable); file.close(); } else { if (opts.inPurgeable) { RandomAccessFile f = new RandomAccessFile(cacheFileFinal, "r"); int len = (int) f.length(); - byte[] data = bytesThumb != null && bytesThumb.length >= len - ? bytesThumb : null; + byte[] data = bytesThumb != null && bytesThumb.length >= len ? bytesThumb : null; if (data == null) { bytesThumb = data = new byte[len]; } f.readFully(data, 0, len); + f.close(); + if (inEncryptedFile) { + EncryptedFileInputStream.decryptBytesWithKeyFile(data, 0, len, cacheImage.encryptionKeyPath); + } image = BitmapFactory.decodeByteArray(data, 0, len, opts); } else { - FileInputStream is = new FileInputStream(cacheFileFinal); + FileInputStream is; + if (inEncryptedFile) { + is = new EncryptedFileInputStream(cacheFileFinal, cacheImage.encryptionKeyPath); + } else { + is = new FileInputStream(cacheFileFinal); + } image = BitmapFactory.decodeStream(is, null, opts); is.close(); } @@ -660,28 +729,35 @@ public void run() { } else { if (blurType == 1) { if (image.getConfig() == Bitmap.Config.ARGB_8888) { + Utilities.blurBitmap(image, 3, opts.inPurgeable ? 0 : 1, image.getWidth(), image.getHeight(), image.getRowBytes()); } } else if (blurType == 2) { if (image.getConfig() == Bitmap.Config.ARGB_8888) { + Utilities.blurBitmap(image, 1, opts.inPurgeable ? 0 : 1, image.getWidth(), image.getHeight(), image.getRowBytes()); } } else if (blurType == 3) { if (image.getConfig() == Bitmap.Config.ARGB_8888) { + Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1, image.getWidth(), image.getHeight(), image.getRowBytes()); + Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1, image.getWidth(), image.getHeight(), image.getRowBytes()); + Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1, image.getWidth(), image.getHeight(), image.getRowBytes()); } } else if (blurType == 0 && opts.inPurgeable) { Utilities.pinBitmap(image); } } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } } else { try { + String mediaThumbPath = null; if (cacheImage.httpUrl != null) { if (cacheImage.httpUrl.startsWith("thumb://")) { int idx = cacheImage.httpUrl.indexOf(":", 8); if (idx >= 0) { mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx)); mediaIsVideo = false; + mediaThumbPath = cacheImage.httpUrl.substring(idx + 1); } canDeleteFile = false; } else if (cacheImage.httpUrl.startsWith("vthumb://")) { @@ -700,9 +776,7 @@ public void run() { if (mediaId != null) { delay = 0; } - if (delay != 0 && lastCacheOutTime != 0 - && lastCacheOutTime > System.currentTimeMillis() - delay - && Build.VERSION.SDK_INT < 21) { + if (delay != 0 && lastCacheOutTime != 0 && lastCacheOutTime > System.currentTimeMillis() - delay && Build.VERSION.SDK_INT < 21) { Thread.sleep(delay); } lastCacheOutTime = System.currentTimeMillis(); @@ -730,22 +804,19 @@ public void run() { if (w_filter != 0 && h_filter != 0) { opts.inJustDecodeBounds = true; - if (mediaId != null) { + if (mediaId != null && mediaThumbPath == null) { if (mediaIsVideo) { - MediaStore.Video.Thumbnails.getThumbnail( - Gallery.applicationContext - .getContentResolver(), - mediaId, MediaStore.Video.Thumbnails.MINI_KIND, - opts); + MediaStore.Video.Thumbnails.getThumbnail(Gallery.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts); } else { - MediaStore.Images.Thumbnails.getThumbnail( - Gallery.applicationContext - .getContentResolver(), - mediaId, MediaStore.Images.Thumbnails.MINI_KIND, - opts); + MediaStore.Images.Thumbnails.getThumbnail(Gallery.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts); } } else { - FileInputStream is = new FileInputStream(cacheFileFinal); + FileInputStream is; + if (inEncryptedFile) { + is = new EncryptedFileInputStream(cacheFileFinal, cacheImage.encryptionKeyPath); + } else { + is = new FileInputStream(cacheFileFinal); + } image = BitmapFactory.decodeStream(is, null, opts); is.close(); } @@ -759,6 +830,23 @@ public void run() { opts.inJustDecodeBounds = false; opts.inSampleSize = (int) scaleFactor; } + } else if (mediaThumbPath != null) { + opts.inJustDecodeBounds = true; + FileInputStream is = new FileInputStream(cacheFileFinal); + image = BitmapFactory.decodeStream(is, null, opts); + is.close(); + float photoW = opts.outWidth; + float photoH = opts.outHeight; + float scaleFactor = Math.max(photoW / 512, photoH / 384); + if (scaleFactor < 1) { + scaleFactor = 1; + } + opts.inJustDecodeBounds = false; + int sample = 1; + do { + sample *= 2; + } while (sample * 2 < scaleFactor); + opts.inSampleSize = sample; } synchronized (sync) { if (isCancelled) { @@ -776,50 +864,53 @@ public void run() { } opts.inDither = false; - if (mediaId != null) { + if (mediaId != null && mediaThumbPath == null) { if (mediaIsVideo) { - image = MediaStore.Video.Thumbnails.getThumbnail( - Gallery.applicationContext.getContentResolver(), - mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts); + image = MediaStore.Video.Thumbnails.getThumbnail(Gallery.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts); } else { - image = MediaStore.Images.Thumbnails.getThumbnail( - Gallery.applicationContext.getContentResolver(), - mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts); + image = MediaStore.Images.Thumbnails.getThumbnail(Gallery.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts); } } if (image == null) { if (useNativeWebpLoaded) { RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r"); - ByteBuffer buffer = file.getChannel().map( - FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length()); + ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length()); BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; - image = Bitmaps.createBitmap(bmOptions.outWidth, - bmOptions.outHeight, Bitmap.Config.ARGB_8888); + Utilities.loadWebpImage(null, buffer, buffer.limit(), bmOptions, true); + image = Bitmaps.createBitmap(bmOptions.outWidth, bmOptions.outHeight, Bitmap.Config.ARGB_8888); + Utilities.loadWebpImage(image, buffer, buffer.limit(), null, !opts.inPurgeable); file.close(); } else { if (opts.inPurgeable) { RandomAccessFile f = new RandomAccessFile(cacheFileFinal, "r"); int len = (int) f.length(); - byte[] data = bytes != null && bytes.length >= len ? bytes - : null; + byte[] data = bytes != null && bytes.length >= len ? bytes : null; if (data == null) { bytes = data = new byte[len]; } f.readFully(data, 0, len); + f.close(); + if (inEncryptedFile) { + EncryptedFileInputStream.decryptBytesWithKeyFile(data, 0, len, cacheImage.encryptionKeyPath); + } image = BitmapFactory.decodeByteArray(data, 0, len, opts); } else { - FileInputStream is = new FileInputStream(cacheFileFinal); + FileInputStream is; + if (inEncryptedFile) { + is = new EncryptedFileInputStream(cacheFileFinal, cacheImage.encryptionKeyPath); + } else { + is = new FileInputStream(cacheFileFinal); + } image = BitmapFactory.decodeStream(is, null, opts); is.close(); } } } if (image == null) { - if (canDeleteFile && (cacheFileFinal.length() == 0 - || cacheImage.filter == null)) { + if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) { cacheFileFinal.delete(); } } else { @@ -827,11 +918,9 @@ public void run() { if (cacheImage.filter != null) { float bitmapW = image.getWidth(); float bitmapH = image.getHeight(); - if (!opts.inPurgeable && w_filter != 0 && bitmapW != w_filter - && bitmapW > w_filter + 20) { + if (!opts.inPurgeable && w_filter != 0 && bitmapW != w_filter && bitmapW > w_filter + 20) { float scaleFactor = bitmapW / w_filter; - Bitmap scaledBitmap = Bitmaps.createScaledBitmap(image, - (int) w_filter, (int) (bitmapH / scaleFactor), true); + Bitmap scaledBitmap = Bitmaps.createScaledBitmap(image, (int) w_filter, (int) (bitmapH / scaleFactor), true); if (image != scaledBitmap) { image.recycle(); image = scaledBitmap; @@ -839,6 +928,7 @@ public void run() { } if (image != null && blur && bitmapH < 100 && bitmapW < 100) { if (image.getConfig() == Bitmap.Config.ARGB_8888) { + Utilities.blurBitmap(image, 3, opts.inPurgeable ? 0 : 1, image.getWidth(), image.getHeight(), image.getRowBytes()); } blured = true; } @@ -847,8 +937,8 @@ public void run() { Utilities.pinBitmap(image); } } - } catch (Throwable e) { - // don't promt + } catch (Throwable ignore) { + ignore.getStackTrace(); } } Thread.interrupted(); @@ -892,7 +982,8 @@ public void cancel() { runningThread.interrupt(); } } catch (Exception e) { - // don't promt + //don't promt + e.getStackTrace(); } } } @@ -910,24 +1001,24 @@ private class CacheImage { protected File tempFilePath; protected boolean thumb; + protected File encryptionKeyPath; + protected String httpUrl; protected HttpImageTask httpTask; protected CacheOutTask cacheTask; protected ArrayList imageReceiverArray = new ArrayList<>(); + protected ArrayList keys = new ArrayList<>(); + protected ArrayList filters = new ArrayList<>(); - public void addImageReceiver(ImageReceiver imageReceiver) { - boolean exist = false; - for (ImageReceiver v : imageReceiverArray) { - if (v == imageReceiver) { - exist = true; - break; - } - } - if (!exist) { - imageReceiverArray.add(imageReceiver); - imageLoadingByTag.put(imageReceiver.getTag(thumb), this); + public void addImageReceiver(ImageReceiver imageReceiver, String key, String filter) { + if (imageReceiverArray.contains(imageReceiver)) { + return; } + imageReceiverArray.add(imageReceiver); + keys.add(key); + filters.add(filter); + imageLoadingByTag.put(imageReceiver.getTag(thumb), this); } public void removeImageReceiver(ImageReceiver imageReceiver) { @@ -935,6 +1026,8 @@ public void removeImageReceiver(ImageReceiver imageReceiver) { ImageReceiver obj = imageReceiverArray.get(a); if (obj == null || obj == imageReceiver) { imageReceiverArray.remove(a); + keys.remove(a); + filters.remove(a); if (obj != null) { imageLoadingByTag.remove(obj.getTag(thumb)); } @@ -978,8 +1071,7 @@ public void removeImageReceiver(ImageReceiver imageReceiver) { public void setImageAndClear(final BitmapDrawable image) { if (image != null) { - final ArrayList finalImageReceiverArray = new ArrayList<>( - imageReceiverArray); + final ArrayList finalImageReceiverArray = new ArrayList<>(imageReceiverArray); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -988,9 +1080,7 @@ public void run() { AnimatedFileDrawable fileDrawable = (AnimatedFileDrawable) image; for (int a = 0; a < finalImageReceiverArray.size(); a++) { ImageReceiver imgView = finalImageReceiverArray.get(a); - if (imgView.setImageBitmapByKey( - a == 0 ? fileDrawable : fileDrawable.makeCopy(), key, thumb, - false)) { + if (imgView.setImageBitmapByKey(a == 0 ? fileDrawable : fileDrawable.makeCopy(), key, thumb, false)) { imageSet = true; } } @@ -1020,32 +1110,29 @@ public void run() { } } - private static volatile ImageLoader Instance = null; + private static volatile GalleryImageLoader Instance = null; - public static ImageLoader getInstance() { - ImageLoader localInstance = Instance; + public static GalleryImageLoader getInstance() { + GalleryImageLoader localInstance = Instance; if (localInstance == null) { - synchronized (ImageLoader.class) { + synchronized (GalleryImageLoader.class) { localInstance = Instance; if (localInstance == null) { - Instance = localInstance = new ImageLoader(); + Instance = localInstance = new GalleryImageLoader(); } } } return localInstance; } - public ImageLoader() { + public GalleryImageLoader() { cacheOutQueue.setPriority(Thread.MIN_PRIORITY); cacheThumbOutQueue.setPriority(Thread.MIN_PRIORITY); thumbGeneratingQueue.setPriority(Thread.MIN_PRIORITY); imageLoadQueue.setPriority(Thread.MIN_PRIORITY); - int cacheSize = Math.min(15, - ((ActivityManager) Gallery.applicationContext - .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass() / 7) - * 1024 * 1024; + int cacheSize = Math.min(15, ((ActivityManager) Gallery.applicationContext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass() / 7) * 1024 * 1024; memCache = new LruCache(cacheSize) { @Override @@ -1054,8 +1141,7 @@ protected int sizeOf(String key, BitmapDrawable value) { } @Override - protected void entryRemoved(boolean evicted, String key, final BitmapDrawable oldValue, - BitmapDrawable newValue) { + protected void entryRemoved(boolean evicted, String key, final BitmapDrawable oldValue, BitmapDrawable newValue) { if (ignoreRemoval != null && key != null && ignoreRemoval.equals(key)) { return; } @@ -1070,22 +1156,67 @@ protected void entryRemoved(boolean evicted, String key, final BitmapDrawable ol }; FileLoader.getInstance().setDelegate(new FileLoader.FileLoaderDelegate() { + @Override + public void fileUploadProgressChanged(final String location, final float progress, final boolean isEncrypted) { + fileProgresses.put(location, progress); + long currentTime = System.currentTimeMillis(); + if (lastProgressUpdateTime == 0 || lastProgressUpdateTime < currentTime - 500) { + lastProgressUpdateTime = currentTime; + + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileUploadProgressChanged, location, progress, isEncrypted); + } + }); + } + } + + @Override + public void fileDidUploaded(final String location, final InputFile inputFile, final InputEncryptedFile inputEncryptedFile, final byte[] key, final byte[] iv, final long totalFileSize) { + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidUpload, location, inputFile, inputEncryptedFile, key, iv, totalFileSize); + } + }); + fileProgresses.remove(location); + } + }); + } + + @Override + public void fileDidFailedUpload(final String location, final boolean isEncrypted) { + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailUpload, location, isEncrypted); + } + }); + fileProgresses.remove(location); + } + }); + } + @Override public void fileDidLoaded(final String location, final File finalFile, final int type) { fileProgresses.remove(location); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - if (MediaController.getInstance().canSaveToGallery() && telegramPath != null - && finalFile != null - && (location.endsWith(".mp4") || location.endsWith(".jpg"))) { + if (MediaController.getInstance().canSaveToGallery() && telegramPath != null && finalFile != null && (location.endsWith(".mp4") || location.endsWith(".jpg"))) { if (finalFile.toString().startsWith(telegramPath.toString())) { AndroidUtilities.addMediaToGallery(finalFile.toString()); } } - NotificationCenter.getInstance() - .postNotificationName(NotificationCenter.FileDidLoaded, location); - ImageLoader.this.fileDidLoaded(location, finalFile, type); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidLoaded, location); + GalleryImageLoader.this.fileDidLoaded(location, finalFile, type); } }); } @@ -1096,9 +1227,8 @@ public void fileDidFailedLoad(final String location, final int canceled) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - ImageLoader.this.fileDidFailedLoad(location, canceled); - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.FileDidFailedLoad, location, canceled); + GalleryImageLoader.this.fileDidFailedLoad(location, canceled); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, location, canceled); } }); } @@ -1112,43 +1242,42 @@ public void fileLoadProgressChanged(final String location, final float progress) AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.FileLoadProgressChanged, location, progress); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileLoadProgressChanged, location, progress); } }); } } }); -// BroadcastReceiver receiver = new BroadcastReceiver() { -// @Override -// public void onReceive(Context arg0, Intent intent) { -// Log.e("tmessages", "file system changed"); -// Runnable r = new Runnable() { -// public void run() { -// checkMediaPaths(); -// } -// }; -// if (Intent.ACTION_MEDIA_UNMOUNTED.equals(intent.getAction())) { -// AndroidUtilities.runOnUIThread(r, 1000); -// } else { -// r.run(); -// } -// } -// }; -// -// IntentFilter filter = new IntentFilter(); -// filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); -// filter.addAction(Intent.ACTION_MEDIA_CHECKING); -// filter.addAction(Intent.ACTION_MEDIA_EJECT); -// filter.addAction(Intent.ACTION_MEDIA_MOUNTED); -// filter.addAction(Intent.ACTION_MEDIA_NOFS); -// filter.addAction(Intent.ACTION_MEDIA_REMOVED); -// filter.addAction(Intent.ACTION_MEDIA_SHARED); -// filter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE); -// filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); -// filter.addDataScheme("file"); -// AppApplication.applicationContext.registerReceiver(receiver, filter); + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context arg0, Intent intent) { + FileLog.e("file system changed"); + Runnable r = new Runnable() { + public void run() { + checkMediaPaths(); + } + }; + if (Intent.ACTION_MEDIA_UNMOUNTED.equals(intent.getAction())) { + AndroidUtilities.runOnUIThread(r, 1000); + } else { + r.run(); + } + } + }; + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); + filter.addAction(Intent.ACTION_MEDIA_CHECKING); + filter.addAction(Intent.ACTION_MEDIA_EJECT); + filter.addAction(Intent.ACTION_MEDIA_MOUNTED); + filter.addAction(Intent.ACTION_MEDIA_NOFS); + filter.addAction(Intent.ACTION_MEDIA_REMOVED); + filter.addAction(Intent.ACTION_MEDIA_SHARED); + filter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE); + filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); + filter.addDataScheme("file"); + Gallery.applicationContext.registerReceiver(receiver, filter); HashMap mediaDirs = new HashMap<>(); File cachePath = AndroidUtilities.getCacheDir(); @@ -1156,18 +1285,18 @@ public void run() { try { cachePath.mkdirs(); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } try { new File(cachePath, ".nomedia").createNewFile(); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } mediaDirs.put(FileLoader.MEDIA_DIR_CACHE, cachePath); FileLoader.getInstance().setMediaDirs(mediaDirs); -// checkMediaPaths(); + checkMediaPaths(); } public void checkMediaPaths() { @@ -1192,80 +1321,76 @@ public HashMap createMediaPaths() { try { cachePath.mkdirs(); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } try { new File(cachePath, ".nomedia").createNewFile(); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } mediaDirs.put(FileLoader.MEDIA_DIR_CACHE, cachePath); - Log.e("tmessages", "cache path = " + cachePath); + FileLog.e("cache path = " + cachePath); try { if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { - telegramPath = new File(Environment.getExternalStorageDirectory(), "Telegram"); + telegramPath = new File(Environment.getExternalStorageDirectory(), "toon"); telegramPath.mkdirs(); if (telegramPath.isDirectory()) { try { - File imagePath = new File(telegramPath, "Telegram Images"); + File imagePath = new File(telegramPath, "Toon Images"); imagePath.mkdir(); - if (imagePath.isDirectory() - && canMoveFiles(cachePath, imagePath, FileLoader.MEDIA_DIR_IMAGE)) { + if (imagePath.isDirectory() && canMoveFiles(cachePath, imagePath, FileLoader.MEDIA_DIR_IMAGE)) { mediaDirs.put(FileLoader.MEDIA_DIR_IMAGE, imagePath); - Log.e("tmessages", "image path = " + imagePath); + FileLog.e("image path = " + imagePath); } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } try { - File videoPath = new File(telegramPath, "Telegram Video"); + File videoPath = new File(telegramPath, "Toon Video"); videoPath.mkdir(); - if (videoPath.isDirectory() - && canMoveFiles(cachePath, videoPath, FileLoader.MEDIA_DIR_VIDEO)) { + if (videoPath.isDirectory() && canMoveFiles(cachePath, videoPath, FileLoader.MEDIA_DIR_VIDEO)) { mediaDirs.put(FileLoader.MEDIA_DIR_VIDEO, videoPath); - Log.e("tmessages", "video path = " + videoPath); + FileLog.e("video path = " + videoPath); } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } try { - File audioPath = new File(telegramPath, "Telegram Audio"); + File audioPath = new File(telegramPath, "Toon Audio"); audioPath.mkdir(); - if (audioPath.isDirectory() - && canMoveFiles(cachePath, audioPath, FileLoader.MEDIA_DIR_AUDIO)) { + if (audioPath.isDirectory() && canMoveFiles(cachePath, audioPath, FileLoader.MEDIA_DIR_AUDIO)) { new File(audioPath, ".nomedia").createNewFile(); mediaDirs.put(FileLoader.MEDIA_DIR_AUDIO, audioPath); - Log.e("tmessages", "audio path = " + audioPath); + FileLog.e("audio path = " + audioPath); } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } try { - File documentPath = new File(telegramPath, "Telegram Documents"); + File documentPath = new File(telegramPath, "Toon Documents"); documentPath.mkdir(); - if (documentPath.isDirectory() && canMoveFiles(cachePath, documentPath, - FileLoader.MEDIA_DIR_DOCUMENT)) { + if (documentPath.isDirectory() && canMoveFiles(cachePath, documentPath, FileLoader.MEDIA_DIR_DOCUMENT)) { new File(documentPath, ".nomedia").createNewFile(); mediaDirs.put(FileLoader.MEDIA_DIR_DOCUMENT, documentPath); - Log.e("tmessages", "documents path = " + documentPath); + FileLog.e("documents path = " + documentPath); } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } } else { - Log.e("tmessages", "this Android can't rename files"); + FileLog.e("this Android can't rename files"); } MediaController.getInstance().checkSaveToGalleryFiles(); } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } return mediaDirs; @@ -1302,14 +1427,14 @@ private boolean canMoveFiles(File from, File to, int type) { return true; } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } finally { try { if (file != null) { file.close(); } } catch (Exception e) { - e.printStackTrace(); + FileLog.e(e); } } return false; @@ -1325,10 +1450,23 @@ public Float getFileProgress(String location) { private void performReplace(String oldKey, String newKey) { BitmapDrawable b = memCache.get(oldKey); if (b != null) { - ignoreRemoval = oldKey; - memCache.remove(oldKey); - memCache.put(newKey, b); - ignoreRemoval = null; + BitmapDrawable oldBitmap = memCache.get(newKey); + boolean dontChange = false; + if (oldBitmap != null && oldBitmap.getBitmap() != null && b.getBitmap() != null) { + Bitmap oldBitmapObject = oldBitmap.getBitmap(); + Bitmap newBitmapObject = b.getBitmap(); + if (oldBitmapObject.getWidth() > newBitmapObject.getWidth() || oldBitmapObject.getHeight() > newBitmapObject.getHeight()) { + dontChange = true; + } + } + if (!dontChange) { + ignoreRemoval = oldKey; + memCache.remove(oldKey); + memCache.put(newKey, b); + ignoreRemoval = null; + } else { + memCache.remove(oldKey); + } } Integer val = bitmapUseCounts.get(oldKey); if (val != null) { @@ -1434,7 +1572,11 @@ public BitmapDrawable getImageFromMemory(TLObject fileLocation, String httpUrl, key = location.volume_id + "_" + location.local_id; } else if (fileLocation instanceof Document) { Document location = (Document) fileLocation; - key = location.dc_id + "_" + location.id; + if (location.version == 0) { + key = location.dc_id + "_" + location.id; + } else { + key = location.dc_id + "_" + location.id + "_" + location.version; + } } } if (filter != null) { @@ -1443,8 +1585,7 @@ public BitmapDrawable getImageFromMemory(TLObject fileLocation, String httpUrl, return memCache.get(key); } - private void replaceImageInCacheInternal(final String oldKey, final String newKey, - final FileLocation newLocation) { + private void replaceImageInCacheInternal(final String oldKey, final String newKey, final FileLocation newLocation) { ArrayList arr = memCache.getFilterKeys(oldKey); if (arr != null) { for (int a = 0; a < arr.size(); a++) { @@ -1452,18 +1593,15 @@ private void replaceImageInCacheInternal(final String oldKey, final String newKe String oldK = oldKey + "@" + filter; String newK = newKey + "@" + filter; performReplace(oldK, newK); - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.didReplacedPhotoInMemCache, oldK, newK, newLocation); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReplacedPhotoInMemCache, oldK, newK, newLocation); } } else { performReplace(oldKey, newKey); - NotificationCenter.getInstance().postNotificationName( - NotificationCenter.didReplacedPhotoInMemCache, oldKey, newKey, newLocation); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReplacedPhotoInMemCache, oldKey, newKey, newLocation); } } - public void replaceImageInCache(final String oldKey, final String newKey, - final FileLocation newLocation, boolean post) { + public void replaceImageInCache(final String oldKey, final String newKey, final FileLocation newLocation, boolean post) { if (post) { AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -1480,11 +1618,8 @@ public void putImageToCache(BitmapDrawable bitmap, String key) { memCache.put(key, bitmap); } - private void generateThumb(int mediaType, File originalPath, FileLocation thumbLocation, - String filter) { - if (mediaType != FileLoader.MEDIA_DIR_IMAGE && mediaType != FileLoader.MEDIA_DIR_VIDEO - && mediaType != FileLoader.MEDIA_DIR_DOCUMENT || originalPath == null - || thumbLocation == null) { + private void generateThumb(int mediaType, File originalPath, FileLocation thumbLocation, String filter) { + if (mediaType != FileLoader.MEDIA_DIR_IMAGE && mediaType != FileLoader.MEDIA_DIR_VIDEO && mediaType != FileLoader.MEDIA_DIR_DOCUMENT || originalPath == null || thumbLocation == null) { return; } String name = FileLoader.getAttachFileName(thumbLocation); @@ -1495,10 +1630,7 @@ private void generateThumb(int mediaType, File originalPath, FileLocation thumbL } } - private void createLoadOperationForImageReceiver(final ImageReceiver imageReceiver, - final String key, final String url, final String ext, final TLObject imageLocation, - final String httpLocation, final String filter, final int size, final boolean cacheOnly, - final int thumb) { + private void createLoadOperationForImageReceiver(final ImageReceiver imageReceiver, final String key, final String url, final String ext, final TLObject imageLocation, final String httpLocation, final String filter, final int size, final int cacheType, final int thumb) { if (imageReceiver == null || url == null || key == null) { return; } @@ -1523,8 +1655,7 @@ public void run() { CacheImage alreadyLoadingCache = imageLoadingByKeys.get(key); CacheImage alreadyLoadingImage = imageLoadingByTag.get(finalTag); if (alreadyLoadingImage != null) { - if (alreadyLoadingImage == alreadyLoadingUrl - || alreadyLoadingImage == alreadyLoadingCache) { + if (alreadyLoadingImage == alreadyLoadingUrl || alreadyLoadingImage == alreadyLoadingCache) { added = true; } else { alreadyLoadingImage.removeImageReceiver(imageReceiver); @@ -1532,11 +1663,11 @@ public void run() { } if (!added && alreadyLoadingCache != null) { - alreadyLoadingCache.addImageReceiver(imageReceiver); + alreadyLoadingCache.addImageReceiver(imageReceiver, key, filter); added = true; } if (!added && alreadyLoadingUrl != null) { - alreadyLoadingUrl.addImageReceiver(imageReceiver); + alreadyLoadingUrl.addImageReceiver(imageReceiver, key, filter); added = true; } } @@ -1545,6 +1676,7 @@ public void run() { boolean onlyCache = false; boolean isQuality = false; File cacheFile = null; + boolean cacheFileExists = false; if (httpLocation != null) { if (!httpLocation.startsWith("http")) { @@ -1565,33 +1697,39 @@ public void run() { } } else if (thumb != 0) { if (finalIsNeedsQualityThumb) { - cacheFile = new File(FileLoader.getInstance() - .getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + url); + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + url); if (!cacheFile.exists()) { cacheFile = null; + } else { + cacheFileExists = true; } } } if (thumb != 2) { + boolean isEncrypted = imageLocation instanceof Document.TL_documentEncrypted || + imageLocation instanceof FileLocation.TL_fileEncryptedLocation; CacheImage img = new CacheImage(); - if (httpLocation != null && !httpLocation.startsWith("vthumb") - && !httpLocation.startsWith("thumb") - && (httpLocation.endsWith("mp4") || httpLocation.endsWith("gif")) - || imageLocation instanceof Document) { + if (httpLocation != null && + !httpLocation.startsWith("vthumb") && + !httpLocation.startsWith("thumb") && + (httpLocation.endsWith("mp4") || httpLocation.endsWith("gif")) || + imageLocation instanceof Document) { img.animatedFile = true; } if (cacheFile == null) { - if (cacheOnly || size == 0 || httpLocation != null) { - cacheFile = new File(FileLoader.getInstance() - .getDirectory(FileLoader.MEDIA_DIR_CACHE), url); + if (cacheType != 0 || size == 0 || httpLocation != null || isEncrypted) { + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url); + if (cacheFile.exists()) { + cacheFileExists = true; + } else if (cacheType == 2) { + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url + ".enc"); + } } else if (imageLocation instanceof Document) { - cacheFile = new File(FileLoader.getInstance() - .getDirectory(FileLoader.MEDIA_DIR_DOCUMENT), url); + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_DOCUMENT), url); } else { - cacheFile = new File(FileLoader.getInstance() - .getDirectory(FileLoader.MEDIA_DIR_IMAGE), url); + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_IMAGE), url); } } @@ -1600,8 +1738,11 @@ public void run() { img.filter = filter; img.httpUrl = httpLocation; img.ext = ext; - img.addImageReceiver(imageReceiver); - if (onlyCache || cacheFile.exists()) { + if (cacheType == 2) { + img.encryptionKeyPath = new File(FileLoader.getInternalCacheDir(), url + ".enc.key"); + } + img.addImageReceiver(imageReceiver, key, filter); + if (onlyCache || cacheFileExists || cacheFile.exists()) { img.finalFilePath = cacheFile; img.cacheTask = new CacheOutTask(img); imageLoadingByKeys.put(key, img); @@ -1617,20 +1758,22 @@ public void run() { if (httpLocation == null) { if (imageLocation instanceof FileLocation) { FileLocation location = (FileLocation) imageLocation; - FileLoader.getInstance().loadFile(location, ext, size, - size == 0 || location.key != null || cacheOnly); + int localCacheType = cacheType; + if (localCacheType == 0 && (size == 0 || location.key != null)) { + localCacheType = 1; + } + FileLoader.getInstance().loadFile(location, ext, size, localCacheType); } else if (imageLocation instanceof Document) { - FileLoader.getInstance().loadFile((Document) imageLocation, - true, cacheOnly); + FileLoader.getInstance().loadFile((Document) imageLocation, true, cacheType); } } else { String file = Utilities.MD5(httpLocation); - File cacheDir = FileLoader.getInstance() - .getDirectory(FileLoader.MEDIA_DIR_CACHE); + File cacheDir = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE); img.tempFilePath = new File(cacheDir, file + "_temp.jpg"); img.finalFilePath = cacheFile; img.httpTask = new HttpImageTask(img, size); httpTasks.add(img.httpTask); + runHttpTasks(false); } } } @@ -1644,13 +1787,15 @@ public void loadImageForImageReceiver(ImageReceiver imageReceiver) { return; } + boolean imageSet = false; String key = imageReceiver.getKey(); if (key != null) { BitmapDrawable bitmapDrawable = memCache.get(key); if (bitmapDrawable != null) { cancelLoadingForImageReceiver(imageReceiver, 0); + imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false, true); + imageSet = true; if (!imageReceiver.isForcePreview()) { - imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false, true); return; } } @@ -1662,6 +1807,9 @@ public void loadImageForImageReceiver(ImageReceiver imageReceiver) { if (bitmapDrawable != null) { imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, true, true); cancelLoadingForImageReceiver(imageReceiver, 1); + if (imageSet && imageReceiver.isForcePreview()) { + return; + } thumbSet = true; } } @@ -1688,8 +1836,7 @@ public void loadImageForImageReceiver(ImageReceiver imageReceiver) { FileLocation location = (FileLocation) imageLocation; key = location.volume_id + "_" + location.local_id; url = key + "." + ext; - if (imageReceiver.getExt() != null || location.key != null - || location.volume_id == Integer.MIN_VALUE && location.local_id < 0) { + if (imageReceiver.getExt() != null || location.key != null || location.volume_id == Integer.MIN_VALUE && location.local_id < 0) { saveImageToCache = true; } } else if (imageLocation instanceof Document) { @@ -1697,7 +1844,12 @@ public void loadImageForImageReceiver(ImageReceiver imageReceiver) { if (document.id == 0 || document.dc_id == 0) { return; } - key = document.dc_id + "_" + document.id; + if (document.version == 0) { + key = document.dc_id + "_" + document.id; + } else { + key = document.dc_id + "_" + document.id + "_" + document.version; + } + String docExt = FileLoader.getDocumentFileName(document); int idx; if (docExt == null || (idx = docExt.lastIndexOf('.')) == -1) { @@ -1739,16 +1891,15 @@ public void loadImageForImageReceiver(ImageReceiver imageReceiver) { } if (httpLocation != null) { - createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, - thumbLocation, null, thumbFilter, 0, true, thumbSet ? 2 : 1); - createLoadOperationForImageReceiver(imageReceiver, key, url, ext, null, httpLocation, - filter, 0, true, 0); + createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, thumbLocation, null, thumbFilter, 0, 1, thumbSet ? 2 : 1); + createLoadOperationForImageReceiver(imageReceiver, key, url, ext, null, httpLocation, filter, 0, 1, 0); } else { - createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, - thumbLocation, null, thumbFilter, 0, true, thumbSet ? 2 : 1); - createLoadOperationForImageReceiver(imageReceiver, key, url, ext, imageLocation, null, - filter, imageReceiver.getSize(), - saveImageToCache || imageReceiver.getCacheOnly(), 0); + int cacheType = imageReceiver.getCacheType(); + if (cacheType == 0 && saveImageToCache) { + cacheType = 1; + } + createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, thumbLocation, null, thumbFilter, 0, cacheType == 0 ? 1 : cacheType, thumbSet ? 2 : 1); + createLoadOperationForImageReceiver(imageReceiver, key, url, ext, imageLocation, null, filter, imageReceiver.getSize(), cacheType, 0); } } @@ -1763,6 +1914,7 @@ public void run() { HttpImageTask oldTask = img.httpTask; img.httpTask = new HttpImageTask(oldTask.cacheImage, oldTask.imageSize); httpTasks.add(img.httpTask); + runHttpTasks(false); } }); } @@ -1781,29 +1933,33 @@ public void run() { return; } imageLoadingByUrl.remove(location); - CacheOutTask task = null; + ArrayList tasks = new ArrayList<>(); for (int a = 0; a < img.imageReceiverArray.size(); a++) { + String key = img.keys.get(a); + String filter = img.filters.get(a); ImageReceiver imageReceiver = img.imageReceiverArray.get(a); - CacheImage cacheImage = imageLoadingByKeys.get(img.key); + CacheImage cacheImage = imageLoadingByKeys.get(key); if (cacheImage == null) { cacheImage = new CacheImage(); cacheImage.finalFilePath = finalFile; - cacheImage.key = img.key; + cacheImage.key = key; cacheImage.httpUrl = img.httpUrl; cacheImage.thumb = img.thumb; cacheImage.ext = img.ext; - cacheImage.cacheTask = task = new CacheOutTask(cacheImage); - cacheImage.filter = img.filter; + cacheImage.encryptionKeyPath = img.encryptionKeyPath; + cacheImage.cacheTask = new CacheOutTask(cacheImage); + cacheImage.filter = filter; cacheImage.animatedFile = img.animatedFile; - imageLoadingByKeys.put(cacheImage.key, cacheImage); + imageLoadingByKeys.put(key, cacheImage); + tasks.add(cacheImage.cacheTask); } - cacheImage.addImageReceiver(imageReceiver); + cacheImage.addImageReceiver(imageReceiver, key, filter); } - if (task != null) { + for (int a = 0; a < tasks.size(); a++) { if (img.thumb) { - cacheThumbOutQueue.postRunnable(task); + cacheThumbOutQueue.postRunnable(tasks.get(a)); } else { - cacheOutQueue.postRunnable(task); + cacheOutQueue.postRunnable(tasks.get(a)); } } } @@ -1825,8 +1981,90 @@ public void run() { }); } - public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight, - boolean useMaxScale) { + private void runHttpTasks(boolean complete) { + if (complete) { + currentHttpTasksCount--; + } + while (currentHttpTasksCount < 4 && !httpTasks.isEmpty()) { + HttpImageTask task = httpTasks.poll(); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); + currentHttpTasksCount++; + } + } + + public boolean isLoadingHttpFile(String url) { + return httpFileLoadTasksByKeys.containsKey(url); + } + + public void loadHttpFile(String url, String defaultExt) { + if (url == null || url.length() == 0 || httpFileLoadTasksByKeys.containsKey(url)) { + return; + } + String ext = getHttpUrlExtension(url, defaultExt); + File file = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), Utilities.MD5(url) + "_temp." + ext); + file.delete(); + + HttpFileTask task = new HttpFileTask(url, file, ext); + httpFileLoadTasks.add(task); + httpFileLoadTasksByKeys.put(url, task); + runHttpFileLoadTasks(null, 0); + } + + public void cancelLoadHttpFile(String url) { + HttpFileTask task = httpFileLoadTasksByKeys.get(url); + if (task != null) { + task.cancel(true); + httpFileLoadTasksByKeys.remove(url); + httpFileLoadTasks.remove(task); + } + Runnable runnable = retryHttpsTasks.get(url); + if (runnable != null) { + AndroidUtilities.cancelRunOnUIThread(runnable); + } + runHttpFileLoadTasks(null, 0); + } + + private void runHttpFileLoadTasks(final HttpFileTask oldTask, final int reason) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (oldTask != null) { + currentHttpFileLoadTasksCount--; + } + if (oldTask != null) { + if (reason == 1) { + if (oldTask.canRetry) { + final HttpFileTask newTask = new HttpFileTask(oldTask.url, oldTask.tempFile, oldTask.ext); + Runnable runnable = new Runnable() { + @Override + public void run() { + httpFileLoadTasks.add(newTask); + runHttpFileLoadTasks(null, 0); + } + }; + retryHttpsTasks.put(oldTask.url, runnable); + AndroidUtilities.runOnUIThread(runnable, 1000); + } else { + httpFileLoadTasksByKeys.remove(oldTask.url); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.httpFileDidFailedLoad, oldTask.url, 0); + } + } else if (reason == 2) { + httpFileLoadTasksByKeys.remove(oldTask.url); + File file = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), Utilities.MD5(oldTask.url) + "." + oldTask.ext); + String result = oldTask.tempFile.renameTo(file) ? file.toString() : oldTask.tempFile.toString(); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.httpFileDidLoaded, oldTask.url, result); + } + } + while (currentHttpFileLoadTasksCount < 2 && !httpFileLoadTasks.isEmpty()) { + HttpFileTask task = httpFileLoadTasks.poll(); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); + currentHttpFileLoadTasksCount++; + } + } + }); + } + + public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight, boolean useMaxScale) { BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; InputStream inputStream = null; @@ -1839,7 +2077,7 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH try { path = AndroidUtilities.getPath(uri); } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } } } @@ -1849,35 +2087,30 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH } else if (uri != null) { boolean error = false; try { - inputStream = Gallery.applicationContext.getContentResolver() - .openInputStream(uri); + inputStream = Gallery.applicationContext.getContentResolver().openInputStream(uri); BitmapFactory.decodeStream(inputStream, null, bmOptions); - if (inputStream != null) - inputStream.close(); - inputStream = Gallery.applicationContext.getContentResolver() - .openInputStream(uri); + inputStream.close(); + inputStream = Gallery.applicationContext.getContentResolver().openInputStream(uri); } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); return null; - }finally { - if (inputStream != null){ - try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } } } float photoW = bmOptions.outWidth; float photoH = bmOptions.outHeight; - float scaleFactor = useMaxScale ? Math.max(photoW / maxWidth, photoH / maxHeight) - : Math.min(photoW / maxWidth, photoH / maxHeight); + float scaleFactor = useMaxScale ? Math.max(photoW / maxWidth, photoH / maxHeight) : Math.min(photoW / maxWidth, photoH / maxHeight); if (scaleFactor < 1) { scaleFactor = 1; } bmOptions.inJustDecodeBounds = false; bmOptions.inSampleSize = (int) scaleFactor; + if (bmOptions.inSampleSize % 2 != 0) { + int sample = 1; + while (sample * 2 < bmOptions.inSampleSize) { + sample *= 2; + } + bmOptions.inSampleSize = sample; + } bmOptions.inPurgeable = Build.VERSION.SDK_INT < 21; String exifPath = null; @@ -1906,8 +2139,8 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH matrix.postRotate(270); break; } - } catch (Throwable e) { - e.printStackTrace(); + } catch (Throwable ignore) { + ignore.getStackTrace(); } } @@ -1919,16 +2152,15 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH if (bmOptions.inPurgeable) { Utilities.pinBitmap(b); } - Bitmap newBitmap = Bitmaps.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), - matrix, true); + Bitmap newBitmap = Bitmaps.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true); if (newBitmap != b) { b.recycle(); b = newBitmap; } } } catch (Throwable e) { - e.printStackTrace(); - ImageLoader.getInstance().clearMemory(); + FileLog.e(e); + GalleryImageLoader.getInstance().clearMemory(); try { if (b == null) { b = BitmapFactory.decodeFile(path, bmOptions); @@ -1937,15 +2169,14 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH } } if (b != null) { - Bitmap newBitmap = Bitmaps.createBitmap(b, 0, 0, b.getWidth(), - b.getHeight(), matrix, true); + Bitmap newBitmap = Bitmaps.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true); if (newBitmap != b) { b.recycle(); b = newBitmap; } } } catch (Throwable e2) { - e2.printStackTrace(); + FileLog.e(e2); } } } else if (uri != null) { @@ -1955,20 +2186,19 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH if (bmOptions.inPurgeable) { Utilities.pinBitmap(b); } - Bitmap newBitmap = Bitmaps.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), - matrix, true); + Bitmap newBitmap = Bitmaps.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true); if (newBitmap != b) { b.recycle(); b = newBitmap; } } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } finally { try { inputStream.close(); } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } } } @@ -1989,13 +2219,11 @@ public static void fillPhotoSizeWithBytes(PhotoSize photoSize) { f.readFully(photoSize.bytes, 0, photoSize.bytes.length); } } catch (Throwable e) { - e.printStackTrace(); + FileLog.e(e); } } - private static PhotoSize scaleAndSaveImageInternal(Bitmap bitmap, int w, int h, float photoW, - float photoH, float scaleFactor, int quality, boolean cache, boolean scaleAnyway) - throws Exception { + private static PhotoSize scaleAndSaveImageInternal(Bitmap bitmap, int w, int h, float photoW, float photoH, float scaleFactor, int quality, boolean cache, boolean scaleAnyway) throws IOException { Bitmap scaledBitmap; if (scaleFactor > 1 || scaleAnyway) { scaledBitmap = Bitmaps.createScaledBitmap(bitmap, w, h, true); @@ -2022,21 +2250,40 @@ private static PhotoSize scaleAndSaveImageInternal(Bitmap bitmap, int w, int h, size.type = "w"; } - String fileName = location.volume_id + "_" + location.local_id + ".jpg"; - final File cacheFile = new File( - FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName); - FileOutputStream stream = new FileOutputStream(cacheFile); - scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream); - if (cache) { - ByteArrayOutputStream stream2 = new ByteArrayOutputStream(); - scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream2); - size.bytes = stream2.toByteArray(); - size.size = size.bytes.length; - stream2.close(); - } else { - size.size = (int) stream.getChannel().size(); + FileOutputStream stream = null; + ByteArrayOutputStream stream2 = null; + try { + String fileName = location.volume_id + "_" + location.local_id + ".jpg"; + final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName); + stream = new FileOutputStream(cacheFile); + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream); + if (cache) { + stream2 = new ByteArrayOutputStream(); + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream2); + size.bytes = stream2.toByteArray(); + size.size = size.bytes.length; + } else { + size.size = (int) stream.getChannel().size(); + } + } finally { + if (null != stream) { + try { + stream.close(); + } catch (IOException e) { + /*igone*/ + e.getStackTrace(); + } + } + + if (null != stream2) { + try { + stream2.close(); + } catch (IOException e) { + /*igone*/ + e.getStackTrace(); + } + } } - stream.close(); if (scaledBitmap != bitmap) { scaledBitmap.recycle(); } @@ -2044,13 +2291,11 @@ private static PhotoSize scaleAndSaveImageInternal(Bitmap bitmap, int w, int h, return size; } - public static PhotoSize scaleAndSaveImage(Bitmap bitmap, float maxWidth, float maxHeight, - int quality, boolean cache) { + public static PhotoSize scaleAndSaveImage(Bitmap bitmap, float maxWidth, float maxHeight, int quality, boolean cache) { return scaleAndSaveImage(bitmap, maxWidth, maxHeight, quality, cache, 0, 0); } - public static PhotoSize scaleAndSaveImage(Bitmap bitmap, float maxWidth, float maxHeight, - int quality, boolean cache, int minWidth, int minHeight) { + public static PhotoSize scaleAndSaveImage(Bitmap bitmap, float maxWidth, float maxHeight, int quality, boolean cache, int minWidth, int minHeight) { if (bitmap == null) { return null; } @@ -2078,17 +2323,15 @@ public static PhotoSize scaleAndSaveImage(Bitmap bitmap, float maxWidth, float m } try { - return scaleAndSaveImageInternal(bitmap, w, h, photoW, photoH, scaleFactor, quality, - cache, scaleAnyway); + return scaleAndSaveImageInternal(bitmap, w, h, photoW, photoH, scaleFactor, quality, cache, scaleAnyway); } catch (Throwable e) { - e.printStackTrace(); - ImageLoader.getInstance().clearMemory(); + FileLog.e(e); + GalleryImageLoader.getInstance().clearMemory(); System.gc(); try { - return scaleAndSaveImageInternal(bitmap, w, h, photoW, photoH, scaleFactor, quality, - cache, scaleAnyway); + return scaleAndSaveImageInternal(bitmap, w, h, photoW, photoH, scaleFactor, quality, cache, scaleAnyway); } catch (Throwable e2) { - e2.printStackTrace(); + FileLog.e(e2); return null; } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/HashBag.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/HashBag.java new file mode 100644 index 0000000..3f27fdf --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/HashBag.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013 Michael Evans + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tangxiaolv.telegramgallery.utils; + +import java.util.HashMap; +import java.util.Iterator; + +public class HashBag extends HashMap { + + public HashBag() { + super(); + } + + public int getCount(K value) { + if (get(value) == null) { + return 0; + } else { + return get(value); + } + } + + public void add(K value) { + if (get(value) == null) { + put(value, 1); + } else { + put(value, get(value) + 1); + } + } + + public Iterator iterator() { + return keySet().iterator(); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/LayoutHelper.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LayoutHelper.java similarity index 78% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/LayoutHelper.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LayoutHelper.java index 7ad3c68..b23a085 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/LayoutHelper.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LayoutHelper.java @@ -1,5 +1,5 @@ -package com.tangxiaolv.telegramgallery.Utils; +package com.tangxiaolv.telegramgallery.utils; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -20,7 +20,7 @@ public static FrameLayout.LayoutParams createScroll(int width, int height, int g } public static FrameLayout.LayoutParams createFrame(int width, float height, int gravity, - float leftMargin, float topMargin, float rightMargin, float bottomMargin) { + float leftMargin, float topMargin, float rightMargin, float bottomMargin) { FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(getSize(width), getSize(height), gravity); layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), @@ -37,8 +37,8 @@ public static FrameLayout.LayoutParams createFrame(int width, float height) { } public static RelativeLayout.LayoutParams createRelative(float width, float height, - int leftMargin, int topMargin, int rightMargin, int bottomMargin, int alignParent, - int alignRelative, int anchorRelative) { + int leftMargin, int topMargin, int rightMargin, int bottomMargin, int alignParent, + int alignRelative, int anchorRelative) { RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(getSize(width), getSize(height)); if (alignParent >= 0) { @@ -55,26 +55,26 @@ public static RelativeLayout.LayoutParams createRelative(float width, float heig } public static RelativeLayout.LayoutParams createRelative(int width, int height, int leftMargin, - int topMargin, int rightMargin, int bottomMargin) { + int topMargin, int rightMargin, int bottomMargin) { return createRelative(width, height, leftMargin, topMargin, rightMargin, bottomMargin, -1, -1, -1); } public static RelativeLayout.LayoutParams createRelative(int width, int height, int leftMargin, - int topMargin, int rightMargin, int bottomMargin, int alignParent) { + int topMargin, int rightMargin, int bottomMargin, int alignParent) { return createRelative(width, height, leftMargin, topMargin, rightMargin, bottomMargin, alignParent, -1, -1); } public static RelativeLayout.LayoutParams createRelative(float width, float height, - int leftMargin, int topMargin, int rightMargin, int bottomMargin, int alignRelative, - int anchorRelative) { + int leftMargin, int topMargin, int rightMargin, int bottomMargin, int alignRelative, + int anchorRelative) { return createRelative(width, height, leftMargin, topMargin, rightMargin, bottomMargin, -1, alignRelative, anchorRelative); } public static RelativeLayout.LayoutParams createRelative(int width, int height, int alignParent, - int alignRelative, int anchorRelative) { + int alignRelative, int anchorRelative) { return createRelative(width, height, 0, 0, 0, 0, alignParent, alignRelative, anchorRelative); } @@ -84,17 +84,17 @@ public static RelativeLayout.LayoutParams createRelative(int width, int height) } public static RelativeLayout.LayoutParams createRelative(int width, int height, - int alignParent) { + int alignParent) { return createRelative(width, height, 0, 0, 0, 0, alignParent, -1, -1); } public static RelativeLayout.LayoutParams createRelative(int width, int height, - int alignRelative, int anchorRelative) { + int alignRelative, int anchorRelative) { return createRelative(width, height, 0, 0, 0, 0, -1, alignRelative, anchorRelative); } public static LinearLayout.LayoutParams createLinear(int width, int height, float weight, - int gravity, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { + int gravity, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height), weight); layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), @@ -104,7 +104,7 @@ public static LinearLayout.LayoutParams createLinear(int width, int height, floa } public static LinearLayout.LayoutParams createLinear(int width, int height, float weight, - int leftMargin, int topMargin, int rightMargin, int bottomMargin) { + int leftMargin, int topMargin, int rightMargin, int bottomMargin) { LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height), weight); layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), @@ -113,7 +113,7 @@ public static LinearLayout.LayoutParams createLinear(int width, int height, floa } public static LinearLayout.LayoutParams createLinear(int width, int height, int gravity, - int leftMargin, int topMargin, int rightMargin, int bottomMargin) { + int leftMargin, int topMargin, int rightMargin, int bottomMargin) { LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height)); layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), @@ -123,7 +123,7 @@ public static LinearLayout.LayoutParams createLinear(int width, int height, int } public static LinearLayout.LayoutParams createLinear(int width, int height, float leftMargin, - float topMargin, float rightMargin, float bottomMargin) { + float topMargin, float rightMargin, float bottomMargin) { LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height)); layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), @@ -132,7 +132,7 @@ public static LinearLayout.LayoutParams createLinear(int width, int height, floa } public static LinearLayout.LayoutParams createLinear(int width, int height, float weight, - int gravity) { + int gravity) { LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height), weight); layoutParams.gravity = gravity; diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LocaleController.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LocaleController.java new file mode 100644 index 0000000..267f60c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LocaleController.java @@ -0,0 +1,853 @@ + +package com.tangxiaolv.telegramgallery.utils; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.ArrayMap; +import android.util.Xml; + +import com.tangxiaolv.telegramgallery.Gallery; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.File; +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Locale; + +public class LocaleController { + + static final int QUANTITY_OTHER = 0x0000; + static final int QUANTITY_ZERO = 0x0001; + static final int QUANTITY_ONE = 0x0002; + static final int QUANTITY_TWO = 0x0004; + static final int QUANTITY_FEW = 0x0008; + static final int QUANTITY_MANY = 0x0010; + + public static boolean isRTL = false; + public static int nameDisplayOrder = 1; + private static boolean is24HourFormat = false; + + private ArrayMap allRules = new ArrayMap<>(); + + private Locale currentLocale; + private Locale systemDefaultLocale; + private PluralRules currentPluralRules; + private LocaleInfo currentLocaleInfo; + private LocaleInfo defaultLocalInfo; + private ArrayMap localeValues = new ArrayMap<>(); + private String languageOverride; + private boolean changingConfiguration = false; + + public static class LocaleInfo { + public String name; + public String nameEnglish; + public String shortName; + public String pathToFile; + + public String getSaveString() { + return name + "|" + nameEnglish + "|" + shortName + "|" + pathToFile; + } + + public static LocaleInfo createWithString(String string) { + if (string == null || string.length() == 0) { + return null; + } + String[] args = string.split("\\|"); + if (args.length != 4) { + return null; + } + LocaleInfo localeInfo = new LocaleInfo(); + localeInfo.name = args[0]; + localeInfo.nameEnglish = args[1]; + localeInfo.shortName = args[2]; + localeInfo.pathToFile = args[3]; + return localeInfo; + } + } + + public ArrayList sortedLanguages = new ArrayList<>(); + public ArrayMap languagesDict = new ArrayMap<>(); + + private ArrayList otherLanguages = new ArrayList<>(); + + private static volatile LocaleController Instance = null; + + public static LocaleController getInstance() { + LocaleController localInstance = Instance; + if (localInstance == null) { + synchronized (LocaleController.class) { + localInstance = Instance; + if (localInstance == null) { + Instance = localInstance = new LocaleController(); + } + } + } + return localInstance; + } + + public LocaleController() { + addRules(new String[] { + "bem", "brx", "da", "de", "el", "en", "eo", "es", "et", "fi", "fo", "gl", "he", + "iw", "it", "nb", "nl", "nn", "no", "sv", "af", "bg", "bn", "ca", "eu", "fur", "fy", + "gu", "ha", "is", "ku", "lb", "ml", "mr", "nah", "ne", "om", "or", "pa", "pap", + "ps", "so", "sq", "sw", "ta", "te", "tk", "ur", "zu", "mn", "gsw", "chr", "rm", + "pt", "an", "ast" + }, new PluralRules_One()); + addRules(new String[] { + "cs", "sk" + }, new PluralRules_Czech()); + addRules(new String[] { + "ff", "fr", "kab" + }, new PluralRules_French()); + addRules(new String[] { + "hr", "ru", "sr", "uk", "be", "bs", "sh" + }, new PluralRules_Balkan()); + addRules(new String[] { + "lv" + }, new PluralRules_Latvian()); + addRules(new String[] { + "lt" + }, new PluralRules_Lithuanian()); + addRules(new String[] { + "pl" + }, new PluralRules_Polish()); + addRules(new String[] { + "ro", "mo" + }, new PluralRules_Romanian()); + addRules(new String[] { + "sl" + }, new PluralRules_Slovenian()); + addRules(new String[] { + "ar" + }, new PluralRules_Arabic()); + addRules(new String[] { + "mk" + }, new PluralRules_Macedonian()); + addRules(new String[] { + "cy" + }, new PluralRules_Welsh()); + addRules(new String[] { + "br" + }, new PluralRules_Breton()); + addRules(new String[] { + "lag" + }, new PluralRules_Langi()); + addRules(new String[] { + "shi" + }, new PluralRules_Tachelhit()); + addRules(new String[] { + "mt" + }, new PluralRules_Maltese()); + addRules(new String[] { + "ga", "se", "sma", "smi", "smj", "smn", "sms" + }, new PluralRules_Two()); + addRules(new String[] { + "ak", "am", "bh", "fil", "tl", "guw", "hi", "ln", "mg", "nso", "ti", "wa" + }, new PluralRules_Zero()); + addRules(new String[] { + "az", "bm", "fa", "ig", "hu", "ja", "kde", "kea", "ko", "my", "ses", "sg", "to", + "tr", "vi", "wo", "yo", "zh", "bo", "dz", "id", "jv", "ka", "km", "kn", "ms", "th" + }, new PluralRules_None()); + + LocaleInfo localeInfo = new LocaleInfo(); + localeInfo.name = "English"; + localeInfo.nameEnglish = "English"; + localeInfo.shortName = "en"; + localeInfo.pathToFile = null; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + localeInfo = new LocaleInfo(); + localeInfo.name = "Italiano"; + localeInfo.nameEnglish = "Italian"; + localeInfo.shortName = "it"; + localeInfo.pathToFile = null; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + localeInfo = new LocaleInfo(); + localeInfo.name = "Español"; + localeInfo.nameEnglish = "Spanish"; + localeInfo.shortName = "es"; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + localeInfo = new LocaleInfo(); + localeInfo.name = "Deutsch"; + localeInfo.nameEnglish = "German"; + localeInfo.shortName = "de"; + localeInfo.pathToFile = null; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + localeInfo = new LocaleInfo(); + localeInfo.name = "Nederlands"; + localeInfo.nameEnglish = "Dutch"; + localeInfo.shortName = "nl"; + localeInfo.pathToFile = null; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + localeInfo = new LocaleInfo(); + localeInfo.name = "العربية"; + localeInfo.nameEnglish = "Arabic"; + localeInfo.shortName = "ar"; + localeInfo.pathToFile = null; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + localeInfo = new LocaleInfo(); + localeInfo.name = "Português (Brasil)"; + localeInfo.nameEnglish = "Portuguese (Brazil)"; + localeInfo.shortName = "pt_BR"; + localeInfo.pathToFile = null; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + localeInfo = new LocaleInfo(); + localeInfo.name = "Português (Portugal)"; + localeInfo.nameEnglish = "Portuguese (Portugal)"; + localeInfo.shortName = "pt_PT"; + localeInfo.pathToFile = null; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + localeInfo = new LocaleInfo(); + localeInfo.name = "한국어"; + localeInfo.nameEnglish = "Korean"; + localeInfo.shortName = "ko"; + localeInfo.pathToFile = null; + sortedLanguages.add(localeInfo); + languagesDict.put(localeInfo.shortName, localeInfo); + + loadOtherLanguages(); + + for (LocaleInfo locale : otherLanguages) { + sortedLanguages.add(locale); + languagesDict.put(locale.shortName, locale); + } + + Collections.sort(sortedLanguages, new Comparator() { + @Override + public int compare(LocaleInfo o, LocaleInfo o2) { + return o.name.compareTo(o2.name); + } + }); + + defaultLocalInfo = localeInfo = new LocaleInfo(); + localeInfo.name = "System default"; + localeInfo.nameEnglish = "System default"; + localeInfo.shortName = null; + localeInfo.pathToFile = null; + sortedLanguages.add(0, localeInfo); + + systemDefaultLocale = Locale.getDefault(); + is24HourFormat = DateFormat.is24HourFormat(Gallery.applicationContext); + LocaleInfo currentInfo = null; + boolean override = false; + + try { + SharedPreferences preferences = Gallery.applicationContext + .getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + String lang = preferences.getString("language", null); + if (lang != null) { + currentInfo = languagesDict.get(lang); + if (currentInfo != null) { + override = true; + } + } + + if (currentInfo == null && systemDefaultLocale.getLanguage() != null) { + currentInfo = languagesDict.get(systemDefaultLocale.getLanguage()); + } + if (currentInfo == null) { + currentInfo = languagesDict.get(getLocaleString(systemDefaultLocale)); + } + if (currentInfo == null) { + currentInfo = languagesDict.get("en"); + } + applyLanguage(currentInfo, override); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void addRules(String[] languages, PluralRules rules) { + for (String language : languages) { + allRules.put(language, rules); + } + } + + private String stringForQuantity(int quantity) { + switch (quantity) { + case QUANTITY_ZERO: + return "zero"; + case QUANTITY_ONE: + return "one"; + case QUANTITY_TWO: + return "two"; + case QUANTITY_FEW: + return "few"; + case QUANTITY_MANY: + return "many"; + default: + return "other"; + } + } + + public Locale getSystemDefaultLocale() { + return systemDefaultLocale; + } + + private String getLocaleString(Locale locale) { + if (locale == null) { + return "en"; + } + String languageCode = locale.getLanguage(); + String countryCode = locale.getCountry(); + String variantCode = locale.getVariant(); + if (languageCode.length() == 0 && countryCode.length() == 0) { + return "en"; + } + StringBuilder result = new StringBuilder(11); + result.append(languageCode); + if (countryCode.length() > 0 || variantCode.length() > 0) { + result.append('_'); + } + result.append(countryCode); + if (variantCode.length() > 0) { + result.append('_'); + } + result.append(variantCode); + return result.toString(); + } + + public static String getLocaleStringIso639() { + Locale locale = getInstance().getSystemDefaultLocale(); + if (locale == null) { + return "en"; + } + String languageCode = locale.getLanguage(); + String countryCode = locale.getCountry(); + String variantCode = locale.getVariant(); + if (languageCode.length() == 0 && countryCode.length() == 0) { + return "en"; + } + StringBuilder result = new StringBuilder(11); + result.append(languageCode); + if (countryCode.length() > 0 || variantCode.length() > 0) { + result.append('-'); + } + result.append(countryCode); + if (variantCode.length() > 0) { + result.append('_'); + } + result.append(variantCode); + return result.toString(); + } + + private void saveOtherLanguages() { + SharedPreferences preferences = Gallery.applicationContext + .getSharedPreferences("langconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + String locales = ""; + for (LocaleInfo localeInfo : otherLanguages) { + String loc = localeInfo.getSaveString(); + if (loc != null) { + if (locales.length() != 0) { + locales += "&"; + } + locales += loc; + } + } + editor.putString("locales", locales); + editor.commit(); + } + + public boolean deleteLanguage(LocaleInfo localeInfo) { + if (localeInfo.pathToFile == null) { + return false; + } + if (currentLocaleInfo == localeInfo) { + applyLanguage(defaultLocalInfo, true); + } + + otherLanguages.remove(localeInfo); + sortedLanguages.remove(localeInfo); + languagesDict.remove(localeInfo.shortName); + File file = new File(localeInfo.pathToFile); + file.delete(); + saveOtherLanguages(); + return true; + } + + private void loadOtherLanguages() { + SharedPreferences preferences = Gallery.applicationContext + .getSharedPreferences("langconfig", Activity.MODE_PRIVATE); + String locales = preferences.getString("locales", null); + if (locales == null || locales.length() == 0) { + return; + } + String[] localesArr = locales.split("&"); + for (String locale : localesArr) { + LocaleInfo localeInfo = LocaleInfo.createWithString(locale); + if (localeInfo != null) { + otherLanguages.add(localeInfo); + } + } + } + + private ArrayMap getLocaleFileStrings(File file) { + FileInputStream stream = null; + try { + ArrayMap stringMap = new ArrayMap<>(); + XmlPullParser parser = Xml.newPullParser(); + stream = new FileInputStream(file); + parser.setInput(stream, "UTF-8"); + int eventType = parser.getEventType(); + String name = null; + String value = null; + String attrName = null; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + name = parser.getName(); + int c = parser.getAttributeCount(); + if (c > 0) { + attrName = parser.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.TEXT) { + if (attrName != null) { + value = parser.getText(); + if (value != null) { + value = value.trim(); + value = value.replace("\\n", "\n"); + value = value.replace("\\", ""); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + value = null; + attrName = null; + name = null; + } + if (name != null && name.equals("string") && value != null && attrName != null + && value.length() != 0 && attrName.length() != 0) { + stringMap.put(attrName, value); + name = null; + value = null; + attrName = null; + } + eventType = parser.next(); + } + return stringMap; + } catch (Exception e) { + e.getStackTrace(); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return new ArrayMap<>(); + } + + public void applyLanguage(LocaleInfo localeInfo, boolean override) { + applyLanguage(localeInfo, override, false); + } + + public void applyLanguage(LocaleInfo localeInfo, boolean override, boolean fromFile) { + if (localeInfo == null) { + return; + } + try { + Locale newLocale; + if (localeInfo.shortName != null) { + String[] args = localeInfo.shortName.split("_"); + if (args.length == 1) { + newLocale = new Locale(localeInfo.shortName); + } else { + newLocale = new Locale(args[0], args[1]); + } + if (newLocale != null) { + if (override) { + languageOverride = localeInfo.shortName; + + SharedPreferences preferences = Gallery.applicationContext + .getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("language", localeInfo.shortName); + editor.commit(); + } + } + } else { + newLocale = systemDefaultLocale; + languageOverride = null; + SharedPreferences preferences = Gallery.applicationContext + .getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.remove("language"); + editor.commit(); + + if (newLocale != null) { + LocaleInfo info = null; + if (newLocale.getLanguage() != null) { + info = languagesDict.get(newLocale.getLanguage()); + } + if (info == null) { + info = languagesDict.get(getLocaleString(newLocale)); + } + if (info == null) { + newLocale = Locale.US; + } + } + } + if (newLocale != null) { + if (localeInfo.pathToFile == null) { + localeValues.clear(); + } else if (!fromFile) { + localeValues = getLocaleFileStrings(new File(localeInfo.pathToFile)); + } + currentLocale = newLocale; + currentLocaleInfo = localeInfo; + currentPluralRules = allRules.get(currentLocale.getLanguage()); + if (currentPluralRules == null) { + currentPluralRules = allRules.get("en"); + } + changingConfiguration = true; + Locale locale = Gallery.applicationContext.getResources().getConfiguration().locale; + if (TextUtils.equals(locale.getLanguage(), "zh")) { + currentLocale = locale; + } + Locale.setDefault(currentLocale); + Configuration config = new Configuration(); + config.locale = currentLocale; + Gallery.applicationContext.getResources().updateConfiguration(config, + Gallery.applicationContext.getResources().getDisplayMetrics()); + changingConfiguration = false; + } + } catch (Exception e) { + e.printStackTrace(); + changingConfiguration = false; + } + } + + private String getStringInternal(String key, int res) { + String value = localeValues.get(key); + if (value == null) { + try { + value = Gallery.applicationContext.getString(res); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (value == null) { + value = "LOC_ERR:" + key; + } + return value; + } + + public static String getString(String key, int res) { + return getInstance().getStringInternal(key, res); + } + + public static String formatPluralString(String key, int plural) { + if (key == null || key.length() == 0 || getInstance().currentPluralRules == null) { + return "LOC_ERR:" + key; + } + String param = getInstance() + .stringForQuantity(getInstance().currentPluralRules.quantityForNumber(plural)); + param = key + "_" + param; + int resourceId = Gallery.applicationContext.getResources().getIdentifier(param, + "string", Gallery.applicationContext.getPackageName()); + return formatString(param, resourceId, plural); + } + + public static String formatString(String key, int res, Object... args) { + try { + String value = getInstance().localeValues.get(key); + if (value == null) { + value = Gallery.applicationContext.getString(res); + } + + if (getInstance().currentLocale != null) { + return String.format(getInstance().currentLocale, value, args); + } else { + return String.format(value, args); + } + } catch (Exception e) { + e.printStackTrace(); + return "LOC_ERR: " + key; + } + } + + public static String formatStringSimple(String string, Object... args) { + try { + if (getInstance().currentLocale != null) { + return String.format(getInstance().currentLocale, string, args); + } else { + return String.format(string, args); + } + } catch (Exception e) { + e.printStackTrace(); + return "LOC_ERR: " + string; + } + } + + abstract public static class PluralRules { + abstract int quantityForNumber(int n); + } + + public static class PluralRules_Zero extends PluralRules { + public int quantityForNumber(int count) { + if (count == 0 || count == 1) { + return QUANTITY_ONE; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Welsh extends PluralRules { + public int quantityForNumber(int count) { + if (count == 0) { + return QUANTITY_ZERO; + } else if (count == 1) { + return QUANTITY_ONE; + } else if (count == 2) { + return QUANTITY_TWO; + } else if (count == 3) { + return QUANTITY_FEW; + } else if (count == 6) { + return QUANTITY_MANY; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Two extends PluralRules { + public int quantityForNumber(int count) { + if (count == 1) { + return QUANTITY_ONE; + } else if (count == 2) { + return QUANTITY_TWO; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Tachelhit extends PluralRules { + public int quantityForNumber(int count) { + if (count >= 0 && count <= 1) { + return QUANTITY_ONE; + } else if (count >= 2 && count <= 10) { + return QUANTITY_FEW; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Slovenian extends PluralRules { + public int quantityForNumber(int count) { + int rem100 = count % 100; + if (rem100 == 1) { + return QUANTITY_ONE; + } else if (rem100 == 2) { + return QUANTITY_TWO; + } else if (rem100 >= 3 && rem100 <= 4) { + return QUANTITY_FEW; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Romanian extends PluralRules { + public int quantityForNumber(int count) { + int rem100 = count % 100; + if (count == 1) { + return QUANTITY_ONE; + } else if ((count == 0 || (rem100 >= 1 && rem100 <= 19))) { + return QUANTITY_FEW; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Polish extends PluralRules { + public int quantityForNumber(int count) { + int rem100 = count % 100; + int rem10 = count % 10; + if (count == 1) { + return QUANTITY_ONE; + } else if (rem10 >= 2 && rem10 <= 4 && !(rem100 >= 12 && rem100 <= 14) + && !(rem100 >= 22 && rem100 <= 24)) { + return QUANTITY_FEW; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_One extends PluralRules { + public int quantityForNumber(int count) { + return count == 1 ? QUANTITY_ONE : QUANTITY_OTHER; + } + } + + public static class PluralRules_None extends PluralRules { + public int quantityForNumber(int count) { + return QUANTITY_OTHER; + } + } + + public static class PluralRules_Maltese extends PluralRules { + public int quantityForNumber(int count) { + int rem100 = count % 100; + if (count == 1) { + return QUANTITY_ONE; + } else if (count == 0 || (rem100 >= 2 && rem100 <= 10)) { + return QUANTITY_FEW; + } else if (rem100 >= 11 && rem100 <= 19) { + return QUANTITY_MANY; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Macedonian extends PluralRules { + public int quantityForNumber(int count) { + if (count % 10 == 1 && count != 11) { + return QUANTITY_ONE; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Lithuanian extends PluralRules { + public int quantityForNumber(int count) { + int rem100 = count % 100; + int rem10 = count % 10; + if (rem10 == 1 && !(rem100 >= 11 && rem100 <= 19)) { + return QUANTITY_ONE; + } else if (rem10 >= 2 && rem10 <= 9 && !(rem100 >= 11 && rem100 <= 19)) { + return QUANTITY_FEW; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Latvian extends PluralRules { + public int quantityForNumber(int count) { + if (count == 0) { + return QUANTITY_ZERO; + } else if (count % 10 == 1 && count % 100 != 11) { + return QUANTITY_ONE; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Langi extends PluralRules { + public int quantityForNumber(int count) { + if (count == 0) { + return QUANTITY_ZERO; + } else if (count > 0 && count < 2) { + return QUANTITY_ONE; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_French extends PluralRules { + public int quantityForNumber(int count) { + if (count >= 0 && count < 2) { + return QUANTITY_ONE; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Czech extends PluralRules { + public int quantityForNumber(int count) { + if (count == 1) { + return QUANTITY_ONE; + } else if (count >= 2 && count <= 4) { + return QUANTITY_FEW; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Breton extends PluralRules { + public int quantityForNumber(int count) { + if (count == 0) { + return QUANTITY_ZERO; + } else if (count == 1) { + return QUANTITY_ONE; + } else if (count == 2) { + return QUANTITY_TWO; + } else if (count == 3) { + return QUANTITY_FEW; + } else if (count == 6) { + return QUANTITY_MANY; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Balkan extends PluralRules { + public int quantityForNumber(int count) { + int rem100 = count % 100; + int rem10 = count % 10; + if (rem10 == 1 && rem100 != 11) { + return QUANTITY_ONE; + } else if (rem10 >= 2 && rem10 <= 4 && !(rem100 >= 12 && rem100 <= 14)) { + return QUANTITY_FEW; + } else if ((rem10 == 0 || (rem10 >= 5 && rem10 <= 9) + || (rem100 >= 11 && rem100 <= 14))) { + return QUANTITY_MANY; + } else { + return QUANTITY_OTHER; + } + } + } + + public static class PluralRules_Arabic extends PluralRules { + public int quantityForNumber(int count) { + int rem100 = count % 100; + if (count == 0) { + return QUANTITY_ZERO; + } else if (count == 1) { + return QUANTITY_ONE; + } else if (count == 2) { + return QUANTITY_TWO; + } else if (rem100 >= 3 && rem100 <= 10) { + return QUANTITY_FEW; + } else if (rem100 >= 11 && rem100 <= 99) { + return QUANTITY_MANY; + } else { + return QUANTITY_OTHER; + } + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LruCache.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LruCache.java new file mode 100644 index 0000000..a49977c --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/LruCache.java @@ -0,0 +1,245 @@ +package com.tangxiaolv.telegramgallery.utils; + +import android.graphics.drawable.BitmapDrawable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; + +/** + * Static library version of {@link android.util.LruCache}. Used to write apps + * that run on API levels prior to 12. When running on API level 12 or above, + * this implementation is still used; it does not try to switch to the + * framework's implementation. See the framework SDK documentation for a class + * overview. + */ +public class LruCache { + private final LinkedHashMap map; + private final LinkedHashMap> mapFilters; + + /** Size of this cache in units. Not necessarily the number of elements. */ + private int size; + private int maxSize; + + /** + * @param maxSize for caches that do not override {@link #sizeOf}, this is + * the maximum number of entries in the cache. For all other caches, + * this is the maximum sum of the sizes of the entries in this cache. + */ + public LruCache(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + this.maxSize = maxSize; + this.map = new LinkedHashMap<>(0, 0.75f, true); + this.mapFilters = new LinkedHashMap<>(); + } + + /** + * Returns the value for {@code key} if it exists in the cache or can be + * created by {@code #create}. If a value was returned, it is moved to the + * head of the queue. This returns null if a value is not cached and cannot + * be created. + */ + public final BitmapDrawable get(String key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + BitmapDrawable mapValue; + synchronized (this) { + mapValue = map.get(key); + if (mapValue != null) { + return mapValue; + } + } + return null; + } + + public ArrayList getFilterKeys(String key) { + ArrayList arr = mapFilters.get(key); + if (arr != null) { + return new ArrayList<>(arr); + } + return null; + } + + /** + * Caches {@code value} for {@code key}. The value is moved to the head of + * the queue. + * + * @return the previous value mapped by {@code key}. + */ + public BitmapDrawable put(String key, BitmapDrawable value) { + if (key == null || value == null) { + throw new NullPointerException("key == null || value == null"); + } + + BitmapDrawable previous; + synchronized (this) { + size += safeSizeOf(key, value); + previous = map.put(key, value); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + String[] args = key.split("@"); + if (args.length > 1) { + ArrayList arr = mapFilters.get(args[0]); + if (arr == null) { + arr = new ArrayList<>(); + mapFilters.put(args[0], arr); + } + if (!arr.contains(args[1])) { + arr.add(args[1]); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, value); + } + + trimToSize(maxSize, key); + return previous; + } + + /** + * @param maxSize the maximum size of the cache before returning. May be -1 + * to evict even 0-sized elements. + */ + private void trimToSize(int maxSize, String justAdded) { + synchronized (this) { + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + if (size <= maxSize || map.isEmpty()) { + break; + } + HashMap.Entry entry = iterator.next(); + + String key = entry.getKey(); + if (justAdded != null && justAdded.equals(key)) { + continue; + } + BitmapDrawable value = entry.getValue(); + size -= safeSizeOf(key, value); + iterator.remove(); + + String[] args = key.split("@"); + if (args.length > 1) { + ArrayList arr = mapFilters.get(args[0]); + if (arr != null) { + arr.remove(args[1]); + if (arr.isEmpty()) { + mapFilters.remove(args[0]); + } + } + } + + entryRemoved(true, key, value, null); + } + } + } + + /** + * Removes the entry for {@code key} if it exists. + * + * @return the previous value mapped by {@code key}. + */ + public final BitmapDrawable remove(String key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + BitmapDrawable previous; + synchronized (this) { + previous = map.remove(key); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + String[] args = key.split("@"); + if (args.length > 1) { + ArrayList arr = mapFilters.get(args[0]); + if (arr != null) { + arr.remove(args[1]); + if (arr.isEmpty()) { + mapFilters.remove(args[0]); + } + } + } + + entryRemoved(false, key, previous, null); + } + + return previous; + } + + public boolean contains(String key){ + return map.containsKey(key); + } + + /** + * Called for entries that have been evicted or removed. This method is + * invoked when a value is evicted to make space, removed by a call to + * {@link #remove}, or replaced by a call to {@link #put}. The default + * implementation does nothing. + * + *

    The method is called without synchronization: other threads may + * access the cache while this method is executing. + * + * @param evicted true if the entry is being removed to make space, false + * if the removal was caused by a {@link #put} or {@link #remove}. + * @param newValue the new value for {@code key}, if it exists. If non-null, + * this removal was caused by a {@link #put}. Otherwise it was caused by + * an eviction or a {@link #remove}. + */ + protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) {} + + private int safeSizeOf(String key, BitmapDrawable value) { + int result = sizeOf(key, value); + if (result < 0) { + throw new IllegalStateException("Negative size: " + key + "=" + value); + } + return result; + } + + /** + * Returns the size of the entry for {@code key} and {@code value} in + * user-defined units. The default implementation returns 1 so that size + * is the number of entries and max size is the maximum number of entries. + * + *

    An entry's size must not change while it is in the cache. + */ + protected int sizeOf(String key, BitmapDrawable value) { + return 1; + } + + /** + * Clear the cache, calling {@link #entryRemoved} on each removed entry. + */ + public final void evictAll() { + trimToSize(-1, null); // -1 will evict 0-sized elements + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the number + * of entries in the cache. For all other caches, this returns the sum of + * the sizes of the entries in this cache. + */ + public synchronized final int size() { + return size; + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the maximum + * number of entries in the cache. For all other caches, this returns the + * maximum sum of the sizes of the entries in this cache. + */ + public synchronized final int maxSize() { + return maxSize; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/MediaController.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/MediaController.java similarity index 66% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/MediaController.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/MediaController.java index 1088484..570eb71 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/MediaController.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/MediaController.java @@ -1,39 +1,31 @@ +package com.tangxiaolv.telegramgallery.utils; -package com.tangxiaolv.telegramgallery.Utils; - -import android.annotation.SuppressLint; import android.app.Activity; import android.content.SharedPreferences; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.BitmapFactory; import android.graphics.Point; -import android.media.AudioFormat; -import android.media.AudioRecord; -import android.media.AudioTrack; -import android.media.MediaCodecInfo; -import android.media.MediaCodecList; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; -import android.util.SparseArray; +import android.util.ArrayMap; import com.tangxiaolv.telegramgallery.Gallery; import com.tangxiaolv.telegramgallery.R; -import com.tangxiaolv.telegramgallery.TL.Document; +import java.io.Closeable; import java.io.File; +import java.io.Serializable; import java.lang.ref.WeakReference; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; -import static android.R.attr.orientation; +import static com.tangxiaolv.telegramgallery.GalleryActivity.getConfig; public class MediaController implements NotificationCenter.NotificationCenterDelegate { - public static int[] readArgs = new int[3]; private boolean saveToGallery = true; public interface FileDownloadProgressListener { @@ -43,24 +35,9 @@ public interface FileDownloadProgressListener { void onProgressDownload(String fileName, float progress); - void onProgressUpload(String fileName, float progress, boolean isEncrypted); - int getObserverTag(); } - private class AudioBuffer { - public AudioBuffer(int capacity) { - buffer = ByteBuffer.allocateDirect(capacity); - bufferBytes = new byte[capacity]; - } - - ByteBuffer buffer; - byte[] bufferBytes; - int size; - int finished; - long pcmOffset; - } - private static final String[] projectionPhotos = { MediaStore.Images.Media._ID, MediaStore.Images.Media.BUCKET_ID, @@ -68,8 +45,8 @@ public AudioBuffer(int capacity) { MediaStore.Images.Media.DATA, MediaStore.Images.Media.DATE_TAKEN, MediaStore.Images.Media.ORIENTATION, - MediaStore.Images.Media.SIZE - // MediaStore.Images.Media.MIME_TYPE + MediaStore.Images.Media.SIZE, + MediaStore.Images.Media.MIME_TYPE, }; private static final String[] projectionVideo = { @@ -77,7 +54,11 @@ public AudioBuffer(int capacity) { MediaStore.Video.Media.BUCKET_ID, MediaStore.Video.Media.BUCKET_DISPLAY_NAME, MediaStore.Video.Media.DATA, - MediaStore.Video.Media.DATE_TAKEN + MediaStore.Video.Media.DATE_TAKEN, + MediaStore.Video.Media.DURATION, + MediaStore.Video.Media.MIME_TYPE, + MediaStore.Video.Media.DISPLAY_NAME, + MediaStore.Video.Media.SIZE, }; public static class AlbumEntry { @@ -101,48 +82,59 @@ public void addPhoto(PhotoEntry photoEntry) { } } - public static class PhotoEntry { - public int sortindex; + public static class PhotoEntry implements Comparator, Serializable { public int bucketId; public int imageId; public long dateTaken; + public long size; public String path; + public String mimeType; public int orientation; public String thumbPath; public String imagePath; public boolean isVideo; public CharSequence caption; - - public PhotoEntry(int bucketId, int imageId, long dateTaken, String path, int orientation, - boolean isVideo) { + public int sortindex; + public int duration;//sec + public String title; + + public PhotoEntry(int bucketId, + int imageId, + long dateTaken, + long size, + String path, + String mimeType, + int orientation, + boolean isVideo, + String title + ) { this.bucketId = bucketId; this.imageId = imageId; this.dateTaken = dateTaken; + this.size = size; this.path = path; - this.orientation = orientation; + this.mimeType = mimeType; + this.title = title; + if (isVideo) { + this.duration = orientation; + } else { + this.orientation = 0; + } this.isVideo = isVideo; } - } - - public static class SearchImage { - public String id; - public String imageUrl; - public String thumbUrl; - public String localUrl; - public int width; - public int height; - public int size; - public int type; - public int date; - public String thumbPath; - public String imagePath; - public CharSequence caption; - public Document document; + @Override + public int compare(PhotoEntry o1, PhotoEntry o2) { + if (o1.sortindex > o2.sortindex) { + return 1; + } else if (o1.sortindex == o2.sortindex) { + return 0; + } else { + return -1; + } + } } - private HashMap typingTimes = new HashMap<>(); - public static final int AUTODOWNLOAD_MASK_PHOTO = 1; public static final int AUTODOWNLOAD_MASK_AUDIO = 2; public static final int AUTODOWNLOAD_MASK_MUSIC = 16; @@ -152,23 +144,12 @@ public static class SearchImage { public int roamingDownloadMask = 0; private Runnable refreshGalleryRunnable; - public static AlbumEntry allPhotosAlbumEntry; private HashMap>> loadingFileObservers = new HashMap<>(); private HashMap observersByTag = new HashMap<>(); private boolean listenerInProgress = false; - private HashMap addLaterArray = new HashMap<>(); private ArrayList deleteLaterArray = new ArrayList<>(); - private int playerBufferSize = 0; - - private ArrayList freePlayerBuffers = new ArrayList<>(); - - private final Object sync = new Object(); - - private ArrayList recordBuffers = new ArrayList<>(); - private int recordBufferSize; - private class InternalObserver extends ContentObserver { public InternalObserver() { super(null); @@ -285,29 +266,6 @@ public static MediaController getInstance() { } public MediaController() { - try { - recordBufferSize = AudioRecord.getMinBufferSize(16000, AudioFormat.CHANNEL_IN_MONO, - AudioFormat.ENCODING_PCM_16BIT); - if (recordBufferSize <= 0) { - recordBufferSize = 1280; - } - playerBufferSize = AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, - AudioFormat.ENCODING_PCM_16BIT); - if (playerBufferSize <= 0) { - playerBufferSize = 3840; - } - for (int a = 0; a < 5; a++) { - ByteBuffer buffer = ByteBuffer.allocateDirect(4096); - buffer.order(ByteOrder.nativeOrder()); - recordBuffers.add(buffer); - } - for (int a = 0; a < 3; a++) { - freePlayerBuffers.add(new AudioBuffer(playerBufferSize)); - } - } catch (Exception e) { - e.printStackTrace(); - } - SharedPreferences preferences = Gallery.applicationContext .getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); mobileDataDownloadMask = preferences.getInt("mobileDataDownloadMask", @@ -317,30 +275,8 @@ public MediaController() { | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | AUTODOWNLOAD_MASK_GIF); roamingDownloadMask = preferences.getInt("roamingDownloadMask", 0); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - NotificationCenter.getInstance().addObserver(MediaController.this, - NotificationCenter.FileDidFailedLoad); - NotificationCenter.getInstance().addObserver(MediaController.this, - NotificationCenter.didReceivedNewMessages); - NotificationCenter.getInstance().addObserver(MediaController.this, - NotificationCenter.messagesDeleted); - NotificationCenter.getInstance().addObserver(MediaController.this, - NotificationCenter.FileDidLoaded); - NotificationCenter.getInstance().addObserver(MediaController.this, - NotificationCenter.FileLoadProgressChanged); - NotificationCenter.getInstance().addObserver(MediaController.this, - NotificationCenter.FileUploadProgressChanged); - NotificationCenter.getInstance().addObserver(MediaController.this, - NotificationCenter.removeAllMessagesFromDialog); - NotificationCenter.getInstance().addObserver(MediaController.this, - NotificationCenter.musicDidLoaded); - } - }); - if (Build.VERSION.SDK_INT >= 16) { - mediaProjections = new String[] { + mediaProjections = new String[]{ MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.DISPLAY_NAME, MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, @@ -350,7 +286,7 @@ public void run() { MediaStore.Images.ImageColumns.HEIGHT }; } else { - mediaProjections = new String[] { + mediaProjections = new String[]{ MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.DISPLAY_NAME, MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, @@ -375,8 +311,7 @@ public void run() { } } - public void cleanup() { - typingTimes.clear(); + public void cleanUp() { } public void processMediaObserver(Uri uri) { @@ -388,7 +323,6 @@ public void processMediaObserver(Uri uri) { final ArrayList screenshotDates = new ArrayList<>(); if (cursor != null) { while (cursor.moveToNext()) { - String val = ""; String data = cursor.getString(0); String display_name = cursor.getString(1); String album_name = cursor.getString(2); @@ -465,7 +399,6 @@ public void removeLoadingFileObserver(FileDownloadProgressListener observer) { } private void processLaterArrays() { - addLaterArray.clear(); for (FileDownloadProgressListener listener : deleteLaterArray) { removeLoadingFileObserver(listener); } @@ -527,58 +460,61 @@ public void didReceivedNotification(int id, Object... args) { } } - public static void loadGalleryPhotosAlbums(final int guid, final String[] filterMimiType) { + public static void loadGalleryPhotosAlbums(final int guid, final String[] filterMimiTypes) { Thread thread = new Thread(new Runnable() { @Override public void run() { - final ArrayList albumsSorted = new ArrayList<>(); - final ArrayList videoAlbumsSorted = new ArrayList<>(); - SparseArray albums = new SparseArray<>(); - AlbumEntry allPhotosAlbum = null; + final ArrayList mediaAlbumsSorted = new ArrayList<>(); + HashMap albums = new HashMap<>(); + AlbumEntry allPhotoAlbum = null; + AlbumEntry allVideosAlbum = null; String cameraFolder = Environment .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) .getAbsolutePath() + "/" + "Camera/"; // 相当于我们常用sql where 后面的写法 + String selection = ""; + int length = 0; StringBuilder selectionBuilder = new StringBuilder(); - if (filterMimiType != null && filterMimiType.length > 0) { - int length = filterMimiType.length; + if (filterMimiTypes != null && (length = filterMimiTypes.length) > 0) { + selectionBuilder.append(MediaStore.Files.FileColumns.MIME_TYPE); + selectionBuilder.append(" in ("); for (int i = 0; i < length; i++) { - String mimeType = MediaStore.Files.FileColumns.MIME_TYPE; - if (0 == i) { - selectionBuilder.append(mimeType).append(" !=?"); - } else { - selectionBuilder.append(" and ").append(mimeType).append(" !=?"); + String mimeType = filterMimiTypes[i]; + if (mimeType.contains("image")) { + selectionBuilder.append("'"); + selectionBuilder.append(mimeType); + selectionBuilder.append("'").append(","); } } + + selectionBuilder.append(")"); + int index = selectionBuilder.lastIndexOf(","); + if (index != -1) { + selection = selectionBuilder.deleteCharAt(index).toString(); + } } - String selection = selectionBuilder == null ? null : selectionBuilder.toString(); Integer cameraAlbumId = null; - Integer cameraAlbumVideoId = null; + + //加载图片 Cursor cursor = null; try { cursor = MediaStore.Images.Media.query( Gallery.applicationContext.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projectionPhotos, - selection, filterMimiType, + selection, MediaStore.Images.Media.DATE_TAKEN + " DESC"); if (cursor != null) { int imageIdColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID); - int bucketIdColumn = cursor - .getColumnIndex(MediaStore.Images.Media.BUCKET_ID); - int bucketNameColumn = cursor - .getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + int bucketIdColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); + int bucketNameColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); int dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA); - int dateColumn = cursor - .getColumnIndex(MediaStore.Images.Media.DATE_TAKEN); - int orientationColumn = cursor - .getColumnIndex(MediaStore.Images.Media.ORIENTATION); - int imageSize = cursor - .getColumnIndex(MediaStore.Files.FileColumns.SIZE); - // int mimeTypeColumn = - // cursor.getColumnIndex(MediaStore.Files.FileColumns.MIME_TYPE); + int dateColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN); + int orientationColumn = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); + int imageSize = cursor.getColumnIndex(MediaStore.Images.Media.SIZE); + int mimeType = cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE); while (cursor.moveToNext()) { int imageId = cursor.getInt(imageIdColumn); @@ -588,38 +524,35 @@ public void run() { long dateTaken = cursor.getLong(dateColumn); int orientation = cursor.getInt(orientationColumn); int size = cursor.getInt(imageSize); - // String mimeType = cursor.getString(mimeTypeColumn); + String type = cursor.getString(mimeType); - if (path == null || path.length() == 0 || size == 0) { + if (path == null || size == 0 || !new File(path).exists()) { continue; } PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, - path, orientation, false); + size, path, type, orientation, false, ""); - if (allPhotosAlbum == null) { - allPhotosAlbum = new AlbumEntry(0, Gallery.applicationContext.getString( - R.string.AllPhotos), photoEntry, false); - albumsSorted.add(0, allPhotosAlbum); - } - if (allPhotosAlbum != null) { - allPhotosAlbum.addPhoto(photoEntry); + if (allPhotoAlbum == null) { + allPhotoAlbum = new AlbumEntry(0, LocaleController.getString( + "AllPhotos", R.string.AllPhotos), photoEntry, false); + mediaAlbumsSorted.add(0, allPhotoAlbum); } + allPhotoAlbum.addPhoto(photoEntry); - AlbumEntry albumEntry = albums.get(bucketId); + AlbumEntry albumEntry = albums.get(bucketName.hashCode()); if (albumEntry == null) { - albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry, - false); - albums.put(bucketId, albumEntry); + albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry, false); + albums.put(bucketName.hashCode(), albumEntry); if (cameraAlbumId == null && path.startsWith(cameraFolder)) { - if (albumsSorted.size() >= 2) { - albumsSorted.add(1, albumEntry); + if (mediaAlbumsSorted.size() >= 2) { + mediaAlbumsSorted.add(1, albumEntry); } else { - albumsSorted.add(albumEntry); + mediaAlbumsSorted.add(albumEntry); } cameraAlbumId = bucketId; } else { - albumsSorted.add(albumEntry); + mediaAlbumsSorted.add(albumEntry); } } @@ -629,35 +562,53 @@ public void run() { } catch (Throwable e) { e.printStackTrace(); } finally { - if (cursor != null) { - try { - cursor.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } + close(cursor); } + //加载video try { - albums.clear(); - cursor = null; - AlbumEntry allVideosAlbum = null; + if (!getConfig().hasVideo()) { + throw new Exception("igone"); + } + + // 相当于我们常用sql where 后面的写法 + selection = ""; + selectionBuilder = new StringBuilder(); + if (length > 0) { + selectionBuilder.append(MediaStore.Files.FileColumns.MIME_TYPE); + selectionBuilder.append(" in ("); + for (int i = 0; i < length; i++) { + String mimeType = filterMimiTypes[i]; + if (mimeType.contains("video")) { + selectionBuilder.append("'"); + selectionBuilder.append(mimeType); + selectionBuilder.append("'").append(","); + } + } + + selectionBuilder.append(")"); + int index = selectionBuilder.lastIndexOf(","); + if (index != -1) { + selection = selectionBuilder.deleteCharAt(index).toString(); + } + } + cursor = MediaStore.Images.Media.query( Gallery.applicationContext.getContentResolver(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projectionVideo, - selectionBuilder.toString(), - filterMimiType, + selection, MediaStore.Video.Media.DATE_TAKEN + " DESC"); if (cursor != null) { int imageIdColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID); - int bucketIdColumn = cursor - .getColumnIndex(MediaStore.Video.Media.BUCKET_ID); - int bucketNameColumn = cursor - .getColumnIndex(MediaStore.Video.Media.BUCKET_DISPLAY_NAME); + int bucketIdColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_ID); + int bucketNameColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_DISPLAY_NAME); int dataColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATA); - int dateColumn = cursor - .getColumnIndex(MediaStore.Video.Media.DATE_TAKEN); + int dateColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATE_TAKEN); + int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION); + int mimeTypeColumn = cursor.getColumnIndex(MediaStore.Video.Media.MIME_TYPE); + int titleColumn = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME); + int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE); while (cursor.moveToNext()) { int imageId = cursor.getInt(imageIdColumn); @@ -665,63 +616,58 @@ public void run() { String bucketName = cursor.getString(bucketNameColumn); String path = cursor.getString(dataColumn); long dateTaken = cursor.getLong(dateColumn); + long duration = cursor.getLong(durationColumn); + String type = cursor.getString(mimeTypeColumn); + String title = cursor.getString(titleColumn); + long size = cursor.getLong(sizeColumn); - if (path == null || path.length() == 0) { + if (path == null || path.length() == 0 || duration == 0) { continue; } PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, - path, 0, true); + size, path, type, (int)(Math.ceil(duration / 1000d)), true, title); if (allVideosAlbum == null) { - - allVideosAlbum = new AlbumEntry(0, Gallery.applicationContext.getString - (R.string.AllVideo), photoEntry, true); - videoAlbumsSorted.add(0, allVideosAlbum); - } - if (allVideosAlbum != null) { - allVideosAlbum.addPhoto(photoEntry); + allVideosAlbum = new AlbumEntry(1, LocaleController.getString( + "AllVideo", R.string.AllVideo), photoEntry, false); + mediaAlbumsSorted.add(1, allVideosAlbum); } - AlbumEntry albumEntry = albums.get(bucketId); + allVideosAlbum.addPhoto(photoEntry); + /*AlbumEntry albumEntry = albums.get(bucketName.hashCode()); if (albumEntry == null) { - albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry, - true); - albums.put(bucketId, albumEntry); - if (cameraAlbumVideoId == null && cameraFolder != null - && path != null && path.startsWith(cameraFolder)) { - videoAlbumsSorted.add(0, albumEntry); - cameraAlbumVideoId = bucketId; + albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry, false); + albums.put(bucketName.hashCode(), albumEntry); + if (cameraAlbumId == null && path.startsWith(cameraFolder)) { + if (mediaAlbumsSorted.size() >= 2) { + mediaAlbumsSorted.add(2, albumEntry); + } else { + mediaAlbumsSorted.add(albumEntry); + } + cameraAlbumId = bucketId; } else { - videoAlbumsSorted.add(albumEntry); + mediaAlbumsSorted.add(albumEntry); } } - albumEntry.addPhoto(photoEntry); + albumEntry.addPhoto(photoEntry);*/ } } } catch (Throwable e) { - e.printStackTrace(); + //igone + e.getStackTrace(); } finally { - if (cursor != null) { - try { - cursor.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } + close(cursor); } final Integer cameraAlbumIdFinal = cameraAlbumId; - final Integer cameraAlbumVideoIdFinal = cameraAlbumVideoId; - final AlbumEntry allPhotosAlbumFinal = allPhotosAlbum; AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - allPhotosAlbumEntry = allPhotosAlbumFinal; NotificationCenter.getInstance().postNotificationName( - NotificationCenter.albumsDidLoaded, guid, albumsSorted, - cameraAlbumIdFinal, videoAlbumsSorted, cameraAlbumVideoIdFinal); + NotificationCenter.albumsDidLoaded, guid, mediaAlbumsSorted, + cameraAlbumIdFinal, null, cameraAlbumIdFinal); } }); } @@ -730,28 +676,14 @@ public void run() { thread.start(); } - @SuppressLint("NewApi") - public static MediaCodecInfo selectCodec(String mimeType) { - int numCodecs = MediaCodecList.getCodecCount(); - MediaCodecInfo lastCodecInfo = null; - for (int i = 0; i < numCodecs; i++) { - MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); - if (!codecInfo.isEncoder()) { - continue; - } - String[] types = codecInfo.getSupportedTypes(); - for (String type : types) { - if (type.equalsIgnoreCase(mimeType)) { - lastCodecInfo = codecInfo; - if (!lastCodecInfo.getName().equals("OMX.SEC.avc.enc")) { - return lastCodecInfo; - } else if (lastCodecInfo.getName().equals("OMX.SEC.AVC.Encoder")) { - return lastCodecInfo; - } - } + private static void close(Cursor closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception e) { + e.printStackTrace(); } } - return lastCodecInfo; } public boolean canSaveToGallery() { @@ -760,10 +692,10 @@ public boolean canSaveToGallery() { public void checkSaveToGalleryFiles() { try { - File telegramPath = new File(Environment.getExternalStorageDirectory(), "Telegram"); - File imagePath = new File(telegramPath, "Telegram Images"); + File telegramPath = new File(Environment.getExternalStorageDirectory(), "toon"); + File imagePath = new File(telegramPath, "toon Images"); imagePath.mkdir(); - File videoPath = new File(telegramPath, "Telegram Video"); + File videoPath = new File(telegramPath, "toon Video"); videoPath.mkdir(); if (saveToGallery) { diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/NotificationCenter.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/NotificationCenter.java similarity index 99% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/NotificationCenter.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/NotificationCenter.java index de5de4b..4948998 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/NotificationCenter.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/NotificationCenter.java @@ -1,4 +1,4 @@ -package com.tangxiaolv.telegramgallery.Utils; +package com.tangxiaolv.telegramgallery.utils; import android.os.Looper; import android.util.SparseArray; diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/Utilities.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Utilities.java similarity index 93% rename from telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/Utilities.java rename to telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Utilities.java index 6296abb..5f7ada9 100644 --- a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/Utils/Utilities.java +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/Utilities.java @@ -1,41 +1,26 @@ -package com.tangxiaolv.telegramgallery.Utils; +package com.tangxiaolv.telegramgallery.utils; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import com.tangxiaolv.telegramgallery.DispatchQueue; -import java.io.File; -import java.io.FileInputStream; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.MessageDigest; -import java.security.SecureRandom; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Utilities { public static Pattern pattern = Pattern.compile("[\\-0-9]+"); - public static SecureRandom random = new SecureRandom(); public static volatile DispatchQueue stageQueue = new DispatchQueue("stageQueue"); + public static volatile DispatchQueue globalQueue = new DispatchQueue("globalQueue"); final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); - static { - try { - File URANDOM_FILE = new File("/dev/urandom"); - FileInputStream sUrandomIn = new FileInputStream(URANDOM_FILE); - byte[] buffer = new byte[1024]; - sUrandomIn.read(buffer); - sUrandomIn.close(); - random.setSeed(buffer); - } catch (Exception e) { - e.printStackTrace(); - } - } - public static void pinBitmap(Bitmap bitmap) { } @@ -252,4 +237,12 @@ public static String MD5(String md5) { } return null; } + + public static void loadWebpImage(Object o, ByteBuffer buffer, int limit, BitmapFactory.Options bmOptions, boolean b) { + //TODO C++ IMP + } + + public static void blurBitmap(Bitmap image, int i, int i1, int width, int height, int rowBytes) { + //TODO C++ IMP + } } diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/VideoUtils.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/VideoUtils.java new file mode 100644 index 0000000..ed4c088 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/utils/VideoUtils.java @@ -0,0 +1,710 @@ +package com.tangxiaolv.telegramgallery.utils; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.os.Build; + +import com.tangxiaolv.telegramgallery.entity.VideoEditedInfo; +import com.tangxiaolv.telegramgallery.video.InputSurface; +import com.tangxiaolv.telegramgallery.video.MP4Builder; +import com.tangxiaolv.telegramgallery.video.Mp4Movie; +import com.tangxiaolv.telegramgallery.video.OutputSurface; + +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +public class VideoUtils { + + public final static String MIME_TYPE = "video/avc"; + + private final static int PROCESSOR_TYPE_OTHER = 0; + private final static int PROCESSOR_TYPE_QCOM = 1; + private final static int PROCESSOR_TYPE_INTEL = 2; + private final static int PROCESSOR_TYPE_MTK = 3; + private final static int PROCESSOR_TYPE_SEC = 4; + private final static int PROCESSOR_TYPE_TI = 5; + + private ArrayList videoConvertQueue = new ArrayList<>(); + + private final Object videoConvertSync = new Object(); + private boolean videoConvertFirstWrite; + private boolean cancelCurrentVideoConversion = false; + + private static volatile VideoUtils Instance = null; + + public static VideoUtils getInstance() { + VideoUtils localInstance = Instance; + if (localInstance == null) { + synchronized (VideoUtils.class) { + localInstance = Instance; + if (localInstance == null) { + Instance = localInstance = new VideoUtils(); + } + } + } + return localInstance; + } + + static { + System.loadLibrary("gly"); + } + + public native static int convertVideoFrame(ByteBuffer src, ByteBuffer dest, int destFormat, int width, int height, int padding, int swap); + public static native int createDecoder(String src, int[] params); + public static native void destroyDecoder(int ptr); + public static native int getVideoFrame(int ptr, Bitmap bitmap, int[] params); + + @TargetApi(16) + public String convertVideo(final VideoEditedInfo videoEditedInfo, String outDir) { + String videoPath = videoEditedInfo.originalPath; + long startTime = videoEditedInfo.startTime; + long endTime = videoEditedInfo.endTime; + int resultWidth = videoEditedInfo.resultWidth; + int resultHeight = videoEditedInfo.resultHeight; + int rotationValue = videoEditedInfo.rotationValue; + int originalWidth = videoEditedInfo.originalWidth; + int originalHeight = videoEditedInfo.originalHeight; + int bitrate = videoEditedInfo.bitrate; + int rotateRender = 0; + + String tempPath = outDir + + "/" + + videoPath.hashCode() + + System.currentTimeMillis() + + ".mp4"; + File cacheFile = new File(tempPath); + + if (Build.VERSION.SDK_INT < 18 && resultHeight > resultWidth && resultWidth != originalWidth && resultHeight != originalHeight) { + int temp = resultHeight; + resultHeight = resultWidth; + resultWidth = temp; + rotationValue = 90; + rotateRender = 270; + } else if (Build.VERSION.SDK_INT > 20) { + if (rotationValue == 90) { + int temp = resultHeight; + resultHeight = resultWidth; + resultWidth = temp; + rotationValue = 0; + rotateRender = 270; + } else if (rotationValue == 180) { + rotateRender = 180; + rotationValue = 0; + } else if (rotationValue == 270) { + int temp = resultHeight; + resultHeight = resultWidth; + resultWidth = temp; + rotationValue = 0; + rotateRender = 90; + } + } + + videoConvertFirstWrite = true; + boolean error = false; + long videoStartTime = startTime; + + long time = System.currentTimeMillis(); + + if (resultWidth != 0 && resultHeight != 0) { + MP4Builder mediaMuxer = null; + MediaExtractor extractor = null; + + try { + MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); + Mp4Movie movie = new Mp4Movie(); + movie.setCacheFile(cacheFile); + movie.setRotation(rotationValue); + movie.setSize(resultWidth, resultHeight); + mediaMuxer = new MP4Builder().createMovie(movie); + extractor = new MediaExtractor(); + extractor.setDataSource(videoPath); + + checkConversionCanceled(); + + if (resultWidth != originalWidth || resultHeight != originalHeight || rotateRender != 0 || videoEditedInfo.roundVideo) { + int videoIndex; + videoIndex = selectTrack(extractor, false); + if (videoIndex >= 0) { + MediaCodec decoder = null; + MediaCodec encoder = null; + InputSurface inputSurface = null; + OutputSurface outputSurface = null; + + try { + long videoTime = -1; + boolean outputDone = false; + boolean inputDone = false; + boolean decoderDone = false; + int swapUV = 0; + int videoTrackIndex = -5; + + int colorFormat; + int processorType = PROCESSOR_TYPE_OTHER; + String manufacturer = Build.MANUFACTURER.toLowerCase(); + if (Build.VERSION.SDK_INT < 18) { + MediaCodecInfo codecInfo = selectCodec(MIME_TYPE); + colorFormat = selectColorFormat(codecInfo, MIME_TYPE); + if (colorFormat == 0) { + throw new RuntimeException("no supported color format"); + } + String codecName = codecInfo.getName(); + if (codecName.contains("OMX.qcom.")) { + processorType = PROCESSOR_TYPE_QCOM; + if (Build.VERSION.SDK_INT == 16) { + if (manufacturer.equals("lge") || manufacturer.equals("nokia")) { + swapUV = 1; + } + } + } else if (codecName.contains("OMX.Intel.")) { + processorType = PROCESSOR_TYPE_INTEL; + } else if (codecName.equals("OMX.MTK.VIDEO.ENCODER.AVC")) { + processorType = PROCESSOR_TYPE_MTK; + } else if (codecName.equals("OMX.SEC.AVC.Encoder")) { + processorType = PROCESSOR_TYPE_SEC; + swapUV = 1; + } else if (codecName.equals("OMX.TI.DUCATI1.VIDEO.H264E")) { + processorType = PROCESSOR_TYPE_TI; + } + FileLog.e("codec = " + codecInfo.getName() + " manufacturer = " + manufacturer + "device = " + Build.MODEL); + } else { + colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; + } + FileLog.e("colorFormat = " + colorFormat); + + int resultHeightAligned = resultHeight; + int padding = 0; + int bufferSize = resultWidth * resultHeight * 3 / 2; + if (processorType == PROCESSOR_TYPE_OTHER) { + if (resultHeight % 16 != 0) { + resultHeightAligned += (16 - (resultHeight % 16)); + padding = resultWidth * (resultHeightAligned - resultHeight); + bufferSize += padding * 5 / 4; + } + } else if (processorType == PROCESSOR_TYPE_QCOM) { + if (!manufacturer.equalsIgnoreCase("lge")) { + int uvoffset = (resultWidth * resultHeight + 2047) & ~2047; + padding = uvoffset - (resultWidth * resultHeight); + bufferSize += padding; + } + } else if (processorType == PROCESSOR_TYPE_TI) { + //resultHeightAligned = 368; + //bufferSize = resultWidth * resultHeightAligned * 3 / 2; + //resultHeightAligned += (16 - (resultHeight % 16)); + //padding = resultWidth * (resultHeightAligned - resultHeight); + //bufferSize += padding * 5 / 4; + } else if (processorType == PROCESSOR_TYPE_MTK) { + if (manufacturer.equals("baidu")) { + resultHeightAligned += (16 - (resultHeight % 16)); + padding = resultWidth * (resultHeightAligned - resultHeight); + bufferSize += padding * 5 / 4; + } + } + + extractor.selectTrack(videoIndex); + if (startTime > 0) { + extractor.seekTo(startTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } else { + extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } + MediaFormat inputFormat = extractor.getTrackFormat(videoIndex); + + MediaFormat outputFormat = MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight); + outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); + outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate > 0 ? bitrate : 921600); + outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25); + outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); + if (Build.VERSION.SDK_INT < 18) { + outputFormat.setInteger("stride", resultWidth + 32); + outputFormat.setInteger("slice-height", resultHeight); + } + + encoder = MediaCodec.createEncoderByType(MIME_TYPE); + encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + if (Build.VERSION.SDK_INT >= 18) { + inputSurface = new InputSurface(encoder.createInputSurface()); + inputSurface.makeCurrent(); + } + encoder.start(); + + decoder = MediaCodec.createDecoderByType(inputFormat.getString(MediaFormat.KEY_MIME)); + if (Build.VERSION.SDK_INT >= 18) { + outputSurface = new OutputSurface(); + } else { + outputSurface = new OutputSurface(resultWidth, resultHeight, rotateRender); + } + decoder.configure(inputFormat, outputSurface.getSurface(), null, 0); + decoder.start(); + + final int TIMEOUT_USEC = 2500; + ByteBuffer[] decoderInputBuffers = null; + ByteBuffer[] encoderOutputBuffers = null; + ByteBuffer[] encoderInputBuffers = null; + if (Build.VERSION.SDK_INT < 21) { + decoderInputBuffers = decoder.getInputBuffers(); + encoderOutputBuffers = encoder.getOutputBuffers(); + if (Build.VERSION.SDK_INT < 18) { + encoderInputBuffers = encoder.getInputBuffers(); + } + } + + checkConversionCanceled(); + + while (!outputDone) { + checkConversionCanceled(); + if (!inputDone) { + boolean eof = false; + int index = extractor.getSampleTrackIndex(); + if (index == videoIndex) { + int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC); + if (inputBufIndex >= 0) { + ByteBuffer inputBuf; + if (Build.VERSION.SDK_INT < 21) { + inputBuf = decoderInputBuffers[inputBufIndex]; + } else { + inputBuf = decoder.getInputBuffer(inputBufIndex); + } + int chunkSize = extractor.readSampleData(inputBuf, 0); + if (chunkSize < 0) { + decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + inputDone = true; + } else { + decoder.queueInputBuffer(inputBufIndex, 0, chunkSize, extractor.getSampleTime(), 0); + extractor.advance(); + } + } + } else if (index == -1) { + eof = true; + } + if (eof) { + int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC); + if (inputBufIndex >= 0) { + decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + inputDone = true; + } + } + } + + boolean decoderOutputAvailable = !decoderDone; + boolean encoderOutputAvailable = true; + while (decoderOutputAvailable || encoderOutputAvailable) { + checkConversionCanceled(); + int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC); + if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { + encoderOutputAvailable = false; + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + if (Build.VERSION.SDK_INT < 21) { + encoderOutputBuffers = encoder.getOutputBuffers(); + } + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + MediaFormat newFormat = encoder.getOutputFormat(); + if (videoTrackIndex == -5) { + videoTrackIndex = mediaMuxer.addTrack(newFormat, false); + } + } else if (encoderStatus < 0) { + throw new RuntimeException("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); + } else { + ByteBuffer encodedData; + if (Build.VERSION.SDK_INT < 21) { + encodedData = encoderOutputBuffers[encoderStatus]; + } else { + encodedData = encoder.getOutputBuffer(encoderStatus); + } + if (encodedData == null) { + throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); + } + if (info.size > 1) { + if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { + if (mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info, true)) { + didWriteData(videoEditedInfo, cacheFile, false, false); + } + } else if (videoTrackIndex == -5) { + byte[] csd = new byte[info.size]; + encodedData.limit(info.offset + info.size); + encodedData.position(info.offset); + encodedData.get(csd); + ByteBuffer sps = null; + ByteBuffer pps = null; + for (int a = info.size - 1; a >= 0; a--) { + if (a > 3) { + if (csd[a] == 1 && csd[a - 1] == 0 && csd[a - 2] == 0 && csd[a - 3] == 0) { + sps = ByteBuffer.allocate(a - 3); + pps = ByteBuffer.allocate(info.size - (a - 3)); + sps.put(csd, 0, a - 3).position(0); + pps.put(csd, a - 3, info.size - (a - 3)).position(0); + break; + } + } else { + break; + } + } + + MediaFormat newFormat = MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight); + if (sps != null && pps != null) { + newFormat.setByteBuffer("csd-0", sps); + newFormat.setByteBuffer("csd-1", pps); + } + videoTrackIndex = mediaMuxer.addTrack(newFormat, false); + } + } + outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; + encoder.releaseOutputBuffer(encoderStatus, false); + } + if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) { + continue; + } + + if (!decoderDone) { + int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC); + if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { + decoderOutputAvailable = false; + } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + + } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + MediaFormat newFormat = decoder.getOutputFormat(); + FileLog.e("newFormat = " + newFormat); + } else if (decoderStatus < 0) { + throw new RuntimeException("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus); + } else { + boolean doRender; + if (Build.VERSION.SDK_INT >= 18) { + doRender = info.size != 0; + } else { + doRender = info.size != 0 || info.presentationTimeUs != 0; + } + if (endTime > 0 && info.presentationTimeUs >= endTime) { + inputDone = true; + decoderDone = true; + doRender = false; + info.flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; + } + if (startTime > 0 && videoTime == -1) { + if (info.presentationTimeUs < startTime) { + doRender = false; + FileLog.e("drop frame startTime = " + startTime + " present time = " + info.presentationTimeUs); + } else { + videoTime = info.presentationTimeUs; + } + } + decoder.releaseOutputBuffer(decoderStatus, doRender); + if (doRender) { + boolean errorWait = false; + try { + outputSurface.awaitNewImage(); + } catch (Exception e) { + errorWait = true; + e.printStackTrace(); + } + if (!errorWait) { + if (Build.VERSION.SDK_INT >= 18) { + outputSurface.drawImage(false); + inputSurface.setPresentationTime(info.presentationTimeUs * 1000); + inputSurface.swapBuffers(); + } else { + int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); + if (inputBufIndex >= 0) { + outputSurface.drawImage(true); + ByteBuffer rgbBuf = outputSurface.getFrame(); + ByteBuffer yuvBuf = encoderInputBuffers[inputBufIndex]; + yuvBuf.clear(); + convertVideoFrame(rgbBuf, yuvBuf, colorFormat, resultWidth, resultHeight, padding, swapUV); + encoder.queueInputBuffer(inputBufIndex, 0, bufferSize, info.presentationTimeUs, 0); + } else { + FileLog.e("input buffer not available"); + } + } + } + } + if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + decoderOutputAvailable = false; + FileLog.e("decoder stream end"); + if (Build.VERSION.SDK_INT >= 18) { + encoder.signalEndOfInputStream(); + } else { + int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); + if (inputBufIndex >= 0) { + encoder.queueInputBuffer(inputBufIndex, 0, 1, info.presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + } + } + } + } + } + } + } + if (videoTime != -1) { + videoStartTime = videoTime; + } + } catch (Exception e) { + FileLog.e(e); + error = true; + } + + extractor.unselectTrack(videoIndex); + + if (outputSurface != null) { + outputSurface.release(); + } + if (inputSurface != null) { + inputSurface.release(); + } + if (decoder != null) { + decoder.stop(); + decoder.release(); + } + if (encoder != null) { + encoder.stop(); + encoder.release(); + } + + checkConversionCanceled(); + } + } else { + long videoTime = readAndWriteTrack(videoEditedInfo, extractor, mediaMuxer, info, startTime, endTime, cacheFile, false); + if (videoTime != -1) { + videoStartTime = videoTime; + } + } + if (!error && bitrate != -1) { + readAndWriteTrack(videoEditedInfo, extractor, mediaMuxer, info, videoStartTime, endTime, cacheFile, true); + } + } catch (Exception e) { + error = true; + FileLog.e(e); + } finally { + if (extractor != null) { + extractor.release(); + } + if (mediaMuxer != null) { + try { + mediaMuxer.finishMovie(); + } catch (Exception e) { + FileLog.e(e); + } + } + FileLog.e("time = " + (System.currentTimeMillis() - time)); + } + } else { + didWriteData(videoEditedInfo, cacheFile, true, true); + return tempPath; + } + didWriteData(videoEditedInfo, cacheFile, true, error); + return tempPath; + } + + private void checkConversionCanceled() throws Exception { + boolean cancelConversion; + synchronized (videoConvertSync) { + cancelConversion = cancelCurrentVideoConversion; + } + if (cancelConversion) { + throw new RuntimeException("canceled conversion"); + } + } + + @SuppressLint("NewApi") + public static MediaCodecInfo selectCodec(String mimeType) { + int numCodecs = MediaCodecList.getCodecCount(); + MediaCodecInfo lastCodecInfo = null; + for (int i = 0; i < numCodecs; i++) { + MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { + continue; + } + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.equalsIgnoreCase(mimeType)) { + lastCodecInfo = codecInfo; + if (!lastCodecInfo.getName().equals("OMX.SEC.avc.enc")) { + return lastCodecInfo; + } else if (lastCodecInfo.getName().equals("OMX.SEC.AVC.Encoder")) { + return lastCodecInfo; + } + } + } + } + return lastCodecInfo; + } + + private void didWriteData(final VideoEditedInfo videoEditedInfo, final File file, final boolean last, final boolean error) { + final boolean firstWrite = videoConvertFirstWrite; + if (firstWrite) { + videoConvertFirstWrite = false; + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (error || last) { + synchronized (videoConvertSync) { + cancelCurrentVideoConversion = false; + } + videoConvertQueue.remove(videoEditedInfo); + startVideoConvertFromQueue(); + } + /*if (error) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FilePreparingFailed, videoEditedInfo, file.toString()); + } else { + if (firstWrite) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FilePreparingStarted, videoEditedInfo, file.toString()); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileNewChunkAvailable, videoEditedInfo, file.toString(), last ? file.length() : 0); + }*/ + } + }); + } + + private boolean startVideoConvertFromQueue() { + if (!videoConvertQueue.isEmpty()) { + synchronized (videoConvertSync) { + cancelCurrentVideoConversion = false; + } + return true; + } + return false; + } + + @TargetApi(16) + private long readAndWriteTrack(final VideoEditedInfo messageObject, MediaExtractor extractor, MP4Builder mediaMuxer, MediaCodec.BufferInfo info, long start, long end, File file, boolean isAudio) throws Exception { + int trackIndex = selectTrack(extractor, isAudio); + if (trackIndex >= 0) { + extractor.selectTrack(trackIndex); + MediaFormat trackFormat = extractor.getTrackFormat(trackIndex); + int muxerTrackIndex = mediaMuxer.addTrack(trackFormat, isAudio); + int maxBufferSize = trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); + boolean inputDone = false; + if (start > 0) { + extractor.seekTo(start, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } else { + extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } + ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize); + long startTime = -1; + + checkConversionCanceled(); + + while (!inputDone) { + checkConversionCanceled(); + + boolean eof = false; + int index = extractor.getSampleTrackIndex(); + if (index == trackIndex) { + info.size = extractor.readSampleData(buffer, 0); + if (Build.VERSION.SDK_INT < 21) { + buffer.position(0); + buffer.limit(info.size); + } + if (!isAudio) { + byte[] array = buffer.array(); + if (array != null) { + int offset = buffer.arrayOffset(); + int len = offset + buffer.limit(); + int writeStart = -1; + for (int a = offset; a <= len - 4; a++) { + if (array[a] == 0 && array[a + 1] == 0 && array[a + 2] == 0 && array[a + 3] == 1 || a == len - 4) { + if (writeStart != -1) { + int l = a - writeStart - (a != len - 4 ? 4 : 0); + array[writeStart] = (byte) (l >> 24); + array[writeStart + 1] = (byte) (l >> 16); + array[writeStart + 2] = (byte) (l >> 8); + array[writeStart + 3] = (byte) l; + writeStart = a; + } else { + writeStart = a; + } + } + } + } + } + if (info.size >= 0) { + info.presentationTimeUs = extractor.getSampleTime(); + } else { + info.size = 0; + eof = true; + } + + if (info.size > 0 && !eof) { + if (start > 0 && startTime == -1) { + startTime = info.presentationTimeUs; + } + if (end < 0 || info.presentationTimeUs < end) { + info.offset = 0; + info.flags = extractor.getSampleFlags(); + if (mediaMuxer.writeSampleData(muxerTrackIndex, buffer, info, false)) { + didWriteData(messageObject, file, false, false); + } + } else { + eof = true; + } + } + if (!eof) { + extractor.advance(); + } + } else if (index == -1) { + eof = true; + } else { + extractor.advance(); + } + if (eof) { + inputDone = true; + } + } + + extractor.unselectTrack(trackIndex); + return startTime; + } + return -1; + } + + @TargetApi(16) + private int selectTrack(MediaExtractor extractor, boolean audio) { + int numTracks = extractor.getTrackCount(); + for (int i = 0; i < numTracks; i++) { + MediaFormat format = extractor.getTrackFormat(i); + String mime = format.getString(MediaFormat.KEY_MIME); + if (audio) { + if (mime.startsWith("audio/")) { + return i; + } + } else { + if (mime.startsWith("video/")) { + return i; + } + } + } + return -5; + } + + @SuppressLint("NewApi") + public static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) { + MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType); + int lastColorFormat = 0; + for (int i = 0; i < capabilities.colorFormats.length; i++) { + int colorFormat = capabilities.colorFormats[i]; + if (isRecognizedFormat(colorFormat)) { + lastColorFormat = colorFormat; + if (!(codecInfo.getName().equals("OMX.SEC.AVC.Encoder") && colorFormat == 19)) { + return colorFormat; + } + } + } + return lastColorFormat; + } + + private static boolean isRecognizedFormat(int colorFormat) { + switch (colorFormat) { + case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar: + case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar: + case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: + case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar: + case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar: + return true; + default: + return false; + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/InputSurface.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/InputSurface.java new file mode 100644 index 0000000..fa38be4 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/InputSurface.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tangxiaolv.telegramgallery.video; + +import android.annotation.TargetApi; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLExt; +import android.opengl.EGLSurface; +import android.view.Surface; + +@TargetApi(17) +public class InputSurface { + + private static final int EGL_RECORDABLE_ANDROID = 0x3142; + private static final int EGL_OPENGL_ES2_BIT = 4; + private EGLDisplay mEGLDisplay; + private EGLContext mEGLContext; + private EGLSurface mEGLSurface; + private Surface mSurface; + + public InputSurface(Surface surface) { + if (surface == null) { + throw new NullPointerException(); + } + mSurface = surface; + eglSetup(); + } + + private void eglSetup() { + mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL14 display"); + } + int[] version = new int[2]; + if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { + mEGLDisplay = null; + throw new RuntimeException("unable to initialize EGL14"); + } + + int[] attribList = { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RECORDABLE_ANDROID, 1, + EGL14.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, + numConfigs, 0)) { + throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); + } + + int[] attrib_list = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE + }; + + mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, attrib_list, 0); + checkEglError("eglCreateContext"); + if (mEGLContext == null) { + throw new RuntimeException("null context"); + } + + int[] surfaceAttribs = { + EGL14.EGL_NONE + }; + mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface, + surfaceAttribs, 0); + checkEglError("eglCreateWindowSurface"); + if (mEGLSurface == null) { + throw new RuntimeException("surface was null"); + } + } + + public void release() { + if (EGL14.eglGetCurrentContext().equals(mEGLContext)) { + EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + } + EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); + EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); + mSurface.release(); + mEGLDisplay = null; + mEGLContext = null; + mEGLSurface = null; + mSurface = null; + } + + public void makeCurrent() { + if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + + public boolean swapBuffers() { + return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); + } + + public Surface getSurface() { + return mSurface; + } + + public void setPresentationTime(long nsecs) { + EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); + } + + private void checkEglError(String msg) { + boolean failed = false; + while (EGL14.eglGetError() != EGL14.EGL_SUCCESS) { + failed = true; + } + if (failed) { + throw new RuntimeException("EGL error encountered (see log)"); + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/MP4Builder.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/MP4Builder.java new file mode 100644 index 0000000..681ac0e --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/MP4Builder.java @@ -0,0 +1,474 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package com.tangxiaolv.telegramgallery.video; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaFormat; + +import com.coremedia.iso.BoxParser; +import com.coremedia.iso.IsoFile; +import com.coremedia.iso.IsoTypeWriter; +import com.coremedia.iso.boxes.Box; +import com.coremedia.iso.boxes.CompositionTimeToSample; +import com.coremedia.iso.boxes.Container; +import com.coremedia.iso.boxes.DataEntryUrlBox; +import com.coremedia.iso.boxes.DataInformationBox; +import com.coremedia.iso.boxes.DataReferenceBox; +import com.coremedia.iso.boxes.FileTypeBox; +import com.coremedia.iso.boxes.HandlerBox; +import com.coremedia.iso.boxes.MediaBox; +import com.coremedia.iso.boxes.MediaHeaderBox; +import com.coremedia.iso.boxes.MediaInformationBox; +import com.coremedia.iso.boxes.MovieBox; +import com.coremedia.iso.boxes.MovieHeaderBox; +import com.coremedia.iso.boxes.SampleSizeBox; +import com.coremedia.iso.boxes.SampleTableBox; +import com.coremedia.iso.boxes.SampleToChunkBox; +import com.coremedia.iso.boxes.StaticChunkOffsetBox; +import com.coremedia.iso.boxes.SyncSampleBox; +import com.coremedia.iso.boxes.TimeToSampleBox; +import com.coremedia.iso.boxes.TrackBox; +import com.coremedia.iso.boxes.TrackHeaderBox; +import com.googlecode.mp4parser.DataSource; +import com.googlecode.mp4parser.util.Matrix; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +@TargetApi(16) +public class MP4Builder { + + private InterleaveChunkMdat mdat = null; + private Mp4Movie currentMp4Movie = null; + private FileOutputStream fos = null; + private FileChannel fc = null; + private long dataOffset = 0; + private long writedSinceLastMdat = 0; + private boolean writeNewMdat = true; + private HashMap track2SampleSizes = new HashMap<>(); + private ByteBuffer sizeBuffer = null; + + public MP4Builder createMovie(Mp4Movie mp4Movie) throws Exception { + currentMp4Movie = mp4Movie; + + fos = new FileOutputStream(mp4Movie.getCacheFile()); + fc = fos.getChannel(); + + FileTypeBox fileTypeBox = createFileTypeBox(); + fileTypeBox.getBox(fc); + dataOffset += fileTypeBox.getSize(); + writedSinceLastMdat += dataOffset; + + mdat = new InterleaveChunkMdat(); + + sizeBuffer = ByteBuffer.allocateDirect(4); + + return this; + } + + private void flushCurrentMdat() throws Exception { + long oldPosition = fc.position(); + fc.position(mdat.getOffset()); + mdat.getBox(fc); + fc.position(oldPosition); + mdat.setDataOffset(0); + mdat.setContentSize(0); + fos.flush(); + } + + public boolean writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo, boolean writeLength) throws Exception { + if (writeNewMdat) { + mdat.setContentSize(0); + mdat.getBox(fc); + mdat.setDataOffset(dataOffset); + dataOffset += 16; + writedSinceLastMdat += 16; + writeNewMdat = false; + } + + mdat.setContentSize(mdat.getContentSize() + bufferInfo.size); + writedSinceLastMdat += bufferInfo.size; + + boolean flush = false; + if (writedSinceLastMdat >= 32 * 1024) { + flushCurrentMdat(); + writeNewMdat = true; + flush = true; + writedSinceLastMdat -= 32 * 1024; + } + + currentMp4Movie.addSample(trackIndex, dataOffset, bufferInfo); + byteBuf.position(bufferInfo.offset + (!writeLength ? 0 : 4)); + byteBuf.limit(bufferInfo.offset + bufferInfo.size); + + if (writeLength) { + sizeBuffer.position(0); + sizeBuffer.putInt(bufferInfo.size - 4); + sizeBuffer.position(0); + fc.write(sizeBuffer); + } + + fc.write(byteBuf); + dataOffset += bufferInfo.size; + + if (flush) { + fos.flush(); + } + return flush; + } + + public int addTrack(MediaFormat mediaFormat, boolean isAudio) { + return currentMp4Movie.addTrack(mediaFormat, isAudio); + } + + public void finishMovie() throws Exception { + if (mdat.getContentSize() != 0) { + flushCurrentMdat(); + } + + for (Track track : currentMp4Movie.getTracks()) { + List samples = track.getSamples(); + long[] sizes = new long[samples.size()]; + for (int i = 0; i < sizes.length; i++) { + sizes[i] = samples.get(i).getSize(); + } + track2SampleSizes.put(track, sizes); + } + + Box moov = createMovieBox(currentMp4Movie); + moov.getBox(fc); + fos.flush(); + + fc.close(); + fos.close(); + } + + protected FileTypeBox createFileTypeBox() { + LinkedList minorBrands = new LinkedList<>(); + minorBrands.add("isom"); + minorBrands.add("iso2"); + minorBrands.add("avc1"); + minorBrands.add("mp41"); + return new FileTypeBox("isom", 512, minorBrands); + } + + private class InterleaveChunkMdat implements Box { + private Container parent; + private long contentSize = 1024 * 1024 * 1024; + private long dataOffset = 0; + + public Container getParent() { + return parent; + } + + public long getOffset() { + return dataOffset; + } + + public void setDataOffset(long offset) { + dataOffset = offset; + } + + public void setParent(Container parent) { + this.parent = parent; + } + + public void setContentSize(long contentSize) { + this.contentSize = contentSize; + } + + public long getContentSize() { + return contentSize; + } + + public String getType() { + return "mdat"; + } + + public long getSize() { + return 16 + contentSize; + } + + private boolean isSmallBox(long contentSize) { + return (contentSize + 8) < 4294967296L; + } + + @Override + public void parse(DataSource dataSource, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException { + + } + + public void getBox(WritableByteChannel writableByteChannel) throws IOException { + ByteBuffer bb = ByteBuffer.allocate(16); + long size = getSize(); + if (isSmallBox(size)) { + IsoTypeWriter.writeUInt32(bb, size); + } else { + IsoTypeWriter.writeUInt32(bb, 1); + } + bb.put(IsoFile.fourCCtoBytes("mdat")); + if (isSmallBox(size)) { + bb.put(new byte[8]); + } else { + IsoTypeWriter.writeUInt64(bb, size); + } + bb.rewind(); + writableByteChannel.write(bb); + } + } + + public static long gcd(long a, long b) { + if (b == 0) { + return a; + } + return gcd(b, a % b); + } + + public long getTimescale(Mp4Movie mp4Movie) { + long timescale = 0; + if (!mp4Movie.getTracks().isEmpty()) { + timescale = mp4Movie.getTracks().iterator().next().getTimeScale(); + } + for (Track track : mp4Movie.getTracks()) { + timescale = gcd(track.getTimeScale(), timescale); + } + return timescale; + } + + protected MovieBox createMovieBox(Mp4Movie movie) { + MovieBox movieBox = new MovieBox(); + MovieHeaderBox mvhd = new MovieHeaderBox(); + + mvhd.setCreationTime(new Date()); + mvhd.setModificationTime(new Date()); + mvhd.setMatrix(Matrix.ROTATE_0); + long movieTimeScale = getTimescale(movie); + long duration = 0; + + for (Track track : movie.getTracks()) { + track.prepare(); + long tracksDuration = track.getDuration() * movieTimeScale / track.getTimeScale(); + if (tracksDuration > duration) { + duration = tracksDuration; + } + } + + mvhd.setDuration(duration); + mvhd.setTimescale(movieTimeScale); + mvhd.setNextTrackId(movie.getTracks().size() + 1); + + movieBox.addBox(mvhd); + for (Track track : movie.getTracks()) { + movieBox.addBox(createTrackBox(track, movie)); + } + return movieBox; + } + + protected TrackBox createTrackBox(Track track, Mp4Movie movie) { + TrackBox trackBox = new TrackBox(); + TrackHeaderBox tkhd = new TrackHeaderBox(); + + tkhd.setEnabled(true); + tkhd.setInMovie(true); + tkhd.setInPreview(true); + if (track.isAudio()) { + tkhd.setMatrix(Matrix.ROTATE_0); + } else { + tkhd.setMatrix(movie.getMatrix()); + } + tkhd.setAlternateGroup(0); + tkhd.setCreationTime(track.getCreationTime()); + tkhd.setDuration(track.getDuration() * getTimescale(movie) / track.getTimeScale()); + tkhd.setHeight(track.getHeight()); + tkhd.setWidth(track.getWidth()); + tkhd.setLayer(0); + tkhd.setModificationTime(new Date()); + tkhd.setTrackId(track.getTrackId() + 1); + tkhd.setVolume(track.getVolume()); + + trackBox.addBox(tkhd); + + MediaBox mdia = new MediaBox(); + trackBox.addBox(mdia); + MediaHeaderBox mdhd = new MediaHeaderBox(); + mdhd.setCreationTime(track.getCreationTime()); + mdhd.setDuration(track.getDuration()); + mdhd.setTimescale(track.getTimeScale()); + mdhd.setLanguage("eng"); + mdia.addBox(mdhd); + HandlerBox hdlr = new HandlerBox(); + hdlr.setName(track.isAudio() ? "SoundHandle" : "VideoHandle"); + hdlr.setHandlerType(track.getHandler()); + + mdia.addBox(hdlr); + + MediaInformationBox minf = new MediaInformationBox(); + minf.addBox(track.getMediaHeaderBox()); + + DataInformationBox dinf = new DataInformationBox(); + DataReferenceBox dref = new DataReferenceBox(); + dinf.addBox(dref); + DataEntryUrlBox url = new DataEntryUrlBox(); + url.setFlags(1); + dref.addBox(url); + minf.addBox(dinf); + + Box stbl = createStbl(track); + minf.addBox(stbl); + mdia.addBox(minf); + + return trackBox; + } + + protected Box createStbl(Track track) { + SampleTableBox stbl = new SampleTableBox(); + + createStsd(track, stbl); + createStts(track, stbl); + createCtts(track, stbl); + createStss(track, stbl); + createStsc(track, stbl); + createStsz(track, stbl); + createStco(track, stbl); + + return stbl; + } + + protected void createStsd(Track track, SampleTableBox stbl) { + stbl.addBox(track.getSampleDescriptionBox()); + } + + protected void createCtts(Track track, SampleTableBox stbl) { + int[] sampleCompositions = track.getSampleCompositions(); + if (sampleCompositions == null) { + return; + } + CompositionTimeToSample.Entry lastEntry = null; + List entries = new ArrayList<>(); + + for (int a = 0; a < sampleCompositions.length; a++) { + int offset = sampleCompositions[a]; + if (lastEntry != null && lastEntry.getOffset() == offset) { + lastEntry.setCount(lastEntry.getCount() + 1); + } else { + lastEntry = new CompositionTimeToSample.Entry(1, offset); + entries.add(lastEntry); + } + } + CompositionTimeToSample ctts = new CompositionTimeToSample(); + ctts.setEntries(entries); + stbl.addBox(ctts); + } + + protected void createStts(Track track, SampleTableBox stbl) { + TimeToSampleBox.Entry lastEntry = null; + List entries = new ArrayList<>(); + long[] deltas = track.getSampleDurations(); + + for (int a = 0; a < deltas.length; a++) { + long delta = deltas[a]; + if (lastEntry != null && lastEntry.getDelta() == delta) { + lastEntry.setCount(lastEntry.getCount() + 1); + } else { + lastEntry = new TimeToSampleBox.Entry(1, delta); + entries.add(lastEntry); + } + } + TimeToSampleBox stts = new TimeToSampleBox(); + stts.setEntries(entries); + stbl.addBox(stts); + } + + protected void createStss(Track track, SampleTableBox stbl) { + long[] syncSamples = track.getSyncSamples(); + if (syncSamples != null && syncSamples.length > 0) { + SyncSampleBox stss = new SyncSampleBox(); + stss.setSampleNumber(syncSamples); + stbl.addBox(stss); + } + } + + protected void createStsc(Track track, SampleTableBox stbl) { + SampleToChunkBox stsc = new SampleToChunkBox(); + stsc.setEntries(new LinkedList()); + + long lastOffset; + int lastChunkNumber = 1; + int lastSampleCount = 0; + + int previousWritedChunkCount = -1; + + int samplesCount = track.getSamples().size(); + for (int a = 0; a < samplesCount; a++) { + Sample sample = track.getSamples().get(a); + long offset = sample.getOffset(); + long size = sample.getSize(); + + lastOffset = offset + size; + lastSampleCount++; + + boolean write = false; + if (a != samplesCount - 1) { + Sample nextSample = track.getSamples().get(a + 1); + if (lastOffset != nextSample.getOffset()) { + write = true; + } + } else { + write = true; + } + if (write) { + if (previousWritedChunkCount != lastSampleCount) { + stsc.getEntries().add(new SampleToChunkBox.Entry(lastChunkNumber, lastSampleCount, 1)); + previousWritedChunkCount = lastSampleCount; + } + lastSampleCount = 0; + lastChunkNumber++; + } + } + stbl.addBox(stsc); + } + + protected void createStsz(Track track, SampleTableBox stbl) { + SampleSizeBox stsz = new SampleSizeBox(); + stsz.setSampleSizes(track2SampleSizes.get(track)); + stbl.addBox(stsz); + } + + protected void createStco(Track track, SampleTableBox stbl) { + ArrayList chunksOffsets = new ArrayList<>(); + long lastOffset = -1; + for (Sample sample : track.getSamples()) { + long offset = sample.getOffset(); + if (lastOffset != -1 && lastOffset != offset) { + lastOffset = -1; + } + if (lastOffset == -1) { + chunksOffsets.add(offset); + } + lastOffset = offset + sample.getSize(); + } + long[] chunkOffsetsLong = new long[chunksOffsets.size()]; + for (int a = 0; a < chunksOffsets.size(); a++) { + chunkOffsetsLong[a] = chunksOffsets.get(a); + } + + StaticChunkOffsetBox stco = new StaticChunkOffsetBox(); + stco.setChunkOffsets(chunkOffsetsLong); + stbl.addBox(stco); + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Mp4Movie.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Mp4Movie.java new file mode 100644 index 0000000..bd7cbbd --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Mp4Movie.java @@ -0,0 +1,81 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package com.tangxiaolv.telegramgallery.video; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaFormat; + +import com.googlecode.mp4parser.util.Matrix; + +import java.io.File; +import java.util.ArrayList; + +@TargetApi(16) +public class Mp4Movie { + private Matrix matrix = Matrix.ROTATE_0; + private ArrayList tracks = new ArrayList<>(); + private File cacheFile; + private int width; + private int height; + + public Matrix getMatrix() { + return matrix; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public void setCacheFile(File file) { + cacheFile = file; + } + + public void setRotation(int angle) { + if (angle == 0) { + matrix = Matrix.ROTATE_0; + } else if (angle == 90) { + matrix = Matrix.ROTATE_90; + } else if (angle == 180) { + matrix = Matrix.ROTATE_180; + } else if (angle == 270) { + matrix = Matrix.ROTATE_270; + } + } + + public void setSize(int w, int h) { + width = w; + height = h; + } + + public ArrayList getTracks() { + return tracks; + } + + public File getCacheFile() { + return cacheFile; + } + + public void addSample(int trackIndex, long offset, MediaCodec.BufferInfo bufferInfo) { + if (trackIndex < 0 || trackIndex >= tracks.size()) { + return; + } + Track track = tracks.get(trackIndex); + track.addSample(offset, bufferInfo); + } + + public int addTrack(MediaFormat mediaFormat, boolean isAudio) { + tracks.add(new Track(tracks.size(), mediaFormat, isAudio)); + return tracks.size() - 1; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/OutputSurface.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/OutputSurface.java new file mode 100644 index 0000000..f1c2324 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/OutputSurface.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tangxiaolv.telegramgallery.video; + +import android.annotation.TargetApi; +import android.graphics.SurfaceTexture; +import android.opengl.GLES20; +import android.view.Surface; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +@TargetApi(16) +public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { + + private static final int EGL_OPENGL_ES2_BIT = 4; + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private EGL10 mEGL; + private EGLDisplay mEGLDisplay = null; + private EGLContext mEGLContext = null; + private EGLSurface mEGLSurface = null; + private SurfaceTexture mSurfaceTexture; + private Surface mSurface; + private final Object mFrameSyncObject = new Object(); + private boolean mFrameAvailable; + private TextureRenderer mTextureRender; + private int mWidth; + private int mHeight; + private int rotateRender = 0; + private ByteBuffer mPixelBuf; + + public OutputSurface(int width, int height, int rotate) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException(); + } + mWidth = width; + mHeight = height; + rotateRender = rotate; + mPixelBuf = ByteBuffer.allocateDirect(mWidth * mHeight * 4); + mPixelBuf.order(ByteOrder.LITTLE_ENDIAN); + eglSetup(width, height); + makeCurrent(); + setup(); + } + + public OutputSurface() { + setup(); + } + + private void setup() { + mTextureRender = new TextureRenderer(rotateRender); + mTextureRender.surfaceCreated(); + mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); + mSurfaceTexture.setOnFrameAvailableListener(this); + mSurface = new Surface(mSurfaceTexture); + } + + private void eglSetup(int width, int height) { + mEGL = (EGL10) EGLContext.getEGL(); + mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL10 display"); + } + + if (!mEGL.eglInitialize(mEGLDisplay, null)) { + mEGLDisplay = null; + throw new RuntimeException("unable to initialize EGL10"); + } + + int[] attribList = { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, + EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, configs.length, numConfigs)) { + throw new RuntimeException("unable to find RGB888+pbuffer EGL config"); + } + int[] attrib_list = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL10.EGL_NONE + }; + mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list); + checkEglError("eglCreateContext"); + if (mEGLContext == null) { + throw new RuntimeException("null context"); + } + int[] surfaceAttribs = { + EGL10.EGL_WIDTH, width, + EGL10.EGL_HEIGHT, height, + EGL10.EGL_NONE + }; + mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs); + checkEglError("eglCreatePbufferSurface"); + if (mEGLSurface == null) { + throw new RuntimeException("surface was null"); + } + } + + public void release() { + if (mEGL != null) { + if (mEGL.eglGetCurrentContext().equals(mEGLContext)) { + mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + } + mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface); + mEGL.eglDestroyContext(mEGLDisplay, mEGLContext); + } + mSurface.release(); + mEGLDisplay = null; + mEGLContext = null; + mEGLSurface = null; + mEGL = null; + mTextureRender = null; + mSurface = null; + mSurfaceTexture = null; + } + + public void makeCurrent() { + if (mEGL == null) { + throw new RuntimeException("not configured for makeCurrent"); + } + checkEglError("before makeCurrent"); + if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + + public Surface getSurface() { + return mSurface; + } + + public void awaitNewImage() { + final int TIMEOUT_MS = 2500; + synchronized (mFrameSyncObject) { + while (!mFrameAvailable) { + try { + mFrameSyncObject.wait(TIMEOUT_MS); + if (!mFrameAvailable) { + throw new RuntimeException("Surface frame wait timed out"); + } + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + } + mFrameAvailable = false; + } + mTextureRender.checkGlError("before updateTexImage"); + mSurfaceTexture.updateTexImage(); + } + + public void drawImage(boolean invert) { + mTextureRender.drawFrame(mSurfaceTexture, invert); + } + + @Override + public void onFrameAvailable(SurfaceTexture st) { + synchronized (mFrameSyncObject) { + if (mFrameAvailable) { + throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); + } + mFrameAvailable = true; + mFrameSyncObject.notifyAll(); + } + } + + public ByteBuffer getFrame() { + mPixelBuf.rewind(); + GLES20.glReadPixels(0, 0, mWidth, mHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf); + return mPixelBuf; + } + + private void checkEglError(String msg) { + if (mEGL.eglGetError() != EGL10.EGL_SUCCESS) { + throw new RuntimeException("EGL error encountered (see log)"); + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Sample.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Sample.java new file mode 100644 index 0000000..06078ca --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Sample.java @@ -0,0 +1,27 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package com.tangxiaolv.telegramgallery.video; + +public class Sample { + private long offset = 0; + private long size = 0; + + public Sample(long offset, long size) { + this.offset = offset; + this.size = size; + } + + public long getOffset() { + return offset; + } + + public long getSize() { + return size; + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/TextureRenderer.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/TextureRenderer.java new file mode 100644 index 0000000..e3a2a97 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/TextureRenderer.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tangxiaolv.telegramgallery.video; + +import android.annotation.TargetApi; +import android.graphics.SurfaceTexture; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.Matrix; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +@TargetApi(16) +public class TextureRenderer { + + private static final int FLOAT_SIZE_BYTES = 4; + private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; + private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; + private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; + private FloatBuffer mTriangleVertices; + + private static final String VERTEX_SHADER = + "uniform mat4 uMVPMatrix;\n" + + "uniform mat4 uSTMatrix;\n" + + "attribute vec4 aPosition;\n" + + "attribute vec4 aTextureCoord;\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + + "}\n"; + + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; + + private float[] mMVPMatrix = new float[16]; + private float[] mSTMatrix = new float[16]; + private int mTextureID = -12345; + private int mProgram; + private int muMVPMatrixHandle; + private int muSTMatrixHandle; + private int maPositionHandle; + private int maTextureHandle; + + private int rotationAngle; + + public TextureRenderer(int rotation) { + rotationAngle = rotation; + float[] mTriangleVerticesData = { + -1.0f, -1.0f, 0, 0.f, 0.f, + 1.0f, -1.0f, 0, 1.f, 0.f, + -1.0f, 1.0f, 0, 0.f, 1.f, + 1.0f, 1.0f, 0, 1.f, 1.f, + }; + mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer(); + mTriangleVertices.put(mTriangleVerticesData).position(0); + Matrix.setIdentityM(mSTMatrix, 0); + } + + public int getTextureId() { + return mTextureID; + } + + public void drawFrame(SurfaceTexture st, boolean invert) { + checkGlError("onDrawFrame start"); + st.getTransformMatrix(mSTMatrix); + + if (invert) { + mSTMatrix[5] = -mSTMatrix[5]; + mSTMatrix[13] = 1.0f - mSTMatrix[13]; + } + + GLES20.glUseProgram(mProgram); + checkGlError("glUseProgram"); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); + GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maPosition"); + GLES20.glEnableVertexAttribArray(maPositionHandle); + checkGlError("glEnableVertexAttribArray maPositionHandle"); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); + GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maTextureHandle"); + GLES20.glEnableVertexAttribArray(maTextureHandle); + checkGlError("glEnableVertexAttribArray maTextureHandle"); + + GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); + GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + checkGlError("glDrawArrays"); + GLES20.glFinish(); + } + + public void surfaceCreated() { + mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); + } + maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); + checkGlError("glGetAttribLocation aPosition"); + if (maPositionHandle == -1) { + throw new RuntimeException("Could not get attrib location for aPosition"); + } + maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); + checkGlError("glGetAttribLocation aTextureCoord"); + if (maTextureHandle == -1) { + throw new RuntimeException("Could not get attrib location for aTextureCoord"); + } + muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); + checkGlError("glGetUniformLocation uMVPMatrix"); + if (muMVPMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uMVPMatrix"); + } + muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); + checkGlError("glGetUniformLocation uSTMatrix"); + if (muSTMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uSTMatrix"); + } + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + mTextureID = textures[0]; + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + checkGlError("glBindTexture mTextureID"); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + checkGlError("glTexParameter"); + + Matrix.setIdentityM(mMVPMatrix, 0); + if (rotationAngle != 0) { + Matrix.rotateM(mMVPMatrix, 0, rotationAngle, 0, 0, 1); + } + } + + private int loadShader(int shaderType, String source) { + int shader = GLES20.glCreateShader(shaderType); + checkGlError("glCreateShader type=" + shaderType); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + + private int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + int program = GLES20.glCreateProgram(); + checkGlError("glCreateProgram"); + if (program == 0) { + return 0; + } + GLES20.glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + GLES20.glDeleteProgram(program); + program = 0; + } + return program; + } + + public void checkGlError(String op) { + int error; + if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + throw new RuntimeException(op + ": glError " + error); + } + } +} diff --git a/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Track.java b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Track.java new file mode 100644 index 0000000..f2ff7a7 --- /dev/null +++ b/telegramgallery/src/main/java/com/tangxiaolv/telegramgallery/video/Track.java @@ -0,0 +1,384 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package com.tangxiaolv.telegramgallery.video; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaFormat; + +import com.coremedia.iso.boxes.AbstractMediaHeaderBox; +import com.coremedia.iso.boxes.SampleDescriptionBox; +import com.coremedia.iso.boxes.SoundMediaHeaderBox; +import com.coremedia.iso.boxes.VideoMediaHeaderBox; +import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; +import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry; +import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.SLConfigDescriptor; +import com.mp4parser.iso14496.part15.AvcConfigurationBox; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +@TargetApi(16) +public class Track { + + private class SamplePresentationTime { + + private int index; + private long presentationTime; + private long dt; + + public SamplePresentationTime(int idx, long time) { + index = idx; + presentationTime = time; + } + } + + private long trackId = 0; + private ArrayList samples = new ArrayList<>(); + private long duration = 0; + private int[] sampleCompositions; + private String handler; + private AbstractMediaHeaderBox headerBox = null; + private SampleDescriptionBox sampleDescriptionBox = null; + private LinkedList syncSamples = null; + private int timeScale; + private Date creationTime = new Date(); + private int height; + private int width; + private float volume = 0; + private long[] sampleDurations; + private ArrayList samplePresentationTimes = new ArrayList<>(); + private boolean isAudio = false; + private static Map samplingFrequencyIndexMap = new HashMap<>(); + private boolean first = true; + + static { + samplingFrequencyIndexMap.put(96000, 0x0); + samplingFrequencyIndexMap.put(88200, 0x1); + samplingFrequencyIndexMap.put(64000, 0x2); + samplingFrequencyIndexMap.put(48000, 0x3); + samplingFrequencyIndexMap.put(44100, 0x4); + samplingFrequencyIndexMap.put(32000, 0x5); + samplingFrequencyIndexMap.put(24000, 0x6); + samplingFrequencyIndexMap.put(22050, 0x7); + samplingFrequencyIndexMap.put(16000, 0x8); + samplingFrequencyIndexMap.put(12000, 0x9); + samplingFrequencyIndexMap.put(11025, 0xa); + samplingFrequencyIndexMap.put(8000, 0xb); + } + + public Track(int id, MediaFormat format, boolean audio) { + trackId = id; + isAudio = audio; + if (!isAudio) { + width = format.getInteger(MediaFormat.KEY_WIDTH); + height = format.getInteger(MediaFormat.KEY_HEIGHT); + timeScale = 90000; + syncSamples = new LinkedList<>(); + handler = "vide"; + headerBox = new VideoMediaHeaderBox(); + sampleDescriptionBox = new SampleDescriptionBox(); + String mime = format.getString(MediaFormat.KEY_MIME); + if (mime.equals("video/avc")) { + VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1"); + visualSampleEntry.setDataReferenceIndex(1); + visualSampleEntry.setDepth(24); + visualSampleEntry.setFrameCount(1); + visualSampleEntry.setHorizresolution(72); + visualSampleEntry.setVertresolution(72); + visualSampleEntry.setWidth(width); + visualSampleEntry.setHeight(height); + + AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox(); + + if (format.getByteBuffer("csd-0") != null) { + ArrayList spsArray = new ArrayList<>(); + ByteBuffer spsBuff = format.getByteBuffer("csd-0"); + spsBuff.position(4); + byte[] spsBytes = new byte[spsBuff.remaining()]; + spsBuff.get(spsBytes); + spsArray.add(spsBytes); + + ArrayList ppsArray = new ArrayList<>(); + ByteBuffer ppsBuff = format.getByteBuffer("csd-1"); + ppsBuff.position(4); + byte[] ppsBytes = new byte[ppsBuff.remaining()]; + ppsBuff.get(ppsBytes); + ppsArray.add(ppsBytes); + avcConfigurationBox.setSequenceParameterSets(spsArray); + avcConfigurationBox.setPictureParameterSets(ppsArray); + } + + if (format.containsKey("level")) { + int level = format.getInteger("level"); + if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel1) { + avcConfigurationBox.setAvcLevelIndication(1); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel2) { + avcConfigurationBox.setAvcLevelIndication(2); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel11) { + avcConfigurationBox.setAvcLevelIndication(11); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel12) { + avcConfigurationBox.setAvcLevelIndication(12); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel13) { + avcConfigurationBox.setAvcLevelIndication(13); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel21) { + avcConfigurationBox.setAvcLevelIndication(21); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel22) { + avcConfigurationBox.setAvcLevelIndication(22); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel3) { + avcConfigurationBox.setAvcLevelIndication(3); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel31) { + avcConfigurationBox.setAvcLevelIndication(31); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel32) { + avcConfigurationBox.setAvcLevelIndication(32); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel4) { + avcConfigurationBox.setAvcLevelIndication(4); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel41) { + avcConfigurationBox.setAvcLevelIndication(41); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel42) { + avcConfigurationBox.setAvcLevelIndication(42); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel5) { + avcConfigurationBox.setAvcLevelIndication(5); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel51) { + avcConfigurationBox.setAvcLevelIndication(51); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel52) { + avcConfigurationBox.setAvcLevelIndication(52); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel1b) { + avcConfigurationBox.setAvcLevelIndication(0x1b); + } + } else { + avcConfigurationBox.setAvcLevelIndication(13); + } + if (format.containsKey("profile")) { + int profile = format.getInteger("profile"); + if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline) { + avcConfigurationBox.setAvcProfileIndication(66); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileMain) { + avcConfigurationBox.setAvcProfileIndication(77); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileExtended) { + avcConfigurationBox.setAvcProfileIndication(88); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh) { + avcConfigurationBox.setAvcProfileIndication(100); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10) { + avcConfigurationBox.setAvcProfileIndication(110); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh422) { + avcConfigurationBox.setAvcProfileIndication(122); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh444) { + avcConfigurationBox.setAvcProfileIndication(244); + } + } else { + avcConfigurationBox.setAvcProfileIndication(100); + } + avcConfigurationBox.setBitDepthLumaMinus8(-1); + avcConfigurationBox.setBitDepthChromaMinus8(-1); + avcConfigurationBox.setChromaFormat(-1); + avcConfigurationBox.setConfigurationVersion(1); + avcConfigurationBox.setLengthSizeMinusOne(3); + avcConfigurationBox.setProfileCompatibility(0); + + visualSampleEntry.addBox(avcConfigurationBox); + sampleDescriptionBox.addBox(visualSampleEntry); + } else if (mime.equals("video/mp4v")) { + VisualSampleEntry visualSampleEntry = new VisualSampleEntry("mp4v"); + visualSampleEntry.setDataReferenceIndex(1); + visualSampleEntry.setDepth(24); + visualSampleEntry.setFrameCount(1); + visualSampleEntry.setHorizresolution(72); + visualSampleEntry.setVertresolution(72); + visualSampleEntry.setWidth(width); + visualSampleEntry.setHeight(height); + + sampleDescriptionBox.addBox(visualSampleEntry); + } + } else { + volume = 1; + timeScale = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); + handler = "soun"; + headerBox = new SoundMediaHeaderBox(); + sampleDescriptionBox = new SampleDescriptionBox(); + AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a"); + audioSampleEntry.setChannelCount(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)); + audioSampleEntry.setSampleRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE)); + audioSampleEntry.setDataReferenceIndex(1); + audioSampleEntry.setSampleSize(16); + + ESDescriptorBox esds = new ESDescriptorBox(); + ESDescriptor descriptor = new ESDescriptor(); + descriptor.setEsId(0); + + SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor(); + slConfigDescriptor.setPredefined(2); + descriptor.setSlConfigDescriptor(slConfigDescriptor); + + DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor(); + decoderConfigDescriptor.setObjectTypeIndication(0x40); + decoderConfigDescriptor.setStreamType(5); + decoderConfigDescriptor.setBufferSizeDB(1536); + if (format.containsKey("max-bitrate")) { + decoderConfigDescriptor.setMaxBitRate(format.getInteger("max-bitrate")); + } else { + decoderConfigDescriptor.setMaxBitRate(96000); + } + decoderConfigDescriptor.setAvgBitRate(timeScale); + + AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig(); + audioSpecificConfig.setAudioObjectType(2); + audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get((int) audioSampleEntry.getSampleRate())); + audioSpecificConfig.setChannelConfiguration(audioSampleEntry.getChannelCount()); + decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig); + + descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor); + + ByteBuffer data = descriptor.serialize(); + esds.setEsDescriptor(descriptor); + esds.setData(data); + audioSampleEntry.addBox(esds); + sampleDescriptionBox.addBox(audioSampleEntry); + } + } + + public long getTrackId() { + return trackId; + } + + public void addSample(long offset, MediaCodec.BufferInfo bufferInfo) { + boolean isSyncFrame = !isAudio && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + samples.add(new Sample(offset, bufferInfo.size)); + if (syncSamples != null && isSyncFrame) { + syncSamples.add(samples.size()); + } + samplePresentationTimes.add(new SamplePresentationTime(samplePresentationTimes.size(), (bufferInfo.presentationTimeUs * timeScale + 500000L) / 1000000L)); + } + + public void prepare() { + ArrayList original = new ArrayList<>(samplePresentationTimes); + Collections.sort(samplePresentationTimes, new Comparator() { + @Override + public int compare(SamplePresentationTime o1, SamplePresentationTime o2) { + if (o1.presentationTime > o2.presentationTime) { + return 1; + } else if (o1.presentationTime < o2.presentationTime) { + return -1; + } + return 0; + } + }); + long lastPresentationTimeUs = 0; + sampleDurations = new long[samplePresentationTimes.size()]; + long minDelta = Long.MAX_VALUE; + boolean outOfOrder = false; + for (int a = 0; a < samplePresentationTimes.size(); a++) { + SamplePresentationTime presentationTime = samplePresentationTimes.get(a); + long delta = presentationTime.presentationTime - lastPresentationTimeUs; + lastPresentationTimeUs = presentationTime.presentationTime; + sampleDurations[presentationTime.index] = delta; + if (presentationTime.index != 0) { + duration += delta; + } + if (delta != 0) { + minDelta = Math.min(minDelta, delta); + } + if (presentationTime.index != a) { + outOfOrder = true; + } + } + if (sampleDurations.length > 0) { + sampleDurations[0] = minDelta; + duration += minDelta; + } + for (int a = 1; a < original.size(); a++) { + original.get(a).dt = sampleDurations[a] + original.get(a - 1).dt; + } + if (outOfOrder) { + sampleCompositions = new int[samplePresentationTimes.size()]; + for (int a = 0; a < samplePresentationTimes.size(); a++) { + SamplePresentationTime presentationTime = samplePresentationTimes.get(a); + sampleCompositions[presentationTime.index] = (int) (presentationTime.presentationTime - presentationTime.dt); + } + } + //if (!first) { + // sampleDurations.add(sampleDurations.size() - 1, delta); + // duration += delta; + //} + } + + public ArrayList getSamples() { + return samples; + } + + public long getDuration() { + return duration; + } + + public String getHandler() { + return handler; + } + + public AbstractMediaHeaderBox getMediaHeaderBox() { + return headerBox; + } + + public int[] getSampleCompositions() { + return sampleCompositions; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return sampleDescriptionBox; + } + + public long[] getSyncSamples() { + if (syncSamples == null || syncSamples.isEmpty()) { + return null; + } + long[] returns = new long[syncSamples.size()]; + for (int i = 0; i < syncSamples.size(); i++) { + returns[i] = syncSamples.get(i); + } + return returns; + } + + public int getTimeScale() { + return timeScale; + } + + public Date getCreationTime() { + return creationTime; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public float getVolume() { + return volume; + } + + public long[] getSampleDurations() { + return sampleDurations; + } + + public boolean isAudio() { + return isAudio; + } +} diff --git a/telegramgallery/src/main/jni/Android.mk b/telegramgallery/src/main/jni/Android.mk index 0f7fad5..3309a44 100644 --- a/telegramgallery/src/main/jni/Android.mk +++ b/telegramgallery/src/main/jni/Android.mk @@ -60,7 +60,8 @@ LOCAL_CFLAGS := -w -std=c11 -Os -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USE LOCAL_CFLAGS += -Drestrict='' -D__EMX__ -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT -DHAVE_LRINTF -fno-math-errno LOCAL_CFLAGS += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -ffast-math -D__STDC_CONSTANT_MACROS LOCAL_CPPFLAGS := -DBSD=1 -ffast-math -Os -funroll-loops -std=c++11 -LOCAL_LDLIBS := -ljnigraphics -llog -lz -latomic +LOCAL_LDLIBS := -ljnigraphics -llog +#-llog -lz -latomic LOCAL_STATIC_LIBRARIES := avformat avcodec avutil ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) @@ -141,6 +142,7 @@ endif LOCAL_SRC_FILES += \ ./utils.c \ ./jni.c \ +./video.c \ ./gifvideo.cpp include $(BUILD_SHARED_LIBRARY) diff --git a/telegramgallery/src/main/jni/gifvideo.cpp b/telegramgallery/src/main/jni/gifvideo.cpp index e728113..bfbb6ee 100644 --- a/telegramgallery/src/main/jni/gifvideo.cpp +++ b/telegramgallery/src/main/jni/gifvideo.cpp @@ -5,8 +5,10 @@ #include #include #include + extern "C" { #include +#include static const std::string av_make_error_str(int errnum) { char errbuf[AV_ERROR_MAX_STRING_SIZE]; @@ -112,7 +114,7 @@ int decode_packet(VideoInfo *info, int *got_frame) { return decoded; } -jint Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_createDecoder(JNIEnv *env, jclass clazz, jstring src, jintArray data) { +jint Java_com_tangxiaolv_telegramgallery_utils_VideoUtils_createDecoder(JNIEnv *env, jclass clazz, jstring src, jintArray data) { VideoInfo *info = new VideoInfo(); char const *srcString = env->GetStringUTFChars(src, 0); @@ -163,15 +165,25 @@ jint Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_createDecoder(JNIE if (dataArr != nullptr) { dataArr[0] = info->video_dec_ctx->width; dataArr[1] = info->video_dec_ctx->height; + AVDictionaryEntry *rotate_tag = av_dict_get(info->video_stream->metadata, "rotate", NULL, 0); + if (rotate_tag && *rotate_tag->value && strcmp(rotate_tag->value, "0")) { + char *tail; + dataArr[2] = (int) av_strtod(rotate_tag->value, &tail); + if (*tail) { + dataArr[2] = 0; + } + } else { + dataArr[2] = 0; + } env->ReleaseIntArrayElements(data, dataArr, 0); } - //LOGD("successfully opened file %s", info->src); + LOGD("successfully opened file %s", info->src); return (int) info; } -void Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_destroyDecoder(JNIEnv *env, jclass clazz, jobject ptr) { +void Java_com_tangxiaolv_telegramgallery_utils_VideoUtils_destroyDecoder(JNIEnv *env, jclass clazz, jobject ptr) { if (ptr == NULL) { return; } @@ -180,7 +192,7 @@ void Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_destroyDecoder(JNI } -jint Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_getVideoFrame(JNIEnv *env, jclass clazz, jobject ptr, jobject bitmap, jintArray data) { +jint Java_com_tangxiaolv_telegramgallery_utils_VideoUtils_getVideoFrame(JNIEnv *env, jclass clazz, jobject ptr, jobject bitmap, jintArray data) { if (ptr == NULL || bitmap == nullptr) { return 0; } @@ -191,7 +203,7 @@ jint Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_getVideoFrame(JNIE while (true) { if (info->pkt.size == 0) { ret = av_read_frame(info->fmt_ctx, &info->pkt); - //LOGD("got packet with size %d", info->pkt.size); + LOGD("got packet with size %d", info->pkt.size); if (ret >= 0) { info->orig_pkt = info->pkt; } @@ -205,7 +217,7 @@ jint Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_getVideoFrame(JNIE } info->pkt.size = 0; } else { - //LOGD("read size %d from packet", ret); + LOGD("read size %d from packet", ret); info->pkt.data += ret; info->pkt.size -= ret; } @@ -223,7 +235,7 @@ jint Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_getVideoFrame(JNIE } if (got_frame == 0) { if (info->has_decoded_frames) { - //LOGD("file end reached %s", info->src); + LOGD("file end reached %s", info->src); if ((ret = avformat_seek_file(info->fmt_ctx, -1, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { LOGE("can't seek to begin of file %s, %s", info->src, av_err2str(ret)); return 0; @@ -237,19 +249,30 @@ jint Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_getVideoFrame(JNIE return 0; } if (got_frame) { - //LOGD("decoded frame with w = %d, h = %d, format = %d", info->frame->width, info->frame->height, info->frame->format); - if (info->frame->format == AV_PIX_FMT_YUV420P || info->frame->format == AV_PIX_FMT_BGRA) { + LOGD("decoded frame with w = %d, h = %d, format = %d", info->frame->width, info->frame->height, info->frame->format); + if (info->frame->format == AV_PIX_FMT_YUV420P || info->frame->format == AV_PIX_FMT_BGRA || info->frame->format == AV_PIX_FMT_YUVJ420P) { jint *dataArr = env->GetIntArrayElements(data, 0); + int wantedWidth; + int wantedHeight; if (dataArr != nullptr) { - dataArr[2] = (int) (1000 * info->frame->pkt_pts * av_q2d(info->video_stream->time_base)); + wantedWidth = dataArr[0]; + wantedHeight = dataArr[1]; + dataArr[3] = (int) (1000 * info->frame->pkt_pts * av_q2d(info->video_stream->time_base)); env->ReleaseIntArrayElements(data, dataArr, 0); + } else { + AndroidBitmapInfo bitmapInfo; + AndroidBitmap_getInfo(env, bitmap, &bitmapInfo); + wantedWidth = bitmapInfo.width; + wantedHeight = bitmapInfo.height; } void *pixels; if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { - if (info->frame->format == AV_PIX_FMT_YUV420P) { - //LOGD("y %d, u %d, v %d, width %d, height %d", info->frame->linesize[0], info->frame->linesize[2], info->frame->linesize[1], info->frame->width, info->frame->height); - libyuv::I420ToARGB(info->frame->data[0], info->frame->linesize[0], info->frame->data[2], info->frame->linesize[2], info->frame->data[1], info->frame->linesize[1], (uint8_t *) pixels, info->frame->width * 4, info->frame->width, info->frame->height); + if (info->frame->format == AV_PIX_FMT_YUV420P || info->frame->format == AV_PIX_FMT_YUVJ420P) { + LOGD("y %d, u %d, v %d, width %d, height %d", info->frame->linesize[0], info->frame->linesize[2], info->frame->linesize[1], info->frame->width, info->frame->height); + if (wantedWidth == info->frame->width && wantedHeight == info->frame->height || wantedWidth == info->frame->height && wantedHeight == info->frame->width) { + libyuv::I420ToARGB(info->frame->data[0], info->frame->linesize[0], info->frame->data[2], info->frame->linesize[2], info->frame->data[1], info->frame->linesize[1], (uint8_t *) pixels, info->frame->width * 4, info->frame->width, info->frame->height); + } } else if (info->frame->format == AV_PIX_FMT_BGRA) { libyuv::ABGRToARGB(info->frame->data[0], info->frame->linesize[0], (uint8_t *) pixels, info->frame->width * 4, info->frame->width, info->frame->height); } @@ -261,6 +284,5 @@ jint Java_com_tangxiaolv_telegramgallery_AnimatedFileDrawable_getVideoFrame(JNIE return 1; } } - return 0; } } diff --git a/telegramgallery/src/main/jni/utils.h b/telegramgallery/src/main/jni/utils.h index 34805a1..5e5bc59 100644 --- a/telegramgallery/src/main/jni/utils.h +++ b/telegramgallery/src/main/jni/utils.h @@ -4,7 +4,7 @@ #include #include -#define LOG_TAG "tmessages_native" +#define LOG_TAG "ftssqlite" #ifndef LOG_DISABLED #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) diff --git a/telegramgallery/src/main/jni/video.c b/telegramgallery/src/main/jni/video.c new file mode 100644 index 0000000..f0ad5ce --- /dev/null +++ b/telegramgallery/src/main/jni/video.c @@ -0,0 +1,109 @@ +#include +#include +#include + +enum COLOR_FORMATTYPE { + COLOR_FormatMonochrome = 1, + COLOR_Format8bitRGB332 = 2, + COLOR_Format12bitRGB444 = 3, + COLOR_Format16bitARGB4444 = 4, + COLOR_Format16bitARGB1555 = 5, + COLOR_Format16bitRGB565 = 6, + COLOR_Format16bitBGR565 = 7, + COLOR_Format18bitRGB666 = 8, + COLOR_Format18bitARGB1665 = 9, + COLOR_Format19bitARGB1666 = 10, + COLOR_Format24bitRGB888 = 11, + COLOR_Format24bitBGR888 = 12, + COLOR_Format24bitARGB1887 = 13, + COLOR_Format25bitARGB1888 = 14, + COLOR_Format32bitBGRA8888 = 15, + COLOR_Format32bitARGB8888 = 16, + COLOR_FormatYUV411Planar = 17, + COLOR_FormatYUV411PackedPlanar = 18, + COLOR_FormatYUV420Planar = 19, + COLOR_FormatYUV420PackedPlanar = 20, + COLOR_FormatYUV420SemiPlanar = 21, + COLOR_FormatYUV422Planar = 22, + COLOR_FormatYUV422PackedPlanar = 23, + COLOR_FormatYUV422SemiPlanar = 24, + COLOR_FormatYCbYCr = 25, + COLOR_FormatYCrYCb = 26, + COLOR_FormatCbYCrY = 27, + COLOR_FormatCrYCbY = 28, + COLOR_FormatYUV444Interleaved = 29, + COLOR_FormatRawBayer8bit = 30, + COLOR_FormatRawBayer10bit = 31, + COLOR_FormatRawBayer8bitcompressed = 32, + COLOR_FormatL2 = 33, + COLOR_FormatL4 = 34, + COLOR_FormatL8 = 35, + COLOR_FormatL16 = 36, + COLOR_FormatL24 = 37, + COLOR_FormatL32 = 38, + COLOR_FormatYUV420PackedSemiPlanar = 39, + COLOR_FormatYUV422PackedSemiPlanar = 40, + COLOR_Format18BitBGR666 = 41, + COLOR_Format24BitARGB6666 = 42, + COLOR_Format24BitABGR6666 = 43, + + COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100, + COLOR_FormatSurface = 0x7F000789, + COLOR_QCOM_FormatYUV420SemiPlanar = 0x7fa30c00 +}; + +int isSemiPlanarYUV(int colorFormat) { + switch (colorFormat) { + case COLOR_FormatYUV420Planar: + case COLOR_FormatYUV420PackedPlanar: + return 0; + case COLOR_FormatYUV420SemiPlanar: + case COLOR_FormatYUV420PackedSemiPlanar: + case COLOR_TI_FormatYUV420PackedSemiPlanar: + return 1; + default: + return 0; + } +} + +JNIEXPORT int Java_com_tangxiaolv_telegramgallery_utils_VideoUtils_convertVideoFrame(JNIEnv *env, jclass class, jobject src, jobject dest, int destFormat, int width, int height, int padding, int swap) { + if (!src || !dest || !destFormat) { + return 0; + } + + jbyte *srcBuff = (*env)->GetDirectBufferAddress(env, src); + jbyte *destBuff = (*env)->GetDirectBufferAddress(env, dest); + + int half_width = (width + 1) / 2; + int half_height = (height + 1) / 2; + + if (!isSemiPlanarYUV(destFormat)) { + if (!swap) { + ARGBToI420(srcBuff, width * 4, + destBuff, width, + destBuff + width * height + half_width * half_height + padding * 5 / 4, half_width, + destBuff + width * height + padding, half_width, + width, height); + } else { + ARGBToI420(srcBuff, width * 4, + destBuff, width, + destBuff + width * height + padding, half_width, + destBuff + width * height + half_width * half_height + padding * 5 / 4, half_width, + width, height); + } + } else { + if (!swap) { + ARGBToNV21(srcBuff, width * 4, + destBuff, width, + destBuff + width * height + padding, half_width * 2, + width, height); + } else { + ARGBToNV12(srcBuff, width * 4, + destBuff, width, + destBuff + width * height + padding, half_width * 2, + width, height); + } + } + + return 1; +} diff --git a/telegramgallery/src/main/libs/armeabi-v7a/libgly.so b/telegramgallery/src/main/libs/armeabi-v7a/libgly.so new file mode 100644 index 0000000..34e4228 Binary files /dev/null and b/telegramgallery/src/main/libs/armeabi-v7a/libgly.so differ diff --git a/telegramgallery/src/main/libs/armeabi/libgly.so b/telegramgallery/src/main/libs/armeabi/libgly.so deleted file mode 100644 index 1e261c4..0000000 Binary files a/telegramgallery/src/main/libs/armeabi/libgly.so and /dev/null differ diff --git a/telegramgallery/src/main/res/anim/album_popup_in.xml b/telegramgallery/src/main/res/anim/album_popup_in.xml new file mode 100644 index 0000000..42a4bb0 --- /dev/null +++ b/telegramgallery/src/main/res/anim/album_popup_in.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/anim/album_popup_out.xml b/telegramgallery/src/main/res/anim/album_popup_out.xml new file mode 100644 index 0000000..0975248 --- /dev/null +++ b/telegramgallery/src/main/res/anim/album_popup_out.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/anim/push_bottom_in.xml b/telegramgallery/src/main/res/anim/push_bottom_in.xml new file mode 100644 index 0000000..f2d90c4 --- /dev/null +++ b/telegramgallery/src/main/res/anim/push_bottom_in.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/anim/push_bottom_out.xml b/telegramgallery/src/main/res/anim/push_bottom_out.xml new file mode 100644 index 0000000..6e9c6c0 --- /dev/null +++ b/telegramgallery/src/main/res/anim/push_bottom_out.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable-xhdpi/photobadge_new.png b/telegramgallery/src/main/res/drawable-xhdpi/album_photobadge_new.png similarity index 100% rename from telegramgallery/src/main/res/drawable-xhdpi/photobadge_new.png rename to telegramgallery/src/main/res/drawable-xhdpi/album_photobadge_new.png diff --git a/telegramgallery/src/main/res/drawable-xhdpi/photobadge_new.9.png b/telegramgallery/src/main/res/drawable-xhdpi/photobadge_new.9.png new file mode 100644 index 0000000..96f22c1 Binary files /dev/null and b/telegramgallery/src/main/res/drawable-xhdpi/photobadge_new.9.png differ diff --git a/telegramgallery/src/main/res/drawable/album_ab_back.png b/telegramgallery/src/main/res/drawable/album_ab_back.png new file mode 100644 index 0000000..c78f391 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_ab_back.png differ diff --git a/telegramgallery/src/main/res/drawable/album_ab_back_bule.png b/telegramgallery/src/main/res/drawable/album_ab_back_bule.png new file mode 100644 index 0000000..c69fd2b Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_ab_back_bule.png differ diff --git a/telegramgallery/src/main/res/drawable/album_bar_selector_style.xml b/telegramgallery/src/main/res/drawable/album_bar_selector_style.xml new file mode 100644 index 0000000..081220b --- /dev/null +++ b/telegramgallery/src/main/res/drawable/album_bar_selector_style.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable/album_checkbig.png b/telegramgallery/src/main/res/drawable/album_checkbig.png new file mode 100644 index 0000000..ad8ff9d Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_checkbig.png differ diff --git a/telegramgallery/src/main/res/drawable/album_close_white.png b/telegramgallery/src/main/res/drawable/album_close_white.png new file mode 100644 index 0000000..0eb9d8b Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_close_white.png differ diff --git a/telegramgallery/src/main/res/drawable/album_header_shadow.png b/telegramgallery/src/main/res/drawable/album_header_shadow.png new file mode 100644 index 0000000..382af9c Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_header_shadow.png differ diff --git a/telegramgallery/src/main/res/drawable/album_layer_shadow.png b/telegramgallery/src/main/res/drawable/album_layer_shadow.png new file mode 100644 index 0000000..637e319 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_layer_shadow.png differ diff --git a/telegramgallery/src/main/res/drawable/album_list_selector.xml b/telegramgallery/src/main/res/drawable/album_list_selector.xml new file mode 100644 index 0000000..f311824 --- /dev/null +++ b/telegramgallery/src/main/res/drawable/album_list_selector.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable/album_nophotos.9.png b/telegramgallery/src/main/res/drawable/album_nophotos.9.png new file mode 100644 index 0000000..123b71b Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_nophotos.9.png differ diff --git a/telegramgallery/src/main/res/drawable/album_nophotos_new.png b/telegramgallery/src/main/res/drawable/album_nophotos_new.png new file mode 100644 index 0000000..979ea46 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_nophotos_new.png differ diff --git a/telegramgallery/src/main/res/drawable/album_photoview_placeholder.png b/telegramgallery/src/main/res/drawable/album_photoview_placeholder.png new file mode 100644 index 0000000..979ea46 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_photoview_placeholder.png differ diff --git a/telegramgallery/src/main/res/drawable/album_popup_fixed.9.png b/telegramgallery/src/main/res/drawable/album_popup_fixed.9.png new file mode 100644 index 0000000..050190c Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_popup_fixed.9.png differ diff --git a/telegramgallery/src/main/res/drawable/album_search_carret.xml b/telegramgallery/src/main/res/drawable/album_search_carret.xml new file mode 100644 index 0000000..3c44cd8 --- /dev/null +++ b/telegramgallery/src/main/res/drawable/album_search_carret.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/telegramgallery/src/main/res/drawable/album_selectphoto_large.png b/telegramgallery/src/main/res/drawable/album_selectphoto_large.png new file mode 100644 index 0000000..0c8ff5a Binary files /dev/null and b/telegramgallery/src/main/res/drawable/album_selectphoto_large.png differ diff --git a/telegramgallery/src/main/res/drawable/bg_cicle_frame.png b/telegramgallery/src/main/res/drawable/bg_cicle_frame.png new file mode 100644 index 0000000..06bd645 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/bg_cicle_frame.png differ diff --git a/telegramgallery/src/main/res/drawable/btn_found_event_icon.png b/telegramgallery/src/main/res/drawable/btn_found_event_icon.png new file mode 100644 index 0000000..8c44be1 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/btn_found_event_icon.png differ diff --git a/telegramgallery/src/main/res/drawable/circle_big.png b/telegramgallery/src/main/res/drawable/circle_big.png new file mode 100644 index 0000000..4a0f8dd Binary files /dev/null and b/telegramgallery/src/main/res/drawable/circle_big.png differ diff --git a/telegramgallery/src/main/res/drawable/common_close_gray.png b/telegramgallery/src/main/res/drawable/common_close_gray.png new file mode 100644 index 0000000..0d2b103 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/common_close_gray.png differ diff --git a/telegramgallery/src/main/res/drawable/commution_img_default.9.png b/telegramgallery/src/main/res/drawable/commution_img_default.9.png new file mode 100644 index 0000000..3b0ea38 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/commution_img_default.9.png differ diff --git a/telegramgallery/src/main/res/drawable/ic_gif.png b/telegramgallery/src/main/res/drawable/ic_gif.png new file mode 100644 index 0000000..2fb3e79 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/ic_gif.png differ diff --git a/telegramgallery/src/main/res/drawable/ic_origin_checked.png b/telegramgallery/src/main/res/drawable/ic_origin_checked.png new file mode 100644 index 0000000..c2e3b2e Binary files /dev/null and b/telegramgallery/src/main/res/drawable/ic_origin_checked.png differ diff --git a/telegramgallery/src/main/res/drawable/ic_origin_unchecked.png b/telegramgallery/src/main/res/drawable/ic_origin_unchecked.png new file mode 100644 index 0000000..4b33d95 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/ic_origin_unchecked.png differ diff --git a/telegramgallery/src/main/res/drawable/ic_video.png b/telegramgallery/src/main/res/drawable/ic_video.png new file mode 100644 index 0000000..44da19f Binary files /dev/null and b/telegramgallery/src/main/res/drawable/ic_video.png differ diff --git a/telegramgallery/src/main/res/drawable/ic_video_hint.png b/telegramgallery/src/main/res/drawable/ic_video_hint.png new file mode 100644 index 0000000..b8e2407 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/ic_video_hint.png differ diff --git a/telegramgallery/src/main/res/drawable/ic_video_pause.png b/telegramgallery/src/main/res/drawable/ic_video_pause.png new file mode 100644 index 0000000..840f7c4 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/ic_video_pause.png differ diff --git a/telegramgallery/src/main/res/drawable/ic_video_play.png b/telegramgallery/src/main/res/drawable/ic_video_play.png new file mode 100644 index 0000000..441bbfa Binary files /dev/null and b/telegramgallery/src/main/res/drawable/ic_video_play.png differ diff --git a/telegramgallery/src/main/res/drawable/login_btn_gray.png b/telegramgallery/src/main/res/drawable/login_btn_gray.png new file mode 100644 index 0000000..57def4b Binary files /dev/null and b/telegramgallery/src/main/res/drawable/login_btn_gray.png differ diff --git a/telegramgallery/src/main/res/drawable/login_btn_light.9.png b/telegramgallery/src/main/res/drawable/login_btn_light.9.png new file mode 100644 index 0000000..22e9d42 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/login_btn_light.9.png differ diff --git a/telegramgallery/src/main/res/drawable/login_et_bg.png b/telegramgallery/src/main/res/drawable/login_et_bg.png new file mode 100644 index 0000000..4226444 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/login_et_bg.png differ diff --git a/telegramgallery/src/main/res/drawable/login_pop_bg.9.png b/telegramgallery/src/main/res/drawable/login_pop_bg.9.png new file mode 100644 index 0000000..61296d5 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/login_pop_bg.9.png differ diff --git a/telegramgallery/src/main/res/drawable/login_pop_btn_gray.9.png b/telegramgallery/src/main/res/drawable/login_pop_btn_gray.9.png new file mode 100644 index 0000000..ac950a2 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/login_pop_btn_gray.9.png differ diff --git a/telegramgallery/src/main/res/drawable/login_pop_btn_white.9.png b/telegramgallery/src/main/res/drawable/login_pop_btn_white.9.png new file mode 100644 index 0000000..f2bfd13 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/login_pop_btn_white.9.png differ diff --git a/telegramgallery/src/main/res/drawable/phototime.9.png b/telegramgallery/src/main/res/drawable/phototime.9.png new file mode 100644 index 0000000..650ecf6 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/phototime.9.png differ diff --git a/telegramgallery/src/main/res/drawable/photoview_placeholder.png b/telegramgallery/src/main/res/drawable/photoview_placeholder.png new file mode 100644 index 0000000..979ea46 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/photoview_placeholder.png differ diff --git a/telegramgallery/src/main/res/drawable/play_big.png b/telegramgallery/src/main/res/drawable/play_big.png new file mode 100644 index 0000000..b04acef Binary files /dev/null and b/telegramgallery/src/main/res/drawable/play_big.png differ diff --git a/telegramgallery/src/main/res/drawable/preview_cell_canceled.xml b/telegramgallery/src/main/res/drawable/preview_cell_canceled.xml new file mode 100644 index 0000000..e47b534 --- /dev/null +++ b/telegramgallery/src/main/res/drawable/preview_cell_canceled.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable/preview_cell_checked.xml b/telegramgallery/src/main/res/drawable/preview_cell_checked.xml new file mode 100644 index 0000000..823c4cc --- /dev/null +++ b/telegramgallery/src/main/res/drawable/preview_cell_checked.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable/sel_login_input_bg.xml b/telegramgallery/src/main/res/drawable/sel_login_input_bg.xml new file mode 100644 index 0000000..729dfdb --- /dev/null +++ b/telegramgallery/src/main/res/drawable/sel_login_input_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable/service_classifies_white_bg.9.png b/telegramgallery/src/main/res/drawable/service_classifies_white_bg.9.png new file mode 100644 index 0000000..b4946d6 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/service_classifies_white_bg.9.png differ diff --git a/telegramgallery/src/main/res/drawable/shape_send.xml b/telegramgallery/src/main/res/drawable/shape_send.xml new file mode 100644 index 0000000..6d4b791 --- /dev/null +++ b/telegramgallery/src/main/res/drawable/shape_send.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable/shape_unsend.xml b/telegramgallery/src/main/res/drawable/shape_unsend.xml new file mode 100644 index 0000000..ae469d5 --- /dev/null +++ b/telegramgallery/src/main/res/drawable/shape_unsend.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable/thumb.xml b/telegramgallery/src/main/res/drawable/thumb.xml new file mode 100644 index 0000000..fe5f62e --- /dev/null +++ b/telegramgallery/src/main/res/drawable/thumb.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/telegramgallery/src/main/res/drawable/video_cropleft.png b/telegramgallery/src/main/res/drawable/video_cropleft.png new file mode 100644 index 0000000..7a6ccf2 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/video_cropleft.png differ diff --git a/telegramgallery/src/main/res/drawable/video_cropright.png b/telegramgallery/src/main/res/drawable/video_cropright.png new file mode 100644 index 0000000..4b314d8 Binary files /dev/null and b/telegramgallery/src/main/res/drawable/video_cropright.png differ diff --git a/telegramgallery/src/main/res/mipmap-hdpi/ic_launcher.png b/telegramgallery/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/telegramgallery/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/telegramgallery/src/main/res/mipmap-mdpi/ic_launcher.png b/telegramgallery/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/telegramgallery/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/telegramgallery/src/main/res/mipmap-xhdpi/ic_launcher.png b/telegramgallery/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/telegramgallery/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/telegramgallery/src/main/res/mipmap-xxhdpi/ic_launcher.png b/telegramgallery/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/telegramgallery/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/telegramgallery/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/telegramgallery/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/telegramgallery/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/telegramgallery/src/main/res/values-zh/strings.xml b/telegramgallery/src/main/res/values-zh/strings.xml index 3342390..5262c8a 100644 --- a/telegramgallery/src/main/res/values-zh/strings.xml +++ b/telegramgallery/src/main/res/values-zh/strings.xml @@ -1,21 +1,56 @@ + TelegramGallery + 图片 视频 - 所有图片 + 所有照片 所有视频 + 所有媒体 还没有图片 还没有视频 + No recent photos + No recent GIFs 剪切 图片 %1$d / %2$d 设置 - 相册 + 相册 - 你最多只能选择%d张图片 + Clear - 完成 + 最多可以选择%d个图片或视频 + 最多可以选择%d个图片 + 不能发送超过%s的视频 + 不能发送超过%s的视频,请发送文件 + 原图过大 + 部分原图过大 + 不能同时选择图片或视频 + 视频不能超过%ds + 无效的视频 + + 发送 + 发送(%d) + 原图 取消 预览 + 编辑 相册读取失败,请检查权限 + + + 标记重要 + 确定 + 关闭 + 标为已读 + 发送 + 忘记密码 + 您已经成功发布该服务,可以去“服务机会”中查找需求意向或点击“返回”发布其他服务! + 去服务机会 + 返回 + 确定不添加吗? + 如果您不添加收款账号,该服务项的支付方式将不能选择“服务后线上支付”! + 保存到本地 + sdcard 空间不足 + + 不支持的图片类型:%s diff --git a/telegramgallery/src/main/res/values/attrs.xml b/telegramgallery/src/main/res/values/attrs.xml new file mode 100644 index 0000000..58eeba2 --- /dev/null +++ b/telegramgallery/src/main/res/values/attrs.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/telegramgallery/src/main/res/values/colors.xml b/telegramgallery/src/main/res/values/colors.xml index b4eac3e..41c5421 100644 --- a/telegramgallery/src/main/res/values/colors.xml +++ b/telegramgallery/src/main/res/values/colors.xml @@ -1,4 +1,15 @@ - #dcdcdc - \ No newline at end of file + #3F51B5 + #303F9F + #FF4081 + + #dfe1e0 + #007aff + + #b3b3b3 + + #838383 + #f1f0ee + #dddee3 + diff --git a/telegramgallery/src/main/res/values/strings.xml b/telegramgallery/src/main/res/values/strings.xml index a48bbaf..5cbcc69 100644 --- a/telegramgallery/src/main/res/values/strings.xml +++ b/telegramgallery/src/main/res/values/strings.xml @@ -32,4 +32,21 @@ Cancel Preview Failed to read an album, please check permissions + + + all media + Gallery + You can choose up to %d images or video. + You can choose up to %d images. + Can not send more than %s of video. + Can not send more than %s of video. + too large of image + part of images is too large + can not select either image or video at the same time. + video can not exceed %ds + invalid video + + send(%d) + origin + edit \ No newline at end of file diff --git a/telegramgallery/src/main/res/values/styles.xml b/telegramgallery/src/main/res/values/styles.xml index dc5b692..a588ce4 100644 --- a/telegramgallery/src/main/res/values/styles.xml +++ b/telegramgallery/src/main/res/values/styles.xml @@ -1,10 +1,10 @@ - + - @@ -30,9 +30,10 @@ - \ No newline at end of file + +