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

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

Issue 10831020: Refactor FFmpegAudioDecoder output timestamp logic. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 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 | Annotate | Revision Log
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_audio_decoder.h" 5 #include "media/filters/ffmpeg_audio_decoder.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/callback_helpers.h" 8 #include "base/callback_helpers.h"
9 #include "media/base/audio_decoder_config.h" 9 #include "media/base/audio_decoder_config.h"
10 #include "media/base/data_buffer.h" 10 #include "media/base/data_buffer.h"
11 #include "media/base/decoder_buffer.h" 11 #include "media/base/decoder_buffer.h"
12 #include "media/base/demuxer.h" 12 #include "media/base/demuxer.h"
13 #include "media/base/pipeline.h" 13 #include "media/base/pipeline.h"
14 #include "media/ffmpeg/ffmpeg_common.h" 14 #include "media/ffmpeg/ffmpeg_common.h"
15 #include "media/filters/ffmpeg_glue.h" 15 #include "media/filters/ffmpeg_glue.h"
16 16
17 namespace media { 17 namespace media {
18 18
19 // Returns true if the decode result was a timestamp packet and not actual audio
20 // data.
21 static inline bool IsTimestampMarkerPacket(int result, Buffer* input) {
22 // We can get a positive result but no decoded data. This is ok because this
23 // this can be a marker packet that only contains timestamp.
24 return result > 0 && !input->IsEndOfStream() &&
25 input->GetTimestamp() != kNoTimestamp() &&
26 input->GetDuration() != kNoTimestamp();
27 }
28
29 // Returns true if the decode result was end of stream. 19 // Returns true if the decode result was end of stream.
30 static inline bool IsEndOfStream(int result, int decoded_size, Buffer* input) { 20 static inline bool IsEndOfStream(int result, int decoded_size, Buffer* input) {
31 // Three conditions to meet to declare end of stream for this decoder: 21 // Three conditions to meet to declare end of stream for this decoder:
32 // 1. FFmpeg didn't read anything. 22 // 1. FFmpeg didn't read anything.
33 // 2. FFmpeg didn't output anything. 23 // 2. FFmpeg didn't output anything.
34 // 3. An end of stream buffer is received. 24 // 3. An end of stream buffer is received.
35 return result == 0 && decoded_size == 0 && input->IsEndOfStream(); 25 return result == 0 && decoded_size == 0 && input->IsEndOfStream();
36 } 26 }
37 27
38
39 FFmpegAudioDecoder::FFmpegAudioDecoder( 28 FFmpegAudioDecoder::FFmpegAudioDecoder(
40 const base::Callback<MessageLoop*()>& message_loop_cb) 29 const base::Callback<MessageLoop*()>& message_loop_cb)
41 : message_loop_factory_cb_(message_loop_cb), 30 : message_loop_factory_cb_(message_loop_cb),
42 message_loop_(NULL), 31 message_loop_(NULL),
43 codec_context_(NULL), 32 codec_context_(NULL),
44 bits_per_channel_(0), 33 bits_per_channel_(0),
45 channel_layout_(CHANNEL_LAYOUT_NONE), 34 channel_layout_(CHANNEL_LAYOUT_NONE),
46 samples_per_second_(0), 35 samples_per_second_(0),
36 bytes_per_frame_(0),
37 total_frames_base_(kNoTimestamp()),
38 total_frames_decoded_(0),
39 last_input_timestamp_(kNoTimestamp()),
47 av_frame_(NULL) { 40 av_frame_(NULL) {
48 } 41 }
49 42
50 void FFmpegAudioDecoder::Initialize( 43 void FFmpegAudioDecoder::Initialize(
51 const scoped_refptr<DemuxerStream>& stream, 44 const scoped_refptr<DemuxerStream>& stream,
52 const PipelineStatusCB& status_cb, 45 const PipelineStatusCB& status_cb,
53 const StatisticsCB& statistics_cb) { 46 const StatisticsCB& statistics_cb) {
54 // Ensure FFmpeg has been initialized 47 // Ensure FFmpeg has been initialized
55 FFmpegGlue::GetInstance(); 48 FFmpegGlue::GetInstance();
56 49
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
139 132
140 status_cb.Run(DECODER_ERROR_NOT_SUPPORTED); 133 status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
141 return; 134 return;
142 } 135 }
143 136
144 // Success! 137 // Success!
145 av_frame_ = avcodec_alloc_frame(); 138 av_frame_ = avcodec_alloc_frame();
146 bits_per_channel_ = config.bits_per_channel(); 139 bits_per_channel_ = config.bits_per_channel();
147 channel_layout_ = config.channel_layout(); 140 channel_layout_ = config.channel_layout();
148 samples_per_second_ = config.samples_per_second(); 141 samples_per_second_ = config.samples_per_second();
149 142 bytes_per_frame_ =
DaleCurtis 2012/07/26 00:40:16 codec_context_->channels is also available.
acolwell GONE FROM CHROMIUM 2012/07/26 01:21:34 Done.
143 ChannelLayoutToChannelCount(channel_layout_) * bits_per_channel_ / 8;
150 status_cb.Run(PIPELINE_OK); 144 status_cb.Run(PIPELINE_OK);
151 } 145 }
152 146
153 void FFmpegAudioDecoder::DoReset(const base::Closure& closure) { 147 void FFmpegAudioDecoder::DoReset(const base::Closure& closure) {
154 avcodec_flush_buffers(codec_context_); 148 avcodec_flush_buffers(codec_context_);
155 estimated_next_timestamp_ = kNoTimestamp(); 149 total_frames_base_ = kNoTimestamp();
150 total_frames_decoded_ = 0;
151 last_input_timestamp_ = kNoTimestamp();
156 closure.Run(); 152 closure.Run();
157 } 153 }
158 154
159 void FFmpegAudioDecoder::DoRead(const ReadCB& read_cb) { 155 void FFmpegAudioDecoder::DoRead(const ReadCB& read_cb) {
160 DCHECK_EQ(MessageLoop::current(), message_loop_); 156 DCHECK_EQ(MessageLoop::current(), message_loop_);
161 DCHECK(!read_cb.is_null()); 157 DCHECK(!read_cb.is_null());
162 CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported."; 158 CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
163 159
164 read_cb_ = read_cb; 160 read_cb_ = read_cb;
165 ReadFromDemuxerStream(); 161 ReadFromDemuxerStream();
166 } 162 }
167 163
168 void FFmpegAudioDecoder::DoDecodeBuffer( 164 void FFmpegAudioDecoder::DoDecodeBuffer(
169 DemuxerStream::Status status, 165 DemuxerStream::Status status,
170 const scoped_refptr<DecoderBuffer>& input) { 166 const scoped_refptr<DecoderBuffer>& input) {
171 DCHECK_EQ(MessageLoop::current(), message_loop_); 167 DCHECK_EQ(MessageLoop::current(), message_loop_);
172 DCHECK(!read_cb_.is_null()); 168 DCHECK(!read_cb_.is_null());
173 169
174 if (status != DemuxerStream::kOk) { 170 if (status != DemuxerStream::kOk) {
175 DCHECK(!input); 171 DCHECK(!input);
176 // TODO(acolwell): Add support for reinitializing the decoder when 172 // TODO(acolwell): Add support for reinitializing the decoder when
177 // |status| == kConfigChanged. For now we just trigger a decode error. 173 // |status| == kConfigChanged. For now we just trigger a decode error.
178 AudioDecoder::Status decoder_status = 174 AudioDecoder::Status decoder_status =
179 (status == DemuxerStream::kAborted) ? kAborted : kDecodeError; 175 (status == DemuxerStream::kAborted) ? kAborted : kDecodeError;
180 base::ResetAndReturn(&read_cb_).Run(decoder_status, NULL); 176 base::ResetAndReturn(&read_cb_).Run(decoder_status, NULL);
181 return; 177 return;
182 } 178 }
183 179
184 // FFmpeg tends to seek Ogg audio streams in the middle of nowhere, giving us 180 // Make sure we are notified if http://crbug.com/49709 returns.
185 // a whole bunch of AV_NOPTS_VALUE packets. Discard them until we find 181 CHECK(input->GetTimestamp() != kNoTimestamp() ||
DaleCurtis 2012/07/26 00:40:16 DCHECK, CHECK is a bit heavy for a decoding loop,
acolwell GONE FROM CHROMIUM 2012/07/26 01:21:34 Why? The old code produces incorrect behavior and/
DaleCurtis 2012/07/26 01:49:16 Fair enough, it happens on all content when it doe
186 // something valid. Refer to http://crbug.com/49709 182 total_frames_base_ != kNoTimestamp() ||
187 if (input->GetTimestamp() == kNoTimestamp() && 183 input->IsEndOfStream());
188 estimated_next_timestamp_ == kNoTimestamp() && 184
189 !input->IsEndOfStream()) { 185 if (!input->IsEndOfStream()) {
190 ReadFromDemuxerStream(); 186 if (last_input_timestamp_ != kNoTimestamp() &&
191 return; 187 input->GetTimestamp() < last_input_timestamp_) {
188 DVLOG(1) << "Input timestamps are not monotonically increasing! "
189 << " ts " << input->GetTimestamp().InSecondsF()
DaleCurtis 2012/07/26 00:40:16 Why not InMillisecondsF ? Are they generally O(sec
acolwell GONE FROM CHROMIUM 2012/07/26 01:21:34 I just wanted the timestamp and difference to be i
scherkus (not reviewing) 2012/07/26 01:43:08 I usually do InMicroseconds() myself! (see FFmpegV
acolwell GONE FROM CHROMIUM 2012/07/27 20:45:29 ok. I'll use microseconds to match the FFmpegVideo
190 << " diff "
191 << (input->GetTimestamp() - last_input_timestamp_).InSecondsF();
192 base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
scherkus (not reviewing) 2012/07/26 01:43:08 I'd be interested in knowing if/when this happens
acolwell GONE FROM CHROMIUM 2012/07/27 20:45:29 This is intended to smoke out content that violate
193 return;
194 }
195 last_input_timestamp_ = input->GetTimestamp();
192 } 196 }
193 197
194 AVPacket packet; 198 AVPacket packet;
195 av_init_packet(&packet); 199 av_init_packet(&packet);
196 packet.data = const_cast<uint8*>(input->GetData()); 200 packet.data = const_cast<uint8*>(input->GetData());
197 packet.size = input->GetDataSize(); 201 packet.size = input->GetDataSize();
198 202
199 PipelineStatistics statistics; 203 PipelineStatistics statistics;
200 statistics.audio_bytes_decoded = input->GetDataSize(); 204 statistics.audio_bytes_decoded = input->GetDataSize();
201 205
(...skipping 12 matching lines...) Expand all
214 218
215 DLOG(ERROR) << "Error decoding an audio frame with timestamp: " 219 DLOG(ERROR) << "Error decoding an audio frame with timestamp: "
216 << input->GetTimestamp().InMicroseconds() << " us, duration: " 220 << input->GetTimestamp().InMicroseconds() << " us, duration: "
217 << input->GetDuration().InMicroseconds() << " us, packet size: " 221 << input->GetDuration().InMicroseconds() << " us, packet size: "
218 << input->GetDataSize() << " bytes"; 222 << input->GetDataSize() << " bytes";
219 223
220 ReadFromDemuxerStream(); 224 ReadFromDemuxerStream();
221 return; 225 return;
222 } 226 }
223 227
228 if (result > 0)
229 DCHECK_EQ(result, input->GetDataSize());
230
231 if (total_frames_base_ == kNoTimestamp()) {
232 DCHECK(input->GetTimestamp() != kNoTimestamp());
scherkus (not reviewing) 2012/07/26 01:43:08 somewhat of a longshot, but what if we seeked to t
acolwell GONE FROM CHROMIUM 2012/07/27 20:45:29 Good catch! Added IsEndOfStream() check.
233 total_frames_base_ = input->GetTimestamp();
234 }
235
224 int decoded_audio_size = 0; 236 int decoded_audio_size = 0;
225 if (frame_decoded) { 237 if (frame_decoded) {
226 int output_sample_rate = av_frame_->sample_rate; 238 int output_sample_rate = av_frame_->sample_rate;
227 if (output_sample_rate != samples_per_second_) { 239 if (output_sample_rate != samples_per_second_) {
228 DLOG(ERROR) << "Output sample rate (" << output_sample_rate 240 DLOG(ERROR) << "Output sample rate (" << output_sample_rate
229 << ") doesn't match expected rate " << samples_per_second_; 241 << ") doesn't match expected rate " << samples_per_second_;
230 base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); 242 base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
231 return; 243 return;
232 } 244 }
233 245
234 decoded_audio_size = av_samples_get_buffer_size( 246 decoded_audio_size = av_samples_get_buffer_size(
235 NULL, codec_context_->channels, av_frame_->nb_samples, 247 NULL, codec_context_->channels, av_frame_->nb_samples,
236 codec_context_->sample_fmt, 1); 248 codec_context_->sample_fmt, 1);
237 } 249 }
238 250
239 scoped_refptr<DataBuffer> output; 251 scoped_refptr<DataBuffer> output;
240 252
241 if (decoded_audio_size > 0) { 253 if (decoded_audio_size > 0) {
254 DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0)
DaleCurtis 2012/07/26 00:40:16 Is this true for all the codecs we support? I saw
acolwell GONE FROM CHROMIUM 2012/07/26 01:21:34 If this isn't true then I'm pretty sure other code
scherkus (not reviewing) 2012/07/26 01:43:08 if it isn't I'd like to know about it :)
255 << "Decoder didn't output full frames";
256
242 // Copy the audio samples into an output buffer. 257 // Copy the audio samples into an output buffer.
243 output = new DataBuffer(decoded_audio_size); 258 output = new DataBuffer(decoded_audio_size);
244 output->SetDataSize(decoded_audio_size); 259 output->SetDataSize(decoded_audio_size);
245 uint8* data = output->GetWritableData(); 260 uint8* data = output->GetWritableData();
246 memcpy(data, av_frame_->data[0], decoded_audio_size); 261 memcpy(data, av_frame_->data[0], decoded_audio_size);
247 262
248 UpdateDurationAndTimestamp(input, output); 263 base::TimeDelta timestamp = GetNextOutputTimestamp();
249 } else if (IsTimestampMarkerPacket(result, input)) { 264 total_frames_decoded_ += decoded_audio_size / bytes_per_frame_;
250 // Nothing else to do here but update our estimation. 265
251 estimated_next_timestamp_ = input->GetTimestamp() + input->GetDuration(); 266 output->SetTimestamp(timestamp);
267 output->SetDuration(GetNextOutputTimestamp() - timestamp);
252 } else if (IsEndOfStream(result, decoded_audio_size, input)) { 268 } else if (IsEndOfStream(result, decoded_audio_size, input)) {
253 // Create an end of stream output buffer. 269 // Create an end of stream output buffer.
254 output = new DataBuffer(0); 270 output = new DataBuffer(0);
255 output->SetTimestamp(input->GetTimestamp()); 271 output->SetTimestamp(GetNextOutputTimestamp());
256 output->SetDuration(input->GetDuration());
257 } 272 }
258 273
259 // Decoding finished successfully, update stats and execute callback. 274 // Decoding finished successfully, update stats and execute callback.
260 statistics_cb_.Run(statistics); 275 statistics_cb_.Run(statistics);
261 if (output) 276 if (output)
262 base::ResetAndReturn(&read_cb_).Run(kOk, output); 277 base::ResetAndReturn(&read_cb_).Run(kOk, output);
263 else 278 else
264 ReadFromDemuxerStream(); 279 ReadFromDemuxerStream();
265 } 280 }
266 281
267 void FFmpegAudioDecoder::ReadFromDemuxerStream() { 282 void FFmpegAudioDecoder::ReadFromDemuxerStream() {
268 DCHECK(!read_cb_.is_null()); 283 DCHECK(!read_cb_.is_null());
269 284
270 demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::DecodeBuffer, this)); 285 demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::DecodeBuffer, this));
271 } 286 }
272 287
273 void FFmpegAudioDecoder::DecodeBuffer( 288 void FFmpegAudioDecoder::DecodeBuffer(
274 DemuxerStream::Status status, 289 DemuxerStream::Status status,
275 const scoped_refptr<DecoderBuffer>& buffer) { 290 const scoped_refptr<DecoderBuffer>& buffer) {
276 DCHECK_EQ(status != DemuxerStream::kOk, !buffer) << status; 291 DCHECK_EQ(status != DemuxerStream::kOk, !buffer) << status;
277 292
278 // TODO(scherkus): fix FFmpegDemuxerStream::Read() to not execute our read 293 // TODO(scherkus): fix FFmpegDemuxerStream::Read() to not execute our read
279 // callback on the same execution stack so we can get rid of forced task post. 294 // callback on the same execution stack so we can get rid of forced task post.
280 message_loop_->PostTask(FROM_HERE, base::Bind( 295 message_loop_->PostTask(FROM_HERE, base::Bind(
281 &FFmpegAudioDecoder::DoDecodeBuffer, this, status, buffer)); 296 &FFmpegAudioDecoder::DoDecodeBuffer, this, status, buffer));
282 } 297 }
283 298
284 void FFmpegAudioDecoder::UpdateDurationAndTimestamp( 299 base::TimeDelta FFmpegAudioDecoder::GetNextOutputTimestamp() const {
285 const Buffer* input, 300 DCHECK(total_frames_base_ != kNoTimestamp());
DaleCurtis 2012/07/26 00:40:16 DCHECK_NE ?
acolwell GONE FROM CHROMIUM 2012/07/26 01:21:34 Compiler gets angry because there isn't a << opera
286 DataBuffer* output) { 301 int64 decoded_seconds = total_frames_decoded_ / samples_per_second_;
287 // Always calculate duration based on the actual number of samples decoded. 302 int64 fraction_of_a_second = total_frames_decoded_ % samples_per_second_;
288 base::TimeDelta duration = CalculateDuration(output->GetDataSize()); 303 int64 decoded_microseconds =
DaleCurtis 2012/07/26 00:40:16 Is int64 precise enough for this? Why not double?
acolwell GONE FROM CHROMIUM 2012/07/26 01:21:34 This is precise for microsecond resolution which i
289 output->SetDuration(duration); 304 1000000 * fraction_of_a_second / samples_per_second_;
290 305 return total_frames_base_ + base::TimeDelta::FromSeconds(decoded_seconds)
291 // Use the incoming timestamp if it's valid. 306 + base::TimeDelta::FromMicroseconds(decoded_microseconds);
292 if (input->GetTimestamp() != kNoTimestamp()) {
293 output->SetTimestamp(input->GetTimestamp());
294 estimated_next_timestamp_ = input->GetTimestamp() + duration;
295 return;
296 }
297
298 // Otherwise use an estimated timestamp and attempt to update the estimation
299 // as long as it's valid.
300 output->SetTimestamp(estimated_next_timestamp_);
301 if (estimated_next_timestamp_ != kNoTimestamp()) {
302 estimated_next_timestamp_ += duration;
303 }
304 } 307 }
305
306 base::TimeDelta FFmpegAudioDecoder::CalculateDuration(int size) {
307 int64 denominator = ChannelLayoutToChannelCount(channel_layout_) *
308 bits_per_channel_ / 8 * samples_per_second_;
309 double microseconds = size /
310 (denominator / static_cast<double>(base::Time::kMicrosecondsPerSecond));
311 return base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds));
312 }
313
314 } // namespace media 308 } // namespace media
OLDNEW
« media/filters/ffmpeg_audio_decoder.h ('K') | « media/filters/ffmpeg_audio_decoder.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698