Implement VP9 profile 2 - 10 bit BT2020 support with libvpx. This code truncates the 10 bits to 8. We'll later update this to upload half-float or 16 bit short textures.

Pending: Convert BT2020 to DCI-P3 before render.

I'll add the same code to V2 after initial review.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148403349
This commit is contained in:
anjalibh 2017-02-23 15:59:00 -08:00 committed by Oliver Woodman
parent 88fc337db0
commit 84def0d048
6 changed files with 93 additions and 14 deletions

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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++) {

View File

@ -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<jbyte*>(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<uint16_t*>(
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<uint16_t*>(
img->planes[VPX_PLANE_U] + img->stride[VPX_PLANE_U] * y);
const uint16_t* srcVBase = reinterpret_cast<uint16_t*>(
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;
}