blob: 6b1fe6f721160096eb5cbdf59fa01ed63f5837b1 [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 {
magjed282d49f2017-04-13 04:17:02 -070024 static {
25 System.loadLibrary("jingle_peerconnection_so");
26 }
27
mandermo64e1a322016-10-18 08:47:51 -070028 private interface VideoReader {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020029 VideoFrame getNextFrame();
mandermo64e1a322016-10-18 08:47:51 -070030 void close();
31 }
32
33 /**
34 * Read video data from file for the .y4m container.
35 */
36 private static class VideoReaderY4M implements VideoReader {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020037 private static final String TAG = "VideoReaderY4M";
mandermo64e1a322016-10-18 08:47:51 -070038 private static final String Y4M_FRAME_DELIMETER = "FRAME";
39
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020040 private final int frameWidth;
41 private final int frameHeight;
42 // First char after header
43 private final long videoStart;
mandermo64e1a322016-10-18 08:47:51 -070044 private final RandomAccessFile mediaFileStream;
45
mandermo64e1a322016-10-18 08:47:51 -070046 public VideoReaderY4M(String file) throws IOException {
47 mediaFileStream = new RandomAccessFile(file, "r");
48 StringBuilder builder = new StringBuilder();
49 for (;;) {
50 int c = mediaFileStream.read();
51 if (c == -1) {
52 // End of file reached.
53 throw new RuntimeException("Found end of file before end of header for file: " + file);
54 }
55 if (c == '\n') {
56 // End of header found.
57 break;
58 }
59 builder.append((char) c);
60 }
61 videoStart = mediaFileStream.getFilePointer();
62 String header = builder.toString();
63 String[] headerTokens = header.split("[ ]");
64 int w = 0;
65 int h = 0;
66 String colorSpace = "";
67 for (String tok : headerTokens) {
68 char c = tok.charAt(0);
69 switch (c) {
70 case 'W':
71 w = Integer.parseInt(tok.substring(1));
72 break;
73 case 'H':
74 h = Integer.parseInt(tok.substring(1));
75 break;
76 case 'C':
77 colorSpace = tok.substring(1);
78 break;
79 }
80 }
81 Logging.d(TAG, "Color space: " + colorSpace);
mandermod192dce2016-11-02 09:15:41 -070082 if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
83 throw new IllegalArgumentException(
84 "Does not support any other color space than I420 or I420mpeg2");
mandermo64e1a322016-10-18 08:47:51 -070085 }
86 if ((w % 2) == 1 || (h % 2) == 1) {
87 throw new IllegalArgumentException("Does not support odd width or height");
88 }
89 frameWidth = w;
90 frameHeight = h;
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020091 Logging.d(TAG, "frame dim: (" + w + ", " + h + ")");
mandermo64e1a322016-10-18 08:47:51 -070092 }
93
Sami Kalliomäkibde473e2017-10-30 13:34:41 +010094 @Override
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020095 public VideoFrame getNextFrame() {
96 final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
97 final JavaI420Buffer buffer = JavaI420Buffer.allocate(frameWidth, frameHeight);
98 final ByteBuffer dataY = buffer.getDataY();
99 final ByteBuffer dataU = buffer.getDataU();
100 final ByteBuffer dataV = buffer.getDataV();
101 final int chromaHeight = (frameHeight + 1) / 2;
102 final int sizeY = frameHeight * buffer.getStrideY();
103 final int sizeU = chromaHeight * buffer.getStrideU();
104 final int sizeV = chromaHeight * buffer.getStrideV();
105
mandermo64e1a322016-10-18 08:47:51 -0700106 try {
107 byte[] frameDelim = new byte[Y4M_FRAME_DELIMETER.length() + 1];
108 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
109 // We reach end of file, loop
110 mediaFileStream.seek(videoStart);
111 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
112 throw new RuntimeException("Error looping video");
113 }
114 }
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100115 String frameDelimStr = new String(frameDelim, Charset.forName("US-ASCII"));
mandermo64e1a322016-10-18 08:47:51 -0700116 if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
117 throw new RuntimeException(
118 "Frames should be delimited by FRAME plus newline, found delimter was: '"
119 + frameDelimStr + "'");
120 }
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200121
122 mediaFileStream.readFully(dataY.array(), dataY.arrayOffset(), sizeY);
123 mediaFileStream.readFully(dataU.array(), dataU.arrayOffset(), sizeU);
124 mediaFileStream.readFully(dataV.array(), dataV.arrayOffset(), sizeV);
mandermo64e1a322016-10-18 08:47:51 -0700125 } catch (IOException e) {
126 throw new RuntimeException(e);
127 }
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200128
129 return new VideoFrame(buffer, 0 /* rotation */, captureTimeNs);
mandermo64e1a322016-10-18 08:47:51 -0700130 }
131
Sami Kalliomäkibde473e2017-10-30 13:34:41 +0100132 @Override
mandermo64e1a322016-10-18 08:47:51 -0700133 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
mandermo64e1a322016-10-18 08:47:51 -0700154 public FileVideoCapturer(String inputFile) throws IOException {
155 try {
156 videoReader = new VideoReaderY4M(inputFile);
157 } catch (IOException e) {
158 Logging.d(TAG, "Could not open video file: " + inputFile);
159 throw e;
160 }
161 }
162
mandermo64e1a322016-10-18 08:47:51 -0700163 public void tick() {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200164 capturerObserver.onFrameCaptured(videoReader.getNextFrame());
mandermo64e1a322016-10-18 08:47:51 -0700165 }
166
167 @Override
168 public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
169 CapturerObserver capturerObserver) {
170 this.capturerObserver = capturerObserver;
171 }
172
173 @Override
174 public void startCapture(int width, int height, int framerate) {
175 timer.schedule(tickTask, 0, 1000 / framerate);
176 }
177
178 @Override
179 public void stopCapture() throws InterruptedException {
180 timer.cancel();
181 }
182
183 @Override
184 public void changeCaptureFormat(int width, int height, int framerate) {
185 // Empty on purpose
186 }
187
188 @Override
189 public void dispose() {
190 videoReader.close();
191 }
192
193 @Override
194 public boolean isScreencast() {
195 return false;
196 }
mandermo64e1a322016-10-18 08:47:51 -0700197}