diff options
-rw-r--r-- | DEPS | 4 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_common.cc | 5 | ||||
-rw-r--r-- | media/filters/audio_renderer_impl.cc | 18 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer.cc | 13 | ||||
-rw-r--r-- | media/filters/opus_audio_decoder.cc | 195 | ||||
-rw-r--r-- | media/filters/opus_audio_decoder.h | 11 | ||||
-rw-r--r-- | media/media.gyp | 4 |
7 files changed, 141 insertions, 109 deletions
@@ -36,8 +36,8 @@ vars = { # These two FFmpeg variables must be updated together. One is used for SVN # checkouts and the other for Git checkouts. - "ffmpeg_revision": "240211", - "ffmpeg_hash": "8dc45cbcad763762bb679de280bdc584f35aa22f", + "ffmpeg_revision": "240434", + "ffmpeg_hash": "c6c988923be6b8fd1f381522d478813e14505ce2", "sfntly_revision": "228", "lighttpd_revision": "33737", diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc index 40696c6..6e7bd15 100644 --- a/media/ffmpeg/ffmpeg_common.cc +++ b/media/ffmpeg/ffmpeg_common.cc @@ -286,8 +286,9 @@ static void AVCodecContextToAudioDecoderConfig( if (codec == kCodecOpus) { // |codec_context->sample_fmt| is not set by FFmpeg because Opus decoding is - // not enabled in FFmpeg, so we need to manually set the sample format. - sample_format = kSampleFormatS16; + // not enabled in FFmpeg. It doesn't matter what value is set here, so long + // as it's valid, the true sample format is selected inside the decoder. + sample_format = kSampleFormatF32; } base::TimeDelta seek_preroll; diff --git a/media/filters/audio_renderer_impl.cc b/media/filters/audio_renderer_impl.cc index fdfb45d..fc5b7bf 100644 --- a/media/filters/audio_renderer_impl.cc +++ b/media/filters/audio_renderer_impl.cc @@ -399,8 +399,22 @@ bool AudioRendererImpl::HandleSplicerBuffer( if (state_ == kUnderflow || state_ == kRebuffering) ChangeState_Locked(kPlaying); } else { - if (state_ == kPrerolling && IsBeforePrerollTime(buffer)) - return true; + if (state_ == kPrerolling) { + if (IsBeforePrerollTime(buffer)) + return true; + + // Trim off any additional time before the preroll timestamp. + const base::TimeDelta trim_time = + preroll_timestamp_ - buffer->timestamp(); + if (trim_time > base::TimeDelta()) { + buffer->TrimStart(buffer->frame_count() * + (static_cast<double>(trim_time.InMicroseconds()) / + buffer->duration().InMicroseconds())); + } + // If the entire buffer was trimmed, request a new one. + if (!buffer->frame_count()) + return true; + } if (state_ != kUninitialized && state_ != kStopped) algorithm_->EnqueueBuffer(buffer); diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc index e47c59e..6b80271 100644 --- a/media/filters/ffmpeg_demuxer.cc +++ b/media/filters/ffmpeg_demuxer.cc @@ -845,6 +845,19 @@ void FFmpegDemuxer::OnReadFrameDone(ScopedAVPacket packet, int result) { 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()); } diff --git a/media/filters/opus_audio_decoder.cc b/media/filters/opus_audio_decoder.cc index 37e1abd..e356720 100644 --- a/media/filters/opus_audio_decoder.cc +++ b/media/filters/opus_audio_decoder.cc @@ -25,7 +25,6 @@ namespace media { static uint16 ReadLE16(const uint8* data, size_t data_size, int read_offset) { - DCHECK(data); uint16 value = 0; DCHECK_LE(read_offset + sizeof(value), data_size); memcpy(&value, data + read_offset, sizeof(value)); @@ -46,15 +45,8 @@ static int TimeDeltaToAudioFrames(base::TimeDelta time_delta, // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html static const int kMaxVorbisChannels = 8; -// Opus allows for decode of S16 or float samples. OpusAudioDecoder always uses -// S16 samples. -static const int kBitsPerChannel = 16; -static const int kBytesPerChannel = kBitsPerChannel / 8; - // Maximum packet size used in Xiph's opusdec and FFmpeg's libopusdec. -static const int kMaxOpusOutputPacketSizeSamples = 960 * 6 * kMaxVorbisChannels; -static const int kMaxOpusOutputPacketSizeBytes = - kMaxOpusOutputPacketSizeSamples * kBytesPerChannel; +static const int kMaxOpusOutputPacketSizeSamples = 960 * 6; static void RemapOpusChannelLayout(const uint8* opus_mapping, int num_channels, @@ -206,13 +198,16 @@ struct OpusExtraData { static bool ParseOpusExtraData(const uint8* data, int data_size, const AudioDecoderConfig& config, OpusExtraData* extra_data) { - if (data_size < kOpusExtraDataSize) + if (data_size < kOpusExtraDataSize) { + DLOG(ERROR) << "Extra data size is too small:" << data_size; return false; + } extra_data->channels = *(data + kOpusExtraDataChannelsOffset); if (extra_data->channels <= 0 || extra_data->channels > kMaxVorbisChannels) { - DVLOG(0) << "invalid channel count in extra data: " << extra_data->channels; + DLOG(ERROR) << "invalid channel count in extra data: " + << extra_data->channels; return false; } @@ -223,7 +218,7 @@ static bool ParseOpusExtraData(const uint8* data, int data_size, if (!extra_data->channel_mapping) { if (extra_data->channels > kMaxChannelsWithDefaultLayout) { - DVLOG(0) << "Invalid extra data, missing stream map."; + DLOG(ERROR) << "Invalid extra data, missing stream map."; return false; } @@ -234,8 +229,8 @@ static bool ParseOpusExtraData(const uint8* data, int data_size, } if (data_size < kOpusExtraDataStreamMapOffset + extra_data->channels) { - DVLOG(0) << "Invalid stream map; insufficient data for current channel " - << "count: " << extra_data->channels; + DLOG(ERROR) << "Invalid stream map; insufficient data for current channel " + << "count: " << extra_data->channels; return false; } @@ -256,12 +251,14 @@ OpusAudioDecoder::OpusAudioDecoder( weak_factory_(this), demuxer_stream_(NULL), opus_decoder_(NULL), - bits_per_channel_(0), channel_layout_(CHANNEL_LAYOUT_NONE), samples_per_second_(0), + sample_format_(kSampleFormatF32), + bits_per_channel_(SampleFormatToBytesPerChannel(sample_format_) * 8), last_input_timestamp_(kNoTimestamp()), frames_to_discard_(0), - frame_delay_at_start_(0) { + frame_delay_at_start_(0), + start_input_timestamp_(kNoTimestamp()) { } void OpusAudioDecoder::Initialize( @@ -274,7 +271,7 @@ void OpusAudioDecoder::Initialize( if (demuxer_stream_) { // TODO(scherkus): initialization currently happens more than once in // PipelineIntegrationTest.BasicPlayback. - DVLOG(0) << "Initialize has already been called."; + DLOG(ERROR) << "Initialize has already been called."; CHECK(false); } @@ -375,7 +372,7 @@ void OpusAudioDecoder::BufferReady( // occurs with some damaged files. if (input->timestamp() == kNoTimestamp() && output_timestamp_helper_->base_timestamp() == kNoTimestamp()) { - DVLOG(1) << "Received a buffer without timestamps!"; + DLOG(ERROR) << "Received a buffer without timestamps!"; base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); return; } @@ -384,13 +381,21 @@ void OpusAudioDecoder::BufferReady( input->timestamp() != kNoTimestamp() && input->timestamp() < last_input_timestamp_) { base::TimeDelta diff = input->timestamp() - last_input_timestamp_; - DVLOG(1) << "Input timestamps are not monotonically increasing! " - << " ts " << input->timestamp().InMicroseconds() << " us" - << " diff " << diff.InMicroseconds() << " us"; + DLOG(ERROR) << "Input timestamps are not monotonically increasing! " + << " ts " << input->timestamp().InMicroseconds() << " us" + << " diff " << diff.InMicroseconds() << " us"; base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); return; } + // Apply the necessary codec delay. + if (start_input_timestamp_ == kNoTimestamp()) + start_input_timestamp_ = input->timestamp(); + if (last_input_timestamp_ == kNoTimestamp() && + input->timestamp() == start_input_timestamp_) { + frames_to_discard_ = frame_delay_at_start_; + } + last_input_timestamp_ = input->timestamp(); scoped_refptr<AudioBuffer> output_buffer; @@ -414,53 +419,41 @@ bool OpusAudioDecoder::ConfigureDecoder() { const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config(); if (config.codec() != kCodecOpus) { - DVLOG(0) << "codec must be kCodecOpus."; + DVLOG(1) << "Codec must be kCodecOpus."; return false; } const int channel_count = ChannelLayoutToChannelCount(config.channel_layout()); if (!config.IsValidConfig() || channel_count > kMaxVorbisChannels) { - DVLOG(0) << "Invalid or unsupported audio stream -" - << " codec: " << config.codec() - << " channel count: " << channel_count - << " channel layout: " << config.channel_layout() - << " bits per channel: " << config.bits_per_channel() - << " samples per second: " << config.samples_per_second(); - return false; - } - - if (config.bits_per_channel() != kBitsPerChannel) { - DVLOG(0) << "16 bit samples required."; + DLOG(ERROR) << "Invalid or unsupported audio stream -" + << " codec: " << config.codec() + << " channel count: " << channel_count + << " channel layout: " << config.channel_layout() + << " bits per channel: " << config.bits_per_channel() + << " samples per second: " << config.samples_per_second(); return false; } if (config.is_encrypted()) { - DVLOG(0) << "Encrypted audio stream not supported."; + DLOG(ERROR) << "Encrypted audio stream not supported."; return false; } if (opus_decoder_ && - (bits_per_channel_ != config.bits_per_channel() || - channel_layout_ != config.channel_layout() || + (channel_layout_ != config.channel_layout() || samples_per_second_ != config.samples_per_second())) { - DVLOG(1) << "Unsupported config change :"; - DVLOG(1) << "\tbits_per_channel : " << bits_per_channel_ - << " -> " << config.bits_per_channel(); - DVLOG(1) << "\tchannel_layout : " << channel_layout_ - << " -> " << config.channel_layout(); - DVLOG(1) << "\tsample_rate : " << samples_per_second_ - << " -> " << config.samples_per_second(); + DLOG(ERROR) << "Unsupported config change -" + << ", channel_layout: " << channel_layout_ + << " -> " << config.channel_layout() + << ", sample_rate: " << samples_per_second_ + << " -> " << config.samples_per_second(); return false; } // Clean up existing decoder if necessary. CloseDecoder(); - // Allocate the output buffer if necessary. - if (!output_buffer_) - output_buffer_.reset(new int16[kMaxOpusOutputPacketSizeSamples]); - // Parse the Opus Extra Data. OpusExtraData opus_extra_data; if (!ParseOpusExtraData(config.extra_data(), config.extra_data_size(), @@ -468,24 +461,23 @@ bool OpusAudioDecoder::ConfigureDecoder() { &opus_extra_data)) return false; - if (!config.codec_delay().InMicroseconds()) - return false; - // Convert from seconds to samples. timestamp_offset_ = config.codec_delay(); frame_delay_at_start_ = TimeDeltaToAudioFrames(config.codec_delay(), config.samples_per_second()); - if (frame_delay_at_start_ < 0) { - DVLOG(1) << "Invalid file. Incorrect value for codec delay."; + if (timestamp_offset_ <= base::TimeDelta() || frame_delay_at_start_ < 0) { + DLOG(ERROR) << "Invalid file. Incorrect value for codec delay: " + << config.codec_delay().InMicroseconds(); return false; } + if (frame_delay_at_start_ != opus_extra_data.skip_samples) { - DVLOG(1) << "Invalid file. Codec Delay in container does not match the " - << "value in Opus Extra Data."; + DLOG(ERROR) << "Invalid file. Codec Delay in container does not match the " + << "value in Opus Extra Data."; return false; } - uint8 channel_mapping[kMaxVorbisChannels]; + uint8 channel_mapping[kMaxVorbisChannels] = {0}; memcpy(&channel_mapping, kDefaultOpusChannelLayout, kMaxChannelsWithDefaultLayout); @@ -505,16 +497,16 @@ bool OpusAudioDecoder::ConfigureDecoder() { channel_mapping, &status); if (!opus_decoder_ || status != OPUS_OK) { - DVLOG(0) << "opus_multistream_decoder_create failed status=" - << opus_strerror(status); + DLOG(ERROR) << "opus_multistream_decoder_create failed status=" + << opus_strerror(status); return false; } - bits_per_channel_ = config.bits_per_channel(); channel_layout_ = config.channel_layout(); samples_per_second_ = config.samples_per_second(); output_timestamp_helper_.reset( new AudioTimestampHelper(config.samples_per_second())); + start_input_timestamp_ = kNoTimestamp(); return true; } @@ -535,68 +527,68 @@ void OpusAudioDecoder::ResetTimestampState() { bool OpusAudioDecoder::Decode(const scoped_refptr<DecoderBuffer>& input, scoped_refptr<AudioBuffer>* output_buffer) { - int frames_decoded = opus_multistream_decode(opus_decoder_, - input->data(), - input->data_size(), - &output_buffer_[0], - kMaxOpusOutputPacketSizeSamples, - 0); + // Allocate a buffer for the output samples. + *output_buffer = AudioBuffer::CreateBuffer( + sample_format_, + ChannelLayoutToChannelCount(channel_layout_), + kMaxOpusOutputPacketSizeSamples); + const int buffer_size = + output_buffer->get()->channel_count() * + output_buffer->get()->frame_count() * + SampleFormatToBytesPerChannel(sample_format_); + + float* float_output_buffer = reinterpret_cast<float*>( + output_buffer->get()->channel_data()[0]); + const int frames_decoded = + opus_multistream_decode_float(opus_decoder_, + input->data(), + input->data_size(), + float_output_buffer, + buffer_size, + 0); + if (frames_decoded < 0) { - DVLOG(0) << "opus_multistream_decode failed for" - << " timestamp: " << input->timestamp().InMicroseconds() - << " us, duration: " << input->duration().InMicroseconds() - << " us, packet size: " << input->data_size() << " bytes with" - << " status: " << opus_strerror(frames_decoded); + DLOG(ERROR) << "opus_multistream_decode failed for" + << " timestamp: " << input->timestamp().InMicroseconds() + << " us, duration: " << input->duration().InMicroseconds() + << " us, packet size: " << input->data_size() << " bytes with" + << " status: " << opus_strerror(frames_decoded); return false; } - uint8* decoded_audio_data = reinterpret_cast<uint8*>(&output_buffer_[0]); - int bytes_decoded = frames_decoded * - demuxer_stream_->audio_decoder_config().bytes_per_frame(); - DCHECK_LE(bytes_decoded, kMaxOpusOutputPacketSizeBytes); - if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() && !input->end_of_stream()) { DCHECK(input->timestamp() != kNoTimestamp()); output_timestamp_helper_->SetBaseTimestamp(input->timestamp()); } - // Skip samples should be equal to codec delay when the file starts and when - // there is a seek to zero. - // TODO(vigneshv): This should be checked for start of stream rather than - // input timestamp of zero to accomodate streams that don't start at zero. - if (input->timestamp() == base::TimeDelta()) - frames_to_discard_ = frame_delay_at_start_; + // Trim off any extraneous allocation. + DCHECK_LE(frames_decoded, output_buffer->get()->frame_count()); + const int trim_frames = output_buffer->get()->frame_count() - frames_decoded; + if (trim_frames > 0) + output_buffer->get()->TrimEnd(trim_frames); - if (bytes_decoded > 0 && frames_decoded > frames_to_discard_) { - // Copy the audio samples into an output buffer. - uint8* data[] = { decoded_audio_data }; - *output_buffer = AudioBuffer::CopyFrom( - kSampleFormatS16, - ChannelLayoutToChannelCount(channel_layout_), - frames_decoded, - data, - output_timestamp_helper_->GetTimestamp() - timestamp_offset_, - output_timestamp_helper_->GetFrameDuration(frames_decoded)); - output_timestamp_helper_->AddFrames(frames_decoded); + // Handle frame discard and trimming. + int frames_to_output = frames_decoded; + if (frames_decoded > frames_to_discard_) { if (frames_to_discard_ > 0) { output_buffer->get()->TrimStart(frames_to_discard_); - frames_decoded -= frames_to_discard_; + frames_to_output -= frames_to_discard_; frames_to_discard_ = 0; } if (input->discard_padding().InMicroseconds() > 0) { int discard_padding = TimeDeltaToAudioFrames(input->discard_padding(), samples_per_second_); - if (discard_padding < 0 || discard_padding > frames_decoded) { + if (discard_padding < 0 || discard_padding > frames_to_output) { DVLOG(1) << "Invalid file. Incorrect discard padding value."; return false; } output_buffer->get()->TrimEnd(discard_padding); - frames_decoded -= discard_padding; + frames_to_output -= discard_padding; } - } else if (bytes_decoded > 0) { - frames_to_discard_ -= frames_decoded; - frames_decoded = 0; + } else { + frames_to_discard_ -= frames_to_output; + frames_to_output = 0; } // Decoding finished successfully, update statistics. @@ -604,8 +596,15 @@ bool OpusAudioDecoder::Decode(const scoped_refptr<DecoderBuffer>& input, statistics.audio_bytes_decoded = input->data_size(); statistics_cb_.Run(statistics); + // Assign timestamp and duration to the buffer. + output_buffer->get()->set_timestamp( + output_timestamp_helper_->GetTimestamp() - timestamp_offset_); + output_buffer->get()->set_duration( + output_timestamp_helper_->GetFrameDuration(frames_to_output)); + output_timestamp_helper_->AddFrames(frames_decoded); + // Discard the buffer to indicate we need more data. - if (!frames_decoded) + if (!frames_to_output) *output_buffer = NULL; return true; diff --git a/media/filters/opus_audio_decoder.h b/media/filters/opus_audio_decoder.h index 50ba069..982458b 100644 --- a/media/filters/opus_audio_decoder.h +++ b/media/filters/opus_audio_decoder.h @@ -10,6 +10,7 @@ #include "base/time/time.h" #include "media/base/audio_decoder.h" #include "media/base/demuxer_stream.h" +#include "media/base/sample_format.h" struct OpusMSDecoder; @@ -62,9 +63,10 @@ class MEDIA_EXPORT OpusAudioDecoder : public AudioDecoder { OpusMSDecoder* opus_decoder_; // Decoded audio format. - int bits_per_channel_; ChannelLayout channel_layout_; int samples_per_second_; + const SampleFormat sample_format_; + const int bits_per_channel_; // Used for computing output timestamps. scoped_ptr<AudioTimestampHelper> output_timestamp_helper_; @@ -80,16 +82,15 @@ class MEDIA_EXPORT OpusAudioDecoder : public AudioDecoder { int frames_to_discard_; // Number of frames to be discarded at the start of the stream. This value - // is typically the CodecDelay value from the container. + // is typically the CodecDelay value from the container. This value should + // only be applied when input timestamp is |start_input_timestamp_|. int frame_delay_at_start_; + base::TimeDelta start_input_timestamp_; // Timestamp to be subtracted from all the frames. This is typically computed // from the CodecDelay value in the container. base::TimeDelta timestamp_offset_; - // Buffer for output from libopus. - scoped_ptr<int16[]> output_buffer_; - DISALLOW_IMPLICIT_CONSTRUCTORS(OpusAudioDecoder); }; diff --git a/media/media.gyp b/media/media.gyp index fe3b797..97e2f5d 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -560,6 +560,10 @@ 'base/media.h', 'base/media_stub.cc', ], + 'sources!': [ + 'filters/opus_audio_decoder.cc', + 'filters/opus_audio_decoder.h', + ], 'conditions': [ ['android_webview_build==0', { 'dependencies': [ |