Index: media/filters/ffmpeg_demuxer.cc |
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc |
index 535116ffebd3e4b000f1818ca44d84f8c8f1cc17..6e476240bd5c906b0e40eab0a2f206f4e7a04e32 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 |
+// 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); |
+ |
+ // *** DEBUG Until FFmpeg metadata "enck_key_id" is added for windows. *** |
+ //av_dict_set(stream->metadata, "enc_key_id", "DEBUG_KEY_ID_REMOVE", 0); |
+ // *** DEBUG Until FFmpeg metadata "enck_key_id" is added for windows. *** |
+ |
+ AVDictionaryEntry *key = av_dict_get(stream->metadata, "enc_key_id", NULL, 0); |
ddorwin
2012/08/24 00:20:30
// Only supports WebM for now.
fgalligan1
2012/08/24 20:01:26
Done.
|
+ if (key) { |
+ DCHECK(key->value); |
+ base::StringPiece base64_key_id(key->value); |
+ std::string enc_key_id; |
+ base::Base64Decode(base64_key_id, &enc_key_id); |
+ CHECK(!enc_key_id.empty()); |
+ need_decryption_key_ = true; |
+ demuxer_->NeedKey(enc_key_id); |
+ current_key_id_.assign(enc_key_id); |
+ } |
} |
bool FFmpegDemuxerStream::HasPendingReads() { |
@@ -86,10 +124,39 @@ void FFmpegDemuxerStream::EnqueuePacket( |
LOG(ERROR) << "Format converstion failed."; |
} |
+ // TODO (fgalligan): Remove the WebM specific code to process an encrypted |
+ // buffer. Create a generalized class or function that can be called that |
+ // will work for all encrypted formats. |
+ // Every encrypted Block has an HMAC and IV prepended to it. Current |
+ // encrypted WebM request for comments specification is here |
+ // http://wiki.webmproject.org/encryption/webm-encryption-rfc |
+ // If encrypted, skip past the HMAC to get the buffer. Encrypted buffers |
+ // must include the IV and the encrypted frame because the decryptor will |
+ // verify this data before decryption. The HMAC and IV will be copied into |
+ // DecryptConfig. |
+ int offset = (current_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 (!current_key_id_.empty()) { |
+ 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( |
+ current_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 +252,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_) { |
demuxer_->PostDemuxTask(); |
} |
} |
@@ -238,6 +305,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 +328,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 +341,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 +389,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 +765,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; |