diff --git a/packager/media/base/demuxer.cc b/packager/media/base/demuxer.cc index 45400958f2..327eaa5ce3 100644 --- a/packager/media/base/demuxer.cc +++ b/packager/media/base/demuxer.cc @@ -92,6 +92,10 @@ Status Demuxer::Initialize() { base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)), key_source_.get()); + // Handle trailing 'moov'. + if (container == CONTAINER_MOV) + static_cast(parser_.get())->LoadMoov(file_name_); + if (!parser_->Parse(buffer_.get(), bytes_read)) { init_parsing_status_ = Status(error::PARSER_FAILURE, "Cannot parse media file " + file_name_); diff --git a/packager/media/formats/mp4/mp4.gyp b/packager/media/formats/mp4/mp4.gyp index 4f0346c583..35f091af7b 100644 --- a/packager/media/formats/mp4/mp4.gyp +++ b/packager/media/formats/mp4/mp4.gyp @@ -78,6 +78,7 @@ 'dependencies': [ '../../../testing/gtest.gyp:gtest', '../../../testing/gmock.gyp:gmock', + '../../file/file.gyp:file', '../../test/media_test.gyp:media_test_support', 'mp4', ] diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 6e23d232d8..dd41e5f362 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -13,10 +13,13 @@ #include "packager/base/strings/string_number_conversions.h" #include "packager/media/base/aes_encryptor.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/key_source.h" #include "packager/media/base/media_sample.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_reader.h" #include "packager/media/formats/mp4/es_descriptor.h" @@ -108,6 +111,82 @@ bool MP4MediaParser::Parse(const uint8_t* buf, int size) { return true; } +bool MP4MediaParser::LoadMoov(const std::string& file_path) { + scoped_ptr 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 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) { const uint8_t* buf; int size; @@ -151,6 +230,9 @@ bool MP4MediaParser::ParseBox(bool* err) { } bool MP4MediaParser::ParseMoov(BoxReader* reader) { + if (moov_) + return true; // Already parsed the 'moov' box. + moov_.reset(new Movie); RCHECK(moov_->Parse(reader)); runs_.reset(); @@ -530,10 +612,6 @@ bool MP4MediaParser::ReadAndDiscardMDATsUntil(const int64_t offset) { if (!BoxReader::StartTopLevelBox(buf, size, &type, &box_sz, &err)) break; - if (type != FOURCC_MDAT) { - LOG(ERROR) << "Unexpected box type while parsing MDATs: " - << FourCCToString(type); - } mdat_tail_ += box_sz; } queue_.Trim(std::min(mdat_tail_, offset)); diff --git a/packager/media/formats/mp4/mp4_media_parser.h b/packager/media/formats/mp4/mp4_media_parser.h index 748aa53b93..73226bba44 100644 --- a/packager/media/formats/mp4/mp4_media_parser.h +++ b/packager/media/formats/mp4/mp4_media_parser.h @@ -47,6 +47,14 @@ class MP4MediaParser : public MediaParser { 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: enum State { kWaitingForInit, diff --git a/packager/media/formats/mp4/mp4_media_parser_unittest.cc b/packager/media/formats/mp4/mp4_media_parser_unittest.cc index 2859f4a7a7..c144f0a274 100644 --- a/packager/media/formats/mp4/mp4_media_parser_unittest.cc +++ b/packager/media/formats/mp4/mp4_media_parser_unittest.cc @@ -91,7 +91,8 @@ class MP4MediaParserTest : public testing::Test { bool ParseMP4File(const std::string& filename, int append_bytes) { InitializeParser(NULL); - + if (!parser_->LoadMoov(GetTestDataFilePath(filename).value())) + return false; std::vector buffer = ReadTestDataFile(filename); return AppendDataInPieces(buffer.data(), buffer.size(), append_bytes); } @@ -120,6 +121,12 @@ TEST_F(MP4MediaParserTest, MultiFragmentAppend) { 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) { // Flush while reading sample data, then start a new stream. InitializeParser(NULL); @@ -127,18 +134,10 @@ TEST_F(MP4MediaParserTest, Flush) { std::vector buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4"); EXPECT_TRUE(AppendDataInPieces(buffer.data(), 65536, 512)); parser_->Flush(); - EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512)); EXPECT_EQ(2u, num_streams_); - EXPECT_EQ(201u, num_samples_); -} - -TEST_F(MP4MediaParserTest, Reinitialization) { - InitializeParser(NULL); - - std::vector buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4"); + EXPECT_NE(0u, num_samples_); + num_samples_ = 0; 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_); } diff --git a/packager/media/test/data/bear-1280x720-trailing-moov.mp4 b/packager/media/test/data/bear-1280x720-trailing-moov.mp4 new file mode 100644 index 0000000000..d05cd255c4 Binary files /dev/null and b/packager/media/test/data/bear-1280x720-trailing-moov.mp4 differ