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,8 +60,7 @@ Status Demuxer::Initialize() {
return Status(error::UNIMPLEMENTED, "Container not supported."); return Status(error::UNIMPLEMENTED, "Container not supported.");
} }
parser_->Init( parser_->Init(base::Bind(&Demuxer::ParserInitEvent, base::Unretained(this)),
base::Bind(&Demuxer::ParserInitEvent, base::Unretained(this)),
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)), base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
base::Bind(&Demuxer::KeyNeededEvent, base::Unretained(this))); base::Bind(&Demuxer::KeyNeededEvent, base::Unretained(this)));
@ -72,7 +71,8 @@ Status Demuxer::Initialize() {
// TODO(kqyang): Does not look clean. Consider refactoring later. // TODO(kqyang): Does not look clean. Consider refactoring later.
Status status; Status status;
while (!init_event_received_) { while (!init_event_received_) {
if (!(status = Parse()).ok()) break; if (!(status = Parse()).ok())
break;
} }
return status; return status;
} }
@ -87,8 +87,8 @@ void Demuxer::ParserInitEvent(
} }
} }
bool Demuxer::NewSampleEvent( bool Demuxer::NewSampleEvent(uint32 track_id,
uint32 track_id, const scoped_refptr<MediaSample>& sample) { const scoped_refptr<MediaSample>& sample) {
std::vector<MediaStream*>::iterator it = streams_.begin(); std::vector<MediaStream*>::iterator it = streams_.begin();
for (; it != streams_.end(); ++it) { for (; it != streams_.end(); ++it) {
if (track_id == (*it)->info()->track_id()) { if (track_id == (*it)->info()->track_id()) {
@ -98,8 +98,8 @@ bool Demuxer::NewSampleEvent(
return false; return false;
} }
void Demuxer::KeyNeededEvent( void Demuxer::KeyNeededEvent(MediaContainerName container,
MediaContainerName container, scoped_ptr<uint8[]> init_data, scoped_ptr<uint8[]> init_data,
int init_data_size) { int init_data_size) {
NOTIMPLEMENTED(); NOTIMPLEMENTED();
} }
@ -116,8 +116,9 @@ Status Demuxer::Run() {
return status; return status;
} }
while ((status = Parse()).ok()) continue; while ((status = Parse()).ok())
return status.Matches(Status(error::EOF, "")) ? Status::OK : status; continue;
return status.Matches(Status(error::END_OF_STREAM, "")) ? Status::OK : status;
} }
Status Demuxer::Parse() { Status Demuxer::Parse() {
@ -127,9 +128,9 @@ Status Demuxer::Parse() {
int64 bytes_read = media_file_->Read(buffer_.get(), kBufSize); int64 bytes_read = media_file_->Read(buffer_.get(), kBufSize);
if (bytes_read <= 0) { if (bytes_read <= 0) {
return media_file_->Eof() ? return media_file_->Eof()
Status(error::EOF, "End of file") : ? Status(error::END_OF_STREAM, "End of stream.")
Status(error::FILE_FAILURE, "Cannot read file " + file_name_); : Status(error::FILE_FAILURE, "Cannot read file " + file_name_);
} }
return parser_->Parse(buffer_.get(), bytes_read) 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. // Reads from the source and send it to the parser.
Status Parse(); Status Parse();
uint32 num_streams() const { // Returns the vector of streams in this Demuxer. The caller cannot add or
return streams_.size(); // 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_; }
// Demuxer retains the ownership of streams.
MediaStream* streams(uint32 index) {
if (index >= streams_.size())
return NULL;
return streams_[index];
}
private: private:
// Parser event handlers. // Parser event handlers.
@ -73,7 +67,6 @@ class Demuxer {
DISALLOW_COPY_AND_ASSIGN(Demuxer); DISALLOW_COPY_AND_ASSIGN(Demuxer);
}; };
} }
#endif // MEDIA_BASE_DEMUXER_H_ #endif // MEDIA_BASE_DEMUXER_H_

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,6 @@ enum VideoCodec {
kCodecTheora, kCodecTheora,
kCodecVP8, kCodecVP8,
kCodecVP9, kCodecVP9,
kNumVideoCodec kNumVideoCodec
}; };
@ -34,6 +33,7 @@ class VideoStreamInfo : public StreamInfo {
const std::string& language, const std::string& language,
uint16 width, uint16 width,
uint16 height, uint16 height,
uint8 nalu_length_size,
const uint8* extra_data, const uint8* extra_data,
size_t extra_data_size, size_t extra_data_size,
bool is_encrypted); bool is_encrypted);
@ -50,17 +50,25 @@ class VideoStreamInfo : public StreamInfo {
VideoCodec codec() const { return codec_; } VideoCodec codec() const { return codec_; }
uint16 width() const { return width_; } uint16 width() const { return width_; }
uint16 height() const { return height_; } 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 // Returns the codec string. The parameters beyond codec are only used by
// H.264 codec. // H.264 codec.
static std::string GetCodecString(VideoCodec codec, uint8 profile, static std::string GetCodecString(VideoCodec codec,
uint8 compatible_profiles, uint8 level); uint8 profile,
uint8 compatible_profiles,
uint8 level);
private: private:
VideoCodec codec_; VideoCodec codec_;
uint16 width_; uint16 width_;
uint16 height_; 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 // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler
// generated copy constructor and assignment operator. Since the extra data is // generated copy constructor and assignment operator. Since the extra data is
// typically small, the performance impact is minimal. // typically small, the performance impact is minimal.

View File

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

View File

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

View File

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

View File

@ -17,34 +17,29 @@ static const int kSumAscending1 = 45;
static const int kAudioScale = 48000; static const int kAudioScale = 48000;
static const int kVideoScale = 25; static const int kVideoScale = 25;
static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
static const uint8 kAuxInfo[] = { static const uint8 kAuxInfo[] = {
// Sample 1: IV (no subsumples).
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
// Sample 2: IV.
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32, 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32,
// Sample 2: Subsample count.
0x00, 0x02, 0x00, 0x02,
// Sample 2: Subsample 1.
0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
0x00, 0x03, 0x00, 0x00, 0x00, 0x04 // Sample 2: Subsample 2.
}; 0x00, 0x03, 0x00, 0x00, 0x00, 0x04};
static const char kIv1[] = { static const char kIv1[] = {0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, };
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8 kKeyId[] = { static const uint8 kKeyId[] = {0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54,
0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44};
0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44
};
namespace media { namespace media {
namespace mp4 { namespace mp4 {
class TrackRunIteratorTest : public testing::Test { class TrackRunIteratorTest : public testing::Test {
public: public:
TrackRunIteratorTest() { TrackRunIteratorTest() { CreateMovie(); }
CreateMovie();
}
protected: protected:
Movie moov_; Movie moov_;
@ -87,7 +82,7 @@ class TrackRunIteratorTest : public testing::Test {
moof.tracks.resize(2); moof.tracks.resize(2);
moof.tracks[0].decode_time.decode_time = 0; moof.tracks[0].decode_time.decode_time = 0;
moof.tracks[0].header.track_id = 1; 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_duration = 1024;
moof.tracks[0].header.default_sample_size = 4; moof.tracks[0].header.default_sample_size = 4;
moof.tracks[0].runs.resize(2); 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[0].runs[1].data_offset = 10000;
moof.tracks[1].header.track_id = 2; 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].decode_time.decode_time = 10;
moof.tracks[1].runs.resize(1); moof.tracks[1].runs.resize(1);
moof.tracks[1].runs[0].sample_count = 10; 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); SetAscending(&moof.tracks[1].runs[0].sample_durations);
moof.tracks[1].runs[0].sample_flags.resize(10); moof.tracks[1].runs[0].sample_flags.resize(10);
for (size_t i = 1; i < moof.tracks[1].runs[0].sample_flags.size(); i++) { for (size_t i = 1; i < moof.tracks[1].runs[0].sample_flags.size(); i++) {
moof.tracks[1].runs[0].sample_flags[i] = moof.tracks[1].runs[0].sample_flags[i] = kNonKeySampleMask;
kSampleIsDifferenceSampleFlagMask;
} }
return moof; return moof;
@ -129,9 +123,8 @@ class TrackRunIteratorTest : public testing::Test {
sinf->type.type = FOURCC_CENC; sinf->type.type = FOURCC_CENC;
sinf->info.track_encryption.is_encrypted = true; sinf->info.track_encryption.is_encrypted = true;
sinf->info.track_encryption.default_iv_size = 8; sinf->info.track_encryption.default_iv_size = 8;
sinf->info.track_encryption.default_kid.insert( sinf->info.track_encryption.default_kid.assign(kKeyId,
sinf->info.track_encryption.default_kid.begin(), kKeyId + arraysize(kKeyId));
kKeyId, kKeyId + arraysize(kKeyId));
} }
// Add aux info covering the first track run to a TrackFragment, and update // Add aux info covering the first track run to a TrackFragment, and update
@ -177,7 +170,8 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
EXPECT_TRUE(iter_->is_keyframe()); EXPECT_TRUE(iter_->is_keyframe());
// Advance to the last sample in the current run, and test its properties // 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_->track_id(), 1u);
EXPECT_EQ(iter_->sample_offset(), 100 + kSumAscending1); EXPECT_EQ(iter_->sample_offset(), 100 + kSumAscending1);
EXPECT_EQ(iter_->sample_size(), 10); EXPECT_EQ(iter_->sample_size(), 10);
@ -192,7 +186,8 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
// Test last sample of next run // Test last sample of next run
iter_->AdvanceRun(); iter_->AdvanceRun();
EXPECT_TRUE(iter_->is_keyframe()); 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_->track_id(), 2u);
EXPECT_EQ(iter_->sample_offset(), 200 + kSumAscending1); EXPECT_EQ(iter_->sample_offset(), 200 + kSumAscending1);
EXPECT_EQ(iter_->sample_size(), 10); EXPECT_EQ(iter_->sample_size(), 10);
@ -216,11 +211,10 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) { TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) {
moov_.extends.tracks[0].default_sample_duration = 50; moov_.extends.tracks[0].default_sample_duration = 50;
moov_.extends.tracks[0].default_sample_size = 3; moov_.extends.tracks[0].default_sample_size = 3;
moov_.extends.tracks[0].default_sample_flags = moov_.extends.tracks[0].default_sample_flags = kNonKeySampleMask;
kSampleIsDifferenceSampleFlagMask;
iter_.reset(new TrackRunIterator(&moov_)); iter_.reset(new TrackRunIterator(&moov_));
MovieFragment moof = CreateFragment(); 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_size = 0;
moof.tracks[0].header.default_sample_duration = 0; moof.tracks[0].header.default_sample_duration = 0;
moof.tracks[0].runs[0].sample_sizes.clear(); moof.tracks[0].runs[0].sample_sizes.clear();
@ -239,9 +233,8 @@ TEST_F(TrackRunIteratorTest, FirstSampleFlagTest) {
// defaults for all subsequent samples // defaults for all subsequent samples
iter_.reset(new TrackRunIterator(&moov_)); iter_.reset(new TrackRunIterator(&moov_));
MovieFragment moof = CreateFragment(); MovieFragment moof = CreateFragment();
moof.tracks[1].header.has_default_sample_flags = true; moof.tracks[1].header.flags = kDefaultSampleFlagsPresentMask;
moof.tracks[1].header.default_sample_flags = moof.tracks[1].header.default_sample_flags = kNonKeySampleMask;
kSampleIsDifferenceSampleFlagMask;
moof.tracks[1].runs[0].sample_flags.resize(1); moof.tracks[1].runs[0].sample_flags.resize(1);
ASSERT_TRUE(iter_->Init(moof)); ASSERT_TRUE(iter_->Init(moof));
iter_->AdvanceRun(); iter_->AdvanceRun();
@ -251,7 +244,7 @@ TEST_F(TrackRunIteratorTest, FirstSampleFlagTest) {
} }
TEST_F(TrackRunIteratorTest, ReorderingTest) { 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: // decode timestamps:
// //
// 0ms 40ms 120ms 240ms // 0ms 40ms 120ms 240ms
@ -261,23 +254,11 @@ TEST_F(TrackRunIteratorTest, ReorderingTest) {
// //
// 0ms 40ms 160ms 240ms // 0ms 40ms 160ms 240ms
// | 0 | 2 - - | 1 - | // | 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_)); 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 // 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 // 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 // maximum compatibility, these values are biased up to [2, 5, 0].
// extra 80ms is removed via the edit list.
MovieFragment moof = CreateFragment(); MovieFragment moof = CreateFragment();
std::vector<int32>& cts_offsets = std::vector<int32>& cts_offsets =
moof.tracks[1].runs[0].sample_composition_time_offsets; moof.tracks[1].runs[0].sample_composition_time_offsets;
@ -290,15 +271,15 @@ TEST_F(TrackRunIteratorTest, ReorderingTest) {
ASSERT_TRUE(iter_->Init(moof)); ASSERT_TRUE(iter_->Init(moof));
iter_->AdvanceRun(); iter_->AdvanceRun();
EXPECT_EQ(iter_->dts(), 0); EXPECT_EQ(iter_->dts(), 0);
EXPECT_EQ(iter_->cts(), 0); EXPECT_EQ(iter_->cts(), 2);
EXPECT_EQ(iter_->duration(), 1); EXPECT_EQ(iter_->duration(), 1);
iter_->AdvanceSample(); iter_->AdvanceSample();
EXPECT_EQ(iter_->dts(), 1); EXPECT_EQ(iter_->dts(), 1);
EXPECT_EQ(iter_->cts(), 4); EXPECT_EQ(iter_->cts(), 6);
EXPECT_EQ(iter_->duration(), 2); EXPECT_EQ(iter_->duration(), 2);
iter_->AdvanceSample(); iter_->AdvanceSample();
EXPECT_EQ(iter_->dts(), 3); EXPECT_EQ(iter_->dts(), 3);
EXPECT_EQ(iter_->cts(), 1); EXPECT_EQ(iter_->cts(), 3);
EXPECT_EQ(iter_->duration(), 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); EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[0].runs[0].data_offset);
scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig(); scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
ASSERT_EQ(arraysize(kKeyId), config->key_id().size()); ASSERT_EQ(arraysize(kKeyId), config->key_id().size());
EXPECT_TRUE(!memcmp(kKeyId, config->key_id().data(), EXPECT_TRUE(
config->key_id().size())); !memcmp(kKeyId, config->key_id().data(), config->key_id().size()));
ASSERT_EQ(arraysize(kIv1), config->iv().size()); ASSERT_EQ(arraysize(kIv1), config->iv().size());
EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size())); EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
EXPECT_TRUE(config->subsamples().empty()); EXPECT_TRUE(config->subsamples().empty());