MP4 Muxer implementation.
Change-Id: I2aeb506e5bf7e2301e855456688273fe5f3988a2
This commit is contained in:
parent
01646837e1
commit
1b5c3b5316
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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_
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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_
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
||||||
|
|
29
packager.gyp
29
packager.gyp
|
@ -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',
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue