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/filters/ffmpeg_demuxer.h" | 5 #include "media/filters/ffmpeg_demuxer.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <string> | 8 #include <string> |
9 | 9 |
10 #include "base/base64.h" | 10 #include "base/base64.h" |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
57 case AVMEDIA_TYPE_AUDIO: | 57 case AVMEDIA_TYPE_AUDIO: |
58 type_ = AUDIO; | 58 type_ = AUDIO; |
59 AVStreamToAudioDecoderConfig(stream, &audio_config_, true); | 59 AVStreamToAudioDecoderConfig(stream, &audio_config_, true); |
60 is_encrypted = audio_config_.is_encrypted(); | 60 is_encrypted = audio_config_.is_encrypted(); |
61 break; | 61 break; |
62 case AVMEDIA_TYPE_VIDEO: | 62 case AVMEDIA_TYPE_VIDEO: |
63 type_ = VIDEO; | 63 type_ = VIDEO; |
64 AVStreamToVideoDecoderConfig(stream, &video_config_, true); | 64 AVStreamToVideoDecoderConfig(stream, &video_config_, true); |
65 is_encrypted = video_config_.is_encrypted(); | 65 is_encrypted = video_config_.is_encrypted(); |
66 break; | 66 break; |
67 case AVMEDIA_TYPE_SUBTITLE: | |
68 type_ = TEXT; | |
69 break; | |
67 default: | 70 default: |
68 NOTREACHED(); | 71 NOTREACHED(); |
69 break; | 72 break; |
70 } | 73 } |
71 | 74 |
72 // Calculate the duration. | 75 // Calculate the duration. |
73 duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration); | 76 duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration); |
74 | 77 |
75 if (stream_->codec->codec_id == AV_CODEC_ID_H264) { | 78 if (stream_->codec->codec_id == AV_CODEC_ID_H264) { |
76 bitstream_converter_.reset( | 79 bitstream_converter_.reset( |
(...skipping 26 matching lines...) Expand all Loading... | |
103 NOTREACHED() << "Attempted to enqueue packet on a stopped stream"; | 106 NOTREACHED() << "Attempted to enqueue packet on a stopped stream"; |
104 return; | 107 return; |
105 } | 108 } |
106 | 109 |
107 // Convert the packet if there is a bitstream filter. | 110 // Convert the packet if there is a bitstream filter. |
108 if (packet->data && bitstream_converter_enabled_ && | 111 if (packet->data && bitstream_converter_enabled_ && |
109 !bitstream_converter_->ConvertPacket(packet.get())) { | 112 !bitstream_converter_->ConvertPacket(packet.get())) { |
110 LOG(ERROR) << "Format conversion failed."; | 113 LOG(ERROR) << "Format conversion failed."; |
111 } | 114 } |
112 | 115 |
113 // Get side data if any. For now, the only type of side_data is VP8 Alpha. We | 116 scoped_refptr<DecoderBuffer> buffer; |
114 // keep this generic so that other side_data types in the future can be | 117 |
115 // handled the same way as well. | 118 // Get side data if any. For now, the only types of side_data are VP8 Alpha, |
119 // and WebVTT id and settings. We keep this generic so that other side_data | |
120 // types in the future can be handled the same way as well. | |
116 av_packet_split_side_data(packet.get()); | 121 av_packet_split_side_data(packet.get()); |
117 int side_data_size = 0; | 122 if (type() == DemuxerStream::TEXT) { |
118 uint8* side_data = av_packet_get_side_data( | 123 int id_size = 0; |
119 packet.get(), | 124 uint8* id_data = av_packet_get_side_data( |
120 AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, | 125 packet.get(), |
121 &side_data_size); | 126 AV_PKT_DATA_WEBVTT_IDENTIFIER, |
127 &id_size); | |
122 | 128 |
123 // If a packet is returned by FFmpeg's av_parser_parse2() the packet will | 129 int settings_size = 0; |
124 // reference inner memory of FFmpeg. As such we should transfer the packet | 130 uint8* settings_data = av_packet_get_side_data( |
125 // into memory we control. | 131 packet.get(), |
126 scoped_refptr<DecoderBuffer> buffer; | 132 AV_PKT_DATA_WEBVTT_SETTINGS, |
127 if (side_data_size > 0) { | 133 &settings_size); |
134 | |
135 // The DecoderBuffer only supports a single side data item. In the case of | |
136 // a WebVTT cue, we can have potentially two side data items. In order to | |
137 // avoid disrupting DecoderBuffer any more than we need to, we copy both | |
138 // side data items onto a single one, and separate them with a marker | |
139 // (the byte value 0xFF is not part of the representation of any UTF8 | |
140 // character). | |
141 std::basic_string<uint8> side_data; | |
142 side_data.append(id_data, id_size); | |
143 side_data.append(1, 0xFF); | |
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: Consider just using the null terminator and s
Matthew Heaney (Chromium)
2013/09/13 19:51:54
I appended a NUL to each sub-string, instead of us
| |
144 side_data.append(settings_data, settings_size); | |
145 | |
128 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size, | 146 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size, |
129 side_data, side_data_size); | 147 side_data.data(), side_data.length()); |
130 } else { | 148 } else { |
131 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size); | 149 int side_data_size = 0; |
132 } | 150 uint8* side_data = av_packet_get_side_data( |
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
Why is this change needed for non-texttrack data?
Matthew Heaney (Chromium)
2013/09/13 19:51:54
I'm not quite following here. What change are you
acolwell GONE FROM CHROMIUM
2013/09/13 20:57:30
IIUC This is the non-text track code path. The ori
Matthew Heaney (Chromium)
2013/09/20 23:53:54
I checked master again -- this bit of code was alr
| |
151 packet.get(), | |
152 AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, | |
153 &side_data_size); | |
133 | 154 |
134 if ((type() == DemuxerStream::AUDIO && audio_config_.is_encrypted()) || | 155 // If a packet is returned by FFmpeg's av_parser_parse2() the packet will |
135 (type() == DemuxerStream::VIDEO && video_config_.is_encrypted())) { | 156 // reference inner memory of FFmpeg. As such we should transfer the packet |
136 scoped_ptr<DecryptConfig> config(WebMCreateDecryptConfig( | 157 // into memory we control. |
137 packet->data, packet->size, | 158 if (side_data_size > 0) { |
138 reinterpret_cast<const uint8*>(encryption_key_id_.data()), | 159 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size, |
139 encryption_key_id_.size())); | 160 side_data, side_data_size); |
140 if (!config) | 161 } else { |
141 LOG(ERROR) << "Creation of DecryptConfig failed."; | 162 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size); |
142 buffer->set_decrypt_config(config.Pass()); | 163 } |
164 | |
165 if ((type() == DemuxerStream::AUDIO && audio_config_.is_encrypted()) || | |
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: I don't think you need to move this into the
Matthew Heaney (Chromium)
2013/09/13 19:51:54
Done.
| |
166 (type() == DemuxerStream::VIDEO && video_config_.is_encrypted())) { | |
167 scoped_ptr<DecryptConfig> config(WebMCreateDecryptConfig( | |
168 packet->data, packet->size, | |
169 reinterpret_cast<const uint8*>(encryption_key_id_.data()), | |
170 encryption_key_id_.size())); | |
171 if (!config) | |
172 LOG(ERROR) << "Creation of DecryptConfig failed."; | |
173 buffer->set_decrypt_config(config.Pass()); | |
174 } | |
143 } | 175 } |
144 | 176 |
145 buffer->set_timestamp(ConvertStreamTimestamp( | 177 buffer->set_timestamp(ConvertStreamTimestamp( |
146 stream_->time_base, packet->pts)); | 178 stream_->time_base, packet->pts)); |
147 buffer->set_duration(ConvertStreamTimestamp( | 179 buffer->set_duration(ConvertStreamTimestamp( |
148 stream_->time_base, packet->duration)); | 180 stream_->time_base, packet->duration)); |
149 if (buffer->timestamp() != kNoTimestamp() && | 181 if (buffer->timestamp() != kNoTimestamp() && |
150 last_packet_timestamp_ != kNoTimestamp() && | 182 last_packet_timestamp_ != kNoTimestamp() && |
151 last_packet_timestamp_ < buffer->timestamp()) { | 183 last_packet_timestamp_ < buffer->timestamp()) { |
152 buffered_ranges_.Add(last_packet_timestamp_, buffer->timestamp()); | 184 buffered_ranges_.Add(last_packet_timestamp_, buffer->timestamp()); |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
281 return ConvertFromTimeBase(time_base, timestamp); | 313 return ConvertFromTimeBase(time_base, timestamp); |
282 } | 314 } |
283 | 315 |
284 // | 316 // |
285 // FFmpegDemuxer | 317 // FFmpegDemuxer |
286 // | 318 // |
287 FFmpegDemuxer::FFmpegDemuxer( | 319 FFmpegDemuxer::FFmpegDemuxer( |
288 const scoped_refptr<base::MessageLoopProxy>& message_loop, | 320 const scoped_refptr<base::MessageLoopProxy>& message_loop, |
289 DataSource* data_source, | 321 DataSource* data_source, |
290 const NeedKeyCB& need_key_cb, | 322 const NeedKeyCB& need_key_cb, |
323 const FFmpegAddTextTrackCB& add_text_track_cb, | |
291 const scoped_refptr<MediaLog>& media_log) | 324 const scoped_refptr<MediaLog>& media_log) |
292 : host_(NULL), | 325 : host_(NULL), |
293 message_loop_(message_loop), | 326 message_loop_(message_loop), |
294 weak_factory_(this), | 327 weak_factory_(this), |
295 blocking_thread_("FFmpegDemuxer"), | 328 blocking_thread_("FFmpegDemuxer"), |
296 pending_read_(false), | 329 pending_read_(false), |
297 pending_seek_(false), | 330 pending_seek_(false), |
298 data_source_(data_source), | 331 data_source_(data_source), |
299 media_log_(media_log), | 332 media_log_(media_log), |
300 bitrate_(0), | 333 bitrate_(0), |
301 start_time_(kNoTimestamp()), | 334 start_time_(kNoTimestamp()), |
302 audio_disabled_(false), | 335 audio_disabled_(false), |
303 duration_known_(false), | 336 duration_known_(false), |
304 url_protocol_(data_source, BindToLoop(message_loop_, base::Bind( | 337 url_protocol_(data_source, BindToLoop(message_loop_, base::Bind( |
305 &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))), | 338 &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))), |
306 need_key_cb_(need_key_cb) { | 339 need_key_cb_(need_key_cb), |
340 add_text_track_cb_(add_text_track_cb) { | |
307 DCHECK(message_loop_.get()); | 341 DCHECK(message_loop_.get()); |
308 DCHECK(data_source_); | 342 DCHECK(data_source_); |
309 } | 343 } |
310 | 344 |
311 FFmpegDemuxer::~FFmpegDemuxer() {} | 345 FFmpegDemuxer::~FFmpegDemuxer() {} |
312 | 346 |
313 void FFmpegDemuxer::Stop(const base::Closure& callback) { | 347 void FFmpegDemuxer::Stop(const base::Closure& callback) { |
314 DCHECK(message_loop_->BelongsToCurrentThread()); | 348 DCHECK(message_loop_->BelongsToCurrentThread()); |
315 url_protocol_.Abort(); | 349 url_protocol_.Abort(); |
316 data_source_->Stop(BindToCurrentLoop(base::Bind( | 350 data_source_->Stop(BindToCurrentLoop(base::Bind( |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
386 FROM_HERE, | 420 FROM_HERE, |
387 base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())), | 421 base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())), |
388 base::Bind(&FFmpegDemuxer::OnOpenContextDone, weak_this_, status_cb)); | 422 base::Bind(&FFmpegDemuxer::OnOpenContextDone, weak_this_, status_cb)); |
389 } | 423 } |
390 | 424 |
391 DemuxerStream* FFmpegDemuxer::GetStream(DemuxerStream::Type type) { | 425 DemuxerStream* FFmpegDemuxer::GetStream(DemuxerStream::Type type) { |
392 DCHECK(message_loop_->BelongsToCurrentThread()); | 426 DCHECK(message_loop_->BelongsToCurrentThread()); |
393 return GetFFmpegStream(type); | 427 return GetFFmpegStream(type); |
394 } | 428 } |
395 | 429 |
430 int FFmpegDemuxer::GetStreamCount() const { | |
431 return streams_.size(); | |
432 } | |
433 | |
434 DemuxerStream* FFmpegDemuxer::GetStreamByIndex(int idx) { | |
435 if (idx < 0 || StreamVector::size_type(idx) >= streams_.size()) { | |
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: These should be DCHECKs.
Matthew Heaney (Chromium)
2013/09/13 19:51:54
Done.
| |
436 return NULL; | |
437 } else { | |
438 return streams_[idx]; | |
439 } | |
440 } | |
441 | |
396 FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream( | 442 FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream( |
397 DemuxerStream::Type type) const { | 443 DemuxerStream::Type type) const { |
398 StreamVector::const_iterator iter; | 444 StreamVector::const_iterator iter; |
399 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { | 445 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
400 if (*iter && (*iter)->type() == type) { | 446 if (*iter && (*iter)->type() == type) { |
401 return *iter; | 447 return *iter; |
402 } | 448 } |
403 } | 449 } |
404 return NULL; | 450 return NULL; |
405 } | 451 } |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
487 // partial playback. At least one audio or video stream must be playable. | 533 // partial playback. At least one audio or video stream must be playable. |
488 AVFormatContext* format_context = glue_->format_context(); | 534 AVFormatContext* format_context = glue_->format_context(); |
489 streams_.resize(format_context->nb_streams); | 535 streams_.resize(format_context->nb_streams); |
490 | 536 |
491 AVStream* audio_stream = NULL; | 537 AVStream* audio_stream = NULL; |
492 AudioDecoderConfig audio_config; | 538 AudioDecoderConfig audio_config; |
493 | 539 |
494 AVStream* video_stream = NULL; | 540 AVStream* video_stream = NULL; |
495 VideoDecoderConfig video_config; | 541 VideoDecoderConfig video_config; |
496 | 542 |
543 std::vector<AVStream*> text_streams(format_context->nb_streams); | |
544 | |
497 base::TimeDelta max_duration; | 545 base::TimeDelta max_duration; |
498 for (size_t i = 0; i < format_context->nb_streams; ++i) { | 546 for (size_t i = 0; i < format_context->nb_streams; ++i) { |
499 AVStream* stream = format_context->streams[i]; | 547 AVStream* stream = format_context->streams[i]; |
500 AVCodecContext* codec_context = stream->codec; | 548 AVCodecContext* codec_context = stream->codec; |
501 AVMediaType codec_type = codec_context->codec_type; | 549 AVMediaType codec_type = codec_context->codec_type; |
502 | 550 |
503 if (codec_type == AVMEDIA_TYPE_AUDIO) { | 551 if (codec_type == AVMEDIA_TYPE_AUDIO) { |
504 if (audio_stream) | 552 if (audio_stream) |
505 continue; | 553 continue; |
506 | 554 |
(...skipping 13 matching lines...) Expand all Loading... | |
520 // Log the codec detected, whether it is supported or not. | 568 // Log the codec detected, whether it is supported or not. |
521 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodec", | 569 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodec", |
522 codec_context->codec_id); | 570 codec_context->codec_id); |
523 // Ensure the codec is supported. IsValidConfig() also checks that the | 571 // Ensure the codec is supported. IsValidConfig() also checks that the |
524 // frame size and visible size are valid. | 572 // frame size and visible size are valid. |
525 AVStreamToVideoDecoderConfig(stream, &video_config, false); | 573 AVStreamToVideoDecoderConfig(stream, &video_config, false); |
526 | 574 |
527 if (!video_config.IsValidConfig()) | 575 if (!video_config.IsValidConfig()) |
528 continue; | 576 continue; |
529 video_stream = stream; | 577 video_stream = stream; |
578 } else if (codec_type == AVMEDIA_TYPE_SUBTITLE) { | |
579 if (codec_context->codec_id != AV_CODEC_ID_WEBVTT || | |
580 add_text_track_cb_.is_null()) { | |
581 continue; | |
582 } | |
583 | |
584 text_streams[i] = stream; | |
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: Any harm in doing the work below, right here?
Matthew Heaney (Chromium)
2013/09/13 19:51:54
We iterate through all the streams first, in order
acolwell GONE FROM CHROMIUM
2013/09/13 20:57:30
I don't think this should be a problem. Reads on t
Matthew Heaney (Chromium)
2013/09/20 23:53:54
I ended up moving that loop into its own subprogra
| |
530 } else { | 585 } else { |
531 continue; | 586 continue; |
532 } | 587 } |
533 | 588 |
534 streams_[i] = new FFmpegDemuxerStream(this, stream); | 589 streams_[i] = new FFmpegDemuxerStream(this, stream); |
535 max_duration = std::max(max_duration, streams_[i]->duration()); | 590 max_duration = std::max(max_duration, streams_[i]->duration()); |
536 | 591 |
537 if (stream->first_dts != static_cast<int64_t>(AV_NOPTS_VALUE)) { | 592 if (stream->first_dts != static_cast<int64_t>(AV_NOPTS_VALUE)) { |
538 const base::TimeDelta first_dts = ConvertFromTimeBase( | 593 const base::TimeDelta first_dts = ConvertFromTimeBase( |
539 stream->time_base, stream->first_dts); | 594 stream->time_base, stream->first_dts); |
540 if (start_time_ == kNoTimestamp() || first_dts < start_time_) | 595 if (start_time_ == kNoTimestamp() || first_dts < start_time_) |
541 start_time_ = first_dts; | 596 start_time_ = first_dts; |
542 } | 597 } |
543 } | 598 } |
544 | 599 |
545 if (!audio_stream && !video_stream) { | 600 if (!audio_stream && !video_stream) { |
546 status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS); | 601 status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS); |
547 return; | 602 return; |
548 } | 603 } |
549 | 604 |
605 for (size_t idx = 0; idx < text_streams.size(); ++idx) { | |
606 AVStream* text_stream = text_streams[idx]; | |
607 if (text_stream == NULL) | |
608 continue; | |
609 | |
610 TextKind kind; | |
611 | |
612 if (text_stream->disposition & AV_DISPOSITION_CAPTIONS) { | |
613 kind = kTextCaptions; | |
614 } else if (text_stream->disposition & AV_DISPOSITION_DESCRIPTIONS) { | |
615 kind = kTextDescriptions; | |
616 } else if (text_stream->disposition & AV_DISPOSITION_METADATA) { | |
617 kind = kTextMetadata; | |
618 } else { | |
619 kind = kTextSubtitles; | |
620 } | |
621 | |
622 AVDictionaryEntry* text_title = | |
623 av_dict_get(text_stream->metadata, "title", NULL, 0); | |
624 | |
625 std::string title; | |
626 | |
627 if (text_title != NULL && text_title->value != NULL) { | |
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: move av_dict_get() & NULL checks to a helper
Matthew Heaney (Chromium)
2013/09/13 19:51:54
Done.
| |
628 title = text_title->value; | |
629 } | |
630 | |
631 AVDictionaryEntry* text_language = | |
632 av_dict_get(text_stream->metadata, "language", NULL, 0); | |
633 | |
634 std::string language; | |
635 | |
636 if (text_language != NULL && text_language->value != NULL) { | |
637 language = text_language->value; | |
638 } | |
639 | |
640 add_text_track_cb_.Run(kind, title, language, idx); | |
641 } | |
642 | |
550 if (format_context->duration != static_cast<int64_t>(AV_NOPTS_VALUE)) { | 643 if (format_context->duration != static_cast<int64_t>(AV_NOPTS_VALUE)) { |
551 // If there is a duration value in the container use that to find the | 644 // If there is a duration value in the container use that to find the |
552 // maximum between it and the duration from A/V streams. | 645 // maximum between it and the duration from A/V streams. |
553 const AVRational av_time_base = {1, AV_TIME_BASE}; | 646 const AVRational av_time_base = {1, AV_TIME_BASE}; |
554 max_duration = | 647 max_duration = |
555 std::max(max_duration, | 648 std::max(max_duration, |
556 ConvertFromTimeBase(av_time_base, format_context->duration)); | 649 ConvertFromTimeBase(av_time_base, format_context->duration)); |
557 } else { | 650 } else { |
558 // The duration is unknown, in which case this is likely a live stream. | 651 // The duration is unknown, in which case this is likely a live stream. |
559 max_duration = kInfiniteDuration(); | 652 max_duration = kInfiniteDuration(); |
(...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
838 } | 931 } |
839 for (size_t i = 0; i < buffered.size(); ++i) | 932 for (size_t i = 0; i < buffered.size(); ++i) |
840 host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i)); | 933 host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i)); |
841 } | 934 } |
842 | 935 |
843 void FFmpegDemuxer::OnDataSourceError() { | 936 void FFmpegDemuxer::OnDataSourceError() { |
844 host_->OnDemuxerError(PIPELINE_ERROR_READ); | 937 host_->OnDemuxerError(PIPELINE_ERROR_READ); |
845 } | 938 } |
846 | 939 |
847 } // namespace media | 940 } // namespace media |
OLD | NEW |