Chromium Code Reviews| Index: media/filters/ffmpeg_demuxer.cc |
| diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc |
| index 535116ffebd3e4b000f1818ca44d84f8c8f1cc17..1ce6b991369a999dcb4e66b66921b8d890dd355f 100644 |
| --- a/media/filters/ffmpeg_demuxer.cc |
| +++ b/media/filters/ffmpeg_demuxer.cc |
| @@ -7,6 +7,7 @@ |
| #include <algorithm> |
| #include <string> |
| +#include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| @@ -14,6 +15,7 @@ |
| #include "base/message_loop.h" |
| #include "base/stl_util.h" |
| #include "base/string_util.h" |
| +#include "base/sys_byteorder.h" |
| #include "base/time.h" |
| #include "media/base/audio_decoder_config.h" |
| #include "media/base/decoder_buffer.h" |
| @@ -23,9 +25,28 @@ |
| #include "media/ffmpeg/ffmpeg_common.h" |
| #include "media/filters/ffmpeg_glue.h" |
| #include "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h" |
| +#include "media/webm/webm_constants.h" |
| namespace media { |
| +// Generates a 16 byte CTR counter block. The CTR counter block format is a |
|
ddorwin
2012/08/22 23:20:29
We seem to do this a lot. Can we put it in media/b
fgalligan1
2012/08/23 02:39:11
Sure is there a convention where it should go?
xhwang
2012/08/23 19:04:53
media/crypto/decryptor_helpers.* ?
ddorwin
2012/08/24 00:20:30
and/or webm_helpers.* depending on what it is.
fgalligan1
2012/08/24 20:01:26
Done.
|
| +// CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV. |
| +// Always returns a valid pointer to a buffer of kDecryptionKeySize bytes. |
| +static scoped_array<uint8> GenerateCounterBlock(uint64 iv) { |
| + scoped_array<uint8> counter_block_data( |
| + new uint8[DecryptConfig::kDecryptionKeySize]); |
| + |
| + // Set the IV. |
| + memcpy(counter_block_data.get(), &iv, sizeof(iv)); |
| + |
| + // Set block counter to all 0's. |
| + memset(counter_block_data.get() + sizeof(iv), |
| + 0, |
| + DecryptConfig::kDecryptionKeySize - sizeof(iv)); |
| + |
| + return counter_block_data.Pass(); |
| +} |
| + |
| // |
| // FFmpegDemuxerStream |
| // |
| @@ -36,7 +57,8 @@ FFmpegDemuxerStream::FFmpegDemuxerStream( |
| stream_(stream), |
| type_(UNKNOWN), |
| stopped_(false), |
| - last_packet_timestamp_(kNoTimestamp()) { |
| + last_packet_timestamp_(kNoTimestamp()), |
| + need_decryption_key_(false) { |
| DCHECK(demuxer_); |
| // Determine our media format. |
| @@ -56,6 +78,22 @@ FFmpegDemuxerStream::FFmpegDemuxerStream( |
| // Calculate the duration. |
| duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration); |
| + |
| + AVDictionaryEntry *key = av_dict_get(stream->metadata, "enc_key_id", NULL, 0); |
| + if (key) { |
| + DCHECK(key->value); |
| + base::StringPiece base64_key_id(key->value); |
| + base::Base64Decode(base64_key_id, &enc_key_id_); |
| + CHECK(!enc_key_id_.empty()); |
|
ddorwin
2012/08/22 23:20:29
DCHECK? Do we prevent this in FFmpeg?
fgalligan1
2012/08/23 02:39:11
In the current code in FFmpeg we will only ads an
ddorwin
2012/08/24 00:20:30
I was just wondering whether DCHECK was sufficient
fgalligan1
2012/08/24 20:01:26
I had it as a DCHECK because at one point in time
|
| + need_decryption_key_ = true; |
|
ddorwin
2012/08/22 23:20:29
The lifetime of this object is the entire media st
fgalligan1
2012/08/23 02:39:11
I don't think FFmpegDemuxerStream supports more th
ddorwin
2012/08/24 00:20:30
We should find out how acolwell's changes to handl
|
| + demuxer_->NeedKey(enc_key_id_); |
| + } |
| + |
| + // *** DEBUG Until FFmpeg metadata "enck_key_id" is added for windows. *** |
| + //enc_key_id_ = "DEBUG_KEY_REMOVE"; |
|
ddorwin
2012/08/22 23:20:29
Could you av_dict_set() at 81 instead and thus exe
fgalligan1
2012/08/23 02:39:11
Done.
|
| + //need_decryption_key_ = true; |
| + //demuxer_->NeedKey(enc_key_id_); |
| + // *** DEBUG Until FFmpeg metadata "enck_key_id" is added for windows. *** |
| } |
|
ddorwin
2012/08/22 23:20:29
I think the use of enc_key_id_ for both reporting
fgalligan1
2012/08/23 02:39:11
PTAL and let me if what I did is what you were ask
|
| bool FFmpegDemuxerStream::HasPendingReads() { |
| @@ -86,10 +124,35 @@ void FFmpegDemuxerStream::EnqueuePacket( |
| LOG(ERROR) << "Format converstion failed."; |
| } |
| + // Every encrypted Block has an HMAC and IV prepended to it. Current |
|
ddorwin
2012/08/22 23:20:29
We seem to do this a lot. Can we put it in media/b
fgalligan1
2012/08/23 02:39:11
You want me to remove the comment?
ddorwin
2012/08/24 00:20:30
No. Move the code like line 32.
fgalligan1
2012/08/24 20:01:26
Done.
|
| + // encrypted WebM request for comments specification is here |
| + // http://wiki.webmproject.org/encryption/webm-encryption-rfc |
| + // If encrypted skip past the HMAC. Encrypted buffers must include the IV |
|
ddorwin
2012/08/22 23:20:29
If encrypted, skip past the HMAC to get the buffer
fgalligan1
2012/08/23 02:39:11
Done.
|
| + // and the encrypted frame because the decryptor will verify this data |
| + // before decryption. The HMAC and IV will be copied into DecryptConfig. |
| + int offset = (enc_key_id_.empty()) ? 0 : kWebMHmacSize; |
| + |
| // If a packet is returned by FFmpeg's av_parser_parse2() the packet will |
| // reference inner memory of FFmpeg. As such we should transfer the packet |
| // into memory we control. |
| - buffer = DecoderBuffer::CopyFrom(packet->data, packet->size); |
| + buffer = DecoderBuffer::CopyFrom(packet->data + offset, |
| + packet->size - offset); |
| + if (!enc_key_id_.empty()) { |
|
ddorwin
2012/08/22 23:20:29
We now have WebM code in the middle of the generic
fgalligan1
2012/08/23 02:39:11
Added a TODO, which can be done before the CL is s
|
| + uint64 network_iv; |
| + memcpy(&network_iv, packet->data + kWebMHmacSize, sizeof(network_iv)); |
| + const uint64 iv = base::NetToHost64(network_iv); |
| + |
| + scoped_array<uint8> counter_block(GenerateCounterBlock(iv)); |
| + buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig( |
| + enc_key_id_, |
| + std::string(reinterpret_cast<const char*>(counter_block.get()), |
| + DecryptConfig::kDecryptionKeySize), |
| + std::string(reinterpret_cast<const char*>(packet->data), |
| + kWebMHmacSize), |
| + sizeof(iv), |
| + std::vector<SubsampleEntry>()))); |
| + } |
| + |
| buffer->SetTimestamp(ConvertStreamTimestamp( |
| stream_->time_base, packet->pts)); |
| buffer->SetDuration(ConvertStreamTimestamp( |
| @@ -185,7 +248,7 @@ void FFmpegDemuxerStream::ReadTask(const ReadCB& read_cb) { |
| FulfillPendingRead(); |
| // Check if there are still pending reads, demux some more. |
| - if (!read_queue_.empty()) { |
| + if (!read_queue_.empty() && !need_decryption_key_) { |
|
ddorwin
2012/08/22 23:20:29
Why not post if need a decryption key? It's up to
fgalligan1
2012/08/23 02:39:11
I thought this was a pretty clean break point in w
ddorwin
2012/08/24 00:20:30
As xhwang mentioned, I think this is already handl
fgalligan1
2012/08/24 20:01:26
Done. Works with xhwang changes.
|
| demuxer_->PostDemuxTask(); |
| } |
| } |
| @@ -238,6 +301,10 @@ base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const { |
| return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts); |
| } |
| +void FFmpegDemuxerStream::KeyAdded() { |
| + need_decryption_key_ = false; |
| +} |
| + |
| Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const { |
| base::AutoLock auto_lock(lock_); |
| return buffered_ranges_; |
| @@ -257,7 +324,8 @@ base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp( |
| // |
| FFmpegDemuxer::FFmpegDemuxer( |
| const scoped_refptr<base::MessageLoopProxy>& message_loop, |
| - const scoped_refptr<DataSource>& data_source) |
| + const scoped_refptr<DataSource>& data_source, |
| + const FFmpegNeedKeyCB& need_key_cb) |
| : host_(NULL), |
| message_loop_(message_loop), |
| format_context_(NULL), |
| @@ -269,7 +337,8 @@ FFmpegDemuxer::FFmpegDemuxer( |
| bitrate_(0), |
| start_time_(kNoTimestamp()), |
| audio_disabled_(false), |
| - duration_known_(false) { |
| + duration_known_(false), |
| + need_key_cb_(need_key_cb) { |
| DCHECK(message_loop_); |
| DCHECK(data_source_); |
| } |
| @@ -316,6 +385,18 @@ void FFmpegDemuxer::OnAudioRendererDisabled() { |
| &FFmpegDemuxer::DisableAudioStreamTask, this)); |
| } |
| +void FFmpegDemuxer::KeyAdded() { |
| + message_loop_->PostTask(FROM_HERE, |
| + base::Bind(&FFmpegDemuxer::KeyAddedTask, this)); |
| +} |
| + |
| +void FFmpegDemuxer::NeedKey(const std::string& key_id) { |
| + int key_id_size = key_id.size(); |
| + scoped_array<uint8> key_id_local(new uint8[key_id_size]); |
| + memcpy(key_id_local.get(), key_id.data(), key_id_size); |
| + need_key_cb_.Run(key_id_local.Pass(), key_id_size); |
| +} |
| + |
| void FFmpegDemuxer::Initialize(DemuxerHost* host, |
| const PipelineStatusCB& status_cb) { |
| message_loop_->PostTask(FROM_HERE, base::Bind( |
| @@ -680,6 +761,19 @@ void FFmpegDemuxer::DisableAudioStreamTask() { |
| } |
| } |
| +void FFmpegDemuxer::KeyAddedTask() { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + StreamVector::iterator iter; |
| + for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
| + if (*iter && (*iter)->type() == DemuxerStream::VIDEO) { |
| + (*iter)->KeyAdded(); |
| + } |
| + } |
| + |
| + PostDemuxTask(); |
| +} |
| + |
| bool FFmpegDemuxer::StreamsHavePendingReads() { |
| DCHECK(message_loop_->BelongsToCurrentThread()); |
| StreamVector::iterator iter; |