• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "talk/base/bytebuffer.h"
31 #include "talk/base/criticalsection.h"
32 #include "talk/base/logging.h"
33 #include "talk/base/thread.h"
34 
35 namespace cricket {
36 
37 /////////////////////////////////////////////////////////////////////
38 // Implementation of class VideoRecorder
39 /////////////////////////////////////////////////////////////////////
Start(const std::string & filename,bool write_header)40 bool 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 
Stop()51 void VideoRecorder::Stop() {
52   video_file_.Close();
53 }
54 
RecordFrame(const CapturedFrame & frame)55 bool VideoRecorder::RecordFrame(const CapturedFrame& frame) {
56   if (talk_base::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     talk_base::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 (talk_base::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 (talk_base::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 ///////////////////////////////////////////////////////////////////////
104 class FileVideoCapturer::FileReadThread
105     : public talk_base::Thread, public talk_base::MessageHandler {
106  public:
FileReadThread(FileVideoCapturer * capturer)107   explicit FileReadThread(FileVideoCapturer* capturer)
108       : capturer_(capturer),
109         finished_(false) {
110   }
111 
~FileReadThread()112   virtual ~FileReadThread() {
113     Stop();
114   }
115 
116   // Override virtual method of parent Thread. Context: Worker Thread.
Run()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     talk_base::CritScope cs(&crit_);
127     finished_ = true;
128   }
129 
130   // Override virtual method of parent MessageHandler. Context: Worker Thread.
OnMessage(talk_base::Message *)131   virtual void OnMessage(talk_base::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.
Finished() const141   bool Finished() const {
142     talk_base::CritScope cs(&crit_);
143     return finished_;
144   }
145 
146  private:
147   FileVideoCapturer* capturer_;
148   mutable talk_base::CriticalSection crit_;
149   bool finished_;
150 
151   DISALLOW_COPY_AND_ASSIGN(FileReadThread);
152 };
153 
154 /////////////////////////////////////////////////////////////////////
155 // Implementation of class FileVideoCapturer
156 /////////////////////////////////////////////////////////////////////
157 static const int64 kNumNanoSecsPerMilliSec = 1000000;
158 const char* FileVideoCapturer::kVideoFileDevicePrefix = "video-file:";
159 
FileVideoCapturer()160 FileVideoCapturer::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 
~FileVideoCapturer()169 FileVideoCapturer::~FileVideoCapturer() {
170   Stop();
171   delete[] static_cast<char*>(captured_frame_.data);
172 }
173 
Init(const Device & device)174 bool 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 (talk_base::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 
Init(const std::string & filename)223 bool FileVideoCapturer::Init(const std::string& filename) {
224   return Init(FileVideoCapturer::CreateFileVideoCapturerDevice(filename));
225 }
226 
Start(const VideoFormat & capture_format)227 CaptureState 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 (talk_base::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>(talk_base::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 
IsRunning()256 bool FileVideoCapturer::IsRunning() {
257   return file_read_thread_ && !file_read_thread_->Finished();
258 }
259 
Stop()260 void 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 
GetPreferredFourccs(std::vector<uint32> * fourccs)269 bool 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 
ReadFrameHeader(CapturedFrame * frame)278 talk_base::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   talk_base::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 (talk_base::SR_SUCCESS == sr) {
294     if (CapturedFrame::kFrameHeaderSize != bytes_read) {
295       return talk_base::SR_EOS;
296     }
297     talk_base::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.
ReadFrame(bool first_frame,int * wait_time_ms)312 bool FileVideoCapturer::ReadFrame(bool first_frame, int* wait_time_ms) {
313   uint32 start_read_time_ms = talk_base::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 (talk_base::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   talk_base::StreamResult result = ReadFrameHeader(&captured_frame_);
330   if (talk_base::SR_EOS == result) {  // Loop back if repeat.
331     if (repeat_ != talk_base::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 (talk_base::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 (talk_base::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 -= talk_base::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