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