Removed EditList, added NAL and several other cleanup.
Change-Id: I2658eae0789f1c4e8d0534a6ff70267058bee2fc
This commit is contained in:
parent
21aad421ce
commit
dc88702315
|
@ -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)
|
||||||
|
|
|
@ -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_
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in New Issue