blob: 88a0d300901370ef8d42e40e0a5ec3b66cc53ff3 [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 */
Oleh Prypine78824e2018-01-15 20:30:59 +010032 @SuppressWarnings("StringSplitter")
mandermo64e1a322016-10-18 08:47:51 -070033 private static class VideoReaderY4M implements VideoReader {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020034 private static final String TAG = "VideoReaderY4M";
mandermo64e1a322016-10-18 08:47:51 -070035 private static final String Y4M_FRAME_DELIMETER = "FRAME";
36
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020037 private final int frameWidth;
38 private final int frameHeight;
39 // First char after header
40 private final long videoStart;
mandermo64e1a322016-10-18 08:47:51 -070041 private final RandomAccessFile mediaFileStream;
42
mandermo64e1a322016-10-18 08:47:51 -070043 public VideoReaderY4M(String file) throws IOException {
44 mediaFileStream = new RandomAccessFile(file, "r");
45 StringBuilder builder = new StringBuilder();
46 for (;;) {
47 int c = mediaFileStream.read();
48 if (c == -1) {
49 // End of file reached.
50 throw new RuntimeException("Found end of file before end of header for file: " + file);
51 }
52 if (c == '\n') {
53 // End of header found.
54 break;
55 }
56 builder.append((char) c);
57 }
58 videoStart = mediaFileStream.getFilePointer();
59 String header = builder.toString();
60 String[] headerTokens = header.split("[ ]");
61 int w = 0;
62 int h = 0;
63 String colorSpace = "";
64 for (String tok : headerTokens) {
65 char c = tok.charAt(0);
66 switch (c) {
67 case 'W':
68 w = Integer.parseInt(tok.substring(1));
69 break;
70 case 'H':
71 h = Integer.parseInt(tok.substring(1));
72 break;
73 case 'C':
74 colorSpace = tok.substring(1);
75 break;
76 }
77 }
78 Logging.d(TAG, "Color space: " + colorSpace);
mandermod192dce2016-11-02 09:15:41 -070079 if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
80 throw new IllegalArgumentException(
81 "Does not support any other color space than I420 or I420mpeg2");
mandermo64e1a322016-10-18 08:47:51 -070082 }
83 if ((w % 2) == 1 || (h % 2) == 1) {
84 throw new IllegalArgumentException("Does not support odd width or height");
85 }
86 frameWidth = w;
87 frameHeight = h;
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020088 Logging.d(TAG, "frame dim: (" + w + ", " + h + ")");
mandermo64e1a322016-10-18 08:47:51 -070089 }
90
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010091 @Override
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020092 public VideoFrame getNextFrame() {
93 final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
94 final JavaI420Buffer buffer = JavaI420Buffer.allocate(frameWidth, frameHeight);
95 final ByteBuffer dataY = buffer.getDataY();
96 final ByteBuffer dataU = buffer.getDataU();
97 final ByteBuffer dataV = buffer.getDataV();
98 final int chromaHeight = (frameHeight + 1) / 2;
99 final int sizeY = frameHeight * buffer.getStrideY();
100 final int sizeU = chromaHeight * buffer.getStrideU();
101 final int sizeV = chromaHeight * buffer.getStrideV();
102
mandermo64e1a322016-10-18 08:47:51 -0700103 try {
104 byte[] frameDelim = new byte[Y4M_FRAME_DELIMETER.length() + 1];
105 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
106 // We reach end of file, loop
107 mediaFileStream.seek(videoStart);
108 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
109 throw new RuntimeException("Error looping video");
110 }
111 }
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100112 String frameDelimStr = new String(frameDelim, Charset.forName("US-ASCII"));
mandermo64e1a322016-10-18 08:47:51 -0700113 if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
114 throw new RuntimeException(
115 "Frames should be delimited by FRAME plus newline, found delimter was: '"
116 + frameDelimStr + "'");
117 }
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200118
119 mediaFileStream.readFully(dataY.array(), dataY.arrayOffset(), sizeY);
120 mediaFileStream.readFully(dataU.array(), dataU.arrayOffset(), sizeU);
121 mediaFileStream.readFully(dataV.array(), dataV.arrayOffset(), sizeV);
mandermo64e1a322016-10-18 08:47:51 -0700122 } catch (IOException e) {
123 throw new RuntimeException(e);
124 }
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200125
126 return new VideoFrame(buffer, 0 /* rotation */, captureTimeNs);
mandermo64e1a322016-10-18 08:47:51 -0700127 }
128
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100129 @Override
mandermo64e1a322016-10-18 08:47:51 -0700130 public void close() {
131 try {
132 mediaFileStream.close();
133 } catch (IOException e) {
134 Logging.e(TAG, "Problem closing file", e);
135 }
136 }
137 }
138
139 private final static String TAG = "FileVideoCapturer";
140 private final VideoReader videoReader;
141 private CapturerObserver capturerObserver;
142 private final Timer timer = new Timer();
143
144 private final TimerTask tickTask = new TimerTask() {
145 @Override
146 public void run() {
147 tick();
148 }
149 };
150
mandermo64e1a322016-10-18 08:47:51 -0700151 public FileVideoCapturer(String inputFile) throws IOException {
152 try {
153 videoReader = new VideoReaderY4M(inputFile);
154 } catch (IOException e) {
155 Logging.d(TAG, "Could not open video file: " + inputFile);
156 throw e;
157 }
158 }
159
mandermo64e1a322016-10-18 08:47:51 -0700160 public void tick() {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200161 capturerObserver.onFrameCaptured(videoReader.getNextFrame());
mandermo64e1a322016-10-18 08:47:51 -0700162 }
163
164 @Override
165 public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
166 CapturerObserver capturerObserver) {
167 this.capturerObserver = capturerObserver;
168 }
169
170 @Override
171 public void startCapture(int width, int height, int framerate) {
172 timer.schedule(tickTask, 0, 1000 / framerate);
173 }
174
175 @Override
176 public void stopCapture() throws InterruptedException {
177 timer.cancel();
178 }
179
180 @Override
181 public void changeCaptureFormat(int width, int height, int framerate) {
182 // Empty on purpose
183 }
184
185 @Override
186 public void dispose() {
187 videoReader.close();
188 }
189
190 @Override
191 public boolean isScreencast() {
192 return false;
193 }
mandermo64e1a322016-10-18 08:47:51 -0700194}