blob: a71e9327ae7f75fa0967323c1514b8f4feb4a9ba [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;
15
16import java.util.concurrent.TimeUnit;
17import java.util.Timer;
18import java.util.TimerTask;
19import java.io.RandomAccessFile;
20import java.io.IOException;
21
22public class FileVideoCapturer implements VideoCapturer {
magjed282d49f2017-04-13 04:17:02 -070023 static {
24 System.loadLibrary("jingle_peerconnection_so");
25 }
26
mandermo64e1a322016-10-18 08:47:51 -070027 private interface VideoReader {
28 int getFrameWidth();
29 int getFrameHeight();
30 byte[] getNextFrame();
31 void close();
32 }
33
34 /**
35 * Read video data from file for the .y4m container.
36 */
37 private static class VideoReaderY4M implements VideoReader {
38 private final static String TAG = "VideoReaderY4M";
39 private final int frameWidth;
40 private final int frameHeight;
41 private final int frameSize;
42
43 // First char after header
44 private final long videoStart;
45
46 private static final String Y4M_FRAME_DELIMETER = "FRAME";
47
48 private final RandomAccessFile mediaFileStream;
49
50 public int getFrameWidth() {
51 return frameWidth;
52 }
53
54 public int getFrameHeight() {
55 return frameHeight;
56 }
57
58 public VideoReaderY4M(String file) throws IOException {
59 mediaFileStream = new RandomAccessFile(file, "r");
60 StringBuilder builder = new StringBuilder();
61 for (;;) {
62 int c = mediaFileStream.read();
63 if (c == -1) {
64 // End of file reached.
65 throw new RuntimeException("Found end of file before end of header for file: " + file);
66 }
67 if (c == '\n') {
68 // End of header found.
69 break;
70 }
71 builder.append((char) c);
72 }
73 videoStart = mediaFileStream.getFilePointer();
74 String header = builder.toString();
75 String[] headerTokens = header.split("[ ]");
76 int w = 0;
77 int h = 0;
78 String colorSpace = "";
79 for (String tok : headerTokens) {
80 char c = tok.charAt(0);
81 switch (c) {
82 case 'W':
83 w = Integer.parseInt(tok.substring(1));
84 break;
85 case 'H':
86 h = Integer.parseInt(tok.substring(1));
87 break;
88 case 'C':
89 colorSpace = tok.substring(1);
90 break;
91 }
92 }
93 Logging.d(TAG, "Color space: " + colorSpace);
mandermod192dce2016-11-02 09:15:41 -070094 if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
95 throw new IllegalArgumentException(
96 "Does not support any other color space than I420 or I420mpeg2");
mandermo64e1a322016-10-18 08:47:51 -070097 }
98 if ((w % 2) == 1 || (h % 2) == 1) {
99 throw new IllegalArgumentException("Does not support odd width or height");
100 }
101 frameWidth = w;
102 frameHeight = h;
103 frameSize = w * h * 3 / 2;
104 Logging.d(TAG, "frame dim: (" + w + ", " + h + ") frameSize: " + frameSize);
105 }
106
107 public byte[] getNextFrame() {
108 byte[] frame = new byte[frameSize];
109 try {
110 byte[] frameDelim = new byte[Y4M_FRAME_DELIMETER.length() + 1];
111 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
112 // We reach end of file, loop
113 mediaFileStream.seek(videoStart);
114 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
115 throw new RuntimeException("Error looping video");
116 }
117 }
118 String frameDelimStr = new String(frameDelim);
119 if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
120 throw new RuntimeException(
121 "Frames should be delimited by FRAME plus newline, found delimter was: '"
122 + frameDelimStr + "'");
123 }
124 mediaFileStream.readFully(frame);
125 byte[] nv21Frame = new byte[frameSize];
126 nativeI420ToNV21(frame, frameWidth, frameHeight, nv21Frame);
127 return nv21Frame;
128 } catch (IOException e) {
129 throw new RuntimeException(e);
130 }
131 }
132
133 public void close() {
134 try {
135 mediaFileStream.close();
136 } catch (IOException e) {
137 Logging.e(TAG, "Problem closing file", e);
138 }
139 }
140 }
141
142 private final static String TAG = "FileVideoCapturer";
143 private final VideoReader videoReader;
144 private CapturerObserver capturerObserver;
145 private final Timer timer = new Timer();
146
147 private final TimerTask tickTask = new TimerTask() {
148 @Override
149 public void run() {
150 tick();
151 }
152 };
153
154 private int getFrameWidth() {
155 return videoReader.getFrameWidth();
156 }
157
158 private int getFrameHeight() {
159 return videoReader.getFrameHeight();
160 }
161
162 public FileVideoCapturer(String inputFile) throws IOException {
163 try {
164 videoReader = new VideoReaderY4M(inputFile);
165 } catch (IOException e) {
166 Logging.d(TAG, "Could not open video file: " + inputFile);
167 throw e;
168 }
169 }
170
171 private byte[] getNextFrame() {
172 return videoReader.getNextFrame();
173 }
174
175 public void tick() {
176 final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
177
178 byte[] frameData = getNextFrame();
179 capturerObserver.onByteBufferFrameCaptured(
180 frameData, getFrameWidth(), getFrameHeight(), 0, captureTimeNs);
181 }
182
183 @Override
184 public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
185 CapturerObserver capturerObserver) {
186 this.capturerObserver = capturerObserver;
187 }
188
189 @Override
190 public void startCapture(int width, int height, int framerate) {
191 timer.schedule(tickTask, 0, 1000 / framerate);
192 }
193
194 @Override
195 public void stopCapture() throws InterruptedException {
196 timer.cancel();
197 }
198
199 @Override
200 public void changeCaptureFormat(int width, int height, int framerate) {
201 // Empty on purpose
202 }
203
204 @Override
205 public void dispose() {
206 videoReader.close();
207 }
208
209 @Override
210 public boolean isScreencast() {
211 return false;
212 }
213
214 public static native void nativeI420ToNV21(byte[] src, int width, int height, byte[] dst);
215}