diff --git a/extensions/vp9/src/androidTest/assets/roadtrip-vp92-10bit.webm b/extensions/vp9/src/androidTest/assets/roadtrip-vp92-10bit.webm new file mode 100644 index 0000000000..b3bd1b9d74 Binary files /dev/null and b/extensions/vp9/src/androidTest/assets/roadtrip-vp92-10bit.webm differ diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index b1ddf2368c..f888554e22 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -19,6 +19,7 @@ import android.content.Context; import android.net.Uri; import android.os.Looper; import android.test.InstrumentationTestCase; +import android.util.Log; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -38,8 +39,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { private static final String BEAR_URI = "asset:///bear-vp9.webm"; private static final String BEAR_ODD_DIMENSIONS_URI = "asset:///bear-vp9-odd-dimensions.webm"; + private static final String ROADTRIP_10BIT_URI = "asset:///roadtrip-vp92-10bit.webm"; private static final String INVALID_BITSTREAM_URI = "asset:///invalid-bitstream.webm"; + private static final String TAG = "VpxPlaybackTest"; + public void testBasicPlayback() throws ExoPlaybackException { playUri(BEAR_URI); } @@ -48,6 +52,15 @@ public class VpxPlaybackTest extends InstrumentationTestCase { playUri(BEAR_ODD_DIMENSIONS_URI); } + public void test10BitProfile2Playback() throws ExoPlaybackException { + if (VpxLibrary.isHighBitDepthSupported()) { + Log.d(TAG, "High Bit Depth supported."); + playUri(ROADTRIP_10BIT_URI); + return; + } + Log.d(TAG, "High Bit Depth not supported."); + } + public void testInvalidBitstream() { try { playUri(INVALID_BITSTREAM_URI); diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java index 2caa33c17c..24331127ec 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java @@ -57,6 +57,16 @@ public final class VpxLibrary { return isAvailable() ? vpxGetBuildConfig() : null; } + /** + * Returns true if the underlying libvpx library supports high bit depth. + */ + public static boolean isHighBitDepthSupported() { + String config = getBuildConfig(); + int indexHbd = config != null + ? config.indexOf("--enable-vp9-highbitdepth") : -1; + return indexHbd >= 0; + } + private static native String vpxGetVersion(); private static native String vpxGetBuildConfig(); public static native boolean vpxIsSecureDecodeSupported(); diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java index c76d0eda03..db3cf49b0c 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java @@ -26,6 +26,7 @@ import java.nio.ByteBuffer; public static final int COLORSPACE_UNKNOWN = 0; public static final int COLORSPACE_BT601 = 1; public static final int COLORSPACE_BT709 = 2; + public static final int COLORSPACE_BT2020 = 3; private final VpxDecoder owner; diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java index d108ae8b4f..837539593e 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java @@ -42,6 +42,12 @@ import javax.microedition.khronos.opengles.GL10; 1.793f, -0.533f, 0.0f, }; + private static final float[] kColorConversion2020 = { + 1.168f, 1.168f, 1.168f, + 0.0f, -0.188f, 2.148f, + 1.683f, -0.652f, 0.0f, + }; + private static final String VERTEX_SHADER = "varying vec2 interp_tc;\n" + "attribute vec4 in_pos;\n" @@ -59,12 +65,13 @@ import javax.microedition.khronos.opengles.GL10; + "uniform sampler2D v_tex;\n" + "uniform mat3 mColorConversion;\n" + "void main() {\n" - + " vec3 yuv;" + + " vec3 yuv;\n" + " yuv.x = texture2D(y_tex, interp_tc).r - 0.0625;\n" + " yuv.y = texture2D(u_tex, interp_tc).r - 0.5;\n" + " yuv.z = texture2D(v_tex, interp_tc).r - 0.5;\n" - + " gl_FragColor = vec4(mColorConversion * yuv, 1.0);" + + " gl_FragColor = vec4(mColorConversion * yuv, 1.0);\n" + "}\n"; + private static final FloatBuffer TEXTURE_VERTICES = nativeFloatBuffer( -1.0f, 1.0f, -1.0f, -1.0f, @@ -156,8 +163,18 @@ import javax.microedition.khronos.opengles.GL10; } VpxOutputBuffer outputBuffer = renderedOutputBuffer; // Set color matrix. Assume BT709 if the color space is unknown. - float[] colorConversion = outputBuffer.colorspace == VpxOutputBuffer.COLORSPACE_BT601 - ? kColorConversion601 : kColorConversion709; + float[] colorConversion = kColorConversion709; + switch (outputBuffer.colorspace) { + case VpxOutputBuffer.COLORSPACE_BT601: + colorConversion = kColorConversion601; + break; + case VpxOutputBuffer.COLORSPACE_BT2020: + colorConversion = kColorConversion2020; + break; + case VpxOutputBuffer.COLORSPACE_BT709: + default: + break; // Do nothing + } GLES20.glUniformMatrix3fv(colorMatrixLocation, 1, false, colorConversion, 0); for (int i = 0; i < 3; i++) { diff --git a/extensions/vp9/src/main/jni/vpx_jni.cc b/extensions/vp9/src/main/jni/vpx_jni.cc index 137ff9ac21..67fd250fdc 100644 --- a/extensions/vp9/src/main/jni/vpx_jni.cc +++ b/extensions/vp9/src/main/jni/vpx_jni.cc @@ -74,8 +74,11 @@ DECODER_FUNC(jlong, vpxInit) { vpx_codec_dec_cfg_t cfg = {0, 0, 0}; cfg.threads = android_getCpuCount(); errorCode = 0; - if (vpx_codec_dec_init(context, &vpx_codec_vp9_dx_algo, &cfg, 0)) { - LOGE("ERROR: Fail to initialize libvpx decoder."); + vpx_codec_err_t err = vpx_codec_dec_init(context, &vpx_codec_vp9_dx_algo, + &cfg, 0); + if (err) { + LOGE("ERROR: Failed to initialize libvpx decoder, error = %d.", err); + errorCode = err; return 0; } @@ -160,6 +163,7 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) { const int kColorspaceUnknown = 0; const int kColorspaceBT601 = 1; const int kColorspaceBT709 = 2; + const int kColorspaceBT2020 = 3; int colorspace = kColorspaceUnknown; switch (img->cs) { @@ -169,6 +173,9 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) { case VPX_CS_BT_709: colorspace = kColorspaceBT709; break; + case VPX_CS_BT_2020: + colorspace = kColorspaceBT2020; + break; default: break; } @@ -186,14 +193,45 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) { jbyte* const data = reinterpret_cast(env->GetDirectBufferAddress(dataObject)); - // TODO: This copy can be eliminated by using external frame buffers. NOLINT - // This is insignificant for smaller videos but takes ~1.5ms for 1080p - // clips. So this should eventually be gotten rid of. - const uint64_t y_length = img->stride[VPX_PLANE_Y] * img->d_h; - const uint64_t uv_length = img->stride[VPX_PLANE_U] * ((img->d_h + 1) / 2); - memcpy(data, img->planes[VPX_PLANE_Y], y_length); - memcpy(data + y_length, img->planes[VPX_PLANE_U], uv_length); - memcpy(data + y_length + uv_length, img->planes[VPX_PLANE_V], uv_length); + const int32_t uvHeight = (img->d_h + 1) / 2; + const uint64_t yLength = img->stride[VPX_PLANE_Y] * img->d_h; + const uint64_t uvLength = img->stride[VPX_PLANE_U] * uvHeight; + if (img->fmt == VPX_IMG_FMT_I42016) { // HBD planar 420. + // Note: The stride for BT2020 is twice of what we use so this is wasting + // memory. The long term goal however is to upload half-float/short so + // it's not important to optimize the stride at this time. + // Y + for (int y = 0; y < img->d_h; y++) { + const uint16_t* srcBase = reinterpret_cast( + img->planes[VPX_PLANE_Y] + img->stride[VPX_PLANE_Y] * y); + int8_t* destBase = data + img->stride[VPX_PLANE_Y] * y; + for (int x = 0; x < img->d_w; x++) { + *destBase++ = *srcBase++ / 4; + } + } + // UV + const int32_t uvWidth = (img->d_w + 1) / 2; + for (int y = 0; y < uvHeight; y++) { + const uint16_t* srcUBase = reinterpret_cast( + img->planes[VPX_PLANE_U] + img->stride[VPX_PLANE_U] * y); + const uint16_t* srcVBase = reinterpret_cast( + img->planes[VPX_PLANE_V] + img->stride[VPX_PLANE_V] * y); + int8_t* destUBase = data + yLength + img->stride[VPX_PLANE_U] * y; + int8_t* destVBase = data + yLength + uvLength + + img->stride[VPX_PLANE_V] * y; + for (int x = 0; x < uvWidth; x++) { + *destUBase++ = *srcUBase++ / 4; + *destVBase++ = *srcVBase++ / 4; + } + } + } else { + // TODO: This copy can be eliminated by using external frame buffers. This + // is insignificant for smaller videos but takes ~1.5ms for 1080p clips. + // So this should eventually be gotten rid of. + memcpy(data, img->planes[VPX_PLANE_Y], yLength); + memcpy(data + yLength, img->planes[VPX_PLANE_U], uvLength); + memcpy(data + yLength + uvLength, img->planes[VPX_PLANE_V], uvLength); + } } return 0; }