OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/mp4/mp4_stream_parser.h" | 5 #include "media/mp4/mp4_stream_parser.h" |
6 | 6 |
7 #include "base/callback.h" | 7 #include "base/callback.h" |
8 #include "base/callback_helpers.h" | 8 #include "base/callback_helpers.h" |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/time.h" | 10 #include "base/time.h" |
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
150 track != moov_->tracks.end(); ++track) { | 150 track != moov_->tracks.end(); ++track) { |
151 // TODO(strobe): Only the first audio and video track present in a file are | 151 // TODO(strobe): Only the first audio and video track present in a file are |
152 // used. (Track selection is better accomplished via Source IDs, though, so | 152 // used. (Track selection is better accomplished via Source IDs, though, so |
153 // adding support for track selection within a stream is low-priority.) | 153 // adding support for track selection within a stream is low-priority.) |
154 const SampleDescription& samp_descr = | 154 const SampleDescription& samp_descr = |
155 track->media.information.sample_table.description; | 155 track->media.information.sample_table.description; |
156 if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) { | 156 if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) { |
157 RCHECK(!samp_descr.audio_entries.empty()); | 157 RCHECK(!samp_descr.audio_entries.empty()); |
158 const AudioSampleEntry& entry = samp_descr.audio_entries[0]; | 158 const AudioSampleEntry& entry = samp_descr.audio_entries[0]; |
159 | 159 |
160 // TODO(strobe): We accept all format values, pending clarification on | 160 RCHECK(entry.format == FOURCC_MP4A || |
ddorwin
2012/07/17 01:14:21
Is this worth logging since it means the file form
strobe_
2012/07/19 02:43:35
I'd prefer to leave it in. DLOG() is cheaper than
ddorwin
2012/07/19 06:11:10
I think I meant should we ALWAYS log it.
strobe_
2012/07/19 16:55:02
Oh! RCHECK() does a DLOG(ERROR) on false. Should w
ddorwin
2012/07/24 01:00:09
I was thinking a separate LOG() for these cases si
| |
161 // the formats used for encrypted media (http://crbug.com/132351). | 161 (entry.format == FOURCC_ENCA && |
162 // RCHECK(entry.format == FOURCC_MP4A || | 162 entry.sinf.format.format == FOURCC_MP4A)); |
163 // (entry.format == FOURCC_ENCA && | 163 RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption)); |
164 // entry.sinf.format.format == FOURCC_MP4A)); | |
165 | 164 |
166 // Check if it is MPEG4 AAC defined in ISO 14496 Part 3. | 165 // Check if it is MPEG4 AAC defined in ISO 14496 Part 3. |
167 RCHECK(entry.esds.object_type == kISO_14496_3); | 166 RCHECK(entry.esds.object_type == kISO_14496_3); |
ddorwin
2012/07/17 01:14:21
same
| |
168 aac_ = entry.esds.aac; | 167 aac_ = entry.esds.aac; |
169 audio_config.Initialize(kCodecAAC, entry.samplesize, | 168 audio_config.Initialize(kCodecAAC, entry.samplesize, |
170 aac_.channel_layout(), aac_.frequency(), | 169 aac_.channel_layout(), aac_.frequency(), |
171 NULL, 0, false); | 170 NULL, 0, false); |
172 | 171 |
173 has_audio_ = true; | 172 has_audio_ = true; |
174 audio_track_id_ = track->header.track_id; | 173 audio_track_id_ = track->header.track_id; |
175 } | 174 } |
176 if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { | 175 if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { |
177 RCHECK(!samp_descr.video_entries.empty()); | 176 RCHECK(!samp_descr.video_entries.empty()); |
178 const VideoSampleEntry& entry = samp_descr.video_entries[0]; | 177 const VideoSampleEntry& entry = samp_descr.video_entries[0]; |
179 | 178 |
180 // RCHECK(entry.format == FOURCC_AVC1 || | 179 RCHECK(entry.format == FOURCC_AVC1 || |
ddorwin
2012/07/17 01:14:21
same
| |
181 // (entry.format == FOURCC_ENCV && | 180 (entry.format == FOURCC_ENCV && |
182 // entry.sinf.format.format == FOURCC_AVC1)); | 181 entry.sinf.format.format == FOURCC_AVC1)); |
182 RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption)); | |
183 | 183 |
184 // TODO(strobe): Recover correct crop box | 184 // TODO(strobe): Recover correct crop box |
185 video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, | 185 video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, |
186 gfx::Size(entry.width, entry.height), | 186 gfx::Size(entry.width, entry.height), |
187 gfx::Rect(0, 0, entry.width, entry.height), | 187 gfx::Rect(0, 0, entry.width, entry.height), |
188 // Framerate of zero is provided to signal that | 188 // Framerate of zero is provided to signal that |
189 // the decoder should trust demuxer timestamps | 189 // the decoder should trust demuxer timestamps |
190 0, 1, | 190 0, 1, |
ddorwin
2012/07/17 01:14:21
what is the 1?
if it's not paired with the 0 (or e
strobe_
2012/07/19 02:43:35
Framerate numerator and denominator.
| |
191 entry.pixel_aspect.h_spacing, | 191 entry.pixel_aspect.h_spacing, |
192 entry.pixel_aspect.v_spacing, | 192 entry.pixel_aspect.v_spacing, |
193 // No decoder-specific buffer needed for AVC; | 193 // No decoder-specific buffer needed for AVC; |
194 // SPS/PPS are embedded in the video stream | 194 // SPS/PPS are embedded in the video stream |
195 NULL, 0, false); | 195 NULL, 0, false); |
196 has_video_ = true; | 196 has_video_ = true; |
197 video_track_id_ = track->header.track_id; | 197 video_track_id_ = track->header.track_id; |
198 | 198 |
199 size_of_nalu_length_ = entry.avcc.length_size; | 199 size_of_nalu_length_ = entry.avcc.length_size; |
200 } | 200 } |
201 } | 201 } |
202 | 202 |
203 // TODO(strobe): For now, we avoid sending new configs on a new | 203 // TODO(strobe): For now, we avoid sending new configs on a new |
204 // reinitialization segment, and instead simply embed the updated parameter | 204 // reinitialization segment, and instead simply embed the updated parameter |
205 // sets into the video stream. The conditional should be removed when | 205 // sets into the video stream. The conditional should be removed when |
206 // http://crbug.com/122913 is fixed. | 206 // http://crbug.com/122913 is fixed. |
207 if (!init_cb_.is_null()) | 207 if (!init_cb_.is_null()) |
ddorwin
2012/07/17 01:14:21
checking wrong callback?
strobe_
2012/07/19 02:43:35
No, I think this is correct. We currently use null
ddorwin
2012/07/19 06:11:10
Hmm, might be worth a comment explaining that here
strobe_
2012/07/19 16:55:02
Done.
| |
208 RCHECK(config_cb_.Run(audio_config, video_config)); | 208 RCHECK(config_cb_.Run(audio_config, video_config)); |
209 | 209 |
210 base::TimeDelta duration; | 210 base::TimeDelta duration; |
211 if (moov_->extends.header.fragment_duration > 0) { | 211 if (moov_->extends.header.fragment_duration > 0) { |
212 duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration, | 212 duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration, |
213 moov_->header.timescale); | 213 moov_->header.timescale); |
214 } else if (moov_->header.duration > 0) { | 214 } else if (moov_->header.duration > 0) { |
215 duration = TimeDeltaFromFrac(moov_->header.duration, | 215 duration = TimeDeltaFromFrac(moov_->header.duration, |
216 moov_->header.timescale); | 216 moov_->header.timescale); |
217 } else { | 217 } else { |
218 duration = kInfiniteDuration(); | 218 duration = kInfiniteDuration(); |
219 } | 219 } |
220 | 220 |
221 if (!init_cb_.is_null()) | 221 if (!init_cb_.is_null()) |
222 base::ResetAndReturn(&init_cb_).Run(true, duration); | 222 base::ResetAndReturn(&init_cb_).Run(true, duration); |
223 return true; | 223 return true; |
224 } | 224 } |
225 | 225 |
226 bool MP4StreamParser::ParseMoof(BoxReader* reader) { | 226 bool MP4StreamParser::ParseMoof(BoxReader* reader) { |
227 RCHECK(moov_.get()); // Must already have initialization segment | 227 RCHECK(moov_.get()); // Must already have initialization segment |
228 MovieFragment moof; | 228 MovieFragment moof; |
229 RCHECK(moof.Parse(reader)); | 229 RCHECK(moof.Parse(reader)); |
230 RCHECK(runs_.Init(*moov_, moof)); | 230 RCHECK(runs_.Init(*moov_, moof)); |
231 new_segment_cb_.Run(runs_.GetMinDecodeTimestamp()); | 231 new_segment_cb_.Run(runs_.GetMinDecodeTimestamp()); |
232 ChangeState(kEmittingSamples); | 232 ChangeState(kEmittingSamples); |
233 return true; | 233 return true; |
234 } | 234 } |
235 | 235 |
236 bool MP4StreamParser::EmitKeyNeeded(const TrackEncryption& track_encryption) { | |
237 // TODO(strobe): Send the correct value for initData. The format of initData | |
238 // has not yet been defined; see | |
239 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673. | |
240 if (!track_encryption.is_encrypted) return true; | |
241 scoped_array<uint8> kid(new uint8[track_encryption.default_kid.size()]); | |
242 memcpy(kid.get(), &track_encryption.default_kid[0], | |
243 track_encryption.default_kid.size()); | |
244 return need_key_cb_.Run(kid.Pass(), track_encryption.default_kid.size()); | |
245 } | |
246 | |
247 bool MP4StreamParser::PrepareAVCBuffer( | |
248 std::vector<uint8>* frame_buf, | |
249 std::vector<SubsampleEntry>* subsamples) { | |
250 // Convert the AVC NALU length fields to Annex B headers, as expected by | |
251 // decoding libraries. Since this may enlarge the size of the buffer, we also | |
252 // update the clear byte count for each subsample if encryption is used. | |
253 RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, frame_buf)); | |
ddorwin
2012/07/17 01:14:21
Is there a [D]CHECK that size_of_nalu_length_ <= 4
strobe_
2012/07/19 02:43:35
Yes, in ConvertToAnnexB.
| |
254 if (!subsamples->empty()) { | |
255 const int nalu_size_diff = 4 - size_of_nalu_length_; | |
256 size_t expected_size = runs_.sample_size() + | |
257 subsamples->size() * nalu_size_diff; | |
258 RCHECK(frame_buf->size() == expected_size); | |
259 for (size_t i = 0; i < subsamples->size(); i++) | |
260 (*subsamples)[i].clear_bytes += nalu_size_diff; | |
261 } | |
262 | |
263 if (runs_.is_keyframe()) { | |
264 // If this is a keyframe, we (re-)inject SPS and PPS headers at the start of | |
265 // a frame. If subsample info is present, we also update the clear byte | |
ddorwin
2012/07/17 01:14:21
update to what?
strobe_
2012/07/19 02:43:35
Commented.
ddorwin
2012/07/19 06:11:10
Where?
ddorwin
2012/07/24 01:00:09
Seem to have missed this one. :)
strobe_
2012/07/25 01:05:13
Done.
| |
266 // count for that first subsample. | |
267 const AVCDecoderConfigurationRecord* avc_config = NULL; | |
268 for (size_t t = 0; t < moov_->tracks.size(); t++) { | |
269 if (moov_->tracks[t].header.track_id == runs_.track_id()) { | |
270 avc_config = &moov_->tracks[t].media.information. | |
ddorwin
2012/07/17 01:14:21
strange line break. I think everything after = sho
strobe_
2012/07/19 02:43:35
Rebased away.
ddorwin
2012/07/19 06:11:10
Still here.
strobe_
2012/07/19 16:55:02
Sorry, *really* fixed. Thanks for your dilligence!
| |
271 sample_table.description.video_entries[0].avcc; | |
ddorwin
2012/07/17 01:14:21
is video_entries size guaranteed to be >= 1?
strobe_
2012/07/19 02:43:35
Rebased away.
ddorwin
2012/07/19 06:11:10
Still here.
strobe_
2012/07/19 16:55:02
Done.
| |
272 break; | |
273 } | |
274 } | |
275 RCHECK(avc_config != NULL); | |
276 std::vector<uint8> param_sets; | |
277 RCHECK(AVC::ConvertParameterSets(*avc_config, ¶m_sets)); | |
ddorwin
2012/07/17 01:14:21
ConvertConfigToParameterSets?
strobe_
2012/07/19 02:43:35
Done.
| |
278 frame_buf->insert(frame_buf->begin(), | |
279 param_sets.begin(), param_sets.end()); | |
280 if (!subsamples->empty()) | |
281 (*subsamples)[0].clear_bytes += param_sets.size(); | |
282 } | |
283 return true; | |
284 } | |
285 | |
236 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, | 286 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, |
237 BufferQueue* video_buffers, | 287 BufferQueue* video_buffers, |
238 bool* err) { | 288 bool* err) { |
239 if (!runs_.RunValid()) { | 289 if (!runs_.RunIsValid()) { |
ddorwin
2012/07/17 01:14:21
Why isn't this IsRunValid? Unless you really are r
strobe_
2012/07/19 02:43:35
Evidence of my functional bias. Renamed.
| |
240 // Flush any buffers we've gotten in this chunk so that buffers don't | 290 // Flush any buffers we've gotten in this chunk so that buffers don't |
241 // cross NewSegment() calls | 291 // cross NewSegment() calls |
242 *err = !SendAndFlushSamples(audio_buffers, video_buffers); | 292 *err = !SendAndFlushSamples(audio_buffers, video_buffers); |
243 if (*err) return false; | 293 if (*err) return false; |
244 ChangeState(kParsingBoxes); | 294 ChangeState(kParsingBoxes); |
245 return true; | 295 return true; |
246 } | 296 } |
247 | 297 |
248 if (!runs_.SampleValid()) { | 298 if (!runs_.SampleIsValid()) { |
ddorwin
2012/07/17 01:14:21
same
strobe_
2012/07/19 02:43:35
Done.
| |
249 runs_.AdvanceRun(); | 299 runs_.AdvanceRun(); |
250 return true; | 300 return true; |
251 } | 301 } |
252 | 302 |
253 DCHECK(!(*err)); | 303 DCHECK(!(*err)); |
254 | 304 |
255 const uint8* buf; | 305 const uint8* buf; |
256 int size; | 306 int buf_size; |
257 queue_.Peek(&buf, &size); | 307 queue_.Peek(&buf, &buf_size); |
258 if (!size) return false; | 308 if (!buf_size) return false; |
259 | 309 |
260 bool audio = has_audio_ && audio_track_id_ == runs_.track_id(); | 310 bool audio = has_audio_ && audio_track_id_ == runs_.track_id(); |
261 bool video = has_video_ && video_track_id_ == runs_.track_id(); | 311 bool video = has_video_ && video_track_id_ == runs_.track_id(); |
262 | 312 |
263 // Skip this entire track if it's not one we're interested in | 313 // Skip this entire track if it's not one we're interested in |
264 if (!audio && !video) runs_.AdvanceRun(); | 314 if (!audio && !video) runs_.AdvanceRun(); |
265 | 315 |
266 // Attempt to cache the auxiliary information first. Aux info is usually | 316 // Attempt to cache the auxiliary information first. Aux info is usually |
267 // placed in a contiguous block before the sample data, rather than being | 317 // placed in a contiguous block before the sample data, rather than being |
268 // interleaved. If we didn't cache it, this would require that we retain the | 318 // interleaved. If we didn't cache it, this would require that we retain the |
269 // start of the segment buffer while reading samples. Aux info is typically | 319 // start of the segment buffer while reading samples. Aux info is typically |
270 // quite small compared to sample data, so this pattern is useful on | 320 // quite small compared to sample data, so this pattern is useful on |
271 // memory-constrained devices where the source buffer consumes a substantial | 321 // memory-constrained devices where the source buffer consumes a substantial |
272 // portion of the total system memory. | 322 // portion of the total system memory. |
273 if (runs_.NeedsCENC()) { | 323 if (runs_.AuxInfoNeedsToBeCached()) { |
274 queue_.PeekAt(runs_.cenc_offset() + moof_head_, &buf, &size); | 324 queue_.PeekAt(runs_.aux_info_offset() + moof_head_, &buf, &buf_size); |
275 return runs_.CacheCENC(buf, size); | 325 if (buf_size < runs_.aux_info_size()) return false; |
326 *err = !runs_.CacheAuxInfo(buf, buf_size); | |
327 return !*err; | |
276 } | 328 } |
277 | 329 |
278 queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size); | 330 queue_.PeekAt(runs_.sample_offset() + moof_head_, &buf, &buf_size); |
279 if (size < runs_.size()) return false; | 331 if (buf_size < runs_.sample_size()) return false; |
280 | 332 |
281 std::vector<uint8> frame_buf(buf, buf + runs_.size()); | 333 scoped_ptr<DecryptConfig> decrypt_config; |
334 if (runs_.is_encrypted()) | |
335 decrypt_config = runs_.GetDecryptConfig(); | |
336 | |
337 std::vector<uint8> frame_buf(buf, buf + runs_.sample_size()); | |
282 if (video) { | 338 if (video) { |
283 RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, &frame_buf)); | 339 std::vector<SubsampleEntry> subsamples; |
284 if (runs_.is_keyframe()) { | 340 if (decrypt_config.get()) |
285 const AVCDecoderConfigurationRecord* avc_config = NULL; | 341 subsamples = decrypt_config->subsamples(); |
286 for (size_t t = 0; t < moov_->tracks.size(); t++) { | 342 RCHECK(PrepareAVCBuffer(&frame_buf, &subsamples)); |
ddorwin
2012/07/17 01:14:21
If else above, we want to run this for the key fra
strobe_
2012/07/19 02:43:35
Sorry, can you clarify this comment?
ddorwin
2012/07/19 06:11:10
If line 340 - if (decrypt_config.get()) - is not t
strobe_
2012/07/19 16:55:02
No. H.264 NALUs are stored in "AVC" format (length
ddorwin
2012/07/24 01:00:09
Ahh, I overlooked the conversion call.
| |
287 if (moov_->tracks[t].header.track_id == runs_.track_id()) { | 343 if (!subsamples.empty()) { |
288 avc_config = &moov_->tracks[t].media.information. | 344 decrypt_config.reset(new DecryptConfig( |
289 sample_table.description.video_entries[0].avcc; | 345 decrypt_config->key_id(), |
290 break; | 346 decrypt_config->key_id_size(), |
291 } | 347 decrypt_config->iv(), |
292 } | 348 subsamples)); |
293 RCHECK(avc_config != NULL); | |
294 RCHECK(AVC::InsertParameterSets(*avc_config, &frame_buf)); | |
295 } | 349 } |
296 } | 350 } |
297 | 351 |
298 if (audio) { | 352 if (audio) { |
299 aac_.ConvertEsdsToADTS(&frame_buf); | 353 aac_.ConvertEsdsToADTS(&frame_buf); |
300 } | 354 } |
301 | 355 |
302 scoped_refptr<StreamParserBuffer> stream_buf = | 356 scoped_refptr<StreamParserBuffer> stream_buf = |
303 StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), | 357 StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), |
304 runs_.is_keyframe()); | 358 runs_.is_keyframe()); |
305 | 359 |
360 if (runs_.is_encrypted()) | |
361 stream_buf->SetDecryptConfig(decrypt_config.Pass()); | |
362 | |
306 stream_buf->SetDuration(runs_.duration()); | 363 stream_buf->SetDuration(runs_.duration()); |
307 stream_buf->SetTimestamp(runs_.cts()); | 364 stream_buf->SetTimestamp(runs_.cts()); |
308 stream_buf->SetDecodeTimestamp(runs_.dts()); | 365 stream_buf->SetDecodeTimestamp(runs_.dts()); |
309 | 366 |
310 DVLOG(3) << "Pushing frame: aud=" << audio | 367 DVLOG(3) << "Pushing frame: aud=" << audio |
311 << ", key=" << runs_.is_keyframe() | 368 << ", key=" << runs_.is_keyframe() |
312 << ", dur=" << runs_.duration().InMilliseconds() | 369 << ", dur=" << runs_.duration().InMilliseconds() |
313 << ", dts=" << runs_.dts().InMilliseconds() | 370 << ", dts=" << runs_.dts().InMilliseconds() |
314 << ", cts=" << runs_.cts().InMilliseconds() | 371 << ", cts=" << runs_.cts().InMilliseconds() |
ddorwin
2012/07/17 01:14:21
set cts before dts above. should it appear that wa
strobe_
2012/07/19 02:43:35
Timestamp is the more important field, since Decod
| |
315 << ", size=" << runs_.size(); | 372 << ", size=" << runs_.sample_size(); |
316 | 373 |
317 if (audio) { | 374 if (audio) { |
318 audio_buffers->push_back(stream_buf); | 375 audio_buffers->push_back(stream_buf); |
319 } else { | 376 } else { |
320 video_buffers->push_back(stream_buf); | 377 video_buffers->push_back(stream_buf); |
321 } | 378 } |
322 | 379 |
323 runs_.AdvanceSample(); | 380 runs_.AdvanceSample(); |
324 return true; | 381 return true; |
325 } | 382 } |
326 | 383 |
327 bool MP4StreamParser::SendAndFlushSamples(BufferQueue* audio_buffers, | 384 bool MP4StreamParser::SendAndFlushSamples(BufferQueue* audio_buffers, |
328 BufferQueue* video_buffers) { | 385 BufferQueue* video_buffers) { |
329 if (!audio_buffers->empty()) { | 386 if (!audio_buffers->empty()) { |
330 if (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers)) | 387 if (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers)) |
331 return false; | 388 return false; |
332 audio_buffers->clear(); | 389 audio_buffers->clear(); |
ddorwin
2012/07/17 01:14:21
Don't clear the buffers if there is no cb or it fa
strobe_
2012/07/19 02:43:35
Fixed.
ddorwin
2012/07/19 06:11:10
Where?
strobe_
2012/07/19 16:55:02
Apparently the fix was also lost in the failed reb
ddorwin
2012/07/24 01:00:09
I don't understand. Are you intentionally keeping
strobe_
2012/07/25 01:05:13
Done.
| |
333 } | 390 } |
334 if (!video_buffers->empty()) { | 391 if (!video_buffers->empty()) { |
335 if (video_cb_.is_null() || !video_cb_.Run(*video_buffers)) | 392 if (video_cb_.is_null() || !video_cb_.Run(*video_buffers)) |
336 return false; | 393 return false; |
337 video_buffers->clear(); | 394 video_buffers->clear(); |
338 } | 395 } |
339 return true; | 396 return true; |
340 } | 397 } |
341 | 398 |
342 bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) { | 399 bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) { |
(...skipping 21 matching lines...) Expand all Loading... | |
364 return true; | 421 return true; |
365 } | 422 } |
366 | 423 |
367 void MP4StreamParser::ChangeState(State new_state) { | 424 void MP4StreamParser::ChangeState(State new_state) { |
368 DVLOG(2) << "Changing state: " << new_state; | 425 DVLOG(2) << "Changing state: " << new_state; |
369 state_ = new_state; | 426 state_ = new_state; |
370 } | 427 } |
371 | 428 |
372 } // namespace mp4 | 429 } // namespace mp4 |
373 } // namespace media | 430 } // namespace media |
OLD | NEW |