2016-10-18 08:47:51 -07:00
|
|
|
/*
|
|
|
|
|
* Copyright 2016 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.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package org.webrtc;
|
|
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.os.SystemClock;
|
|
|
|
|
|
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
import java.util.Timer;
|
|
|
|
|
import java.util.TimerTask;
|
|
|
|
|
import java.io.RandomAccessFile;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
|
|
|
|
|
public class FileVideoCapturer implements VideoCapturer {
|
Reland of Android Logging.java: Load native library only when needed (patchset #1 id:1 of https://codereview.webrtc.org/2814133002/ )
Reason for revert:
Fix bug in original CL.
Original issue's description:
> Revert of Android Logging.java: Load native library only when needed (patchset #2 id:2 of https://codereview.webrtc.org/2817593003/ )
>
> Reason for revert:
> Change in Logging.java breaking compilation (incorrect reference to enum)
>
> Original issue's description:
> > Reland of Android Logging.java: Load native library only when needed (patchset #1 id:1 of https://codereview.webrtc.org/2816753002/ )
> >
> > Reason for revert:
> > Fix bug in original CL.
> >
> > Original issue's description:
> > > Revert of Android Logging.java: Load native library only when needed (patchset #3 id:40001 of https://codereview.webrtc.org/2803203002/ )
> > >
> > > Reason for revert:
> > > Breaks C++ logs in Java apps.
> > >
> > > Original issue's description:
> > > > Android Logging.java: Load native library only when needed
> > > >
> > > > Logging.java currently always tries to load jingle_peerconnection_so in
> > > > the static section, but some clients don't want to use it. This CL loads
> > > > jingle_peerconnection_so only when a client requests it by calling one
> > > > of:
> > > > * Logging.enableLogThreads
> > > > * Logging.enableLogTimeStamps
> > > > * Logging.enableTracing
> > > > * Logging.enableLogToDebugOutput
> > > >
> > > > BUG=b/36410678
> > > >
> > > > Review-Url: https://codereview.webrtc.org/2803203002
> > > > Cr-Commit-Position: refs/heads/master@{#17647}
> > > > Committed: https://chromium.googlesource.com/external/webrtc/+/dee5eb14e1d4e5bd894772448cc9418e94b3a967
> > >
> > > TBR=sakal@webrtc.org,glaznev@webrtc.org,noahric@chromium.org,magjed@webrtc.org
> > > # Not skipping CQ checks because original CL landed more than 1 days ago.
> > > BUG=b/36410678
> > >
> > > Review-Url: https://codereview.webrtc.org/2816753002
> > > Cr-Commit-Position: refs/heads/master@{#17676}
> > > Committed: https://chromium.googlesource.com/external/webrtc/+/6e4a4427dc58ca252461310981e1f07ed987c334
> >
> > TBR=sakal@webrtc.org,glaznev@webrtc.org,noahric@chromium.org,brandtr@webrtc.org
> > # Skipping CQ checks because original CL landed less than 1 days ago.
> > NOPRESUBMIT=true
> > NOTREECHECKS=true
> > NOTRY=true
> > BUG=b/36410678
> >
> > Review-Url: https://codereview.webrtc.org/2817593003
> > Cr-Commit-Position: refs/heads/master@{#17677}
> > Committed: https://chromium.googlesource.com/external/webrtc/+/297714619fc4252c08ef8a04ac4ca18653c70b39
>
> TBR=sakal@webrtc.org,glaznev@webrtc.org,noahric@chromium.org,brandtr@webrtc.org,magjed@webrtc.org
> # Skipping CQ checks because original CL landed less than 1 days ago.
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=b/36410678
>
> Review-Url: https://codereview.webrtc.org/2814133002
> Cr-Commit-Position: refs/heads/master@{#17678}
> Committed: https://chromium.googlesource.com/external/webrtc/+/06034c7d6401b8e447aac98fd062b87523ae5f4f
TBR=sakal@webrtc.org,glaznev@webrtc.org,noahric@chromium.org,brandtr@webrtc.org,lliuu@webrtc.org
# Skipping CQ checks because original CL landed less than 1 days ago.
BUG=b/36410678
Review-Url: https://codereview.webrtc.org/2813363004
Cr-Commit-Position: refs/heads/master@{#17694}
2017-04-13 04:17:02 -07:00
|
|
|
static {
|
|
|
|
|
System.loadLibrary("jingle_peerconnection_so");
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-18 08:47:51 -07:00
|
|
|
private interface VideoReader {
|
|
|
|
|
int getFrameWidth();
|
|
|
|
|
int getFrameHeight();
|
|
|
|
|
byte[] getNextFrame();
|
|
|
|
|
void close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 Y4M_FRAME_DELIMETER = "FRAME";
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
for (;;) {
|
|
|
|
|
int c = mediaFileStream.read();
|
|
|
|
|
if (c == -1) {
|
|
|
|
|
// End of file reached.
|
|
|
|
|
throw new RuntimeException("Found end of file before end of header for file: " + file);
|
|
|
|
|
}
|
|
|
|
|
if (c == '\n') {
|
|
|
|
|
// End of header found.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
builder.append((char) c);
|
|
|
|
|
}
|
|
|
|
|
videoStart = mediaFileStream.getFilePointer();
|
|
|
|
|
String header = builder.toString();
|
|
|
|
|
String[] headerTokens = header.split("[ ]");
|
|
|
|
|
int w = 0;
|
|
|
|
|
int h = 0;
|
|
|
|
|
String colorSpace = "";
|
|
|
|
|
for (String tok : headerTokens) {
|
|
|
|
|
char c = tok.charAt(0);
|
|
|
|
|
switch (c) {
|
|
|
|
|
case 'W':
|
|
|
|
|
w = Integer.parseInt(tok.substring(1));
|
|
|
|
|
break;
|
|
|
|
|
case 'H':
|
|
|
|
|
h = Integer.parseInt(tok.substring(1));
|
|
|
|
|
break;
|
|
|
|
|
case 'C':
|
|
|
|
|
colorSpace = tok.substring(1);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Logging.d(TAG, "Color space: " + colorSpace);
|
2016-11-02 09:15:41 -07:00
|
|
|
if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
|
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
|
"Does not support any other color space than I420 or I420mpeg2");
|
2016-10-18 08:47:51 -07:00
|
|
|
}
|
|
|
|
|
if ((w % 2) == 1 || (h % 2) == 1) {
|
|
|
|
|
throw new IllegalArgumentException("Does not support odd width or height");
|
|
|
|
|
}
|
|
|
|
|
frameWidth = w;
|
|
|
|
|
frameHeight = h;
|
|
|
|
|
frameSize = w * h * 3 / 2;
|
|
|
|
|
Logging.d(TAG, "frame dim: (" + w + ", " + h + ") frameSize: " + frameSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] getNextFrame() {
|
|
|
|
|
byte[] frame = new byte[frameSize];
|
|
|
|
|
try {
|
|
|
|
|
byte[] frameDelim = new byte[Y4M_FRAME_DELIMETER.length() + 1];
|
|
|
|
|
if (mediaFileStream.read(frameDelim) < frameDelim.length) {
|
|
|
|
|
// We reach end of file, loop
|
|
|
|
|
mediaFileStream.seek(videoStart);
|
|
|
|
|
if (mediaFileStream.read(frameDelim) < frameDelim.length) {
|
|
|
|
|
throw new RuntimeException("Error looping video");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
String frameDelimStr = new String(frameDelim);
|
|
|
|
|
if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
|
|
|
|
|
throw new RuntimeException(
|
|
|
|
|
"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;
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void close() {
|
|
|
|
|
try {
|
|
|
|
|
mediaFileStream.close();
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Logging.e(TAG, "Problem closing file", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private final static String TAG = "FileVideoCapturer";
|
|
|
|
|
private final VideoReader videoReader;
|
|
|
|
|
private CapturerObserver capturerObserver;
|
|
|
|
|
private final Timer timer = new Timer();
|
|
|
|
|
|
|
|
|
|
private final TimerTask tickTask = new TimerTask() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
tick();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private int getFrameWidth() {
|
|
|
|
|
return videoReader.getFrameWidth();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int getFrameHeight() {
|
|
|
|
|
return videoReader.getFrameHeight();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public FileVideoCapturer(String inputFile) throws IOException {
|
|
|
|
|
try {
|
|
|
|
|
videoReader = new VideoReaderY4M(inputFile);
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Logging.d(TAG, "Could not open video file: " + inputFile);
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
|
|
|
|
|
CapturerObserver capturerObserver) {
|
|
|
|
|
this.capturerObserver = capturerObserver;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void startCapture(int width, int height, int framerate) {
|
|
|
|
|
timer.schedule(tickTask, 0, 1000 / framerate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void stopCapture() throws InterruptedException {
|
|
|
|
|
timer.cancel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void changeCaptureFormat(int width, int height, int framerate) {
|
|
|
|
|
// Empty on purpose
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void dispose() {
|
|
|
|
|
videoReader.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isScreencast() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static native void nativeI420ToNV21(byte[] src, int width, int height, byte[] dst);
|
|
|
|
|
}
|