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