root/media/filters/ffmpeg_demuxer.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. bitstream_converter_enabled_
  2. EnqueuePacket
  3. SetEndOfStream
  4. FlushBuffers
  5. Stop
  6. duration
  7. type
  8. Read
  9. EnableBitstreamConverter
  10. SupportsConfigChanges
  11. audio_decoder_config
  12. video_decoder_config
  13. GetElapsedTime
  14. GetBufferedRanges
  15. SatisfyPendingRead
  16. HasAvailableCapacity
  17. MemoryUsage
  18. GetTextKind
  19. GetMetadata
  20. ConvertStreamTimestamp
  21. weak_factory_
  22. Stop
  23. Seek
  24. OnAudioRendererDisabled
  25. Initialize
  26. GetStream
  27. GetFFmpegStream
  28. GetStartTime
  29. AddTextStreams
  30. CalculateBitrate
  31. OnOpenContextDone
  32. OnFindStreamInfoDone
  33. OnSeekFrameDone
  34. ReadFrameIfNeeded
  35. OnReadFrameDone
  36. OnDataSourceStopped
  37. StreamsHaveAvailableCapacity
  38. IsMaxMemoryUsageReached
  39. StreamHasEnded
  40. FireNeedKey
  41. NotifyCapacityAvailable
  42. NotifyBufferingChanged
  43. OnDataSourceError

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/filters/ffmpeg_demuxer.h"

#include <algorithm>
#include <string>

#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/metrics/sparse_histogram.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/sys_byteorder.h"
#include "base/task_runner_util.h"
#include "base/time/time.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/limits.h"
#include "media/base/media_log.h"
#include "media/base/video_decoder_config.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/ffmpeg_glue.h"
#include "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h"
#include "media/filters/webvtt_util.h"
#include "media/formats/webm/webm_crypto_helpers.h"

namespace media {

//
// FFmpegDemuxerStream
//
FFmpegDemuxerStream::FFmpegDemuxerStream(
    FFmpegDemuxer* demuxer,
    AVStream* stream)
    : demuxer_(demuxer),
      task_runner_(base::MessageLoopProxy::current()),
      stream_(stream),
      type_(UNKNOWN),
      end_of_stream_(false),
      last_packet_timestamp_(kNoTimestamp()),
      bitstream_converter_enabled_(false) {
  DCHECK(demuxer_);

  bool is_encrypted = false;

  // Determine our media format.
  switch (stream->codec->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
      type_ = AUDIO;
      AVStreamToAudioDecoderConfig(stream, &audio_config_, true);
      is_encrypted = audio_config_.is_encrypted();
      break;
    case AVMEDIA_TYPE_VIDEO:
      type_ = VIDEO;
      AVStreamToVideoDecoderConfig(stream, &video_config_, true);
      is_encrypted = video_config_.is_encrypted();
      break;
    case AVMEDIA_TYPE_SUBTITLE:
      type_ = TEXT;
      break;
    default:
      NOTREACHED();
      break;
  }

  // Calculate the duration.
  duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration);

  if (stream_->codec->codec_id == AV_CODEC_ID_H264) {
    bitstream_converter_.reset(
        new FFmpegH264ToAnnexBBitstreamConverter(stream_->codec));
  }

  if (is_encrypted) {
    AVDictionaryEntry* key = av_dict_get(stream->metadata, "enc_key_id", NULL,
                                         0);
    DCHECK(key);
    DCHECK(key->value);
    if (!key || !key->value)
      return;
    base::StringPiece base64_key_id(key->value);
    std::string enc_key_id;
    base::Base64Decode(base64_key_id, &enc_key_id);
    DCHECK(!enc_key_id.empty());
    if (enc_key_id.empty())
      return;

    encryption_key_id_.assign(enc_key_id);
    demuxer_->FireNeedKey(kWebMEncryptInitDataType, enc_key_id);
  }
}

void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (!demuxer_ || end_of_stream_) {
    NOTREACHED() << "Attempted to enqueue packet on a stopped stream";
    return;
  }

  // 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.";
  }

  // 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
  // handled the same way as well.
  av_packet_split_side_data(packet.get());

  scoped_refptr<DecoderBuffer> buffer;

  if (type() == DemuxerStream::TEXT) {
    int id_size = 0;
    uint8* id_data = av_packet_get_side_data(
        packet.get(),
        AV_PKT_DATA_WEBVTT_IDENTIFIER,
        &id_size);

    int settings_size = 0;
    uint8* settings_data = av_packet_get_side_data(
        packet.get(),
        AV_PKT_DATA_WEBVTT_SETTINGS,
        &settings_size);

    std::vector<uint8> side_data;
    MakeSideData(id_data, id_data + id_size,
                 settings_data, settings_data + settings_size,
                 &side_data);

    buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size,
                                     side_data.data(), side_data.size());
  } else {
    int side_data_size = 0;
    uint8* side_data = av_packet_get_side_data(
        packet.get(),
        AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
        &side_data_size);

    scoped_ptr<DecryptConfig> decrypt_config;
    int data_offset = 0;
    if ((type() == DemuxerStream::AUDIO && audio_config_.is_encrypted()) ||
        (type() == DemuxerStream::VIDEO && video_config_.is_encrypted())) {
      if (!WebMCreateDecryptConfig(
          packet->data, packet->size,
          reinterpret_cast<const uint8*>(encryption_key_id_.data()),
          encryption_key_id_.size(),
          &decrypt_config,
          &data_offset)) {
        LOG(ERROR) << "Creation of DecryptConfig failed.";
      }
    }

    // If a packet is returned by FFmpeg's av_parser_parse2() the packet will
    // reference inner memory of FFmpeg.  As such we should transfer the packet
    // into memory we control.
    if (side_data_size > 0) {
      buffer = DecoderBuffer::CopyFrom(packet.get()->data + data_offset,
                                       packet.get()->size - data_offset,
                                       side_data, side_data_size);
    } else {
      buffer = DecoderBuffer::CopyFrom(packet.get()->data + data_offset,
                                       packet.get()->size - data_offset);
    }

    int skip_samples_size = 0;
    uint8* skip_samples = av_packet_get_side_data(packet.get(),
                                                  AV_PKT_DATA_SKIP_SAMPLES,
                                                  &skip_samples_size);
    const int kSkipSamplesValidSize = 10;
    const int kSkipSamplesOffset = 4;
    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()));
    }

    if (decrypt_config)
      buffer->set_decrypt_config(decrypt_config.Pass());
  }

  buffer->set_timestamp(ConvertStreamTimestamp(
      stream_->time_base, packet->pts));
  buffer->set_duration(ConvertStreamTimestamp(
      stream_->time_base, packet->duration));
  if (buffer->timestamp() != kNoTimestamp() &&
      last_packet_timestamp_ != kNoTimestamp() &&
      last_packet_timestamp_ < buffer->timestamp()) {
    buffered_ranges_.Add(last_packet_timestamp_, buffer->timestamp());
    demuxer_->NotifyBufferingChanged();
  }
  last_packet_timestamp_ = buffer->timestamp();

  buffer_queue_.Push(buffer);
  SatisfyPendingRead();
}

void FFmpegDemuxerStream::SetEndOfStream() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  end_of_stream_ = true;
  SatisfyPendingRead();
}

void FFmpegDemuxerStream::FlushBuffers() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  DCHECK(read_cb_.is_null()) << "There should be no pending read";
  buffer_queue_.Clear();
  end_of_stream_ = false;
  last_packet_timestamp_ = kNoTimestamp();
}

void FFmpegDemuxerStream::Stop() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  buffer_queue_.Clear();
  if (!read_cb_.is_null()) {
    base::ResetAndReturn(&read_cb_).Run(
        DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
  }
  demuxer_ = NULL;
  stream_ = NULL;
  end_of_stream_ = true;
}

base::TimeDelta FFmpegDemuxerStream::duration() {
  return duration_;
}

DemuxerStream::Type FFmpegDemuxerStream::type() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  return type_;
}

void FFmpegDemuxerStream::Read(const ReadCB& read_cb) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  CHECK(read_cb_.is_null()) << "Overlapping reads are not supported";
  read_cb_ = BindToCurrentLoop(read_cb);

  // Don't accept any additional reads if we've been told to stop.
  // The |demuxer_| may have been destroyed in the pipeline thread.
  //
  // TODO(scherkus): it would be cleaner to reply with an error message.
  if (!demuxer_) {
    base::ResetAndReturn(&read_cb_).Run(
        DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
    return;
  }

  SatisfyPendingRead();
}

void FFmpegDemuxerStream::EnableBitstreamConverter() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  CHECK(bitstream_converter_.get());
  bitstream_converter_enabled_ = true;
}

bool FFmpegDemuxerStream::SupportsConfigChanges() { return false; }

AudioDecoderConfig FFmpegDemuxerStream::audio_decoder_config() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  CHECK_EQ(type_, AUDIO);
  return audio_config_;
}

VideoDecoderConfig FFmpegDemuxerStream::video_decoder_config() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  CHECK_EQ(type_, VIDEO);
  return video_config_;
}

FFmpegDemuxerStream::~FFmpegDemuxerStream() {
  DCHECK(!demuxer_);
  DCHECK(read_cb_.is_null());
  DCHECK(buffer_queue_.IsEmpty());
}

base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const {
  return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts);
}

Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const {
  return buffered_ranges_;
}

void FFmpegDemuxerStream::SatisfyPendingRead() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  if (!read_cb_.is_null()) {
    if (!buffer_queue_.IsEmpty()) {
      base::ResetAndReturn(&read_cb_).Run(
          DemuxerStream::kOk, buffer_queue_.Pop());
    } else if (end_of_stream_) {
      base::ResetAndReturn(&read_cb_).Run(
          DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
    }
  }

  // Have capacity? Ask for more!
  if (HasAvailableCapacity() && !end_of_stream_) {
    demuxer_->NotifyCapacityAvailable();
  }
}

bool FFmpegDemuxerStream::HasAvailableCapacity() {
  // TODO(scherkus): Remove this return and reenable time-based capacity
  // after our data sources support canceling/concurrent reads, see
  // http://crbug.com/165762 for details.
#if 1
  return !read_cb_.is_null();
#else
  // Try to have one second's worth of encoded data per stream.
  const base::TimeDelta kCapacity = base::TimeDelta::FromSeconds(1);
  return buffer_queue_.IsEmpty() || buffer_queue_.Duration() < kCapacity;
#endif
}

size_t FFmpegDemuxerStream::MemoryUsage() const {
  return buffer_queue_.data_size();
}

TextKind FFmpegDemuxerStream::GetTextKind() const {
  DCHECK_EQ(type_, DemuxerStream::TEXT);

  if (stream_->disposition & AV_DISPOSITION_CAPTIONS)
    return kTextCaptions;

  if (stream_->disposition & AV_DISPOSITION_DESCRIPTIONS)
    return kTextDescriptions;

  if (stream_->disposition & AV_DISPOSITION_METADATA)
    return kTextMetadata;

  return kTextSubtitles;
}

std::string FFmpegDemuxerStream::GetMetadata(const char* key) const {
  const AVDictionaryEntry* entry =
      av_dict_get(stream_->metadata, key, NULL, 0);
  return (entry == NULL || entry->value == NULL) ? "" : entry->value;
}

// static
base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp(
    const AVRational& time_base, int64 timestamp) {
  if (timestamp == static_cast<int64>(AV_NOPTS_VALUE))
    return kNoTimestamp();

  return ConvertFromTimeBase(time_base, timestamp);
}

//
// FFmpegDemuxer
//
FFmpegDemuxer::FFmpegDemuxer(
    const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
    DataSource* data_source,
    const NeedKeyCB& need_key_cb,
    const scoped_refptr<MediaLog>& media_log)
    : host_(NULL),
      task_runner_(task_runner),
      blocking_thread_("FFmpegDemuxer"),
      pending_read_(false),
      pending_seek_(false),
      data_source_(data_source),
      media_log_(media_log),
      bitrate_(0),
      start_time_(kNoTimestamp()),
      audio_disabled_(false),
      text_enabled_(false),
      duration_known_(false),
      need_key_cb_(need_key_cb),
      weak_factory_(this) {
  DCHECK(task_runner_.get());
  DCHECK(data_source_);
}

FFmpegDemuxer::~FFmpegDemuxer() {}

void FFmpegDemuxer::Stop(const base::Closure& callback) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  url_protocol_->Abort();
  data_source_->Stop(
      BindToCurrentLoop(base::Bind(&FFmpegDemuxer::OnDataSourceStopped,
                                   weak_factory_.GetWeakPtr(),
                                   BindToCurrentLoop(callback))));
  data_source_ = NULL;
}

void FFmpegDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  CHECK(!pending_seek_);

  // TODO(scherkus): Inspect |pending_read_| and cancel IO via |blocking_url_|,
  // otherwise we can end up waiting for a pre-seek read to complete even though
  // we know we're going to drop it on the floor.

  // Always seek to a timestamp less than or equal to the desired timestamp.
  int flags = AVSEEK_FLAG_BACKWARD;

  // Passing -1 as our stream index lets FFmpeg pick a default stream.  FFmpeg
  // will attempt to use the lowest-index video stream, if present, followed by
  // the lowest-index audio stream.
  pending_seek_ = true;
  base::PostTaskAndReplyWithResult(
      blocking_thread_.message_loop_proxy().get(),
      FROM_HERE,
      base::Bind(&av_seek_frame,
                 glue_->format_context(),
                 -1,
                 time.InMicroseconds(),
                 flags),
      base::Bind(
          &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) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  host_ = host;
  text_enabled_ = enable_text_tracks;

  url_protocol_.reset(new BlockingUrlProtocol(data_source_, BindToCurrentLoop(
      base::Bind(&FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))));
  glue_.reset(new FFmpegGlue(url_protocol_.get()));
  AVFormatContext* format_context = glue_->format_context();

  // Disable ID3v1 tag reading to avoid costly seeks to end of file for data we
  // don't use.  FFmpeg will only read ID3v1 tags if no other metadata is
  // available, so add a metadata entry to ensure some is always present.
  av_dict_set(&format_context->metadata, "skip_id3v1_tags", "", 0);

  // Open the AVFormatContext using our glue layer.
  CHECK(blocking_thread_.Start());
  base::PostTaskAndReplyWithResult(
      blocking_thread_.message_loop_proxy().get(),
      FROM_HERE,
      base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())),
      base::Bind(&FFmpegDemuxer::OnOpenContextDone,
                 weak_factory_.GetWeakPtr(),
                 status_cb));
}

DemuxerStream* FFmpegDemuxer::GetStream(DemuxerStream::Type type) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  return GetFFmpegStream(type);
}

FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream(
    DemuxerStream::Type type) const {
  StreamVector::const_iterator iter;
  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
    if (*iter && (*iter)->type() == type) {
      return *iter;
    }
  }
  return NULL;
}

base::TimeDelta FFmpegDemuxer::GetStartTime() const {
  DCHECK(task_runner_->BelongsToCurrentThread());
  return start_time_;
}

void FFmpegDemuxer::AddTextStreams() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  for (StreamVector::size_type idx = 0; idx < streams_.size(); ++idx) {
    FFmpegDemuxerStream* stream = streams_[idx];
    if (stream == NULL || stream->type() != DemuxerStream::TEXT)
      continue;

    TextKind kind = stream->GetTextKind();
    std::string title = stream->GetMetadata("title");
    std::string language = stream->GetMetadata("language");

    // TODO: Implement "id" metadata in FFMPEG.
    // See: http://crbug.com/323183
    host_->AddTextStream(stream, TextTrackConfig(kind, title, language,
        std::string()));
  }
}

// Helper for calculating the bitrate of the media based on information stored
// in |format_context| or failing that the size and duration of the media.
//
// Returns 0 if a bitrate could not be determined.
static int CalculateBitrate(
    AVFormatContext* format_context,
    const base::TimeDelta& duration,
    int64 filesize_in_bytes) {
  // If there is a bitrate set on the container, use it.
  if (format_context->bit_rate > 0)
    return format_context->bit_rate;

  // Then try to sum the bitrates individually per stream.
  int bitrate = 0;
  for (size_t i = 0; i < format_context->nb_streams; ++i) {
    AVCodecContext* codec_context = format_context->streams[i]->codec;
    bitrate += codec_context->bit_rate;
  }
  if (bitrate > 0)
    return bitrate;

  // See if we can approximate the bitrate as long as we have a filesize and
  // valid duration.
  if (duration.InMicroseconds() <= 0 ||
      duration == kInfiniteDuration() ||
      filesize_in_bytes == 0) {
    return 0;
  }

  // Do math in floating point as we'd overflow an int64 if the filesize was
  // larger than ~1073GB.
  double bytes = filesize_in_bytes;
  double duration_us = duration.InMicroseconds();
  return bytes * 8000000.0 / duration_us;
}

void FFmpegDemuxer::OnOpenContextDone(const PipelineStatusCB& status_cb,
                                      bool result) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  if (!blocking_thread_.IsRunning()) {
    status_cb.Run(PIPELINE_ERROR_ABORT);
    return;
  }

  if (!result) {
    status_cb.Run(DEMUXER_ERROR_COULD_NOT_OPEN);
    return;
  }

  // Fully initialize AVFormatContext by parsing the stream a little.
  base::PostTaskAndReplyWithResult(
      blocking_thread_.message_loop_proxy().get(),
      FROM_HERE,
      base::Bind(&avformat_find_stream_info,
                 glue_->format_context(),
                 static_cast<AVDictionary**>(NULL)),
      base::Bind(&FFmpegDemuxer::OnFindStreamInfoDone,
                 weak_factory_.GetWeakPtr(),
                 status_cb));
}

void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
                                         int result) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  if (!blocking_thread_.IsRunning() || !data_source_) {
    status_cb.Run(PIPELINE_ERROR_ABORT);
    return;
  }

  if (result < 0) {
    status_cb.Run(DEMUXER_ERROR_COULD_NOT_PARSE);
    return;
  }

  // Create demuxer stream entries for each possible AVStream. Each stream
  // is examined to determine if it is supported or not (is the codec enabled
  // for it in this release?). Unsupported streams are skipped, allowing for
  // partial playback. At least one audio or video stream must be playable.
  AVFormatContext* format_context = glue_->format_context();
  streams_.resize(format_context->nb_streams);

  AVStream* audio_stream = NULL;
  AudioDecoderConfig audio_config;

  AVStream* video_stream = NULL;
  VideoDecoderConfig video_config;

  base::TimeDelta max_duration;
  for (size_t i = 0; i < format_context->nb_streams; ++i) {
    AVStream* stream = format_context->streams[i];
    AVCodecContext* codec_context = stream->codec;
    AVMediaType codec_type = codec_context->codec_type;

    if (codec_type == AVMEDIA_TYPE_AUDIO) {
      if (audio_stream)
        continue;

      // Log the codec detected, whether it is supported or not.
      UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedAudioCodec",
                                  codec_context->codec_id);
      // Ensure the codec is supported. IsValidConfig() also checks that the
      // channel layout and sample format are valid.
      AVStreamToAudioDecoderConfig(stream, &audio_config, false);
      if (!audio_config.IsValidConfig())
        continue;
      audio_stream = stream;
    } else if (codec_type == AVMEDIA_TYPE_VIDEO) {
      if (video_stream)
        continue;

      // Log the codec detected, whether it is supported or not.
      UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodec",
                                  codec_context->codec_id);
      // Ensure the codec is supported. IsValidConfig() also checks that the
      // frame size and visible size are valid.
      AVStreamToVideoDecoderConfig(stream, &video_config, false);

      if (!video_config.IsValidConfig())
        continue;
      video_stream = stream;
    } else if (codec_type == AVMEDIA_TYPE_SUBTITLE) {
      if (codec_context->codec_id != AV_CODEC_ID_WEBVTT || !text_enabled_) {
        continue;
      }
    } else {
      continue;
    }

    streams_[i] = new FFmpegDemuxerStream(this, stream);
    max_duration = std::max(max_duration, streams_[i]->duration());

    if (stream->first_dts != static_cast<int64_t>(AV_NOPTS_VALUE)) {
      const base::TimeDelta first_dts = ConvertFromTimeBase(
          stream->time_base, stream->first_dts);
      if (start_time_ == kNoTimestamp() || first_dts < start_time_)
        start_time_ = first_dts;
    }
  }

  if (!audio_stream && !video_stream) {
    status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
    return;
  }

  if (text_enabled_)
    AddTextStreams();

  if (format_context->duration != static_cast<int64_t>(AV_NOPTS_VALUE)) {
    // If there is a duration value in the container use that to find the
    // maximum between it and the duration from A/V streams.
    const AVRational av_time_base = {1, AV_TIME_BASE};
    max_duration =
        std::max(max_duration,
                 ConvertFromTimeBase(av_time_base, format_context->duration));
  } else {
    // The duration is unknown, in which case this is likely a live stream.
    max_duration = kInfiniteDuration();
  }

  // Some demuxers, like WAV, do not put timestamps on their frames. We
  // assume the the start time is 0.
  if (start_time_ == kNoTimestamp())
    start_time_ = base::TimeDelta();

  // MPEG-4 B-frames cause grief for a simple container like AVI. Enable PTS
  // generation so we always get timestamps, see http://crbug.com/169570
  if (strcmp(format_context->iformat->name, "avi") == 0)
    format_context->flags |= AVFMT_FLAG_GENPTS;

  // Good to go: set the duration and bitrate and notify we're done
  // initializing.
  host_->SetDuration(max_duration);
  duration_known_ = (max_duration != kInfiniteDuration());

  int64 filesize_in_bytes = 0;
  url_protocol_->GetSize(&filesize_in_bytes);
  bitrate_ = CalculateBitrate(format_context, max_duration, filesize_in_bytes);
  if (bitrate_ > 0)
    data_source_->SetBitrate(bitrate_);

  // Audio logging
  if (audio_stream) {
    AVCodecContext* audio_codec = audio_stream->codec;
    media_log_->SetBooleanProperty("found_audio_stream", true);

    SampleFormat sample_format = audio_config.sample_format();
    std::string sample_name = SampleFormatToString(sample_format);

    media_log_->SetStringProperty("audio_sample_format", sample_name);

    AVCodec* codec = avcodec_find_decoder(audio_codec->codec_id);
    if (codec) {
      media_log_->SetStringProperty("audio_codec_name", codec->name);
    }

    media_log_->SetIntegerProperty("audio_channels_count",
                                   audio_codec->channels);
    media_log_->SetIntegerProperty("audio_samples_per_second",
                                   audio_config.samples_per_second());
  } else {
    media_log_->SetBooleanProperty("found_audio_stream", false);
  }

  // Video logging
  if (video_stream) {
    AVCodecContext* video_codec = video_stream->codec;
    media_log_->SetBooleanProperty("found_video_stream", true);

    AVCodec* codec = avcodec_find_decoder(video_codec->codec_id);
    if (codec) {
      media_log_->SetStringProperty("video_codec_name", codec->name);
    }

    media_log_->SetIntegerProperty("width", video_codec->width);
    media_log_->SetIntegerProperty("height", video_codec->height);
    media_log_->SetIntegerProperty("coded_width",
                                   video_codec->coded_width);
    media_log_->SetIntegerProperty("coded_height",
                                   video_codec->coded_height);
    media_log_->SetStringProperty(
        "time_base",
        base::StringPrintf("%d/%d",
                           video_codec->time_base.num,
                           video_codec->time_base.den));
    media_log_->SetStringProperty(
        "video_format", VideoFrame::FormatToString(video_config.format()));
    media_log_->SetBooleanProperty("video_is_encrypted",
                                   video_config.is_encrypted());
  } else {
    media_log_->SetBooleanProperty("found_video_stream", false);
  }


  media_log_->SetTimeProperty("max_duration", max_duration);
  media_log_->SetTimeProperty("start_time", start_time_);
  media_log_->SetIntegerProperty("bitrate", bitrate_);

  status_cb.Run(PIPELINE_OK);
}

void FFmpegDemuxer::OnSeekFrameDone(const PipelineStatusCB& cb, int result) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  CHECK(pending_seek_);
  pending_seek_ = false;

  if (!blocking_thread_.IsRunning()) {
    cb.Run(PIPELINE_ERROR_ABORT);
    return;
  }

  if (result < 0) {
    // Use VLOG(1) instead of NOTIMPLEMENTED() to prevent the message being
    // captured from stdout and contaminates testing.
    // TODO(scherkus): Implement this properly and signal error (BUG=23447).
    VLOG(1) << "Not implemented";
  }

  // Tell streams to flush buffers due to seeking.
  StreamVector::iterator iter;
  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
    if (*iter)
      (*iter)->FlushBuffers();
  }

  // Resume reading until capacity.
  ReadFrameIfNeeded();

  // Notify we're finished seeking.
  cb.Run(PIPELINE_OK);
}

void FFmpegDemuxer::ReadFrameIfNeeded() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  // Make sure we have work to do before reading.
  if (!blocking_thread_.IsRunning() || !StreamsHaveAvailableCapacity() ||
      pending_read_ || pending_seek_) {
    return;
  }

  // Allocate and read an AVPacket from the media. Save |packet_ptr| since
  // evaluation order of packet.get() and base::Passed(&packet) is
  // undefined.
  ScopedAVPacket packet(new AVPacket());
  AVPacket* packet_ptr = packet.get();

  pending_read_ = true;
  base::PostTaskAndReplyWithResult(
      blocking_thread_.message_loop_proxy().get(),
      FROM_HERE,
      base::Bind(&av_read_frame, glue_->format_context(), packet_ptr),
      base::Bind(&FFmpegDemuxer::OnReadFrameDone,
                 weak_factory_.GetWeakPtr(),
                 base::Passed(&packet)));
}

void FFmpegDemuxer::OnReadFrameDone(ScopedAVPacket packet, int result) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  DCHECK(pending_read_);
  pending_read_ = false;

  if (!blocking_thread_.IsRunning() || pending_seek_) {
    return;
  }

  // Consider the stream as ended if:
  // - either underlying ffmpeg returned an error
  // - or FFMpegDemuxer reached the maximum allowed memory usage.
  if (result < 0 || IsMaxMemoryUsageReached()) {
    // Update the duration based on the highest elapsed time across all streams
    // if it was previously unknown.
    if (!duration_known_) {
      base::TimeDelta max_duration;

      for (StreamVector::iterator iter = streams_.begin();
           iter != streams_.end();
           ++iter) {
        if (!*iter)
          continue;

        base::TimeDelta duration = (*iter)->GetElapsedTime();
        if (duration != kNoTimestamp() && duration > max_duration)
          max_duration = duration;
      }

      if (max_duration > base::TimeDelta()) {
        host_->SetDuration(max_duration);
        duration_known_ = true;
      }
    }
    // If we have reached the end of stream, tell the downstream filters about
    // the event.
    StreamHasEnded();
    return;
  }

  // Queue the packet with the appropriate stream.
  DCHECK_GE(packet->stream_index, 0);
  DCHECK_LT(packet->stream_index, static_cast<int>(streams_.size()));

  // 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)) {

    // TODO(scherkus): Fix demuxing upstream to never return packets w/o data
    // when av_read_frame() returns success code. See bug comment for ideas:
    //
    // https://code.google.com/p/chromium/issues/detail?id=169133#c10
    if (!packet->data) {
      ScopedAVPacket new_packet(new AVPacket());
      av_new_packet(new_packet.get(), 0);

      new_packet->pts = packet->pts;
      new_packet->dts = packet->dts;
      new_packet->pos = packet->pos;
      new_packet->duration = packet->duration;
      new_packet->convergence_duration = packet->convergence_duration;
      new_packet->flags = packet->flags;
      new_packet->stream_index = packet->stream_index;

      packet.swap(new_packet);
    }

    // Special case for opus in ogg.  FFmpeg is pre-trimming the codec delay
    // from the packet timestamp.  Chrome expects to handle this itself inside
    // the decoder, so shift timestamps by the delay in this case.
    // TODO(dalecurtis): Try to get fixed upstream.  See http://crbug.com/328207
    if (strcmp(glue_->format_context()->iformat->name, "ogg") == 0) {
      const AVCodecContext* codec_context =
          glue_->format_context()->streams[packet->stream_index]->codec;
      if (codec_context->codec_id == AV_CODEC_ID_OPUS &&
          codec_context->delay > 0) {
        packet->pts += codec_context->delay;
      }
    }

    FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index];
    demuxer_stream->EnqueuePacket(packet.Pass());
  }

  // Keep reading until we've reached capacity.
  ReadFrameIfNeeded();
}

void FFmpegDemuxer::OnDataSourceStopped(const base::Closure& callback) {
  // This will block until all tasks complete. Note that after this returns it's
  // possible for reply tasks (e.g., OnReadFrameDone()) to be queued on this
  // thread. Each of the reply task methods must check whether we've stopped the
  // thread and drop their results on the floor.
  DCHECK(task_runner_->BelongsToCurrentThread());
  blocking_thread_.Stop();

  StreamVector::iterator iter;
  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
    if (*iter)
      (*iter)->Stop();
  }

  callback.Run();
}

bool FFmpegDemuxer::StreamsHaveAvailableCapacity() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  StreamVector::iterator iter;
  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
    if (*iter && (*iter)->HasAvailableCapacity()) {
      return true;
    }
  }
  return false;
}

bool FFmpegDemuxer::IsMaxMemoryUsageReached() const {
  DCHECK(task_runner_->BelongsToCurrentThread());

  // Max allowed memory usage, all streams combined.
  const size_t kDemuxerMemoryLimit = 150 * 1024 * 1024;

  size_t memory_left = kDemuxerMemoryLimit;
  for (StreamVector::const_iterator iter = streams_.begin();
       iter != streams_.end(); ++iter) {
    if (!(*iter))
      continue;

    size_t stream_memory_usage = (*iter)->MemoryUsage();
    if (stream_memory_usage > memory_left)
      return true;
    memory_left -= stream_memory_usage;
  }
  return false;
}

void FFmpegDemuxer::StreamHasEnded() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  StreamVector::iterator iter;
  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
    if (!*iter ||
        (audio_disabled_ && (*iter)->type() == DemuxerStream::AUDIO)) {
      continue;
    }
    (*iter)->SetEndOfStream();
  }
}

void FFmpegDemuxer::FireNeedKey(const std::string& init_data_type,
                                const std::string& encryption_key_id) {
  std::vector<uint8> key_id_local(encryption_key_id.begin(),
                                  encryption_key_id.end());
  need_key_cb_.Run(init_data_type, key_id_local);
}

void FFmpegDemuxer::NotifyCapacityAvailable() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  ReadFrameIfNeeded();
}

void FFmpegDemuxer::NotifyBufferingChanged() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  Ranges<base::TimeDelta> buffered;
  FFmpegDemuxerStream* audio =
      audio_disabled_ ? NULL : GetFFmpegStream(DemuxerStream::AUDIO);
  FFmpegDemuxerStream* video = GetFFmpegStream(DemuxerStream::VIDEO);
  if (audio && video) {
    buffered = audio->GetBufferedRanges().IntersectionWith(
        video->GetBufferedRanges());
  } else if (audio) {
    buffered = audio->GetBufferedRanges();
  } else if (video) {
    buffered = video->GetBufferedRanges();
  }
  for (size_t i = 0; i < buffered.size(); ++i)
    host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i));
}

void FFmpegDemuxer::OnDataSourceError() {
  host_->OnDemuxerError(PIPELINE_ERROR_READ);
}

}  // namespace media

/* [<][>][^][v][top][bottom][index][help] */