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

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

Powered by Google App Engine
This is Rietveld 408576698