• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import java.io.IOException;
16 import java.io.RandomAccessFile;
17 import java.nio.ByteBuffer;
18 import java.nio.channels.FileChannel;
19 import java.nio.charset.Charset;
20 import java.util.Timer;
21 import java.util.TimerTask;
22 import java.util.concurrent.TimeUnit;
23 
24 public class FileVideoCapturer implements VideoCapturer {
25   private interface VideoReader {
getNextFrame()26     VideoFrame getNextFrame();
close()27     void close();
28   }
29 
30   /**
31    * Read video data from file for the .y4m container.
32    */
33   @SuppressWarnings("StringSplitter")
34   private static class VideoReaderY4M implements VideoReader {
35     private static final String TAG = "VideoReaderY4M";
36     private static final String Y4M_FRAME_DELIMETER = "FRAME";
37     private static final int FRAME_DELIMETER_LENGTH = Y4M_FRAME_DELIMETER.length() + 1;
38 
39     private final int frameWidth;
40     private final int frameHeight;
41     // First char after header
42     private final long videoStart;
43     private final RandomAccessFile mediaFile;
44     private final FileChannel mediaFileChannel;
45 
VideoReaderY4M(String file)46     public VideoReaderY4M(String file) throws IOException {
47       mediaFile = new RandomAccessFile(file, "r");
48       mediaFileChannel = mediaFile.getChannel();
49       StringBuilder builder = new StringBuilder();
50       for (;;) {
51         int c = mediaFile.read();
52         if (c == -1) {
53           // End of file reached.
54           throw new RuntimeException("Found end of file before end of header for file: " + file);
55         }
56         if (c == '\n') {
57           // End of header found.
58           break;
59         }
60         builder.append((char) c);
61       }
62       videoStart = mediaFileChannel.position();
63       String header = builder.toString();
64       String[] headerTokens = header.split("[ ]");
65       int w = 0;
66       int h = 0;
67       String colorSpace = "";
68       for (String tok : headerTokens) {
69         char c = tok.charAt(0);
70         switch (c) {
71           case 'W':
72             w = Integer.parseInt(tok.substring(1));
73             break;
74           case 'H':
75             h = Integer.parseInt(tok.substring(1));
76             break;
77           case 'C':
78             colorSpace = tok.substring(1);
79             break;
80         }
81       }
82       Logging.d(TAG, "Color space: " + colorSpace);
83       if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
84         throw new IllegalArgumentException(
85             "Does not support any other color space than I420 or I420mpeg2");
86       }
87       if ((w % 2) == 1 || (h % 2) == 1) {
88         throw new IllegalArgumentException("Does not support odd width or height");
89       }
90       frameWidth = w;
91       frameHeight = h;
92       Logging.d(TAG, "frame dim: (" + w + ", " + h + ")");
93     }
94 
95     @Override
getNextFrame()96     public VideoFrame getNextFrame() {
97       final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
98       final JavaI420Buffer buffer = JavaI420Buffer.allocate(frameWidth, frameHeight);
99       final ByteBuffer dataY = buffer.getDataY();
100       final ByteBuffer dataU = buffer.getDataU();
101       final ByteBuffer dataV = buffer.getDataV();
102       final int chromaHeight = (frameHeight + 1) / 2;
103       final int sizeY = frameHeight * buffer.getStrideY();
104       final int sizeU = chromaHeight * buffer.getStrideU();
105       final int sizeV = chromaHeight * buffer.getStrideV();
106 
107       try {
108         ByteBuffer frameDelim = ByteBuffer.allocate(FRAME_DELIMETER_LENGTH);
109         if (mediaFileChannel.read(frameDelim) < FRAME_DELIMETER_LENGTH) {
110           // We reach end of file, loop
111           mediaFileChannel.position(videoStart);
112           if (mediaFileChannel.read(frameDelim) < FRAME_DELIMETER_LENGTH) {
113             throw new RuntimeException("Error looping video");
114           }
115         }
116         String frameDelimStr = new String(frameDelim.array(), Charset.forName("US-ASCII"));
117         if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
118           throw new RuntimeException(
119               "Frames should be delimited by FRAME plus newline, found delimter was: '"
120               + frameDelimStr + "'");
121         }
122 
123         mediaFileChannel.read(dataY);
124         mediaFileChannel.read(dataU);
125         mediaFileChannel.read(dataV);
126       } catch (IOException e) {
127         throw new RuntimeException(e);
128       }
129 
130       return new VideoFrame(buffer, 0 /* rotation */, captureTimeNs);
131     }
132 
133     @Override
close()134     public void close() {
135       try {
136         // Closing a file also closes the channel.
137         mediaFile.close();
138       } catch (IOException e) {
139         Logging.e(TAG, "Problem closing file", e);
140       }
141     }
142   }
143 
144   private final static String TAG = "FileVideoCapturer";
145   private final VideoReader videoReader;
146   private CapturerObserver capturerObserver;
147   private final Timer timer = new Timer();
148 
149   private final TimerTask tickTask = new TimerTask() {
150     @Override
151     public void run() {
152       tick();
153     }
154   };
155 
FileVideoCapturer(String inputFile)156   public FileVideoCapturer(String inputFile) throws IOException {
157     try {
158       videoReader = new VideoReaderY4M(inputFile);
159     } catch (IOException e) {
160       Logging.d(TAG, "Could not open video file: " + inputFile);
161       throw e;
162     }
163   }
164 
tick()165   public void tick() {
166     VideoFrame videoFrame = videoReader.getNextFrame();
167     capturerObserver.onFrameCaptured(videoFrame);
168     videoFrame.release();
169   }
170 
171   @Override
initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext, CapturerObserver capturerObserver)172   public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
173       CapturerObserver capturerObserver) {
174     this.capturerObserver = capturerObserver;
175   }
176 
177   @Override
startCapture(int width, int height, int framerate)178   public void startCapture(int width, int height, int framerate) {
179     timer.schedule(tickTask, 0, 1000 / framerate);
180   }
181 
182   @Override
stopCapture()183   public void stopCapture() throws InterruptedException {
184     timer.cancel();
185   }
186 
187   @Override
changeCaptureFormat(int width, int height, int framerate)188   public void changeCaptureFormat(int width, int height, int framerate) {
189     // Empty on purpose
190   }
191 
192   @Override
dispose()193   public void dispose() {
194     videoReader.close();
195   }
196 
197   @Override
isScreencast()198   public boolean isScreencast() {
199     return false;
200   }
201 }
202