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

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

Issue 2284923003: Implement support for multiple tracks in FFmpegDemuxer (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 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 <memory> 8 #include <memory>
9 #include <utility> 9 #include <utility>
10 10
(...skipping 877 matching lines...) Expand 10 before | Expand all | Expand 10 after
888 // in the process of being fulfilled by the DataSource. 888 // in the process of being fulfilled by the DataSource.
889 data_source_->Stop(); 889 data_source_->Stop();
890 url_protocol_->Abort(); 890 url_protocol_->Abort();
891 891
892 // This will block until all tasks complete. Note that after this returns it's 892 // This will block until all tasks complete. Note that after this returns it's
893 // possible for reply tasks (e.g., OnReadFrameDone()) to be queued on this 893 // possible for reply tasks (e.g., OnReadFrameDone()) to be queued on this
894 // thread. Each of the reply task methods must check whether we've stopped the 894 // thread. Each of the reply task methods must check whether we've stopped the
895 // thread and drop their results on the floor. 895 // thread and drop their results on the floor.
896 blocking_thread_.Stop(); 896 blocking_thread_.Stop();
897 897
898 StreamVector::iterator iter; 898 for (const auto& stream : streams_) {
899 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { 899 if (stream)
900 if (*iter) 900 stream->Stop();
901 (*iter)->Stop();
902 } 901 }
903 902
904 data_source_ = NULL; 903 data_source_ = NULL;
905 904
906 // Invalidate WeakPtrs on |task_runner_|, destruction may happen on another 905 // Invalidate WeakPtrs on |task_runner_|, destruction may happen on another
907 // thread. 906 // thread.
908 weak_factory_.InvalidateWeakPtrs(); 907 weak_factory_.InvalidateWeakPtrs();
909 } 908 }
910 909
911 void FFmpegDemuxer::StartWaitingForSeek(base::TimeDelta seek_time) {} 910 void FFmpegDemuxer::StartWaitingForSeek(base::TimeDelta seek_time) {}
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
973 return timeline_offset_; 972 return timeline_offset_;
974 } 973 }
975 974
976 DemuxerStream* FFmpegDemuxer::GetStream(DemuxerStream::Type type) { 975 DemuxerStream* FFmpegDemuxer::GetStream(DemuxerStream::Type type) {
977 DCHECK(task_runner_->BelongsToCurrentThread()); 976 DCHECK(task_runner_->BelongsToCurrentThread());
978 return GetFFmpegStream(type); 977 return GetFFmpegStream(type);
979 } 978 }
980 979
981 FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream( 980 FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream(
982 DemuxerStream::Type type) const { 981 DemuxerStream::Type type) const {
983 StreamVector::const_iterator iter; 982 for (const auto& it : streams_) {
DaleCurtis 2016/08/29 18:15:20 Not really an iterator any more.
servolk 2016/08/29 18:38:20 What would be a better name? 'stream'? 's'?
DaleCurtis 2016/08/29 18:49:29 Yeah, you renamed all the rest to streams :)
servolk 2016/08/29 20:25:28 Done.
984 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { 983 if (it && it->type() == type && it->enabled()) {
985 if (*iter && (*iter)->type() == type) { 984 return it.get();
986 return *iter;
987 } 985 }
988 } 986 }
989 return NULL; 987 return NULL;
990 } 988 }
991 989
992 base::TimeDelta FFmpegDemuxer::GetStartTime() const { 990 base::TimeDelta FFmpegDemuxer::GetStartTime() const {
993 return std::max(start_time_, base::TimeDelta()); 991 return std::max(start_time_, base::TimeDelta());
994 } 992 }
995 993
996 void FFmpegDemuxer::AddTextStreams() { 994 void FFmpegDemuxer::AddTextStreams() {
997 DCHECK(task_runner_->BelongsToCurrentThread()); 995 DCHECK(task_runner_->BelongsToCurrentThread());
998 996
999 for (StreamVector::size_type idx = 0; idx < streams_.size(); ++idx) { 997 for (const auto& stream : streams_) {
1000 FFmpegDemuxerStream* stream = streams_[idx]; 998 if (!stream || stream->type() != DemuxerStream::TEXT)
1001 if (stream == NULL || stream->type() != DemuxerStream::TEXT)
1002 continue; 999 continue;
1003 1000
1004 TextKind kind = stream->GetTextKind(); 1001 TextKind kind = stream->GetTextKind();
1005 std::string title = stream->GetMetadata("title"); 1002 std::string title = stream->GetMetadata("title");
1006 std::string language = stream->GetMetadata("language"); 1003 std::string language = stream->GetMetadata("language");
1007 1004
1008 // TODO: Implement "id" metadata in FFMPEG. 1005 // TODO: Implement "id" metadata in FFMPEG.
1009 // See: http://crbug.com/323183 1006 // See: http://crbug.com/323183
1010 host_->AddTextStream(stream, TextTrackConfig(kind, title, language, 1007 host_->AddTextStream(stream.get(),
1011 std::string())); 1008 TextTrackConfig(kind, title, language, std::string()));
1012 } 1009 }
1013 } 1010 }
1014 1011
1015 int64_t FFmpegDemuxer::GetMemoryUsage() const { 1012 int64_t FFmpegDemuxer::GetMemoryUsage() const {
1016 int64_t allocation_size = 0; 1013 int64_t allocation_size = 0;
1017 for (auto* stream : streams_) { 1014 for (const auto& stream : streams_) {
1018 if (stream) 1015 if (stream)
1019 allocation_size += stream->MemoryUsage(); 1016 allocation_size += stream->MemoryUsage();
1020 } 1017 }
1021 return allocation_size; 1018 return allocation_size;
1022 } 1019 }
1023 1020
1024 void FFmpegDemuxer::OnEncryptedMediaInitData( 1021 void FFmpegDemuxer::OnEncryptedMediaInitData(
1025 EmeInitDataType init_data_type, 1022 EmeInitDataType init_data_type,
1026 const std::string& encryption_key_id) { 1023 const std::string& encryption_key_id) {
1027 std::vector<uint8_t> key_id_local(encryption_key_id.begin(), 1024 std::vector<uint8_t> key_id_local(encryption_key_id.begin(),
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after
1157 const base::TimeDelta packet_pts = 1154 const base::TimeDelta packet_pts =
1158 ConvertFromTimeBase(stream->time_base, packet_buffer->pkt.pts); 1155 ConvertFromTimeBase(stream->time_base, packet_buffer->pkt.pts);
1159 if (packet_pts < start_time_estimates[stream->index]) 1156 if (packet_pts < start_time_estimates[stream->index])
1160 start_time_estimates[stream->index] = packet_pts; 1157 start_time_estimates[stream->index] = packet_pts;
1161 } 1158 }
1162 packet_buffer = packet_buffer->next; 1159 packet_buffer = packet_buffer->next;
1163 } 1160 }
1164 } 1161 }
1165 1162
1166 std::unique_ptr<MediaTracks> media_tracks(new MediaTracks()); 1163 std::unique_ptr<MediaTracks> media_tracks(new MediaTracks());
1167 AVStream* audio_stream = NULL;
1168 AudioDecoderConfig audio_config;
1169 AVStream* video_stream = NULL;
1170 VideoDecoderConfig video_config;
1171 1164
1172 DCHECK(track_id_to_demux_stream_map_.empty()); 1165 DCHECK(track_id_to_demux_stream_map_.empty());
1173 1166
1174 // If available, |start_time_| will be set to the lowest stream start time. 1167 // If available, |start_time_| will be set to the lowest stream start time.
1175 start_time_ = kInfiniteDuration; 1168 start_time_ = kInfiniteDuration;
1176 1169
1177 base::TimeDelta max_duration; 1170 base::TimeDelta max_duration;
1178 int detected_audio_track_count = 0; 1171 int detected_audio_track_count = 0;
1179 int detected_video_track_count = 0; 1172 int detected_video_track_count = 0;
1180 int detected_text_track_count = 0; 1173 int detected_text_track_count = 0;
1181 for (size_t i = 0; i < format_context->nb_streams; ++i) { 1174 for (size_t i = 0; i < format_context->nb_streams; ++i) {
1182 AVStream* stream = format_context->streams[i]; 1175 AVStream* stream = format_context->streams[i];
1183 const AVCodecContext* codec_context = stream->codec; 1176 const AVCodecContext* codec_context = stream->codec;
1184 const AVMediaType codec_type = codec_context->codec_type; 1177 const AVMediaType codec_type = codec_context->codec_type;
1185 1178
1186 if (codec_type == AVMEDIA_TYPE_AUDIO) { 1179 if (codec_type == AVMEDIA_TYPE_AUDIO) {
1187 // Log the codec detected, whether it is supported or not, and whether or 1180 // Log the codec detected, whether it is supported or not, and whether or
1188 // not we have already detected a supported codec in another stream. 1181 // not we have already detected a supported codec in another stream.
1189 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedAudioCodecHash", 1182 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedAudioCodecHash",
1190 HashCodecName(GetCodecName(codec_context))); 1183 HashCodecName(GetCodecName(codec_context)));
1191 detected_audio_track_count++; 1184 detected_audio_track_count++;
1192
1193 if (audio_stream) {
1194 MEDIA_LOG(INFO, media_log_) << GetDisplayName()
1195 << ": skipping extra audio track";
1196 continue;
1197 }
1198 } else if (codec_type == AVMEDIA_TYPE_VIDEO) { 1185 } else if (codec_type == AVMEDIA_TYPE_VIDEO) {
1199 // Log the codec detected, whether it is supported or not, and whether or 1186 // Log the codec detected, whether it is supported or not, and whether or
1200 // not we have already detected a supported codec in another stream. 1187 // not we have already detected a supported codec in another stream.
1201 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodecHash", 1188 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodecHash",
1202 HashCodecName(GetCodecName(codec_context))); 1189 HashCodecName(GetCodecName(codec_context)));
1203 detected_video_track_count++; 1190 detected_video_track_count++;
1204 1191
1205 if (video_stream) {
1206 MEDIA_LOG(INFO, media_log_) << GetDisplayName()
1207 << ": skipping extra video track";
1208 continue;
1209 }
1210
1211 #if BUILDFLAG(ENABLE_HEVC_DEMUXING) 1192 #if BUILDFLAG(ENABLE_HEVC_DEMUXING)
1212 if (stream->codec->codec_id == AV_CODEC_ID_HEVC) { 1193 if (stream->codec->codec_id == AV_CODEC_ID_HEVC) {
1213 // If ffmpeg is built without HEVC parser/decoder support, it will be 1194 // If ffmpeg is built without HEVC parser/decoder support, it will be
1214 // able to demux HEVC based solely on container-provided information, 1195 // able to demux HEVC based solely on container-provided information,
1215 // but unable to get some of the parameters without parsing the stream 1196 // but unable to get some of the parameters without parsing the stream
1216 // (e.g. coded size needs to be read from SPS, pixel format is typically 1197 // (e.g. coded size needs to be read from SPS, pixel format is typically
1217 // deduced from decoder config in hvcC box). These are not really needed 1198 // deduced from decoder config in hvcC box). These are not really needed
1218 // when using external decoder (e.g. hardware decoder), so override them 1199 // when using external decoder (e.g. hardware decoder), so override them
1219 // here, to make sure this translates into a valid VideoDecoderConfig. 1200 // here, to make sure this translates into a valid VideoDecoderConfig.
1220 if (stream->codec->coded_width == 0 && 1201 if (stream->codec->coded_width == 0 &&
(...skipping 16 matching lines...) Expand all
1237 } else { 1218 } else {
1238 continue; 1219 continue;
1239 } 1220 }
1240 1221
1241 // Attempt to create a FFmpegDemuxerStream from the AVStream. This will 1222 // Attempt to create a FFmpegDemuxerStream from the AVStream. This will
1242 // return nullptr if the AVStream is invalid. Validity checks will verify 1223 // return nullptr if the AVStream is invalid. Validity checks will verify
1243 // things like: codec, channel layout, sample/pixel format, etc... 1224 // things like: codec, channel layout, sample/pixel format, etc...
1244 std::unique_ptr<FFmpegDemuxerStream> demuxer_stream = 1225 std::unique_ptr<FFmpegDemuxerStream> demuxer_stream =
1245 FFmpegDemuxerStream::Create(this, stream, media_log_); 1226 FFmpegDemuxerStream::Create(this, stream, media_log_);
1246 if (demuxer_stream.get()) { 1227 if (demuxer_stream.get()) {
1247 streams_[i] = demuxer_stream.release(); 1228 streams_[i] = std::move(demuxer_stream);
1248 } else { 1229 } else {
1249 if (codec_type == AVMEDIA_TYPE_AUDIO) { 1230 if (codec_type == AVMEDIA_TYPE_AUDIO) {
1250 MEDIA_LOG(INFO, media_log_) 1231 MEDIA_LOG(INFO, media_log_)
1251 << GetDisplayName() 1232 << GetDisplayName()
1252 << ": skipping invalid or unsupported audio track"; 1233 << ": skipping invalid or unsupported audio track";
1253 } else if (codec_type == AVMEDIA_TYPE_VIDEO) { 1234 } else if (codec_type == AVMEDIA_TYPE_VIDEO) {
1254 MEDIA_LOG(INFO, media_log_) 1235 MEDIA_LOG(INFO, media_log_)
1255 << GetDisplayName() 1236 << GetDisplayName()
1256 << ": skipping invalid or unsupported video track"; 1237 << ": skipping invalid or unsupported video track";
1257 } 1238 }
(...skipping 13 matching lines...) Expand all
1271 // Need to fix that and use it as track id. crbug.com/323183 1252 // Need to fix that and use it as track id. crbug.com/323183
1272 track_id = 1253 track_id =
1273 static_cast<StreamParser::TrackId>(media_tracks->tracks().size() + 1); 1254 static_cast<StreamParser::TrackId>(media_tracks->tracks().size() + 1);
1274 track_label = streams_[i]->GetMetadata("title"); 1255 track_label = streams_[i]->GetMetadata("title");
1275 } 1256 }
1276 1257
1277 // Note when we find our audio/video stream (we only want one of each) and 1258 // Note when we find our audio/video stream (we only want one of each) and
1278 // record src= playback UMA stats for the stream's decoder config. 1259 // record src= playback UMA stats for the stream's decoder config.
1279 MediaTrack* media_track = nullptr; 1260 MediaTrack* media_track = nullptr;
1280 if (codec_type == AVMEDIA_TYPE_AUDIO) { 1261 if (codec_type == AVMEDIA_TYPE_AUDIO) {
1281 CHECK(!audio_stream); 1262 AudioDecoderConfig audio_config = streams_[i]->audio_decoder_config();
1282 audio_stream = stream;
1283 audio_config = streams_[i]->audio_decoder_config();
1284 RecordAudioCodecStats(audio_config); 1263 RecordAudioCodecStats(audio_config);
1285 1264
1286 media_track = media_tracks->AddAudioTrack(audio_config, track_id, "main", 1265 media_track = media_tracks->AddAudioTrack(audio_config, track_id, "main",
1287 track_label, track_language); 1266 track_label, track_language);
1288 media_track->set_id(base::UintToString(track_id)); 1267 media_track->set_id(base::UintToString(track_id));
1289 DCHECK(track_id_to_demux_stream_map_.find(media_track->id()) == 1268 DCHECK(track_id_to_demux_stream_map_.find(media_track->id()) ==
1290 track_id_to_demux_stream_map_.end()); 1269 track_id_to_demux_stream_map_.end());
1291 track_id_to_demux_stream_map_[media_track->id()] = streams_[i]; 1270 track_id_to_demux_stream_map_[media_track->id()] = streams_[i].get();
1292 } else if (codec_type == AVMEDIA_TYPE_VIDEO) { 1271 } else if (codec_type == AVMEDIA_TYPE_VIDEO) {
1293 CHECK(!video_stream); 1272 VideoDecoderConfig video_config = streams_[i]->video_decoder_config();
1294 video_stream = stream;
1295 video_config = streams_[i]->video_decoder_config();
1296 1273
1297 RecordVideoCodecStats(video_config, stream->codec->color_range, 1274 RecordVideoCodecStats(video_config, stream->codec->color_range,
1298 media_log_.get()); 1275 media_log_.get());
1299 1276
1300 media_track = media_tracks->AddVideoTrack(video_config, track_id, "main", 1277 media_track = media_tracks->AddVideoTrack(video_config, track_id, "main",
1301 track_label, track_language); 1278 track_label, track_language);
1302 media_track->set_id(base::UintToString(track_id)); 1279 media_track->set_id(base::UintToString(track_id));
1303 DCHECK(track_id_to_demux_stream_map_.find(media_track->id()) == 1280 DCHECK(track_id_to_demux_stream_map_.find(media_track->id()) ==
1304 track_id_to_demux_stream_map_.end()); 1281 track_id_to_demux_stream_map_.end());
1305 track_id_to_demux_stream_map_[media_track->id()] = streams_[i]; 1282 track_id_to_demux_stream_map_[media_track->id()] = streams_[i].get();
1306 } 1283 }
1307 1284
1308 max_duration = std::max(max_duration, streams_[i]->duration()); 1285 max_duration = std::max(max_duration, streams_[i]->duration());
1309 1286
1310 const base::TimeDelta start_time = 1287 const base::TimeDelta start_time =
1311 ExtractStartTime(stream, start_time_estimates[i]); 1288 ExtractStartTime(stream, start_time_estimates[i]);
1312 const bool has_start_time = start_time != kNoTimestamp; 1289 const bool has_start_time = start_time != kNoTimestamp;
1313 1290
1314 if (!has_start_time) 1291 if (!has_start_time)
1315 continue; 1292 continue;
1316 1293
1317 streams_[i]->set_start_time(start_time); 1294 streams_[i]->set_start_time(start_time);
1318 if (start_time < start_time_) { 1295 if (start_time < start_time_) {
1319 start_time_ = start_time; 1296 start_time_ = start_time;
1320 } 1297 }
1321 } 1298 }
1322 1299
1323 RecordDetectedTrackTypeStats(detected_audio_track_count, 1300 RecordDetectedTrackTypeStats(detected_audio_track_count,
1324 detected_video_track_count, 1301 detected_video_track_count,
1325 detected_text_track_count); 1302 detected_text_track_count);
1326 1303
1327 if (!audio_stream && !video_stream) { 1304 if (media_tracks->tracks().size() == 0) {
DaleCurtis 2016/08/29 18:15:20 .empty?
servolk 2016/08/29 18:38:20 Done.
1328 MEDIA_LOG(ERROR, media_log_) << GetDisplayName() 1305 MEDIA_LOG(ERROR, media_log_) << GetDisplayName()
1329 << ": no supported streams"; 1306 << ": no supported streams";
1330 status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS); 1307 status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
1331 return; 1308 return;
1332 } 1309 }
1333 1310
1334 if (text_enabled_) 1311 if (text_enabled_)
1335 AddTextStreams(); 1312 AddTextStreams();
1336 1313
1337 if (format_context->duration != static_cast<int64_t>(AV_NOPTS_VALUE)) { 1314 if (format_context->duration != static_cast<int64_t>(AV_NOPTS_VALUE)) {
(...skipping 17 matching lines...) Expand all
1355 // not be exposed to negative timestamps. Which means we need to rebase these 1332 // not be exposed to negative timestamps. Which means we need to rebase these
1356 // negative timestamps and mark them for discard post decoding. 1333 // negative timestamps and mark them for discard post decoding.
1357 // 1334 //
1358 // Post-decode frame dropping for packets with negative timestamps is outlined 1335 // Post-decode frame dropping for packets with negative timestamps is outlined
1359 // in section A.2 in the Ogg Vorbis spec: 1336 // in section A.2 in the Ogg Vorbis spec:
1360 // http://xiph.org/vorbis/doc/Vorbis_I_spec.html 1337 // http://xiph.org/vorbis/doc/Vorbis_I_spec.html
1361 // 1338 //
1362 // FFmpeg's use of negative timestamps for opus pre-skip is nonstandard, but 1339 // FFmpeg's use of negative timestamps for opus pre-skip is nonstandard, but
1363 // for more information on pre-skip see section 4.2 of the Ogg Opus spec: 1340 // for more information on pre-skip see section 4.2 of the Ogg Opus spec:
1364 // https://tools.ietf.org/html/draft-ietf-codec-oggopus-08#section-4.2 1341 // https://tools.ietf.org/html/draft-ietf-codec-oggopus-08#section-4.2
1365 if (audio_stream && (audio_stream->codec->codec_id == AV_CODEC_ID_OPUS || 1342 for (const auto& stream : streams_) {
DaleCurtis 2016/08/29 18:15:20 The behavior here is different. Previously it was
servolk 2016/08/29 18:38:21 It think it might matter. If we do have a stream t
1366 (strcmp(format_context->iformat->name, "ogg") == 0 && 1343 if (!stream || stream->type() != DemuxerStream::AUDIO)
1367 audio_stream->codec->codec_id == AV_CODEC_ID_VORBIS))) { 1344 continue;
1368 for (size_t i = 0; i < streams_.size(); ++i) { 1345 const AVStream* audio_stream = stream->av_stream();
1369 if (!streams_[i]) 1346 DCHECK(audio_stream);
1370 continue; 1347 if (audio_stream->codec->codec_id == AV_CODEC_ID_OPUS ||
1371 streams_[i]->enable_negative_timestamp_fixups(); 1348 (strcmp(format_context->iformat->name, "ogg") == 0 &&
DaleCurtis 2016/08/29 18:15:21 This can be moved outside the loop.
servolk 2016/08/29 18:38:21 Are you talking about strcmp? But it's bound to th
DaleCurtis 2016/08/29 18:49:29 Ah, sorry I forgot this isn't explicitly for ogg a
servolk 2016/08/29 20:25:28 Acknowledged.
1349 audio_stream->codec->codec_id == AV_CODEC_ID_VORBIS)) {
1350 for (size_t i = 0; i < streams_.size(); ++i) {
1351 if (!streams_[i])
1352 continue;
1353 streams_[i]->enable_negative_timestamp_fixups();
1372 1354
1373 // Fixup the seeking information to avoid selecting the audio stream 1355 // Fixup the seeking information to avoid selecting the audio stream
1374 // simply because it has a lower starting time. 1356 // simply because it has a lower starting time.
1375 if (streams_[i]->av_stream() == audio_stream && 1357 if (streams_[i]->av_stream() == audio_stream &&
1376 streams_[i]->start_time() < base::TimeDelta()) { 1358 streams_[i]->start_time() < base::TimeDelta()) {
1377 streams_[i]->set_start_time(base::TimeDelta()); 1359 streams_[i]->set_start_time(base::TimeDelta());
1360 }
1378 } 1361 }
1379 } 1362 }
1380 } 1363 }
1381 1364
1382 // If no start time could be determined, default to zero. 1365 // If no start time could be determined, default to zero.
1383 if (start_time_ == kInfiniteDuration) { 1366 if (start_time_ == kInfiniteDuration) {
1384 start_time_ = base::TimeDelta(); 1367 start_time_ = base::TimeDelta();
1385 } 1368 }
1386 1369
1387 // MPEG-4 B-frames cause grief for a simple container like AVI. Enable PTS 1370 // MPEG-4 B-frames cause grief for a simple container like AVI. Enable PTS
(...skipping 22 matching lines...) Expand all
1410 // initializing. 1393 // initializing.
1411 host_->SetDuration(max_duration); 1394 host_->SetDuration(max_duration);
1412 duration_known_ = (max_duration != kInfiniteDuration); 1395 duration_known_ = (max_duration != kInfiniteDuration);
1413 1396
1414 int64_t filesize_in_bytes = 0; 1397 int64_t filesize_in_bytes = 0;
1415 url_protocol_->GetSize(&filesize_in_bytes); 1398 url_protocol_->GetSize(&filesize_in_bytes);
1416 bitrate_ = CalculateBitrate(format_context, max_duration, filesize_in_bytes); 1399 bitrate_ = CalculateBitrate(format_context, max_duration, filesize_in_bytes);
1417 if (bitrate_ > 0) 1400 if (bitrate_ > 0)
1418 data_source_->SetBitrate(bitrate_); 1401 data_source_->SetBitrate(bitrate_);
1419 1402
1403 LogMetadata(format_context, max_duration);
1404 media_tracks_updated_cb_.Run(std::move(media_tracks));
1405
1406 status_cb.Run(PIPELINE_OK);
1407 }
1408
1409 void FFmpegDemuxer::LogMetadata(AVFormatContext* avctx,
1410 base::TimeDelta max_duration) {
1420 // Use a single MediaLogEvent to batch all parameter updates at once; this 1411 // Use a single MediaLogEvent to batch all parameter updates at once; this
1421 // prevents throttling of events due to the large number of updates here. 1412 // prevents throttling of events due to the large number of updates here.
1422 std::unique_ptr<MediaLogEvent> metadata_event = 1413 std::unique_ptr<MediaLogEvent> metadata_event =
1423 media_log_->CreateEvent(MediaLogEvent::PROPERTY_CHANGE); 1414 media_log_->CreateEvent(MediaLogEvent::PROPERTY_CHANGE);
1424 1415
1425 // Audio logging. 1416 DCHECK_EQ(avctx->nb_streams, streams_.size());
1426 metadata_event->params.SetBoolean("found_audio_stream", !!audio_stream); 1417 auto& params = metadata_event->params;
1427 if (audio_stream) { 1418 int audio_track_count = 0;
1428 const AVCodecContext* audio_codec = audio_stream->codec; 1419 int video_track_count = 0;
1429 metadata_event->params.SetString("audio_codec_name", 1420 for (size_t i = 0; i < streams_.size(); ++i) {
DaleCurtis 2016/08/29 18:15:20 I think some of these keys might be used by MediaI
servolk 2016/08/29 18:38:21 I have found only one place where these params see
DaleCurtis 2016/08/29 18:49:29 It's still necessary, it allows us to break out pi
servolk 2016/08/29 20:25:28 IIUC codecs will never change within tracks, but i
DaleCurtis 2016/08/29 20:29:07 Yeah, that's definitely not going to work today no
servolk 2016/08/29 21:46:24 There is probably no need to change DecoderSelecto
servolk 2016/08/29 22:02:38 Also, I have just looked a bit more into how this
1430 GetCodecName(audio_codec)); 1421 FFmpegDemuxerStream* stream = streams_[i].get();
1431 metadata_event->params.SetInteger("audio_channels_count", 1422 if (!stream)
1432 audio_codec->channels); 1423 continue;
1433 metadata_event->params.SetString( 1424 if (stream->type() == DemuxerStream::AUDIO) {
1434 "audio_sample_format", 1425 ++audio_track_count;
1435 SampleFormatToString(audio_config.sample_format())); 1426 std::string suffix = "";
1436 metadata_event->params.SetInteger("audio_samples_per_second", 1427 if (audio_track_count > 1)
1437 audio_config.samples_per_second()); 1428 suffix = "_track" + base::IntToString(audio_track_count);
1429 const AVCodecContext* audio_codec = avctx->streams[i]->codec;
1430 const AudioDecoderConfig& audio_config = stream->audio_decoder_config();
1431 params.SetString("audio_codec_name" + suffix, GetCodecName(audio_codec));
1432 params.SetInteger("audio_channels_count" + suffix, audio_codec->channels);
1433 params.SetString("audio_sample_format" + suffix,
1434 SampleFormatToString(audio_config.sample_format()));
1435 params.SetInteger("audio_samples_per_second" + suffix,
1436 audio_config.samples_per_second());
1437 } else if (stream->type() == DemuxerStream::VIDEO) {
1438 ++video_track_count;
1439 std::string suffix = "";
1440 if (video_track_count > 1)
1441 suffix = "_track" + base::IntToString(video_track_count);
1442 const AVCodecContext* video_codec = avctx->streams[i]->codec;
1443 const VideoDecoderConfig& video_config = stream->video_decoder_config();
1444 params.SetString("video_codec_name" + suffix, GetCodecName(video_codec));
1445 params.SetInteger("width" + suffix, video_codec->width);
1446 params.SetInteger("height" + suffix, video_codec->height);
1447 params.SetInteger("coded_width" + suffix, video_codec->coded_width);
1448 params.SetInteger("coded_height" + suffix, video_codec->coded_height);
1449 params.SetString("time_base" + suffix,
1450 base::StringPrintf("%d/%d", video_codec->time_base.num,
1451 video_codec->time_base.den));
1452 params.SetString("video_format" + suffix,
1453 VideoPixelFormatToString(video_config.format()));
1454 params.SetBoolean("video_is_encrypted" + suffix,
1455 video_config.is_encrypted());
1456 }
1438 } 1457 }
1439 1458 params.SetBoolean("found_audio_stream", (audio_track_count > 0));
1440 // Video logging 1459 params.SetBoolean("found_video_stream", (video_track_count > 0));
1441 metadata_event->params.SetBoolean("found_video_stream", !!video_stream);
1442 if (video_stream) {
1443 const AVCodecContext* video_codec = video_stream->codec;
1444 metadata_event->params.SetString("video_codec_name",
1445 GetCodecName(video_codec));
1446 metadata_event->params.SetInteger("width", video_codec->width);
1447 metadata_event->params.SetInteger("height", video_codec->height);
1448 metadata_event->params.SetInteger("coded_width", video_codec->coded_width);
1449 metadata_event->params.SetInteger("coded_height",
1450 video_codec->coded_height);
1451 metadata_event->params.SetString(
1452 "time_base", base::StringPrintf("%d/%d", video_codec->time_base.num,
1453 video_codec->time_base.den));
1454 metadata_event->params.SetString(
1455 "video_format", VideoPixelFormatToString(video_config.format()));
1456 metadata_event->params.SetBoolean("video_is_encrypted",
1457 video_config.is_encrypted());
1458 }
1459
1460 SetTimeProperty(metadata_event.get(), "max_duration", max_duration); 1460 SetTimeProperty(metadata_event.get(), "max_duration", max_duration);
1461 SetTimeProperty(metadata_event.get(), "start_time", start_time_); 1461 SetTimeProperty(metadata_event.get(), "start_time", start_time_);
1462 metadata_event->params.SetInteger("bitrate", bitrate_); 1462 metadata_event->params.SetInteger("bitrate", bitrate_);
1463 media_log_->AddEvent(std::move(metadata_event)); 1463 media_log_->AddEvent(std::move(metadata_event));
1464
1465 media_tracks_updated_cb_.Run(std::move(media_tracks));
1466
1467 status_cb.Run(PIPELINE_OK);
1468 } 1464 }
1469 1465
1470 FFmpegDemuxerStream* FFmpegDemuxer::FindPreferredStreamForSeeking( 1466 FFmpegDemuxerStream* FFmpegDemuxer::FindPreferredStreamForSeeking(
1471 base::TimeDelta seek_time) { 1467 base::TimeDelta seek_time) {
1472 // If we have a selected/enabled video stream and its start time is lower 1468 // If we have a selected/enabled video stream and its start time is lower
1473 // than the |seek_time| or unknown, then always prefer it for seeking. 1469 // than the |seek_time| or unknown, then always prefer it for seeking.
1474 FFmpegDemuxerStream* video_stream = nullptr; 1470 FFmpegDemuxerStream* video_stream = nullptr;
1475 for (const auto& stream : streams_) { 1471 for (const auto& stream : streams_) {
1476 if (stream && stream->type() == DemuxerStream::VIDEO && stream->enabled()) { 1472 if (stream && stream->type() == DemuxerStream::VIDEO && stream->enabled()) {
1477 video_stream = stream; 1473 video_stream = stream.get();
1478 if (video_stream->start_time() == kNoTimestamp || 1474 if (video_stream->start_time() == kNoTimestamp ||
1479 video_stream->start_time() <= seek_time) { 1475 video_stream->start_time() <= seek_time) {
1480 return stream; 1476 return video_stream;
1481 } 1477 }
1482 break; 1478 break;
1483 } 1479 }
1484 } 1480 }
1485 1481
1486 // If video stream is not present or |seek_time| is lower than the video start 1482 // If video stream is not present or |seek_time| is lower than the video start
1487 // time, then try to find an enabled stream with the lowest start time. 1483 // time, then try to find an enabled stream with the lowest start time.
1488 FFmpegDemuxerStream* lowest_start_time_stream = nullptr; 1484 FFmpegDemuxerStream* lowest_start_time_stream = nullptr;
1489 for (const auto& stream : streams_) { 1485 for (const auto& stream : streams_) {
1490 if (!stream || !stream->enabled() || stream->start_time() == kNoTimestamp) 1486 if (!stream || !stream->enabled() || stream->start_time() == kNoTimestamp)
1491 continue; 1487 continue;
1492 if (!lowest_start_time_stream || 1488 if (!lowest_start_time_stream ||
1493 stream->start_time() < lowest_start_time_stream->start_time()) { 1489 stream->start_time() < lowest_start_time_stream->start_time()) {
1494 lowest_start_time_stream = stream; 1490 lowest_start_time_stream = stream.get();
1495 } 1491 }
1496 } 1492 }
1497 // If we found a stream with start time lower than |seek_time|, then use it. 1493 // If we found a stream with start time lower than |seek_time|, then use it.
1498 if (lowest_start_time_stream && 1494 if (lowest_start_time_stream &&
1499 lowest_start_time_stream->start_time() <= seek_time) { 1495 lowest_start_time_stream->start_time() <= seek_time) {
1500 return lowest_start_time_stream; 1496 return lowest_start_time_stream;
1501 } 1497 }
1502 1498
1503 // If we couldn't find any streams with the start time lower than |seek_time| 1499 // If we couldn't find any streams with the start time lower than |seek_time|
1504 // then use either video (if one exists) or any audio stream. 1500 // then use either video (if one exists) or any audio stream.
(...skipping 13 matching lines...) Expand all
1518 } 1514 }
1519 1515
1520 if (result < 0) { 1516 if (result < 0) {
1521 // Use VLOG(1) instead of NOTIMPLEMENTED() to prevent the message being 1517 // Use VLOG(1) instead of NOTIMPLEMENTED() to prevent the message being
1522 // captured from stdout and contaminates testing. 1518 // captured from stdout and contaminates testing.
1523 // TODO(scherkus): Implement this properly and signal error (BUG=23447). 1519 // TODO(scherkus): Implement this properly and signal error (BUG=23447).
1524 VLOG(1) << "Not implemented"; 1520 VLOG(1) << "Not implemented";
1525 } 1521 }
1526 1522
1527 // Tell streams to flush buffers due to seeking. 1523 // Tell streams to flush buffers due to seeking.
1528 StreamVector::iterator iter; 1524 for (const auto& stream : streams_) {
1529 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { 1525 if (stream)
1530 if (*iter) 1526 stream->FlushBuffers();
1531 (*iter)->FlushBuffers();
1532 } 1527 }
1533 1528
1534 // Resume reading until capacity. 1529 // Resume reading until capacity.
1535 ReadFrameIfNeeded(); 1530 ReadFrameIfNeeded();
1536 1531
1537 // Notify we're finished seeking. 1532 // Notify we're finished seeking.
1538 cb.Run(PIPELINE_OK); 1533 cb.Run(PIPELINE_OK);
1539 } 1534 }
1540 1535
1541 void FFmpegDemuxer::OnEnabledAudioTracksChanged( 1536 void FFmpegDemuxer::OnEnabledAudioTracksChanged(
1542 const std::vector<MediaTrack::Id>& track_ids, 1537 const std::vector<MediaTrack::Id>& track_ids,
1543 base::TimeDelta currTime) { 1538 base::TimeDelta currTime) {
1544 DCHECK(task_runner_->BelongsToCurrentThread()); 1539 DCHECK(task_runner_->BelongsToCurrentThread());
1545 bool enabled = false; 1540
1546 DemuxerStream* audio_stream = GetStream(DemuxerStream::AUDIO); 1541 std::set<DemuxerStream*> enabled_streams;
1547 CHECK(audio_stream); 1542 for (const auto& id : track_ids) {
1548 if (track_ids.size() > 0) { 1543 DemuxerStream* stream = track_id_to_demux_stream_map_[id];
1549 DCHECK(track_id_to_demux_stream_map_[track_ids[0]] == audio_stream); 1544 DCHECK(stream);
1550 enabled = true; 1545 DCHECK_EQ(DemuxerStream::AUDIO, stream->type());
1546 enabled_streams.insert(stream);
DaleCurtis 2016/08/29 18:15:20 Is there a more efficient insert here? This can ca
DaleCurtis 2016/08/29 18:15:20 Is there a more efficient insert here? This can ca
servolk 2016/08/29 18:38:20 I think we don't need to be concerned about perfor
1551 } 1547 }
1552 DVLOG(1) << __func__ << ": " << (enabled ? "enabling" : "disabling") 1548
1553 << " audio stream"; 1549 // First disable all streams that need to be disabled and then enable streams
1554 audio_stream->set_enabled(enabled, currTime); 1550 // that are enabled.
1551 for (const auto& stream : streams_) {
1552 if (stream->type() == DemuxerStream::AUDIO &&
1553 enabled_streams.find(stream.get()) == enabled_streams.end()) {
1554 DVLOG(1) << __func__ << ": disabling stream " << stream.get();
1555 stream->set_enabled(false, currTime);
1556 }
1557 }
1558 for (const auto& stream : enabled_streams) {
1559 DVLOG(1) << __func__ << ": enabling stream " << stream;
1560 stream->set_enabled(true, currTime);
1561 }
1555 } 1562 }
1556 1563
1557 void FFmpegDemuxer::OnSelectedVideoTrackChanged( 1564 void FFmpegDemuxer::OnSelectedVideoTrackChanged(
1558 const std::vector<MediaTrack::Id>& track_ids, 1565 const std::vector<MediaTrack::Id>& track_ids,
1559 base::TimeDelta currTime) { 1566 base::TimeDelta currTime) {
1560 DCHECK(task_runner_->BelongsToCurrentThread()); 1567 DCHECK(task_runner_->BelongsToCurrentThread());
1561 bool enabled = false; 1568 DCHECK_LE(track_ids.size(), 1u);
1562 DemuxerStream* video_stream = GetStream(DemuxerStream::VIDEO); 1569
1563 CHECK(video_stream); 1570 DemuxerStream* selected_stream = nullptr;
1564 if (track_ids.size() > 0) { 1571 if (!track_ids.empty()) {
1565 DCHECK(track_id_to_demux_stream_map_[track_ids[0]] == video_stream); 1572 selected_stream = track_id_to_demux_stream_map_[track_ids[0]];
1566 enabled = true; 1573 DCHECK(selected_stream);
1574 DCHECK_EQ(DemuxerStream::VIDEO, selected_stream->type());
1567 } 1575 }
1568 DVLOG(1) << __func__ << ": " << (enabled ? "enabling" : "disabling") 1576
1569 << " video stream"; 1577 // First disable all streams that need to be disabled and then enable the
1570 video_stream->set_enabled(enabled, currTime); 1578 // stream that needs to be enabled (if any).
1579 for (const auto& stream : streams_) {
1580 if (stream->type() == DemuxerStream::VIDEO &&
1581 stream.get() != selected_stream) {
1582 DVLOG(1) << __func__ << ": disabling stream " << stream.get();
1583 stream->set_enabled(false, currTime);
1584 }
1585 }
1586 if (selected_stream) {
1587 DVLOG(1) << __func__ << ": enabling stream " << selected_stream;
1588 selected_stream->set_enabled(true, currTime);
1589 }
1571 } 1590 }
1572 1591
1573 void FFmpegDemuxer::ReadFrameIfNeeded() { 1592 void FFmpegDemuxer::ReadFrameIfNeeded() {
1574 DCHECK(task_runner_->BelongsToCurrentThread()); 1593 DCHECK(task_runner_->BelongsToCurrentThread());
1575 1594
1576 // Make sure we have work to do before reading. 1595 // Make sure we have work to do before reading.
1577 if (!blocking_thread_.IsRunning() || !StreamsHaveAvailableCapacity() || 1596 if (!blocking_thread_.IsRunning() || !StreamsHaveAvailableCapacity() ||
1578 pending_read_ || pending_seek_) { 1597 pending_read_ || pending_seek_) {
1579 return; 1598 return;
1580 } 1599 }
(...skipping 27 matching lines...) Expand all
1608 // - either underlying ffmpeg returned an error 1627 // - either underlying ffmpeg returned an error
1609 // - or FFMpegDemuxer reached the maximum allowed memory usage. 1628 // - or FFMpegDemuxer reached the maximum allowed memory usage.
1610 if (result < 0 || IsMaxMemoryUsageReached()) { 1629 if (result < 0 || IsMaxMemoryUsageReached()) {
1611 LOG(ERROR) << __func__ << " result=" << result 1630 LOG(ERROR) << __func__ << " result=" << result
1612 << " IsMaxMemoryUsageReached=" << IsMaxMemoryUsageReached(); 1631 << " IsMaxMemoryUsageReached=" << IsMaxMemoryUsageReached();
1613 // Update the duration based on the highest elapsed time across all streams 1632 // Update the duration based on the highest elapsed time across all streams
1614 // if it was previously unknown. 1633 // if it was previously unknown.
1615 if (!duration_known_) { 1634 if (!duration_known_) {
1616 base::TimeDelta max_duration; 1635 base::TimeDelta max_duration;
1617 1636
1618 for (StreamVector::iterator iter = streams_.begin(); 1637 for (const auto& stream : streams_) {
1619 iter != streams_.end(); 1638 if (!stream)
1620 ++iter) {
1621 if (!*iter)
1622 continue; 1639 continue;
1623 1640
1624 base::TimeDelta duration = (*iter)->GetElapsedTime(); 1641 base::TimeDelta duration = stream->GetElapsedTime();
1625 if (duration != kNoTimestamp && duration > max_duration) 1642 if (duration != kNoTimestamp && duration > max_duration)
1626 max_duration = duration; 1643 max_duration = duration;
1627 } 1644 }
1628 1645
1629 if (max_duration > base::TimeDelta()) { 1646 if (max_duration > base::TimeDelta()) {
1630 host_->SetDuration(max_duration); 1647 host_->SetDuration(max_duration);
1631 duration_known_ = true; 1648 duration_known_ = true;
1632 } 1649 }
1633 } 1650 }
1634 // If we have reached the end of stream, tell the downstream filters about 1651 // If we have reached the end of stream, tell the downstream filters about
(...skipping 14 matching lines...) Expand all
1649 // when av_read_frame() returns success code. See bug comment for ideas: 1666 // when av_read_frame() returns success code. See bug comment for ideas:
1650 // 1667 //
1651 // https://code.google.com/p/chromium/issues/detail?id=169133#c10 1668 // https://code.google.com/p/chromium/issues/detail?id=169133#c10
1652 if (!packet->data) { 1669 if (!packet->data) {
1653 ScopedAVPacket new_packet(new AVPacket()); 1670 ScopedAVPacket new_packet(new AVPacket());
1654 av_new_packet(new_packet.get(), 0); 1671 av_new_packet(new_packet.get(), 0);
1655 av_packet_copy_props(new_packet.get(), packet.get()); 1672 av_packet_copy_props(new_packet.get(), packet.get());
1656 packet.swap(new_packet); 1673 packet.swap(new_packet);
1657 } 1674 }
1658 1675
1659 FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index]; 1676 FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index].get();
1660 if (demuxer_stream->enabled()) 1677 if (demuxer_stream->enabled())
1661 demuxer_stream->EnqueuePacket(std::move(packet)); 1678 demuxer_stream->EnqueuePacket(std::move(packet));
1662 } 1679 }
1663 1680
1664 // Keep reading until we've reached capacity. 1681 // Keep reading until we've reached capacity.
1665 ReadFrameIfNeeded(); 1682 ReadFrameIfNeeded();
1666 } 1683 }
1667 1684
1668 bool FFmpegDemuxer::StreamsHaveAvailableCapacity() { 1685 bool FFmpegDemuxer::StreamsHaveAvailableCapacity() {
1669 DCHECK(task_runner_->BelongsToCurrentThread()); 1686 DCHECK(task_runner_->BelongsToCurrentThread());
1670 StreamVector::iterator iter; 1687 for (const auto& stream : streams_) {
1671 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { 1688 if (stream && stream->HasAvailableCapacity())
1672 if (*iter && (*iter)->HasAvailableCapacity()) {
1673 return true; 1689 return true;
1674 }
1675 } 1690 }
1676 return false; 1691 return false;
1677 } 1692 }
1678 1693
1679 bool FFmpegDemuxer::IsMaxMemoryUsageReached() const { 1694 bool FFmpegDemuxer::IsMaxMemoryUsageReached() const {
1680 DCHECK(task_runner_->BelongsToCurrentThread()); 1695 DCHECK(task_runner_->BelongsToCurrentThread());
1681 1696
1682 // Max allowed memory usage, all streams combined. 1697 // Max allowed memory usage, all streams combined.
1683 const size_t kDemuxerMemoryLimit = 150 * 1024 * 1024; 1698 const size_t kDemuxerMemoryLimit = 150 * 1024 * 1024;
1684 1699
1685 size_t memory_left = kDemuxerMemoryLimit; 1700 size_t memory_left = kDemuxerMemoryLimit;
1686 for (StreamVector::const_iterator iter = streams_.begin(); 1701 for (const auto& stream : streams_) {
1687 iter != streams_.end(); ++iter) { 1702 if (!stream)
1688 if (!(*iter))
1689 continue; 1703 continue;
1690 1704
1691 size_t stream_memory_usage = (*iter)->MemoryUsage(); 1705 size_t stream_memory_usage = stream->MemoryUsage();
1692 if (stream_memory_usage > memory_left) 1706 if (stream_memory_usage > memory_left)
1693 return true; 1707 return true;
1694 memory_left -= stream_memory_usage; 1708 memory_left -= stream_memory_usage;
1695 } 1709 }
1696 return false; 1710 return false;
1697 } 1711 }
1698 1712
1699 void FFmpegDemuxer::StreamHasEnded() { 1713 void FFmpegDemuxer::StreamHasEnded() {
1700 DCHECK(task_runner_->BelongsToCurrentThread()); 1714 DCHECK(task_runner_->BelongsToCurrentThread());
1701 StreamVector::iterator iter; 1715 for (const auto& stream : streams_) {
1702 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { 1716 if (stream)
1703 if (!*iter) 1717 stream->SetEndOfStream();
1704 continue;
1705 (*iter)->SetEndOfStream();
1706 } 1718 }
1707 } 1719 }
1708 1720
1709 void FFmpegDemuxer::OnDataSourceError() { 1721 void FFmpegDemuxer::OnDataSourceError() {
1710 MEDIA_LOG(ERROR, media_log_) << GetDisplayName() << ": data source error"; 1722 MEDIA_LOG(ERROR, media_log_) << GetDisplayName() << ": data source error";
1711 host_->OnDemuxerError(PIPELINE_ERROR_READ); 1723 host_->OnDemuxerError(PIPELINE_ERROR_READ);
1712 } 1724 }
1713 1725
1714 void FFmpegDemuxer::SetLiveness(DemuxerStream::Liveness liveness) { 1726 void FFmpegDemuxer::SetLiveness(DemuxerStream::Liveness liveness) {
1715 DCHECK(task_runner_->BelongsToCurrentThread()); 1727 DCHECK(task_runner_->BelongsToCurrentThread());
1716 for (auto* stream : streams_) { 1728 for (const auto& stream : streams_) {
1717 if (stream) 1729 if (stream)
1718 stream->SetLiveness(liveness); 1730 stream->SetLiveness(liveness);
1719 } 1731 }
1720 } 1732 }
1721 1733
1722 } // namespace media 1734 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698