Fix handling non-tightly packed ByteBuffers in HardwareVideoDecoder.

Before this CL, there would be an out-of-bounds write in the ByteBuffer
copying when a decoded frame had height != sliceHeight.

Bug: webrtc:9194
Change-Id: Ibb80e5555e8f00d9e1fd4cb8a73f5e4ccd5a0b81
Tested: 640x360 loopback with eglContext == null in AppRTCMobile on Pixel.
Reviewed-on: https://webrtc-review.googlesource.com/74120
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23184}
This commit is contained in:
Sami Kalliomäki
2018-05-08 15:22:08 +02:00
committed by Commit Bot
parent c710ac142e
commit ee98be7811
9 changed files with 87 additions and 83 deletions

View File

@ -15,13 +15,13 @@ import android.media.MediaCodec;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaFormat;
import android.os.SystemClock;
import javax.annotation.Nullable;
import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.webrtc.ThreadUtils.ThreadChecker;
/** Android hardware video decoder. */
@ -512,37 +512,57 @@ class HardwareVideoDecoder
private VideoFrame.Buffer copyI420Buffer(
ByteBuffer buffer, int stride, int sliceHeight, int width, int height) {
if (stride % 2 != 0) {
throw new AssertionError("Stride is not divisible by two: " + stride);
}
// Note that the case with odd |sliceHeight| is handled in a special way.
// The chroma height contained in the payload is rounded down instead of
// up, making it one row less than what we expect in WebRTC. Therefore, we
// have to duplicate the last chroma rows for this case. Also, the offset
// between the Y plane and the U plane is unintuitive for this case. See
// http://bugs.webrtc.org/6651 for more info.
final int chromaWidth = (width + 1) / 2;
final int chromaHeight = (sliceHeight % 2 == 0) ? (height + 1) / 2 : height / 2;
final int uvStride = stride / 2;
final int yPos = 0;
final int yEnd = yPos + stride * height;
final int uPos = yPos + stride * sliceHeight;
final int uEnd = uPos + uvStride * (sliceHeight / 2);
final int uEnd = uPos + uvStride * chromaHeight;
final int vPos = uPos + uvStride * sliceHeight / 2;
final int vEnd = vPos + uvStride * (sliceHeight / 2);
final int vEnd = vPos + uvStride * chromaHeight;
VideoFrame.I420Buffer frameBuffer = JavaI420Buffer.allocate(width, height);
ByteBuffer dataY = frameBuffer.getDataY();
buffer.limit(yEnd);
buffer.position(yPos);
buffer.limit(uPos);
dataY.put(buffer);
YuvHelper.copyPlane(
buffer.slice(), stride, frameBuffer.getDataY(), frameBuffer.getStrideY(), width, height);
ByteBuffer dataU = frameBuffer.getDataU();
buffer.position(uPos);
buffer.limit(uEnd);
dataU.put(buffer);
if (sliceHeight % 2 != 0) {
buffer.position(uEnd - uvStride); // Repeat the last row.
dataU.put(buffer);
buffer.position(uPos);
YuvHelper.copyPlane(buffer.slice(), uvStride, frameBuffer.getDataU(), frameBuffer.getStrideU(),
chromaWidth, chromaHeight);
if (sliceHeight % 2 == 1) {
buffer.position(uPos + uvStride * (chromaHeight - 1)); // Seek to beginning of last full row.
ByteBuffer dataU = frameBuffer.getDataU();
dataU.position(frameBuffer.getStrideU() * chromaHeight); // Seek to beginning of last row.
dataU.put(buffer); // Copy the last row.
}
ByteBuffer dataV = frameBuffer.getDataV();
buffer.position(vPos);
buffer.limit(vEnd);
dataV.put(buffer);
if (sliceHeight % 2 != 0) {
buffer.position(vEnd - uvStride); // Repeat the last row.
dataV.put(buffer);
buffer.position(vPos);
YuvHelper.copyPlane(buffer.slice(), uvStride, frameBuffer.getDataV(), frameBuffer.getStrideV(),
chromaWidth, chromaHeight);
if (sliceHeight % 2 == 1) {
buffer.position(vPos + uvStride * (chromaHeight - 1)); // Seek to beginning of last full row.
ByteBuffer dataV = frameBuffer.getDataV();
dataV.position(frameBuffer.getStrideV() * chromaHeight); // Seek to beginning of last row.
dataV.put(buffer); // Copy the last row.
}
return frameBuffer;

View File

@ -1,52 +0,0 @@
/*
* Copyright (c) 2017 The WebRTC 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 <jni.h>
#include "sdk/android/generated_video_jni/jni/VideoFrameDrawer_jni.h"
#include "sdk/android/native_api/jni/scoped_java_ref.h"
namespace webrtc {
namespace jni {
static void JNI_VideoFrameDrawer_CopyPlane(
JNIEnv* jni,
const JavaParamRef<jclass>&,
const JavaParamRef<jobject>& j_src_buffer,
jint width,
jint height,
jint src_stride,
const JavaParamRef<jobject>& j_dst_buffer,
jint dst_stride) {
size_t src_size = jni->GetDirectBufferCapacity(j_src_buffer.obj());
size_t dst_size = jni->GetDirectBufferCapacity(j_dst_buffer.obj());
RTC_CHECK(src_stride >= width) << "Wrong source stride " << src_stride;
RTC_CHECK(dst_stride >= width) << "Wrong destination stride " << dst_stride;
RTC_CHECK(src_size >= src_stride * height)
<< "Insufficient source buffer capacity " << src_size;
RTC_CHECK(dst_size >= dst_stride * height)
<< "Insufficient destination buffer capacity " << dst_size;
uint8_t* src = reinterpret_cast<uint8_t*>(
jni->GetDirectBufferAddress(j_src_buffer.obj()));
uint8_t* dst = reinterpret_cast<uint8_t*>(
jni->GetDirectBufferAddress(j_dst_buffer.obj()));
if (src_stride == dst_stride) {
memcpy(dst, src, src_stride * height);
} else {
for (int i = 0; i < height; i++) {
memcpy(dst, src, width);
src += src_stride;
dst += dst_stride;
}
}
}
} // namespace jni
} // namespace webrtc

View File

@ -13,10 +13,27 @@
#include "sdk/android/generated_video_jni/jni/YuvHelper_jni.h"
#include "sdk/android/src/jni/jni_helpers.h"
#include "third_party/libyuv/include/libyuv/convert.h"
#include "third_party/libyuv/include/libyuv/planar_functions.h"
namespace webrtc {
namespace jni {
void JNI_YuvHelper_CopyPlane(JNIEnv* jni,
const JavaParamRef<jclass>&,
const JavaParamRef<jobject>& j_src,
jint src_stride,
const JavaParamRef<jobject>& j_dst,
jint dst_stride,
jint width,
jint height) {
const uint8_t* src =
static_cast<const uint8_t*>(jni->GetDirectBufferAddress(j_src.obj()));
uint8_t* dst =
static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_dst.obj()));
libyuv::CopyPlane(src, src_stride, dst, dst_stride, width, height);
}
void JNI_YuvHelper_I420Copy(JNIEnv* jni,
const JavaParamRef<jclass>&,
const JavaParamRef<jobject>& j_src_y,