Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(81)

Side by Side Diff: media/filters/ffmpeg_demuxer.cc

Issue 23702007: Render inband text tracks in the media pipeline (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698