blob: 2cff9e0142c0f2a96d15a9a751be40cd665b2196 [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;
mandermo64e1a322016-10-18 08:47:51 -070018import java.util.Timer;
19import java.util.TimerTask;
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020020import java.util.concurrent.TimeUnit;
mandermo64e1a322016-10-18 08:47:51 -070021
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 {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020028 VideoFrame getNextFrame();
mandermo64e1a322016-10-18 08:47:51 -070029 void close();
30 }
31
32 /**
33 * Read video data from file for the .y4m container.
34 */
35 private static class VideoReaderY4M implements VideoReader {
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020036 private static final String TAG = "VideoReaderY4M";
mandermo64e1a322016-10-18 08:47:51 -070037 private static final String Y4M_FRAME_DELIMETER = "FRAME";
38
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020039 private final int frameWidth;
40 private final int frameHeight;
41 // First char after header
42 private final long videoStart;
mandermo64e1a322016-10-18 08:47:51 -070043 private final RandomAccessFile mediaFileStream;
44
mandermo64e1a322016-10-18 08:47:51 -070045 public VideoReaderY4M(String file) throws IOException {
46 mediaFileStream = new RandomAccessFile(file, "r");
47 StringBuilder builder = new StringBuilder();
48 for (;;) {
49 int c = mediaFileStream.read();
50 if (c == -1) {
51 // End of file reached.
52 throw new RuntimeException("Found end of file before end of header for file: " + file);
53 }
54 if (c == '\n') {
55 // End of header found.
56 break;
57 }
58 builder.append((char) c);
59 }
60 videoStart = mediaFileStream.getFilePointer();
61 String header = builder.toString();
62 String[] headerTokens = header.split("[ ]");
63 int w = 0;
64 int h = 0;
65 String colorSpace = "";
66 for (String tok : headerTokens) {
67 char c = tok.charAt(0);
68 switch (c) {
69 case 'W':
70 w = Integer.parseInt(tok.substring(1));
71 break;
72 case 'H':
73 h = Integer.parseInt(tok.substring(1));
74 break;
75 case 'C':
76 colorSpace = tok.substring(1);
77 break;
78 }
79 }
80 Logging.d(TAG, "Color space: " + colorSpace);
mandermod192dce2016-11-02 09:15:41 -070081 if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
82 throw new IllegalArgumentException(
83 "Does not support any other color space than I420 or I420mpeg2");
mandermo64e1a322016-10-18 08:47:51 -070084 }
85 if ((w % 2) == 1 || (h % 2) == 1) {
86 throw new IllegalArgumentException("Does not support odd width or height");
87 }
88 frameWidth = w;
89 frameHeight = h;
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020090 Logging.d(TAG, "frame dim: (" + w + ", " + h + ")");
mandermo64e1a322016-10-18 08:47:51 -070091 }
92
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +020093 public VideoFrame getNextFrame() {
94 final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
95 final JavaI420Buffer buffer = JavaI420Buffer.allocate(frameWidth, frameHeight);
96 final ByteBuffer dataY = buffer.getDataY();
97 final ByteBuffer dataU = buffer.getDataU();
98 final ByteBuffer dataV = buffer.getDataV();
99 final int chromaHeight = (frameHeight + 1) / 2;
100 final int sizeY = frameHeight * buffer.getStrideY();
101 final int sizeU = chromaHeight * buffer.getStrideU();
102 final int sizeV = chromaHeight * buffer.getStrideV();
103
mandermo64e1a322016-10-18 08:47:51 -0700104 try {
105 byte[] frameDelim = new byte[Y4M_FRAME_DELIMETER.length() + 1];
106 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
107 // We reach end of file, loop
108 mediaFileStream.seek(videoStart);
109 if (mediaFileStream.read(frameDelim) < frameDelim.length) {
110 throw new RuntimeException("Error looping video");
111 }
112 }
113 String frameDelimStr = new String(frameDelim);
114 if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
115 throw new RuntimeException(
116 "Frames should be delimited by FRAME plus newline, found delimter was: '"
117 + frameDelimStr + "'");
118 }
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200119
120 mediaFileStream.readFully(dataY.array(), dataY.arrayOffset(), sizeY);
121 mediaFileStream.readFully(dataU.array(), dataU.arrayOffset(), sizeU);
122 mediaFileStream.readFully(dataV.array(), dataV.arrayOffset(), sizeV);
mandermo64e1a322016-10-18 08:47:51 -0700123 } catch (IOException e) {
124 throw new RuntimeException(e);
125 }
Sami Kalliomäki07c5bfb2017-10-05 13:37:18 +0200126
127 return new VideoFrame(buffer, 0 /* rotation */, captureTimeNs);
mandermo64e1a322016-10-18 08:47:51 -0700128 }
129
130 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}