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_t 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 // Elapsed time is deprecated. 78 const uint64_t dummy_elapsed_time = 0; 79 buffer.WriteUInt64(dummy_elapsed_time); 80 buffer.WriteUInt64(frame.time_stamp); 81 buffer.WriteUInt32(size); 82 83 // Write the bytebuffer to file. 84 if (rtc::SR_SUCCESS != video_file_.Write(buffer.Data(), 85 buffer.Length(), 86 NULL, 87 NULL)) { 88 LOG(LS_ERROR) << "Failed to write frame header"; 89 return false; 90 } 91 } 92 // Write the frame data to file. 93 if (rtc::SR_SUCCESS != video_file_.Write(frame.data, 94 size, 95 NULL, 96 NULL)) { 97 LOG(LS_ERROR) << "Failed to write frame data"; 98 return false; 99 } 100 101 return true; 102} 103 104/////////////////////////////////////////////////////////////////////// 105// Definition of private class FileReadThread that periodically reads 106// frames from a file. 107/////////////////////////////////////////////////////////////////////// 108class FileVideoCapturer::FileReadThread 109 : public rtc::Thread, public rtc::MessageHandler { 110 public: 111 explicit FileReadThread(FileVideoCapturer* capturer) 112 : capturer_(capturer), 113 finished_(false) { 114 } 115 116 virtual ~FileReadThread() { 117 Stop(); 118 } 119 120 // Override virtual method of parent Thread. Context: Worker Thread. 121 virtual void Run() { 122 // Read the first frame and start the message pump. The pump runs until 123 // Stop() is called externally or Quit() is called by OnMessage(). 124 int waiting_time_ms = 0; 125 if (capturer_ && capturer_->ReadFrame(true, &waiting_time_ms)) { 126 PostDelayed(waiting_time_ms, this); 127 Thread::Run(); 128 } 129 130 rtc::CritScope cs(&crit_); 131 finished_ = true; 132 } 133 134 // Override virtual method of parent MessageHandler. Context: Worker Thread. 135 virtual void OnMessage(rtc::Message* /*pmsg*/) { 136 int waiting_time_ms = 0; 137 if (capturer_ && capturer_->ReadFrame(false, &waiting_time_ms)) { 138 PostDelayed(waiting_time_ms, this); 139 } else { 140 Quit(); 141 } 142 } 143 144 // Check if Run() is finished. 145 bool Finished() const { 146 rtc::CritScope cs(&crit_); 147 return finished_; 148 } 149 150 private: 151 FileVideoCapturer* capturer_; 152 mutable rtc::CriticalSection crit_; 153 bool finished_; 154 155 RTC_DISALLOW_COPY_AND_ASSIGN(FileReadThread); 156}; 157 158///////////////////////////////////////////////////////////////////// 159// Implementation of class FileVideoCapturer 160///////////////////////////////////////////////////////////////////// 161static const int64_t kNumNanoSecsPerMilliSec = 1000000; 162const char* FileVideoCapturer::kVideoFileDevicePrefix = "video-file:"; 163 164FileVideoCapturer::FileVideoCapturer() 165 : frame_buffer_size_(0), 166 file_read_thread_(NULL), 167 repeat_(0), 168 last_frame_timestamp_ns_(0), 169 ignore_framerate_(false) { 170} 171 172FileVideoCapturer::~FileVideoCapturer() { 173 Stop(); 174 delete[] static_cast<char*>(captured_frame_.data); 175} 176 177bool FileVideoCapturer::Init(const Device& device) { 178 if (!FileVideoCapturer::IsFileVideoCapturerDevice(device)) { 179 return false; 180 } 181 std::string filename(device.name); 182 if (IsRunning()) { 183 LOG(LS_ERROR) << "The file video capturer is already running"; 184 return false; 185 } 186 // Open the file. 187 int err; 188 if (!video_file_.Open(filename, "rb", &err)) { 189 LOG(LS_ERROR) << "Unable to open the file " << filename << " err=" << err; 190 return false; 191 } 192 // Read the first frame's header to determine the supported format. 193 CapturedFrame frame; 194 if (rtc::SR_SUCCESS != ReadFrameHeader(&frame)) { 195 LOG(LS_ERROR) << "Failed to read the first frame header"; 196 video_file_.Close(); 197 return false; 198 } 199 // Seek back to the start of the file. 200 if (!video_file_.SetPosition(0)) { 201 LOG(LS_ERROR) << "Failed to seek back to beginning of the file"; 202 video_file_.Close(); 203 return false; 204 } 205 206 // Enumerate the supported formats. We have only one supported format. We set 207 // the frame interval to kMinimumInterval here. In Start(), if the capture 208 // format's interval is greater than kMinimumInterval, we use the interval; 209 // otherwise, we use the timestamp in the file to control the interval. 210 VideoFormat format(frame.width, frame.height, VideoFormat::kMinimumInterval, 211 frame.fourcc); 212 std::vector<VideoFormat> supported; 213 supported.push_back(format); 214 215 // TODO(thorcarpenter): Report the actual file video format as the supported 216 // format. Do not use kMinimumInterval as it conflicts with video adaptation. 217 SetId(device.id); 218 SetSupportedFormats(supported); 219 220 // TODO(wuwang): Design an E2E integration test for video adaptation, 221 // then remove the below call to disable the video adapter. 222 set_enable_video_adapter(false); 223 return true; 224} 225 226bool FileVideoCapturer::Init(const std::string& filename) { 227 return Init(FileVideoCapturer::CreateFileVideoCapturerDevice(filename)); 228} 229 230CaptureState FileVideoCapturer::Start(const VideoFormat& capture_format) { 231 if (IsRunning()) { 232 LOG(LS_ERROR) << "The file video capturer is already running"; 233 return CS_FAILED; 234 } 235 236 if (rtc::SS_CLOSED == video_file_.GetState()) { 237 LOG(LS_ERROR) << "File not opened yet"; 238 return CS_NO_DEVICE; 239 } else if (!video_file_.SetPosition(0)) { 240 LOG(LS_ERROR) << "Failed to seek back to beginning of the file"; 241 return CS_FAILED; 242 } 243 244 SetCaptureFormat(&capture_format); 245 // Create a thread to read the file. 246 file_read_thread_ = new FileReadThread(this); 247 bool ret = file_read_thread_->Start(); 248 if (ret) { 249 LOG(LS_INFO) << "File video capturer '" << GetId() << "' started"; 250 return CS_RUNNING; 251 } else { 252 LOG(LS_ERROR) << "File video capturer '" << GetId() << "' failed to start"; 253 return CS_FAILED; 254 } 255} 256 257bool FileVideoCapturer::IsRunning() { 258 return file_read_thread_ && !file_read_thread_->Finished(); 259} 260 261void FileVideoCapturer::Stop() { 262 if (file_read_thread_) { 263 file_read_thread_->Stop(); 264 file_read_thread_ = NULL; 265 LOG(LS_INFO) << "File video capturer '" << GetId() << "' stopped"; 266 } 267 SetCaptureFormat(NULL); 268} 269 270bool FileVideoCapturer::GetPreferredFourccs(std::vector<uint32_t>* fourccs) { 271 if (!fourccs) { 272 return false; 273 } 274 275 fourccs->push_back(GetSupportedFormats()->at(0).fourcc); 276 return true; 277} 278 279rtc::StreamResult FileVideoCapturer::ReadFrameHeader( 280 CapturedFrame* frame) { 281 // We first read kFrameHeaderSize bytes from the file stream to a memory 282 // buffer, then construct a bytebuffer from the memory buffer, and finally 283 // read the frame header from the bytebuffer. 284 char header[CapturedFrame::kFrameHeaderSize]; 285 rtc::StreamResult sr; 286 size_t bytes_read; 287 int error; 288 sr = video_file_.Read(header, 289 CapturedFrame::kFrameHeaderSize, 290 &bytes_read, 291 &error); 292 LOG(LS_VERBOSE) << "Read frame header: stream_result = " << sr 293 << ", bytes read = " << bytes_read << ", error = " << error; 294 if (rtc::SR_SUCCESS == sr) { 295 if (CapturedFrame::kFrameHeaderSize != bytes_read) { 296 return rtc::SR_EOS; 297 } 298 rtc::ByteBuffer buffer(header, CapturedFrame::kFrameHeaderSize); 299 buffer.ReadUInt32(reinterpret_cast<uint32_t*>(&frame->width)); 300 buffer.ReadUInt32(reinterpret_cast<uint32_t*>(&frame->height)); 301 buffer.ReadUInt32(&frame->fourcc); 302 buffer.ReadUInt32(&frame->pixel_width); 303 buffer.ReadUInt32(&frame->pixel_height); 304 // Elapsed time is deprecated. 305 uint64_t dummy_elapsed_time; 306 buffer.ReadUInt64(&dummy_elapsed_time); 307 buffer.ReadUInt64(reinterpret_cast<uint64_t*>(&frame->time_stamp)); 308 buffer.ReadUInt32(&frame->data_size); 309 } 310 311 return sr; 312} 313 314// Executed in the context of FileReadThread. 315bool FileVideoCapturer::ReadFrame(bool first_frame, int* wait_time_ms) { 316 uint32_t start_read_time_ms = rtc::Time(); 317 318 // 1. Signal the previously read frame to downstream. 319 if (!first_frame) { 320 captured_frame_.time_stamp = 321 kNumNanoSecsPerMilliSec * static_cast<int64_t>(start_read_time_ms); 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_ != 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_t 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