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:
committed by
Commit Bot
parent
ea00e483d1
commit
07c5bfb4d6
@ -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",
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
Reference in New Issue
Block a user