MP4 Muxer implementation.

Change-Id: I2aeb506e5bf7e2301e855456688273fe5f3988a2
This commit is contained in:
Kongqun Yang 2013-11-12 12:37:58 -08:00
parent 01646837e1
commit 1b5c3b5316
19 changed files with 1603 additions and 87 deletions

View File

@ -10,8 +10,8 @@
namespace media { namespace media {
Muxer::Muxer(const Options& options, EncryptorSource* encrytor_source) : Muxer::Muxer(const MuxerOptions& options, EncryptorSource* encrytor_source)
encryptor_source_(encrytor_source), encryptor_(NULL) {} : options_(options), encryptor_source_(encrytor_source) {}
Muxer::~Muxer() {} Muxer::~Muxer() {}
@ -25,7 +25,6 @@ Status Muxer::Run() {
DCHECK(!streams_.empty()); DCHECK(!streams_.empty());
Status status; Status status;
// Start the streams. // Start the streams.
for (std::vector<MediaStream*>::iterator it = streams_.begin(); for (std::vector<MediaStream*>::iterator it = streams_.begin();
it != streams_.end(); it != streams_.end();
@ -35,15 +34,21 @@ Status Muxer::Run() {
return status; return status;
} }
uint32 current_stream_id = 0;
while (status.ok()) { while (status.ok()) {
// TODO(kqyang): Need to do some proper synchronization between streams.
scoped_refptr<MediaSample> sample; scoped_refptr<MediaSample> sample;
status = streams_[0]->PullSample(&sample); status = streams_[current_stream_id]->PullSample(&sample);
if (!status.ok()) if (!status.ok())
break; break;
status = AddSample(streams_[0], sample); status = AddSample(streams_[current_stream_id], sample);
// Switch to next stream if the current stream is ready for fragmentation.
if (status.Matches(Status(error::FRAGMENT_FINALIZED, ""))) {
current_stream_id = (current_stream_id + 1) % streams_.size();
status.Clear();
} }
return status.Matches(Status(error::EOF, "")) ? Status::OK : status; }
return status.Matches(Status(error::END_OF_STREAM, "")) ? Status::OK : status;
} }
} // namespace media } // namespace media

View File

@ -1,6 +1,8 @@
// Copyright (c) 2013 Google Inc. All rights reserved. // Copyright (c) 2013 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
//
// Defines the muxer interface.
#ifndef MEDIA_BASE_MUXER_H_ #ifndef MEDIA_BASE_MUXER_H_
#define MEDIA_BASE_MUXER_H_ #define MEDIA_BASE_MUXER_H_
@ -9,92 +11,46 @@
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "media/base/muxer_options.h"
#include "media/base/status.h" #include "media/base/status.h"
namespace media { namespace media {
class Encryptor;
class EncryptorSource; class EncryptorSource;
class MediaSample; class MediaSample;
class MediaStream; class MediaStream;
class Muxer { class Muxer {
public: public:
struct Options { Muxer(const MuxerOptions& options, EncryptorSource* encryptor_source);
// 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(); virtual ~Muxer();
// Initialize the muxer. Must be called after connecting all the streams.
virtual Status Initialize() = 0;
// Final clean up.
virtual Status Finalize() = 0;
// Adds video/audio stream. // Adds video/audio stream.
// Returns OK on success.
virtual Status AddStream(MediaStream* stream); virtual Status AddStream(MediaStream* stream);
// Adds new media sample. // Adds new media sample.
virtual Status AddSample(const MediaStream* stream, virtual Status AddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample) = 0; scoped_refptr<MediaSample> sample) = 0;
// Final clean up.
virtual Status Finalize() = 0;
// Drives the remuxing from muxer side (pull). // Drives the remuxing from muxer side (pull).
virtual Status Run(); virtual Status Run();
uint32 num_streams() const { const std::vector<MediaStream*>& streams() const { return streams_; }
return streams_.size();
}
MediaStream* streams(uint32 index) const { protected:
if (index >= streams_.size()) const MuxerOptions& options() const { return options_; }
return NULL; EncryptorSource* encryptor_source() { return encryptor_source_; }
return streams_[index];
}
private: private:
EncryptorSource* encryptor_source_; MuxerOptions options_;
Encryptor* encryptor_;
std::vector<MediaStream*> streams_; std::vector<MediaStream*> streams_;
EncryptorSource* const encryptor_source_;
DISALLOW_COPY_AND_ASSIGN(Muxer); DISALLOW_COPY_AND_ASSIGN(Muxer);
}; };

View File

@ -0,0 +1,59 @@
// 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_OPTIONS_H_
#define MEDIA_BASE_MUXER_OPTIONS_H_
#include <string>
namespace media {
struct MuxerOptions {
// Generate a single segment for each media presentation. This option
// should be set for on demand profile.
bool single_segment;
// Segment duration in seconds. If single_segment is specified, this parameter
// sets the duration of a subsegment; otherwise, this parameter sets the
// duration of a segment. A segment can contain one to many fragments.
double segment_duration;
// Fragment duration in seconds. Should not be larger than the segment
// duration.
double 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. Setting to true implies
// that segment_sap_aligned is true as well.
bool fragment_sap_aligned;
// For ISO BMFF only.
// 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 the temporary file for on demand media file creation.
std::string temp_file_name;
};
} // namespace media
#endif // MEDIA_BASE_MUXER_OPTIONS_H_

View File

@ -7,6 +7,8 @@
#ifndef PACKAGER_FILE_FILE_H_ #ifndef PACKAGER_FILE_FILE_H_
#define PACKAGER_FILE_FILE_H_ #define PACKAGER_FILE_FILE_H_
#include <string>
#include "base/basictypes.h" #include "base/basictypes.h"
namespace media { namespace media {
@ -55,6 +57,9 @@ class File {
// Return whether we're currently at eof. // Return whether we're currently at eof.
virtual bool Eof() = 0; virtual bool Eof() = 0;
// Return the file name.
const std::string& file_name() const { return file_name_; }
// ************************************************************ // ************************************************************
// * Static Methods: File-on-the-filesystem status // * Static Methods: File-on-the-filesystem status
// ************************************************************ // ************************************************************
@ -64,7 +69,7 @@ class File {
static int64 GetFileSize(const char* fname); static int64 GetFileSize(const char* fname);
protected: protected:
File() {} explicit File(const std::string& file_name) : file_name_(file_name) {}
// Do *not* call the destructor directly (with the "delete" keyword) // Do *not* call the destructor directly (with the "delete" keyword)
// nor use scoped_ptr; instead use Close(). // nor use scoped_ptr; instead use Close().
virtual ~File() {} virtual ~File() {}
@ -77,6 +82,7 @@ class File {
// LocalFile, MemFile based on prefix. // LocalFile, MemFile based on prefix.
static File* Create(const char* fname, const char* mode); static File* Create(const char* fname, const char* mode);
std::string file_name_;
DISALLOW_COPY_AND_ASSIGN(File); DISALLOW_COPY_AND_ASSIGN(File);
}; };

25
media/file/file_closer.h Normal file
View File

@ -0,0 +1,25 @@
// 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.
#ifndef MEDIA_FILE_FILE_CLOSER_H_
#define MEDIA_FILE_FILE_CLOSER_H_
#include "base/logging.h"
#include "media/file/file.h"
namespace media {
// Use by scoped_ptr to automatically close the file when it goes out of scope.
struct FileCloser {
inline void operator()(File* file) const {
if (file != NULL && !file->Close()) {
LOG(WARNING) << "Failed to close the file properly: "
<< file->file_name();
}
}
};
} // namespace media
#endif // MEDIA_FILE_FILE_CLOSER_H_

View File

@ -10,11 +10,11 @@
namespace media { namespace media {
LocalFile::LocalFile(const char* name, const char* mode) LocalFile::LocalFile(const char* name, const char* mode)
: File(), file_name_(name), file_mode_(mode), internal_file_(NULL) {} : File(name), file_mode_(mode), internal_file_(NULL) {}
bool LocalFile::Open() { bool LocalFile::Open() {
internal_file_ = internal_file_ =
file_util::OpenFile(base::FilePath(file_name_), file_mode_.c_str()); file_util::OpenFile(base::FilePath(file_name()), file_mode_.c_str());
return (internal_file_ != NULL); return (internal_file_ != NULL);
} }
@ -50,7 +50,7 @@ int64 LocalFile::Size() {
} }
int64 file_size; int64 file_size;
if (!file_util::GetFileSize(base::FilePath(file_name_), &file_size)) { if (!file_util::GetFileSize(base::FilePath(file_name()), &file_size)) {
LOG(ERROR) << "Cannot get file size."; LOG(ERROR) << "Cannot get file size.";
return -1; return -1;
} }

View File

@ -31,7 +31,6 @@ class LocalFile : public File {
virtual bool Open() OVERRIDE; virtual bool Open() OVERRIDE;
private: private:
std::string file_name_;
std::string file_mode_; std::string file_mode_;
FILE* internal_file_; FILE* internal_file_;

239
media/mp4/mp4_fragmenter.cc Normal file
View File

@ -0,0 +1,239 @@
// 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/mp4/mp4_fragmenter.h"
#include "media/base/aes_encryptor.h"
#include "media/base/buffer_reader.h"
#include "media/base/buffer_writer.h"
#include "media/base/encryptor_source.h"
#include "media/base/media_sample.h"
#include "media/mp4/box_definitions.h"
#include "media/mp4/cenc.h"
namespace {
const uint64 kInvalidTime = kuint64max;
// Optimize sample entries table. If all values in |entries| are identical,
// then |entries| is cleared and the value is assigned to |default_value|;
// otherwise it is a NOP. Return true if the table is optimized.
template <typename T>
bool OptimizeSampleEntries(std::vector<T>* entries, T* default_value) {
DCHECK(entries != NULL && default_value != NULL);
DCHECK(!entries->empty());
typename std::vector<T>::const_iterator it = entries->begin();
T value = *it;
for (; it < entries->end(); ++it)
if (value != *it)
return false;
// Clear |entries| if it contains only one value.
entries->clear();
*default_value = value;
return true;
}
} // namespace
namespace media {
namespace mp4 {
MP4Fragmenter::MP4Fragmenter(TrackFragment* traf,
EncryptorSource* encryptor_source,
int64 clear_time,
uint8 nalu_length_size)
: encryptor_source_(encryptor_source),
nalu_length_size_(nalu_length_size),
traf_(traf),
fragment_finalized_(false),
fragment_duration_(0),
earliest_presentation_time_(0),
first_sap_time_(0),
clear_time_(clear_time) {}
MP4Fragmenter::~MP4Fragmenter() {}
Status MP4Fragmenter::AddSample(scoped_refptr<MediaSample> sample) {
CHECK(sample->pts() >= sample->dts());
CHECK(sample->duration() > 0);
if (ShouldEncryptFragment()) {
Status status = EncryptSample(sample);
if (!status.ok())
return status;
}
// Fill in sample parameters. It will be optimized later.
traf_->runs[0].sample_sizes.push_back(sample->data_size());
traf_->runs[0].sample_durations.push_back(sample->duration());
traf_->runs[0].sample_flags.push_back(
sample->is_key_frame() ? 0 : TrackFragmentHeader::kNonKeySampleMask);
traf_->runs[0]
.sample_composition_time_offsets.push_back(sample->pts() - sample->dts());
if (sample->pts() != sample->dts()) {
traf_->runs[0].flags |=
TrackFragmentRun::kSampleCompTimeOffsetsPresentMask;
}
data_->AppendArray(sample->data(), sample->data_size());
fragment_duration_ += sample->duration();
if (earliest_presentation_time_ > sample->pts())
earliest_presentation_time_ = sample->pts();
if (sample->is_key_frame()) {
if (kInvalidTime == first_sap_time_)
first_sap_time_ = sample->pts();
}
return Status::OK;
}
void MP4Fragmenter::InitializeFragment() {
fragment_finalized_ = false;
traf_->decode_time.decode_time += fragment_duration_;
traf_->auxiliary_size.sample_info_sizes.clear();
traf_->auxiliary_offset.offsets.clear();
traf_->runs.clear();
traf_->runs.resize(1);
traf_->runs[0].flags = TrackFragmentRun::kDataOffsetPresentMask;
traf_->header.flags = TrackFragmentHeader::kDefaultBaseIsMoofMask;
fragment_duration_ = 0;
earliest_presentation_time_ = kInvalidTime;
first_sap_time_ = kInvalidTime;
data_.reset(new BufferWriter());
aux_data_.reset(new BufferWriter());
if (ShouldEncryptFragment()) {
if (!IsSubsampleEncryptionRequired()) {
DCHECK(encryptor_source_ != NULL);
traf_->auxiliary_size.default_sample_info_size =
encryptor_source_->encryptor()->iv().size();
}
}
}
void MP4Fragmenter::FinalizeFragment() {
if (ShouldEncryptFragment()) {
DCHECK(encryptor_source_ != NULL);
// The offset will be adjusted in Segmenter when we know moof size.
traf_->auxiliary_offset.offsets.push_back(0);
// Optimize saiz box.
SampleAuxiliaryInformationSize& saiz = traf_->auxiliary_size;
saiz.sample_count = traf_->runs[0].sample_sizes.size();
if (!saiz.sample_info_sizes.empty()) {
if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
&saiz.default_sample_info_size)) {
saiz.default_sample_info_size = 0;
}
}
} else if (encryptor_source_ && clear_time_ > 0) {
// This fragment should be in clear.
// We generate at most two sample description entries, encrypted entry and
// clear entry. The 1-based clear entry index is always 2.
const uint32 kClearSampleDescriptionIndex = 2;
traf_->header.flags |=
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
traf_->header.sample_description_index = kClearSampleDescriptionIndex;
clear_time_ -= fragment_duration_;
}
// Optimize trun box.
traf_->runs[0].sample_count = traf_->runs[0].sample_sizes.size();
if (OptimizeSampleEntries(&traf_->runs[0].sample_durations,
&traf_->header.default_sample_duration)) {
traf_->header.flags |=
TrackFragmentHeader::kDefaultSampleDurationPresentMask;
} else {
traf_->runs[0].flags |= TrackFragmentRun::kSampleDurationPresentMask;
}
if (OptimizeSampleEntries(&traf_->runs[0].sample_sizes,
&traf_->header.default_sample_size)) {
traf_->header.flags |= TrackFragmentHeader::kDefaultSampleSizePresentMask;
} else {
traf_->runs[0].flags |= TrackFragmentRun::kSampleSizePresentMask;
}
if (OptimizeSampleEntries(&traf_->runs[0].sample_flags,
&traf_->header.default_sample_flags)) {
traf_->header.flags |= TrackFragmentHeader::kDefaultSampleFlagsPresentMask;
} else {
traf_->runs[0].flags |= TrackFragmentRun::kSampleFlagsPresentMask;
}
fragment_finalized_ = true;
}
void MP4Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
// TODO(kqyang): support daisy chain??
reference->reference_type = false;
reference->subsegment_duration = fragment_duration_;
reference->starts_with_sap = StartsWithSAP();
if (kInvalidTime == first_sap_time_) {
reference->sap_type = SegmentReference::TypeUnknown;
reference->sap_delta_time = 0;
} else {
reference->sap_type = SegmentReference::Type1;
reference->sap_delta_time = first_sap_time_ - earliest_presentation_time_;
}
reference->earliest_presentation_time = earliest_presentation_time_;
}
void MP4Fragmenter::EncryptBytes(uint8* data, uint32 size) {
CHECK(encryptor_source_->encryptor()->Encrypt(data, size, data));
}
Status MP4Fragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
DCHECK(encryptor_source_ != NULL && encryptor_source_->encryptor() != NULL);
FrameCENCInfo cenc_info(encryptor_source_->encryptor()->iv());
uint8* data = sample->writable_data();
if (!IsSubsampleEncryptionRequired()) {
EncryptBytes(data, sample->data_size());
} else {
BufferReader reader(data, sample->data_size());
while (reader.HasBytes(1)) {
uint64 nalu_length;
if (!reader.ReadNBytesInto8(&nalu_length, nalu_length_size_))
return Status(error::MUXER_FAILURE, "Fail to read nalu_length.");
SubsampleEntry subsample;
subsample.clear_bytes = nalu_length_size_ + 1;
subsample.cypher_bytes = nalu_length - 1;
if (!reader.SkipBytes(nalu_length)) {
return Status(error::MUXER_FAILURE,
"Sample size does not match nalu_length.");
}
EncryptBytes(data + subsample.clear_bytes, subsample.cypher_bytes);
cenc_info.AddSubsample(subsample);
data += nalu_length_size_ + nalu_length;
}
// The length of per-sample auxiliary datum, defined in CENC ch. 7.
traf_->auxiliary_size.sample_info_sizes.push_back(cenc_info.ComputeSize());
}
cenc_info.Write(aux_data_.get());
encryptor_source_->encryptor()->UpdateIv();
return Status::OK;
}
bool MP4Fragmenter::StartsWithSAP() {
DCHECK(!traf_->runs.empty());
uint32 start_sample_flag;
if (traf_->runs[0].flags & TrackFragmentRun::kSampleFlagsPresentMask) {
DCHECK(!traf_->runs[0].sample_flags.empty());
start_sample_flag = traf_->runs[0].sample_flags[0];
} else {
DCHECK(traf_->header.flags &
TrackFragmentHeader::kDefaultSampleFlagsPresentMask);
start_sample_flag = traf_->header.default_sample_flags;
}
return (start_sample_flag & TrackFragmentHeader::kNonKeySampleMask) == 0;
}
} // namespace mp4
} // namespace media

View File

@ -0,0 +1,96 @@
// 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.
//
// MP4Fragmenter is responsible for the generation of MP4 fragments, i.e. traf
// and the corresponding mdat. The samples are also encrypted if encryption is
// requested.
#ifndef MEDIA_MP4_MP4_FRAGMENTER_H_
#define MEDIA_MP4_MP4_FRAGMENTER_H_
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "media/base/status.h"
namespace media {
class BufferWriter;
class EncryptorSource;
class MediaSample;
namespace mp4 {
class SegmentReference;
class TrackFragment;
class MP4Fragmenter {
public:
// Caller retains the ownership of |traf| and |encryptor_source|.
// |clear_time| specifies clear time in the current track timescale.
// |nalu_length_size| specifies NAL unit length size, for subsample
// encryption.
MP4Fragmenter(TrackFragment* traf,
EncryptorSource* encryptor_source,
int64 clear_time,
uint8 nalu_length_size);
~MP4Fragmenter();
virtual Status AddSample(scoped_refptr<MediaSample> sample);
// Initialize the fragment with default data.
void InitializeFragment();
// Finalize and optimize the fragment.
void FinalizeFragment();
// Fill in |reference| with current fragment information.
void GenerateSegmentReference(SegmentReference* reference);
uint64 fragment_duration() const { return fragment_duration_; }
uint64 first_sap_time() const { return first_sap_time_; }
uint64 earliest_presentation_time() const {
return earliest_presentation_time_;
}
bool fragment_finalized() const { return fragment_finalized_; }
BufferWriter* data() { return data_.get(); }
BufferWriter* aux_data() { return aux_data_.get(); }
private:
void EncryptBytes(uint8* data, uint32 size);
Status EncryptSample(scoped_refptr<MediaSample> sample);
// Should we enable encrytion for the current fragment?
bool ShouldEncryptFragment() {
return (encryptor_source_ != NULL && clear_time_ <= 0);
}
// Should we enable subsample encryption?
bool IsSubsampleEncryptionRequired() { return nalu_length_size_ != 0; }
// Check if the current fragment starts with SAP.
bool StartsWithSAP();
EncryptorSource* encryptor_source_;
// If this stream contains AVC, subsample encryption specifies that the size
// and type of NAL units remain unencrypted. This field specifies the size of
// the size field. Can be 1, 2 or 4 bytes.
uint8 nalu_length_size_;
TrackFragment* traf_;
bool fragment_finalized_;
uint64 fragment_duration_;
uint64 earliest_presentation_time_;
uint64 first_sap_time_;
int64 clear_time_;
scoped_ptr<BufferWriter> data_;
scoped_ptr<BufferWriter> aux_data_;
DISALLOW_COPY_AND_ASSIGN(MP4Fragmenter);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_MP4_FRAGMENTER_H_

View File

@ -0,0 +1,163 @@
// 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/mp4/mp4_general_segmenter.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "media/base/buffer_writer.h"
#include "media/base/media_stream.h"
#include "media/base/muxer_options.h"
#include "media/file/file.h"
#include "media/mp4/box_definitions.h"
namespace media {
namespace mp4 {
MP4GeneralSegmenter::MP4GeneralSegmenter(const MuxerOptions& options,
FileType* ftyp,
Movie* moov)
: MP4Segmenter(options, ftyp, moov),
styp_(new SegmentType),
num_segments_(0) {
// Use the same brands for styp as ftyp.
styp_->major_brand = ftyp->major_brand;
styp_->compatible_brands = ftyp->compatible_brands;
}
MP4GeneralSegmenter::~MP4GeneralSegmenter() {}
Status MP4GeneralSegmenter::Initialize(
EncryptorSource* encryptor_source,
const std::vector<MediaStream*>& streams) {
Status status = MP4Segmenter::Initialize(encryptor_source, streams);
if (!status.ok())
return status;
DCHECK(ftyp() != NULL && moov() != NULL);
// Generate the output file with init segment.
File* file = File::Open(options().output_file_name.c_str(), "w");
if (file == NULL) {
return Status(error::FILE_FAILURE,
"Cannot open file for write " + options().output_file_name);
}
scoped_ptr<BufferWriter> buffer(new BufferWriter);
ftyp()->Write(buffer.get());
moov()->Write(buffer.get());
status = buffer->WriteToFile(file);
if (!file->Close()) {
LOG(WARNING) << "Failed to close the file properly: "
<< options().output_file_name;
}
return status;
}
Status MP4GeneralSegmenter::FinalizeSegment() {
Status status = MP4Segmenter::FinalizeSegment();
if (!status.ok())
return status;
DCHECK(sidx() != NULL);
// earliest_presentation_time is the earliest presentation time of any
// access unit in the reference stream in the first subsegment.
// It will be re-calculated later when subsegments are finalized.
sidx()->earliest_presentation_time =
sidx()->references[0].earliest_presentation_time;
if (options().num_subsegments_per_sidx <= 0)
return WriteSegment();
// sidx() contains pre-generated segment references with one reference per
// fragment. Calculate |num_fragments_per_subsegment| and combine
// pre-generated references into final subsegment references.
uint32 num_fragments = sidx()->references.size();
uint32 num_fragments_per_subsegment =
(num_fragments - 1) / options().num_subsegments_per_sidx + 1;
if (num_fragments_per_subsegment <= 1)
return WriteSegment();
uint32 frag_index = 0;
uint32 subseg_index = 0;
std::vector<SegmentReference>& refs = sidx()->references;
uint64 first_sap_time =
refs[0].sap_delta_time + refs[0].earliest_presentation_time;
for (uint32 i = 1; i < num_fragments; ++i) {
refs[subseg_index].referenced_size += refs[i].referenced_size;
refs[subseg_index].subsegment_duration += refs[i].subsegment_duration;
refs[subseg_index].earliest_presentation_time =
std::min(refs[subseg_index].earliest_presentation_time,
refs[i].earliest_presentation_time);
if (refs[subseg_index].sap_type == SegmentReference::TypeUnknown &&
refs[i].sap_type != SegmentReference::TypeUnknown) {
refs[subseg_index].sap_type = refs[i].sap_type;
first_sap_time =
refs[i].sap_delta_time + refs[i].earliest_presentation_time;
}
if (++frag_index >= num_fragments_per_subsegment) {
// Calculate sap delta time w.r.t. sidx_->earliest_presentation_time.
if (refs[subseg_index].sap_type != SegmentReference::TypeUnknown) {
refs[subseg_index].sap_delta_time =
first_sap_time - refs[subseg_index].earliest_presentation_time;
}
if (++i >= num_fragments)
break;
refs[++subseg_index] = refs[i];
first_sap_time =
refs[i].sap_delta_time + refs[i].earliest_presentation_time;
frag_index = 1;
}
}
refs.resize(options().num_subsegments_per_sidx);
// earliest_presentation_time is the earliest presentation time of any
// access unit in the reference stream in the first subsegment.
sidx()->earliest_presentation_time = refs[0].earliest_presentation_time;
return WriteSegment();
}
Status MP4GeneralSegmenter::WriteSegment() {
DCHECK(sidx() != NULL && fragment_buffer() != NULL && styp_ != NULL);
scoped_ptr<BufferWriter> buffer(new BufferWriter());
File* file;
std::string file_name;
if (options().segment_template.empty()) {
// Append the segment to output file if segment template is not specified.
file_name = options().output_file_name.c_str();
file = File::Open(file_name.c_str(), "a+");
if (file == NULL) {
return Status(
error::FILE_FAILURE,
"Cannot open file for append " + options().output_file_name);
}
} else {
// TODO(kqyang): generate the segment template name.
file_name = options().segment_template;
ReplaceSubstringsAfterOffset(
&file_name, 0, "$Number$", base::UintToString(++num_segments_));
file = File::Open(file_name.c_str(), "w");
if (file == NULL) {
return Status(error::FILE_FAILURE,
"Cannot open file for write " + file_name);
}
styp_->Write(buffer.get());
}
// If num_subsegments_per_sidx is negative, no SIDX box is generated.
if (options().num_subsegments_per_sidx >= 0)
sidx()->Write(buffer.get());
Status status = buffer->WriteToFile(file);
if (status.ok())
status = fragment_buffer()->WriteToFile(file);
if (!file->Close())
LOG(WARNING) << "Failed to close the file properly: " << file_name;
return status;
}
} // namespace mp4
} // namespace media

View File

@ -0,0 +1,55 @@
// 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.
//
// Segmenter for MP4 live, main and simple profiles. The generated media file
// could contain one to many segments with segment duration defined by
// |MuxerOptions.segment_duration|. A segment could contain one to many
// subsegments defined by |num_subsegments_per_sidx|. A subsegment could
// contain one to many fragments with fragment duration defined by
// |MuxerOptions.fragment_duration|. The actual segment or fragment duration
// may not match the defined duration exactly but in a best effort basic, i.e.
// the segmenter tries to end segment/fragment at the first sample with
// overall segment/fragment duration not smaller than defined duration and
// yet meet SAP requirements. The generated segments are written into files
// defined by |MuxerOptions.segment_template| if it is defined; otherwise,
// the segments are appended to the main output file defined by
// |MuxerOptions.output_file_name|.
#ifndef MEDIA_MP4_MP4_GENERAL_SEGMENTER_H_
#define MEDIA_MP4_MP4_GENERAL_SEGMENTER_H_
#include "media/mp4/mp4_segmenter.h"
namespace media {
namespace mp4 {
struct SegmentType;
class MP4GeneralSegmenter : public MP4Segmenter {
public:
// Caller transfers the ownership of |ftyp| and |moov| to this class.
MP4GeneralSegmenter(const MuxerOptions& options, FileType* ftyp, Movie* moov);
~MP4GeneralSegmenter();
// MP4Segmenter implementations.
virtual Status Initialize(EncryptorSource* encryptor_source,
const std::vector<MediaStream*>& streams) OVERRIDE;
protected:
virtual Status FinalizeSegment() OVERRIDE;
private:
// Write segment to file.
Status WriteSegment();
scoped_ptr<SegmentType> styp_;
uint32 num_segments_;
DISALLOW_COPY_AND_ASSIGN(MP4GeneralSegmenter);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_MP4_GENERAL_SEGMENTER_H_

223
media/mp4/mp4_muxer.cc Normal file
View File

@ -0,0 +1,223 @@
// 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/mp4/mp4_muxer.h"
#include "base/time/time.h"
#include "media/base/aes_encryptor.h"
#include "media/base/audio_stream_info.h"
#include "media/base/encryptor_source.h"
#include "media/base/media_sample.h"
#include "media/base/media_stream.h"
#include "media/base/video_stream_info.h"
#include "media/file/file.h"
#include "media/mp4/box_definitions.h"
#include "media/mp4/es_descriptor.h"
#include "media/mp4/fourccs.h"
#include "media/mp4/mp4_general_segmenter.h"
#include "media/mp4/mp4_vod_segmenter.h"
namespace {
// The version of cenc implemented here. CENC 4.
const int kCencSchemeVersion = 0x00010000;
// Get time in seconds since midnight, Jan. 1, 1904, in UTC Time.
uint64 IsoTimeNow() {
// Time in seconds from Jan. 1, 1904 to epoch time, i.e. Jan. 1, 1970.
const uint64 kIsomTimeOffset = 2082844800l;
return kIsomTimeOffset + base::Time::Now().ToDoubleT();
}
} // namespace
namespace media {
namespace mp4 {
MP4Muxer::MP4Muxer(const MuxerOptions& options,
EncryptorSource* encryptor_source)
: Muxer(options, encryptor_source) {}
MP4Muxer::~MP4Muxer() {}
Status MP4Muxer::Initialize() {
DCHECK(!streams().empty());
scoped_ptr<FileType> ftyp(new FileType);
scoped_ptr<Movie> moov(new Movie);
ftyp->major_brand = FOURCC_DASH;
ftyp->compatible_brands.push_back(FOURCC_ISO6);
ftyp->compatible_brands.push_back(FOURCC_MP41);
if (streams().size() == 1 &&
streams()[0]->info()->stream_type() == kStreamVideo)
ftyp->compatible_brands.push_back(FOURCC_AVC1);
moov->header.creation_time = IsoTimeNow();
moov->header.modification_time = IsoTimeNow();
moov->header.next_track_id = streams().size() + 1;
moov->tracks.resize(streams().size());
moov->extends.tracks.resize(streams().size());
// Initialize tracks.
for (uint32 i = 0; i < streams().size(); ++i) {
Track& trak = moov->tracks[i];
trak.header.track_id = i + 1;
TrackExtends& trex = moov->extends.tracks[i];
trex.track_id = trak.header.track_id;
trex.default_sample_description_index = 1;
switch (streams()[i]->info()->stream_type()) {
case kStreamVideo:
GenerateVideoTrak(
static_cast<VideoStreamInfo*>(streams()[i]->info().get()),
&trak,
i + 1);
break;
case kStreamAudio:
GenerateAudioTrak(
static_cast<AudioStreamInfo*>(streams()[i]->info().get()),
&trak,
i + 1);
break;
default:
NOTIMPLEMENTED() << "Not implemented for stream type: "
<< streams()[i]->info()->stream_type();
}
}
if (IsEncryptionRequired()) {
moov->pssh.resize(1);
GeneratePssh(&moov->pssh[0]);
}
if (options().single_segment) {
segmenter_.reset(
new MP4VODSegmenter(options(), ftyp.release(), moov.release()));
} else {
segmenter_.reset(
new MP4GeneralSegmenter(options(), ftyp.release(), moov.release()));
}
return segmenter_->Initialize(encryptor_source(), streams());
}
Status MP4Muxer::Finalize() {
DCHECK(segmenter_ != NULL);
return segmenter_->Finalize();
}
Status MP4Muxer::AddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample) {
DCHECK(segmenter_ != NULL);
return segmenter_->AddSample(stream, sample);
}
void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) {
uint64 now = IsoTimeNow();
trak->header.creation_time = now;
trak->header.modification_time = now;
trak->header.duration = 0;
trak->media.header.creation_time = now;
trak->media.header.modification_time = now;
trak->media.header.timescale = info->time_scale();
trak->media.header.duration = 0;
if (!info->language().empty()) {
DCHECK_EQ(info->language().size(),
arraysize(trak->media.header.language) - 1);
strcpy(trak->media.header.language, info->language().c_str());
trak->media.header.language[info->language().size()] = '\0';
}
}
void MP4Muxer::GenerateVideoTrak(const VideoStreamInfo* video_info,
Track* trak,
uint32 track_id) {
InitializeTrak(video_info, trak);
trak->header.width = video_info->width();
trak->header.height = video_info->height();
trak->media.handler.type = kVideo;
VideoSampleEntry video;
video.format = FOURCC_AVC1;
video.width = video_info->width();
video.height = video_info->height();
video.avcc.data = video_info->extra_data();
SampleDescription& sample_description =
trak->media.information.sample_table.description;
sample_description.type = kVideo;
sample_description.video_entries.push_back(video);
if (IsEncryptionRequired()) {
DCHECK(encryptor_source() != NULL);
// Add a second entry for clear content if needed.
if (encryptor_source()->clear_milliseconds() > 0)
sample_description.video_entries.push_back(video);
VideoSampleEntry& encrypted_video = sample_description.video_entries[0];
GenerateSinf(&encrypted_video.sinf, encrypted_video.format);
encrypted_video.format = FOURCC_ENCV;
}
}
void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
Track* trak,
uint32 track_id) {
InitializeTrak(audio_info, trak);
trak->header.volume = 0x100;
trak->media.handler.type = kAudio;
AudioSampleEntry audio;
audio.format = FOURCC_MP4A;
audio.channelcount = audio_info->num_channels();
audio.samplesize = audio_info->sample_bits();
audio.samplerate = audio_info->sampling_frequency();
if (kCodecAAC) {
audio.esds.es_descriptor.set_object_type(kISO_14496_3); // MPEG4 AAC.
} else {
// Do we need to support other types?
NOTIMPLEMENTED();
}
audio.esds.es_descriptor.set_esid(track_id);
audio.esds.es_descriptor.set_decoder_specific_info(audio_info->extra_data());
SampleDescription& sample_description =
trak->media.information.sample_table.description;
sample_description.type = kAudio;
sample_description.audio_entries.push_back(audio);
if (IsEncryptionRequired()) {
DCHECK(encryptor_source() != NULL);
// Add a second entry for clear content if needed.
if (encryptor_source()->clear_milliseconds() > 0)
sample_description.audio_entries.push_back(audio);
AudioSampleEntry& encrypted_audio = sample_description.audio_entries[0];
GenerateSinf(&encrypted_audio.sinf, encrypted_audio.format);
encrypted_audio.format = FOURCC_ENCA;
}
}
void MP4Muxer::GeneratePssh(ProtectionSystemSpecificHeader* pssh) {
DCHECK(encryptor_source() != NULL);
pssh->system_id = encryptor_source()->key_system_id();
pssh->data = encryptor_source()->pssh();
}
void MP4Muxer::GenerateSinf(ProtectionSchemeInfo* sinf, FourCC old_type) {
DCHECK(encryptor_source() != NULL);
DCHECK(encryptor_source()->encryptor() != NULL);
sinf->format.format = old_type;
sinf->type.type = FOURCC_CENC;
sinf->type.version = kCencSchemeVersion;
sinf->info.track_encryption.is_encrypted = true;
sinf->info.track_encryption.default_iv_size =
encryptor_source()->encryptor()->iv().size();
sinf->info.track_encryption.default_kid = encryptor_source()->key_id();
}
} // namespace mp4
} // namespace media

65
media/mp4/mp4_muxer.h Normal file
View File

@ -0,0 +1,65 @@
// 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.
//
// Implements MP4 Muxer.
#ifndef MEDIA_MP4_MP4_MUXER_H_
#define MEDIA_MP4_MP4_MUXER_H_
#include "media/base/muxer.h"
#include "media/mp4/fourccs.h"
namespace media {
class AudioStreamInfo;
class StreamInfo;
class VideoStreamInfo;
namespace mp4 {
class MP4Segmenter;
struct ProtectionSchemeInfo;
struct ProtectionSystemSpecificHeader;
struct Track;
class MP4Muxer : public Muxer {
public:
MP4Muxer(const MuxerOptions& options, EncryptorSource* encryptor_source);
virtual ~MP4Muxer();
// Muxer implementations.
virtual Status Initialize() OVERRIDE;
virtual Status Finalize() OVERRIDE;
virtual Status AddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample) OVERRIDE;
private:
// Generate Audio/Video Track atom.
void InitializeTrak(const StreamInfo* info, Track* trak);
void GenerateAudioTrak(const AudioStreamInfo* audio_info,
Track* trak,
uint32 track_id);
void GenerateVideoTrak(const VideoStreamInfo* video_info,
Track* trak,
uint32 track_id);
// Generate Pssh atom.
void GeneratePssh(ProtectionSystemSpecificHeader* pssh);
// Generates a sinf atom with CENC encryption parameters.
void GenerateSinf(ProtectionSchemeInfo* sinf, FourCC old_type);
// Should we enable encrytion?
bool IsEncryptionRequired() { return (encryptor_source() != NULL); }
scoped_ptr<MP4Segmenter> segmenter_;
DISALLOW_COPY_AND_ASSIGN(MP4Muxer);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_MP4_MUXER_H_

244
media/mp4/mp4_segmenter.cc Normal file
View File

@ -0,0 +1,244 @@
// 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/mp4/mp4_segmenter.h"
#include <algorithm>
#include "base/stl_util.h"
#include "media/base/buffer_writer.h"
#include "media/base/encryptor_source.h"
#include "media/base/media_sample.h"
#include "media/base/media_stream.h"
#include "media/base/muxer_options.h"
#include "media/base/video_stream_info.h"
#include "media/mp4/box_definitions.h"
#include "media/mp4/mp4_fragmenter.h"
namespace {
uint64 Rescale(uint64 time_in_old_scale, uint32 old_scale, uint32 new_scale) {
return static_cast<double>(time_in_old_scale) / old_scale * new_scale;
}
} // namespace
namespace media {
namespace mp4 {
MP4Segmenter::MP4Segmenter(const MuxerOptions& options,
FileType* ftyp,
Movie* moov)
: options_(options),
fragment_buffer_(new BufferWriter()),
ftyp_(ftyp),
moov_(moov),
moof_(new MovieFragment()),
sidx_(new SegmentIndex()),
segment_initialized_(false),
end_of_segment_(false) {}
MP4Segmenter::~MP4Segmenter() { STLDeleteElements(&fragmenters_); }
Status MP4Segmenter::Initialize(EncryptorSource* encryptor_source,
const std::vector<MediaStream*>& streams) {
DCHECK_LT(0, streams.size());
moof_->header.sequence_number = 0;
moof_->tracks.resize(streams.size());
segment_durations_.resize(streams.size());
fragmenters_.resize(streams.size());
for (uint32 i = 0; i < streams.size(); ++i) {
stream_map_[streams[i]] = i;
moof_->tracks[i].header.track_id = i + 1;
uint8 nalu_length_size = 0;
if (streams[i]->info()->stream_type() == kStreamVideo) {
VideoStreamInfo* video =
static_cast<VideoStreamInfo*>(streams[i]->info().get());
nalu_length_size = video->nalu_length_size();
// We use the first video stream as the reference stream.
if (sidx_->reference_id == 0)
sidx_->reference_id = i + 1;
}
int64 clear_time = 0;
if (encryptor_source) {
clear_time = encryptor_source->clear_milliseconds() / 1000.0 *
streams[i]->info()->time_scale();
}
fragmenters_[i] = new MP4Fragmenter(
&moof_->tracks[i], encryptor_source, clear_time, nalu_length_size);
}
// Choose the first stream if there is no VIDEO.
if (sidx_->reference_id == 0)
sidx_->reference_id = 1;
sidx_->timescale = streams[GetReferenceStreamId()]->info()->time_scale();
// Use the reference stream's time scale as movie time scale.
moov_->header.timescale = sidx_->timescale;
InitializeFragments();
return Status::OK;
}
Status MP4Segmenter::Finalize() {
end_of_segment_ = true;
for (std::vector<MP4Fragmenter*>::iterator it = fragmenters_.begin();
it != fragmenters_.end();
++it) {
Status status = FinalizeFragment(*it);
if (!status.ok())
return status;
}
// Set tracks and moov durations.
// Note that the updated moov box will be written to output file for VOD case
// only.
for (std::vector<Track>::iterator track = moov_->tracks.begin();
track != moov_->tracks.end();
++track) {
track->header.duration = Rescale(track->media.header.duration,
track->media.header.timescale,
moov_->header.timescale);
if (track->header.duration > moov_->header.duration)
moov_->header.duration = track->header.duration;
}
return Status::OK;
}
Status MP4Segmenter::AddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample) {
// Find the fragmenter for this stream.
DCHECK(stream != NULL && stream_map_.find(stream) != stream_map_.end());
uint32 stream_id = stream_map_[stream];
MP4Fragmenter* fragmenter = fragmenters_[stream_id];
// Set default sample duration if it has not been set yet.
if (moov_->extends.tracks[stream_id].default_sample_duration == 0) {
moov_->extends.tracks[stream_id].default_sample_duration =
sample->duration();
}
if (!segment_initialized_) {
InitializeSegment();
segment_initialized_ = true;
}
if (fragmenter->fragment_finalized()) {
return Status(error::FRAGMENT_FINALIZED,
"Current fragment is finalized already.");
}
bool finalize_fragment = false;
if (fragmenter->fragment_duration() >=
options_.fragment_duration * stream->info()->time_scale()) {
if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
finalize_fragment = true;
}
}
if (segment_durations_[stream_id] >=
options_.segment_duration * stream->info()->time_scale()) {
if (sample->is_key_frame() || !options_.segment_sap_aligned) {
end_of_segment_ = true;
finalize_fragment = true;
}
}
Status status;
if (finalize_fragment) {
status = FinalizeFragment(fragmenter);
if (!status.ok())
return status;
}
status = fragmenter->AddSample(sample);
if (!status.ok())
return status;
moov_->tracks[stream_id].media.header.duration += sample->duration();
segment_durations_[stream_id] += sample->duration();
return Status::OK;
}
void MP4Segmenter::InitializeSegment() {
sidx_->references.clear();
end_of_segment_ = false;
std::vector<uint64>::iterator it = segment_durations_.begin();
for (; it != segment_durations_.end(); ++it)
*it = 0;
}
Status MP4Segmenter::FinalizeSegment() {
segment_initialized_ = false;
return Status::OK;
}
uint32 MP4Segmenter::GetReferenceStreamId() {
DCHECK(sidx_ != NULL);
return sidx_->reference_id - 1;
}
void MP4Segmenter::InitializeFragments() {
++moof_->header.sequence_number;
for (std::vector<MP4Fragmenter*>::iterator it = fragmenters_.begin();
it != fragmenters_.end();
++it) {
(*it)->InitializeFragment();
}
}
Status MP4Segmenter::FinalizeFragment(MP4Fragmenter* fragmenter) {
fragmenter->FinalizeFragment();
// Check if all tracks are ready for fragmentation.
for (std::vector<MP4Fragmenter*>::iterator it = fragmenters_.begin();
it != fragmenters_.end();
++it) {
if (!(*it)->fragment_finalized())
return Status::OK;
}
MediaData mdat;
// Fill in data offsets. Data offset base is moof size + mdat box size.
// (mdat is still empty, mdat size is the same as mdat box size).
uint64 base = moof_->ComputeSize() + mdat.ComputeSize();
for (uint i = 0; i < moof_->tracks.size(); ++i) {
TrackFragment& traf = moof_->tracks[i];
MP4Fragmenter* fragmenter = fragmenters_[i];
if (fragmenter->aux_data()->Size() > 0) {
traf.auxiliary_offset.offsets[0] += base;
base += fragmenter->aux_data()->Size();
}
traf.runs[0].data_offset += base;
base += fragmenter->data()->Size();
}
// Generate segment reference.
sidx_->references.resize(sidx_->references.size() + 1);
fragmenters_[GetReferenceStreamId()]->GenerateSegmentReference(
&sidx_->references[sidx_->references.size() - 1]);
sidx_->references[sidx_->references.size() - 1].referenced_size = base;
// Write the fragment to buffer.
moof_->Write(fragment_buffer_.get());
for (uint i = 0; i < moof_->tracks.size(); ++i) {
MP4Fragmenter* fragmenter = fragmenters_[i];
mdat.data_size =
fragmenter->aux_data()->Size() + fragmenter->data()->Size();
mdat.Write(fragment_buffer_.get());
if (fragmenter->aux_data()->Size()) {
fragment_buffer_->AppendBuffer(*fragmenter->aux_data());
}
fragment_buffer_->AppendBuffer(*fragmenter->data());
}
InitializeFragments();
if (end_of_segment_)
return FinalizeSegment();
return Status::OK;
}
} // namespace mp4
} // namespace media

90
media/mp4/mp4_segmenter.h Normal file
View File

@ -0,0 +1,90 @@
// 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.
//
// This class defines the MP4 Segmenter which is responsible for organizing
// MP4 fragments into segments/subsegments and package into a MP4 file.
// Inherited by MP4GeneralSegmenter and MP4VODSegmenter. MP4VODSegmenter defines
// the segmenter for DASH Video-On-Demand with a single segment for each media
// presentation while MP4GeneralSegmenter handles all other cases including
// DASH live profile.
#ifndef MEDIA_MP4_MP4_SEGMENTER_H_
#define MEDIA_MP4_MP4_SEGMENTER_H_
#include <map>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "media/base/status.h"
namespace media {
struct MuxerOptions;
class BufferWriter;
class EncryptorSource;
class MediaSample;
class MediaStream;
namespace mp4 {
class MP4Fragmenter;
struct FileType;
struct Movie;
struct MovieFragment;
struct SegmentIndex;
class MP4Segmenter {
public:
// Caller transfers the ownership of |ftyp| and |moov| to this class.
MP4Segmenter(const MuxerOptions& options, FileType* ftyp, Movie* moov);
virtual ~MP4Segmenter();
// Initialize the segmenter. Caller retains the ownership of
// |encryptor_source|. |encryptor_source| can be NULL.
virtual Status Initialize(EncryptorSource* encryptor_source,
const std::vector<MediaStream*>& streams);
virtual Status Finalize();
virtual Status AddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample);
protected:
void InitializeSegment();
virtual Status FinalizeSegment();
uint32 GetReferenceStreamId();
const MuxerOptions& options() const { return options_; }
FileType* ftyp() { return ftyp_.get(); }
Movie* moov() { return moov_.get(); }
BufferWriter* fragment_buffer() { return fragment_buffer_.get(); }
SegmentIndex* sidx() { return sidx_.get(); }
private:
void InitializeFragments();
Status FinalizeFragment(MP4Fragmenter* fragment);
const MuxerOptions& options_;
scoped_ptr<FileType> ftyp_;
scoped_ptr<Movie> moov_;
scoped_ptr<MovieFragment> moof_;
scoped_ptr<BufferWriter> fragment_buffer_;
scoped_ptr<SegmentIndex> sidx_;
std::vector<MP4Fragmenter*> fragmenters_;
std::vector<uint64> segment_durations_;
std::map<const MediaStream*, uint32> stream_map_;
bool segment_initialized_;
bool end_of_segment_;
DISALLOW_COPY_AND_ASSIGN(MP4Segmenter);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_MP4_SEGMENTER_H_

View File

@ -0,0 +1,140 @@
// 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/mp4/mp4_vod_segmenter.h"
#include "media/base/buffer_writer.h"
#include "media/base/media_stream.h"
#include "media/base/muxer_options.h"
#include "media/file/file.h"
#include "media/mp4/box_definitions.h"
namespace media {
namespace mp4 {
MP4VODSegmenter::MP4VODSegmenter(const MuxerOptions& options,
FileType* ftyp,
Movie* moov)
: MP4Segmenter(options, ftyp, moov) {}
MP4VODSegmenter::~MP4VODSegmenter() {}
Status MP4VODSegmenter::Initialize(EncryptorSource* encryptor_source,
const std::vector<MediaStream*>& streams) {
Status status = MP4Segmenter::Initialize(encryptor_source, streams);
if (!status.ok())
return status;
temp_file_.reset(File::Open(options().temp_file_name.c_str(), "w"));
if (temp_file_ == NULL) {
return Status(error::FILE_FAILURE,
"Cannot open file to write " + options().temp_file_name);
}
return Status::OK;
}
Status MP4VODSegmenter::Finalize() {
DCHECK(temp_file_ != NULL && ftyp() != NULL && moov() != NULL &&
vod_sidx_ != NULL);
Status status = MP4Segmenter::Finalize();
if (!status.ok())
return status;
// Close the temp file to prepare for reading later.
if (!temp_file_.release()->Close()) {
return Status(error::FILE_FAILURE,
"Cannot close the temp file " + options().temp_file_name);
}
scoped_ptr<File, FileCloser> file(
File::Open(options().output_file_name.c_str(), "w"));
if (file == NULL) {
return Status(error::FILE_FAILURE,
"Cannot open file to write " + options().output_file_name);
}
// Write ftyp, moov and sidx to output file.
scoped_ptr<BufferWriter> buffer(new BufferWriter());
ftyp()->Write(buffer.get());
moov()->Write(buffer.get());
vod_sidx_->Write(buffer.get());
status = buffer->WriteToFile(file.get());
if (!status.ok())
return status;
// Load the temp file and write to output file.
scoped_ptr<File, FileCloser> temp_file(
File::Open(options().temp_file_name.c_str(), "r"));
if (temp_file == NULL) {
return Status(error::FILE_FAILURE,
"Cannot open file to read " + options().temp_file_name);
}
const int kBufSize = 0x40000; // 256KB.
scoped_ptr<uint8[]> buf(new uint8[kBufSize]);
while (!temp_file->Eof()) {
int64 size = temp_file->Read(buf.get(), kBufSize);
if (size <= 0) {
return Status(error::FILE_FAILURE,
"Failed to read file " + options().temp_file_name);
}
int64 size_written = file->Write(buf.get(), size);
if (size_written != size) {
return Status(error::FILE_FAILURE,
"Failed to write file " + options().output_file_name);
}
}
return Status::OK;
}
Status MP4VODSegmenter::FinalizeSegment() {
Status status = MP4Segmenter::FinalizeSegment();
if (!status.ok())
return status;
DCHECK(sidx() != NULL && fragment_buffer() != NULL);
// sidx() contains pre-generated segment references with one reference per
// fragment. In VOD, this segment is converted into a subsegment, i.e. one
// reference, which contains all the fragments in sidx().
std::vector<SegmentReference>& refs = sidx()->references;
SegmentReference& vod_ref = refs[0];
uint64 first_sap_time =
refs[0].sap_delta_time + refs[0].earliest_presentation_time;
for (uint32 i = 1; i < sidx()->references.size(); ++i) {
vod_ref.referenced_size += refs[i].referenced_size;
// TODO(kqyang): should we calculate subsegment duration by subtracting
// earliest_presentation time instead?
vod_ref.subsegment_duration += refs[i].subsegment_duration;
vod_ref.earliest_presentation_time = std::min(
vod_ref.earliest_presentation_time, refs[i].earliest_presentation_time);
if (vod_ref.sap_type == SegmentReference::TypeUnknown &&
refs[i].sap_type != SegmentReference::TypeUnknown) {
vod_ref.sap_type = refs[i].sap_type;
first_sap_time =
refs[i].sap_delta_time + refs[i].earliest_presentation_time;
}
}
// Calculate sap delta time w.r.t. earliest_presentation_time.
if (vod_ref.sap_type != SegmentReference::TypeUnknown) {
vod_ref.sap_delta_time =
first_sap_time - vod_ref.earliest_presentation_time;
}
// Create segment if it does not exist yet.
if (vod_sidx_ == NULL) {
vod_sidx_.reset(new SegmentIndex());
vod_sidx_->reference_id = sidx()->reference_id;
vod_sidx_->timescale = sidx()->timescale;
// earliest_presentation_time is the earliest presentation time of any
// access unit in the reference stream in the first subsegment.
vod_sidx_->earliest_presentation_time = vod_ref.earliest_presentation_time;
}
vod_sidx_->references.push_back(vod_ref);
// Append fragment buffer to temp file.
return fragment_buffer()->WriteToFile(temp_file_.get());
}
} // namespace mp4
} // namespace media

View File

@ -0,0 +1,50 @@
// 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.
//
// Segmenter for MP4 Dash Video-On-Demand profile. A single MP4 file with a
// single segment is created, i.e. with only one SIDX box. The generated media
// file could contain one to many subsegments with subsegment duration
// defined by|MuxerOptions.segment_duration|. A subsegment could contain one
// to many fragments with fragment duration defined by
// |MuxerOptions.fragment_duration|. The actual subsegment or fragment duration
// may not match the defined duration exactly but in a best effort basic, i.e.
// the segmenter tries to end subsegment/fragment at the first sample with
// overall subsegment/fragment duration not smaller than defined duration and
// yet meet SAP requirements. VOD segmenter ignores
// |MuxerOptions.num_subsegments_per_sidx|.
#ifndef MEDIA_MP4_MP4_VOD_SEGMENTER_H_
#define MEDIA_MP4_MP4_VOD_SEGMENTER_H_
#include "media/file/file_closer.h"
#include "media/mp4/mp4_segmenter.h"
namespace media {
namespace mp4 {
class MP4VODSegmenter : public MP4Segmenter {
public:
// Caller transfers the ownership of |ftyp| and |moov| to this class.
MP4VODSegmenter(const MuxerOptions& options, FileType* ftyp, Movie* moov);
~MP4VODSegmenter();
// MP4Segmenter implementations.
virtual Status Initialize(EncryptorSource* encryptor_source,
const std::vector<MediaStream*>& streams) OVERRIDE;
virtual Status Finalize() OVERRIDE;
protected:
virtual Status FinalizeSegment() OVERRIDE;
private:
scoped_ptr<SegmentIndex> vod_sidx_;
scoped_ptr<File, FileCloser> temp_file_;
DISALLOW_COPY_AND_ASSIGN(MP4VODSegmenter);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_MP4_VOD_SEGMENTER_H_

View File

@ -2,34 +2,57 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/strings/string_number_conversions.h"
#include "media/base/demuxer.h" #include "media/base/demuxer.h"
#include "media/base/fixed_encryptor_source.h"
#include "media/base/media_sample.h" #include "media/base/media_sample.h"
#include "media/base/media_stream.h" #include "media/base/media_stream.h"
#include "media/base/muxer.h" #include "media/base/muxer.h"
#include "media/base/muxer_options.h"
#include "media/base/status_test_util.h" #include "media/base/status_test_util.h"
#include "media/base/stream_info.h" #include "media/base/stream_info.h"
#include "media/base/test_data_util.h" #include "media/base/test_data_util.h"
#include "media/mp4/mp4_muxer.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
using ::testing::Combine;
using ::testing::Values;
using ::testing::ValuesIn;
namespace { namespace {
const char* kMediaFiles[] = {"bear-1280x720.mp4", "bear-1280x720-av_frag.mp4"}; const char* kMediaFiles[] = {"bear-1280x720.mp4", "bear-1280x720-av_frag.mp4"};
}
// Encryption constants.
const char kKeyIdHex[] = "e5007e6e9dcd5ac095202ed3758382cd";
const char kKeyHex[] = "6fc96fe628a265b13aeddec0bc421f4d";
const char kPsshHex[] =
"08011210e5007e6e9dcd5ac095202ed3"
"758382cd1a0d7769646576696e655f746573742211544553545f"
"434f4e54454e545f49445f312a025344";
const uint32 kClearMilliseconds = 1500;
} // namespace
namespace media { namespace media {
class TestingMuxer : public Muxer { class TestingMuxer : public Muxer {
public: public:
TestingMuxer(const Options& options, EncryptorSource* encryptor_source) TestingMuxer(const MuxerOptions& options, EncryptorSource* encryptor_source)
: Muxer(options, encryptor_source) {} : Muxer(options, encryptor_source) {}
virtual Status Initialize() OVERRIDE {
DVLOG(1) << "Initialize is called.";
return Status::OK;
}
virtual Status AddSample(const MediaStream* stream, virtual Status AddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample) { scoped_refptr<MediaSample> sample) OVERRIDE {
DVLOG(1) << "Add Sample: " << sample->ToString(); DVLOG(1) << "Add Sample: " << sample->ToString();
DVLOG(2) << "To Stream: " << stream->ToString(); DVLOG(2) << "To Stream: " << stream->ToString();
return Status::OK; return Status::OK;
} }
virtual Status Finalize() { virtual Status Finalize() OVERRIDE {
DVLOG(1) << "Finalize is called."; DVLOG(1) << "Finalize is called.";
return Status::OK; return Status::OK;
} }
@ -38,31 +61,80 @@ class TestingMuxer : public Muxer {
DISALLOW_COPY_AND_ASSIGN(TestingMuxer); DISALLOW_COPY_AND_ASSIGN(TestingMuxer);
}; };
class PackagerTest : public ::testing::TestWithParam<const char*> {}; typedef Muxer* CreateMuxerFunc(const std::string& input_file_name,
EncryptorSource* encryptor_source);
Muxer* CreateTestingMuxer(const std::string& input_file_name,
EncryptorSource* encryptor_source) {
MuxerOptions options;
return new TestingMuxer(options, NULL);
}
Muxer* CreateNormalMP4Muxer(const std::string& input_file_name,
EncryptorSource* encryptor_source) {
MuxerOptions options;
options.single_segment = true;
options.segment_duration = 0.005;
options.fragment_duration = 0.002;
options.segment_sap_aligned = true;
options.fragment_sap_aligned = true;
options.num_subsegments_per_sidx = 1;
options.output_file_name = "/tmp/clear_" + input_file_name;
options.segment_template = "/tmp/template$Number$.m4s";
options.temp_file_name = "/tmp/tmp.mp4";
return new mp4::MP4Muxer(options, NULL);
}
Muxer* CreateEncryptionMP4Muxer(const std::string& input_file_name,
EncryptorSource* encryptor_source) {
MuxerOptions options;
options.single_segment = true;
options.segment_duration = 0.005;
options.fragment_duration = 0.002;
options.segment_sap_aligned = true;
options.fragment_sap_aligned = true;
options.num_subsegments_per_sidx = 1;
options.output_file_name = "/tmp/enc_" + input_file_name;
options.segment_template = "/tmp/template$Number$.m4s";
options.temp_file_name = "/tmp/tmp.mp4";
return new mp4::MP4Muxer(options, encryptor_source);
}
class PackagerTest : public ::testing::TestWithParam<
::std::tr1::tuple<const char*, CreateMuxerFunc*> > {};
TEST_P(PackagerTest, Remux) { TEST_P(PackagerTest, Remux) {
Demuxer demuxer( std::string file_name = ::std::tr1::get<0>(GetParam());
GetTestDataFilePath(GetParam()).value(), NULL); CreateMuxerFunc* CreateMuxer = ::std::tr1::get<1>(GetParam());
Demuxer demuxer(GetTestDataFilePath(file_name).value(), NULL);
ASSERT_OK(demuxer.Initialize()); ASSERT_OK(demuxer.Initialize());
LOG(INFO) << "Num Streams: " << demuxer.num_streams(); LOG(INFO) << "Num Streams: " << demuxer.streams().size();
for (int i = 0; i < demuxer.num_streams(); ++i) { for (int i = 0; i < demuxer.streams().size(); ++i) {
LOG(INFO) << "Streams " << i << " " << demuxer.streams(i)->ToString(); LOG(INFO) << "Streams " << i << " " << demuxer.streams()[i]->ToString();
} }
Muxer::Options options; FixedEncryptorSource encryptor_source(
TestingMuxer muxer(options, NULL); kKeyIdHex, kKeyHex, kPsshHex, kClearMilliseconds);
EXPECT_OK(encryptor_source.Initialize());
ASSERT_OK(muxer.AddStream(demuxer.streams(0))); scoped_ptr<Muxer> muxer(CreateMuxer(file_name, &encryptor_source));
ASSERT_OK(muxer->AddStream(demuxer.streams()[0]));
ASSERT_OK(muxer->Initialize());
// Starts remuxing process. // Starts remuxing process.
ASSERT_OK(demuxer.Run()); ASSERT_OK(demuxer.Run());
ASSERT_OK(muxer.Finalize()); ASSERT_OK(muxer->Finalize());
} }
INSTANTIATE_TEST_CASE_P(PackagerE2ETest, INSTANTIATE_TEST_CASE_P(PackagerE2ETest,
PackagerTest, PackagerTest,
::testing::ValuesIn(kMediaFiles)); Combine(ValuesIn(kMediaFiles),
Values(&CreateTestingMuxer,
&CreateNormalMP4Muxer,
&CreateEncryptionMP4Muxer)));
} // namespace media } // namespace media

View File

@ -13,11 +13,17 @@
'target_name': 'media_base', 'target_name': 'media_base',
'type': 'static_library', 'type': 'static_library',
'sources': [ 'sources': [
'media/base/aes_encryptor.cc',
'media/base/aes_encryptor.h',
'media/base/audio_stream_info.cc', 'media/base/audio_stream_info.cc',
'media/base/audio_stream_info.h', 'media/base/audio_stream_info.h',
'media/base/bit_reader.cc', 'media/base/bit_reader.cc',
'media/base/bit_reader.h', 'media/base/bit_reader.h',
'media/base/buffers.h', 'media/base/buffers.h',
'media/base/buffer_reader.cc',
'media/base/buffer_reader.h',
'media/base/buffer_writer.cc',
'media/base/buffer_writer.h',
'media/base/byte_queue.cc', 'media/base/byte_queue.cc',
'media/base/byte_queue.h', 'media/base/byte_queue.h',
'media/base/container_names.cc', 'media/base/container_names.cc',
@ -29,7 +35,10 @@
'media/base/decrypt_config.cc', 'media/base/decrypt_config.cc',
'media/base/decrypt_config.h', 'media/base/decrypt_config.h',
'media/base/decryptor_source.h', 'media/base/decryptor_source.h',
'media/base/encryptor_source.cc',
'media/base/encryptor_source.h', 'media/base/encryptor_source.h',
'media/base/fixed_encryptor_source.cc',
'media/base/fixed_encryptor_source.h',
'media/base/limits.h', 'media/base/limits.h',
'media/base/media_parser.h', 'media/base/media_parser.h',
'media/base/media_sample.cc', 'media/base/media_sample.cc',
@ -48,6 +57,7 @@
], ],
'dependencies': [ 'dependencies': [
'base/base.gyp:base', 'base/base.gyp:base',
'third_party/openssl/openssl.gyp:openssl',
], ],
}, },
{ {
@ -68,13 +78,16 @@
'target_name': 'media_base_unittest', 'target_name': 'media_base_unittest',
'type': 'executable', 'type': 'executable',
'sources': [ 'sources': [
'media/base/aes_encryptor_unittest.cc',
'media/base/bit_reader_unittest.cc', 'media/base/bit_reader_unittest.cc',
'media/base/buffer_writer_unittest.cc',
'media/base/container_names_unittest.cc', 'media/base/container_names_unittest.cc',
'media/base/status_test_util.h', 'media/base/status_test_util.h',
'media/base/status_test_util_unittest.cc', 'media/base/status_test_util_unittest.cc',
'media/base/status_unittest.cc', 'media/base/status_unittest.cc',
], ],
'dependencies': [ 'dependencies': [
'file',
'media_base', 'media_base',
'media_test_support', 'media_test_support',
'testing/gtest.gyp:gtest', 'testing/gtest.gyp:gtest',
@ -87,10 +100,14 @@
'sources': [ 'sources': [
'media/mp4/aac.cc', 'media/mp4/aac.cc',
'media/mp4/aac.h', 'media/mp4/aac.h',
'media/mp4/box.cc',
'media/mp4/box.h',
'media/mp4/box_buffer_interface.h',
'media/mp4/box_definitions.cc', 'media/mp4/box_definitions.cc',
'media/mp4/box_definitions.h', 'media/mp4/box_definitions.h',
'media/mp4/box_reader.cc', 'media/mp4/box_reader.cc',
'media/mp4/box_reader.h', 'media/mp4/box_reader.h',
'media/mp4/box_writer.h',
'media/mp4/cenc.cc', 'media/mp4/cenc.cc',
'media/mp4/cenc.h', 'media/mp4/cenc.h',
'media/mp4/chunk_info_iterator.cc', 'media/mp4/chunk_info_iterator.cc',
@ -102,8 +119,18 @@
'media/mp4/es_descriptor.cc', 'media/mp4/es_descriptor.cc',
'media/mp4/es_descriptor.h', 'media/mp4/es_descriptor.h',
'media/mp4/fourccs.h', 'media/mp4/fourccs.h',
'media/mp4/mp4_fragmenter.cc',
'media/mp4/mp4_fragmenter.h',
'media/mp4/mp4_general_segmenter.cc',
'media/mp4/mp4_general_segmenter.h',
'media/mp4/mp4_media_parser.cc', 'media/mp4/mp4_media_parser.cc',
'media/mp4/mp4_media_parser.h', 'media/mp4/mp4_media_parser.h',
'media/mp4/mp4_muxer.cc',
'media/mp4/mp4_muxer.h',
'media/mp4/mp4_segmenter.cc',
'media/mp4/mp4_segmenter.h',
'media/mp4/mp4_vod_segmenter.cc',
'media/mp4/mp4_vod_segmenter.h',
'media/mp4/offset_byte_queue.cc', 'media/mp4/offset_byte_queue.cc',
'media/mp4/offset_byte_queue.h', 'media/mp4/offset_byte_queue.h',
'media/mp4/rcheck.h', 'media/mp4/rcheck.h',
@ -121,6 +148,7 @@
'type': 'executable', 'type': 'executable',
'sources': [ 'sources': [
'media/mp4/aac_unittest.cc', 'media/mp4/aac_unittest.cc',
'media/mp4/box_definitions_unittest.cc',
'media/mp4/box_reader_unittest.cc', 'media/mp4/box_reader_unittest.cc',
'media/mp4/chunk_info_iterator_unittest.cc', 'media/mp4/chunk_info_iterator_unittest.cc',
'media/mp4/composition_offset_iterator_unittest.cc', 'media/mp4/composition_offset_iterator_unittest.cc',
@ -144,6 +172,7 @@
'sources': [ 'sources': [
'media/file/file.cc', 'media/file/file.cc',
'media/file/file.h', 'media/file/file.h',
'media/file/file_closer.h',
'media/file/local_file.cc', 'media/file/local_file.cc',
'media/file/local_file.h', 'media/file/local_file.h',
], ],