filevideocapturer.cc revision 5f93d0a140515e3b8cdd1b9a4c6f5871144e5dee
1/*
2 * libjingle
3 * Copyright 2004 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28// Implementation of VideoRecorder and FileVideoCapturer.
29
30#include "talk/media/devices/filevideocapturer.h"
31
32#include "webrtc/base/bytebuffer.h"
33#include "webrtc/base/criticalsection.h"
34#include "webrtc/base/logging.h"
35#include "webrtc/base/thread.h"
36
37namespace cricket {
38
39/////////////////////////////////////////////////////////////////////
40// Implementation of class VideoRecorder
41/////////////////////////////////////////////////////////////////////
42bool VideoRecorder::Start(const std::string& filename, bool write_header) {
43  Stop();
44  write_header_ = write_header;
45  int err;
46  if (!video_file_.Open(filename, "wb", &err)) {
47    LOG(LS_ERROR) << "Unable to open file " << filename << " err=" << err;
48    return false;
49  }
50  return true;
51}
52
53void VideoRecorder::Stop() {
54  video_file_.Close();
55}
56
57bool VideoRecorder::RecordFrame(const CapturedFrame& frame) {
58  if (rtc::SS_CLOSED == video_file_.GetState()) {
59    LOG(LS_ERROR) << "File not opened yet";
60    return false;
61  }
62
63  uint32 size = 0;
64  if (!frame.GetDataSize(&size)) {
65    LOG(LS_ERROR) << "Unable to calculate the data size of the frame";
66    return false;
67  }
68
69  if (write_header_) {
70    // Convert the frame header to bytebuffer.
71    rtc::ByteBuffer buffer;
72    buffer.WriteUInt32(frame.width);
73    buffer.WriteUInt32(frame.height);
74    buffer.WriteUInt32(frame.fourcc);
75    buffer.WriteUInt32(frame.pixel_width);
76    buffer.WriteUInt32(frame.pixel_height);
77    buffer.WriteUInt64(frame.elapsed_time);
78    buffer.WriteUInt64(frame.time_stamp);
79    buffer.WriteUInt32(size);
80
81    // Write the bytebuffer to file.
82    if (rtc::SR_SUCCESS != video_file_.Write(buffer.Data(),
83                                                   buffer.Length(),
84                                                   NULL,
85                                                   NULL)) {
86      LOG(LS_ERROR) << "Failed to write frame header";
87      return false;
88    }
89  }
90  // Write the frame data to file.
91  if (rtc::SR_SUCCESS != video_file_.Write(frame.data,
92                                                 size,
93                                                 NULL,
94                                                 NULL)) {
95    LOG(LS_ERROR) << "Failed to write frame data";
96    return false;
97  }
98
99  return true;
100}
101
102///////////////////////////////////////////////////////////////////////
103// Definition of private class FileReadThread that periodically reads
104// frames from a file.
105///////////////////////////////////////////////////////////////////////
106class FileVideoCapturer::FileReadThread
107    : public rtc::Thread, public rtc::MessageHandler {
108 public:
109  explicit FileReadThread(FileVideoCapturer* capturer)
110      : capturer_(capturer),
111        finished_(false) {
112  }
113
114  virtual ~FileReadThread() {
115    Stop();
116  }
117
118  // Override virtual method of parent Thread. Context: Worker Thread.
119  virtual void Run() {
120    // Read the first frame and start the message pump. The pump runs until
121    // Stop() is called externally or Quit() is called by OnMessage().
122    int waiting_time_ms = 0;
123    if (capturer_ && capturer_->ReadFrame(true, &waiting_time_ms)) {
124      PostDelayed(waiting_time_ms, this);
125      Thread::Run();
126    }
127
128    rtc::CritScope cs(&crit_);
129    finished_ = true;
130  }
131
132  // Override virtual method of parent MessageHandler. Context: Worker Thread.
133  virtual void OnMessage(rtc::Message* /*pmsg*/) {
134    int waiting_time_ms = 0;
135    if (capturer_ && capturer_->ReadFrame(false, &waiting_time_ms)) {
136      PostDelayed(waiting_time_ms, this);
137    } else {
138      Quit();
139    }
140  }
141
142  // Check if Run() is finished.
143  bool Finished() const {
144    rtc::CritScope cs(&crit_);
145    return finished_;
146  }
147
148 private:
149  FileVideoCapturer* capturer_;
150  mutable rtc::CriticalSection crit_;
151  bool finished_;
152
153  DISALLOW_COPY_AND_ASSIGN(FileReadThread);
154};
155
156/////////////////////////////////////////////////////////////////////
157// Implementation of class FileVideoCapturer
158/////////////////////////////////////////////////////////////////////
159static const int64 kNumNanoSecsPerMilliSec = 1000000;
160const char* FileVideoCapturer::kVideoFileDevicePrefix = "video-file:";
161
162FileVideoCapturer::FileVideoCapturer()
163    : frame_buffer_size_(0),
164      file_read_thread_(NULL),
165      repeat_(0),
166      start_time_ns_(0),
167      last_frame_timestamp_ns_(0),
168      ignore_framerate_(false) {
169}
170
171FileVideoCapturer::~FileVideoCapturer() {
172  Stop();
173  delete[] static_cast<char*>(captured_frame_.data);
174}
175
176bool FileVideoCapturer::Init(const Device& device) {
177  if (!FileVideoCapturer::IsFileVideoCapturerDevice(device)) {
178    return false;
179  }
180  std::string filename(device.name);
181  if (IsRunning()) {
182    LOG(LS_ERROR) << "The file video capturer is already running";
183    return false;
184  }
185  // Open the file.
186  int err;
187  if (!video_file_.Open(filename, "rb", &err)) {
188    LOG(LS_ERROR) << "Unable to open the file " << filename << " err=" << err;
189    return false;
190  }
191  // Read the first frame's header to determine the supported format.
192  CapturedFrame frame;
193  if (rtc::SR_SUCCESS != ReadFrameHeader(&frame)) {
194    LOG(LS_ERROR) << "Failed to read the first frame header";
195    video_file_.Close();
196    return false;
197  }
198  // Seek back to the start of the file.
199  if (!video_file_.SetPosition(0)) {
200    LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
201    video_file_.Close();
202    return false;
203  }
204
205  // Enumerate the supported formats. We have only one supported format. We set
206  // the frame interval to kMinimumInterval here. In Start(), if the capture
207  // format's interval is greater than kMinimumInterval, we use the interval;
208  // otherwise, we use the timestamp in the file to control the interval.
209  VideoFormat format(frame.width, frame.height, VideoFormat::kMinimumInterval,
210                     frame.fourcc);
211  std::vector<VideoFormat> supported;
212  supported.push_back(format);
213
214  // TODO(thorcarpenter): Report the actual file video format as the supported
215  // format. Do not use kMinimumInterval as it conflicts with video adaptation.
216  SetId(device.id);
217  SetSupportedFormats(supported);
218
219  // TODO(wuwang): Design an E2E integration test for video adaptation,
220  // then remove the below call to disable the video adapter.
221  set_enable_video_adapter(false);
222  return true;
223}
224
225bool FileVideoCapturer::Init(const std::string& filename) {
226  return Init(FileVideoCapturer::CreateFileVideoCapturerDevice(filename));
227}
228
229CaptureState FileVideoCapturer::Start(const VideoFormat& capture_format) {
230  if (IsRunning()) {
231    LOG(LS_ERROR) << "The file video capturer is already running";
232    return CS_FAILED;
233  }
234
235  if (rtc::SS_CLOSED == video_file_.GetState()) {
236    LOG(LS_ERROR) << "File not opened yet";
237    return CS_NO_DEVICE;
238  } else if (!video_file_.SetPosition(0)) {
239    LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
240    return CS_FAILED;
241  }
242
243  SetCaptureFormat(&capture_format);
244  // Create a thread to read the file.
245  file_read_thread_ = new FileReadThread(this);
246  start_time_ns_ = kNumNanoSecsPerMilliSec *
247      static_cast<int64>(rtc::Time());
248  bool ret = file_read_thread_->Start();
249  if (ret) {
250    LOG(LS_INFO) << "File video capturer '" << GetId() << "' started";
251    return CS_RUNNING;
252  } else {
253    LOG(LS_ERROR) << "File video capturer '" << GetId() << "' failed to start";
254    return CS_FAILED;
255  }
256}
257
258bool FileVideoCapturer::IsRunning() {
259  return file_read_thread_ && !file_read_thread_->Finished();
260}
261
262void FileVideoCapturer::Stop() {
263  if (file_read_thread_) {
264    file_read_thread_->Stop();
265    file_read_thread_ = NULL;
266    LOG(LS_INFO) << "File video capturer '" << GetId() << "' stopped";
267  }
268  SetCaptureFormat(NULL);
269}
270
271bool FileVideoCapturer::GetPreferredFourccs(std::vector<uint32>* fourccs) {
272  if (!fourccs) {
273    return false;
274  }
275
276  fourccs->push_back(GetSupportedFormats()->at(0).fourcc);
277  return true;
278}
279
280rtc::StreamResult FileVideoCapturer::ReadFrameHeader(
281    CapturedFrame* frame) {
282  // We first read kFrameHeaderSize bytes from the file stream to a memory
283  // buffer, then construct a bytebuffer from the memory buffer, and finally
284  // read the frame header from the bytebuffer.
285  char header[CapturedFrame::kFrameHeaderSize];
286  rtc::StreamResult sr;
287  size_t bytes_read;
288  int error;
289  sr = video_file_.Read(header,
290                        CapturedFrame::kFrameHeaderSize,
291                        &bytes_read,
292                        &error);
293  LOG(LS_VERBOSE) << "Read frame header: stream_result = " << sr
294                  << ", bytes read = " << bytes_read << ", error = " << error;
295  if (rtc::SR_SUCCESS == sr) {
296    if (CapturedFrame::kFrameHeaderSize != bytes_read) {
297      return rtc::SR_EOS;
298    }
299    rtc::ByteBuffer buffer(header, CapturedFrame::kFrameHeaderSize);
300    buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->width));
301    buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->height));
302    buffer.ReadUInt32(&frame->fourcc);
303    buffer.ReadUInt32(&frame->pixel_width);
304    buffer.ReadUInt32(&frame->pixel_height);
305    buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->elapsed_time));
306    buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->time_stamp));
307    buffer.ReadUInt32(&frame->data_size);
308  }
309
310  return sr;
311}
312
313// Executed in the context of FileReadThread.
314bool FileVideoCapturer::ReadFrame(bool first_frame, int* wait_time_ms) {
315  uint32 start_read_time_ms = rtc::Time();
316
317  // 1. Signal the previously read frame to downstream.
318  if (!first_frame) {
319    captured_frame_.time_stamp = kNumNanoSecsPerMilliSec *
320        static_cast<int64>(start_read_time_ms);
321    captured_frame_.elapsed_time = captured_frame_.time_stamp - start_time_ns_;
322    SignalFrameCaptured(this, &captured_frame_);
323  }
324
325  // 2. Read the next frame.
326  if (rtc::SS_CLOSED == video_file_.GetState()) {
327    LOG(LS_ERROR) << "File not opened yet";
328    return false;
329  }
330  // 2.1 Read the frame header.
331  rtc::StreamResult result = ReadFrameHeader(&captured_frame_);
332  if (rtc::SR_EOS == result) {  // Loop back if repeat.
333    if (repeat_ != rtc::kForever) {
334      if (repeat_ > 0) {
335        --repeat_;
336      } else {
337        return false;
338      }
339    }
340
341    if (video_file_.SetPosition(0)) {
342      result = ReadFrameHeader(&captured_frame_);
343    }
344  }
345  if (rtc::SR_SUCCESS != result) {
346    LOG(LS_ERROR) << "Failed to read the frame header";
347    return false;
348  }
349  // 2.2 Reallocate memory for the frame data if necessary.
350  if (frame_buffer_size_ < captured_frame_.data_size) {
351    frame_buffer_size_ = captured_frame_.data_size;
352    delete[] static_cast<char*>(captured_frame_.data);
353    captured_frame_.data = new char[frame_buffer_size_];
354  }
355  // 2.3 Read the frame adata.
356  if (rtc::SR_SUCCESS != video_file_.Read(captured_frame_.data,
357                                                captured_frame_.data_size,
358                                                NULL, NULL)) {
359    LOG(LS_ERROR) << "Failed to read frame data";
360    return false;
361  }
362
363  // 3. Decide how long to wait for the next frame.
364  *wait_time_ms = 0;
365
366  // If the capture format's interval is not kMinimumInterval, we use it to
367  // control the rate; otherwise, we use the timestamp in the file to control
368  // the rate.
369  if (!first_frame && !ignore_framerate_) {
370    int64 interval_ns =
371        GetCaptureFormat()->interval > VideoFormat::kMinimumInterval ?
372        GetCaptureFormat()->interval :
373        captured_frame_.time_stamp - last_frame_timestamp_ns_;
374    int interval_ms = static_cast<int>(interval_ns / kNumNanoSecsPerMilliSec);
375    interval_ms -= rtc::Time() - start_read_time_ms;
376    if (interval_ms > 0) {
377      *wait_time_ms = interval_ms;
378    }
379  }
380  // Keep the original timestamp read from the file.
381  last_frame_timestamp_ns_ = captured_frame_.time_stamp;
382  return true;
383}
384
385}  // namespace cricket
386