namespace media {
+static base::Time ExtractTimelineOffset(AVFormatContext* format_context) {
+ if (strstr(format_context->iformat->name, "webm") ||
+ strstr(format_context->iformat->name, "matroska")) {
+ const AVDictionaryEntry* entry =
+ av_dict_get(format_context->metadata, "creation_time", NULL, 0);
+
+ base::Time timeline_offset;
+ if (entry != NULL && entry->value != NULL &&
+ FFmpegUTCDateToTime(entry->value, &timeline_offset)) {
+ return timeline_offset;
+ }
+ }
+
+ return base::Time();
+}
+
+static base::TimeDelta FramesToTimeDelta(int frames, double sample_rate) {
+ return base::TimeDelta::FromMicroseconds(
+ frames * base::Time::kMicrosecondsPerSecond / sample_rate);
+}
+
//
// FFmpegDemuxerStream
//
// Calculate the duration.
duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration);
+#if defined(USE_PROPRIETARY_CODECS)
if (stream_->codec->codec_id == AV_CODEC_ID_H264) {
bitstream_converter_.reset(
new FFmpegH264ToAnnexBBitstreamConverter(stream_->codec));
}
+#endif
if (is_encrypted) {
AVDictionaryEntry* key = av_dict_get(stream->metadata, "enc_key_id", NULL,
return;
}
+#if defined(USE_PROPRIETARY_CODECS)
// Convert the packet if there is a bitstream filter.
if (packet->data && bitstream_converter_enabled_ &&
!bitstream_converter_->ConvertPacket(packet.get())) {
LOG(ERROR) << "Format conversion failed.";
}
+#endif
// Get side data if any. For now, the only type of side_data is VP8 Alpha. We
// keep this generic so that other side_data types in the future can be
}
int skip_samples_size = 0;
- uint8* skip_samples = av_packet_get_side_data(packet.get(),
- AV_PKT_DATA_SKIP_SAMPLES,
- &skip_samples_size);
+ const uint32* skip_samples_ptr =
+ reinterpret_cast<const uint32*>(av_packet_get_side_data(
+ packet.get(), AV_PKT_DATA_SKIP_SAMPLES, &skip_samples_size));
const int kSkipSamplesValidSize = 10;
- const int kSkipSamplesOffset = 4;
+ const int kSkipEndSamplesOffset = 1;
if (skip_samples_size >= kSkipSamplesValidSize) {
- int discard_padding_samples = base::ByteSwapToLE32(
- *(reinterpret_cast<const uint32*>(skip_samples +
- kSkipSamplesOffset)));
- // TODO(vigneshv): Change decoder buffer to use number of samples so that
- // this conversion can be avoided.
- buffer->set_discard_padding(base::TimeDelta::FromMicroseconds(
- discard_padding_samples * 1000000.0 /
- audio_decoder_config().samples_per_second()));
+ // Because FFmpeg rolls codec delay and skip samples into one we can only
+ // allow front discard padding on the first buffer. Otherwise the discard
+ // helper can't figure out which data to discard. See AudioDiscardHelper.
+ int discard_front_samples = base::ByteSwapToLE32(*skip_samples_ptr);
+ if (last_packet_timestamp_ != kNoTimestamp()) {
+ DLOG(ERROR) << "Skip samples are only allowed for the first packet.";
+ discard_front_samples = 0;
+ }
+
+ const int discard_end_samples =
+ base::ByteSwapToLE32(*(skip_samples_ptr + kSkipEndSamplesOffset));
+ const int samples_per_second =
+ audio_decoder_config().samples_per_second();
+ buffer->set_discard_padding(std::make_pair(
+ FramesToTimeDelta(discard_front_samples, samples_per_second),
+ FramesToTimeDelta(discard_end_samples, samples_per_second)));
}
if (decrypt_config)
void FFmpegDemuxerStream::EnableBitstreamConverter() {
DCHECK(task_runner_->BelongsToCurrentThread());
+
+#if defined(USE_PROPRIETARY_CODECS)
CHECK(bitstream_converter_.get());
bitstream_converter_enabled_ = true;
+#else
+ NOTREACHED() << "Proprietary codecs not enabled.";
+#endif
}
bool FFmpegDemuxerStream::SupportsConfigChanges() { return false; }
media_log_(media_log),
bitrate_(0),
start_time_(kNoTimestamp()),
- audio_disabled_(false),
+ liveness_(LIVENESS_UNKNOWN),
text_enabled_(false),
duration_known_(false),
need_key_cb_(need_key_cb),
&FFmpegDemuxer::OnSeekFrameDone, weak_factory_.GetWeakPtr(), cb));
}
-void FFmpegDemuxer::OnAudioRendererDisabled() {
- DCHECK(task_runner_->BelongsToCurrentThread());
- audio_disabled_ = true;
- StreamVector::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if (*iter && (*iter)->type() == DemuxerStream::AUDIO) {
- (*iter)->Stop();
- }
- }
-}
-
void FFmpegDemuxer::Initialize(DemuxerHost* host,
const PipelineStatusCB& status_cb,
bool enable_text_tracks) {
host_ = host;
text_enabled_ = enable_text_tracks;
- // TODO(scherkus): DataSource should have a host by this point,
- // see http://crbug.com/122071
- data_source_->set_host(host);
-
url_protocol_.reset(new BlockingUrlProtocol(data_source_, BindToCurrentLoop(
base::Bind(&FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))));
glue_.reset(new FFmpegGlue(url_protocol_.get()));
return start_time_;
}
+base::Time FFmpegDemuxer::GetTimelineOffset() const {
+ return timeline_offset_;
+}
+
+Demuxer::Liveness FFmpegDemuxer::GetLiveness() const {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ return liveness_;
+}
+
void FFmpegDemuxer::AddTextStreams() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (strcmp(format_context->iformat->name, "avi") == 0)
format_context->flags |= AVFMT_FLAG_GENPTS;
+ timeline_offset_ = ExtractTimelineOffset(format_context);
+
+ if (max_duration == kInfiniteDuration() && !timeline_offset_.is_null()) {
+ liveness_ = LIVENESS_LIVE;
+ } else if (max_duration != kInfiniteDuration()) {
+ liveness_ = LIVENESS_RECORDED;
+ } else {
+ liveness_ = LIVENESS_UNKNOWN;
+ }
+
// Good to go: set the duration and bitrate and notify we're done
// initializing.
host_->SetDuration(max_duration);
// Defend against ffmpeg giving us a bad stream index.
if (packet->stream_index >= 0 &&
packet->stream_index < static_cast<int>(streams_.size()) &&
- streams_[packet->stream_index] &&
- (!audio_disabled_ ||
- streams_[packet->stream_index]->type() != DemuxerStream::AUDIO)) {
-
+ streams_[packet->stream_index]) {
// TODO(scherkus): Fix demuxing upstream to never return packets w/o data
// when av_read_frame() returns success code. See bug comment for ideas:
//
DCHECK(task_runner_->BelongsToCurrentThread());
StreamVector::iterator iter;
for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if (!*iter ||
- (audio_disabled_ && (*iter)->type() == DemuxerStream::AUDIO)) {
+ if (!*iter)
continue;
- }
(*iter)->SetEndOfStream();
}
}
void FFmpegDemuxer::NotifyBufferingChanged() {
DCHECK(task_runner_->BelongsToCurrentThread());
Ranges<base::TimeDelta> buffered;
- FFmpegDemuxerStream* audio =
- audio_disabled_ ? NULL : GetFFmpegStream(DemuxerStream::AUDIO);
+ FFmpegDemuxerStream* audio = GetFFmpegStream(DemuxerStream::AUDIO);
FFmpegDemuxerStream* video = GetFFmpegStream(DemuxerStream::VIDEO);
if (audio && video) {
buffered = audio->GetBufferedRanges().IntersectionWith(