webrtc/talk/media/devices/filevideocapturer.cc

384 lines
13 KiB
C++
Raw Normal View History

// libjingle
// Copyright 2004 Google Inc.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// 3. The name of the author may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Implementation of VideoRecorder and FileVideoCapturer.
#include "talk/media/devices/filevideocapturer.h"
#include "talk/base/bytebuffer.h"
#include "talk/base/criticalsection.h"
#include "talk/base/logging.h"
#include "talk/base/thread.h"
namespace cricket {
/////////////////////////////////////////////////////////////////////
// Implementation of class VideoRecorder
/////////////////////////////////////////////////////////////////////
bool VideoRecorder::Start(const std::string& filename, bool write_header) {
Stop();
write_header_ = write_header;
int err;
if (!video_file_.Open(filename, "wb", &err)) {
LOG(LS_ERROR) << "Unable to open file " << filename << " err=" << err;
return false;
}
return true;
}
void VideoRecorder::Stop() {
video_file_.Close();
}
bool VideoRecorder::RecordFrame(const CapturedFrame& frame) {
if (talk_base::SS_CLOSED == video_file_.GetState()) {
LOG(LS_ERROR) << "File not opened yet";
return false;
}
uint32 size = 0;
if (!frame.GetDataSize(&size)) {
LOG(LS_ERROR) << "Unable to calculate the data size of the frame";
return false;
}
if (write_header_) {
// Convert the frame header to bytebuffer.
talk_base::ByteBuffer buffer;
buffer.WriteUInt32(frame.width);
buffer.WriteUInt32(frame.height);
buffer.WriteUInt32(frame.fourcc);
buffer.WriteUInt32(frame.pixel_width);
buffer.WriteUInt32(frame.pixel_height);
buffer.WriteUInt64(frame.elapsed_time);
buffer.WriteUInt64(frame.time_stamp);
buffer.WriteUInt32(size);
// Write the bytebuffer to file.
if (talk_base::SR_SUCCESS != video_file_.Write(buffer.Data(),
buffer.Length(),
NULL,
NULL)) {
LOG(LS_ERROR) << "Failed to write frame header";
return false;
}
}
// Write the frame data to file.
if (talk_base::SR_SUCCESS != video_file_.Write(frame.data,
size,
NULL,
NULL)) {
LOG(LS_ERROR) << "Failed to write frame data";
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////////
// Definition of private class FileReadThread that periodically reads
// frames from a file.
///////////////////////////////////////////////////////////////////////
class FileVideoCapturer::FileReadThread
: public talk_base::Thread, public talk_base::MessageHandler {
public:
explicit FileReadThread(FileVideoCapturer* capturer)
: capturer_(capturer),
finished_(false) {
}
virtual ~FileReadThread() {
Stop();
}
// Override virtual method of parent Thread. Context: Worker Thread.
virtual void Run() {
// Read the first frame and start the message pump. The pump runs until
// Stop() is called externally or Quit() is called by OnMessage().
int waiting_time_ms = 0;
if (capturer_ && capturer_->ReadFrame(true, &waiting_time_ms)) {
PostDelayed(waiting_time_ms, this);
Thread::Run();
}
talk_base::CritScope cs(&crit_);
finished_ = true;
}
// Override virtual method of parent MessageHandler. Context: Worker Thread.
virtual void OnMessage(talk_base::Message* /*pmsg*/) {
int waiting_time_ms = 0;
if (capturer_ && capturer_->ReadFrame(false, &waiting_time_ms)) {
PostDelayed(waiting_time_ms, this);
} else {
Quit();
}
}
// Check if Run() is finished.
bool Finished() const {
talk_base::CritScope cs(&crit_);
return finished_;
}
private:
FileVideoCapturer* capturer_;
mutable talk_base::CriticalSection crit_;
bool finished_;
DISALLOW_COPY_AND_ASSIGN(FileReadThread);
};
/////////////////////////////////////////////////////////////////////
// Implementation of class FileVideoCapturer
/////////////////////////////////////////////////////////////////////
static const int64 kNumNanoSecsPerMilliSec = 1000000;
const char* FileVideoCapturer::kVideoFileDevicePrefix = "video-file:";
FileVideoCapturer::FileVideoCapturer()
: frame_buffer_size_(0),
file_read_thread_(NULL),
repeat_(0),
start_time_ns_(0),
last_frame_timestamp_ns_(0),
ignore_framerate_(false) {
}
FileVideoCapturer::~FileVideoCapturer() {
Stop();
delete[] static_cast<char*>(captured_frame_.data);
}
bool FileVideoCapturer::Init(const Device& device) {
if (!FileVideoCapturer::IsFileVideoCapturerDevice(device)) {
return false;
}
std::string filename(device.name);
if (IsRunning()) {
LOG(LS_ERROR) << "The file video capturer is already running";
return false;
}
// Open the file.
int err;
if (!video_file_.Open(filename, "rb", &err)) {
LOG(LS_ERROR) << "Unable to open the file " << filename << " err=" << err;
return false;
}
// Read the first frame's header to determine the supported format.
CapturedFrame frame;
if (talk_base::SR_SUCCESS != ReadFrameHeader(&frame)) {
LOG(LS_ERROR) << "Failed to read the first frame header";
video_file_.Close();
return false;
}
// Seek back to the start of the file.
if (!video_file_.SetPosition(0)) {
LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
video_file_.Close();
return false;
}
// Enumerate the supported formats. We have only one supported format. We set
// the frame interval to kMinimumInterval here. In Start(), if the capture
// format's interval is greater than kMinimumInterval, we use the interval;
// otherwise, we use the timestamp in the file to control the interval.
VideoFormat format(frame.width, frame.height, VideoFormat::kMinimumInterval,
frame.fourcc);
std::vector<VideoFormat> supported;
supported.push_back(format);
// TODO(thorcarpenter): Report the actual file video format as the supported
// format. Do not use kMinimumInterval as it conflicts with video adaptation.
SetId(device.id);
SetSupportedFormats(supported);
// TODO(wuwang): Design an E2E integration test for video adaptation,
// then remove the below call to disable the video adapter.
set_enable_video_adapter(false);
return true;
}
bool FileVideoCapturer::Init(const std::string& filename) {
return Init(FileVideoCapturer::CreateFileVideoCapturerDevice(filename));
}
CaptureState FileVideoCapturer::Start(const VideoFormat& capture_format) {
if (IsRunning()) {
LOG(LS_ERROR) << "The file video capturer is already running";
return CS_FAILED;
}
if (talk_base::SS_CLOSED == video_file_.GetState()) {
LOG(LS_ERROR) << "File not opened yet";
return CS_NO_DEVICE;
} else if (!video_file_.SetPosition(0)) {
LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
return CS_FAILED;
}
SetCaptureFormat(&capture_format);
// Create a thread to read the file.
file_read_thread_ = new FileReadThread(this);
start_time_ns_ = kNumNanoSecsPerMilliSec *
static_cast<int64>(talk_base::Time());
bool ret = file_read_thread_->Start();
if (ret) {
LOG(LS_INFO) << "File video capturer '" << GetId() << "' started";
return CS_RUNNING;
} else {
LOG(LS_ERROR) << "File video capturer '" << GetId() << "' failed to start";
return CS_FAILED;
}
}
bool FileVideoCapturer::IsRunning() {
return file_read_thread_ && !file_read_thread_->Finished();
}
void FileVideoCapturer::Stop() {
if (file_read_thread_) {
file_read_thread_->Stop();
file_read_thread_ = NULL;
LOG(LS_INFO) << "File video capturer '" << GetId() << "' stopped";
}
SetCaptureFormat(NULL);
}
bool FileVideoCapturer::GetPreferredFourccs(std::vector<uint32>* fourccs) {
if (!fourccs) {
return false;
}
fourccs->push_back(GetSupportedFormats()->at(0).fourcc);
return true;
}
talk_base::StreamResult FileVideoCapturer::ReadFrameHeader(
CapturedFrame* frame) {
// We first read kFrameHeaderSize bytes from the file stream to a memory
// buffer, then construct a bytebuffer from the memory buffer, and finally
// read the frame header from the bytebuffer.
char header[CapturedFrame::kFrameHeaderSize];
talk_base::StreamResult sr;
size_t bytes_read;
int error;
sr = video_file_.Read(header,
CapturedFrame::kFrameHeaderSize,
&bytes_read,
&error);
LOG(LS_VERBOSE) << "Read frame header: stream_result = " << sr
<< ", bytes read = " << bytes_read << ", error = " << error;
if (talk_base::SR_SUCCESS == sr) {
if (CapturedFrame::kFrameHeaderSize != bytes_read) {
return talk_base::SR_EOS;
}
talk_base::ByteBuffer buffer(header, CapturedFrame::kFrameHeaderSize);
buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->width));
buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->height));
buffer.ReadUInt32(&frame->fourcc);
buffer.ReadUInt32(&frame->pixel_width);
buffer.ReadUInt32(&frame->pixel_height);
buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->elapsed_time));
buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->time_stamp));
buffer.ReadUInt32(&frame->data_size);
}
return sr;
}
// Executed in the context of FileReadThread.
bool FileVideoCapturer::ReadFrame(bool first_frame, int* wait_time_ms) {
uint32 start_read_time_ms = talk_base::Time();
// 1. Signal the previously read frame to downstream.
if (!first_frame) {
captured_frame_.time_stamp = kNumNanoSecsPerMilliSec *
static_cast<int64>(start_read_time_ms);
captured_frame_.elapsed_time = captured_frame_.time_stamp - start_time_ns_;
SignalFrameCaptured(this, &captured_frame_);
}
// 2. Read the next frame.
if (talk_base::SS_CLOSED == video_file_.GetState()) {
LOG(LS_ERROR) << "File not opened yet";
return false;
}
// 2.1 Read the frame header.
talk_base::StreamResult result = ReadFrameHeader(&captured_frame_);
if (talk_base::SR_EOS == result) { // Loop back if repeat.
if (repeat_ != talk_base::kForever) {
if (repeat_ > 0) {
--repeat_;
} else {
return false;
}
}
if (video_file_.SetPosition(0)) {
result = ReadFrameHeader(&captured_frame_);
}
}
if (talk_base::SR_SUCCESS != result) {
LOG(LS_ERROR) << "Failed to read the frame header";
return false;
}
// 2.2 Reallocate memory for the frame data if necessary.
if (frame_buffer_size_ < captured_frame_.data_size) {
frame_buffer_size_ = captured_frame_.data_size;
delete[] static_cast<char*>(captured_frame_.data);
captured_frame_.data = new char[frame_buffer_size_];
}
// 2.3 Read the frame adata.
if (talk_base::SR_SUCCESS != video_file_.Read(captured_frame_.data,
captured_frame_.data_size,
NULL, NULL)) {
LOG(LS_ERROR) << "Failed to read frame data";
return false;
}
// 3. Decide how long to wait for the next frame.
*wait_time_ms = 0;
// If the capture format's interval is not kMinimumInterval, we use it to
// control the rate; otherwise, we use the timestamp in the file to control
// the rate.
if (!first_frame && !ignore_framerate_) {
int64 interval_ns =
GetCaptureFormat()->interval > VideoFormat::kMinimumInterval ?
GetCaptureFormat()->interval :
captured_frame_.time_stamp - last_frame_timestamp_ns_;
int interval_ms = static_cast<int>(interval_ns / kNumNanoSecsPerMilliSec);
interval_ms -= talk_base::Time() - start_read_time_ms;
if (interval_ms > 0) {
*wait_time_ms = interval_ms;
}
}
// Keep the original timestamp read from the file.
last_frame_timestamp_ns_ = captured_frame_.time_stamp;
return true;
}
} // namespace cricket