Convert FileVideoCapturer to capture VideoFrames.

Bug: webrtc:7749
Change-Id: I0e102888bf3f9d413b9e9282354f7577c52bef59
Reviewed-on: https://webrtc-review.googlesource.com/6920
Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20173}
This commit is contained in:
Sami Kalliomäki
2017-10-05 13:37:18 +02:00
committed by Commit Bot
parent ea00e483d1
commit 07c5bfb4d6
4 changed files with 78 additions and 149 deletions

View File

@ -97,7 +97,6 @@ rtc_static_library("video_jni") {
"src/jni/androidvideotracksource.cc",
"src/jni/androidvideotracksource.h",
"src/jni/androidvideotracksource_jni.cc",
"src/jni/filevideocapturer_jni.cc",
"src/jni/native_handle_impl.cc",
"src/jni/native_handle_impl.h",
"src/jni/nv12buffer_jni.cc",

View File

@ -12,12 +12,12 @@ package org.webrtc;
import android.content.Context;
import android.os.SystemClock;
import java.util.concurrent.TimeUnit;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Timer;
import java.util.TimerTask;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class FileVideoCapturer implements VideoCapturer {
static {
@ -25,9 +25,7 @@ public class FileVideoCapturer implements VideoCapturer {
}
private interface VideoReader {
int getFrameWidth();
int getFrameHeight();
byte[] getNextFrame();
VideoFrame getNextFrame();
void close();
}
@ -35,26 +33,15 @@ public class FileVideoCapturer implements VideoCapturer {
* Read video data from file for the .y4m container.
*/
private static class VideoReaderY4M implements VideoReader {
private final static String TAG = "VideoReaderY4M";
private final int frameWidth;
private final int frameHeight;
private final int frameSize;
// First char after header
private final long videoStart;
private static final String TAG = "VideoReaderY4M";
private static final String Y4M_FRAME_DELIMETER = "FRAME";
private final int frameWidth;
private final int frameHeight;
// First char after header
private final long videoStart;
private final RandomAccessFile mediaFileStream;
public int getFrameWidth() {
return frameWidth;
}
public int getFrameHeight() {
return frameHeight;
}
public VideoReaderY4M(String file) throws IOException {
mediaFileStream = new RandomAccessFile(file, "r");
StringBuilder builder = new StringBuilder();
@ -100,12 +87,20 @@ public class FileVideoCapturer implements VideoCapturer {
}
frameWidth = w;
frameHeight = h;
frameSize = w * h * 3 / 2;
Logging.d(TAG, "frame dim: (" + w + ", " + h + ") frameSize: " + frameSize);
Logging.d(TAG, "frame dim: (" + w + ", " + h + ")");
}
public byte[] getNextFrame() {
byte[] frame = new byte[frameSize];
public VideoFrame getNextFrame() {
final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
final JavaI420Buffer buffer = JavaI420Buffer.allocate(frameWidth, frameHeight);
final ByteBuffer dataY = buffer.getDataY();
final ByteBuffer dataU = buffer.getDataU();
final ByteBuffer dataV = buffer.getDataV();
final int chromaHeight = (frameHeight + 1) / 2;
final int sizeY = frameHeight * buffer.getStrideY();
final int sizeU = chromaHeight * buffer.getStrideU();
final int sizeV = chromaHeight * buffer.getStrideV();
try {
byte[] frameDelim = new byte[Y4M_FRAME_DELIMETER.length() + 1];
if (mediaFileStream.read(frameDelim) < frameDelim.length) {
@ -121,13 +116,15 @@ public class FileVideoCapturer implements VideoCapturer {
"Frames should be delimited by FRAME plus newline, found delimter was: '"
+ frameDelimStr + "'");
}
mediaFileStream.readFully(frame);
byte[] nv21Frame = new byte[frameSize];
nativeI420ToNV21(frame, frameWidth, frameHeight, nv21Frame);
return nv21Frame;
mediaFileStream.readFully(dataY.array(), dataY.arrayOffset(), sizeY);
mediaFileStream.readFully(dataU.array(), dataU.arrayOffset(), sizeU);
mediaFileStream.readFully(dataV.array(), dataV.arrayOffset(), sizeV);
} catch (IOException e) {
throw new RuntimeException(e);
}
return new VideoFrame(buffer, 0 /* rotation */, captureTimeNs);
}
public void close() {
@ -151,14 +148,6 @@ public class FileVideoCapturer implements VideoCapturer {
}
};
private int getFrameWidth() {
return videoReader.getFrameWidth();
}
private int getFrameHeight() {
return videoReader.getFrameHeight();
}
public FileVideoCapturer(String inputFile) throws IOException {
try {
videoReader = new VideoReaderY4M(inputFile);
@ -168,16 +157,8 @@ public class FileVideoCapturer implements VideoCapturer {
}
}
private byte[] getNextFrame() {
return videoReader.getNextFrame();
}
public void tick() {
final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
byte[] frameData = getNextFrame();
capturerObserver.onByteBufferFrameCaptured(
frameData, getFrameWidth(), getFrameHeight(), 0, captureTimeNs);
capturerObserver.onFrameCaptured(videoReader.getNextFrame());
}
@Override
@ -210,6 +191,4 @@ public class FileVideoCapturer implements VideoCapturer {
public boolean isScreencast() {
return false;
}
public static native void nativeI420ToNV21(byte[] src, int width, int height, byte[] dst);
}

View File

@ -14,11 +14,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.os.Environment;
import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import java.io.IOException;
import java.lang.Thread;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
@ -28,14 +26,8 @@ import org.junit.runner.RunWith;
@RunWith(BaseJUnit4ClassRunner.class)
public class FileVideoCapturerTest {
private static class Frame {
public byte[] data;
public int width;
public int height;
}
public class MockCapturerObserver implements VideoCapturer.CapturerObserver {
private final ArrayList<Frame> frameDatas = new ArrayList<Frame>();
private final ArrayList<VideoFrame> frames = new ArrayList<VideoFrame>();
@Override
public void onCapturerStarted(boolean success) {
@ -50,13 +42,7 @@ public class FileVideoCapturerTest {
@Override
public synchronized void onByteBufferFrameCaptured(
byte[] data, int width, int height, int rotation, long timeStamp) {
Frame frame = new Frame();
frame.data = data;
frame.width = width;
frame.height = height;
assertTrue(data.length != 0);
frameDatas.add(frame);
notify();
// Empty on purpose.
}
@Override
@ -66,16 +52,17 @@ public class FileVideoCapturerTest {
}
@Override
public void onFrameCaptured(VideoFrame frame) {
// Empty on purpose.
public synchronized void onFrameCaptured(VideoFrame frame) {
frames.add(frame);
notify();
}
public synchronized ArrayList<Frame> getMinimumFramesBlocking(int minFrames)
public synchronized ArrayList<VideoFrame> getMinimumFramesBlocking(int minFrames)
throws InterruptedException {
while (frameDatas.size() < minFrames) {
while (frames.size() < minFrames) {
wait();
}
return new ArrayList<Frame>(frameDatas);
return new ArrayList<VideoFrame>(frames);
}
}
@ -84,37 +71,63 @@ public class FileVideoCapturerTest {
public void testVideoCaptureFromFile() throws InterruptedException, IOException {
final int FRAME_WIDTH = 4;
final int FRAME_HEIGHT = 4;
final int FRAME_CHROMA_WIDTH = (FRAME_WIDTH + 1) / 2;
final int FRAME_CHROMA_HEIGHT = (FRAME_HEIGHT + 1) / 2;
final int FRAME_SIZE_Y = FRAME_WIDTH * FRAME_HEIGHT;
final int FRAME_SIZE_CHROMA = FRAME_CHROMA_WIDTH * FRAME_CHROMA_HEIGHT;
final FileVideoCapturer fileVideoCapturer =
new FileVideoCapturer(Environment.getExternalStorageDirectory().getPath()
+ "/chromium_tests_root/sdk/android/instrumentationtests/src/org/webrtc/"
+ "capturetestvideo.y4m");
final MockCapturerObserver capturerObserver = new MockCapturerObserver();
fileVideoCapturer.initialize(null, null, capturerObserver);
fileVideoCapturer.startCapture(FRAME_WIDTH, FRAME_HEIGHT, 33);
fileVideoCapturer.initialize(
null /* surfaceTextureHelper */, null /* applicationContext */, capturerObserver);
fileVideoCapturer.startCapture(FRAME_WIDTH, FRAME_HEIGHT, 33 /* fps */);
final String[] expectedFrames = {
"THIS IS JUST SOME TEXT x", "THE SECOND FRAME qwerty.", "HERE IS THE THRID FRAME!"};
final ArrayList<Frame> frameDatas;
frameDatas = capturerObserver.getMinimumFramesBlocking(expectedFrames.length);
assertEquals(expectedFrames.length, frameDatas.size());
final ArrayList<VideoFrame> frames =
capturerObserver.getMinimumFramesBlocking(expectedFrames.length);
assertEquals(expectedFrames.length, frames.size());
fileVideoCapturer.stopCapture();
fileVideoCapturer.dispose();
// Check the content of the frames.
for (int i = 0; i < expectedFrames.length; ++i) {
Frame frame = frameDatas.get(i);
final VideoFrame frame = frames.get(i);
final VideoFrame.Buffer buffer = frame.getBuffer();
assertTrue(buffer instanceof VideoFrame.I420Buffer);
final VideoFrame.I420Buffer i420Buffer = (VideoFrame.I420Buffer) buffer;
assertEquals(FRAME_WIDTH, frame.width);
assertEquals(FRAME_HEIGHT, frame.height);
assertEquals(FRAME_WIDTH * FRAME_HEIGHT * 3 / 2, frame.data.length);
assertEquals(FRAME_WIDTH, i420Buffer.getWidth());
assertEquals(FRAME_HEIGHT, i420Buffer.getHeight());
byte[] expectedNV12Bytes = new byte[frame.data.length];
FileVideoCapturer.nativeI420ToNV21(expectedFrames[i].getBytes(Charset.forName("US-ASCII")),
FRAME_WIDTH, FRAME_HEIGHT, expectedNV12Bytes);
final ByteBuffer dataY = i420Buffer.getDataY();
final ByteBuffer dataU = i420Buffer.getDataU();
final ByteBuffer dataV = i420Buffer.getDataV();
assertTrue(Arrays.equals(expectedNV12Bytes, frame.data));
assertEquals(FRAME_SIZE_Y, dataY.remaining());
assertEquals(FRAME_SIZE_CHROMA, dataU.remaining());
assertEquals(FRAME_SIZE_CHROMA, dataV.remaining());
ByteBuffer frameContents = ByteBuffer.allocate(FRAME_SIZE_Y + 2 * FRAME_SIZE_CHROMA);
frameContents.put(dataY);
frameContents.put(dataU);
frameContents.put(dataV);
frameContents.rewind(); // Move back to the beginning.
assertByteBufferContents(
expectedFrames[i].getBytes(Charset.forName("US-ASCII")), frameContents);
}
}
private static void assertByteBufferContents(byte[] expected, ByteBuffer actual) {
assertEquals("Unexpected ByteBuffer size.", expected.length, actual.remaining());
for (int i = 0; i < expected.length; i++) {
assertEquals("Unexpected byte at index: " + i, expected[i], actual.get());
}
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright 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 "third_party/libyuv/include/libyuv/convert_from.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
namespace jni {
extern "C" JNIEXPORT void JNICALL
Java_org_webrtc_FileVideoCapturer_nativeI420ToNV21(JNIEnv* jni,
jclass,
jbyteArray j_src_buffer,
jint width,
jint height,
jbyteArray j_dst_buffer) {
size_t src_size = jni->GetArrayLength(j_src_buffer);
size_t dst_size = jni->GetArrayLength(j_dst_buffer);
int src_stride = width;
int dst_stride = width;
RTC_CHECK_GE(src_size, src_stride * height * 3 / 2);
RTC_CHECK_GE(dst_size, dst_stride * height * 3 / 2);
jbyte* src_bytes = jni->GetByteArrayElements(j_src_buffer, 0);
uint8_t* src = reinterpret_cast<uint8_t*>(src_bytes);
jbyte* dst_bytes = jni->GetByteArrayElements(j_dst_buffer, 0);
uint8_t* dst = reinterpret_cast<uint8_t*>(dst_bytes);
uint8_t* src_y = src;
size_t src_stride_y = src_stride;
uint8_t* src_u = src + src_stride * height;
size_t src_stride_u = src_stride / 2;
uint8_t* src_v = src + src_stride * height * 5 / 4;
size_t src_stride_v = src_stride / 2;
uint8_t* dst_y = dst;
size_t dst_stride_y = dst_stride;
size_t dst_stride_uv = dst_stride;
uint8_t* dst_uv = dst + dst_stride * height;
int ret = libyuv::I420ToNV21(src_y, src_stride_y, src_u, src_stride_u, src_v,
src_stride_v, dst_y, dst_stride_y, dst_uv,
dst_stride_uv, width, height);
jni->ReleaseByteArrayElements(j_src_buffer, src_bytes, 0);
jni->ReleaseByteArrayElements(j_dst_buffer, dst_bytes, 0);
if (ret) {
LOG(LS_ERROR) << "Error converting I420 frame to NV21: " << ret;
}
}
} // namespace jni
} // namespace webrtc