From 07c5bfb4d6e315794aa9c5ae398a723b09e8496b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20Kalliom=C3=A4ki?= Date: Thu, 5 Oct 2017 13:37:18 +0200 Subject: [PATCH] Convert FileVideoCapturer to capture VideoFrames. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: webrtc:7749 Change-Id: I0e102888bf3f9d413b9e9282354f7577c52bef59 Reviewed-on: https://webrtc-review.googlesource.com/6920 Reviewed-by: Magnus Jedvert Commit-Queue: Sami Kalliomäki Cr-Commit-Position: refs/heads/master@{#20173} --- sdk/android/BUILD.gn | 1 - .../api/org/webrtc/FileVideoCapturer.java | 79 +++++++---------- .../src/org/webrtc/FileVideoCapturerTest.java | 85 +++++++++++-------- sdk/android/src/jni/filevideocapturer_jni.cc | 62 -------------- 4 files changed, 78 insertions(+), 149 deletions(-) delete mode 100644 sdk/android/src/jni/filevideocapturer_jni.cc diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn index 9f770761a3..322104bf04 100644 --- a/sdk/android/BUILD.gn +++ b/sdk/android/BUILD.gn @@ -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", diff --git a/sdk/android/api/org/webrtc/FileVideoCapturer.java b/sdk/android/api/org/webrtc/FileVideoCapturer.java index a71e9327ae..2cff9e0142 100644 --- a/sdk/android/api/org/webrtc/FileVideoCapturer.java +++ b/sdk/android/api/org/webrtc/FileVideoCapturer.java @@ -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); } diff --git a/sdk/android/instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java b/sdk/android/instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java index fd8f3b4102..627e7770ff 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java @@ -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 frameDatas = new ArrayList(); + private final ArrayList frames = new ArrayList(); @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 getMinimumFramesBlocking(int minFrames) + public synchronized ArrayList getMinimumFramesBlocking(int minFrames) throws InterruptedException { - while (frameDatas.size() < minFrames) { + while (frames.size() < minFrames) { wait(); } - return new ArrayList(frameDatas); + return new ArrayList(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 frameDatas; - frameDatas = capturerObserver.getMinimumFramesBlocking(expectedFrames.length); - - assertEquals(expectedFrames.length, frameDatas.size()); + final ArrayList 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()); } } } diff --git a/sdk/android/src/jni/filevideocapturer_jni.cc b/sdk/android/src/jni/filevideocapturer_jni.cc deleted file mode 100644 index b5b5c552c2..0000000000 --- a/sdk/android/src/jni/filevideocapturer_jni.cc +++ /dev/null @@ -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 - -#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(src_bytes); - jbyte* dst_bytes = jni->GetByteArrayElements(j_dst_buffer, 0); - uint8_t* dst = reinterpret_cast(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