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