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

Unified Diff: media/filters/ffmpeg_demuxer.cc

Issue 10829470: Support for parsing encrypted WebM streams by src. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 months 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 side-by-side diff with in-line comments
Download patch
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;

Powered by Google App Engine
This is Rietveld 408576698