blob: 8270367970f3ec4790e06d846e749bacfbb8daf3 [file] [log] [blame]
/*
* 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.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
public class FileVideoCapturer implements VideoCapturer {
private interface VideoReader {
VideoFrame getNextFrame();
void close();
}
/**
* Read video data from file for the .y4m container.
*/
@SuppressWarnings("StringSplitter")
private static class VideoReaderY4M implements VideoReader {
private static final String TAG = "VideoReaderY4M";
private static final String Y4M_FRAME_DELIMETER = "FRAME";
private static final int FRAME_DELIMETER_LENGTH = Y4M_FRAME_DELIMETER.length() + 1;
private final int frameWidth;
private final int frameHeight;
// First char after header
private final long videoStart;
private final RandomAccessFile mediaFile;
private final FileChannel mediaFileChannel;
public VideoReaderY4M(String file) throws IOException {
mediaFile = new RandomAccessFile(file, "r");
mediaFileChannel = mediaFile.getChannel();
StringBuilder builder = new StringBuilder();
for (;;) {
int c = mediaFile.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 = mediaFileChannel.position();
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);
if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
throw new IllegalArgumentException(
"Does not support any other color space than I420 or I420mpeg2");
}
if ((w % 2) == 1 || (h % 2) == 1) {
throw new IllegalArgumentException("Does not support odd width or height");
}
frameWidth = w;
frameHeight = h;
Logging.d(TAG, "frame dim: (" + w + ", " + h + ")");
}
@Override
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 {
ByteBuffer frameDelim = ByteBuffer.allocate(FRAME_DELIMETER_LENGTH);
if (mediaFileChannel.read(frameDelim) < FRAME_DELIMETER_LENGTH) {
// We reach end of file, loop
mediaFileChannel.position(videoStart);
if (mediaFileChannel.read(frameDelim) < FRAME_DELIMETER_LENGTH) {
throw new RuntimeException("Error looping video");
}
}
String frameDelimStr = new String(frameDelim.array(), Charset.forName("US-ASCII"));
if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
throw new RuntimeException(
"Frames should be delimited by FRAME plus newline, found delimter was: '"
+ frameDelimStr + "'");
}
mediaFileChannel.read(dataY);
mediaFileChannel.read(dataU);
mediaFileChannel.read(dataV);
} catch (IOException e) {
throw new RuntimeException(e);
}
return new VideoFrame(buffer, 0 /* rotation */, captureTimeNs);
}
@Override
public void close() {
try {
// Closing a file also closes the channel.
mediaFile.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();
}
};
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;
}
}
public void tick() {
VideoFrame videoFrame = videoReader.getNextFrame();
capturerObserver.onFrameCaptured(videoFrame);
videoFrame.release();
}
@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;
}
}