Added support for ISO-BMFF files with trailing 'moov' boxes.

Change-Id: Icdc9058179a998617f865566d635ecdbd0e422c5
This commit is contained in:
Thomas Inskip 2015-05-20 17:38:09 -07:00
parent ada218c089
commit a0dc98c13e
6 changed files with 105 additions and 15 deletions

View File

@ -92,6 +92,10 @@ Status Demuxer::Initialize() {
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)), base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
key_source_.get()); key_source_.get());
// Handle trailing 'moov'.
if (container == CONTAINER_MOV)
static_cast<mp4::MP4MediaParser*>(parser_.get())->LoadMoov(file_name_);
if (!parser_->Parse(buffer_.get(), bytes_read)) { if (!parser_->Parse(buffer_.get(), bytes_read)) {
init_parsing_status_ = init_parsing_status_ =
Status(error::PARSER_FAILURE, "Cannot parse media file " + file_name_); Status(error::PARSER_FAILURE, "Cannot parse media file " + file_name_);

View File

@ -78,6 +78,7 @@
'dependencies': [ 'dependencies': [
'../../../testing/gtest.gyp:gtest', '../../../testing/gtest.gyp:gtest',
'../../../testing/gmock.gyp:gmock', '../../../testing/gmock.gyp:gmock',
'../../file/file.gyp:file',
'../../test/media_test.gyp:media_test_support', '../../test/media_test.gyp:media_test_support',
'mp4', 'mp4',
] ]

View File

@ -13,10 +13,13 @@
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
#include "packager/media/base/aes_encryptor.h" #include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/audio_stream_info.h" #include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/buffer_reader.h"
#include "packager/media/base/decrypt_config.h" #include "packager/media/base/decrypt_config.h"
#include "packager/media/base/key_source.h" #include "packager/media/base/key_source.h"
#include "packager/media/base/media_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/base/video_stream_info.h" #include "packager/media/base/video_stream_info.h"
#include "packager/media/file/file.h"
#include "packager/media/file/file_closer.h"
#include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/box_definitions.h"
#include "packager/media/formats/mp4/box_reader.h" #include "packager/media/formats/mp4/box_reader.h"
#include "packager/media/formats/mp4/es_descriptor.h" #include "packager/media/formats/mp4/es_descriptor.h"
@ -108,6 +111,82 @@ bool MP4MediaParser::Parse(const uint8_t* buf, int size) {
return true; return true;
} }
bool MP4MediaParser::LoadMoov(const std::string& file_path) {
scoped_ptr<File, FileCloser> file(
File::OpenWithNoBuffering(file_path.c_str(), "r"));
if (!file) {
LOG(ERROR) << "Unable to open media file '" << file_path << "'";
return false;
}
if (file->Seek(0) < 0) {
LOG(WARNING) << "Filesystem does not support seeking on file '" << file_path
<< "'";
return false;
}
uint64_t file_position(0);
bool mdat_seen(false);
while (true) {
const uint32_t kBoxHeaderReadSize(16);
std::vector<uint8_t> buffer(kBoxHeaderReadSize);
int64_t bytes_read = file->Read(&buffer[0], kBoxHeaderReadSize);
if (bytes_read == 0) {
LOG(ERROR) << "Could not find 'moov' box in file '" << file_path << "'";
return false;
}
if (bytes_read < kBoxHeaderReadSize) {
LOG(ERROR) << "Error reading media file '" << file_path << "'";
return false;
}
uint64_t box_size;
FourCC box_type;
bool err;
if (!BoxReader::StartTopLevelBox(&buffer[0], kBoxHeaderReadSize, &box_type,
&box_size, &err)) {
LOG(ERROR) << "Could not start top level box from file '" << file_path
<< "'";
return false;
}
if (box_type == FOURCC_MDAT) {
mdat_seen = true;
} else if (box_type == FOURCC_MOOV) {
if (!mdat_seen) {
// 'moov' is before 'mdat'. Nothing to do.
break;
}
// 'mdat' before 'moov'. Read and parse 'moov'.
if (!Parse(&buffer[0], bytes_read)) {
LOG(ERROR) << "Error parsing mp4 file '" << file_path << "'";
return false;
}
uint64_t bytes_to_read = box_size - bytes_read;
buffer.resize(bytes_to_read);
while (bytes_to_read > 0) {
bytes_read = file->Read(&buffer[0], bytes_to_read);
if (bytes_read <= 0) {
LOG(ERROR) << "Error reading 'moov' contents from file '" << file_path
<< "'";
return false;
}
if (!Parse(&buffer[0], bytes_read)) {
LOG(ERROR) << "Error parsing mp4 file '" << file_path << "'";
return false;
}
bytes_to_read -= bytes_read;
}
queue_.Reset(); // So that we don't need to adjust data offsets.
mdat_tail_ = 0; // So it will skip boxes until mdat.
break; // Done.
}
file_position += box_size;
if (!file->Seek(file_position)) {
LOG(ERROR) << "Error skipping box in mp4 file '" << file_path << "'";
return false;
}
}
return true;
}
bool MP4MediaParser::ParseBox(bool* err) { bool MP4MediaParser::ParseBox(bool* err) {
const uint8_t* buf; const uint8_t* buf;
int size; int size;
@ -151,6 +230,9 @@ bool MP4MediaParser::ParseBox(bool* err) {
} }
bool MP4MediaParser::ParseMoov(BoxReader* reader) { bool MP4MediaParser::ParseMoov(BoxReader* reader) {
if (moov_)
return true; // Already parsed the 'moov' box.
moov_.reset(new Movie); moov_.reset(new Movie);
RCHECK(moov_->Parse(reader)); RCHECK(moov_->Parse(reader));
runs_.reset(); runs_.reset();
@ -530,10 +612,6 @@ bool MP4MediaParser::ReadAndDiscardMDATsUntil(const int64_t offset) {
if (!BoxReader::StartTopLevelBox(buf, size, &type, &box_sz, &err)) if (!BoxReader::StartTopLevelBox(buf, size, &type, &box_sz, &err))
break; break;
if (type != FOURCC_MDAT) {
LOG(ERROR) << "Unexpected box type while parsing MDATs: "
<< FourCCToString(type);
}
mdat_tail_ += box_sz; mdat_tail_ += box_sz;
} }
queue_.Trim(std::min(mdat_tail_, offset)); queue_.Trim(std::min(mdat_tail_, offset));

View File

@ -47,6 +47,14 @@ class MP4MediaParser : public MediaParser {
virtual bool Parse(const uint8_t* buf, int size) OVERRIDE; virtual bool Parse(const uint8_t* buf, int size) OVERRIDE;
/// @} /// @}
/// Handles ISO-BMFF containers which have the 'moov' box trailing the
/// movie data ('mdat'). It does this by doing a sparse parse of the file
/// to locate the 'moov' box, and parsing its contents if it is found to be
/// located after the 'mdat' box(es).
/// @param file_path is the path to the media file to be parsed.
/// @return true if successful, false otherwise.
bool LoadMoov(const std::string& file_path);
private: private:
enum State { enum State {
kWaitingForInit, kWaitingForInit,

View File

@ -91,7 +91,8 @@ class MP4MediaParserTest : public testing::Test {
bool ParseMP4File(const std::string& filename, int append_bytes) { bool ParseMP4File(const std::string& filename, int append_bytes) {
InitializeParser(NULL); InitializeParser(NULL);
if (!parser_->LoadMoov(GetTestDataFilePath(filename).value()))
return false;
std::vector<uint8_t> buffer = ReadTestDataFile(filename); std::vector<uint8_t> buffer = ReadTestDataFile(filename);
return AppendDataInPieces(buffer.data(), buffer.size(), append_bytes); return AppendDataInPieces(buffer.data(), buffer.size(), append_bytes);
} }
@ -120,6 +121,12 @@ TEST_F(MP4MediaParserTest, MultiFragmentAppend) {
EXPECT_EQ(201u, num_samples_); EXPECT_EQ(201u, num_samples_);
} }
TEST_F(MP4MediaParserTest, TrailingMoov) {
EXPECT_TRUE(ParseMP4File("bear-1280x720-trailing-moov.mp4", 1024));
EXPECT_EQ(2u, num_streams_);
EXPECT_EQ(201u, num_samples_);
}
TEST_F(MP4MediaParserTest, Flush) { TEST_F(MP4MediaParserTest, Flush) {
// Flush while reading sample data, then start a new stream. // Flush while reading sample data, then start a new stream.
InitializeParser(NULL); InitializeParser(NULL);
@ -127,18 +134,10 @@ TEST_F(MP4MediaParserTest, Flush) {
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4"); std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
EXPECT_TRUE(AppendDataInPieces(buffer.data(), 65536, 512)); EXPECT_TRUE(AppendDataInPieces(buffer.data(), 65536, 512));
parser_->Flush(); parser_->Flush();
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
EXPECT_EQ(2u, num_streams_); EXPECT_EQ(2u, num_streams_);
EXPECT_EQ(201u, num_samples_); EXPECT_NE(0u, num_samples_);
} num_samples_ = 0;
TEST_F(MP4MediaParserTest, Reinitialization) {
InitializeParser(NULL);
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512)); EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
EXPECT_EQ(2u, num_streams_);
EXPECT_EQ(201u, num_samples_); EXPECT_EQ(201u, num_samples_);
} }