Do not copy I420 frames in the decoder when not necessary.

In most cases we can just return a frame referencing the buffer
returned by the decoder.

Bug: webrtc:7760
Change-Id: I0b42ab9662b39149e42a3c83adfd38a9d80e0e30
Reviewed-on: https://chromium-review.googlesource.com/544299
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Reviewed-by: Bjorn Mellem <mellem@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#18824}
This commit is contained in:
Sami Kalliomäki 2017-06-28 11:20:50 +02:00 committed by Commit Bot
parent b14fad45b8
commit 8d08a92c05

View File

@ -82,6 +82,10 @@ class HardwareVideoDecoder implements VideoDecoder {
private volatile boolean running = false;
private volatile Exception shutdownException = null;
// Prevents the decoder from being released before all output buffers have been released.
private final Object activeOutputBuffersLock = new Object();
private int activeOutputBuffers = 0; // Guarded by activeOutputBuffersLock
// Dimensions (width, height, stride, and sliceHeight) may be accessed by either the decode thread
// or the output thread. Accesses should be protected with this lock.
private final Object dimensionLock = new Object();
@ -366,18 +370,25 @@ class HardwareVideoDecoder implements VideoDecoder {
buffer.position(info.offset);
buffer.limit(info.size);
VideoFrame.I420Buffer frameBuffer = new I420BufferImpl(width, height);
final VideoFrame.I420Buffer frameBuffer;
// TODO(mellem): As an optimization, avoid copying data here. Wrap the output buffers into
// the frame buffer without copying or reformatting.
// TODO(mellem): As an optimization, use libyuv via JNI to copy/reformatting data.
if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Planar) {
copyI420(buffer, info.offset, frameBuffer, stride, sliceHeight, width, height);
if (sliceHeight % 2 == 0) {
frameBuffer =
createBufferFromI420(buffer, result, info.offset, stride, sliceHeight, width, height);
} else {
frameBuffer = new I420BufferImpl(width, height);
// Optimal path is not possible because we have to copy the last rows of U- and V-planes.
copyI420(buffer, info.offset, frameBuffer, stride, sliceHeight, width, height);
codec.releaseOutputBuffer(result, false);
}
} else {
frameBuffer = new I420BufferImpl(width, height);
// All other supported color formats are NV12.
nv12ToI420(buffer, info.offset, frameBuffer, stride, sliceHeight, width, height);
codec.releaseOutputBuffer(result, false);
}
codec.releaseOutputBuffer(result, false);
long presentationTimeNs = info.presentationTimeUs * 1000;
VideoFrame frame = new VideoFrame(frameBuffer, rotation, presentationTimeNs, new Matrix());
@ -444,6 +455,7 @@ class HardwareVideoDecoder implements VideoDecoder {
private void releaseCodecOnOutputThread() {
outputThreadChecker.checkIsOnValidThread();
Logging.d(TAG, "Releasing MediaCodec on output thread");
waitOutputBuffersReleasedOnOutputThread();
try {
codec.stop();
} catch (Exception e) {
@ -463,6 +475,21 @@ class HardwareVideoDecoder implements VideoDecoder {
Logging.d(TAG, "Release on output thread done");
}
private void waitOutputBuffersReleasedOnOutputThread() {
outputThreadChecker.checkIsOnValidThread();
synchronized (activeOutputBuffersLock) {
while (activeOutputBuffers > 0) {
Logging.d(TAG, "Waiting for all frames to be released.");
try {
activeOutputBuffersLock.wait();
} catch (InterruptedException e) {
Logging.e(TAG, "Interrupted while waiting for output buffers to be released.", e);
return;
}
}
}
}
private void stopOnOutputThread(Exception e) {
outputThreadChecker.checkIsOnValidThread();
running = false;
@ -478,6 +505,97 @@ class HardwareVideoDecoder implements VideoDecoder {
return false;
}
private VideoFrame.I420Buffer createBufferFromI420(final ByteBuffer buffer,
final int outputBufferIndex, final int offset, final int stride, final int sliceHeight,
final int width, final int height) {
final int uvStride = stride / 2;
final int chromaWidth = (width + 1) / 2;
final int chromaHeight = (height + 1) / 2;
final int yPos = offset;
final int uPos = yPos + stride * sliceHeight;
final int vPos = uPos + uvStride * sliceHeight / 2;
synchronized (activeOutputBuffersLock) {
activeOutputBuffers++;
}
return new VideoFrame.I420Buffer() {
private int refCount = 1;
@Override
public ByteBuffer getDataY() {
ByteBuffer data = buffer.slice();
data.position(yPos);
data.limit(yPos + getStrideY() * height);
return data;
}
@Override
public ByteBuffer getDataU() {
ByteBuffer data = buffer.slice();
data.position(uPos);
data.limit(uPos + getStrideU() * chromaHeight);
return data;
}
@Override
public ByteBuffer getDataV() {
ByteBuffer data = buffer.slice();
data.position(vPos);
data.limit(vPos + getStrideV() * chromaHeight);
return data;
}
@Override
public int getStrideY() {
return stride;
}
@Override
public int getStrideU() {
return uvStride;
}
@Override
public int getStrideV() {
return uvStride;
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public VideoFrame.I420Buffer toI420() {
return this;
}
@Override
public void retain() {
refCount++;
}
@Override
public void release() {
refCount--;
if (refCount == 0) {
codec.releaseOutputBuffer(outputBufferIndex, false);
synchronized (activeOutputBuffersLock) {
activeOutputBuffers--;
activeOutputBuffersLock.notifyAll();
}
}
}
};
}
private static void copyI420(ByteBuffer src, int offset, VideoFrame.I420Buffer frameBuffer,
int stride, int sliceHeight, int width, int height) {
int uvStride = stride / 2;