Removed EditList, added NAL and several other cleanup.

Change-Id: I2658eae0789f1c4e8d0534a6ff70267058bee2fc
This commit is contained in:
Kongqun Yang 2013-11-12 12:32:44 -08:00
parent 21aad421ce
commit dc88702315
14 changed files with 203 additions and 264 deletions

View File

@ -60,10 +60,9 @@ Status Demuxer::Initialize() {
return Status(error::UNIMPLEMENTED, "Container not supported.");
}
parser_->Init(
base::Bind(&Demuxer::ParserInitEvent, base::Unretained(this)),
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
base::Bind(&Demuxer::KeyNeededEvent, base::Unretained(this)));
parser_->Init(base::Bind(&Demuxer::ParserInitEvent, base::Unretained(this)),
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
base::Bind(&Demuxer::KeyNeededEvent, base::Unretained(this)));
if (!parser_->Parse(buffer_.get(), bytes_read))
return Status(error::PARSER_FAILURE,
@ -72,7 +71,8 @@ Status Demuxer::Initialize() {
// TODO(kqyang): Does not look clean. Consider refactoring later.
Status status;
while (!init_event_received_) {
if (!(status = Parse()).ok()) break;
if (!(status = Parse()).ok())
break;
}
return status;
}
@ -87,8 +87,8 @@ void Demuxer::ParserInitEvent(
}
}
bool Demuxer::NewSampleEvent(
uint32 track_id, const scoped_refptr<MediaSample>& sample) {
bool Demuxer::NewSampleEvent(uint32 track_id,
const scoped_refptr<MediaSample>& sample) {
std::vector<MediaStream*>::iterator it = streams_.begin();
for (; it != streams_.end(); ++it) {
if (track_id == (*it)->info()->track_id()) {
@ -98,9 +98,9 @@ bool Demuxer::NewSampleEvent(
return false;
}
void Demuxer::KeyNeededEvent(
MediaContainerName container, scoped_ptr<uint8[]> init_data,
int init_data_size) {
void Demuxer::KeyNeededEvent(MediaContainerName container,
scoped_ptr<uint8[]> init_data,
int init_data_size) {
NOTIMPLEMENTED();
}
@ -116,8 +116,9 @@ Status Demuxer::Run() {
return status;
}
while ((status = Parse()).ok()) continue;
return status.Matches(Status(error::EOF, "")) ? Status::OK : status;
while ((status = Parse()).ok())
continue;
return status.Matches(Status(error::END_OF_STREAM, "")) ? Status::OK : status;
}
Status Demuxer::Parse() {
@ -127,9 +128,9 @@ Status Demuxer::Parse() {
int64 bytes_read = media_file_->Read(buffer_.get(), kBufSize);
if (bytes_read <= 0) {
return media_file_->Eof() ?
Status(error::EOF, "End of file") :
Status(error::FILE_FAILURE, "Cannot read file " + file_name_);
return media_file_->Eof()
? Status(error::END_OF_STREAM, "End of stream.")
: Status(error::FILE_FAILURE, "Cannot read file " + file_name_);
}
return parser_->Parse(buffer_.get(), bytes_read)

View File

@ -43,16 +43,10 @@ class Demuxer {
// Reads from the source and send it to the parser.
Status Parse();
uint32 num_streams() const {
return streams_.size();
}
// Demuxer retains the ownership of streams.
MediaStream* streams(uint32 index) {
if (index >= streams_.size())
return NULL;
return streams_[index];
}
// Returns the vector of streams in this Demuxer. The caller cannot add or
// remove streams from the returned vector, but the caller could change
// the internal state of the streams in the vector through MediaStream APIs.
const std::vector<MediaStream*>& streams() { return streams_; }
private:
// Parser event handlers.
@ -73,7 +67,6 @@ class Demuxer {
DISALLOW_COPY_AND_ASSIGN(Demuxer);
};
}
#endif // MEDIA_BASE_DEMUXER_H_

View File

@ -20,8 +20,7 @@ class DecryptConfig;
// Holds media sample. Also includes decoder specific functionality for
// decryption.
class MediaSample
: public base::RefCountedThreadSafe<MediaSample> {
class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
public:
// Create a MediaSample whose |data_| is copied from |data|.
// |data| must not be NULL and |size| >= 0.
@ -82,12 +81,12 @@ class MediaSample
const uint8* data() const {
DCHECK(!end_of_stream());
return data_.data();
return &data_[0];
}
uint8* writable_data() {
DCHECK(!end_of_stream());
return data_.data();
return &data_[0];
}
int data_size() const {
@ -97,7 +96,7 @@ class MediaSample
const uint8* side_data() const {
DCHECK(!end_of_stream());
return side_data_.data();
return &side_data_[0];
}
int side_data_size() const {
@ -116,9 +115,7 @@ class MediaSample
}
// If there's no data in this buffer, it represents end of stream.
bool end_of_stream() const {
return data_.size() == 0;
}
bool end_of_stream() const { return data_.size() == 0; }
// Returns a human-readable string describing |*this|.
std::string ToString() const;

View File

@ -59,7 +59,6 @@ Status MediaStream::Start(MediaStreamOperation operation) {
DCHECK(demuxer_ != NULL);
DCHECK(operation == kPush || operation == kPull);
switch (state_) {
case kIdle:
// Disconnect the stream if it is not connected to a muxer.
@ -79,8 +78,8 @@ Status MediaStream::Start(MediaStreamOperation operation) {
} else {
// We need to disconnect all its peer streams which are not connected
// to a muxer.
for (int i = 0; i < demuxer_->num_streams(); ++i) {
Status status = demuxer_->streams(i)->Start(operation);
for (int i = 0; i < demuxer_->streams().size(); ++i) {
Status status = demuxer_->streams()[i]->Start(operation);
if (!status.ok())
return status;
}
@ -95,14 +94,11 @@ Status MediaStream::Start(MediaStreamOperation operation) {
}
}
scoped_refptr<StreamInfo> MediaStream::info() {
return info_;
}
const scoped_refptr<StreamInfo> MediaStream::info() const { return info_; }
std::string MediaStream::ToString() const {
std::ostringstream s;
s << "state: " << state_
<< " samples in the queue: " << samples_.size()
s << "state: " << state_ << " samples in the queue: " << samples_.size()
<< " " << info_->ToString();
return s.str();
}

View File

@ -43,13 +43,9 @@ class MediaStream {
// Pull sample from Demuxer (triggered by Muxer).
Status PullSample(scoped_refptr<MediaSample>* sample);
Demuxer* demuxer() {
return demuxer_;
}
Muxer* muxer() {
return muxer_;
}
scoped_refptr<StreamInfo> info();
Demuxer* demuxer() { return demuxer_; }
Muxer* muxer() { return muxer_; }
const scoped_refptr<StreamInfo> info() const;
// Returns a human-readable string describing |*this|.
std::string ToString() const;

View File

@ -37,12 +37,18 @@ enum Code {
// Cannot open file.
FILE_FAILURE,
// End of file.
EOF,
// End of stream.
END_OF_STREAM,
// Unable to parse the media file.
PARSER_FAILURE,
// Fail to mux the media file.
MUXER_FAILURE,
// This track fragment is finalized.
FRAGMENT_FINALIZED,
// TODO(kqyang): define packager specific error codes.
};
@ -56,18 +62,16 @@ class Status {
// Create a status with the specified code, and error message. If "code == 0",
// error_message is ignored and a Status object identical to Status::OK is
// constructed.
Status(error::Code error_code, const std::string& error_message) :
error_code_(error_code) {
Status(error::Code error_code, const std::string& error_message)
: error_code_(error_code) {
if (!ok())
error_message_ = error_message;
}
//Status(const Status&);
//Status& operator=(const Status& x);
~Status() {}
// Some pre-defined Status objects.
static const Status& OK; // Identical to 0-arg constructor.
static const Status& OK; // Identical to 0-arg constructor.
static const Status& UNKNOWN;
// Store the specified error in this Status object.
@ -102,28 +106,18 @@ class Status {
}
// Accessor.
bool ok() const {
return error_code_ == error::OK;
}
error::Code error_code() const {
return error_code_;
}
const std::string& error_message() const {
return error_message_;
}
bool ok() const { return error_code_ == error::OK; }
error::Code error_code() const { return error_code_; }
const std::string& error_message() const { return error_message_; }
bool operator==(const Status& x) const {
return error_code_ == x.error_code() && error_message_ == x.error_message();
}
bool operator!=(const Status& x) const {
return !(*this == x);
}
bool operator!=(const Status& x) const { return !(*this == x); }
// Returns true iff this has the same error_code as "x". I.e., the two
// Status objects are identical except possibly for the error message.
bool Matches(const Status& x) const {
return error_code_ == x.error_code();
}
bool Matches(const Status& x) const { return error_code_ == x.error_code(); }
// Return a combination of the error code name and message.
std::string ToString() const;

View File

@ -17,7 +17,7 @@ enum StreamType {
kStreamVideo,
};
class StreamInfo : public base::RefCountedThreadSafe<StreamInfo> {
class StreamInfo : public base::RefCountedThreadSafe<StreamInfo> {
public:
StreamInfo(StreamType stream_type,
int track_id,
@ -46,12 +46,7 @@ class StreamInfo : public base::RefCountedThreadSafe<StreamInfo> {
bool is_encrypted() const { return is_encrypted_; }
const uint8* extra_data() const {
return extra_data_.empty() ? NULL : &extra_data_[0];
}
size_t extra_data_size() const {
return extra_data_.size();
}
const std::vector<uint8>& extra_data() const { return extra_data_; }
void set_duration(int duration) { duration_ = duration; }

View File

@ -15,8 +15,9 @@ base::FilePath GetTestDataFilePath(const std::string& name) {
CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
file_path = file_path.Append(FILE_PATH_LITERAL("media"))
.Append(FILE_PATH_LITERAL("test")).Append(FILE_PATH_LITERAL("data"))
.AppendASCII(name);
.Append(FILE_PATH_LITERAL("test"))
.Append(FILE_PATH_LITERAL("data"))
.AppendASCII(name);
return file_path;
}
@ -25,8 +26,9 @@ std::vector<uint8> ReadTestDataFile(const std::string& name) {
CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
file_path = file_path.Append(FILE_PATH_LITERAL("media"))
.Append(FILE_PATH_LITERAL("test")).Append(FILE_PATH_LITERAL("data"))
.AppendASCII(name);
.Append(FILE_PATH_LITERAL("test"))
.Append(FILE_PATH_LITERAL("data"))
.AppendASCII(name);
int64 tmp = 0;
CHECK(file_util::GetFileSize(file_path, &tmp))
@ -37,8 +39,8 @@ std::vector<uint8> ReadTestDataFile(const std::string& name) {
CHECK_EQ(file_size,
file_util::ReadFile(
file_path, reinterpret_cast<char*>(buffer.data()),
file_size)) << "Failed to read '" << name << "'";
file_path, reinterpret_cast<char*>(&buffer[0]), file_size))
<< "Failed to read '" << name << "'";
return buffer;
}

View File

@ -20,6 +20,7 @@ VideoStreamInfo::VideoStreamInfo(int track_id,
const std::string& language,
uint16 width,
uint16 height,
uint8 nalu_length_size,
const uint8* extra_data,
size_t extra_data_size,
bool is_encrypted)
@ -34,14 +35,16 @@ VideoStreamInfo::VideoStreamInfo(int track_id,
is_encrypted),
codec_(codec),
width_(width),
height_(height) {}
height_(height),
nalu_length_size_(nalu_length_size) {}
VideoStreamInfo::~VideoStreamInfo() {}
bool VideoStreamInfo::IsValidConfig() const {
return codec_ != kUnknownVideoCodec &&
width_ > 0 && width_ <= limits::kMaxDimension &&
height_ > 0 && height_ <= limits::kMaxDimension;
height_ > 0 && height_ <= limits::kMaxDimension &&
(nalu_length_size_ <= 2 || nalu_length_size_ == 4);
}
std::string VideoStreamInfo::ToString() const {
@ -49,6 +52,7 @@ std::string VideoStreamInfo::ToString() const {
s << "codec: " << codec_
<< " width: " << width_
<< " height: " << height_
<< " nalu_length_size_: " << static_cast<int>(nalu_length_size_)
<< " " << StreamInfo::ToString();
return s.str();
}
@ -64,8 +68,8 @@ std::string VideoStreamInfo::GetCodecString(VideoCodec codec,
return "vp9";
case kCodecH264: {
const uint8 bytes[] = {profile, compatible_profiles, level};
return "avc1."
+ StringToLowerASCII(base::HexEncode(bytes, arraysize(bytes)));
return "avc1." +
StringToLowerASCII(base::HexEncode(bytes, arraysize(bytes)));
}
default:
NOTIMPLEMENTED() << "Codec: " << codec;

View File

@ -18,7 +18,6 @@ enum VideoCodec {
kCodecTheora,
kCodecVP8,
kCodecVP9,
kNumVideoCodec
};
@ -34,6 +33,7 @@ class VideoStreamInfo : public StreamInfo {
const std::string& language,
uint16 width,
uint16 height,
uint8 nalu_length_size,
const uint8* extra_data,
size_t extra_data_size,
bool is_encrypted);
@ -50,17 +50,25 @@ class VideoStreamInfo : public StreamInfo {
VideoCodec codec() const { return codec_; }
uint16 width() const { return width_; }
uint16 height() const { return height_; }
uint8 nalu_length_size() const { return nalu_length_size_; }
// Returns the codec string. The parameters beyond codec are only used by
// H.264 codec.
static std::string GetCodecString(VideoCodec codec, uint8 profile,
uint8 compatible_profiles, uint8 level);
static std::string GetCodecString(VideoCodec codec,
uint8 profile,
uint8 compatible_profiles,
uint8 level);
private:
VideoCodec codec_;
uint16 width_;
uint16 height_;
// Specifies the normalized size of the NAL unit length field. Can be 1, 2 or
// 4 bytes, or 0 if the size if unknown or the stream is not a AVC stream
// (H.264).
uint8 nalu_length_size_;
// Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler
// generated copy constructor and assignment operator. Since the extra data is
// typically small, the performance impact is minimal.

View File

@ -4,8 +4,6 @@
#include "media/file/file.h"
#include <vector>
#include "base/file_util.h"
#include "testing/gtest/include/gtest/gtest.h"

View File

@ -37,8 +37,7 @@ MP4MediaParser::MP4MediaParser()
audio_track_id_(0),
video_track_id_(0),
is_audio_track_encrypted_(false),
is_video_track_encrypted_(false) {
}
is_video_track_encrypted_(false) {}
MP4MediaParser::~MP4MediaParser() {}
@ -102,10 +101,12 @@ bool MP4MediaParser::ParseBox(bool* err) {
const uint8* buf;
int size;
queue_.Peek(&buf, &size);
if (!size) return false;
if (!size)
return false;
scoped_ptr<BoxReader> reader(BoxReader::ReadTopLevelBox(buf, size, err));
if (reader.get() == NULL) return false;
if (reader.get() == NULL)
return false;
// Set up mdat offset for ReadMDATsUntil().
mdat_tail_ = queue_.head() + reader->size();
@ -130,7 +131,6 @@ bool MP4MediaParser::ParseBox(bool* err) {
return !(*err);
}
bool MP4MediaParser::ParseMoov(BoxReader* reader) {
moov_.reset(new Movie);
RCHECK(moov_->Parse(reader));
@ -152,12 +152,13 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
} else if (moov_->extends.header.fragment_duration > 0) {
DCHECK(moov_->header.timescale != 0);
duration = Rescale(moov_->extends.header.fragment_duration,
moov_->header.timescale, timescale);
moov_->header.timescale,
timescale);
} else if (moov_->header.duration > 0 &&
moov_->header.duration != kuint64max) {
DCHECK(moov_->header.timescale != 0);
duration = Rescale(moov_->header.duration, moov_->header.timescale,
timescale);
duration =
Rescale(moov_->header.duration, moov_->header.timescale, timescale);
}
// TODO(strobe): Only the first audio and video track present in a file are
@ -202,7 +203,6 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
if (desc_idx >= samp_descr.audio_entries.size())
desc_idx = 0;
const AudioSampleEntry& entry = samp_descr.audio_entries[desc_idx];
const AAC& aac = entry.esds.aac;
if (!(entry.format == FOURCC_MP4A || entry.format == FOURCC_EAC3 ||
(entry.format == FOURCC_ENCA &&
@ -212,7 +212,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
return false;
}
ObjectType audio_type = static_cast<ObjectType>(entry.esds.object_type);
ObjectType audio_type = entry.esds.es_descriptor.object_type();
DVLOG(1) << "audio_type " << std::hex << audio_type;
if (audio_type == kForbidden && entry.format == FOURCC_EAC3) {
audio_type = kEAC3;
@ -225,12 +225,13 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
std::vector<uint8> extra_data;
// Check if it is MPEG4 AAC defined in ISO 14496 Part 3 or
// supported MPEG2 AAC variants.
if (ESDescriptor::IsAAC(audio_type)) {
if (entry.esds.es_descriptor.IsAAC()) {
codec = kCodecAAC;
const AAC& aac = entry.esds.aac;
num_channels = aac.num_channels();
sampling_frequency = aac.frequency();
audio_object_type = aac.audio_object_type();
extra_data = aac.codec_specific_data();
extra_data = entry.esds.es_descriptor.decoder_specific_info();
} else if (audio_type == kEAC3) {
codec = kCodecEAC3;
num_channels = entry.channelcount;
@ -243,20 +244,19 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
is_audio_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted;
DVLOG(1) << "is_audio_track_encrypted_: " << is_audio_track_encrypted_;
streams.push_back(
new AudioStreamInfo(
track->header.track_id,
timescale,
duration,
codec,
AudioStreamInfo::GetCodecString(codec, audio_object_type),
track->media.header.language,
entry.samplesize,
num_channels,
sampling_frequency,
extra_data.size() ? &extra_data[0] : NULL,
extra_data.size(),
is_audio_track_encrypted_));
streams.push_back(new AudioStreamInfo(
track->header.track_id,
timescale,
duration,
codec,
AudioStreamInfo::GetCodecString(codec, audio_object_type),
track->media.header.language,
entry.samplesize,
num_channels,
sampling_frequency,
extra_data.size() ? &extra_data[0] : NULL,
extra_data.size(),
is_audio_track_encrypted_));
has_audio_ = true;
audio_track_id_ = track->header.track_id;
}
@ -280,25 +280,25 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
}
const std::string codec_string =
VideoStreamInfo::GetCodecString(
kCodecH264, entry.avcc.profile_indication,
entry.avcc.profile_compatibility, entry.avcc.avc_level);
VideoStreamInfo::GetCodecString(kCodecH264,
entry.avcc.profile_indication,
entry.avcc.profile_compatibility,
entry.avcc.avc_level);
is_video_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted;
DVLOG(1) << "is_video_track_encrypted_: " << is_video_track_encrypted_;
streams.push_back(
new VideoStreamInfo(
track->header.track_id,
timescale,
duration,
kCodecH264,
codec_string,
track->media.header.language,
entry.width,
entry.height,
&entry.avcc.data[0],
entry.avcc.data.size(),
is_video_track_encrypted_));
streams.push_back(new VideoStreamInfo(track->header.track_id,
timescale,
duration,
kCodecH264,
codec_string,
track->media.header.language,
entry.width,
entry.height,
entry.avcc.length_size,
&entry.avcc.data[0],
entry.avcc.data.size(),
is_video_track_encrypted_));
has_video_ = true;
video_track_id_ = track->header.track_id;
}
@ -338,7 +338,8 @@ void MP4MediaParser::EmitNeedKeyIfNecessary(
scoped_ptr<uint8[]> init_data(new uint8[total_size]);
size_t pos = 0;
for (size_t i = 0; i < headers.size(); i++) {
memcpy(&init_data.get()[pos], &headers[i].raw_box[0],
memcpy(&init_data.get()[pos],
&headers[i].raw_box[0],
headers[i].raw_box.size());
pos += headers[i].raw_box.size();
}
@ -366,7 +367,8 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
const uint8* buf;
int buf_size;
queue_.Peek(&buf, &buf_size);
if (!buf_size) return false;
if (!buf_size)
return false;
bool audio = has_audio_ && audio_track_id_ == runs_->track_id();
bool video = has_video_ && video_track_id_ == runs_->track_id();
@ -384,13 +386,15 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
// portion of the total system memory.
if (runs_->AuxInfoNeedsToBeCached()) {
queue_.PeekAt(runs_->aux_info_offset() + moof_head_, &buf, &buf_size);
if (buf_size < runs_->aux_info_size()) return false;
if (buf_size < runs_->aux_info_size())
return false;
*err = !runs_->CacheAuxInfo(buf, buf_size);
return !*err;
}
queue_.PeekAt(runs_->sample_offset() + moof_head_, &buf, &buf_size);
if (buf_size < runs_->sample_size()) return false;
if (buf_size < runs_->sample_size())
return false;
scoped_ptr<DecryptConfig> decrypt_config;
std::vector<SubsampleEntry> subsamples;
@ -405,12 +409,11 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
if (decrypt_config) {
if (!subsamples.empty()) {
// Create a new config with the updated subsamples.
decrypt_config.reset(new DecryptConfig(
decrypt_config->key_id(),
decrypt_config->iv(),
decrypt_config->data_offset(),
subsamples));
// Create a new config with the updated subsamples.
decrypt_config.reset(new DecryptConfig(decrypt_config->key_id(),
decrypt_config->iv(),
decrypt_config->data_offset(),
subsamples));
}
// else, use the existing config.
}

View File

@ -12,10 +12,6 @@
#include "media/mp4/rcheck.h"
#include "media/mp4/sync_sample_iterator.h"
namespace {
static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
}
namespace media {
namespace mp4 {
@ -54,12 +50,11 @@ TrackRunInfo::TrackRunInfo()
is_audio(false),
aux_info_start_offset(-1),
aux_info_default_size(0),
aux_info_total_size(0) {
}
aux_info_total_size(0) {}
TrackRunInfo::~TrackRunInfo() {}
TrackRunIterator::TrackRunIterator(const Movie* moov)
: moov_(moov), sample_offset_(0) {
: moov_(moov), sample_dts_(0), sample_offset_(0) {
CHECK(moov);
}
@ -68,7 +63,6 @@ TrackRunIterator::~TrackRunIterator() {}
static void PopulateSampleInfo(const TrackExtends& trex,
const TrackFragmentHeader& tfhd,
const TrackFragmentRun& trun,
const int64 edit_list_offset,
const uint32 i,
SampleInfo* sample_info) {
if (i < trun.sample_sizes.size()) {
@ -92,17 +86,16 @@ static void PopulateSampleInfo(const TrackExtends& trex,
} else {
sample_info->cts_offset = 0;
}
sample_info->cts_offset += edit_list_offset;
uint32 flags;
if (i < trun.sample_flags.size()) {
flags = trun.sample_flags[i];
} else if (tfhd.has_default_sample_flags) {
} else if (tfhd.flags & kDefaultSampleFlagsPresentMask) {
flags = tfhd.default_sample_flags;
} else {
flags = trex.default_sample_flags;
}
sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask);
sample_info->is_keyframe = !(flags & kNonKeySampleMask);
}
// In well-structured encrypted media, each track run will be immediately
@ -127,7 +120,8 @@ class CompareMinTrackRunDataOffset {
int64 b_lesser = std::min(b_aux, b.sample_start_offset);
int64 b_greater = std::max(b_aux, b.sample_start_offset);
if (a_lesser == b_lesser) return a_greater < b_greater;
if (a_lesser == b_lesser)
return a_greater < b_greater;
return a_lesser < b_lesser;
}
};
@ -136,7 +130,7 @@ bool TrackRunIterator::Init() {
runs_.clear();
for (std::vector<Track>::const_iterator trak = moov_->tracks.begin();
trak != moov_->tracks.end(); ++trak) {
trak != moov_->tracks.end(); ++trak) {
const SampleDescription& stsd =
trak->media.information.sample_table.description;
if (stsd.type != kAudio && stsd.type != kVideo) {
@ -144,21 +138,17 @@ bool TrackRunIterator::Init() {
continue;
}
// Process edit list to remove CTS offset introduced in the presence of
// B-frames (those that contain a single edit with a nonnegative media
// time). Other uses of edit lists are not supported, as they are
// both uncommon and better served by higher-level protocols.
int64 edit_list_offset = 0;
// Edit list is ignored.
// We may consider supporting the single edit with a nonnegative media time
// if it is required. Just need to pass the media_time to Muxer and
// generate the edit list.
const std::vector<EditListEntry>& edits = trak->edit.list.edits;
if (!edits.empty()) {
if (edits.size() > 1)
DVLOG(1) << "Multi-entry edit box detected; some components ignored.";
DVLOG(1) << "Multi-entry edit box detected.";
if (edits[0].media_time < 0) {
DVLOG(1) << "Empty edit list entry ignored.";
} else {
edit_list_offset = -edits[0].media_time;
}
LOG(INFO) << "Edit list with media time " << edits[0].media_time
<< " ignored.";
}
DecodingTimeIterator decoding_time(
@ -176,7 +166,7 @@ bool TrackRunIterator::Init() {
const SampleSize& sample_size =
trak->media.information.sample_table.sample_size;
const std::vector<uint64>& chunk_offset_vector =
trak->media.information.sample_table.chunk_offset.offsets;
trak->media.information.sample_table.chunk_large_offset.offsets;
int64 run_start_dts = 0;
int64 run_data_offset = 0;
@ -188,7 +178,7 @@ bool TrackRunIterator::Init() {
DCHECK(num_samples == decoding_time.NumSamples() &&
num_samples == composition_offset.NumSamples() &&
(num_chunks == 0 ||
num_samples == chunk_info.NumSamples(1, num_chunks)) &&
num_samples == chunk_info.NumSamples(1, num_chunks)) &&
num_chunks >= chunk_info.LastFirstChunk());
if (num_samples > 0) {
@ -233,13 +223,12 @@ bool TrackRunIterator::Init() {
tri.samples.resize(samples_per_chunk);
for (uint32 k = 0; k < samples_per_chunk; ++k) {
SampleInfo& sample = tri.samples[k];
sample.size =
sample_size.sample_size != 0 ?
sample_size.sample_size : sample_size.sizes[sample_index];
sample.size = sample_size.sample_size != 0
? sample_size.sample_size
: sample_size.sizes[sample_index];
sample.duration = decoding_time.sample_delta();
sample.cts_offset =
has_composition_offset ? composition_offset.sample_offset() : 0;
sample.cts_offset += edit_list_offset;
sample.is_keyframe = sync_sample.IsSyncSample();
run_start_dts += sample.duration;
@ -297,27 +286,11 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
continue;
}
size_t desc_idx = traf.header.sample_description_index;
if (!desc_idx) desc_idx = trex->default_sample_description_index;
if (!desc_idx)
desc_idx = trex->default_sample_description_index;
RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file
desc_idx -= 1;
// Process edit list to remove CTS offset introduced in the presence of
// B-frames (those that contain a single edit with a nonnegative media
// time). Other uses of edit lists are not supported, as they are
// both uncommon and better served by higher-level protocols.
int64 edit_list_offset = 0;
const std::vector<EditListEntry>& edits = trak->edit.list.edits;
if (!edits.empty()) {
if (edits.size() > 1)
DVLOG(1) << "Multi-entry edit box detected; some components ignored.";
if (edits[0].media_time < 0) {
DVLOG(1) << "Empty edit list entry ignored.";
} else {
edit_list_offset = -edits[0].media_time;
}
}
int64 run_start_dts = traf.decode_time.decode_time;
int sample_count_sum = 0;
@ -356,7 +329,8 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
if (tri.aux_info_default_size == 0) {
const std::vector<uint8>& sizes =
traf.auxiliary_size.sample_info_sizes;
tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(),
tri.aux_info_sizes.insert(
tri.aux_info_sizes.begin(),
sizes.begin() + sample_count_sum,
sizes.begin() + sample_count_sum + trun.sample_count);
}
@ -380,8 +354,7 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
tri.samples.resize(trun.sample_count);
for (size_t k = 0; k < trun.sample_count; k++) {
PopulateSampleInfo(*trex, traf.header, trun, edit_list_offset,
k, &tri.samples[k]);
PopulateSampleInfo(*trex, traf.header, trun, k, &tri.samples[k]);
run_start_dts += tri.samples[k].duration;
}
runs_.push_back(tri);
@ -401,7 +374,8 @@ void TrackRunIterator::AdvanceRun() {
}
void TrackRunIterator::ResetRun() {
if (!IsRunValid()) return;
if (!IsRunValid())
return;
sample_dts_ = run_itr_->start_dts;
sample_offset_ = run_itr_->sample_start_offset;
sample_itr_ = run_itr_->samples.begin();
@ -441,9 +415,7 @@ bool TrackRunIterator::CacheAuxInfo(const uint8* buf, int buf_size) {
return true;
}
bool TrackRunIterator::IsRunValid() const {
return run_itr_ != runs_.end();
}
bool TrackRunIterator::IsRunValid() const { return run_itr_ != runs_.end(); }
bool TrackRunIterator::IsSampleValid() const {
return IsRunValid() && (sample_itr_ != run_itr_->samples.end());
@ -471,7 +443,8 @@ int64 TrackRunIterator::GetMaxClearOffset() {
offset = std::min(offset, next_run->aux_info_start_offset);
}
}
if (offset == kint64max) return 0;
if (offset == kint64max)
return 0;
return offset;
}
@ -552,9 +525,8 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
const FrameCENCInfo& cenc_info = cenc_info_[sample_idx];
DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached());
if (!cenc_info.subsamples.empty() &&
(cenc_info.GetTotalSizeOfSubsamples() !=
static_cast<size_t>(sample_size()))) {
if (!cenc_info.subsamples.empty() && (cenc_info.GetTotalSizeOfSubsamples() !=
static_cast<size_t>(sample_size()))) {
LOG(ERROR) << "Incorrect CENC subsample size.";
return scoped_ptr<DecryptConfig>();
}
@ -562,8 +534,7 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
const std::vector<uint8>& kid = track_encryption().default_kid;
return scoped_ptr<DecryptConfig>(new DecryptConfig(
std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()),
std::string(reinterpret_cast<const char*>(cenc_info.iv),
arraysize(cenc_info.iv)),
std::string(cenc_info.iv.begin(), cenc_info.iv.end()),
0, // No offset to start of media data in MP4 using CENC.
cenc_info.subsamples));
}

View File

@ -17,34 +17,29 @@ static const int kSumAscending1 = 45;
static const int kAudioScale = 48000;
static const int kVideoScale = 25;
static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
static const uint8 kAuxInfo[] = {
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32,
0x00, 0x02,
0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
0x00, 0x03, 0x00, 0x00, 0x00, 0x04
};
// Sample 1: IV (no subsumples).
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
// Sample 2: IV.
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32,
// Sample 2: Subsample count.
0x00, 0x02,
// Sample 2: Subsample 1.
0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
// Sample 2: Subsample 2.
0x00, 0x03, 0x00, 0x00, 0x00, 0x04};
static const char kIv1[] = {
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const char kIv1[] = {0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, };
static const uint8 kKeyId[] = {
0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54,
0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44
};
static const uint8 kKeyId[] = {0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54,
0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44};
namespace media {
namespace mp4 {
class TrackRunIteratorTest : public testing::Test {
public:
TrackRunIteratorTest() {
CreateMovie();
}
TrackRunIteratorTest() { CreateMovie(); }
protected:
Movie moov_;
@ -87,7 +82,7 @@ class TrackRunIteratorTest : public testing::Test {
moof.tracks.resize(2);
moof.tracks[0].decode_time.decode_time = 0;
moof.tracks[0].header.track_id = 1;
moof.tracks[0].header.has_default_sample_flags = true;
moof.tracks[0].header.flags = kDefaultSampleFlagsPresentMask;
moof.tracks[0].header.default_sample_duration = 1024;
moof.tracks[0].header.default_sample_size = 4;
moof.tracks[0].runs.resize(2);
@ -99,7 +94,7 @@ class TrackRunIteratorTest : public testing::Test {
moof.tracks[0].runs[1].data_offset = 10000;
moof.tracks[1].header.track_id = 2;
moof.tracks[1].header.has_default_sample_flags = false;
moof.tracks[1].header.flags = 0;
moof.tracks[1].decode_time.decode_time = 10;
moof.tracks[1].runs.resize(1);
moof.tracks[1].runs[0].sample_count = 10;
@ -108,8 +103,7 @@ class TrackRunIteratorTest : public testing::Test {
SetAscending(&moof.tracks[1].runs[0].sample_durations);
moof.tracks[1].runs[0].sample_flags.resize(10);
for (size_t i = 1; i < moof.tracks[1].runs[0].sample_flags.size(); i++) {
moof.tracks[1].runs[0].sample_flags[i] =
kSampleIsDifferenceSampleFlagMask;
moof.tracks[1].runs[0].sample_flags[i] = kNonKeySampleMask;
}
return moof;
@ -121,17 +115,16 @@ class TrackRunIteratorTest : public testing::Test {
&track->media.information.sample_table.description;
ProtectionSchemeInfo* sinf;
if (!stsd->video_entries.empty()) {
sinf = &stsd->video_entries[0].sinf;
sinf = &stsd->video_entries[0].sinf;
} else {
sinf = &stsd->audio_entries[0].sinf;
sinf = &stsd->audio_entries[0].sinf;
}
sinf->type.type = FOURCC_CENC;
sinf->info.track_encryption.is_encrypted = true;
sinf->info.track_encryption.default_iv_size = 8;
sinf->info.track_encryption.default_kid.insert(
sinf->info.track_encryption.default_kid.begin(),
kKeyId, kKeyId + arraysize(kKeyId));
sinf->info.track_encryption.default_kid.assign(kKeyId,
kKeyId + arraysize(kKeyId));
}
// Add aux info covering the first track run to a TrackFragment, and update
@ -148,7 +141,7 @@ class TrackRunIteratorTest : public testing::Test {
void SetAscending(std::vector<uint32>* vec) {
vec->resize(10);
for (size_t i = 0; i < vec->size(); i++)
(*vec)[i] = i+1;
(*vec)[i] = i + 1;
}
};
@ -177,7 +170,8 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
EXPECT_TRUE(iter_->is_keyframe());
// Advance to the last sample in the current run, and test its properties
for (int i = 0; i < 9; i++) iter_->AdvanceSample();
for (int i = 0; i < 9; i++)
iter_->AdvanceSample();
EXPECT_EQ(iter_->track_id(), 1u);
EXPECT_EQ(iter_->sample_offset(), 100 + kSumAscending1);
EXPECT_EQ(iter_->sample_size(), 10);
@ -192,7 +186,8 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
// Test last sample of next run
iter_->AdvanceRun();
EXPECT_TRUE(iter_->is_keyframe());
for (int i = 0; i < 9; i++) iter_->AdvanceSample();
for (int i = 0; i < 9; i++)
iter_->AdvanceSample();
EXPECT_EQ(iter_->track_id(), 2u);
EXPECT_EQ(iter_->sample_offset(), 200 + kSumAscending1);
EXPECT_EQ(iter_->sample_size(), 10);
@ -207,7 +202,7 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
EXPECT_EQ(iter_->dts(), 1024 * 10);
iter_->AdvanceSample();
EXPECT_EQ(moof.tracks[0].runs[1].data_offset +
moof.tracks[0].header.default_sample_size,
moof.tracks[0].header.default_sample_size,
iter_->sample_offset());
iter_->AdvanceRun();
EXPECT_FALSE(iter_->IsRunValid());
@ -216,11 +211,10 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) {
moov_.extends.tracks[0].default_sample_duration = 50;
moov_.extends.tracks[0].default_sample_size = 3;
moov_.extends.tracks[0].default_sample_flags =
kSampleIsDifferenceSampleFlagMask;
moov_.extends.tracks[0].default_sample_flags = kNonKeySampleMask;
iter_.reset(new TrackRunIterator(&moov_));
MovieFragment moof = CreateFragment();
moof.tracks[0].header.has_default_sample_flags = false;
moof.tracks[0].header.flags = 0;
moof.tracks[0].header.default_sample_size = 0;
moof.tracks[0].header.default_sample_duration = 0;
moof.tracks[0].runs[0].sample_sizes.clear();
@ -239,9 +233,8 @@ TEST_F(TrackRunIteratorTest, FirstSampleFlagTest) {
// defaults for all subsequent samples
iter_.reset(new TrackRunIterator(&moov_));
MovieFragment moof = CreateFragment();
moof.tracks[1].header.has_default_sample_flags = true;
moof.tracks[1].header.default_sample_flags =
kSampleIsDifferenceSampleFlagMask;
moof.tracks[1].header.flags = kDefaultSampleFlagsPresentMask;
moof.tracks[1].header.default_sample_flags = kNonKeySampleMask;
moof.tracks[1].runs[0].sample_flags.resize(1);
ASSERT_TRUE(iter_->Init(moof));
iter_->AdvanceRun();
@ -251,7 +244,7 @@ TEST_F(TrackRunIteratorTest, FirstSampleFlagTest) {
}
TEST_F(TrackRunIteratorTest, ReorderingTest) {
// Test frame reordering and edit list support. The frames have the following
// Test frame reordering. The frames have the following
// decode timestamps:
//
// 0ms 40ms 120ms 240ms
@ -261,26 +254,14 @@ TEST_F(TrackRunIteratorTest, ReorderingTest) {
//
// 0ms 40ms 160ms 240ms
// | 0 | 2 - - | 1 - |
// Create an edit list with one entry, with an initial start time of 80ms
// (that is, 2 / kVideoTimescale) and a duration of zero (which is treated as
// infinite according to 14496-12:2012). This will cause the first 80ms of the
// media timeline - which will be empty, due to CTS biasing - to be discarded.
iter_.reset(new TrackRunIterator(&moov_));
EditListEntry entry;
entry.segment_duration = 0;
entry.media_time = 2;
entry.media_rate_integer = 1;
entry.media_rate_fraction = 0;
moov_.tracks[1].edit.list.edits.push_back(entry);
// Add CTS offsets. Without bias, the CTS offsets for the first three frames
// would simply be [0, 3, -2]. Since CTS offsets should be non-negative for
// maximum compatibility, these values are biased up to [2, 5, 0], and the
// extra 80ms is removed via the edit list.
// maximum compatibility, these values are biased up to [2, 5, 0].
MovieFragment moof = CreateFragment();
std::vector<int32>& cts_offsets =
moof.tracks[1].runs[0].sample_composition_time_offsets;
moof.tracks[1].runs[0].sample_composition_time_offsets;
cts_offsets.resize(10);
cts_offsets[0] = 2;
cts_offsets[1] = 5;
@ -290,15 +271,15 @@ TEST_F(TrackRunIteratorTest, ReorderingTest) {
ASSERT_TRUE(iter_->Init(moof));
iter_->AdvanceRun();
EXPECT_EQ(iter_->dts(), 0);
EXPECT_EQ(iter_->cts(), 0);
EXPECT_EQ(iter_->cts(), 2);
EXPECT_EQ(iter_->duration(), 1);
iter_->AdvanceSample();
EXPECT_EQ(iter_->dts(), 1);
EXPECT_EQ(iter_->cts(), 4);
EXPECT_EQ(iter_->cts(), 6);
EXPECT_EQ(iter_->duration(), 2);
iter_->AdvanceSample();
EXPECT_EQ(iter_->dts(), 3);
EXPECT_EQ(iter_->cts(), 1);
EXPECT_EQ(iter_->cts(), 3);
EXPECT_EQ(iter_->duration(), 3);
}
@ -340,8 +321,8 @@ TEST_F(TrackRunIteratorTest, DecryptConfigTest) {
EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[0].runs[0].data_offset);
scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
ASSERT_EQ(arraysize(kKeyId), config->key_id().size());
EXPECT_TRUE(!memcmp(kKeyId, config->key_id().data(),
config->key_id().size()));
EXPECT_TRUE(
!memcmp(kKeyId, config->key_id().data(), config->key_id().size()));
ASSERT_EQ(arraysize(kIv1), config->iv().size());
EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
EXPECT_TRUE(config->subsamples().empty());