blob: b8e044c12967c157c5ffe1d334e5a3adf813e9dd [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_stream_parser.h"
#include <string>
#include "base/callback.h"
#include "base/logging.h"
#include "media/webm/webm_cluster_parser.h"
#include "media/webm/webm_constants.h"
#include "media/webm/webm_content_encodings.h"
#include "media/webm/webm_info_parser.h"
#include "media/webm/webm_tracks_parser.h"
namespace media {
// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed.
static const char kWebMInitDataType[] = "video/webm";
WebMStreamParser::WebMStreamParser()
: state_(kWaitingForInit),
waiting_for_buffers_(false) {
}
WebMStreamParser::~WebMStreamParser() {}
void WebMStreamParser::Init(const InitCB& init_cb,
const NewConfigCB& config_cb,
const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb,
const NeedKeyCB& need_key_cb,
const NewMediaSegmentCB& new_segment_cb,
const base::Closure& end_of_segment_cb,
const LogCB& log_cb) {
DCHECK_EQ(state_, kWaitingForInit);
DCHECK(init_cb_.is_null());
DCHECK(!init_cb.is_null());
DCHECK(!config_cb.is_null());
DCHECK(!audio_cb.is_null() || !video_cb.is_null());
DCHECK(!need_key_cb.is_null());
DCHECK(!new_segment_cb.is_null());
DCHECK(!end_of_segment_cb.is_null());
ChangeState(kParsingHeaders);
init_cb_ = init_cb;
config_cb_ = config_cb;
audio_cb_ = audio_cb;
video_cb_ = video_cb;
need_key_cb_ = need_key_cb;
new_segment_cb_ = new_segment_cb;
end_of_segment_cb_ = end_of_segment_cb;
log_cb_ = log_cb;
}
void WebMStreamParser::Flush() {
DCHECK_NE(state_, kWaitingForInit);
byte_queue_.Reset();
if (state_ != kParsingClusters)
return;
cluster_parser_->Reset();
}
bool WebMStreamParser::Parse(const uint8* buf, int size) {
DCHECK_NE(state_, kWaitingForInit);
if (state_ == kError)
return false;
byte_queue_.Push(buf, size);
int result = 0;
int bytes_parsed = 0;
const uint8* cur = NULL;
int cur_size = 0;
byte_queue_.Peek(&cur, &cur_size);
while (cur_size > 0) {
State oldState = state_;
switch (state_) {
case kParsingHeaders:
result = ParseInfoAndTracks(cur, cur_size);
break;
case kParsingClusters:
result = ParseCluster(cur, cur_size);
break;
case kWaitingForInit:
case kError:
return false;
}
if (result < 0) {
ChangeState(kError);
return false;
}
if (state_ == oldState && result == 0)
break;
DCHECK_GE(result, 0);
cur += result;
cur_size -= result;
bytes_parsed += result;
}
byte_queue_.Pop(bytes_parsed);
return true;
}
void WebMStreamParser::ChangeState(State new_state) {
DVLOG(1) << "ChangeState() : " << state_ << " -> " << new_state;
state_ = new_state;
}
int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) {
DVLOG(2) << "ParseInfoAndTracks()";
DCHECK(data);
DCHECK_GT(size, 0);
const uint8* cur = data;
int cur_size = size;
int bytes_parsed = 0;
int id;
int64 element_size;
int result = WebMParseElementHeader(cur, cur_size, &id, &element_size);
if (result <= 0)
return result;
switch (id) {
case kWebMIdEBMLHeader:
case kWebMIdSeekHead:
case kWebMIdVoid:
case kWebMIdCRC32:
case kWebMIdCues:
if (cur_size < (result + element_size)) {
// We don't have the whole element yet. Signal we need more data.
return 0;
}
// Skip the element.
return result + element_size;
break;
case kWebMIdSegment:
// Just consume the segment header.
return result;
break;
case kWebMIdInfo:
// We've found the element we are looking for.
break;
default: {
MEDIA_LOG(log_cb_) << "Unexpected element ID 0x" << std::hex << id;
return -1;
}
}
WebMInfoParser info_parser;
result = info_parser.Parse(cur, cur_size);
if (result <= 0)
return result;
cur += result;
cur_size -= result;
bytes_parsed += result;
WebMTracksParser tracks_parser(log_cb_);
result = tracks_parser.Parse(cur, cur_size);
if (result <= 0)
return result;
bytes_parsed += result;
base::TimeDelta duration = kInfiniteDuration();
if (info_parser.duration() > 0) {
double mult = info_parser.timecode_scale() / 1000.0;
int64 duration_in_us = info_parser.duration() * mult;
duration = base::TimeDelta::FromMicroseconds(duration_in_us);
}
const AudioDecoderConfig& audio_config = tracks_parser.audio_decoder_config();
if (audio_config.is_encrypted())
FireNeedKey(tracks_parser.audio_encryption_key_id());
const VideoDecoderConfig& video_config = tracks_parser.video_decoder_config();
if (video_config.is_encrypted())
FireNeedKey(tracks_parser.video_encryption_key_id());
if (!config_cb_.Run(audio_config, video_config)) {
DVLOG(1) << "New config data isn't allowed.";
return -1;
}
cluster_parser_.reset(new WebMClusterParser(
info_parser.timecode_scale(),
tracks_parser.audio_track_num(),
tracks_parser.video_track_num(),
tracks_parser.audio_encryption_key_id(),
tracks_parser.video_encryption_key_id(),
log_cb_));
ChangeState(kParsingClusters);
if (!init_cb_.is_null()) {
init_cb_.Run(true, duration);
init_cb_.Reset();
}
return bytes_parsed;
}
int WebMStreamParser::ParseCluster(const uint8* data, int size) {
if (!cluster_parser_.get())
return -1;
int id;
int64 element_size;
int result = WebMParseElementHeader(data, size, &id, &element_size);
if (result <= 0)
return result;
if (id == kWebMIdCluster)
waiting_for_buffers_ = true;
if (id == kWebMIdCues) {
if (size < (result + element_size)) {
// We don't have the whole element yet. Signal we need more data.
return 0;
}
// Skip the element.
return result + element_size;
}
if (id == kWebMIdEBMLHeader) {
ChangeState(kParsingHeaders);
return 0;
}
int bytes_parsed = cluster_parser_->Parse(data, size);
if (bytes_parsed <= 0)
return bytes_parsed;
const BufferQueue& audio_buffers = cluster_parser_->audio_buffers();
const BufferQueue& video_buffers = cluster_parser_->video_buffers();
base::TimeDelta cluster_start_time = cluster_parser_->cluster_start_time();
bool cluster_ended = cluster_parser_->cluster_ended();
if (waiting_for_buffers_ && cluster_start_time != kNoTimestamp()) {
new_segment_cb_.Run(cluster_start_time);
waiting_for_buffers_ = false;
}
if (!audio_buffers.empty() && !audio_cb_.Run(audio_buffers))
return -1;
if (!video_buffers.empty() && !video_cb_.Run(video_buffers))
return -1;
if (cluster_ended)
end_of_segment_cb_.Run();
return bytes_parsed;
}
void WebMStreamParser::FireNeedKey(const std::string& key_id) {
int key_id_size = key_id.size();
DCHECK_GT(key_id_size, 0);
scoped_array<uint8> key_id_array(new uint8[key_id_size]);
memcpy(key_id_array.get(), key_id.data(), key_id_size);
need_key_cb_.Run(kWebMInitDataType, key_id_array.Pass(), key_id_size);
}
} // namespace media