OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "media/base/data_buffer.h" | 8 #include "media/base/data_buffer.h" |
| 9 #include "media/base/demuxer.h" |
| 10 #include "media/base/filter_host.h" |
8 #include "media/base/limits.h" | 11 #include "media/base/limits.h" |
9 #include "media/ffmpeg/ffmpeg_common.h" | 12 #include "media/ffmpeg/ffmpeg_common.h" |
10 #include "media/filters/ffmpeg_demuxer.h" | |
11 | |
12 #if !defined(USE_SSE) | |
13 #if defined(__SSE__) || defined(ARCH_CPU_X86_64) || _M_IX86_FP==1 | |
14 #define USE_SSE 1 | |
15 #else | |
16 #define USE_SSE 0 | |
17 #endif | |
18 #endif | |
19 #if USE_SSE | |
20 #include <xmmintrin.h> | |
21 #endif | |
22 | 13 |
23 namespace media { | 14 namespace media { |
24 | 15 |
25 // Size of the decoded audio buffer. | 16 // Returns true if the decode result was an error. |
26 const size_t FFmpegAudioDecoder::kOutputBufferSize = | 17 static bool IsErrorResult(int result, int decoded_size) { |
27 AVCODEC_MAX_AUDIO_FRAME_SIZE; | 18 return result < 0 || |
| 19 decoded_size < 0 || |
| 20 decoded_size > AVCODEC_MAX_AUDIO_FRAME_SIZE; |
| 21 } |
| 22 |
| 23 // Returns true if the decode result produced audio samples. |
| 24 static bool ProducedAudioSamples(int decoded_size) { |
| 25 return decoded_size > 0; |
| 26 } |
| 27 |
| 28 // Returns true if the decode result was a timestamp packet and not actual audio |
| 29 // data. |
| 30 static bool IsTimestampMarkerPacket(int result, Buffer* input) { |
| 31 // We can get a positive result but no decoded data. This is ok because this |
| 32 // this can be a marker packet that only contains timestamp. |
| 33 return result > 0 && !input->IsEndOfStream() && |
| 34 input->GetTimestamp() != kNoTimestamp && |
| 35 input->GetDuration() != kNoTimestamp; |
| 36 } |
| 37 |
| 38 // Returns true if the decode result was end of stream. |
| 39 static bool IsEndOfStream(int result, int decoded_size, Buffer* input) { |
| 40 // Three conditions to meet to declare end of stream for this decoder: |
| 41 // 1. FFmpeg didn't read anything. |
| 42 // 2. FFmpeg didn't output anything. |
| 43 // 3. An end of stream buffer is received. |
| 44 return result == 0 && decoded_size == 0 && input->IsEndOfStream(); |
| 45 } |
| 46 |
28 | 47 |
29 FFmpegAudioDecoder::FFmpegAudioDecoder(MessageLoop* message_loop) | 48 FFmpegAudioDecoder::FFmpegAudioDecoder(MessageLoop* message_loop) |
30 : DecoderBase<AudioDecoder, Buffer>(message_loop), | 49 : message_loop_(message_loop), |
31 codec_context_(NULL), | 50 codec_context_(NULL), |
32 bits_per_channel_(0), | 51 bits_per_channel_(0), |
33 channel_layout_(CHANNEL_LAYOUT_NONE), | 52 channel_layout_(CHANNEL_LAYOUT_NONE), |
34 sample_rate_(0), | 53 sample_rate_(0), |
35 estimated_next_timestamp_(kNoTimestamp) { | 54 decoded_audio_size_(AVCODEC_MAX_AUDIO_FRAME_SIZE), |
| 55 decoded_audio_(static_cast<uint8*>(av_malloc(decoded_audio_size_))), |
| 56 pending_reads_(0) { |
36 } | 57 } |
37 | 58 |
38 FFmpegAudioDecoder::~FFmpegAudioDecoder() { | 59 FFmpegAudioDecoder::~FFmpegAudioDecoder() { |
| 60 av_free(decoded_audio_); |
39 } | 61 } |
40 | 62 |
41 void FFmpegAudioDecoder::DoInitialize(DemuxerStream* demuxer_stream, | 63 void FFmpegAudioDecoder::Flush(FilterCallback* callback) { |
42 bool* success, | 64 message_loop_->PostTask( |
43 Task* done_cb) { | 65 FROM_HERE, |
44 base::ScopedTaskRunner done_runner(done_cb); | 66 NewRunnableMethod(this, &FFmpegAudioDecoder::DoFlush, callback)); |
45 *success = false; | 67 } |
46 | 68 |
47 AVStream* av_stream = demuxer_stream->GetAVStream(); | 69 void FFmpegAudioDecoder::Initialize( |
48 if (!av_stream) { | 70 DemuxerStream* stream, |
49 return; | 71 FilterCallback* callback, |
50 } | 72 StatisticsCallback* stats_callback) { |
| 73 // TODO(scherkus): change Initialize() signature to pass |stream| as a |
| 74 // scoped_refptr<>. |
| 75 scoped_refptr<DemuxerStream> ref_stream(stream); |
| 76 message_loop_->PostTask( |
| 77 FROM_HERE, |
| 78 NewRunnableMethod(this, &FFmpegAudioDecoder::DoInitialize, |
| 79 ref_stream, callback, stats_callback)); |
| 80 } |
| 81 |
| 82 void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> buffer) { |
| 83 message_loop_->PostTask( |
| 84 FROM_HERE, |
| 85 NewRunnableMethod(this, &FFmpegAudioDecoder::DoProduceAudioSamples, |
| 86 buffer)); |
| 87 } |
| 88 |
| 89 int FFmpegAudioDecoder::bits_per_channel() { |
| 90 return bits_per_channel_; |
| 91 } |
| 92 |
| 93 ChannelLayout FFmpegAudioDecoder::channel_layout() { |
| 94 return channel_layout_; |
| 95 } |
| 96 |
| 97 int FFmpegAudioDecoder::sample_rate() { |
| 98 return sample_rate_; |
| 99 } |
| 100 |
| 101 void FFmpegAudioDecoder::DoInitialize( |
| 102 const scoped_refptr<DemuxerStream>& stream, |
| 103 FilterCallback* callback, |
| 104 StatisticsCallback* stats_callback) { |
| 105 scoped_ptr<FilterCallback> c(callback); |
| 106 |
| 107 demuxer_stream_ = stream; |
| 108 AVStream* av_stream = demuxer_stream_->GetAVStream(); |
| 109 CHECK(av_stream); |
| 110 |
| 111 stats_callback_.reset(stats_callback); |
51 | 112 |
52 // Grab the AVStream's codec context and make sure we have sensible values. | 113 // Grab the AVStream's codec context and make sure we have sensible values. |
53 codec_context_ = av_stream->codec; | 114 codec_context_ = av_stream->codec; |
54 int bps = av_get_bits_per_sample_fmt(codec_context_->sample_fmt); | 115 int bps = av_get_bits_per_sample_fmt(codec_context_->sample_fmt); |
55 if (codec_context_->channels <= 0 || | 116 if (codec_context_->channels <= 0 || |
56 codec_context_->channels > Limits::kMaxChannels || | 117 codec_context_->channels > Limits::kMaxChannels || |
57 (codec_context_->channel_layout == 0 && codec_context_->channels > 2) || | 118 (codec_context_->channel_layout == 0 && codec_context_->channels > 2) || |
58 bps <= 0 || bps > Limits::kMaxBitsPerSample || | 119 bps <= 0 || bps > Limits::kMaxBitsPerSample || |
59 codec_context_->sample_rate <= 0 || | 120 codec_context_->sample_rate <= 0 || |
60 codec_context_->sample_rate > Limits::kMaxSampleRate) { | 121 codec_context_->sample_rate > Limits::kMaxSampleRate) { |
61 DLOG(WARNING) << "Invalid audio stream -" | 122 DLOG(ERROR) << "Invalid audio stream -" |
62 << " channels: " << codec_context_->channels | 123 << " channels: " << codec_context_->channels |
63 << " channel layout:" << codec_context_->channel_layout | 124 << " channel layout:" << codec_context_->channel_layout |
64 << " bps: " << bps | 125 << " bps: " << bps |
65 << " sample rate: " << codec_context_->sample_rate; | 126 << " sample rate: " << codec_context_->sample_rate; |
| 127 |
| 128 host()->SetError(PIPELINE_ERROR_DECODE); |
| 129 callback->Run(); |
66 return; | 130 return; |
67 } | 131 } |
68 | 132 |
69 // Serialize calls to avcodec_open(). | 133 // Serialize calls to avcodec_open(). |
70 AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); | 134 AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); |
71 if (!codec || avcodec_open(codec_context_, codec) < 0) { | 135 if (!codec || avcodec_open(codec_context_, codec) < 0) { |
| 136 DLOG(ERROR) << "Could not initialize audio decoder: " |
| 137 << codec_context_->codec_id; |
| 138 |
| 139 host()->SetError(PIPELINE_ERROR_DECODE); |
| 140 callback->Run(); |
72 return; | 141 return; |
73 } | 142 } |
74 | 143 |
| 144 // Success! |
75 bits_per_channel_ = av_get_bits_per_sample_fmt(codec_context_->sample_fmt); | 145 bits_per_channel_ = av_get_bits_per_sample_fmt(codec_context_->sample_fmt); |
76 channel_layout_ = | 146 channel_layout_ = |
77 ChannelLayoutToChromeChannelLayout(codec_context_->channel_layout, | 147 ChannelLayoutToChromeChannelLayout(codec_context_->channel_layout, |
78 codec_context_->channels); | 148 codec_context_->channels); |
79 sample_rate_ = codec_context_->sample_rate; | 149 sample_rate_ = codec_context_->sample_rate; |
80 | 150 |
81 // Prepare the output buffer. | 151 callback->Run(); |
82 output_buffer_.reset(static_cast<uint8*>(av_malloc(kOutputBufferSize))); | |
83 if (!output_buffer_.get()) { | |
84 host()->SetError(PIPELINE_ERROR_OUT_OF_MEMORY); | |
85 return; | |
86 } | |
87 *success = true; | |
88 } | 152 } |
89 | 153 |
90 void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> output) { | 154 void FFmpegAudioDecoder::DoFlush(FilterCallback* callback) { |
91 DecoderBase<AudioDecoder, Buffer>::PostReadTaskHack(output); | 155 avcodec_flush_buffers(codec_context_); |
| 156 estimated_next_timestamp_ = kNoTimestamp; |
| 157 |
| 158 callback->Run(); |
| 159 delete callback; |
92 } | 160 } |
93 | 161 |
94 int FFmpegAudioDecoder::bits_per_channel() { | 162 void FFmpegAudioDecoder::DoProduceAudioSamples( |
95 return bits_per_channel_; | 163 const scoped_refptr<Buffer>& output) { |
| 164 output_buffers_.push_back(output); |
| 165 ReadFromDemuxerStream(); |
96 } | 166 } |
97 | 167 |
98 ChannelLayout FFmpegAudioDecoder::channel_layout() { | 168 void FFmpegAudioDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& input) { |
99 return channel_layout_; | 169 DCHECK(!output_buffers_.empty()); |
100 } | 170 DCHECK_GT(pending_reads_, 0); |
101 | 171 pending_reads_--; |
102 int FFmpegAudioDecoder::sample_rate() { | |
103 return sample_rate_; | |
104 } | |
105 | |
106 void FFmpegAudioDecoder::DoSeek(base::TimeDelta time, Task* done_cb) { | |
107 avcodec_flush_buffers(codec_context_); | |
108 estimated_next_timestamp_ = kNoTimestamp; | |
109 done_cb->Run(); | |
110 delete done_cb; | |
111 } | |
112 | |
113 // ConvertAudioF32ToS32() converts float audio (F32) to int (S32) in place. | |
114 // This is a temporary solution. | |
115 // The purpose of this short term fix is to enable WMApro, which decodes to | |
116 // float. | |
117 // The audio driver has been tested by passing the float audio thru. | |
118 // FFmpeg for ChromeOS only exposes U8, S16 and F32. | |
119 // To properly expose new audio sample types at the audio driver layer, a enum | |
120 // should be created to represent all suppported types, including types | |
121 // for Pepper. FFmpeg should be queried for type and passed along. | |
122 | |
123 // TODO(fbarchard): Remove this function. Expose all FFmpeg types to driver. | |
124 // TODO(fbarchard): If this function is kept, move it to audio_util.cc | |
125 | |
126 #if USE_SSE | |
127 const __m128 kFloatScaler = _mm_set1_ps( 2147483648.0f ); | |
128 static void FloatToIntSaturate(float* p) { | |
129 __m128 a = _mm_set1_ps(*p); | |
130 a = _mm_mul_ss(a, kFloatScaler); | |
131 *reinterpret_cast<int32*>(p) = _mm_cvtss_si32(a); | |
132 } | |
133 #else | |
134 const float kFloatScaler = 2147483648.0f; | |
135 const int kMinSample = std::numeric_limits<int32>::min(); | |
136 const int kMaxSample = std::numeric_limits<int32>::max(); | |
137 const float kMinSampleFloat = | |
138 static_cast<float>(std::numeric_limits<int32>::min()); | |
139 const float kMaxSampleFloat = | |
140 static_cast<float>(std::numeric_limits<int32>::max()); | |
141 static void FloatToIntSaturate(float* p) { | |
142 float f = *p * kFloatScaler + 0.5f; | |
143 int sample; | |
144 if (f <= kMinSampleFloat) { | |
145 sample = kMinSample; | |
146 } else if (f >= kMaxSampleFloat) { | |
147 sample = kMaxSample; | |
148 } else { | |
149 sample = static_cast<int32>(f); | |
150 } | |
151 *reinterpret_cast<int32*>(p) = sample; | |
152 } | |
153 #endif | |
154 static void ConvertAudioF32ToS32(void* buffer, int buffer_size) { | |
155 for (int i = 0; i < buffer_size / 4; ++i) { | |
156 FloatToIntSaturate(reinterpret_cast<float*>(buffer) + i); | |
157 } | |
158 } | |
159 | |
160 void FFmpegAudioDecoder::DoDecode(Buffer* input) { | |
161 PipelineStatistics statistics; | |
162 | 172 |
163 // FFmpeg tends to seek Ogg audio streams in the middle of nowhere, giving us | 173 // FFmpeg tends to seek Ogg audio streams in the middle of nowhere, giving us |
164 // a whole bunch of AV_NOPTS_VALUE packets. Discard them until we find | 174 // a whole bunch of AV_NOPTS_VALUE packets. Discard them until we find |
165 // something valid. Refer to http://crbug.com/49709 | 175 // something valid. Refer to http://crbug.com/49709 |
166 // TODO(hclam): remove this once fixing the issue in FFmpeg. | |
167 if (input->GetTimestamp() == kNoTimestamp && | 176 if (input->GetTimestamp() == kNoTimestamp && |
168 estimated_next_timestamp_ == kNoTimestamp && | 177 estimated_next_timestamp_ == kNoTimestamp && |
169 !input->IsEndOfStream()) { | 178 !input->IsEndOfStream()) { |
170 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); | 179 ReadFromDemuxerStream(); |
171 return; | 180 return; |
172 } | 181 } |
173 | 182 |
174 // Due to FFmpeg API changes we no longer have const read-only pointers. | |
175 AVPacket packet; | 183 AVPacket packet; |
176 av_init_packet(&packet); | 184 av_init_packet(&packet); |
177 packet.data = const_cast<uint8*>(input->GetData()); | 185 if (input->IsEndOfStream()) { |
178 packet.size = input->GetDataSize(); | 186 packet.data = NULL; |
| 187 packet.size = 0; |
| 188 } else { |
| 189 packet.data = const_cast<uint8*>(input->GetData()); |
| 190 packet.size = input->GetDataSize(); |
| 191 } |
179 | 192 |
| 193 PipelineStatistics statistics; |
180 statistics.audio_bytes_decoded = input->GetDataSize(); | 194 statistics.audio_bytes_decoded = input->GetDataSize(); |
181 | 195 |
182 int16_t* output_buffer = reinterpret_cast<int16_t*>(output_buffer_.get()); | 196 int decoded_audio_size = decoded_audio_size_; |
183 int output_buffer_size = kOutputBufferSize; | 197 int result = avcodec_decode_audio3( |
184 int result = avcodec_decode_audio3(codec_context_, | 198 codec_context_, reinterpret_cast<int16_t*>(decoded_audio_), |
185 output_buffer, | 199 &decoded_audio_size, &packet); |
186 &output_buffer_size, | |
187 &packet); | |
188 | 200 |
189 if (codec_context_->sample_fmt == SAMPLE_FMT_FLT) { | 201 if (IsErrorResult(result, decoded_audio_size)) { |
190 ConvertAudioF32ToS32(output_buffer, output_buffer_size); | 202 DCHECK(!input->IsEndOfStream()) |
191 } | 203 << "End of stream buffer produced an error! " |
| 204 << "This is quite possibly a bug in the audio decoder not handling " |
| 205 << "end of stream AVPackets correctly."; |
192 | 206 |
193 // TODO(ajwong): Consider if kOutputBufferSize should just be an int instead | 207 DLOG(ERROR) << "Error decoding an audio frame with timestamp: " |
194 // of a size_t. | 208 << input->GetTimestamp().InMicroseconds() << " us, duration: " |
195 if (result < 0 || | 209 << input->GetDuration().InMicroseconds() << " us, packet size: " |
196 output_buffer_size < 0 || | 210 << input->GetDataSize() << " bytes"; |
197 static_cast<size_t>(output_buffer_size) > kOutputBufferSize) { | 211 |
198 VLOG(1) << "Error decoding an audio frame with timestamp: " | 212 ReadFromDemuxerStream(); |
199 << input->GetTimestamp().InMicroseconds() << " us, duration: " | |
200 << input->GetDuration().InMicroseconds() << " us, packet size: " | |
201 << input->GetDataSize() << " bytes"; | |
202 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); | |
203 return; | 213 return; |
204 } | 214 } |
205 | 215 |
206 // If we have decoded something, enqueue the result. | 216 scoped_refptr<DataBuffer> output; |
207 if (output_buffer_size) { | |
208 DataBuffer* result_buffer = new DataBuffer(output_buffer_size); | |
209 result_buffer->SetDataSize(output_buffer_size); | |
210 uint8* data = result_buffer->GetWritableData(); | |
211 memcpy(data, output_buffer, output_buffer_size); | |
212 | 217 |
213 // We don't trust the demuxer, so always calculate the duration based on | 218 if (ProducedAudioSamples(decoded_audio_size)) { |
214 // the actual number of samples decoded. | 219 // Copy the audio samples into an output buffer. |
215 base::TimeDelta duration = CalculateDuration(output_buffer_size); | 220 output = new DataBuffer(decoded_audio_size); |
216 result_buffer->SetDuration(duration); | 221 output->SetDataSize(decoded_audio_size); |
| 222 uint8* data = output->GetWritableData(); |
| 223 memcpy(data, decoded_audio_, decoded_audio_size); |
217 | 224 |
218 // Use an estimated timestamp unless the incoming buffer has a valid one. | 225 UpdateDurationAndTimestamp(input, output); |
219 if (input->GetTimestamp() == kNoTimestamp) { | 226 } else if (IsTimestampMarkerPacket(result, input)) { |
220 result_buffer->SetTimestamp(estimated_next_timestamp_); | 227 // Nothing else to do here but update our estimation. |
| 228 estimated_next_timestamp_ = input->GetTimestamp() + input->GetDuration(); |
| 229 } else if (IsEndOfStream(result, decoded_audio_size, input)) { |
| 230 // Create an end of stream output buffer. |
| 231 output = new DataBuffer(0); |
| 232 output->SetTimestamp(input->GetTimestamp()); |
| 233 output->SetDuration(input->GetDuration()); |
| 234 } |
221 | 235 |
222 // Keep the estimated timestamp invalid until we get an incoming buffer | 236 // Decoding finished successfully, update stats and execute callback. |
223 // with a valid timestamp. This can happen during seeks, etc... | 237 stats_callback_->Run(statistics); |
224 if (estimated_next_timestamp_ != kNoTimestamp) { | 238 if (output) { |
225 estimated_next_timestamp_ += duration; | 239 DCHECK_GT(output_buffers_.size(), 0u); |
226 } | 240 output_buffers_.pop_front(); |
227 } else { | |
228 result_buffer->SetTimestamp(input->GetTimestamp()); | |
229 estimated_next_timestamp_ = input->GetTimestamp() + duration; | |
230 } | |
231 | 241 |
232 EnqueueResult(result_buffer); | 242 ConsumeAudioSamples(output); |
233 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); | 243 } else { |
| 244 ReadFromDemuxerStream(); |
| 245 } |
| 246 } |
| 247 |
| 248 void FFmpegAudioDecoder::ReadFromDemuxerStream() { |
| 249 DCHECK(!output_buffers_.empty()) |
| 250 << "Reads should only occur if there are output buffers."; |
| 251 |
| 252 pending_reads_++; |
| 253 demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::DecodeBuffer, this)); |
| 254 } |
| 255 |
| 256 void FFmpegAudioDecoder::DecodeBuffer(Buffer* buffer) { |
| 257 // TODO(scherkus): change DemuxerStream::Read() to use scoped_refptr<> for |
| 258 // callback. |
| 259 scoped_refptr<Buffer> ref_buffer(buffer); |
| 260 message_loop_->PostTask( |
| 261 FROM_HERE, |
| 262 NewRunnableMethod(this, &FFmpegAudioDecoder::DoDecodeBuffer, ref_buffer)); |
| 263 } |
| 264 |
| 265 void FFmpegAudioDecoder::UpdateDurationAndTimestamp( |
| 266 const Buffer* input, |
| 267 DataBuffer* output) { |
| 268 // Always calculate duration based on the actual number of samples decoded. |
| 269 base::TimeDelta duration = CalculateDuration(output->GetDataSize()); |
| 270 output->SetDuration(duration); |
| 271 |
| 272 // Use the incoming timestamp if it's valid. |
| 273 if (input->GetTimestamp() != kNoTimestamp) { |
| 274 output->SetTimestamp(input->GetTimestamp()); |
| 275 estimated_next_timestamp_ = input->GetTimestamp() + duration; |
234 return; | 276 return; |
235 } | 277 } |
236 | 278 |
237 // We can get a positive result but no decoded data. This is ok because this | 279 // Otherwise use an estimated timestamp and attempt to update the estimation |
238 // this can be a marker packet that only contains timestamp. In this case we | 280 // as long as it's valid. |
239 // save the timestamp for later use. | 281 output->SetTimestamp(estimated_next_timestamp_); |
240 if (result && !input->IsEndOfStream() && | 282 if (estimated_next_timestamp_ != kNoTimestamp) { |
241 input->GetTimestamp() != kNoTimestamp && | 283 estimated_next_timestamp_ += duration; |
242 input->GetDuration() != kNoTimestamp) { | |
243 estimated_next_timestamp_ = input->GetTimestamp() + input->GetDuration(); | |
244 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); | |
245 return; | |
246 } | 284 } |
247 | |
248 // Three conditions to meet to declare end of stream for this decoder: | |
249 // 1. FFmpeg didn't read anything. | |
250 // 2. FFmpeg didn't output anything. | |
251 // 3. An end of stream buffer is received. | |
252 if (result == 0 && output_buffer_size == 0 && input->IsEndOfStream()) { | |
253 DataBuffer* result_buffer = new DataBuffer(0); | |
254 result_buffer->SetTimestamp(input->GetTimestamp()); | |
255 result_buffer->SetDuration(input->GetDuration()); | |
256 EnqueueResult(result_buffer); | |
257 } | |
258 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); | |
259 } | 285 } |
260 | 286 |
261 base::TimeDelta FFmpegAudioDecoder::CalculateDuration(size_t size) { | 287 base::TimeDelta FFmpegAudioDecoder::CalculateDuration(int size) { |
262 int64 denominator = codec_context_->channels * | 288 int64 denominator = ChannelLayoutToChannelCount(channel_layout_) * |
263 av_get_bits_per_sample_fmt(codec_context_->sample_fmt) / 8 * | 289 bits_per_channel_ / 8 * sample_rate_; |
264 codec_context_->sample_rate; | |
265 double microseconds = size / | 290 double microseconds = size / |
266 (denominator / static_cast<double>(base::Time::kMicrosecondsPerSecond)); | 291 (denominator / static_cast<double>(base::Time::kMicrosecondsPerSecond)); |
267 return base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds)); | 292 return base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds)); |
268 } | 293 } |
269 | 294 |
270 } // namespace | 295 } // namespace media |
OLD | NEW |