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 ///////////////////////////////////////////////////////////////////// 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 51 void VideoRecorder::Stop() { 52 video_file_.Close(); 53 } 54 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: 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 talk_base::CritScope cs(&crit_); 127 finished_ = true; 128 } 129 130 // Override virtual method of parent MessageHandler. Context: Worker Thread. 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. 141 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 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 169 FileVideoCapturer::~FileVideoCapturer() { 170 Stop(); 171 delete[] static_cast<char*>(captured_frame_.data); 172 } 173 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 SetId(device.id); 213 SetSupportedFormats(supported); 214 return true; 215 } 216 217 bool FileVideoCapturer::Init(const std::string& filename) { 218 return Init(FileVideoCapturer::CreateFileVideoCapturerDevice(filename)); 219 } 220 221 CaptureState FileVideoCapturer::Start(const VideoFormat& capture_format) { 222 if (IsRunning()) { 223 LOG(LS_ERROR) << "The file video capturer is already running"; 224 return CS_FAILED; 225 } 226 227 if (talk_base::SS_CLOSED == video_file_.GetState()) { 228 LOG(LS_ERROR) << "File not opened yet"; 229 return CS_NO_DEVICE; 230 } else if (!video_file_.SetPosition(0)) { 231 LOG(LS_ERROR) << "Failed to seek back to beginning of the file"; 232 return CS_FAILED; 233 } 234 235 SetCaptureFormat(&capture_format); 236 // Create a thread to read the file. 237 file_read_thread_ = new FileReadThread(this); 238 start_time_ns_ = kNumNanoSecsPerMilliSec * 239 static_cast<int64>(talk_base::Time()); 240 bool ret = file_read_thread_->Start(); 241 if (ret) { 242 LOG(LS_INFO) << "File video capturer '" << GetId() << "' started"; 243 return CS_RUNNING; 244 } else { 245 LOG(LS_ERROR) << "File video capturer '" << GetId() << "' failed to start"; 246 return CS_FAILED; 247 } 248 } 249 250 bool FileVideoCapturer::IsRunning() { 251 return file_read_thread_ && !file_read_thread_->Finished(); 252 } 253 254 void FileVideoCapturer::Stop() { 255 if (file_read_thread_) { 256 file_read_thread_->Stop(); 257 file_read_thread_ = NULL; 258 LOG(LS_INFO) << "File video capturer '" << GetId() << "' stopped"; 259 } 260 SetCaptureFormat(NULL); 261 } 262 263 bool FileVideoCapturer::GetPreferredFourccs(std::vector<uint32>* fourccs) { 264 if (!fourccs) { 265 return false; 266 } 267 268 fourccs->push_back(GetSupportedFormats()->at(0).fourcc); 269 return true; 270 } 271 272 talk_base::StreamResult FileVideoCapturer::ReadFrameHeader( 273 CapturedFrame* frame) { 274 // We first read kFrameHeaderSize bytes from the file stream to a memory 275 // buffer, then construct a bytebuffer from the memory buffer, and finally 276 // read the frame header from the bytebuffer. 277 char header[CapturedFrame::kFrameHeaderSize]; 278 talk_base::StreamResult sr; 279 size_t bytes_read; 280 int error; 281 sr = video_file_.Read(header, 282 CapturedFrame::kFrameHeaderSize, 283 &bytes_read, 284 &error); 285 LOG(LS_VERBOSE) << "Read frame header: stream_result = " << sr 286 << ", bytes read = " << bytes_read << ", error = " << error; 287 if (talk_base::SR_SUCCESS == sr) { 288 if (CapturedFrame::kFrameHeaderSize != bytes_read) { 289 return talk_base::SR_EOS; 290 } 291 talk_base::ByteBuffer buffer(header, CapturedFrame::kFrameHeaderSize); 292 buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->width)); 293 buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->height)); 294 buffer.ReadUInt32(&frame->fourcc); 295 buffer.ReadUInt32(&frame->pixel_width); 296 buffer.ReadUInt32(&frame->pixel_height); 297 buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->elapsed_time)); 298 buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->time_stamp)); 299 buffer.ReadUInt32(&frame->data_size); 300 } 301 302 return sr; 303 } 304 305 // Executed in the context of FileReadThread. 306 bool FileVideoCapturer::ReadFrame(bool first_frame, int* wait_time_ms) { 307 uint32 start_read_time_ms = talk_base::Time(); 308 309 // 1. Signal the previously read frame to downstream. 310 if (!first_frame) { 311 captured_frame_.time_stamp = kNumNanoSecsPerMilliSec * 312 static_cast<int64>(start_read_time_ms); 313 captured_frame_.elapsed_time = captured_frame_.time_stamp - start_time_ns_; 314 SignalFrameCaptured(this, &captured_frame_); 315 } 316 317 // 2. Read the next frame. 318 if (talk_base::SS_CLOSED == video_file_.GetState()) { 319 LOG(LS_ERROR) << "File not opened yet"; 320 return false; 321 } 322 // 2.1 Read the frame header. 323 talk_base::StreamResult result = ReadFrameHeader(&captured_frame_); 324 if (talk_base::SR_EOS == result) { // Loop back if repeat. 325 if (repeat_ != talk_base::kForever) { 326 if (repeat_ > 0) { 327 --repeat_; 328 } else { 329 return false; 330 } 331 } 332 333 if (video_file_.SetPosition(0)) { 334 result = ReadFrameHeader(&captured_frame_); 335 } 336 } 337 if (talk_base::SR_SUCCESS != result) { 338 LOG(LS_ERROR) << "Failed to read the frame header"; 339 return false; 340 } 341 // 2.2 Reallocate memory for the frame data if necessary. 342 if (frame_buffer_size_ < captured_frame_.data_size) { 343 frame_buffer_size_ = captured_frame_.data_size; 344 delete[] static_cast<char*>(captured_frame_.data); 345 captured_frame_.data = new char[frame_buffer_size_]; 346 } 347 // 2.3 Read the frame adata. 348 if (talk_base::SR_SUCCESS != video_file_.Read(captured_frame_.data, 349 captured_frame_.data_size, 350 NULL, NULL)) { 351 LOG(LS_ERROR) << "Failed to read frame data"; 352 return false; 353 } 354 355 // 3. Decide how long to wait for the next frame. 356 *wait_time_ms = 0; 357 358 // If the capture format's interval is not kMinimumInterval, we use it to 359 // control the rate; otherwise, we use the timestamp in the file to control 360 // the rate. 361 if (!first_frame && !ignore_framerate_) { 362 int64 interval_ns = 363 GetCaptureFormat()->interval > VideoFormat::kMinimumInterval ? 364 GetCaptureFormat()->interval : 365 captured_frame_.time_stamp - last_frame_timestamp_ns_; 366 int interval_ms = static_cast<int>(interval_ns / kNumNanoSecsPerMilliSec); 367 interval_ms -= talk_base::Time() - start_read_time_ms; 368 if (interval_ms > 0) { 369 *wait_time_ms = interval_ms; 370 } 371 } 372 // Keep the original timestamp read from the file. 373 last_frame_timestamp_ns_ = captured_frame_.time_stamp; 374 return true; 375 } 376 377 } // namespace cricket 378