blob: d21ca244e6ed77ef83bfc68e4a43e56990a44cf2 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/webm/webm_cluster_parser.h"
#include <vector>
#include "base/logging.h"
#include "media/base/data_buffer.h"
#include "media/base/decrypt_config.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.
// |iv_size| is the size of |iv| in btyes. Returns a string of
// kDecryptionKeySize bytes.
static std::string GenerateCounterBlock(const uint8* iv, int iv_size) {
std::string counter_block(reinterpret_cast<const char*>(iv), iv_size);
counter_block.append(DecryptConfig::kDecryptionKeySize - iv_size, 0);
return counter_block;
}
namespace {
uint32 ReadInteger(const uint8* buf, int size) {
// Read in the big-endian integer.
uint32 value = 0;
for (int i = 0; i < size; ++i)
value = (value << 8) | buf[i];
return value;
}
bool ExtractSubsamples(const uint8* buf,
size_t frame_data_size,
size_t num_partitions,
std::vector<SubsampleEntry>* subsample_entries) {
subsample_entries->clear();
uint32 clear_bytes = 0;
// Partition is the wall between alternating sections. Partition offsets are
// relative to the start of the actual frame data.
// Size of clear/cipher sections can be calculated from the difference between
// adjacent partition offsets.
// Here is an example with 4 partitions (5 sections):
// "clear |1 cipher |2 clear |3 cipher |4 clear"
// With the first and the last implicit partition included:
// "|0 clear |1 cipher |2 clear |3 cipher |4 clear |5"
// where partition_offset_0 = 0, partition_offset_5 = frame_data_size
// There are three subsamples in the above example:
// Subsample0.clear_bytes = partition_offset_1 - partition_offset_0
// Subsample0.cypher_bytes = partition_offset_2 - partition_offset_1
// ...
// Subsample2.clear_bytes = partition_offset_5 - partition_offset_4
// Subsample2.cypher_bytes = 0
uint32 partition_offset = 0;
for (size_t i = 0, offset = 0; i <= num_partitions; ++i) {
const uint32 prev_partition_offset = partition_offset;
partition_offset =
(i == num_partitions)
? frame_data_size
: ReadInteger(buf + offset, kWebMEncryptedFramePartitionOffsetSize);
offset += kWebMEncryptedFramePartitionOffsetSize;
if (partition_offset < prev_partition_offset) {
DVLOG(1) << "Partition should not be decreasing " << prev_partition_offset
<< " " << partition_offset;
return false;
}
uint32 cypher_bytes = 0;
bool new_subsample_entry = false;
// Alternating clear and cipher sections.
if ((i % 2) == 0) {
clear_bytes = partition_offset - prev_partition_offset;
// Generate a new subsample when finishing reading partition offsets.
new_subsample_entry = i == num_partitions;
} else {
cypher_bytes = partition_offset - prev_partition_offset;
// Generate a new subsample after seeing a cipher section.
new_subsample_entry = true;
}
if (new_subsample_entry) {
if (clear_bytes == 0 && cypher_bytes == 0) {
DVLOG(1) << "Not expecting >2 partitions with the same offsets.";
return false;
}
SubsampleEntry entry;
entry.clear_bytes = clear_bytes;
entry.cypher_bytes = cypher_bytes;
subsample_entries->push_back(entry);
}
}
return true;
}
} // namespace
WebMClusterParser::WebMClusterParser(
int64 timecode_scale, int audio_track_num, int video_track_num,
const std::string& audio_encryption_key_id,
const std::string& video_encryption_key_id,
const LogCB& log_cb)
: timecode_multiplier_(timecode_scale / 1000.0),
audio_encryption_key_id_(audio_encryption_key_id),
video_encryption_key_id_(video_encryption_key_id),
parser_(kWebMIdCluster, this),
last_block_timecode_(-1),
block_data_size_(-1),
block_duration_(-1),
cluster_timecode_(-1),
cluster_start_time_(kNoTimestamp()),
cluster_ended_(false),
audio_(audio_track_num),
video_(video_track_num),
log_cb_(log_cb) {
}
WebMClusterParser::~WebMClusterParser() {}
void WebMClusterParser::Reset() {
last_block_timecode_ = -1;
cluster_timecode_ = -1;
cluster_start_time_ = kNoTimestamp();
cluster_ended_ = false;
parser_.Reset();
audio_.Reset();
video_.Reset();
}
int WebMClusterParser::Parse(const uint8* buf, int size) {
audio_.Reset();
video_.Reset();
int result = parser_.Parse(buf, size);
if (result < 0) {
cluster_ended_ = false;
return result;
}
cluster_ended_ = parser_.IsParsingComplete();
if (cluster_ended_) {
// If there were no buffers in this cluster, set the cluster start time to
// be the |cluster_timecode_|.
if (cluster_start_time_ == kNoTimestamp()) {
DCHECK_GT(cluster_timecode_, -1);
cluster_start_time_ = base::TimeDelta::FromMicroseconds(
cluster_timecode_ * timecode_multiplier_);
}
// Reset the parser if we're done parsing so that
// it is ready to accept another cluster on the next
// call.
parser_.Reset();
last_block_timecode_ = -1;
cluster_timecode_ = -1;
}
return result;
}
WebMParserClient* WebMClusterParser::OnListStart(int id) {
if (id == kWebMIdCluster) {
cluster_timecode_ = -1;
cluster_start_time_ = kNoTimestamp();
} else if (id == kWebMIdBlockGroup) {
block_data_.reset();
block_data_size_ = -1;
block_duration_ = -1;
}
return this;
}
bool WebMClusterParser::OnListEnd(int id) {
if (id != kWebMIdBlockGroup)
return true;
// Make sure the BlockGroup actually had a Block.
if (block_data_size_ == -1) {
MEDIA_LOG(log_cb_) << "Block missing from BlockGroup.";
return false;
}
bool result = ParseBlock(block_data_.get(), block_data_size_,
block_duration_);
block_data_.reset();
block_data_size_ = -1;
block_duration_ = -1;
return result;
}
bool WebMClusterParser::OnUInt(int id, int64 val) {
if (id == kWebMIdTimecode) {
if (cluster_timecode_ != -1)
return false;
cluster_timecode_ = val;
} else if (id == kWebMIdBlockDuration) {
if (block_duration_ != -1)
return false;
block_duration_ = val;
}
return true;
}
bool WebMClusterParser::ParseBlock(const uint8* buf, int size, int duration) {
if (size < 4)
return false;
// Return an error if the trackNum > 127. We just aren't
// going to support large track numbers right now.
if (!(buf[0] & 0x80)) {
MEDIA_LOG(log_cb_) << "TrackNumber over 127 not supported";
return false;
}
int track_num = buf[0] & 0x7f;
int timecode = buf[1] << 8 | buf[2];
int flags = buf[3] & 0xff;
int lacing = (flags >> 1) & 0x3;
if (lacing) {
MEDIA_LOG(log_cb_) << "Lacing " << lacing << " is not supported yet.";
return false;
}
// Sign extend negative timecode offsets.
if (timecode & 0x8000)
timecode |= (-1 << 16);
const uint8* frame_data = buf + 4;
int frame_size = size - (frame_data - buf);
return OnBlock(track_num, timecode, duration, flags, frame_data, frame_size);
}
bool WebMClusterParser::OnBinary(int id, const uint8* data, int size) {
if (id == kWebMIdSimpleBlock)
return ParseBlock(data, size, -1);
if (id != kWebMIdBlock)
return true;
if (block_data_.get()) {
MEDIA_LOG(log_cb_) << "More than 1 Block in a BlockGroup is not supported.";
return false;
}
block_data_.reset(new uint8[size]);
memcpy(block_data_.get(), data, size);
block_data_size_ = size;
return true;
}
bool WebMClusterParser::OnBlock(int track_num, int timecode,
int block_duration,
int flags,
const uint8* data, int size) {
DCHECK_GE(size, 0);
if (cluster_timecode_ == -1) {
MEDIA_LOG(log_cb_) << "Got a block before cluster timecode.";
return false;
}
if (timecode < 0) {
MEDIA_LOG(log_cb_) << "Got a block with negative timecode offset "
<< timecode;
return false;
}
if (last_block_timecode_ != -1 && timecode < last_block_timecode_) {
MEDIA_LOG(log_cb_)
<< "Got a block with a timecode before the previous block.";
return false;
}
Track* track = NULL;
std::string encryption_key_id;
if (track_num == audio_.track_num()) {
track = &audio_;
encryption_key_id = audio_encryption_key_id_;
} else if (track_num == video_.track_num()) {
track = &video_;
encryption_key_id = video_encryption_key_id_;
} else {
MEDIA_LOG(log_cb_) << "Unexpected track number " << track_num;
return false;
}
last_block_timecode_ = timecode;
base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds(
(cluster_timecode_ + timecode) * timecode_multiplier_);
// The first bit of the flags is set when the block contains only keyframes.
// http://www.matroska.org/technical/specs/index.html
bool is_keyframe = (flags & 0x80) != 0;
#if defined(__LB_SHELL__) || defined(COBALT)
scoped_refptr<StreamParserBuffer> buffer;
// Create buffer and copy data over for non-encrypted data. Encrypted data
// will be handled in the following if block.
if (encryption_key_id.empty())
buffer = StreamParserBuffer::CopyFrom(data, size, is_keyframe);
#else // defined(__LB_SHELL__) || defined(COBALT)
scoped_refptr<StreamParserBuffer> buffer =
StreamParserBuffer::CopyFrom(data, size, is_keyframe);
#endif // defined(__LB_SHELL__) || defined(COBALT)
// Every encrypted Block has a signal byte and IV prepended to it. Current
// encrypted WebM request for comments specification is here
// http://www.webmproject.org/docs/webm-encryption/
if (!encryption_key_id.empty()) {
DCHECK_EQ(kWebMSignalByteSize, 1);
if (size < kWebMSignalByteSize) {
MEDIA_LOG(log_cb_)
<< "Got a block from an encrypted stream with no data.";
return false;
}
const uint8 signal_byte = data[0];
int data_offset = sizeof(signal_byte);
// Setting the DecryptConfig object of the buffer while leaving the
// initialization vector empty will tell the decryptor that the frame is
// unencrypted.
std::string counter_block;
std::vector<SubsampleEntry> subsample_entries;
if (signal_byte & kWebMFlagEncryptedFrame) {
if (size < kWebMSignalByteSize + kWebMIvSize) {
MEDIA_LOG(log_cb_) << "Got an encrypted block with not enough data "
<< size;
return false;
}
counter_block = GenerateCounterBlock(data + data_offset, kWebMIvSize);
data_offset += kWebMIvSize;
if (signal_byte & kWebMFlagEncryptedFramePartitioned) {
if (size < data_offset + kWebMEncryptedFrameNumPartitionsSize) {
DVLOG(1) << "Got a partitioned encrypted block with not enough data "
<< size;
return false;
}
const size_t num_partitions = data[data_offset];
if (num_partitions == 0) {
DVLOG(1) << "Got a partitioned encrypted block with 0 partitions.";
return false;
}
data_offset += kWebMEncryptedFrameNumPartitionsSize;
const uint8* partition_data_start = data + data_offset;
data_offset += kWebMEncryptedFramePartitionOffsetSize * num_partitions;
if (size <= data_offset) {
DVLOG(1) << "Got a partitioned encrypted block with "
<< num_partitions << " partitions but not enough data "
<< size;
return false;
}
const size_t frame_data_size = size - data_offset;
if (!ExtractSubsamples(partition_data_start, frame_data_size,
num_partitions, &subsample_entries)) {
return false;
}
}
}
#if defined(__LB_SHELL__) || defined(COBALT)
// Don't copy prepended meta data as it is not used by the decryptor and
// decoder.
buffer = StreamParserBuffer::CopyFrom(data + data_offset,
size - data_offset, is_keyframe);
#endif // defined(__LB_SHELL__) || defined(COBALT)
// TODO(fgalligan): Revisit if DecryptConfig needs to be set on unencrypted
// frames after the CDM API is finalized.
// Unencrypted frames of potentially encrypted streams currently set
// DecryptConfig.
if (buffer) {
buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(
new DecryptConfig(encryption_key_id, counter_block,
#if !defined(__LB_SHELL__) && !defined(COBALT)
data_offset,
#endif // !defined(__LB_SHELL__) && !defined(COBALT)
subsample_entries)));
}
}
if (!buffer) {
DLOG(WARNING) << "Failed to create StreamParserBuffer";
return false;
}
buffer->SetTimestamp(timestamp);
if (cluster_start_time_ == kNoTimestamp())
cluster_start_time_ = timestamp;
if (block_duration >= 0) {
buffer->SetDuration(base::TimeDelta::FromMicroseconds(
block_duration * timecode_multiplier_));
}
return track->AddBuffer(buffer);
}
WebMClusterParser::Track::Track(int track_num)
: track_num_(track_num) {
}
WebMClusterParser::Track::~Track() {}
bool WebMClusterParser::Track::AddBuffer(
const scoped_refptr<StreamParserBuffer>& buffer) {
DVLOG(2) << "AddBuffer() : " << track_num_
<< " ts " << buffer->GetTimestamp().InSecondsF()
<< " dur " << buffer->GetDuration().InSecondsF()
<< " kf " << buffer->IsKeyframe()
<< " size " << buffer->GetDataSize();
buffers_.push_back(buffer);
return true;
}
void WebMClusterParser::Track::Reset() {
buffers_.clear();
}
} // namespace media