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