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

Side by Side Diff: webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.cc

Issue 11260007: Add FFmpeg audio decoder for the clear key CDM. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebased on 11242005 and 11189082 Created 8 years, 1 month 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 "webkit/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.h"
6
xhwang 2012/10/24 08:18:15 add #include <algorithm> for std::min
Tom Finegan 2012/10/24 21:16:44 Done.
7 #include "base/logging.h"
8 #include "media/base/buffers.h"
9 #include "media/base/limits.h"
10
11 // Include FFmpeg header files.
12 extern "C" {
13 // Temporarily disable possible loss of data warning.
14 MSVC_PUSH_DISABLE_WARNING(4244);
15 #include <libavcodec/avcodec.h>
16 MSVC_POP_WARNING();
17 } // extern "C"
18
19 namespace webkit_media {
20
21 // Maximum number of channels with defined order in the Vorbis specification.
22 // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
23 static const int kMaxVorbisChannels = 8;
24
25 static CodecID CdmAudioCodecToCodecID(
26 cdm::AudioDecoderConfig::AudioCodec audio_codec) {
27 switch (audio_codec) {
28 case cdm::AudioDecoderConfig::kCodecVorbis:
29 return CODEC_ID_VORBIS;
30 default:
31 NOTREACHED() << "Unsupported cdm::AudioCodec: " << audio_codec;
32 }
33
34 return CODEC_ID_NONE;
35 }
36
37 static void CdmAudioDecoderConfigToAVCodecContext(
38 const cdm::AudioDecoderConfig& config,
39 AVCodecContext* codec_context) {
40 codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
41 codec_context->codec_id = CdmAudioCodecToCodecID(config.codec);
42
43 switch (config.bits_per_channel) {
44 case 8:
45 codec_context->sample_fmt = AV_SAMPLE_FMT_U8;
46 break;
47 case 16:
48 codec_context->sample_fmt = AV_SAMPLE_FMT_S16;
49 break;
50 case 32:
51 codec_context->sample_fmt = AV_SAMPLE_FMT_S32;
52 break;
53 default:
54 DVLOG(1) << "CdmAudioDecoderConfigToAVCodecContext() Unsupported bits "
55 "per channel: " << config.bits_per_channel;
56 codec_context->sample_fmt = AV_SAMPLE_FMT_NONE;
57 }
58
59 codec_context->channels = config.channel_count;
60 codec_context->sample_rate = config.samples_per_second;
61
62 if (config.extra_data) {
63 codec_context->extradata_size = config.extra_data_size;
64 codec_context->extradata = reinterpret_cast<uint8_t*>(
65 av_malloc(config.extra_data_size + FF_INPUT_BUFFER_PADDING_SIZE));
66 memcpy(codec_context->extradata, config.extra_data,
67 config.extra_data_size);
68 memset(codec_context->extradata + config.extra_data_size, '\0',
69 FF_INPUT_BUFFER_PADDING_SIZE);
70 } else {
71 codec_context->extradata = NULL;
72 codec_context->extradata_size = 0;
73 }
74 }
75
76 // Returns true when the decode result was end of stream.
77 static inline bool IsEndOfOutputStream(int result,
78 int decoded_size,
79 bool is_end_of_input_stream) {
80 // Three conditions to meet to declare end of stream for this decoder:
81 // 1. FFmpeg didn't read anything.
82 // 2. FFmpeg didn't output anything.
83 // 3. An end of stream buffer is received.
84 return result == 0 && decoded_size == 0 && is_end_of_input_stream;
85 }
86
87 FFmpegCdmAudioDecoder::FFmpegCdmAudioDecoder(cdm::Allocator* allocator)
88 : allocator_(allocator),
xhwang 2012/10/24 08:18:15 initialization order should match declaration orde
Tom Finegan 2012/10/24 21:16:44 Done.
89 codec_context_(NULL),
90 av_frame_(NULL),
91 is_initialized_(false),
92 bits_per_channel_(0),
93 samples_per_second_(0),
94 bytes_per_frame_(0),
95 output_timestamp_base_(media::kNoTimestamp()),
96 total_frames_decoded_(0),
xhwang 2012/10/24 08:18:15 If we really need to use double for total_frames_d
Tom Finegan 2012/10/24 21:16:44 It's int64_t now.
97 last_input_timestamp_(media::kNoTimestamp()),
98 output_bytes_to_drop_(0) {
99 }
100
101 FFmpegCdmAudioDecoder::~FFmpegCdmAudioDecoder() {
102 ReleaseFFmpegResources();
103 }
104
105 bool FFmpegCdmAudioDecoder::Initialize(const cdm::AudioDecoderConfig& config) {
106 DVLOG(1) << "Initialize()";
107
108 if (!IsValidConfig(config)) {
109 LOG(ERROR) << "Initialize(): invalid audio decoder configuration.";
110 return false;
111 }
112
113 if (is_initialized_) {
114 LOG(ERROR) << "Initialize(): Already initialized.";
115 return false;
116 }
117
118 // Release existing resources if necessary.
119 ReleaseFFmpegResources();
xhwang 2012/10/24 08:18:15 In which case do we need this? Are we worrying tha
Tom Finegan 2012/10/24 21:16:44 Done, here and in the video decoder. Copied from m
120
121 // Initialize AVCodecContext structure.
122 codec_context_ = avcodec_alloc_context3(NULL);
123 CdmAudioDecoderConfigToAVCodecContext(config, codec_context_);
124 DCHECK_EQ(CODEC_ID_VORBIS, codec_context_->codec_id);
125
126 AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
127 if (!codec) {
128 LOG(ERROR) << "Initialize(): avcodec_find_decoder failed.";
129 return false;
130 }
131
132 int status;
133 if ((status = avcodec_open2(codec_context_, codec, NULL)) < 0) {
134 LOG(ERROR) << "Initialize(): avcodec_open2 failed: " << status;
135 return false;
136 }
137
138 av_frame_ = avcodec_alloc_frame();
139 is_initialized_ = true;
xhwang 2012/10/24 08:18:15 Move this down to line 144.
Tom Finegan 2012/10/24 21:16:44 Done.
140 bits_per_channel_ = config.bits_per_channel;
141 samples_per_second_ = config.samples_per_second;
142 bytes_per_frame_ = codec_context_->channels * bits_per_channel_ / 8;
143 serialized_audio_frames_.reserve(bytes_per_frame_ * samples_per_second_);
144
145 return true;
146 }
147
148 void FFmpegCdmAudioDecoder::Deinitialize() {
149 DVLOG(1) << "Deinitialize()";
150 ReleaseFFmpegResources();
151 is_initialized_ = false;
152 ResetOutputTime();
153 }
154
155 void FFmpegCdmAudioDecoder::Reset() {
156 DVLOG(1) << "Reset()";
157 avcodec_flush_buffers(codec_context_);
158 ResetOutputTime();
159 }
160
161 // static
162 bool FFmpegCdmAudioDecoder::IsValidConfig(
163 const cdm::AudioDecoderConfig& config) {
164 return config.codec == cdm::AudioDecoderConfig::kCodecVorbis &&
165 config.channel_count > 0 && config.channel_count <= kMaxVorbisChannels &&
xhwang 2012/10/24 08:18:15 split this to two lines to be consistent with the
Tom Finegan 2012/10/24 21:16:44 Done.
166 config.bits_per_channel > 0 &&
167 config.bits_per_channel <= media::limits::kMaxBitsPerSample &&
168 config.samples_per_second > 0 &&
169 config.samples_per_second <= media::limits::kMaxSampleRate;
170 }
171
172 cdm::Status FFmpegCdmAudioDecoder::DecodeBuffer(
173 const uint8_t* compressed_buffer,
174 int32_t compressed_buffer_size,
175 int64_t input_timestamp,
176 cdm::AudioFrames* decoded_frames) {
177 const bool is_end_of_stream = compressed_buffer_size == 0;
178 base::TimeDelta timestamp =
179 base::TimeDelta::FromMicroseconds(input_timestamp);
180 if (!is_end_of_stream) {
181 if (last_input_timestamp_ == media::kNoTimestamp()) {
182 if (codec_context_->codec_id == CODEC_ID_VORBIS &&
183 timestamp < base::TimeDelta()) {
184 // Dropping frames for negative timestamps as outlined in section A.2
185 // in the Vorbis spec. http://xiph.org/vorbis/doc/Vorbis_I_spec.html
186 int frames_to_drop = floor(
187 0.5 + -timestamp.InSecondsF() * samples_per_second_);
188 output_bytes_to_drop_ = bytes_per_frame_ * frames_to_drop;
189 } else {
190 last_input_timestamp_ = timestamp;
191 }
192 } else if (timestamp != media::kNoTimestamp()) {
193 if (timestamp < last_input_timestamp_) {
194 base::TimeDelta diff = timestamp - last_input_timestamp_;
195 DVLOG(1) << "Input timestamps are not monotonically increasing! "
196 << " ts " << timestamp.InMicroseconds() << " us"
197 << " diff " << diff.InMicroseconds() << " us";
198 return cdm::kDecodeError;
199 }
200
201 last_input_timestamp_ = timestamp;
202 }
203 }
204
205 AVPacket packet;
206 av_init_packet(&packet);
207 packet.data = const_cast<uint8_t*>(compressed_buffer);
208 packet.size = compressed_buffer_size;
209
210 // Each audio packet may contain several frames, so we must call the decoder
211 // until we've exhausted the packet. Regardless of the packet size we always
212 // want to hand it to the decoder at least once, otherwise we would end up
213 // skipping end of stream packets since they have a size of zero.
214 do {
215 // Reset frame to default values.
216 avcodec_get_frame_defaults(av_frame_);
217
218 int frame_decoded = 0;
219 int result = avcodec_decode_audio4(
220 codec_context_, av_frame_, &frame_decoded, &packet);
221
222 if (result < 0) {
223 DCHECK(!is_end_of_stream)
224 << "End of stream buffer produced an error! "
225 << "This is quite possibly a bug in the audio decoder not handling "
226 << "end of stream AVPackets correctly.";
227
228 DLOG(ERROR)
229 << "Error decoding an audio frame with timestamp: "
230 << timestamp.InMicroseconds() << " us, duration: "
231 << timestamp.InMicroseconds() << " us, packet size: "
232 << compressed_buffer_size << " bytes";
233
234 // TODO(tomfinegan): Return cdm::kDecodeError here when
235 // http://crbug.com/145276 is fixed.
236 break;
xhwang 2012/10/24 08:18:15 The break here will hit line 315. In that case we
Tom Finegan 2012/10/24 21:16:44 Done.
237 }
238
239 // Update packet size and data pointer in case we need to call the decoder
240 // with the remaining bytes from this packet.
241 packet.size -= result;
242 packet.data += result;
243
244 if (output_timestamp_base_ == media::kNoTimestamp() && !is_end_of_stream) {
245 DCHECK(timestamp != media::kNoTimestamp());
246 if (output_bytes_to_drop_ > 0) {
247 // If we have to drop samples it always means the timeline starts at 0.
248 output_timestamp_base_ = base::TimeDelta();
249 } else {
250 output_timestamp_base_ = timestamp;
251 }
252 }
253
254 const uint8* decoded_audio_data = NULL;
xhwang 2012/10/24 08:18:15 we are usng uint8_t in other places in this file.
Tom Finegan 2012/10/24 21:16:44 _t added; missed this one after the copy from medi
255 int decoded_audio_size = 0;
256 if (frame_decoded) {
257 int output_sample_rate = av_frame_->sample_rate;
258 if (output_sample_rate != samples_per_second_) {
259 DLOG(ERROR) << "Output sample rate (" << output_sample_rate
260 << ") doesn't match expected rate " << samples_per_second_;
261 return cdm::kDecodeError;
262 }
263
264 decoded_audio_data = av_frame_->data[0];
265 decoded_audio_size = av_samples_get_buffer_size(
266 NULL, codec_context_->channels, av_frame_->nb_samples,
267 codec_context_->sample_fmt, 1);
268 }
269
270 if (decoded_audio_size > 0 && output_bytes_to_drop_ > 0) {
271 int dropped_size = std::min(decoded_audio_size, output_bytes_to_drop_);
272 decoded_audio_data += dropped_size;
273 decoded_audio_size -= dropped_size;
274 output_bytes_to_drop_ -= dropped_size;
275 }
276
277 if (decoded_audio_size > 0) {
278 DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0)
279 << "Decoder didn't output full frames";
280
281 base::TimeDelta output_timestamp = GetNextOutputTimestamp();
xhwang 2012/10/24 08:18:15 hmm, ISTM that this timestamp is the time of the l
xhwang 2012/10/24 16:33:21 Okay, now I understand how it works ;) Could you p
Tom Finegan 2012/10/24 21:16:44 Done.
282
283 // Serialize the audio samples into |serialized_audio_frames_|.
284 SerializeInt64(output_timestamp.InMicroseconds());
285 SerializeInt64(decoded_audio_size);
286 serialized_audio_frames_.insert(
287 serialized_audio_frames_.end(),
288 decoded_audio_data,
289 decoded_audio_data + decoded_audio_size);
290
291 total_frames_decoded_ += decoded_audio_size / bytes_per_frame_;
292 } else if (IsEndOfOutputStream(result,
293 decoded_audio_size,
294 is_end_of_stream)) {
xhwang 2012/10/24 08:18:15 I am overwhelmed by the logic in this function (I
Tom Finegan 2012/10/24 21:16:44 Done (returning kNeedMoreData). Changed nothing el
295 DCHECK_EQ(packet.size, 0);
296 // Serialize an end of stream buffer.
297 SerializeInt64(0);
298 SerializeInt64(0);
xhwang 2012/10/24 08:18:15 The new contract is: suppose when the input is EOS
Tom Finegan 2012/10/24 21:16:44 Done.
299 }
300 } while (packet.size > 0);
301
302 if (serialized_audio_frames_.size() > 0) {
303 decoded_frames->set_buffer(
304 allocator_->Allocate(serialized_audio_frames_.size()));
305 if (!decoded_frames->buffer()) {
306 LOG(ERROR) << "DecodeBuffer() cdm::Allocator::Allocate failed.";
307 return cdm::kDecodeError;
308 }
309 memcpy(decoded_frames->buffer()->data(),
310 &serialized_audio_frames_[0],
311 serialized_audio_frames_.size());
312 serialized_audio_frames_.clear();
313 }
314
315 return cdm::kSuccess;
316 }
317
318 void FFmpegCdmAudioDecoder::ResetOutputTime() {
xhwang 2012/10/24 08:18:15 This function name is a little misleading as we ar
Tom Finegan 2012/10/24 21:16:44 Renamed to ResetAudioTimingData().
319 output_timestamp_base_ = media::kNoTimestamp();
320 total_frames_decoded_ = 0;
321 last_input_timestamp_ = media::kNoTimestamp();
322 output_bytes_to_drop_ = 0;
323 }
324
325 void FFmpegCdmAudioDecoder::ReleaseFFmpegResources() {
326 DVLOG(1) << "ReleaseFFmpegResources()";
327
328 if (codec_context_) {
329 av_free(codec_context_->extradata);
330 avcodec_close(codec_context_);
331 av_free(codec_context_);
332 codec_context_ = NULL;
333 }
334 if (av_frame_) {
335 av_free(av_frame_);
336 av_frame_ = NULL;
337 }
338 }
339
340 base::TimeDelta FFmpegCdmAudioDecoder::GetNextOutputTimestamp() const {
341 DCHECK(output_timestamp_base_ != media::kNoTimestamp());
342 double decoded_us = (total_frames_decoded_ / samples_per_second_) *
343 base::Time::kMicrosecondsPerSecond;
344 return output_timestamp_base_ +
345 base::TimeDelta::FromMicroseconds(decoded_us);
346 }
347
348 void FFmpegCdmAudioDecoder::SerializeInt64(int64 value) {
349 const uint8_t* ptr = reinterpret_cast<uint8_t*>(&value);
350 serialized_audio_frames_.insert(serialized_audio_frames_.end(),
351 ptr, ptr + sizeof(value));
352 }
353
354 } // namespace webkit_media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698