1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <algorithm>
6 #include <string>
7
8 #include "base/bind.h"
9 #include "base/bind_helpers.h"
10 #include "base/logging.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/time/time.h"
13 #include "media/base/audio_decoder_config.h"
14 #include "media/base/decoder_buffer.h"
15 #include "media/base/stream_parser_buffer.h"
16 #include "media/base/test_data_util.h"
17 #include "media/base/text_track_config.h"
18 #include "media/base/video_decoder_config.h"
19 #include "media/formats/mp4/es_descriptor.h"
20 #include "media/formats/mp4/mp4_stream_parser.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22
23 using base::TimeDelta;
24
25 namespace media {
26 namespace mp4 {
27
28 // TODO(xhwang): Figure out the init data type appropriately once it's spec'ed.
29 static const char kMp4InitDataType[] = "video/mp4";
30
31 class MP4StreamParserTest : public testing::Test {
32 public:
MP4StreamParserTest()33 MP4StreamParserTest()
34 : configs_received_(false),
35 lower_bound_(
36 DecodeTimestamp::FromPresentationTime(base::TimeDelta::Max())) {
37 std::set<int> audio_object_types;
38 audio_object_types.insert(kISO_14496_3);
39 parser_.reset(new MP4StreamParser(audio_object_types, false));
40 }
41
42 protected:
43 scoped_ptr<MP4StreamParser> parser_;
44 bool configs_received_;
45 DecodeTimestamp lower_bound_;
46
AppendData(const uint8 * data,size_t length)47 bool AppendData(const uint8* data, size_t length) {
48 return parser_->Parse(data, length);
49 }
50
AppendDataInPieces(const uint8 * data,size_t length,size_t piece_size)51 bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) {
52 const uint8* start = data;
53 const uint8* end = data + length;
54 while (start < end) {
55 size_t append_size = std::min(piece_size,
56 static_cast<size_t>(end - start));
57 if (!AppendData(start, append_size))
58 return false;
59 start += append_size;
60 }
61 return true;
62 }
63
InitF(bool init_ok,const StreamParser::InitParameters & params)64 void InitF(bool init_ok, const StreamParser::InitParameters& params) {
65 DVLOG(1) << "InitF: ok=" << init_ok
66 << ", dur=" << params.duration.InMilliseconds()
67 << ", autoTimestampOffset=" << params.auto_update_timestamp_offset;
68 }
69
NewConfigF(const AudioDecoderConfig & ac,const VideoDecoderConfig & vc,const StreamParser::TextTrackConfigMap & tc)70 bool NewConfigF(const AudioDecoderConfig& ac,
71 const VideoDecoderConfig& vc,
72 const StreamParser::TextTrackConfigMap& tc) {
73 DVLOG(1) << "NewConfigF: audio=" << ac.IsValidConfig()
74 << ", video=" << vc.IsValidConfig();
75 configs_received_ = true;
76 return true;
77 }
78
DumpBuffers(const std::string & label,const StreamParser::BufferQueue & buffers)79 void DumpBuffers(const std::string& label,
80 const StreamParser::BufferQueue& buffers) {
81 DVLOG(2) << "DumpBuffers: " << label << " size " << buffers.size();
82 for (StreamParser::BufferQueue::const_iterator buf = buffers.begin();
83 buf != buffers.end(); buf++) {
84 DVLOG(3) << " n=" << buf - buffers.begin()
85 << ", size=" << (*buf)->data_size()
86 << ", dur=" << (*buf)->duration().InMilliseconds();
87 }
88 }
89
NewBuffersF(const StreamParser::BufferQueue & audio_buffers,const StreamParser::BufferQueue & video_buffers,const StreamParser::TextBufferQueueMap & text_map)90 bool NewBuffersF(const StreamParser::BufferQueue& audio_buffers,
91 const StreamParser::BufferQueue& video_buffers,
92 const StreamParser::TextBufferQueueMap& text_map) {
93 DumpBuffers("audio_buffers", audio_buffers);
94 DumpBuffers("video_buffers", video_buffers);
95
96 // TODO(wolenetz/acolwell): Add text track support to more MSE parsers. See
97 // http://crbug.com/336926.
98 if (!text_map.empty())
99 return false;
100
101 // Find the second highest timestamp so that we know what the
102 // timestamps on the next set of buffers must be >= than.
103 DecodeTimestamp audio = !audio_buffers.empty() ?
104 audio_buffers.back()->GetDecodeTimestamp() : kNoDecodeTimestamp();
105 DecodeTimestamp video = !video_buffers.empty() ?
106 video_buffers.back()->GetDecodeTimestamp() : kNoDecodeTimestamp();
107 DecodeTimestamp second_highest_timestamp =
108 (audio == kNoDecodeTimestamp() ||
109 (video != kNoDecodeTimestamp() && audio > video)) ? video : audio;
110
111 DCHECK(second_highest_timestamp != kNoDecodeTimestamp());
112
113 if (lower_bound_ != kNoDecodeTimestamp() &&
114 second_highest_timestamp < lower_bound_) {
115 return false;
116 }
117
118 lower_bound_ = second_highest_timestamp;
119 return true;
120 }
121
KeyNeededF(const std::string & type,const std::vector<uint8> & init_data)122 void KeyNeededF(const std::string& type,
123 const std::vector<uint8>& init_data) {
124 DVLOG(1) << "KeyNeededF: " << init_data.size();
125 EXPECT_EQ(kMp4InitDataType, type);
126 EXPECT_FALSE(init_data.empty());
127 }
128
NewSegmentF()129 void NewSegmentF() {
130 DVLOG(1) << "NewSegmentF";
131 lower_bound_ = kNoDecodeTimestamp();
132 }
133
EndOfSegmentF()134 void EndOfSegmentF() {
135 DVLOG(1) << "EndOfSegmentF()";
136 lower_bound_ =
137 DecodeTimestamp::FromPresentationTime(base::TimeDelta::Max());
138 }
139
InitializeParser()140 void InitializeParser() {
141 parser_->Init(
142 base::Bind(&MP4StreamParserTest::InitF, base::Unretained(this)),
143 base::Bind(&MP4StreamParserTest::NewConfigF, base::Unretained(this)),
144 base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)),
145 true,
146 base::Bind(&MP4StreamParserTest::KeyNeededF, base::Unretained(this)),
147 base::Bind(&MP4StreamParserTest::NewSegmentF, base::Unretained(this)),
148 base::Bind(&MP4StreamParserTest::EndOfSegmentF,
149 base::Unretained(this)),
150 LogCB());
151 }
152
ParseMP4File(const std::string & filename,int append_bytes)153 bool ParseMP4File(const std::string& filename, int append_bytes) {
154 InitializeParser();
155
156 scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename);
157 EXPECT_TRUE(AppendDataInPieces(buffer->data(),
158 buffer->data_size(),
159 append_bytes));
160 return true;
161 }
162 };
163
TEST_F(MP4StreamParserTest,UnalignedAppend)164 TEST_F(MP4StreamParserTest, UnalignedAppend) {
165 // Test small, non-segment-aligned appends (small enough to exercise
166 // incremental append system)
167 ParseMP4File("bear-1280x720-av_frag.mp4", 512);
168 }
169
TEST_F(MP4StreamParserTest,BytewiseAppend)170 TEST_F(MP4StreamParserTest, BytewiseAppend) {
171 // Ensure no incremental errors occur when parsing
172 ParseMP4File("bear-1280x720-av_frag.mp4", 1);
173 }
174
TEST_F(MP4StreamParserTest,MultiFragmentAppend)175 TEST_F(MP4StreamParserTest, MultiFragmentAppend) {
176 // Large size ensures multiple fragments are appended in one call (size is
177 // larger than this particular test file)
178 ParseMP4File("bear-1280x720-av_frag.mp4", 768432);
179 }
180
TEST_F(MP4StreamParserTest,Flush)181 TEST_F(MP4StreamParserTest, Flush) {
182 // Flush while reading sample data, then start a new stream.
183 InitializeParser();
184
185 scoped_refptr<DecoderBuffer> buffer =
186 ReadTestDataFile("bear-1280x720-av_frag.mp4");
187 EXPECT_TRUE(AppendDataInPieces(buffer->data(), 65536, 512));
188 parser_->Flush();
189 EXPECT_TRUE(AppendDataInPieces(buffer->data(),
190 buffer->data_size(),
191 512));
192 }
193
TEST_F(MP4StreamParserTest,Reinitialization)194 TEST_F(MP4StreamParserTest, Reinitialization) {
195 InitializeParser();
196
197 scoped_refptr<DecoderBuffer> buffer =
198 ReadTestDataFile("bear-1280x720-av_frag.mp4");
199 EXPECT_TRUE(AppendDataInPieces(buffer->data(),
200 buffer->data_size(),
201 512));
202 EXPECT_TRUE(AppendDataInPieces(buffer->data(),
203 buffer->data_size(),
204 512));
205 }
206
TEST_F(MP4StreamParserTest,MPEG2_AAC_LC)207 TEST_F(MP4StreamParserTest, MPEG2_AAC_LC) {
208 std::set<int> audio_object_types;
209 audio_object_types.insert(kISO_13818_7_AAC_LC);
210 parser_.reset(new MP4StreamParser(audio_object_types, false));
211 ParseMP4File("bear-mpeg2-aac-only_frag.mp4", 512);
212 }
213
214 // Test that a moov box is not always required after Flush() is called.
TEST_F(MP4StreamParserTest,NoMoovAfterFlush)215 TEST_F(MP4StreamParserTest, NoMoovAfterFlush) {
216 InitializeParser();
217
218 scoped_refptr<DecoderBuffer> buffer =
219 ReadTestDataFile("bear-1280x720-av_frag.mp4");
220 EXPECT_TRUE(AppendDataInPieces(buffer->data(),
221 buffer->data_size(),
222 512));
223 parser_->Flush();
224
225 const int kFirstMoofOffset = 1307;
226 EXPECT_TRUE(AppendDataInPieces(buffer->data() + kFirstMoofOffset,
227 buffer->data_size() - kFirstMoofOffset,
228 512));
229 }
230
231 // Test an invalid file where there are encrypted samples, but
232 // SampleAuxiliaryInformation{Sizes|Offsets}Box (saiz|saio) are missing.
233 // The parser should fail instead of crash. See http://crbug.com/361347
TEST_F(MP4StreamParserTest,MissingSampleAuxInfo)234 TEST_F(MP4StreamParserTest, MissingSampleAuxInfo) {
235 InitializeParser();
236
237 scoped_refptr<DecoderBuffer> buffer =
238 ReadTestDataFile("bear-1280x720-a_frag-cenc_missing-saiz-saio.mp4");
239 EXPECT_FALSE(AppendDataInPieces(buffer->data(), buffer->data_size(), 512));
240 }
241
242 // Test a file where all video samples start with an Access Unit
243 // Delimiter (AUD) NALU.
TEST_F(MP4StreamParserTest,VideoSamplesStartWithAUDs)244 TEST_F(MP4StreamParserTest, VideoSamplesStartWithAUDs) {
245 ParseMP4File("bear-1280x720-av_with-aud-nalus_frag.mp4", 512);
246 }
247
248 // TODO(strobe): Create and test media which uses CENC auxiliary info stored
249 // inside a private box
250
251 } // namespace mp4
252 } // namespace media
253