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

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

Issue 11416367: Add Opus decode wrapper to media. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address comments. Created 8 years 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
« no previous file with comments | « media/filters/opus_audio_decoder.h ('k') | media/media.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "media/filters/opus_audio_decoder.h"
6
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/location.h"
10 #include "base/message_loop_proxy.h"
11 #include "base/sys_byteorder.h"
12 #include "media/base/audio_decoder_config.h"
13 #include "media/base/audio_timestamp_helper.h"
14 #include "media/base/data_buffer.h"
15 #include "media/base/decoder_buffer.h"
16 #include "media/base/demuxer.h"
17 #include "media/base/pipeline.h"
18 #include "third_party/opus/src/include/opus.h"
19 #include "third_party/opus/src/include/opus_multistream.h"
20
21 namespace media {
22
23 static uint16 ReadLE16(const uint8* data, size_t data_size, int read_offset) {
24 DCHECK(data);
25 uint16 value = 0;
26 DCHECK_LE(read_offset + sizeof(value), data_size);
27 memcpy(&value, data + read_offset, sizeof(value));
28 return base::ByteSwapToLE16(value);
29 }
30
31 // Returns true if the decode result was end of stream.
32 static inline bool IsEndOfStream(int decoded_size, Buffer* input) {
33 // Two conditions to meet to declare end of stream for this decoder:
34 // 1. Opus didn't output anything.
35 // 2. An end of stream buffer is received.
36 return decoded_size == 0 && input->IsEndOfStream();
37 }
38
39 // The Opus specification is part of IETF RFC 6716:
40 // http://tools.ietf.org/html/rfc6716
41
42 // Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
43 // mappings for up to 8 channels. See section 4.3.9 of the vorbis
44 // specification:
45 // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
46 static const int kMaxVorbisChannels = 8;
47
48 // Opus allows for decode of S16 or float samples. OpusAudioDecoder always uses
49 // S16 samples.
50 static const int kRequiredSampleSize = 16;
51 static const int kBytesPerChannel = kRequiredSampleSize / 8;
xhwang 2012/12/14 01:19:21 Thanks. I still don't fully get it. Is kRequiredSa
fgalligan1 2012/12/14 02:39:27 kRequiredSampleSize looks to be bits_per_sample to
Tom Finegan 2012/12/14 03:09:21 Renamed to kBitsPerChannel.
52
53 // Maximum packet size used in Xiph's opusdec and FFmpeg's libopusdec.
54 static const int kMaxOpusOutputPacketSizeSamples = 960 * 6;
55 static const int kMaxOpusOutputPacketSizeBytes =
56 kMaxOpusOutputPacketSizeSamples * kBytesPerChannel;
57
58 static bool RemapOpusChannelLayout(const uint8* opus_mapping,
59 int num_channels,
60 uint8* channel_layout) {
61 DCHECK(opus_mapping);
62 DCHECK(channel_layout);
63 DCHECK_LE(num_channels, kMaxVorbisChannels);
64 if (!channel_layout || num_channels > kMaxVorbisChannels)
65 return false;
66
67 // Opus uses Vorbis channel layout.
68 const int32 num_layouts = kMaxVorbisChannels;
69 const int32 num_layout_values = kMaxVorbisChannels;
70 const uint8 kVorbisChannelLayouts[num_layouts][num_layout_values] = {
71 { 0 },
72 { 0, 1 },
73 { 0, 2, 1 },
74 { 0, 1, 2, 3 },
75 { 0, 2, 1, 3, 4 },
76 { 0, 2, 1, 5, 3, 4 },
77 { 0, 2, 1, 6, 5, 3, 4 },
78 { 0, 2, 1, 7, 5, 6, 3, 4 },
79 };
80
81 // Reorder the channels to produce the same ordering as FFmpeg, which is
82 // what the pipeline expects.
83 const uint8* vorbis_layout_offset = kVorbisChannelLayouts[num_channels - 1];
84 for (int channel = 0; channel < num_channels; ++channel)
85 channel_layout[channel] = opus_mapping[vorbis_layout_offset[channel]];
86
87 return true;
88 }
89
90 // Opus Header contents:
91 // - "OpusHead" (64 bits)
92 // - version number (8 bits)
93 // - Channels C (8 bits)
94 // - Pre-skip (16 bits)
95 // - Sampling rate (32 bits)
96 // - Gain in dB (16 bits, S7.8)
97 // - Mapping (8 bits, 0=single stream (mono/stereo) 1=Vorbis mapping,
98 // 2..254: reserved, 255: multistream with no mapping)
99 //
100 // - if (mapping != 0)
101 // - N = totel number of streams (8 bits)
102 // - M = number of paired streams (8 bits)
103 // - C times channel origin
104 // - if (C<2*M)
105 // - stream = byte/2
106 // - if (byte&0x1 == 0)
107 // - left
108 // else
109 // - right
110 // - else
111 // - stream = byte-M
112
113 // Default audio output channel layout. Used to initialize |stream_map| in
114 // OpusHeader, and passed to |opus_multistream_decoder_create()| when the
115 // header does not contain mapping information. The values are valid only for
116 // mono and stereo output: Opus streams with more than 2 channels require a
117 // stream map.
118 static const uint8 kDefaultOpusChannelLayout[kMaxVorbisChannels] = {
119 0, 1, 0, 0, 0, 0, 0, 0 };
xhwang 2012/12/14 01:19:21 hmm, I still don't get it. What does a 0 or 1 chan
Tom Finegan 2012/12/14 03:09:21 These are not layouts: They are channel values. On
120
121 // Size of the Opus header excluding optional mapping information.
122 static const int kOpusHeaderSize = 19;
123
124 // Offset to the channel count byte in the Opus header.
125 static const int kOpusHeaderChannelsOffset = 9;
126
127 // Offset to the pre-skip value in the Opus header.
128 static const int kOpusHeaderSkipSamplesOffset = 10;
129
130 // Offset to the channel mapping byte in the Opus header.
131 static const int kOpusHeaderChannelMappingOffset = 18;
132
133 // Header contains a stream map. The mapping values are in extra data beyond
134 // the always present |kOpusHeaderSize| bytes of data. The mapping data
135 // contains stream count, coupling information, and per channel mapping values:
136 // - Byte 0: Number of streams.
137 // - Byte 1: Number coupled.
138 // - Byte 2: Starting at byte 2 are |header->channels| uint8 mapping values.
139 static const int kOpusHeaderNumStreamsOffset = kOpusHeaderSize;
140 static const int kOpusHeaderNumCoupledOffset = kOpusHeaderNumStreamsOffset + 1;
141 static const int kOpusHeaderStreamMapOffset = kOpusHeaderNumStreamsOffset + 2;
142
143 struct OpusHeader {
144 OpusHeader()
145 : channels(0),
146 skip_samples(0),
147 channel_mapping(0),
148 num_streams(0),
149 num_coupled(0) {
150 memcpy(stream_map, kDefaultOpusChannelLayout, kMaxVorbisChannels);
151 }
152 int channels;
153 int skip_samples;
154 int channel_mapping;
155 int num_streams;
156 int num_coupled;
157 uint8 stream_map[kMaxVorbisChannels];
158 };
159
160 // Returns true when able to successfully parse and store Opus header data in
161 // data parsed in |header|. Based on opus header parsing code in libopusdec
162 // from FFmpeg, and opus_header from Xiph's opus-tools project.
163 static bool ParseOpusHeader(const uint8* data, int data_size,
164 const AudioDecoderConfig& config,
165 OpusHeader* header) {
166 DCHECK(data);
167 DCHECK(header);
168 DCHECK_GE(data_size, kOpusHeaderSize);
169
170 if (!data || data_size < kOpusHeaderSize || !header)
171 return false;
172
173 header->channels = *(data + kOpusHeaderChannelsOffset);
174
175 DCHECK(header->channels > 0 && header->channels <= kMaxVorbisChannels);
176 if (header->channels <= 0 || header->channels > kMaxVorbisChannels) {
177 LOG(ERROR) << "ParseOpusHeader(): invalid channel count in header "
178 << ChannelLayoutToChannelCount(config.channel_layout());
179 return false;
180 }
181
182 header->skip_samples =
183 ReadLE16(data, data_size, kOpusHeaderSkipSamplesOffset);
184
185 header->channel_mapping = *(data + kOpusHeaderChannelMappingOffset);
186
187 if (!header->channel_mapping) {
188 if (header->channels > 2) {
xhwang 2012/12/14 01:19:21 s/2/kMaxChannelsWithDefaultChannelLayout if you fo
Tom Finegan 2012/12/14 03:09:21 Done.
189 LOG(ERROR) << "ParseOpusHeader(): Invalid header, missing stream map.";
190 return false;
191 }
192
193 header->num_streams = 1;
194 header->num_coupled =
195 (ChannelLayoutToChannelCount(config.channel_layout()) > 1) ? 1 : 0;
196 return true;
197 }
198
199 const int mapping_required_size =
200 kOpusHeaderStreamMapOffset + header->channels;
201 if (data_size < mapping_required_size) {
202 LOG(ERROR) << "ParseOpusHeader(): Invalid stream map.";
203 return false;
204 }
205
206 header->num_streams = *(data + kOpusHeaderNumStreamsOffset);
207 header->num_coupled = *(data + kOpusHeaderNumCoupledOffset);
208
209 if (header->num_streams + header->num_coupled != header->channels)
210 LOG(WARNING) << "ParseOpusHeader(): Inconsistent channel mapping.";
211
212 for (int i = 0; i < kMaxVorbisChannels; ++i)
213 header->stream_map[i] = *(data + kOpusHeaderStreamMapOffset + i);
214
215 return true;
216 }
217
218 OpusAudioDecoder::OpusAudioDecoder(
219 const scoped_refptr<base::MessageLoopProxy>& message_loop)
220 : message_loop_(message_loop),
221 opus_decoder_(NULL),
222 bits_per_channel_(0),
223 channel_layout_(CHANNEL_LAYOUT_NONE),
224 samples_per_second_(0),
225 last_input_timestamp_(kNoTimestamp()),
226 output_bytes_to_drop_(0),
227 skip_samples_(0) {
228 }
229
230 void OpusAudioDecoder::Initialize(
231 const scoped_refptr<DemuxerStream>& stream,
232 const PipelineStatusCB& status_cb,
233 const StatisticsCB& statistics_cb) {
234 if (!message_loop_->BelongsToCurrentThread()) {
235 message_loop_->PostTask(FROM_HERE, base::Bind(
236 &OpusAudioDecoder::DoInitialize, this,
237 stream, status_cb, statistics_cb));
238 return;
239 }
240 DoInitialize(stream, status_cb, statistics_cb);
241 }
242
243 void OpusAudioDecoder::Read(const ReadCB& read_cb) {
244 // Complete operation asynchronously on different stack of execution as per
245 // the API contract of AudioDecoder::Read()
246 message_loop_->PostTask(FROM_HERE, base::Bind(
247 &OpusAudioDecoder::DoRead, this, read_cb));
248 }
249
250 int OpusAudioDecoder::bits_per_channel() {
251 return bits_per_channel_;
252 }
253
254 ChannelLayout OpusAudioDecoder::channel_layout() {
255 return channel_layout_;
256 }
257
258 int OpusAudioDecoder::samples_per_second() {
259 return samples_per_second_;
260 }
261
262 void OpusAudioDecoder::Reset(const base::Closure& closure) {
263 message_loop_->PostTask(FROM_HERE, base::Bind(
264 &OpusAudioDecoder::DoReset, this, closure));
265 }
266
267 OpusAudioDecoder::~OpusAudioDecoder() {
268 // TODO(scherkus): should we require Stop() to be called? this might end up
269 // getting called on a random thread due to refcounting.
270 CloseDecoder();
271 }
272
273 void OpusAudioDecoder::DoInitialize(
274 const scoped_refptr<DemuxerStream>& stream,
275 const PipelineStatusCB& status_cb,
276 const StatisticsCB& statistics_cb) {
277 if (demuxer_stream_) {
278 // TODO(scherkus): initialization currently happens more than once in
279 // PipelineIntegrationTest.BasicPlayback.
280 LOG(ERROR) << "Initialize has already been called.";
281 CHECK(false);
282 }
283
284 demuxer_stream_ = stream;
285
286 if (!ConfigureDecoder()) {
287 status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
288 return;
289 }
290
291 statistics_cb_ = statistics_cb;
292 status_cb.Run(PIPELINE_OK);
293 }
294
295 void OpusAudioDecoder::DoReset(const base::Closure& closure) {
296 opus_multistream_decoder_ctl(opus_decoder_, OPUS_RESET_STATE);
297 ResetTimestampState();
298 closure.Run();
299 }
300
301 void OpusAudioDecoder::DoRead(const ReadCB& read_cb) {
302 DCHECK(message_loop_->BelongsToCurrentThread());
303 DCHECK(!read_cb.is_null());
304 CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
305
306 // OpusAudioDecoder does not queue audio. Ask for more data to fulfill the
307 // read request.
xhwang 2012/12/14 01:19:21 This comment is not necessary. Most decoders (e.g.
Tom Finegan 2012/12/14 03:09:21 Done.
308 read_cb_ = read_cb;
309 ReadFromDemuxerStream();
310 }
311
312 void OpusAudioDecoder::DoDecodeBuffer(
313 DemuxerStream::Status status,
314 const scoped_refptr<DecoderBuffer>& input) {
315 if (!message_loop_->BelongsToCurrentThread()) {
316 message_loop_->PostTask(FROM_HERE, base::Bind(
317 &OpusAudioDecoder::DoDecodeBuffer, this, status, input));
318 return;
319 }
320
321 DCHECK(!read_cb_.is_null());
322 DCHECK_EQ(status != DemuxerStream::kOk, !input) << status;
323
324 if (status == DemuxerStream::kAborted) {
325 DCHECK(!input);
326 base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
327 return;
328 }
329
330 if (status == DemuxerStream::kConfigChanged) {
331 DCHECK(!input);
332
333 // Send a "end of stream" buffer to the decode loop
334 // to output any remaining data still in the decoder.
335 if (!Decode(DecoderBuffer::CreateEOSBuffer(), true)) {
336 base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
337 return;
338 }
339
340 DVLOG(1) << "Config changed.";
341
342 if (!ConfigureDecoder()) {
343 base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
344 return;
345 }
346
347 ResetTimestampState();
348 ReadFromDemuxerStream();
349 return;
350 }
351
352 DCHECK_EQ(status, DemuxerStream::kOk);
353 DCHECK(input);
354
355 // Make sure we are notified if http://crbug.com/49709 returns. Issue also
356 // occurs with some damaged files.
357 if (!input->IsEndOfStream() && input->GetTimestamp() == kNoTimestamp() &&
358 output_timestamp_helper_->base_timestamp() == kNoTimestamp()) {
359 DVLOG(1) << "Received a buffer without timestamps!";
360 base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
361 return;
362 }
363
364 if (!input->IsEndOfStream()) {
365 if (last_input_timestamp_ == kNoTimestamp())
366 last_input_timestamp_ = input->GetTimestamp();
367 else if (input->GetTimestamp() != kNoTimestamp()) {
368 if (input->GetTimestamp() < last_input_timestamp_) {
369 base::TimeDelta diff = input->GetTimestamp() - last_input_timestamp_;
370 DVLOG(1) << "Input timestamps are not monotonically increasing! "
371 << " ts " << input->GetTimestamp().InMicroseconds() << " us"
372 << " diff " << diff.InMicroseconds() << " us";
373 base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
374 return;
375 }
376
377 last_input_timestamp_ = input->GetTimestamp();
378 }
379 }
380
381 if (!Decode(input, false))
382 base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
383 }
384
385 void OpusAudioDecoder::ReadFromDemuxerStream() {
386 DCHECK(!read_cb_.is_null());
387
388 demuxer_stream_->Read(base::Bind(&OpusAudioDecoder::DoDecodeBuffer, this));
389 }
390
391 bool OpusAudioDecoder::ConfigureDecoder() {
392 const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config();
393
394 if (config.codec() != kCodecOpus) {
395 DLOG(ERROR) << "ConfigureDecoder(): codec must be kCodecOpus.";
396 return false;
397 }
398
399 const int channel_count =
400 ChannelLayoutToChannelCount(config.channel_layout());
401 if (!config.IsValidConfig() || channel_count > kMaxVorbisChannels) {
402 DLOG(ERROR) << "ConfigureDecoder(): Invalid or unsupported audio stream -"
403 << " codec: " << config.codec()
404 << " channel count: " << channel_count
405 << " channel layout: " << config.channel_layout()
406 << " bits per channel: " << config.bits_per_channel()
407 << " samples per second: " << config.samples_per_second();
408 return false;
409 }
410
411 if (config.bits_per_channel() != kRequiredSampleSize) {
412 DLOG(ERROR) << "ConfigureDecoder(): 16 bit samples required.";
413 return false;
414 }
415
416 if (config.is_encrypted()) {
417 DLOG(ERROR) << "ConfigureDecoder(): Encrypted audio stream not supported.";
418 return false;
419 }
420
421 if (opus_decoder_ &&
422 (bits_per_channel_ != config.bits_per_channel() ||
423 channel_layout_ != config.channel_layout() ||
424 samples_per_second_ != config.samples_per_second())) {
425 DVLOG(1) << "Unsupported config change :";
426 DVLOG(1) << "\tbits_per_channel : " << bits_per_channel_
427 << " -> " << config.bits_per_channel();
428 DVLOG(1) << "\tchannel_layout : " << channel_layout_
429 << " -> " << config.channel_layout();
430 DVLOG(1) << "\tsample_rate : " << samples_per_second_
431 << " -> " << config.samples_per_second();
432 return false;
433 }
434
435 // Clean up existing decoder if necessary.
436 CloseDecoder();
437
438 // Allocate the output buffer if necessary.
439 if (!output_buffer_)
440 output_buffer_.reset(new int16[kMaxOpusOutputPacketSizeSamples]);
441
442 // Parse the Opus header.
443 OpusHeader opus_header;
444 if (!ParseOpusHeader(config.extra_data(), config.extra_data_size(),
445 config,
446 &opus_header)) {
447 LOG(ERROR) << "ConfigureDecoder(): cannot parse opus header.";
448 return false;
449 }
450
451 skip_samples_ = opus_header.skip_samples;
452
453 if (skip_samples_ > 0)
454 output_bytes_to_drop_ = skip_samples_ * config.bytes_per_frame();
455
456 uint8 channel_mapping[kMaxVorbisChannels];
457 memcpy(&channel_mapping, &kDefaultOpusChannelLayout, kMaxVorbisChannels);
xhwang 2012/12/14 01:19:21 why do you need "&"?
Tom Finegan 2012/12/14 03:09:21 Forgot to remove, thanks.
458 if (channel_count > 2) {
xhwang 2012/12/14 01:19:21 ditto about 2
Tom Finegan 2012/12/14 03:09:21 Done.
459 if (!RemapOpusChannelLayout(opus_header.stream_map,
460 channel_count,
461 channel_mapping)) {
462 LOG(ERROR) << "ConfigureDecoder(): unable to remap opus channels.";
463 return false;
464 }
465 }
466
467 // Init Opus.
468 int status = OPUS_INVALID_STATE;
469 opus_decoder_ = opus_multistream_decoder_create(config.samples_per_second(),
470 channel_count,
471 opus_header.num_streams,
472 opus_header.num_coupled,
473 channel_mapping,
474 &status);
475 if (!opus_decoder_ || status != OPUS_OK) {
476 LOG(ERROR) << "ConfigureDecoder(): opus_multistream_decoder_create failed"
477 << " status=" << opus_strerror(status);
478 return false;
479 }
480
481 // TODO(tomfinegan): Handle audio delay once the matroska spec is updated
482 // to represent the value.
483
484 bits_per_channel_ = config.bits_per_channel();
485 channel_layout_ = config.channel_layout();
486 samples_per_second_ = config.samples_per_second();
487 output_timestamp_helper_.reset(new AudioTimestampHelper(
488 config.bytes_per_frame(), config.samples_per_second()));
489 return true;
490 }
491
492 void OpusAudioDecoder::CloseDecoder() {
493 if (opus_decoder_) {
494 opus_multistream_decoder_destroy(opus_decoder_);
495 opus_decoder_ = NULL;
496 }
497 }
498
499 void OpusAudioDecoder::ResetTimestampState() {
500 output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp());
501 last_input_timestamp_ = kNoTimestamp();
502 output_bytes_to_drop_ = 0;
503 }
504
505 bool OpusAudioDecoder::Decode(const scoped_refptr<DecoderBuffer>& input,
506 bool skip_eos_append) {
507 int samples_decoded =
508 opus_multistream_decode(opus_decoder_,
509 input->GetData(), input->GetDataSize(),
510 &output_buffer_[0],
511 kMaxOpusOutputPacketSizeSamples,
512 0);
513 if (samples_decoded < 0) {
514 DCHECK(!input->IsEndOfStream())
515 << "Decode(): End of stream buffer produced an error!";
516
517 LOG(ERROR) << "ConfigureDecoder(): opus_multistream_decode failed for"
518 << " timestamp: " << input->GetTimestamp().InMicroseconds()
519 << " us, duration: " << input->GetDuration().InMicroseconds()
520 << " us, packet size: " << input->GetDataSize() << " bytes with"
521 << " status: " << opus_strerror(samples_decoded);
522 return false;
523 }
524
525 uint8* decoded_audio_data = reinterpret_cast<uint8*>(&output_buffer_[0]);
526 int decoded_audio_size = samples_decoded *
527 demuxer_stream_->audio_decoder_config().bytes_per_frame();
528 DCHECK_LE(decoded_audio_size, kMaxOpusOutputPacketSizeBytes);
529
530 if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() &&
531 !input->IsEndOfStream()) {
532 DCHECK(input->GetTimestamp() != kNoTimestamp());
533 output_timestamp_helper_->SetBaseTimestamp(input->GetTimestamp());
534 }
535
536 scoped_refptr<DataBuffer> output;
537
538 if (decoded_audio_size > 0 && output_bytes_to_drop_ > 0) {
539 int dropped_size = std::min(decoded_audio_size, output_bytes_to_drop_);
540 DCHECK_EQ(dropped_size % 2, 0);
541 decoded_audio_data += dropped_size;
542 decoded_audio_size -= dropped_size;
543 output_bytes_to_drop_ -= dropped_size;
544 }
545
546 if (decoded_audio_size > 0) {
547 // Copy the audio samples into an output buffer.
548 output = new DataBuffer(decoded_audio_data, decoded_audio_size);
549 output->SetTimestamp(output_timestamp_helper_->GetTimestamp());
550 output->SetDuration(
551 output_timestamp_helper_->GetDuration(decoded_audio_size));
552 output_timestamp_helper_->AddBytes(decoded_audio_size);
553 } else if (IsEndOfStream(decoded_audio_size, input) && !skip_eos_append) {
554 DCHECK_EQ(input->GetDataSize(), 0);
555 // Create an end of stream output buffer.
556 output = new DataBuffer(0);
557 }
558
559 // Decoding finished successfully, update statistics.
560 PipelineStatistics statistics;
561 statistics.audio_bytes_decoded = decoded_audio_size;
562 statistics_cb_.Run(statistics);
563
564 if (output) {
565 // Execute callback to return the decoded audio.
566 base::ResetAndReturn(&read_cb_).Run(kOk, output);
xhwang 2012/12/14 01:19:21 Can we keep the read_cb_ firing in one function. T
Tom Finegan 2012/12/14 03:09:21 Done. Not sure if the wrapper function is more rea
567 } else {
568 // We exhausted the input data, but it wasn't enough for a frame. Ask for
569 // more data in order to fulfill this read.
570 ReadFromDemuxerStream();
571 }
572
573 return true;
574 }
575
576 } // namespace media
OLDNEW
« no previous file with comments | « media/filters/opus_audio_decoder.h ('k') | media/media.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698