Demuxer, MediaStream, File and Status implementations.
Also includes a dummy packager_test with TestingMuxer. Change-Id: I6a6e6dd77e343ef742adc1846ede203993628943
This commit is contained in:
parent
3f3d9a6b76
commit
edf74fc89f
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// DecryptorSource is responsible for decryption key acquisition.
|
||||
|
||||
#ifndef MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
||||
#define MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
||||
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "media/base/container_names.h"
|
||||
#include "media/base/status.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
class DecryptorSource {
|
||||
public:
|
||||
DecryptorSource() {}
|
||||
virtual ~DecryptorSource() {}
|
||||
|
||||
virtual Status OnNeedKey(MediaContainerName container,
|
||||
const std::string& init_data) = 0;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DecryptorSource);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
|
@ -0,0 +1,141 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/demuxer.h"
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "media/base/container_names.h"
|
||||
#include "media/base/decryptor_source.h"
|
||||
#include "media/base/media_stream.h"
|
||||
#include "media/base/stream_info.h"
|
||||
#include "media/file/file.h"
|
||||
#include "media/mp4/mp4_media_parser.h"
|
||||
|
||||
namespace {
|
||||
const int kBufSize = 0x40000; // 256KB.
|
||||
}
|
||||
|
||||
namespace media {
|
||||
|
||||
Demuxer::Demuxer(const std::string& file_name,
|
||||
DecryptorSource* decryptor_source)
|
||||
: decryptor_source_(decryptor_source),
|
||||
file_name_(file_name),
|
||||
media_file_(NULL),
|
||||
init_event_received_(false),
|
||||
buffer_(new uint8[kBufSize]) {}
|
||||
|
||||
Demuxer::~Demuxer() {
|
||||
if (media_file_)
|
||||
media_file_->Close();
|
||||
STLDeleteElements(&streams_);
|
||||
}
|
||||
|
||||
Status Demuxer::Initialize() {
|
||||
DCHECK(media_file_ == NULL);
|
||||
DCHECK(!init_event_received_);
|
||||
|
||||
media_file_ = File::Open(file_name_.c_str(), "r");
|
||||
if (media_file_ == NULL) {
|
||||
return Status(error::FILE_FAILURE,
|
||||
"Cannot open file for read " + file_name_);
|
||||
}
|
||||
|
||||
// Determine media container.
|
||||
int64 bytes_read = media_file_->Read(buffer_.get(), kBufSize);
|
||||
if (bytes_read <= 0)
|
||||
return Status(error::FILE_FAILURE, "Cannot read file " + file_name_);
|
||||
MediaContainerName container = DetermineContainer(buffer_.get(), bytes_read);
|
||||
|
||||
// Initialize media parser.
|
||||
switch (container) {
|
||||
case CONTAINER_MOV:
|
||||
parser_.reset(new mp4::MP4MediaParser());
|
||||
break;
|
||||
default:
|
||||
NOTIMPLEMENTED();
|
||||
return Status(error::UNIMPLEMENTED, "Container not supported.");
|
||||
}
|
||||
|
||||
parser_->Init(
|
||||
base::Bind(&Demuxer::ParserInitEvent, base::Unretained(this)),
|
||||
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
|
||||
base::Bind(&Demuxer::KeyNeededEvent, base::Unretained(this)));
|
||||
|
||||
if (!parser_->Parse(buffer_.get(), bytes_read))
|
||||
return Status(error::PARSER_FAILURE,
|
||||
"Cannot parse media file " + file_name_);
|
||||
|
||||
// TODO(kqyang): Does not look clean. Consider refactoring later.
|
||||
Status status;
|
||||
while (!init_event_received_) {
|
||||
if (!(status = Parse()).ok()) break;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void Demuxer::ParserInitEvent(
|
||||
const std::vector<scoped_refptr<StreamInfo> >& streams) {
|
||||
init_event_received_ = true;
|
||||
|
||||
std::vector<scoped_refptr<StreamInfo> >::const_iterator it = streams.begin();
|
||||
for (; it != streams.end(); ++it) {
|
||||
streams_.push_back(new MediaStream(*it, this));
|
||||
}
|
||||
}
|
||||
|
||||
bool Demuxer::NewSampleEvent(
|
||||
uint32 track_id, const scoped_refptr<MediaSample>& sample) {
|
||||
std::vector<MediaStream*>::iterator it = streams_.begin();
|
||||
for (; it != streams_.end(); ++it) {
|
||||
if (track_id == (*it)->info()->track_id()) {
|
||||
return (*it)->PushSample(sample).ok();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Demuxer::KeyNeededEvent(
|
||||
MediaContainerName container, scoped_ptr<uint8[]> init_data,
|
||||
int init_data_size) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
Status Demuxer::Run() {
|
||||
Status status;
|
||||
|
||||
// Start the streams.
|
||||
for (std::vector<MediaStream*>::iterator it = streams_.begin();
|
||||
it != streams_.end();
|
||||
++it) {
|
||||
status = (*it)->Start(MediaStream::kPush);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
|
||||
while ((status = Parse()).ok()) continue;
|
||||
return status.Matches(Status(error::EOF, "")) ? Status::OK : status;
|
||||
}
|
||||
|
||||
Status Demuxer::Parse() {
|
||||
DCHECK(media_file_ != NULL);
|
||||
DCHECK(parser_ != NULL);
|
||||
DCHECK(buffer_ != NULL);
|
||||
|
||||
int64 bytes_read = media_file_->Read(buffer_.get(), kBufSize);
|
||||
if (bytes_read <= 0) {
|
||||
return media_file_->Eof() ?
|
||||
Status(error::EOF, "End of file") :
|
||||
Status(error::FILE_FAILURE, "Cannot read file " + file_name_);
|
||||
}
|
||||
|
||||
return parser_->Parse(buffer_.get(), bytes_read)
|
||||
? Status::OK
|
||||
: Status(error::PARSER_FAILURE,
|
||||
"Cannot parse media file " + file_name_);
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef MEDIA_BASE_DEMUXER_H_
|
||||
#define MEDIA_BASE_DEMUXER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "media/base/container_names.h"
|
||||
#include "media/base/status.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
class Decryptor;
|
||||
class DecryptorSource;
|
||||
class File;
|
||||
class MediaParser;
|
||||
class MediaSample;
|
||||
class MediaStream;
|
||||
class StreamInfo;
|
||||
|
||||
class Demuxer {
|
||||
public:
|
||||
// |file_name| specifies the input source. It uses prefix matching to create
|
||||
// a proper File object. The user can extend File to support their custom
|
||||
// File objects with its own prefix.
|
||||
// decryptor_source generates decryptor(s) when init_data is available.
|
||||
// Demuxer does not take over the ownership of decryptor_source.
|
||||
Demuxer(const std::string& file_name, DecryptorSource* decryptor_source);
|
||||
~Demuxer();
|
||||
|
||||
// Initializes corresponding MediaParser, Decryptor, instantiates
|
||||
// MediaStream(s) etc.
|
||||
Status Initialize();
|
||||
|
||||
// Drives the remuxing from demuxer side (push): Reads the file and push
|
||||
// the Data to Muxer until Eof.
|
||||
Status Run();
|
||||
|
||||
// Reads from the source and send it to the parser.
|
||||
Status Parse();
|
||||
|
||||
uint32 num_streams() const {
|
||||
return streams_.size();
|
||||
}
|
||||
|
||||
// Demuxer retains the ownership of streams.
|
||||
MediaStream* streams(uint32 index) {
|
||||
if (index >= streams_.size())
|
||||
return NULL;
|
||||
return streams_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
// Parser event handlers.
|
||||
void ParserInitEvent(const std::vector<scoped_refptr<StreamInfo> >& streams);
|
||||
bool NewSampleEvent(uint32 track_id,
|
||||
const scoped_refptr<MediaSample>& sample);
|
||||
void KeyNeededEvent(MediaContainerName container,
|
||||
scoped_ptr<uint8[]> init_data,
|
||||
int init_data_size);
|
||||
|
||||
DecryptorSource* decryptor_source_;
|
||||
std::string file_name_;
|
||||
File* media_file_;
|
||||
bool init_event_received_;
|
||||
scoped_ptr<MediaParser> parser_;
|
||||
std::vector<MediaStream*> streams_;
|
||||
scoped_ptr<uint8[]> buffer_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Demuxer);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MEDIA_BASE_DEMUXER_H_
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// EncryptorSource is responsible for encryption key acquisition.
|
||||
|
||||
#ifndef MEDIA_BASE_ENCRYPTOR_SOURCE_H_
|
||||
#define MEDIA_BASE_ENCRYPTOR_SOURCE_H_
|
||||
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "media/base/container_names.h"
|
||||
#include "media/base/status.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
class EncryptorSource {
|
||||
public:
|
||||
EncryptorSource() {}
|
||||
virtual ~EncryptorSource() {}
|
||||
|
||||
virtual Status Init() = 0;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(EncryptorSource);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MEDIA_BASE_ENCRYPTOR_SOURCE_H_
|
|
@ -24,16 +24,13 @@ class MediaParser {
|
|||
virtual ~MediaParser() {}
|
||||
|
||||
// Indicates completion of parser initialization.
|
||||
// First parameter - Indicates initialization success. Set to true if
|
||||
// initialization was successful. False if an error
|
||||
// occurred.
|
||||
// Second parameter - A vector of all the elementary streams within this file.
|
||||
typedef base::Callback<void(bool, std::vector<scoped_refptr<StreamInfo> >&)>
|
||||
// First parameter - A vector of all the elementary streams within this file.
|
||||
typedef base::Callback<void(const std::vector<scoped_refptr<StreamInfo> >&)>
|
||||
InitCB;
|
||||
|
||||
// New stream sample have been parsed.
|
||||
// First parameter - The track id of the new sample.
|
||||
// Second parameter - The new media sample;
|
||||
// Second parameter - The new media sample.
|
||||
// Return value - True indicates that the sample is accepted.
|
||||
// False if something was wrong with the sample and a parsing
|
||||
// error should be signaled.
|
||||
|
|
|
@ -55,7 +55,7 @@ scoped_refptr<MediaSample> MediaSample::CreateEOSBuffer() {
|
|||
return make_scoped_refptr(new MediaSample(NULL, 0, NULL, 0, false));
|
||||
}
|
||||
|
||||
std::string MediaSample::ToString() {
|
||||
std::string MediaSample::ToString() const {
|
||||
if (end_of_stream()) {
|
||||
return "end of stream";
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ class MediaSample
|
|||
}
|
||||
|
||||
// Returns a human-readable string describing |*this|.
|
||||
std::string ToString();
|
||||
std::string ToString() const;
|
||||
|
||||
protected:
|
||||
friend class base::RefCountedThreadSafe<MediaSample>;
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/media_stream.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "media/base/demuxer.h"
|
||||
#include "media/base/media_sample.h"
|
||||
#include "media/base/muxer.h"
|
||||
#include "media/base/stream_info.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
MediaStream::MediaStream(scoped_refptr<StreamInfo> info, Demuxer* demuxer)
|
||||
: info_(info), demuxer_(demuxer), muxer_(NULL), state_(kIdle) {}
|
||||
|
||||
MediaStream::~MediaStream() {}
|
||||
|
||||
Status MediaStream::PullSample(scoped_refptr<MediaSample>* sample) {
|
||||
DCHECK_EQ(state_, kPulling);
|
||||
|
||||
// Trigger a new parse in demuxer if no more samples.
|
||||
while (samples_.empty()) {
|
||||
Status status = demuxer_->Parse();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
|
||||
*sample = samples_.front();
|
||||
samples_.pop_front();
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status MediaStream::PushSample(const scoped_refptr<MediaSample>& sample) {
|
||||
switch (state_) {
|
||||
case kIdle:
|
||||
case kPulling:
|
||||
samples_.push_back(sample);
|
||||
return Status::OK;
|
||||
case kDisconnected:
|
||||
return Status::OK;
|
||||
case kPushing:
|
||||
return muxer_->AddSample(this, sample);
|
||||
default:
|
||||
NOTREACHED() << "Unexpected State " << state_;
|
||||
return Status::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void MediaStream::Connect(Muxer* muxer) {
|
||||
DCHECK(muxer != NULL);
|
||||
DCHECK(muxer_ == NULL);
|
||||
state_ = kConnected;
|
||||
muxer_ = muxer;
|
||||
}
|
||||
|
||||
Status MediaStream::Start(MediaStreamOperation operation) {
|
||||
DCHECK(demuxer_ != NULL);
|
||||
DCHECK(operation == kPush || operation == kPull);
|
||||
|
||||
|
||||
switch (state_) {
|
||||
case kIdle:
|
||||
// Disconnect the stream if it is not connected to a muxer.
|
||||
state_ = kDisconnected;
|
||||
samples_.clear();
|
||||
return Status::OK;
|
||||
case kConnected:
|
||||
state_ = (operation == kPush) ? kPushing : kPulling;
|
||||
if (operation == kPush) {
|
||||
// Push samples in the queue to muxer if there is any.
|
||||
while (!samples_.empty()) {
|
||||
Status status = muxer_->AddSample(this, samples_.front());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
samples_.pop_front();
|
||||
}
|
||||
} else {
|
||||
// We need to disconnect all its peer streams which are not connected
|
||||
// to a muxer.
|
||||
for (int i = 0; i < demuxer_->num_streams(); ++i) {
|
||||
Status status = demuxer_->streams(i)->Start(operation);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return Status::OK;
|
||||
case kPulling:
|
||||
DCHECK(operation == kPull);
|
||||
return Status::OK;
|
||||
default:
|
||||
NOTREACHED() << "Unexpected State " << state_;
|
||||
return Status::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
scoped_refptr<StreamInfo> MediaStream::info() {
|
||||
return info_;
|
||||
}
|
||||
|
||||
std::string MediaStream::ToString() const {
|
||||
std::ostringstream s;
|
||||
s << "state: " << state_
|
||||
<< " samples in the queue: " << samples_.size()
|
||||
<< " " << info_->ToString();
|
||||
return s.str();
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// MediaStream connects Demuxer to Muxer.
|
||||
|
||||
#ifndef MEDIA_BASE_MEDIA_STREAM_H_
|
||||
#define MEDIA_BASE_MEDIA_STREAM_H_
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "media/base/status.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
class Demuxer;
|
||||
class Muxer;
|
||||
class MediaSample;
|
||||
class StreamInfo;
|
||||
|
||||
class MediaStream {
|
||||
public:
|
||||
enum MediaStreamOperation {
|
||||
kPush,
|
||||
kPull,
|
||||
};
|
||||
// Create MediaStream from StreamInfo and Demuxer. MediaStream does not own
|
||||
// demuxer.
|
||||
MediaStream(scoped_refptr<StreamInfo> info, Demuxer* demuxer);
|
||||
~MediaStream();
|
||||
|
||||
// Connect the stream to Muxer. MediaStream does not own muxer.
|
||||
void Connect(Muxer* muxer);
|
||||
|
||||
// Start the stream for pushing or pulling.
|
||||
Status Start(MediaStreamOperation operation);
|
||||
|
||||
// Push sample to Muxer (triggered by Demuxer).
|
||||
Status PushSample(const scoped_refptr<MediaSample>& sample);
|
||||
|
||||
// Pull sample from Demuxer (triggered by Muxer).
|
||||
Status PullSample(scoped_refptr<MediaSample>* sample);
|
||||
|
||||
Demuxer* demuxer() {
|
||||
return demuxer_;
|
||||
}
|
||||
Muxer* muxer() {
|
||||
return muxer_;
|
||||
}
|
||||
scoped_refptr<StreamInfo> info();
|
||||
|
||||
// Returns a human-readable string describing |*this|.
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
// State transition diagram available @ http://goo.gl/ThJQbl.
|
||||
enum State {
|
||||
kIdle,
|
||||
kConnected,
|
||||
kDisconnected,
|
||||
kPushing,
|
||||
kPulling,
|
||||
};
|
||||
|
||||
scoped_refptr<StreamInfo> info_;
|
||||
Demuxer* demuxer_;
|
||||
Muxer* muxer_;
|
||||
State state_;
|
||||
// An internal buffer to store samples temporarily.
|
||||
std::deque<scoped_refptr<MediaSample> > samples_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MediaStream);
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
||||
#endif // MEDIA_BASE_MEDIA_STREAM_H_
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/muxer.h"
|
||||
|
||||
#include "media/base/encryptor_source.h"
|
||||
#include "media/base/media_sample.h"
|
||||
#include "media/base/media_stream.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
Muxer::Muxer(const Options& options, EncryptorSource* encrytor_source) :
|
||||
encryptor_source_(encrytor_source), encryptor_(NULL) {}
|
||||
|
||||
Muxer::~Muxer() {}
|
||||
|
||||
Status Muxer::AddStream(MediaStream* stream) {
|
||||
stream->Connect(this);
|
||||
streams_.push_back(stream);
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status Muxer::Run() {
|
||||
DCHECK(!streams_.empty());
|
||||
|
||||
Status status;
|
||||
|
||||
// Start the streams.
|
||||
for (std::vector<MediaStream*>::iterator it = streams_.begin();
|
||||
it != streams_.end();
|
||||
++it) {
|
||||
status = (*it)->Start(MediaStream::kPull);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
|
||||
while (status.ok()) {
|
||||
// TODO(kqyang): Need to do some proper synchronization between streams.
|
||||
scoped_refptr<MediaSample> sample;
|
||||
status = streams_[0]->PullSample(&sample);
|
||||
if (!status.ok())
|
||||
break;
|
||||
status = AddSample(streams_[0], sample);
|
||||
}
|
||||
return status.Matches(Status(error::EOF, "")) ? Status::OK : status;
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef MEDIA_BASE_MUXER_H_
|
||||
#define MEDIA_BASE_MUXER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "media/base/status.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
class Encryptor;
|
||||
class EncryptorSource;
|
||||
class MediaSample;
|
||||
class MediaStream;
|
||||
|
||||
class Muxer {
|
||||
public:
|
||||
struct Options {
|
||||
// Generate a single segment for each media presentation. This option
|
||||
// should be set for on demand profile.
|
||||
bool single_segment;
|
||||
|
||||
// Segment duration. If single_segment is specified, this parameter sets
|
||||
// the duration of a subsegment. A subsegment can contain one to several
|
||||
// fragments.
|
||||
int segment_duration;
|
||||
|
||||
// Fragment duration. Should not be larger than the segment duration.
|
||||
int fragment_duration;
|
||||
|
||||
// Force segments to begin with stream access points. Segment duration may
|
||||
// not be exactly what asked by segment_duration.
|
||||
bool segment_sap_aligned;
|
||||
|
||||
// Force fragments to begin with stream access points. Fragment duration
|
||||
// may not be exactly what asked by segment_duration. Imply
|
||||
// segment_sap_aligned.
|
||||
bool fragment_sap_aligned;
|
||||
|
||||
// Set the number of subsegments in each SIDX box. If 0, a single SIDX box
|
||||
// is used per segment. If -1, no SIDX box is used. Otherwise, the Muxer
|
||||
// will pack N subsegments in the root SIDX of the segment, with
|
||||
// segment_duration/N/fragment_duration fragments per subsegment.
|
||||
int num_subsegments_per_sidx;
|
||||
|
||||
// Output file name. If segment_template is not specified, the Muxer
|
||||
// generates this single output file with all segments concatenated;
|
||||
// Otherwise, it specifies the init segment name.
|
||||
std::string output_file_name;
|
||||
|
||||
// Specify output segment name pattern for generated segments. It can
|
||||
// furthermore be configured by using a subset of the SegmentTemplate
|
||||
// identifiers: $RepresentationID$, $Number$, $Bandwidth$ and $Time.
|
||||
// Optional.
|
||||
std::string segment_template;
|
||||
|
||||
// Specify a directory for temporary file creation.
|
||||
std::string temp_directory_name;
|
||||
};
|
||||
|
||||
// Muxer does not take over the ownership of encryptor_source.
|
||||
Muxer(const Options& options, EncryptorSource* encryptor_source);
|
||||
virtual ~Muxer();
|
||||
|
||||
// Adds video/audio stream.
|
||||
// Returns OK on success.
|
||||
virtual Status AddStream(MediaStream* stream);
|
||||
|
||||
// Adds new media sample.
|
||||
virtual Status AddSample(const MediaStream* stream,
|
||||
scoped_refptr<MediaSample> sample) = 0;
|
||||
|
||||
// Final clean up.
|
||||
virtual Status Finalize() = 0;
|
||||
|
||||
// Drives the remuxing from muxer side (pull).
|
||||
virtual Status Run();
|
||||
|
||||
uint32 num_streams() const {
|
||||
return streams_.size();
|
||||
}
|
||||
|
||||
MediaStream* streams(uint32 index) const {
|
||||
if (index >= streams_.size())
|
||||
return NULL;
|
||||
return streams_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
EncryptorSource* encryptor_source_;
|
||||
Encryptor* encryptor_;
|
||||
std::vector<MediaStream*> streams_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Muxer);
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
||||
#endif // MEDIA_BASE_MUXER_H_
|
|
@ -3,10 +3,17 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
#include "base/at_exit.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/logging.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
// Needed to enable VLOG/DVLOG through --vmodule or --v.
|
||||
CommandLine::Init(argc, argv);
|
||||
CHECK(logging::InitLogging(logging::LoggingSettings()));
|
||||
|
||||
base::AtExitManager exit;
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/status.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace media {
|
||||
|
||||
const Status& Status::OK = Status(error::OK, "");
|
||||
const Status& Status::UNKNOWN = Status(error::UNKNOWN, "");
|
||||
|
||||
std::string Status::ToString() const {
|
||||
if (error_code_ == error::OK)
|
||||
return "OK";
|
||||
|
||||
std::ostringstream string_stream;
|
||||
string_stream << error_code_ << ":" << error_message_;
|
||||
return string_stream.str();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Status& x) {
|
||||
os << x.ToString();
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef MEDIA_BASE_STATUS_H_
|
||||
#define MEDIA_BASE_STATUS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
namespace error {
|
||||
|
||||
// Error codes for the packager APIs.
|
||||
enum Code {
|
||||
// Not an error; returned on success
|
||||
OK,
|
||||
|
||||
// Unknown error. An example of where this error may be returned is
|
||||
// errors raised by APIs that do not return enough error information
|
||||
// may be converted to this error.
|
||||
UNKNOWN,
|
||||
|
||||
// The operation was cancelled (typically by the caller).
|
||||
CANCELLED,
|
||||
|
||||
// Client specified an invalid argument. INVALID_ARGUMENT indicates
|
||||
// arguments that are problematic regardless of the state of the system
|
||||
// (e.g. a malformed file name).
|
||||
INVALID_ARGUMENT,
|
||||
|
||||
// Operation is not implemented or not supported/enabled.
|
||||
UNIMPLEMENTED,
|
||||
|
||||
// Cannot open file.
|
||||
FILE_FAILURE,
|
||||
|
||||
// End of file.
|
||||
EOF,
|
||||
|
||||
// Unable to parse the media file.
|
||||
PARSER_FAILURE,
|
||||
|
||||
// TODO(kqyang): define packager specific error codes.
|
||||
};
|
||||
|
||||
} // namespace error
|
||||
|
||||
class Status {
|
||||
public:
|
||||
// Creates a "successful" status.
|
||||
Status() : error_code_(error::OK) {}
|
||||
|
||||
// Create a status with the specified code, and error message. If "code == 0",
|
||||
// error_message is ignored and a Status object identical to Status::OK is
|
||||
// constructed.
|
||||
Status(error::Code error_code, const std::string& error_message) :
|
||||
error_code_(error_code) {
|
||||
if (!ok())
|
||||
error_message_ = error_message;
|
||||
}
|
||||
|
||||
//Status(const Status&);
|
||||
//Status& operator=(const Status& x);
|
||||
~Status() {}
|
||||
|
||||
// Some pre-defined Status objects.
|
||||
static const Status& OK; // Identical to 0-arg constructor.
|
||||
static const Status& UNKNOWN;
|
||||
|
||||
// Store the specified error in this Status object.
|
||||
// If "error_code == error::OK", error_message is ignored and a Status object
|
||||
// identical to Status::OK is constructed.
|
||||
void SetError(error::Code error_code, const std::string& error_message) {
|
||||
if (error_code == error::OK) {
|
||||
Clear();
|
||||
} else {
|
||||
error_code_ = error_code;
|
||||
error_message_ = error_message;
|
||||
}
|
||||
}
|
||||
|
||||
// If "ok()", stores "new_status" into *this. If "!ok()", preserves
|
||||
// the current "error_code()/error_message()",
|
||||
//
|
||||
// Convenient way of keeping track of the first error encountered.
|
||||
// Instead of:
|
||||
// if (overall_status.ok()) overall_status = new_status
|
||||
// Use:
|
||||
// overall_status.Update(new_status);
|
||||
void Update(const Status& new_status) {
|
||||
if (ok())
|
||||
*this = new_status;
|
||||
}
|
||||
|
||||
// Clear this status object to contain the OK code and no error message.
|
||||
void Clear() {
|
||||
error_code_ = error::OK;
|
||||
error_message_ = "";
|
||||
}
|
||||
|
||||
// Accessor.
|
||||
bool ok() const {
|
||||
return error_code_ == error::OK;
|
||||
}
|
||||
error::Code error_code() const {
|
||||
return error_code_;
|
||||
}
|
||||
const std::string& error_message() const {
|
||||
return error_message_;
|
||||
}
|
||||
|
||||
bool operator==(const Status& x) const {
|
||||
return error_code_ == x.error_code() && error_message_ == x.error_message();
|
||||
}
|
||||
bool operator!=(const Status& x) const {
|
||||
return !(*this == x);
|
||||
}
|
||||
|
||||
// Returns true iff this has the same error_code as "x". I.e., the two
|
||||
// Status objects are identical except possibly for the error message.
|
||||
bool Matches(const Status& x) const {
|
||||
return error_code_ == x.error_code();
|
||||
}
|
||||
|
||||
// Return a combination of the error code name and message.
|
||||
std::string ToString() const;
|
||||
|
||||
void Swap(Status* other) {
|
||||
error::Code error_code = error_code_;
|
||||
error_code_ = other->error_code_;
|
||||
other->error_code_ = error_code;
|
||||
error_message_.swap(other->error_message_);
|
||||
}
|
||||
|
||||
private:
|
||||
error::Code error_code_;
|
||||
std::string error_message_;
|
||||
|
||||
// Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler
|
||||
// generated copy constructor and assignment operator.
|
||||
};
|
||||
|
||||
extern std::ostream& operator<<(std::ostream& os, const Status& x);
|
||||
|
||||
// Status success comparison.
|
||||
// This is better than CHECK((val).ok()) because the embedded
|
||||
// error string gets printed by the CHECK_EQ.
|
||||
#define CHECK_OK(val) CHECK_EQ(Status::OK, (val))
|
||||
#define DCHECK_OK(val) DCHECK_EQ(Status::OK, (val))
|
||||
|
||||
} // namespace media
|
||||
|
||||
#endif // MEDIA_BASE_STATUS_H_
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef MEDIA_BASE_STATUS_TEST_UTIL_H_
|
||||
#define MEDIA_BASE_STATUS_TEST_UTIL_H_
|
||||
|
||||
#include "media/base/status.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
#define EXPECT_OK(val) EXPECT_EQ(media::Status::OK, (val))
|
||||
#define ASSERT_OK(val) ASSERT_EQ(media::Status::OK, (val))
|
||||
|
||||
#endif // MEDIA_BASE_STATUS_TEST_UTIL_H_
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/status_test_util.h"
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest-spi.h"
|
||||
|
||||
TEST(StatusTestUtil, ExpectOkSuccess) {
|
||||
EXPECT_OK(media::Status::OK);
|
||||
}
|
||||
|
||||
TEST(StatusTestUtil, AssertOkSuccess) {
|
||||
ASSERT_OK(media::Status::OK);
|
||||
}
|
||||
|
||||
TEST(StatusTestUtil, ExpectOkFailure) {
|
||||
media::Status status(media::error::UNKNOWN, "Status Unknown");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_OK(status), "Status Unknown");
|
||||
}
|
||||
|
||||
TEST(StatusTestUtil, AssertOkFailure) {
|
||||
EXPECT_FATAL_FAILURE(
|
||||
ASSERT_OK(media::Status(media::error::UNKNOWN, "Status Unknown")),
|
||||
"Status Unknown");
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/status.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
static void CheckStatus(const Status& s,
|
||||
error::Code code,
|
||||
const std::string& message) {
|
||||
EXPECT_EQ(code, s.error_code());
|
||||
EXPECT_EQ(message, s.error_message());
|
||||
|
||||
if (code == error::OK) {
|
||||
EXPECT_TRUE(s.ok());
|
||||
EXPECT_EQ("OK", s.ToString());
|
||||
} else {
|
||||
EXPECT_TRUE(!s.ok());
|
||||
EXPECT_THAT(s.ToString(), testing::HasSubstr(message));
|
||||
|
||||
std::ostringstream string_stream;
|
||||
string_stream << code << ":" << message;
|
||||
EXPECT_EQ(string_stream.str(), s.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Status, Empty) {
|
||||
CheckStatus(Status(), error::OK, "");
|
||||
}
|
||||
|
||||
TEST(Status, OK) {
|
||||
CheckStatus(Status::OK, error::OK, "");
|
||||
}
|
||||
|
||||
TEST(Status, ConstructorOK) {
|
||||
CheckStatus(Status(error::OK, "msg"), error::OK, "");
|
||||
}
|
||||
|
||||
TEST(Status, CheckOK) {
|
||||
CHECK_OK(Status());
|
||||
}
|
||||
|
||||
TEST(Status, CheckOKDeath) {
|
||||
Status status(error::UNKNOWN, "Status Unknown");
|
||||
ASSERT_DEATH(CHECK_OK(status), "Status Unknown");
|
||||
ASSERT_DEATH(CHECK_OK(status) << "Foo1234", "Foo1234");
|
||||
}
|
||||
|
||||
TEST(Status, SetError) {
|
||||
Status status;
|
||||
status.SetError(error::CANCELLED, "message");
|
||||
CheckStatus(status, error::CANCELLED, "message");
|
||||
}
|
||||
|
||||
TEST(Status, SetErrorOK) {
|
||||
Status status(error::CANCELLED, "message");
|
||||
status.SetError(error::OK, "msg");
|
||||
CheckStatus(status, error::OK, "");
|
||||
}
|
||||
|
||||
TEST(Status, Unknown) {
|
||||
CheckStatus(Status::UNKNOWN, error::UNKNOWN, "");
|
||||
}
|
||||
|
||||
TEST(Status, Filled) {
|
||||
CheckStatus(Status(error::CANCELLED, "message"), error::CANCELLED, "message");
|
||||
}
|
||||
|
||||
TEST(Status, Clear) {
|
||||
Status status(error::CANCELLED, "message");
|
||||
status.Clear();
|
||||
CheckStatus(status, error::OK, "");
|
||||
}
|
||||
|
||||
TEST(Status, Copy) {
|
||||
Status a(error::CANCELLED, "message");
|
||||
Status b(a);
|
||||
ASSERT_EQ(a, b);
|
||||
}
|
||||
|
||||
TEST(Status, Assign) {
|
||||
Status a(error::CANCELLED, "message");
|
||||
Status b;
|
||||
b = a;
|
||||
ASSERT_EQ(a, b);
|
||||
}
|
||||
|
||||
TEST(Status, AssignEmpty) {
|
||||
Status a(error::CANCELLED, "message");
|
||||
Status b;
|
||||
a = b;
|
||||
ASSERT_EQ("OK", a.ToString());
|
||||
ASSERT_TRUE(b.ok());
|
||||
ASSERT_TRUE(a.ok());
|
||||
}
|
||||
|
||||
TEST(Status, Update) {
|
||||
Status s;
|
||||
s.Update(Status::OK);
|
||||
ASSERT_TRUE(s.ok());
|
||||
Status a(error::CANCELLED, "message");
|
||||
s.Update(a);
|
||||
ASSERT_EQ(s, a);
|
||||
Status b(error::UNIMPLEMENTED, "other message");
|
||||
s.Update(b);
|
||||
ASSERT_EQ(s, a);
|
||||
s.Update(Status::OK);
|
||||
ASSERT_EQ(s, a);
|
||||
ASSERT_FALSE(s.ok());
|
||||
}
|
||||
|
||||
TEST(Status, Swap) {
|
||||
Status a(error::CANCELLED, "message");
|
||||
Status b = a;
|
||||
Status c;
|
||||
c.Swap(&a);
|
||||
ASSERT_EQ(c, b);
|
||||
ASSERT_EQ(a, Status::OK);
|
||||
}
|
||||
|
||||
TEST(Status, MatchOK) {
|
||||
ASSERT_TRUE(Status().Matches(Status::OK));
|
||||
}
|
||||
|
||||
TEST(Status, MatchSame) {
|
||||
Status a(error::UNKNOWN, "message");
|
||||
Status b(error::UNKNOWN, "message");
|
||||
ASSERT_TRUE(a.Matches(b));
|
||||
}
|
||||
|
||||
TEST(Status, MatchCopy) {
|
||||
Status a(error::UNKNOWN, "message");
|
||||
Status b = a;
|
||||
ASSERT_TRUE(a.Matches(b));
|
||||
}
|
||||
|
||||
TEST(Status, MatchDifferentCode) {
|
||||
Status a(error::UNKNOWN, "message");
|
||||
Status b(error::CANCELLED, "message");
|
||||
ASSERT_TRUE(!a.Matches(b));
|
||||
}
|
||||
|
||||
TEST(Status, MatchDifferentMessage) {
|
||||
Status a(error::UNKNOWN, "message");
|
||||
Status b(error::UNKNOWN, "another");
|
||||
ASSERT_TRUE(a.Matches(b));
|
||||
}
|
||||
|
||||
TEST(Status, EqualsOK) {
|
||||
ASSERT_EQ(Status::OK, Status());
|
||||
}
|
||||
|
||||
TEST(Status, EqualsSame) {
|
||||
ASSERT_EQ(Status(error::UNKNOWN, "message"),
|
||||
Status(error::UNKNOWN, "message"));
|
||||
}
|
||||
|
||||
TEST(Status, EqualsCopy) {
|
||||
Status a(error::UNKNOWN, "message");
|
||||
Status b = a;
|
||||
ASSERT_EQ(a, b);
|
||||
}
|
||||
|
||||
TEST(Status, EqualsDifferentCode) {
|
||||
ASSERT_NE(Status(error::UNKNOWN, "message"),
|
||||
Status(error::CANCELLED, "message"));
|
||||
}
|
||||
|
||||
TEST(Status, EqualsDifferentMessage) {
|
||||
ASSERT_NE(Status(error::UNKNOWN, "message"),
|
||||
Status(error::UNKNOWN, "another"));
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license tha can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/file/file.h"
|
||||
|
||||
#include "media/file/local_file.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
const char* kLocalFilePrefix = "file://";
|
||||
|
||||
typedef File* (*FileFactoryFunction)(const char* fname, const char* mode);
|
||||
|
||||
struct SupportedTypeInfo {
|
||||
const char* type;
|
||||
int type_length;
|
||||
const FileFactoryFunction factory_function;
|
||||
};
|
||||
|
||||
static File* CreateLocalFile(const char* fname, const char* mode) {
|
||||
return new LocalFile(fname, mode);
|
||||
}
|
||||
|
||||
static const SupportedTypeInfo kSupportedTypeInfo[] = {
|
||||
{ kLocalFilePrefix, strlen(kLocalFilePrefix), &CreateLocalFile },
|
||||
};
|
||||
|
||||
File* File::Create(const char* fname, const char* mode) {
|
||||
for (size_t i = 0; i < arraysize(kSupportedTypeInfo); ++i) {
|
||||
const SupportedTypeInfo& type_info = kSupportedTypeInfo[i];
|
||||
if (strncmp(type_info.type, fname, type_info.type_length) == 0) {
|
||||
return type_info.factory_function(fname + type_info.type_length, mode);
|
||||
}
|
||||
}
|
||||
// Otherwise we assume it is a local file
|
||||
return CreateLocalFile(fname, mode);
|
||||
}
|
||||
|
||||
File* File::Open(const char* name, const char* mode) {
|
||||
File* file = File::Create(name, mode);
|
||||
if (!file) {
|
||||
return NULL;
|
||||
}
|
||||
if (!file->Open()) {
|
||||
delete file;
|
||||
return NULL;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
// Return the file size or -1 on failure.
|
||||
// Requires opening and closing the file.
|
||||
int64 File::GetFileSize(const char* name) {
|
||||
File* f = File::Open(name, "r");
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
int64 res = f->Size();
|
||||
f->Close();
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license tha can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// Defines an abstract file interface.
|
||||
|
||||
#ifndef PACKAGER_FILE_FILE_H_
|
||||
#define PACKAGER_FILE_FILE_H_
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
extern const char* kLocalFilePrefix;
|
||||
|
||||
class File {
|
||||
public:
|
||||
// Open the specified file, or return NULL on error.
|
||||
// This is actually a file factory method, it opens a proper file, e.g.
|
||||
// LocalFile, MemFile automatically based on prefix.
|
||||
static File* Open(const char* name, const char* mode);
|
||||
|
||||
// Flush() and de-allocate resources associated with this file, and
|
||||
// delete this File object. THIS IS THE ONE TRUE WAY TO DEALLOCATE
|
||||
// THIS OBJECT.
|
||||
// Returns true on success.
|
||||
// For writable files, returning false MAY INDICATE DATA LOSS.
|
||||
virtual bool Close() = 0;
|
||||
|
||||
// Reads data and returns it in buffer. Returns a value < 0 on error,
|
||||
// or the number of bytes read otherwise. Returns zero on end-of-file,
|
||||
// or if 'length' is zero.
|
||||
virtual int64 Read(void* buffer, uint64 length) = 0;
|
||||
|
||||
// Write 'length' bytes from 'buffer', returning the number of bytes
|
||||
// that were actually written. Return < 0 on error.
|
||||
//
|
||||
// For a file open in append mode (i.e., "a" or "a+"), Write()
|
||||
// always appends to the end of the file. For files opened in other
|
||||
// write modes (i.e., "w", or "w+"), writes occur at the
|
||||
// current file offset.
|
||||
virtual int64 Write(const void* buffer, uint64 length) = 0;
|
||||
|
||||
// Return the size of the file in bytes.
|
||||
// A return value less than zero indicates a problem getting the
|
||||
// size.
|
||||
virtual int64 Size() = 0;
|
||||
|
||||
// Flushes the file so that recently written data will survive an
|
||||
// application crash (but not necessarily an OS crash). For
|
||||
// instance, in localfile the data is flushed into the OS but not
|
||||
// necessarily to disk.
|
||||
virtual bool Flush() = 0;
|
||||
|
||||
// Return whether we're currently at eof.
|
||||
virtual bool Eof() = 0;
|
||||
|
||||
// ************************************************************
|
||||
// * Static Methods: File-on-the-filesystem status
|
||||
// ************************************************************
|
||||
|
||||
// Returns the size of a file in bytes, and opens and closes the file
|
||||
// in the process. Returns -1 on failure.
|
||||
static int64 GetFileSize(const char* fname);
|
||||
|
||||
protected:
|
||||
File() {}
|
||||
// Do *not* call the destructor directly (with the "delete" keyword)
|
||||
// nor use scoped_ptr; instead use Close().
|
||||
virtual ~File() {}
|
||||
|
||||
// Internal open. Should not be used directly.
|
||||
virtual bool Open() = 0;
|
||||
|
||||
private:
|
||||
// This is a file factory method, it creates a proper file, e.g.
|
||||
// LocalFile, MemFile based on prefix.
|
||||
static File* Create(const char* fname, const char* mode);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(File);
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
||||
#endif // PACKAGER_FILE_FILE_H_
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license tha can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/file/file.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
const int kDataSize = 1024;
|
||||
const char* kTestLocalFileName = "/tmp/local_file_test";
|
||||
}
|
||||
|
||||
namespace media {
|
||||
|
||||
class LocalFileTest : public testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
data_.resize(kDataSize);
|
||||
for (int i = 0; i < kDataSize; ++i)
|
||||
data_[i] = i % 256;
|
||||
|
||||
// Test file path for file_util API.
|
||||
test_file_path_ = base::FilePath(kTestLocalFileName);
|
||||
|
||||
// Local file name with prefix for File API.
|
||||
local_file_name_ = kLocalFilePrefix;
|
||||
local_file_name_ += kTestLocalFileName;
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
// Remove test file if created.
|
||||
base::DeleteFile(base::FilePath(kTestLocalFileName), false);
|
||||
}
|
||||
|
||||
std::string data_;
|
||||
base::FilePath test_file_path_;
|
||||
std::string local_file_name_;
|
||||
};
|
||||
|
||||
TEST_F(LocalFileTest, ReadNotExist) {
|
||||
// Remove test file if it exists.
|
||||
base::DeleteFile(base::FilePath(kTestLocalFileName), false);
|
||||
ASSERT_TRUE(File::Open(local_file_name_.c_str(), "r") == NULL);
|
||||
}
|
||||
|
||||
TEST_F(LocalFileTest, Size) {
|
||||
ASSERT_EQ(kDataSize,
|
||||
file_util::WriteFile(test_file_path_, data_.data(), kDataSize));
|
||||
ASSERT_EQ(kDataSize, File::GetFileSize(local_file_name_.c_str()));
|
||||
}
|
||||
|
||||
TEST_F(LocalFileTest, Write) {
|
||||
// Write file using File API.
|
||||
File* file = File::Open(local_file_name_.c_str(), "w");
|
||||
ASSERT_TRUE(file != NULL);
|
||||
EXPECT_EQ(kDataSize, file->Write(&data_[0], kDataSize));
|
||||
EXPECT_EQ(kDataSize, file->Size());
|
||||
EXPECT_TRUE(file->Close());
|
||||
|
||||
// Read file using file_util API.
|
||||
std::string read_data(kDataSize, 0);
|
||||
ASSERT_EQ(kDataSize,
|
||||
file_util::ReadFile(test_file_path_, &read_data[0], kDataSize));
|
||||
|
||||
// Compare data written and read.
|
||||
EXPECT_EQ(data_, read_data);
|
||||
}
|
||||
|
||||
TEST_F(LocalFileTest, Read_And_Eof) {
|
||||
// Write file using file_util API.
|
||||
ASSERT_EQ(kDataSize,
|
||||
file_util::WriteFile(test_file_path_, data_.data(), kDataSize));
|
||||
|
||||
// Read file using File API.
|
||||
File* file = File::Open(local_file_name_.c_str(), "r");
|
||||
ASSERT_TRUE(file != NULL);
|
||||
|
||||
// Read half of the file and verify that Eof is not true.
|
||||
const int kFirstReadBytes = kDataSize / 2;
|
||||
std::string read_data(kFirstReadBytes + kDataSize, 0);
|
||||
EXPECT_EQ(kFirstReadBytes, file->Read(&read_data[0], kFirstReadBytes));
|
||||
EXPECT_FALSE(file->Eof());
|
||||
|
||||
// Read the remaining half of the file and verify Eof is true.
|
||||
EXPECT_EQ(kDataSize - kFirstReadBytes,
|
||||
file->Read(&read_data[kFirstReadBytes], kDataSize));
|
||||
EXPECT_TRUE(file->Eof());
|
||||
EXPECT_TRUE(file->Close());
|
||||
|
||||
// Compare data written and read.
|
||||
read_data.resize(kDataSize);
|
||||
EXPECT_EQ(data_, read_data);
|
||||
}
|
||||
|
||||
TEST_F(LocalFileTest, WriteRead) {
|
||||
// Write file using File API, using file name directly (without prefix).
|
||||
File* file = File::Open(kTestLocalFileName, "w");
|
||||
ASSERT_TRUE(file != NULL);
|
||||
EXPECT_EQ(kDataSize, file->Write(&data_[0], kDataSize));
|
||||
EXPECT_EQ(kDataSize, file->Size());
|
||||
EXPECT_TRUE(file->Close());
|
||||
|
||||
// Read file using File API, using local file prefix + file name.
|
||||
file = File::Open(local_file_name_.c_str(), "r");
|
||||
ASSERT_TRUE(file != NULL);
|
||||
|
||||
// Read half of the file and verify that Eof is not true.
|
||||
std::string read_data(kDataSize, 0);
|
||||
EXPECT_EQ(kDataSize, file->Read(&read_data[0], kDataSize));
|
||||
EXPECT_TRUE(file->Close());
|
||||
|
||||
// Compare data written and read.
|
||||
EXPECT_EQ(data_, read_data);
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license tha can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/file/local_file.h"
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
LocalFile::LocalFile(const char* name, const char* mode)
|
||||
: File(), file_name_(name), file_mode_(mode), internal_file_(NULL) {}
|
||||
|
||||
bool LocalFile::Open() {
|
||||
internal_file_ =
|
||||
file_util::OpenFile(base::FilePath(file_name_), file_mode_.c_str());
|
||||
return (internal_file_ != NULL);
|
||||
}
|
||||
|
||||
bool LocalFile::Close() {
|
||||
bool result = true;
|
||||
if (internal_file_) {
|
||||
result = file_util::CloseFile(internal_file_);
|
||||
internal_file_ = NULL;
|
||||
}
|
||||
delete this;
|
||||
return result;
|
||||
}
|
||||
|
||||
int64 LocalFile::Read(void* buffer, uint64 length) {
|
||||
DCHECK(buffer != NULL);
|
||||
DCHECK(internal_file_ != NULL);
|
||||
return fread(buffer, sizeof(char), length, internal_file_);
|
||||
}
|
||||
|
||||
int64 LocalFile::Write(const void* buffer, uint64 length) {
|
||||
DCHECK(buffer != NULL);
|
||||
DCHECK(internal_file_ != NULL);
|
||||
return fwrite(buffer, sizeof(char), length, internal_file_);
|
||||
}
|
||||
|
||||
int64 LocalFile::Size() {
|
||||
DCHECK(internal_file_ != NULL);
|
||||
|
||||
// Flush any buffered data, so we get the true file size.
|
||||
if (!Flush()) {
|
||||
LOG(ERROR) << "Cannot flush file.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64 file_size;
|
||||
if (!file_util::GetFileSize(base::FilePath(file_name_), &file_size)) {
|
||||
LOG(ERROR) << "Cannot get file size.";
|
||||
return -1;
|
||||
}
|
||||
return file_size;
|
||||
}
|
||||
|
||||
bool LocalFile::Flush() {
|
||||
DCHECK(internal_file_ != NULL);
|
||||
return ((fflush(internal_file_) == 0) && !ferror(internal_file_));
|
||||
}
|
||||
|
||||
bool LocalFile::Eof() {
|
||||
DCHECK(internal_file_ != NULL);
|
||||
return static_cast<bool>(feof(internal_file_));
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license tha can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// Implements LocalFile.
|
||||
|
||||
#ifndef PACKAGER_FILE_LOCAL_FILE_H_
|
||||
#define PACKAGER_FILE_LOCAL_FILE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "media/file/file.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
class LocalFile : public File {
|
||||
public:
|
||||
LocalFile(const char* name, const char* mode);
|
||||
|
||||
// File implementations.
|
||||
virtual bool Close() OVERRIDE;
|
||||
virtual int64 Read(void* buffer, uint64 length) OVERRIDE;
|
||||
virtual int64 Write(const void* buffer, uint64 length) OVERRIDE;
|
||||
virtual int64 Size() OVERRIDE;
|
||||
virtual bool Flush() OVERRIDE;
|
||||
virtual bool Eof() OVERRIDE;
|
||||
|
||||
protected:
|
||||
virtual bool Open() OVERRIDE;
|
||||
|
||||
private:
|
||||
std::string file_name_;
|
||||
std::string file_mode_;
|
||||
FILE* internal_file_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(LocalFile);
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
||||
#endif // PACKAGER_FILE_LOCAL_FILE_H_
|
||||
|
|
@ -304,8 +304,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
}
|
||||
}
|
||||
|
||||
init_cb_.Run(true, streams);
|
||||
|
||||
init_cb_.Run(streams);
|
||||
EmitNeedKeyIfNecessary(moov_->pssh);
|
||||
runs_.reset(new TrackRunIterator(moov_.get()));
|
||||
RCHECK(runs_->Init());
|
||||
|
|
|
@ -48,9 +48,8 @@ class MP4MediaParserTest : public testing::Test {
|
|||
return true;
|
||||
}
|
||||
|
||||
void InitF(bool init_ok, std::vector<scoped_refptr<StreamInfo> >& streams) {
|
||||
DVLOG(1) << "InitF: ok=" << init_ok;
|
||||
if (init_ok && streams.size() > 0)
|
||||
void InitF(const std::vector<scoped_refptr<StreamInfo> >& streams) {
|
||||
if (streams.size() > 0)
|
||||
configs_received_ = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/demuxer.h"
|
||||
#include "media/base/media_sample.h"
|
||||
#include "media/base/media_stream.h"
|
||||
#include "media/base/muxer.h"
|
||||
#include "media/base/status_test_util.h"
|
||||
#include "media/base/stream_info.h"
|
||||
#include "media/base/test_data_util.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
const char* kMediaFiles[] = {"bear-1280x720.mp4", "bear-1280x720-av_frag.mp4"};
|
||||
}
|
||||
|
||||
namespace media {
|
||||
|
||||
class TestingMuxer : public Muxer {
|
||||
public:
|
||||
TestingMuxer(const Options& options, EncryptorSource* encryptor_source)
|
||||
: Muxer(options, encryptor_source) {}
|
||||
|
||||
virtual Status AddSample(const MediaStream* stream,
|
||||
scoped_refptr<MediaSample> sample) {
|
||||
DVLOG(1) << "Add Sample: " << sample->ToString();
|
||||
DVLOG(2) << "To Stream: " << stream->ToString();
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
virtual Status Finalize() {
|
||||
DVLOG(1) << "Finalize is called.";
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(TestingMuxer);
|
||||
};
|
||||
|
||||
class PackagerTest : public ::testing::TestWithParam<const char*> {};
|
||||
|
||||
TEST_P(PackagerTest, Remux) {
|
||||
Demuxer demuxer(
|
||||
GetTestDataFilePath(GetParam()).value(), NULL);
|
||||
ASSERT_OK(demuxer.Initialize());
|
||||
|
||||
LOG(INFO) << "Num Streams: " << demuxer.num_streams();
|
||||
for (int i = 0; i < demuxer.num_streams(); ++i) {
|
||||
LOG(INFO) << "Streams " << i << " " << demuxer.streams(i)->ToString();
|
||||
}
|
||||
|
||||
Muxer::Options options;
|
||||
TestingMuxer muxer(options, NULL);
|
||||
|
||||
ASSERT_OK(muxer.AddStream(demuxer.streams(0)));
|
||||
|
||||
// Starts remuxing process.
|
||||
ASSERT_OK(demuxer.Run());
|
||||
|
||||
ASSERT_OK(muxer.Finalize());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PackagerE2ETest,
|
||||
PackagerTest,
|
||||
::testing::ValuesIn(kMediaFiles));
|
||||
|
||||
} // namespace media
|
168
packager.gyp
168
packager.gyp
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
# TODO(kqyang): this file should be in media directory.
|
||||
{
|
||||
'target_defaults': {
|
||||
'include_dirs': [
|
||||
|
@ -8,5 +9,172 @@
|
|||
],
|
||||
},
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'media_base',
|
||||
'type': 'static_library',
|
||||
'sources': [
|
||||
'media/base/audio_stream_info.cc',
|
||||
'media/base/audio_stream_info.h',
|
||||
'media/base/bit_reader.cc',
|
||||
'media/base/bit_reader.h',
|
||||
'media/base/buffers.h',
|
||||
'media/base/byte_queue.cc',
|
||||
'media/base/byte_queue.h',
|
||||
'media/base/container_names.cc',
|
||||
'media/base/container_names.h',
|
||||
# TODO(kqyang): demuxer should not be here, it looks like some kinds of
|
||||
# circular dependencies.
|
||||
'media/base/demuxer.cc',
|
||||
'media/base/demuxer.h',
|
||||
'media/base/decrypt_config.cc',
|
||||
'media/base/decrypt_config.h',
|
||||
'media/base/decryptor_source.h',
|
||||
'media/base/encryptor_source.h',
|
||||
'media/base/limits.h',
|
||||
'media/base/media_parser.h',
|
||||
'media/base/media_sample.cc',
|
||||
'media/base/media_sample.h',
|
||||
'media/base/media_stream.cc',
|
||||
'media/base/media_stream.h',
|
||||
'media/base/muxer.cc',
|
||||
'media/base/muxer.h',
|
||||
'media/base/status.cc',
|
||||
'media/base/status.h',
|
||||
'media/base/stream_info.cc',
|
||||
'media/base/stream_info.h',
|
||||
'media/base/text_track.h',
|
||||
'media/base/video_stream_info.cc',
|
||||
'media/base/video_stream_info.h',
|
||||
],
|
||||
'dependencies': [
|
||||
'base/base.gyp:base',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'media_test_support',
|
||||
'type': 'static_library',
|
||||
'sources': [
|
||||
# TODO(kqyang): move these files to test directory.
|
||||
'media/base/run_tests_with_atexit_manager.cc',
|
||||
'media/base/test_data_util.cc',
|
||||
'media/base/test_data_util.h',
|
||||
],
|
||||
'dependencies': [
|
||||
'base/base.gyp:base',
|
||||
'testing/gtest.gyp:gtest',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'media_base_unittest',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'media/base/bit_reader_unittest.cc',
|
||||
'media/base/container_names_unittest.cc',
|
||||
'media/base/status_test_util.h',
|
||||
'media/base/status_test_util_unittest.cc',
|
||||
'media/base/status_unittest.cc',
|
||||
],
|
||||
'dependencies': [
|
||||
'media_base',
|
||||
'media_test_support',
|
||||
'testing/gtest.gyp:gtest',
|
||||
'testing/gmock.gyp:gmock',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'mp4',
|
||||
'type': 'static_library',
|
||||
'sources': [
|
||||
'media/mp4/aac.cc',
|
||||
'media/mp4/aac.h',
|
||||
'media/mp4/box_definitions.cc',
|
||||
'media/mp4/box_definitions.h',
|
||||
'media/mp4/box_reader.cc',
|
||||
'media/mp4/box_reader.h',
|
||||
'media/mp4/cenc.cc',
|
||||
'media/mp4/cenc.h',
|
||||
'media/mp4/chunk_info_iterator.cc',
|
||||
'media/mp4/chunk_info_iterator.h',
|
||||
'media/mp4/composition_offset_iterator.cc',
|
||||
'media/mp4/composition_offset_iterator.h',
|
||||
'media/mp4/decoding_time_iterator.cc',
|
||||
'media/mp4/decoding_time_iterator.h',
|
||||
'media/mp4/es_descriptor.cc',
|
||||
'media/mp4/es_descriptor.h',
|
||||
'media/mp4/fourccs.h',
|
||||
'media/mp4/mp4_media_parser.cc',
|
||||
'media/mp4/mp4_media_parser.h',
|
||||
'media/mp4/offset_byte_queue.cc',
|
||||
'media/mp4/offset_byte_queue.h',
|
||||
'media/mp4/rcheck.h',
|
||||
'media/mp4/sync_sample_iterator.cc',
|
||||
'media/mp4/sync_sample_iterator.h',
|
||||
'media/mp4/track_run_iterator.cc',
|
||||
'media/mp4/track_run_iterator.h',
|
||||
],
|
||||
'dependencies': [
|
||||
'media_base',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'mp4_unittest',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'media/mp4/aac_unittest.cc',
|
||||
'media/mp4/box_reader_unittest.cc',
|
||||
'media/mp4/chunk_info_iterator_unittest.cc',
|
||||
'media/mp4/composition_offset_iterator_unittest.cc',
|
||||
'media/mp4/decoding_time_iterator_unittest.cc',
|
||||
'media/mp4/es_descriptor_unittest.cc',
|
||||
'media/mp4/mp4_media_parser_unittest.cc',
|
||||
'media/mp4/offset_byte_queue_unittest.cc',
|
||||
'media/mp4/sync_sample_iterator_unittest.cc',
|
||||
'media/mp4/track_run_iterator_unittest.cc',
|
||||
],
|
||||
'dependencies': [
|
||||
'media_test_support',
|
||||
'mp4',
|
||||
'testing/gtest.gyp:gtest',
|
||||
'testing/gmock.gyp:gmock',
|
||||
]
|
||||
},
|
||||
{
|
||||
'target_name': 'file',
|
||||
'type': 'static_library',
|
||||
'sources': [
|
||||
'media/file/file.cc',
|
||||
'media/file/file.h',
|
||||
'media/file/local_file.cc',
|
||||
'media/file/local_file.h',
|
||||
],
|
||||
'dependencies': [
|
||||
'base/base.gyp:base',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'file_unittest',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'media/file/file_unittest.cc',
|
||||
],
|
||||
'dependencies': [
|
||||
'file',
|
||||
'testing/gtest.gyp:gtest',
|
||||
'testing/gtest.gyp:gtest_main',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'packager_test',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'media/test/packager_test.cc',
|
||||
],
|
||||
'dependencies': [
|
||||
'file',
|
||||
'media_test_support',
|
||||
'mp4',
|
||||
'testing/gtest.gyp:gtest',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue