blob: 8ec2c3adab471ee2eaba872ce9afa1868d58393f [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;
}
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://wiki.webmproject.org/encryption/webm-encryption-rfc
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;
}
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;
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 defined(__LB_SHELL__) || defined(COBALT)
// Don't copy prepended meta data as it is not used by the decrytor 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.
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)
std::vector<SubsampleEntry>())));
}
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