blob: 331f9a7736a0f456cdecd5e735a0acbde67685cf [file] [log] [blame]
mandermo64e1a322016-10-18 08:47:51 -07001/*
2 * Copyright 2016 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11package org.webrtc;
12
13import android.content.Context;
14import android.os.SystemClock;
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020015import java.io.IOException;
16import java.io.RandomAccessFile;
17import java.nio.ByteBuffer;
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010018import java.nio.charset.Charset;
mandermo64e1a322016-10-18 08:47:51 -070019import java.util.Timer;
20import java.util.TimerTask;
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020021import java.util.concurrent.TimeUnit;
mandermo64e1a322016-10-18 08:47:51 -070022
23public class FileVideoCapturer implements VideoCapturer {
24 private interface VideoReader {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020025 VideoFrame getNextFrame();
mandermo64e1a322016-10-18 08:47:51 -070026 void close();
27 }
28
29 /**
30 * Read video data from file for the .y4m container.
31 */
32 private static class VideoReaderY4M implements VideoReader {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020033 private static final String TAG = "VideoReaderY4M";
mandermo64e1a322016-10-18 08:47:51 -070034 private static final String Y4M_FRAME_DELIMETER = "FRAME";
35
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020036 private final int frameWidth;
37 private final int frameHeight;
38 // First char after header
39 private final long videoStart;
mandermo64e1a322016-10-18 08:47:51 -070040 private final RandomAccessFile mediaFileStream;
41
mandermo64e1a322016-10-18 08:47:51 -070042 public VideoReaderY4M(String file) throws IOException {
43 mediaFileStream = new RandomAccessFile(file, "r");
44 StringBuilder builder = new StringBuilder();
45 for (;;) {
46 int c = mediaFileStream.read();
47 if (c == -1) {
48 // End of file reached.
49 throw new RuntimeException("Found end of file before end of header for file: " + file);
50 }
51 if (c == '\n') {
52 // End of header found.
53 break;
54 }
55 builder.append((char) c);
56 }
57 videoStart = mediaFileStream.getFilePointer();
58 String header = builder.toString();
59 String[] headerTokens = header.split("[ ]");
60 int w = 0;
61 int h = 0;
62 String colorSpace = "";
63 for (String tok : headerTokens) {
64 char c = tok.charAt(0);
65 switch (c) {
66 case 'W':
67 w = Integer.parseInt(tok.substring(1));
68 break;
69 case 'H':
70 h = Integer.parseInt(tok.substring(1));
71 break;
72 case 'C':
73 colorSpace = tok.substring(1);
74 break;
75 }
76 }
77 Logging.d(TAG, "Color space: " + colorSpace);
mandermod192dce2016-11-02 09:15:41 -070078 if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
79 throw new IllegalArgumentException(
80 "Does not support any other color space than I420 or I420mpeg2");
mandermo64e1a322016-10-18 08:47:51 -070081 }
82 if ((w % 2) == 1 || (h % 2) == 1) {
83 throw new IllegalArgumentException("Does not support odd width or height");
84 }
85 frameWidth = w;
86 frameHeight = h;
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020087 Logging.d(TAG, "frame dim: (" + w + ", " + h + ")");
mandermo64e1a322016-10-18 08:47:51 -070088 }
89
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010090 @Override
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020091 public VideoFrame getNextFrame() {
92 final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
93 final JavaI420Buffer buffer = JavaI420Buffer.allocate(frameWidth, frameHeight);
94 final ByteBuffer dataY = buffer.getDataY();
95 final ByteBuffer dataU = buffer.getDataU();
96 final ByteBuffer dataV = buffer.getDataV();
97 final int chromaHeight = (frameHeight + 1) / 2;
98 final int sizeY = frameHeight * buffer.getStrideY();
99 final int sizeU = chromaHeight * buffer.getStrideU();
100 final int sizeV = chromaHeight * buffer.getStrideV();
101
mandermo64e1a322016-10-18 08:47:51 -0700102 try {
103 byte[] frameDelim = new byte[Y4M_FRAME_DELIMETER.length() + 1];
104 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
105 // We reach end of file, loop
106 mediaFileStream.seek(videoStart);
107 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
108 throw new RuntimeException("Error looping video");
109 }
110 }
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100111 String frameDelimStr = new String(frameDelim, Charset.forName("US-ASCII"));
mandermo64e1a322016-10-18 08:47:51 -0700112 if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
113 throw new RuntimeException(
114 "Frames should be delimited by FRAME plus newline, found delimter was: '"
115 + frameDelimStr + "'");
116 }
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200117
118 mediaFileStream.readFully(dataY.array(), dataY.arrayOffset(), sizeY);
119 mediaFileStream.readFully(dataU.array(), dataU.arrayOffset(), sizeU);
120 mediaFileStream.readFully(dataV.array(), dataV.arrayOffset(), sizeV);
mandermo64e1a322016-10-18 08:47:51 -0700121 } catch (IOException e) {
122 throw new RuntimeException(e);
123 }
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200124
125 return new VideoFrame(buffer, 0 /* rotation */, captureTimeNs);
mandermo64e1a322016-10-18 08:47:51 -0700126 }
127
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100128 @Override
mandermo64e1a322016-10-18 08:47:51 -0700129 public void close() {
130 try {
131 mediaFileStream.close();
132 } catch (IOException e) {
133 Logging.e(TAG, "Problem closing file", e);
134 }
135 }
136 }
137
138 private final static String TAG = "FileVideoCapturer";
139 private final VideoReader videoReader;
140 private CapturerObserver capturerObserver;
141 private final Timer timer = new Timer();
142
143 private final TimerTask tickTask = new TimerTask() {
144 @Override
145 public void run() {
146 tick();
147 }
148 };
149
mandermo64e1a322016-10-18 08:47:51 -0700150 public FileVideoCapturer(String inputFile) throws IOException {
151 try {
152 videoReader = new VideoReaderY4M(inputFile);
153 } catch (IOException e) {
154 Logging.d(TAG, "Could not open video file: " + inputFile);
155 throw e;
156 }
157 }
158
mandermo64e1a322016-10-18 08:47:51 -0700159 public void tick() {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200160 capturerObserver.onFrameCaptured(videoReader.getNextFrame());
mandermo64e1a322016-10-18 08:47:51 -0700161 }
162
163 @Override
164 public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
165 CapturerObserver capturerObserver) {
166 this.capturerObserver = capturerObserver;
167 }
168
169 @Override
170 public void startCapture(int width, int height, int framerate) {
171 timer.schedule(tickTask, 0, 1000 / framerate);
172 }
173
174 @Override
175 public void stopCapture() throws InterruptedException {
176 timer.cancel();
177 }
178
179 @Override
180 public void changeCaptureFormat(int width, int height, int framerate) {
181 // Empty on purpose
182 }
183
184 @Override
185 public void dispose() {
186 videoReader.close();
187 }
188
189 @Override
190 public boolean isScreencast() {
191 return false;
192 }
mandermo64e1a322016-10-18 08:47:51 -0700193}