| Index: media/filters/ffmpeg_demuxer.cc
|
| diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..43441473ded5234dc511328ae1a5797119974090
|
| --- /dev/null
|
| +++ b/media/filters/ffmpeg_demuxer.cc
|
| @@ -0,0 +1,281 @@
|
| +// Copyright (c) 2009 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 "base/string_util.h"
|
| +#include "base/time.h"
|
| +#include "media/base/filter_host.h"
|
| +#include "media/filters/ffmpeg_common.h"
|
| +#include "media/filters/ffmpeg_demuxer.h"
|
| +#include "media/filters/ffmpeg_glue.h"
|
| +
|
| +namespace media {
|
| +
|
| +//
|
| +// AVPacketBuffer
|
| +//
|
| +class AVPacketBuffer : public Buffer {
|
| + public:
|
| + AVPacketBuffer(AVPacket* packet, const base::TimeDelta& timestamp,
|
| + const base::TimeDelta& duration)
|
| + : packet_(packet) {
|
| + DCHECK(packet);
|
| + SetTimestamp(timestamp);
|
| + SetDuration(duration);
|
| + }
|
| +
|
| + virtual ~AVPacketBuffer() {
|
| + av_free_packet(packet_.get());
|
| + }
|
| +
|
| + // Buffer implementation.
|
| + virtual const char* GetData() const {
|
| + return reinterpret_cast<const char*>(packet_->data);
|
| + }
|
| +
|
| + virtual size_t GetDataSize() const {
|
| + return static_cast<size_t>(packet_->size);
|
| + }
|
| +
|
| + private:
|
| + scoped_ptr<AVPacket> packet_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(AVPacketBuffer);
|
| +};
|
| +
|
| +
|
| +//
|
| +// FFmpegDemuxerStream
|
| +//
|
| +FFmpegDemuxerStream::FFmpegDemuxerStream(FFmpegDemuxer* demuxer,
|
| + const AVStream& stream)
|
| + : demuxer_(demuxer) {
|
| + DCHECK(demuxer_);
|
| +
|
| + // Determine our media format.
|
| + switch (stream.codec->codec_type) {
|
| + case CODEC_TYPE_AUDIO:
|
| + media_format_.SetAsString(MediaFormat::kMimeType,
|
| + mime_type::kFFmpegAudio);
|
| + media_format_.SetAsInteger(MediaFormat::kChannels,
|
| + stream.codec->channels);
|
| + media_format_.SetAsInteger(MediaFormat::kSampleRate,
|
| + stream.codec->sample_rate);
|
| + break;
|
| + case CODEC_TYPE_VIDEO:
|
| + media_format_.SetAsString(MediaFormat::kMimeType,
|
| + mime_type::kFFmpegVideo);
|
| + media_format_.SetAsInteger(MediaFormat::kHeight,
|
| + stream.codec->height);
|
| + media_format_.SetAsInteger(MediaFormat::kWidth,
|
| + stream.codec->width);
|
| + break;
|
| + default:
|
| + NOTREACHED();
|
| + break;
|
| + }
|
| + int codec_id = static_cast<int>(stream.codec->codec_id);
|
| + media_format_.SetAsInteger(kFFmpegCodecID, codec_id);
|
| +
|
| + // Calculate the time base and duration in microseconds.
|
| + int64 time_base_us = static_cast<int64>(av_q2d(stream.time_base) *
|
| + base::Time::kMicrosecondsPerSecond);
|
| + int64 duration_us = static_cast<int64>(time_base_us * stream.duration);
|
| + time_base_ = base::TimeDelta::FromMicroseconds(time_base_us);
|
| + duration_ = base::TimeDelta::FromMicroseconds(duration_us);
|
| +}
|
| +
|
| +FFmpegDemuxerStream::~FFmpegDemuxerStream() {
|
| + // Since |input_queue_| and |output_queue_| use scoped_refptr everything
|
| + // should get released.
|
| +}
|
| +
|
| +bool FFmpegDemuxerStream::HasPendingReads() {
|
| + AutoLock auto_lock(lock_);
|
| + return !output_queue_.empty();
|
| +}
|
| +
|
| +void FFmpegDemuxerStream::EnqueuePacket(AVPacket* packet) {
|
| + base::TimeDelta timestamp = time_base_ * packet->pts;
|
| + base::TimeDelta duration = time_base_ * packet->duration;
|
| + Buffer* buffer = new AVPacketBuffer(packet, timestamp, duration);
|
| + DCHECK(buffer);
|
| + {
|
| + AutoLock auto_lock(lock_);
|
| + input_queue_.push_back(buffer);
|
| + }
|
| + FulfillPendingReads();
|
| +}
|
| +
|
| +const MediaFormat* FFmpegDemuxerStream::GetMediaFormat() {
|
| + return &media_format_;
|
| +}
|
| +
|
| +void FFmpegDemuxerStream::Read(Assignable<Buffer>* buffer) {
|
| + DCHECK(buffer);
|
| + {
|
| + AutoLock auto_lock(lock_);
|
| + output_queue_.push_back(scoped_refptr< Assignable<Buffer> >(buffer));
|
| + }
|
| + if (FulfillPendingReads()) {
|
| + demuxer_->ScheduleDemux();
|
| + }
|
| +}
|
| +
|
| +bool FFmpegDemuxerStream::FulfillPendingReads() {
|
| + bool pending_reads = false;
|
| + while (true) {
|
| + scoped_refptr<Buffer> buffer_in;
|
| + scoped_refptr< Assignable<Buffer> > buffer_out;
|
| + {
|
| + AutoLock auto_lock(lock_);
|
| + pending_reads = !output_queue_.empty();
|
| + if (input_queue_.empty() || output_queue_.empty()) {
|
| + break;
|
| + }
|
| + buffer_in = input_queue_.front();
|
| + buffer_out = output_queue_.front();
|
| + input_queue_.pop_front();
|
| + output_queue_.pop_front();
|
| + }
|
| + buffer_out->SetBuffer(buffer_in);
|
| + buffer_out->OnAssignment();
|
| + }
|
| + return pending_reads;
|
| +}
|
| +
|
| +
|
| +//
|
| +// FFmpegDemuxer
|
| +//
|
| +FFmpegDemuxer::FFmpegDemuxer()
|
| + : demuxing_(false),
|
| + format_context_(NULL) {
|
| +}
|
| +
|
| +FFmpegDemuxer::~FFmpegDemuxer() {
|
| + if (format_context_) {
|
| + av_free(format_context_);
|
| + }
|
| + while (!streams_.empty()) {
|
| + delete streams_.back();
|
| + streams_.pop_back();
|
| + }
|
| +}
|
| +
|
| +void FFmpegDemuxer::ScheduleDemux() {
|
| + if (!demuxing_) {
|
| + demuxing_ = true;
|
| + host_->PostTask(NewRunnableMethod(this, &FFmpegDemuxer::Demux));
|
| + }
|
| +}
|
| +
|
| +void FFmpegDemuxer::Stop() {
|
| + // TODO(scherkus): implement Stop().
|
| + NOTIMPLEMENTED();
|
| +}
|
| +
|
| +bool FFmpegDemuxer::Initialize(DataSource* data_source) {
|
| + // In order to get FFmpeg to use |data_source| for file IO we must transfer
|
| + // ownership via FFmpegGlue. We'll add |data_source| to FFmpegGlue and pass
|
| + // the resulting key to FFmpeg. FFmpeg will pass the key to FFmpegGlue which
|
| + // will take care of attaching |data_source| to an FFmpeg context. After
|
| + // we finish initializing the FFmpeg context we can remove |data_source| from
|
| + // FFmpegGlue.
|
| + //
|
| + // Refer to media/filters/ffmpeg_glue.h for details.
|
| +
|
| + // Add our data source and get our unique key.
|
| + std::string key = FFmpegGlue::get()->AddDataSource(data_source);
|
| +
|
| + // Open FFmpeg AVFormatContext.
|
| + DCHECK(!format_context_);
|
| + int result = av_open_input_file(&format_context_, key.c_str(), NULL, 0, NULL);
|
| +
|
| + // Remove our data source.
|
| + FFmpegGlue::get()->RemoveDataSource(data_source);
|
| +
|
| + if (result < 0) {
|
| + host_->Error(DEMUXER_ERROR_COULD_NOT_OPEN);
|
| + return false;
|
| + }
|
| +
|
| + // Fully initialize AVFormatContext by parsing the stream a little.
|
| + result = av_find_stream_info(format_context_);
|
| + if (result < 0) {
|
| + host_->Error(DEMUXER_ERROR_COULD_NOT_PARSE);
|
| + return false;
|
| + }
|
| +
|
| + // Create demuxer streams for all supported streams.
|
| + base::TimeDelta max_duration;
|
| + for (size_t i = 0; i < format_context_->nb_streams; ++i) {
|
| + CodecType codec_type = format_context_->streams[i]->codec->codec_type;
|
| + if (codec_type == CODEC_TYPE_AUDIO || codec_type == CODEC_TYPE_VIDEO) {
|
| + AVStream* stream = format_context_->streams[i];
|
| + FFmpegDemuxerStream* demuxer_stream
|
| + = new FFmpegDemuxerStream(this, *stream);
|
| + DCHECK(demuxer_stream);
|
| + streams_.push_back(demuxer_stream);
|
| + max_duration = std::max(max_duration, demuxer_stream->duration());
|
| + }
|
| + }
|
| + if (streams_.empty()) {
|
| + host_->Error(DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
|
| + return false;
|
| + }
|
| +
|
| + // We have at least one supported stream, set the duration and notify we're
|
| + // done initializing.
|
| + host_->SetDuration(max_duration);
|
| + host_->InitializationComplete();
|
| + return true;
|
| +}
|
| +
|
| +size_t FFmpegDemuxer::GetNumberOfStreams() {
|
| + return streams_.size();
|
| +}
|
| +
|
| +DemuxerStream* FFmpegDemuxer::GetStream(int stream) {
|
| + DCHECK(stream >= 0);
|
| + DCHECK(stream < static_cast<int>(streams_.size()));
|
| + return streams_[stream];
|
| +}
|
| +
|
| +void FFmpegDemuxer::Demux() {
|
| + DCHECK(demuxing_);
|
| +
|
| + // Loop until we've satisfied every stream.
|
| + while (StreamsHavePendingReads()) {
|
| + // Allocate and read an AVPacket from the media.
|
| + scoped_ptr<AVPacket> packet(new AVPacket());
|
| + int result = av_read_frame(format_context_, packet.get());
|
| + if (result < 0) {
|
| + // TODO(scherkus): handle end of stream by marking Buffer with the end of
|
| + // stream flag.
|
| + NOTIMPLEMENTED();
|
| + break;
|
| + }
|
| +
|
| + // Queue the packet with the appropriate stream.
|
| + DCHECK(packet->stream_index >= 0);
|
| + DCHECK(packet->stream_index < static_cast<int>(streams_.size()));
|
| + FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index];
|
| + demuxer_stream->EnqueuePacket(packet.release());
|
| + }
|
| +
|
| + // Finished demuxing.
|
| + demuxing_ = false;
|
| +}
|
| +
|
| +bool FFmpegDemuxer::StreamsHavePendingReads() {
|
| + StreamVector::iterator iter;
|
| + for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
|
| + if ((*iter)->HasPendingReads()) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +} // namespace media
|
|
|