Update webm_cluster_parser to emit samples

Change-Id: I02436cfcb53b96210d6f683227cdabb994f4c01f
This commit is contained in:
KongQun Yang 2015-10-14 16:10:12 -07:00
parent 732e06fde0
commit 5a4234f4da
3 changed files with 283 additions and 515 deletions

View File

@ -9,7 +9,7 @@
#include "packager/base/logging.h"
#include "packager/base/sys_byteorder.h"
#include "packager/media/base/decrypt_config.h"
#include "packager/media/base/timestamp_constants.h"
#include "packager/media/base/timestamp.h"
#include "packager/media/filters/webvtt_util.h"
#include "packager/media/formats/webm/webm_constants.h"
#include "packager/media/formats/webm/webm_crypto_helpers.h"
@ -30,6 +30,10 @@
"may be suppressed): " \
: "")
namespace {
const int64_t kMicrosecondsPerMillisecond = 1000;
} // namespace
namespace edash_packager {
namespace media {
@ -50,29 +54,29 @@ enum {
WebMClusterParser::WebMClusterParser(
int64_t timecode_scale,
int audio_track_num,
base::TimeDelta audio_default_duration,
int64_t audio_default_duration,
int video_track_num,
base::TimeDelta video_default_duration,
int64_t video_default_duration,
const WebMTracksParser::TextTracks& text_tracks,
const std::set<int64_t>& ignored_tracks,
const std::string& audio_encryption_key_id,
const std::string& video_encryption_key_id,
const AudioCodec audio_codec)
const AudioCodec audio_codec,
const MediaParser::NewSampleCB& new_sample_cb)
: timecode_multiplier_(timecode_scale / 1000.0),
ignored_tracks_(ignored_tracks),
audio_encryption_key_id_(audio_encryption_key_id),
video_encryption_key_id_(video_encryption_key_id),
audio_codec_(audio_codec),
parser_(kWebMIdCluster, this),
cluster_start_time_(kNoTimestamp()),
audio_(audio_track_num, false, audio_default_duration),
video_(video_track_num, true, video_default_duration),
ready_buffer_upper_bound_(kNoDecodeTimestamp()) {
cluster_start_time_(kNoTimestamp),
audio_(audio_track_num, false, audio_default_duration, new_sample_cb),
video_(video_track_num, true, video_default_duration, new_sample_cb) {
for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
it != text_tracks.end();
++it) {
text_track_map_.insert(
std::make_pair(it->first, Track(it->first, false, kNoTimestamp())));
text_track_map_.insert(std::make_pair(
it->first, Track(it->first, false, kNoTimestamp, new_sample_cb)));
}
}
@ -81,21 +85,15 @@ WebMClusterParser::~WebMClusterParser() {}
void WebMClusterParser::Reset() {
last_block_timecode_ = -1;
cluster_timecode_ = -1;
cluster_start_time_ = kNoTimestamp();
cluster_start_time_ = kNoTimestamp;
cluster_ended_ = false;
parser_.Reset();
audio_.Reset();
video_.Reset();
ResetTextTracks();
ready_buffer_upper_bound_ = kNoDecodeTimestamp();
}
int WebMClusterParser::Parse(const uint8_t* buf, int size) {
audio_.ClearReadyBuffers();
video_.ClearReadyBuffers();
ClearTextTrackReadyBuffers();
ready_buffer_upper_bound_ = kNoDecodeTimestamp();
int result = parser_.Parse(buf, size);
if (result < 0) {
@ -105,16 +103,18 @@ int WebMClusterParser::Parse(const uint8_t* buf, int size) {
cluster_ended_ = parser_.IsParsingComplete();
if (cluster_ended_) {
audio_.ApplyDurationEstimateIfNeeded();
video_.ApplyDurationEstimateIfNeeded();
// If there were no buffers in this cluster, set the cluster start time to
// be the |cluster_timecode_|.
if (cluster_start_time_ == kNoTimestamp()) {
if (cluster_start_time_ == kNoTimestamp) {
// If the cluster did not even have a |cluster_timecode_|, signal parse
// error.
if (cluster_timecode_ < 0)
return -1;
cluster_start_time_ = base::TimeDelta::FromMicroseconds(
cluster_timecode_ * timecode_multiplier_);
cluster_start_time_ = cluster_timecode_ * timecode_multiplier_;
}
// Reset the parser if we're done parsing so that
@ -129,40 +129,7 @@ int WebMClusterParser::Parse(const uint8_t* buf, int size) {
return result;
}
const WebMClusterParser::BufferQueue& WebMClusterParser::GetAudioBuffers() {
if (ready_buffer_upper_bound_ == kNoDecodeTimestamp())
UpdateReadyBuffers();
return audio_.ready_buffers();
}
const WebMClusterParser::BufferQueue& WebMClusterParser::GetVideoBuffers() {
if (ready_buffer_upper_bound_ == kNoDecodeTimestamp())
UpdateReadyBuffers();
return video_.ready_buffers();
}
const WebMClusterParser::TextBufferQueueMap&
WebMClusterParser::GetTextBuffers() {
if (ready_buffer_upper_bound_ == kNoDecodeTimestamp())
UpdateReadyBuffers();
// Translate our |text_track_map_| into |text_buffers_map_|, inserting rows in
// the output only for non-empty ready_buffer() queues in |text_track_map_|.
text_buffers_map_.clear();
for (TextTrackMap::const_iterator itr = text_track_map_.begin();
itr != text_track_map_.end();
++itr) {
const BufferQueue& text_buffers = itr->second.ready_buffers();
if (!text_buffers.empty())
text_buffers_map_.insert(std::make_pair(itr->first, text_buffers));
}
return text_buffers_map_;
}
base::TimeDelta WebMClusterParser::TryGetEncodedAudioDuration(
int64_t WebMClusterParser::TryGetEncodedAudioDuration(
const uint8_t* data,
int size) {
@ -179,24 +146,22 @@ base::TimeDelta WebMClusterParser::TryGetEncodedAudioDuration(
// TODO(wolenetz/chcunningham): Implement duration reading for Vorbis. See
// motivations in http://crbug.com/396634.
return kNoTimestamp();
return kNoTimestamp;
}
base::TimeDelta WebMClusterParser::ReadOpusDuration(const uint8_t* data,
int size) {
int64_t WebMClusterParser::ReadOpusDuration(const uint8_t* data, int size) {
// Masks and constants for Opus packets. See
// https://tools.ietf.org/html/rfc6716#page-14
static const uint8_t kTocConfigMask = 0xf8;
static const uint8_t kTocFrameCountCodeMask = 0x03;
static const uint8_t kFrameCountMask = 0x3f;
static const base::TimeDelta kPacketDurationMax =
base::TimeDelta::FromMilliseconds(120);
static const int64_t kPacketDurationMax = 120;
if (size < 1) {
LIMITED_DLOG(INFO, num_duration_errors_, kMaxDurationErrorLogs)
<< "Invalid zero-byte Opus packet; demuxed block duration may be "
"imprecise.";
return kNoTimestamp();
return kNoTimestamp;
}
// Frame count type described by last 2 bits of Opus TOC byte.
@ -217,7 +182,7 @@ base::TimeDelta WebMClusterParser::ReadOpusDuration(const uint8_t* data,
LIMITED_DLOG(INFO, num_duration_errors_, kMaxDurationErrorLogs)
<< "Second byte missing from 'Code 3' Opus packet; demuxed block "
"duration may be imprecise.";
return kNoTimestamp();
return kNoTimestamp;
}
frame_count = data[1] & kFrameCountMask;
@ -226,7 +191,7 @@ base::TimeDelta WebMClusterParser::ReadOpusDuration(const uint8_t* data,
LIMITED_DLOG(INFO, num_duration_errors_, kMaxDurationErrorLogs)
<< "Illegal 'Code 3' Opus packet with frame count zero; demuxed "
"block duration may be imprecise.";
return kNoTimestamp();
return kNoTimestamp;
}
break;
@ -234,7 +199,7 @@ base::TimeDelta WebMClusterParser::ReadOpusDuration(const uint8_t* data,
LIMITED_DLOG(INFO, num_duration_errors_, kMaxDurationErrorLogs)
<< "Unexpected Opus frame count type: " << frame_count_type << "; "
<< "demuxed block duration may be imprecise.";
return kNoTimestamp();
return kNoTimestamp;
}
int opusConfig = (data[0] & kTocConfigMask) >> 3;
@ -242,8 +207,7 @@ base::TimeDelta WebMClusterParser::ReadOpusDuration(const uint8_t* data,
CHECK_LT(opusConfig, static_cast<int>(arraysize(kOpusFrameDurationsMu)));
DCHECK_GT(frame_count, 0);
base::TimeDelta duration = base::TimeDelta::FromMicroseconds(
kOpusFrameDurationsMu[opusConfig] * frame_count);
int64_t duration = kOpusFrameDurationsMu[opusConfig] * frame_count;
if (duration > kPacketDurationMax) {
// Intentionally allowing packet to pass through for now. Decoder should
@ -251,8 +215,8 @@ base::TimeDelta WebMClusterParser::ReadOpusDuration(const uint8_t* data,
// things go sideways.
LIMITED_DLOG(INFO, num_duration_errors_, kMaxDurationErrorLogs)
<< "Warning, demuxed Opus packet with encoded duration: "
<< duration.InMilliseconds() << "ms. Should be no greater than "
<< kPacketDurationMax.InMilliseconds() << "ms.";
<< duration << "ms. Should be no greater than "
<< kPacketDurationMax << "ms.";
}
return duration;
@ -261,7 +225,7 @@ base::TimeDelta WebMClusterParser::ReadOpusDuration(const uint8_t* data,
WebMParserClient* WebMClusterParser::OnListStart(int id) {
if (id == kWebMIdCluster) {
cluster_timecode_ = -1;
cluster_start_time_ = kNoTimestamp();
cluster_start_time_ = kNoTimestamp;
} else if (id == kWebMIdBlockGroup) {
block_data_.reset();
block_data_size_ = -1;
@ -444,9 +408,9 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
}
Track* track = NULL;
StreamParserBuffer::Type buffer_type = DemuxerStream::AUDIO;
StreamType stream_type = kStreamAudio;
std::string encryption_key_id;
base::TimeDelta encoded_duration = kNoTimestamp();
int64_t encoded_duration = kNoTimestamp;
if (track_num == audio_.track_num()) {
track = &audio_;
encryption_key_id = audio_encryption_key_id_;
@ -456,7 +420,7 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
} else if (track_num == video_.track_num()) {
track = &video_;
encryption_key_id = video_encryption_key_id_;
buffer_type = DemuxerStream::VIDEO;
stream_type = kStreamVideo;
} else if (ignored_tracks_.find(track_num) != ignored_tracks_.end()) {
return true;
} else if (Track* const text_track = FindTextTrack(track_num)) {
@ -465,7 +429,7 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
if (block_duration < 0) // not specified
return false;
track = text_track;
buffer_type = DemuxerStream::TEXT;
stream_type = kStreamText;
} else {
LOG(ERROR) << "Unexpected track number " << track_num;
return false;
@ -473,11 +437,10 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
last_block_timecode_ = timecode;
base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds(
(cluster_timecode_ + timecode) * timecode_multiplier_);
int64_t timestamp = (cluster_timecode_ + timecode) * timecode_multiplier_;
scoped_refptr<StreamParserBuffer> buffer;
if (buffer_type != DemuxerStream::TEXT) {
scoped_refptr<MediaSample> buffer;
if (stream_type != kStreamText) {
// The first bit of the flags is set when a SimpleBlock contains only
// keyframes. If this is a Block, then inspection of the payload is
// necessary to determine whether it contains a keyframe or not.
@ -499,16 +462,13 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
return false;
}
// TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId
// type with remapped bytestream track numbers and allow multiple tracks as
// applicable. See https://crbug.com/341581.
buffer = StreamParserBuffer::CopyFrom(
data + data_offset, size - data_offset,
additional, additional_size,
is_keyframe, buffer_type, track_num);
buffer = MediaSample::CopyFrom(data + data_offset, size - data_offset,
additional, additional_size, is_keyframe);
if (decrypt_config)
buffer->set_decrypt_config(decrypt_config.Pass());
if (decrypt_config) {
// TODO(kqyang): Decrypt it if it is encrypted.
buffer->set_is_encrypted(true);
}
} else {
std::string id, settings, content;
WebMWebVTTParser::Parse(data, size, &id, &settings, &content);
@ -518,25 +478,18 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
settings.begin(), settings.end(),
&side_data);
// TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId
// type with remapped bytestream track numbers and allow multiple tracks as
// applicable. See https://crbug.com/341581.
buffer = StreamParserBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(content.data()),
content.length(),
&side_data[0],
side_data.size(),
true, buffer_type, track_num);
buffer = MediaSample::CopyFrom(
reinterpret_cast<const uint8_t*>(content.data()), content.length(),
&side_data[0], side_data.size(), true);
}
buffer->set_timestamp(timestamp);
if (cluster_start_time_ == kNoTimestamp())
buffer->set_pts(timestamp);
if (cluster_start_time_ == kNoTimestamp)
cluster_start_time_ = timestamp;
base::TimeDelta block_duration_time_delta = kNoTimestamp();
int64_t block_duration_time_delta = kNoTimestamp;
if (block_duration >= 0) {
block_duration_time_delta = base::TimeDelta::FromMicroseconds(
block_duration * timecode_multiplier_);
block_duration_time_delta = block_duration * timecode_multiplier_;
}
// Prefer encoded duration over BlockGroup->BlockDuration or
@ -550,126 +503,77 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
// as Block Timecode deltas, or once the whole cluster is parsed in the case
// of the last Block in the cluster. See Track::AddBuffer and
// ApplyDurationEstimateIfNeeded().
if (encoded_duration != kNoTimestamp()) {
DCHECK(encoded_duration != kInfiniteDuration());
DCHECK(encoded_duration > base::TimeDelta());
if (encoded_duration != kNoTimestamp) {
DCHECK(encoded_duration != kInfiniteDuration);
DCHECK(encoded_duration > 0);
buffer->set_duration(encoded_duration);
DVLOG(3) << __FUNCTION__ << " : "
<< "Using encoded duration " << encoded_duration.InSecondsF();
<< "Using encoded duration " << encoded_duration;
if (block_duration_time_delta != kNoTimestamp()) {
base::TimeDelta duration_difference =
if (block_duration_time_delta != kNoTimestamp) {
int64_t duration_difference =
block_duration_time_delta - encoded_duration;
const auto kWarnDurationDiff =
base::TimeDelta::FromMicroseconds(timecode_multiplier_ * 2);
if (duration_difference.magnitude() > kWarnDurationDiff) {
const auto kWarnDurationDiff = timecode_multiplier_ * 2;
if (duration_difference > kWarnDurationDiff) {
LIMITED_DLOG(INFO, num_duration_errors_, kMaxDurationErrorLogs)
<< "BlockDuration (" << block_duration_time_delta.InMilliseconds()
<< "BlockDuration (" << block_duration_time_delta
<< "ms) differs significantly from encoded duration ("
<< encoded_duration.InMilliseconds() << "ms).";
<< encoded_duration << "ms).";
}
}
} else if (block_duration_time_delta != kNoTimestamp()) {
} else if (block_duration_time_delta != kNoTimestamp) {
buffer->set_duration(block_duration_time_delta);
} else {
DCHECK_NE(buffer_type, DemuxerStream::TEXT);
buffer->set_duration(track->default_duration());
}
if (discard_padding != 0) {
buffer->set_discard_padding(std::make_pair(
base::TimeDelta(),
base::TimeDelta::FromMicroseconds(discard_padding / 1000)));
}
return track->AddBuffer(buffer);
}
WebMClusterParser::Track::Track(int track_num,
bool is_video,
base::TimeDelta default_duration)
int64_t default_duration,
const MediaParser::NewSampleCB& new_sample_cb)
: track_num_(track_num),
is_video_(is_video),
default_duration_(default_duration),
estimated_next_frame_duration_(kNoTimestamp()) {
DCHECK(default_duration_ == kNoTimestamp() ||
default_duration_ > base::TimeDelta());
estimated_next_frame_duration_(kNoTimestamp),
new_sample_cb_(new_sample_cb) {
DCHECK(default_duration_ == kNoTimestamp || default_duration_ > 0);
}
WebMClusterParser::Track::~Track() {}
DecodeTimestamp WebMClusterParser::Track::GetReadyUpperBound() {
DCHECK(ready_buffers_.empty());
if (last_added_buffer_missing_duration_.get())
return last_added_buffer_missing_duration_->GetDecodeTimestamp();
return DecodeTimestamp::FromPresentationTime(base::TimeDelta::Max());
}
void WebMClusterParser::Track::ExtractReadyBuffers(
const DecodeTimestamp before_timestamp) {
DCHECK(ready_buffers_.empty());
DCHECK(DecodeTimestamp() <= before_timestamp);
DCHECK(kNoDecodeTimestamp() != before_timestamp);
if (buffers_.empty())
return;
if (buffers_.back()->GetDecodeTimestamp() < before_timestamp) {
// All of |buffers_| are ready.
ready_buffers_.swap(buffers_);
DVLOG(3) << __FUNCTION__ << " : " << track_num_ << " All "
<< ready_buffers_.size() << " are ready: before upper bound ts "
<< before_timestamp.InSecondsF();
return;
}
// Not all of |buffers_| are ready yet. Move any that are ready to
// |ready_buffers_|.
while (true) {
const scoped_refptr<StreamParserBuffer>& buffer = buffers_.front();
if (buffer->GetDecodeTimestamp() >= before_timestamp)
break;
ready_buffers_.push_back(buffer);
buffers_.pop_front();
DCHECK(!buffers_.empty());
}
DVLOG(3) << __FUNCTION__ << " : " << track_num_ << " Only "
<< ready_buffers_.size() << " ready, " << buffers_.size()
<< " at or after upper bound ts " << before_timestamp.InSecondsF();
}
bool WebMClusterParser::Track::AddBuffer(
const scoped_refptr<StreamParserBuffer>& buffer) {
const scoped_refptr<MediaSample>& buffer) {
DVLOG(2) << "AddBuffer() : " << track_num_
<< " ts " << buffer->timestamp().InSecondsF()
<< " dur " << buffer->duration().InSecondsF()
<< " ts " << buffer->pts()
<< " dur " << buffer->duration()
<< " kf " << buffer->is_key_frame()
<< " size " << buffer->data_size();
if (last_added_buffer_missing_duration_.get()) {
base::TimeDelta derived_duration =
buffer->timestamp() - last_added_buffer_missing_duration_->timestamp();
int64_t derived_duration =
buffer->pts() - last_added_buffer_missing_duration_->pts();
last_added_buffer_missing_duration_->set_duration(derived_duration);
DVLOG(2) << "AddBuffer() : applied derived duration to held-back buffer : "
<< " ts "
<< last_added_buffer_missing_duration_->timestamp().InSecondsF()
<< last_added_buffer_missing_duration_->pts()
<< " dur "
<< last_added_buffer_missing_duration_->duration().InSecondsF()
<< last_added_buffer_missing_duration_->duration()
<< " kf " << last_added_buffer_missing_duration_->is_key_frame()
<< " size " << last_added_buffer_missing_duration_->data_size();
scoped_refptr<StreamParserBuffer> updated_buffer =
scoped_refptr<MediaSample> updated_buffer =
last_added_buffer_missing_duration_;
last_added_buffer_missing_duration_ = NULL;
if (!QueueBuffer(updated_buffer))
return false;
}
if (buffer->duration() == kNoTimestamp()) {
if (buffer->duration() == kNoTimestamp) {
last_added_buffer_missing_duration_ = buffer;
DVLOG(2) << "AddBuffer() : holding back buffer that is missing duration";
return true;
@ -682,46 +586,37 @@ void WebMClusterParser::Track::ApplyDurationEstimateIfNeeded() {
if (!last_added_buffer_missing_duration_.get())
return;
base::TimeDelta estimated_duration = GetDurationEstimate();
int64_t estimated_duration = GetDurationEstimate();
last_added_buffer_missing_duration_->set_duration(estimated_duration);
if (is_video_) {
// Exposing estimation so splicing/overlap frame processing can make
// informed decisions downstream.
// TODO(chcunningham): Set this for audio as well in later change where
// audio is switched to max estimation and splicing is disabled.
last_added_buffer_missing_duration_->set_is_duration_estimated(true);
// TODO(kqyang): Should we wait for the next cluster to set the duration?
// last_added_buffer_missing_duration_->set_is_duration_estimated(true);
}
LIMITED_LOG(INFO, num_duration_estimates_, kMaxDurationEstimateLogs)
<< "Estimating WebM block duration to be "
<< estimated_duration.InMilliseconds()
<< estimated_duration
<< "ms for the last (Simple)Block in the Cluster for this Track. Use "
"BlockGroups with BlockDurations at the end of each Track in a "
"Cluster to avoid estimation.";
DVLOG(2) << __FUNCTION__ << " new dur : ts "
<< last_added_buffer_missing_duration_->timestamp().InSecondsF()
<< last_added_buffer_missing_duration_->pts()
<< " dur "
<< last_added_buffer_missing_duration_->duration().InSecondsF()
<< last_added_buffer_missing_duration_->duration()
<< " kf " << last_added_buffer_missing_duration_->is_key_frame()
<< " size " << last_added_buffer_missing_duration_->data_size();
// Don't use the applied duration as a future estimation (don't use
// QueueBuffer() here.)
buffers_.push_back(last_added_buffer_missing_duration_);
new_sample_cb_.Run(track_num_, last_added_buffer_missing_duration_);
last_added_buffer_missing_duration_ = NULL;
}
void WebMClusterParser::Track::ClearReadyBuffers() {
// Note that |buffers_| are kept and |estimated_next_frame_duration_| is not
// reset here.
ready_buffers_.clear();
}
void WebMClusterParser::Track::Reset() {
ClearReadyBuffers();
buffers_.clear();
last_added_buffer_missing_duration_ = NULL;
}
@ -749,19 +644,12 @@ bool WebMClusterParser::Track::IsKeyframe(const uint8_t* data, int size) const {
}
bool WebMClusterParser::Track::QueueBuffer(
const scoped_refptr<StreamParserBuffer>& buffer) {
const scoped_refptr<MediaSample>& buffer) {
DCHECK(!last_added_buffer_missing_duration_.get());
// WebMClusterParser::OnBlock() gives LOG and parse error on decreasing
// block timecode detection within a cluster. Therefore, we should not see
// those here.
DecodeTimestamp previous_buffers_timestamp = buffers_.empty() ?
DecodeTimestamp() : buffers_.back()->GetDecodeTimestamp();
CHECK(previous_buffers_timestamp <= buffer->GetDecodeTimestamp());
base::TimeDelta duration = buffer->duration();
if (duration < base::TimeDelta() || duration == kNoTimestamp()) {
LOG(ERROR) << "Invalid buffer duration: " << duration.InSecondsF();
int64_t duration = buffer->duration();
if (duration < 0 || duration == kNoTimestamp) {
LOG(ERROR) << "Invalid buffer duration: " << duration;
return false;
}
@ -774,9 +662,9 @@ bool WebMClusterParser::Track::QueueBuffer(
// the over-estimated duration of the previous frame.
// TODO(chcunningham): Use max for audio and disable splicing whenever
// estimated buffers are encountered.
if (duration > base::TimeDelta()) {
base::TimeDelta orig_duration_estimate = estimated_next_frame_duration_;
if (estimated_next_frame_duration_ == kNoTimestamp()) {
if (duration > 0) {
int64_t orig_duration_estimate = estimated_next_frame_duration_;
if (estimated_next_frame_duration_ == kNoTimestamp) {
estimated_next_frame_duration_ = duration;
} else if (is_video_) {
estimated_next_frame_duration_ =
@ -792,45 +680,33 @@ bool WebMClusterParser::Track::QueueBuffer(
<< " -> "
<< estimated_next_frame_duration_
<< " at timestamp: "
<< buffer->GetDecodeTimestamp().InSecondsF();
<< buffer->dts();
}
}
buffers_.push_back(buffer);
new_sample_cb_.Run(track_num_, buffer);
return true;
}
base::TimeDelta WebMClusterParser::Track::GetDurationEstimate() {
base::TimeDelta duration = estimated_next_frame_duration_;
if (duration != kNoTimestamp()) {
int64_t WebMClusterParser::Track::GetDurationEstimate() {
int64_t duration = estimated_next_frame_duration_;
if (duration != kNoTimestamp) {
DVLOG(3) << __FUNCTION__ << " : using estimated duration";
} else {
DVLOG(3) << __FUNCTION__ << " : using hardcoded default duration";
if (is_video_) {
duration = base::TimeDelta::FromMilliseconds(
kDefaultVideoBufferDurationInMs);
duration = kDefaultVideoBufferDurationInMs * kMicrosecondsPerMillisecond;
} else {
duration = base::TimeDelta::FromMilliseconds(
kDefaultAudioBufferDurationInMs);
duration = kDefaultAudioBufferDurationInMs * kMicrosecondsPerMillisecond;
}
}
DCHECK(duration > base::TimeDelta());
DCHECK(duration != kNoTimestamp());
DCHECK(duration > 0);
DCHECK(duration != kNoTimestamp);
return duration;
}
void WebMClusterParser::ClearTextTrackReadyBuffers() {
text_buffers_map_.clear();
for (TextTrackMap::iterator it = text_track_map_.begin();
it != text_track_map_.end();
++it) {
it->second.ClearReadyBuffers();
}
}
void WebMClusterParser::ResetTextTracks() {
ClearTextTrackReadyBuffers();
for (TextTrackMap::iterator it = text_track_map_.begin();
it != text_track_map_.end();
++it) {
@ -838,37 +714,6 @@ void WebMClusterParser::ResetTextTracks() {
}
}
void WebMClusterParser::UpdateReadyBuffers() {
DCHECK(ready_buffer_upper_bound_ == kNoDecodeTimestamp());
DCHECK(text_buffers_map_.empty());
if (cluster_ended_) {
audio_.ApplyDurationEstimateIfNeeded();
video_.ApplyDurationEstimateIfNeeded();
// Per OnBlock(), all text buffers should already have valid durations, so
// there is no need to call ApplyDurationEstimateIfNeeded() on text tracks
// here.
ready_buffer_upper_bound_ =
DecodeTimestamp::FromPresentationTime(base::TimeDelta::Max());
DCHECK(ready_buffer_upper_bound_ == audio_.GetReadyUpperBound());
DCHECK(ready_buffer_upper_bound_ == video_.GetReadyUpperBound());
} else {
ready_buffer_upper_bound_ = std::min(audio_.GetReadyUpperBound(),
video_.GetReadyUpperBound());
DCHECK(DecodeTimestamp() <= ready_buffer_upper_bound_);
DCHECK(kNoDecodeTimestamp() != ready_buffer_upper_bound_);
}
// Prepare each track's ready buffers for retrieval.
audio_.ExtractReadyBuffers(ready_buffer_upper_bound_);
video_.ExtractReadyBuffers(ready_buffer_upper_bound_);
for (TextTrackMap::iterator itr = text_track_map_.begin();
itr != text_track_map_.end();
++itr) {
itr->second.ExtractReadyBuffers(ready_buffer_upper_bound_);
}
}
WebMClusterParser::Track*
WebMClusterParser::FindTextTrack(int track_num) {
const TextTrackMap::iterator it = text_track_map_.find(track_num);

View File

@ -11,9 +11,8 @@
#include <string>
#include "packager/base/memory/scoped_ptr.h"
#include "packager/media/base/audio_decoder_config.h"
#include "packager/media/base/stream_parser.h"
#include "packager/media/base/stream_parser_buffer.h"
#include "packager/media/base/media_parser.h"
#include "packager/media/base/media_sample.h"
#include "packager/media/formats/webm/webm_parser.h"
#include "packager/media/formats/webm/webm_tracks_parser.h"
@ -22,10 +21,6 @@ namespace media {
class WebMClusterParser : public WebMParserClient {
public:
typedef StreamParser::TrackId TrackId;
typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
typedef std::map<TrackId, const BufferQueue> TextBufferQueueMap;
// Numbers chosen to estimate the duration of a buffer if none is set and
// there is not enough information to get a better estimate.
enum {
@ -49,29 +44,18 @@ class WebMClusterParser : public WebMParserClient {
public:
Track(int track_num,
bool is_video,
base::TimeDelta default_duration);
int64_t default_duration,
const MediaParser::NewSampleCB& new_sample_cb);
~Track();
int track_num() const { return track_num_; }
// If a buffer is currently held aside pending duration calculation, returns
// its decode timestamp. Otherwise, returns kInfiniteDuration().
DecodeTimestamp GetReadyUpperBound();
// Prepares |ready_buffers_| for retrieval. Prior to calling,
// |ready_buffers_| must be empty. Moves all |buffers_| with decode
// timestamp before |before_timestamp| to |ready_buffers_|, preserving their
// order.
void ExtractReadyBuffers(const DecodeTimestamp before_timestamp);
const BufferQueue& ready_buffers() const { return ready_buffers_; }
// If |last_added_buffer_missing_duration_| is set, updates its duration
// relative to |buffer|'s timestamp, and adds it to |buffers_| and unsets
// |last_added_buffer_missing_duration_|. Then, if |buffer| is missing
// duration, saves |buffer| into |last_added_buffer_missing_duration_|, or
// otherwise adds |buffer| to |buffers_|.
bool AddBuffer(const scoped_refptr<StreamParserBuffer>& buffer);
bool AddBuffer(const scoped_refptr<MediaSample>& buffer);
// If |last_added_buffer_missing_duration_| is set, updates its duration to
// be non-kNoTimestamp() value of |estimated_next_frame_duration_| or a
@ -80,14 +64,8 @@ class WebMClusterParser : public WebMParserClient {
// emit all buffers in a media segment before signaling end of segment.)
void ApplyDurationEstimateIfNeeded();
// Clears |ready_buffers_| (use ExtractReadyBuffers() to fill it again).
// Leaves as-is |buffers_| and any possibly held-aside buffer that is
// missing duration.
void ClearReadyBuffers();
// Clears all buffer state, including any possibly held-aside buffer that
// was missing duration, and all contents of |buffers_| and
// |ready_buffers_|.
// was missing duration, and all contents of |buffers_|.
void Reset();
// Helper function used to inspect block data to determine if the
@ -96,18 +74,18 @@ class WebMClusterParser : public WebMParserClient {
// |size| indicates the number of bytes in |data|.
bool IsKeyframe(const uint8_t* data, int size) const;
base::TimeDelta default_duration() const { return default_duration_; }
int64_t default_duration() const { return default_duration_; }
private:
// Helper that sanity-checks |buffer| duration, updates
// |estimated_next_frame_duration_|, and adds |buffer| to |buffers_|.
// Returns false if |buffer| failed sanity check and therefore was not added
// to |buffers_|. Returns true otherwise.
bool QueueBuffer(const scoped_refptr<StreamParserBuffer>& buffer);
bool QueueBuffer(const scoped_refptr<MediaSample>& buffer);
// Helper that calculates the buffer duration to use in
// ApplyDurationEstimateIfNeeded().
base::TimeDelta GetDurationEstimate();
int64_t GetDurationEstimate();
// Counts the number of estimated durations used in this track. Used to
// prevent log spam for LOG()s about estimated duration.
@ -120,26 +98,19 @@ class WebMClusterParser : public WebMParserClient {
// that have not yet been extracted into |ready_buffers_|. Note that up to
// one additional buffer missing duration may be tracked by
// |last_added_buffer_missing_duration_|.
BufferQueue buffers_;
scoped_refptr<StreamParserBuffer> last_added_buffer_missing_duration_;
// Buffers in (decode) timestamp order that were previously parsed into and
// extracted from |buffers_|. Buffers are moved from |buffers_| to
// |ready_buffers_| by ExtractReadyBuffers() if they are below a specified
// upper bound timestamp. Track users can therefore extract only those
// parsed buffers which are "ready" for emission (all before some maximum
// timestamp).
BufferQueue ready_buffers_;
scoped_refptr<MediaSample> last_added_buffer_missing_duration_;
// If kNoTimestamp(), then |estimated_next_frame_duration_| will be used.
base::TimeDelta default_duration_;
int64_t default_duration_;
// If kNoTimestamp(), then a default value will be used. This estimate is
// the maximum (for video), or minimum (for audio) duration seen so far for
// this track, and is used only if |default_duration_| is kNoTimestamp().
// TODO(chcunningham): Use maximum for audio too, adding checks to disable
// splicing when these estimates are observed in SourceBufferStream.
base::TimeDelta estimated_next_frame_duration_;
int64_t estimated_next_frame_duration_;
MediaParser::NewSampleCB new_sample_cb_;
};
typedef std::map<int, Track> TextTrackMap;
@ -147,14 +118,15 @@ class WebMClusterParser : public WebMParserClient {
public:
WebMClusterParser(int64_t timecode_scale,
int audio_track_num,
base::TimeDelta audio_default_duration,
int64_t audio_default_duration,
int video_track_num,
base::TimeDelta video_default_duration,
int64_t video_default_duration,
const WebMTracksParser::TextTracks& text_tracks,
const std::set<int64_t>& ignored_tracks,
const std::string& audio_encryption_key_id,
const std::string& video_encryption_key_id,
const AudioCodec audio_codec);
const AudioCodec audio_codec,
const MediaParser::NewSampleCB& new_sample_cb);
~WebMClusterParser() override;
// Resets the parser state so it can accept a new cluster.
@ -167,35 +139,7 @@ class WebMClusterParser : public WebMParserClient {
// Returns the number of bytes parsed on success.
int Parse(const uint8_t* buf, int size);
base::TimeDelta cluster_start_time() const { return cluster_start_time_; }
// Get the current ready buffers resulting from Parse().
// If the parse reached the end of cluster and the last buffer was held aside
// due to missing duration, the buffer is given an estimated duration and
// included in the result.
// Otherwise, if there are is a buffer held aside due to missing duration for
// any of the tracks, no buffers with same or greater (decode) timestamp will
// be included in the buffers.
// The returned deques are cleared by Parse() or Reset() and updated by the
// next calls to Get{Audio,Video}Buffers().
// If no Parse() or Reset() has occurred since the last call to Get{Audio,
// Video,Text}Buffers(), then the previous BufferQueue& is returned again
// without any recalculation.
const BufferQueue& GetAudioBuffers();
const BufferQueue& GetVideoBuffers();
// Constructs and returns a subset of |text_track_map_| containing only
// tracks with non-empty buffer queues produced by the last Parse() and
// filtered to exclude any buffers that have (decode) timestamp same or
// greater than the lowest (decode) timestamp across all tracks of any buffer
// held aside due to missing duration (unless the end of cluster has been
// reached).
// The returned map is cleared by Parse() or Reset() and updated by the next
// call to GetTextBuffers().
// If no Parse() or Reset() has occurred since the last call to
// GetTextBuffers(), then the previous TextBufferQueueMap& is returned again
// without any recalculation.
const TextBufferQueueMap& GetTextBuffers();
int64_t cluster_start_time() const { return cluster_start_time_; }
// Returns true if the last Parse() call stopped at the end of a cluster.
bool cluster_ended() const { return cluster_ended_; }
@ -228,22 +172,6 @@ class WebMClusterParser : public WebMParserClient {
// Resets the Track objects associated with each text track.
void ResetTextTracks();
// Clears the the ready buffers associated with each text track.
void ClearTextTrackReadyBuffers();
// Helper method for Get{Audio,Video,Text}Buffers() that recomputes
// |ready_buffer_upper_bound_| and calls ExtractReadyBuffers() on each track.
// If |cluster_ended_| is true, first applies duration estimate if needed for
// |audio_| and |video_| and sets |ready_buffer_upper_bound_| to
// kInfiniteDuration(). Otherwise, sets |ready_buffer_upper_bound_| to the
// minimum upper bound across |audio_| and |video_|. (Text tracks can have no
// buffers missing duration, so they are not involved in calculating the upper
// bound.)
// Parse() or Reset() must be called between calls to UpdateReadyBuffers() to
// clear each track's ready buffers and to reset |ready_buffer_upper_bound_|
// to kNoDecodeTimestamp().
void UpdateReadyBuffers();
// Search for the indicated track_num among the text tracks. Returns NULL
// if that track num is not a text track.
Track* FindTextTrack(int track_num);
@ -256,11 +184,11 @@ class WebMClusterParser : public WebMParserClient {
// Cluster we parse, so we can't simply use the delta of the first Block in
// the next Cluster). Avoid calling if encrypted; may produce unexpected
// output. See implementation for supported codecs.
base::TimeDelta TryGetEncodedAudioDuration(const uint8_t* data, int size);
int64_t TryGetEncodedAudioDuration(const uint8_t* data, int size);
// Reads Opus packet header to determine packet duration. Duration returned
// as TimeDelta or kNoTimestamp() upon failure to read duration from packet.
base::TimeDelta ReadOpusDuration(const uint8_t* data, int size);
int64_t ReadOpusDuration(const uint8_t* data, int size);
// Tracks the number of LOGs made in process of reading encoded
// duration. Useful to prevent log spam.
@ -290,26 +218,13 @@ class WebMClusterParser : public WebMParserClient {
bool discard_padding_set_ = false;
int64_t cluster_timecode_ = -1;
base::TimeDelta cluster_start_time_;
int64_t cluster_start_time_;
bool cluster_ended_ = false;
Track audio_;
Track video_;
TextTrackMap text_track_map_;
// Subset of |text_track_map_| maintained by GetTextBuffers(), and cleared by
// ClearTextTrackReadyBuffers(). Callers of GetTextBuffers() get a const-ref
// to this member.
TextBufferQueueMap text_buffers_map_;
// Limits the range of buffers returned by Get{Audio,Video,Text}Buffers() to
// this exclusive upper bound. Set to kNoDecodeTimestamp(), meaning not yet
// calculated, by Reset() and Parse(). If kNoDecodeTimestamp(), then
// Get{Audio,Video,Text}Buffers() will calculate it to be the minimum (decode)
// timestamp across all tracks' |last_buffer_missing_duration_|, or
// kInfiniteDuration() if no buffers are currently missing duration.
DecodeTimestamp ready_buffer_upper_bound_;
DISALLOW_IMPLICIT_CONSTRUCTORS(WebMClusterParser);
};

View File

@ -15,9 +15,8 @@
#include "packager/base/bind.h"
#include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h"
#include "packager/media/base/audio_decoder_config.h"
#include "packager/media/base/decrypt_config.h"
#include "packager/media/base/timestamp_constants.h"
#include "packager/media/base/timestamp.h"
#include "packager/media/formats/webm/cluster_builder.h"
#include "packager/media/formats/webm/opus_packet_builder.h"
#include "packager/media/formats/webm/webm_constants.h"
@ -29,10 +28,15 @@ using ::testing::StrictMock;
using ::testing::Mock;
using ::testing::_;
namespace {
const int64_t kMicrosecondsPerMillisecond = 1000;
} // namespace
namespace edash_packager {
namespace media {
typedef WebMTracksParser::TextTracks TextTracks;
typedef std::map<uint32_t, BufferQueue> TextBufferQueueMap;
// Matchers for verifying common media log entry strings.
MATCHER_P(OpusPacketDurationTooHigh, actual_duration_ms, "") {
@ -115,9 +119,11 @@ const BlockInfo kDefaultBlockInfo[] = {
const uint8_t kEncryptedFrame[] = {
// Block is encrypted
0x01,
// IV
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
// Some dummy encrypted data
0x01,
};
scoped_ptr<Cluster> CreateCluster(int timecode,
const BlockInfo* block_info,
@ -125,7 +131,8 @@ scoped_ptr<Cluster> CreateCluster(int timecode,
ClusterBuilder cb;
cb.SetClusterTimecode(0);
uint8_t kDefaultBlockData[] = { 0x00 };
// Default block data for audio, video and text.
uint8_t kDefaultBlockData[] = {0x00, 0x0A, 0x01, 0x0D, 0x02};
for (int i = 0; i < block_count; i++) {
const uint8_t* data;
@ -171,15 +178,15 @@ scoped_ptr<Cluster> CreateEncryptedCluster(int bytes_to_write) {
return cb.Finish();
}
bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers,
const WebMClusterParser::BufferQueue& video_buffers,
const WebMClusterParser::BufferQueue& text_buffers,
bool VerifyBuffersHelper(const BufferQueue& audio_buffers,
const BufferQueue& video_buffers,
const BufferQueue& text_buffers,
const BlockInfo* block_info,
int block_count) {
int buffer_count = audio_buffers.size() + video_buffers.size() +
text_buffers.size();
if (block_count != buffer_count) {
DVLOG(1) << __FUNCTION__ << " : block_count (" << block_count
LOG(ERROR) << __FUNCTION__ << " : block_count (" << block_count
<< ") mismatches buffer_count (" << buffer_count << ")";
return false;
}
@ -188,73 +195,48 @@ bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers,
size_t video_offset = 0;
size_t text_offset = 0;
for (int i = 0; i < block_count; i++) {
const WebMClusterParser::BufferQueue* buffers = NULL;
const BufferQueue* buffers = NULL;
size_t* offset;
StreamParserBuffer::Type expected_type = DemuxerStream::UNKNOWN;
if (block_info[i].track_num == kAudioTrackNum) {
buffers = &audio_buffers;
offset = &audio_offset;
expected_type = DemuxerStream::AUDIO;
} else if (block_info[i].track_num == kVideoTrackNum) {
buffers = &video_buffers;
offset = &video_offset;
expected_type = DemuxerStream::VIDEO;
} else if (block_info[i].track_num == kTextTrackNum) {
buffers = &text_buffers;
offset = &text_offset;
expected_type = DemuxerStream::TEXT;
} else {
LOG(ERROR) << "Unexpected track number " << block_info[i].track_num;
return false;
}
if (*offset >= buffers->size()) {
DVLOG(1) << __FUNCTION__ << " : Too few buffers (" << buffers->size()
LOG(ERROR) << __FUNCTION__ << " : Too few buffers (" << buffers->size()
<< ") for track_num (" << block_info[i].track_num
<< "), expected at least " << *offset + 1 << " buffers";
return false;
}
scoped_refptr<StreamParserBuffer> buffer = (*buffers)[(*offset)++];
scoped_refptr<MediaSample> buffer = (*buffers)[(*offset)++];
EXPECT_EQ(block_info[i].timestamp, buffer->timestamp().InMilliseconds());
EXPECT_EQ(std::abs(block_info[i].duration),
buffer->duration().InMillisecondsF());
EXPECT_EQ(expected_type, buffer->type());
EXPECT_EQ(block_info[i].track_num, buffer->track_id());
EXPECT_EQ(block_info[i].timestamp * kMicrosecondsPerMillisecond,
buffer->pts());
EXPECT_EQ(std::abs(block_info[i].duration) * kMicrosecondsPerMillisecond,
buffer->duration());
}
return true;
}
bool VerifyBuffers(const scoped_ptr<WebMClusterParser>& parser,
const BlockInfo* block_info,
int block_count) {
const WebMClusterParser::TextBufferQueueMap& text_map =
parser->GetTextBuffers();
const WebMClusterParser::BufferQueue* text_buffers;
const WebMClusterParser::BufferQueue no_text_buffers;
if (!text_map.empty())
text_buffers = &(text_map.rbegin()->second);
else
text_buffers = &no_text_buffers;
return VerifyBuffers(parser->GetAudioBuffers(),
parser->GetVideoBuffers(),
*text_buffers,
block_info,
block_count);
}
bool VerifyTextBuffers(const scoped_ptr<WebMClusterParser>& parser,
const BlockInfo* block_info_ptr,
bool VerifyTextBuffers(const BlockInfo* block_info_ptr,
int block_count,
int text_track_num,
const WebMClusterParser::BufferQueue& text_buffers) {
const BufferQueue& text_buffers) {
const BlockInfo* const block_info_end = block_info_ptr + block_count;
typedef WebMClusterParser::BufferQueue::const_iterator TextBufferIter;
typedef BufferQueue::const_iterator TextBufferIter;
TextBufferIter buffer_iter = text_buffers.begin();
const TextBufferIter buffer_end = text_buffers.end();
@ -267,30 +249,19 @@ bool VerifyTextBuffers(const scoped_ptr<WebMClusterParser>& parser,
EXPECT_FALSE(block_info.use_simple_block);
EXPECT_FALSE(buffer_iter == buffer_end);
const scoped_refptr<StreamParserBuffer> buffer = *buffer_iter++;
EXPECT_EQ(block_info.timestamp, buffer->timestamp().InMilliseconds());
EXPECT_EQ(std::abs(block_info.duration),
buffer->duration().InMillisecondsF());
EXPECT_EQ(DemuxerStream::TEXT, buffer->type());
EXPECT_EQ(text_track_num, buffer->track_id());
const scoped_refptr<MediaSample> buffer = *buffer_iter++;
EXPECT_EQ(block_info.timestamp * kMicrosecondsPerMillisecond,
buffer->pts());
EXPECT_EQ(std::abs(block_info.duration) * kMicrosecondsPerMillisecond,
buffer->duration());
}
EXPECT_TRUE(buffer_iter == buffer_end);
return true;
}
void VerifyEncryptedBuffer(scoped_refptr<StreamParserBuffer> buffer) {
EXPECT_TRUE(buffer->decrypt_config());
EXPECT_EQ(static_cast<unsigned long>(DecryptConfig::kDecryptionKeySize),
buffer->decrypt_config()->iv().length());
}
void AppendToEnd(const WebMClusterParser::BufferQueue& src,
WebMClusterParser::BufferQueue* dest) {
for (WebMClusterParser::BufferQueue::const_iterator itr = src.begin();
itr != src.end(); ++itr) {
dest->push_back(*itr);
}
void VerifyEncryptedBuffer(scoped_refptr<MediaSample> buffer) {
EXPECT_TRUE(buffer->is_encrypted());
}
} // namespace
@ -301,23 +272,43 @@ class WebMClusterParserTest : public testing::Test {
protected:
void ResetParserToHaveDefaultDurations() {
base::TimeDelta default_audio_duration = base::TimeDelta::FromMilliseconds(
kTestAudioFrameDefaultDurationInMs);
base::TimeDelta default_video_duration = base::TimeDelta::FromMilliseconds(
kTestVideoFrameDefaultDurationInMs);
ASSERT_GE(default_audio_duration, base::TimeDelta());
ASSERT_GE(default_video_duration, base::TimeDelta());
ASSERT_NE(kNoTimestamp(), default_audio_duration);
ASSERT_NE(kNoTimestamp(), default_video_duration);
int64_t default_audio_duration =
kTestAudioFrameDefaultDurationInMs * kMicrosecondsPerMillisecond;
int64_t default_video_duration =
kTestVideoFrameDefaultDurationInMs * kMicrosecondsPerMillisecond;
ASSERT_GE(default_audio_duration, 0);
ASSERT_GE(default_video_duration, 0);
ASSERT_NE(kNoTimestamp, default_audio_duration);
ASSERT_NE(kNoTimestamp, default_video_duration);
parser_.reset(CreateParserWithDefaultDurationsAndOptionalTextTracks(
default_audio_duration, default_video_duration));
}
bool NewSampleEvent(uint32_t track_id,
const scoped_refptr<MediaSample>& sample) {
switch (track_id) {
case kAudioTrackNum:
audio_buffers_.push_back(sample);
break;
case kVideoTrackNum:
video_buffers_.push_back(sample);
break;
case kTextTrackNum:
case kTextTrackNum + 1:
text_buffers_map_[track_id].push_back(sample);
break;
default:
LOG(ERROR) << "Unexpected track number " << track_id;
return false;
}
return true;
}
// Helper that hard-codes some non-varying constructor parameters.
WebMClusterParser* CreateParserHelper(
base::TimeDelta audio_default_duration,
base::TimeDelta video_default_duration,
int64_t audio_default_duration,
int64_t video_default_duration,
const WebMTracksParser::TextTracks& text_tracks,
const std::set<int64_t>& ignored_tracks,
const std::string& audio_encryption_key_id,
@ -326,12 +317,14 @@ class WebMClusterParserTest : public testing::Test {
return new WebMClusterParser(
kTimecodeScale, kAudioTrackNum, audio_default_duration, kVideoTrackNum,
video_default_duration, text_tracks, ignored_tracks,
audio_encryption_key_id, video_encryption_key_id, audio_codec);
audio_encryption_key_id, video_encryption_key_id, audio_codec,
base::Bind(&WebMClusterParserTest::NewSampleEvent,
base::Unretained(this)));
}
// Create a default version of the parser for test.
WebMClusterParser* CreateDefaultParser() {
return CreateParserHelper(kNoTimestamp(), kNoTimestamp(), TextTracks(),
return CreateParserHelper(kNoTimestamp, kNoTimestamp, TextTracks(),
std::set<int64_t>(), std::string(), std::string(),
kUnknownAudioCodec);
}
@ -339,8 +332,8 @@ class WebMClusterParserTest : public testing::Test {
// Create a parser for test with custom audio and video default durations, and
// optionally custom text tracks.
WebMClusterParser* CreateParserWithDefaultDurationsAndOptionalTextTracks(
base::TimeDelta audio_default_duration,
base::TimeDelta video_default_duration,
int64_t audio_default_duration,
int64_t video_default_duration,
const WebMTracksParser::TextTracks& text_tracks = TextTracks()) {
return CreateParserHelper(audio_default_duration, video_default_duration,
text_tracks, std::set<int64_t>(), std::string(),
@ -350,7 +343,7 @@ class WebMClusterParserTest : public testing::Test {
// Create a parser for test with custom ignored tracks.
WebMClusterParser* CreateParserWithIgnoredTracks(
std::set<int64_t>& ignored_tracks) {
return CreateParserHelper(kNoTimestamp(), kNoTimestamp(), TextTracks(),
return CreateParserHelper(kNoTimestamp, kNoTimestamp, TextTracks(),
ignored_tracks, std::string(), std::string(),
kUnknownAudioCodec);
}
@ -360,22 +353,31 @@ class WebMClusterParserTest : public testing::Test {
const std::string& audio_encryption_key_id,
const std::string& video_encryption_key_id,
const AudioCodec audio_codec) {
return CreateParserHelper(kNoTimestamp(), kNoTimestamp(), TextTracks(),
return CreateParserHelper(kNoTimestamp, kNoTimestamp, TextTracks(),
std::set<int64_t>(), audio_encryption_key_id,
video_encryption_key_id, audio_codec);
}
bool VerifyBuffers(const BlockInfo* block_info, int block_count) {
bool result = VerifyBuffersHelper(audio_buffers_, video_buffers_,
text_buffers_map_[kTextTrackNum],
block_info, block_count);
audio_buffers_.clear();
video_buffers_.clear();
text_buffers_map_.clear();
return result;
}
scoped_ptr<WebMClusterParser> parser_;
BufferQueue audio_buffers_;
BufferQueue video_buffers_;
TextBufferQueueMap text_buffers_map_;
private:
DISALLOW_COPY_AND_ASSIGN(WebMClusterParserTest);
};
TEST_F(WebMClusterParserTest, HeldBackBufferHoldsBackAllTracks) {
// If a buffer is missing duration and is being held back, then all other
// tracks' buffers that have same or higher (decode) timestamp should be held
// back too to keep the timestamps emitted for a cluster monotonically
// non-decreasing and in same order as parsed.
TEST_F(WebMClusterParserTest, TracksWithSampleMissingDuration) {
InSequence s;
// Reset the parser to have 3 tracks: text, video (no default frame duration),
@ -384,12 +386,12 @@ TEST_F(WebMClusterParserTest, HeldBackBufferHoldsBackAllTracks) {
text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum),
TextTrackConfig(kTextSubtitles, "", "",
"")));
base::TimeDelta default_audio_duration =
base::TimeDelta::FromMilliseconds(kTestAudioFrameDefaultDurationInMs);
ASSERT_GE(default_audio_duration, base::TimeDelta());
ASSERT_NE(kNoTimestamp(), default_audio_duration);
int64_t default_audio_duration = kTestAudioFrameDefaultDurationInMs;
ASSERT_GE(default_audio_duration, 0);
ASSERT_NE(kNoTimestamp, default_audio_duration);
parser_.reset(CreateParserWithDefaultDurationsAndOptionalTextTracks(
default_audio_duration, kNoTimestamp(), text_tracks));
default_audio_duration * kMicrosecondsPerMillisecond, kNoTimestamp,
text_tracks));
const int kExpectedVideoEstimationInMs = 33;
@ -405,15 +407,28 @@ TEST_F(WebMClusterParserTest, HeldBackBufferHoldsBackAllTracks) {
{kAudioTrackNum, 83, kTestAudioFrameDefaultDurationInMs, true, NULL, 0},
};
// Samples are not emitted in the same order as |kBlockInfo| due to missing of
// duration in some samples.
const BlockInfo kExpectedBlockInfo[] = {
{kAudioTrackNum, 0, 23, false, NULL, 0},
{kTextTrackNum, 10, 42, false, NULL, 0},
{kAudioTrackNum, 23, kTestAudioFrameDefaultDurationInMs, true, NULL, 0},
{kVideoTrackNum, 0, 33, true, NULL, 0},
{kAudioTrackNum, 36, kTestAudioFrameDefaultDurationInMs, true, NULL, 0},
{kVideoTrackNum, 33, 33, true, NULL, 0},
{kAudioTrackNum, 70, kTestAudioFrameDefaultDurationInMs, true, NULL, 0},
{kVideoTrackNum, 66, kExpectedVideoEstimationInMs, true, NULL, 0},
{kAudioTrackNum, 83, kTestAudioFrameDefaultDurationInMs, true, NULL, 0},
};
const int kExpectedBuffersOnPartialCluster[] = {
0, // Video simple block without DefaultDuration should be held back
0, // Audio buffer ready, but not emitted because its TS >= held back video
0, // Text buffer ready, but not emitted because its TS >= held back video
0, // 2nd audio buffer ready, also not emitted for same reason as first
4, // All previous buffers emitted, 2nd video held back with no duration
4, // 2nd video still has no duration, 3rd audio ready but not emitted
6, // All previous buffers emitted, 3rd video held back with no duration
6, // 3rd video still has no duration, 4th audio ready but not emitted
1, // Audio buffer ready
2, // Text buffer ready
3, // 2nd audio buffer ready
4, // 1st video emitted, 2nd video held back with no duration
5, // 3rd audio ready
6, // 2nd video emitted, 3rd video held back with no duration
7, // 4th audio ready
9, // Cluster end emits all buffers and 3rd video's duration is estimated
};
@ -451,8 +466,8 @@ TEST_F(WebMClusterParserTest, HeldBackBufferHoldsBackAllTracks) {
EXPECT_LT(0, result);
}
EXPECT_TRUE(VerifyBuffers(parser_, kBlockInfo,
kExpectedBuffersOnPartialCluster[i]));
EXPECT_TRUE(
VerifyBuffers(kExpectedBlockInfo, kExpectedBuffersOnPartialCluster[i]));
}
}
@ -468,13 +483,13 @@ TEST_F(WebMClusterParserTest, Reset) {
EXPECT_GT(result, 0);
EXPECT_LT(result, cluster->size());
ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count - 1));
ASSERT_TRUE(VerifyBuffers(kDefaultBlockInfo, block_count - 1));
parser_->Reset();
// Now parse a whole cluster to verify that all the blocks will get parsed.
result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kDefaultBlockInfo, block_count));
}
TEST_F(WebMClusterParserTest, ParseClusterWithSingleCall) {
@ -483,16 +498,16 @@ TEST_F(WebMClusterParserTest, ParseClusterWithSingleCall) {
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kDefaultBlockInfo, block_count));
}
TEST_F(WebMClusterParserTest, ParseClusterWithMultipleCalls) {
int block_count = arraysize(kDefaultBlockInfo);
scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count));
WebMClusterParser::BufferQueue audio_buffers;
WebMClusterParser::BufferQueue video_buffers;
const WebMClusterParser::BufferQueue no_text_buffers;
BufferQueue audio_buffers;
BufferQueue video_buffers;
const BufferQueue no_text_buffers;
const uint8_t* data = cluster->data();
int size = cluster->size();
@ -511,17 +526,12 @@ TEST_F(WebMClusterParserTest, ParseClusterWithMultipleCalls) {
continue;
}
AppendToEnd(parser_->GetAudioBuffers(), &audio_buffers);
AppendToEnd(parser_->GetVideoBuffers(), &video_buffers);
parse_size = default_parse_size;
data += result;
size -= result;
}
ASSERT_TRUE(VerifyBuffers(audio_buffers, video_buffers,
no_text_buffers, kDefaultBlockInfo,
block_count));
ASSERT_TRUE(VerifyBuffers(kDefaultBlockInfo, block_count));
}
// Verify that both BlockGroups with the BlockDuration before the Block
@ -552,7 +562,7 @@ TEST_F(WebMClusterParserTest, ParseBlockGroup) {
int result = parser_->Parse(kClusterData, kClusterSize);
EXPECT_EQ(kClusterSize, result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count));
}
TEST_F(WebMClusterParserTest, ParseSimpleBlockAndBlockGroupMixture) {
@ -568,7 +578,7 @@ TEST_F(WebMClusterParserTest, ParseSimpleBlockAndBlockGroupMixture) {
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count));
}
TEST_F(WebMClusterParserTest, IgnoredTracks) {
@ -601,7 +611,7 @@ TEST_F(WebMClusterParserTest, IgnoredTracks) {
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kOutputBlockInfo, output_block_count));
ASSERT_TRUE(VerifyBuffers(kOutputBlockInfo, output_block_count));
}
TEST_F(WebMClusterParserTest, ParseTextTracks) {
@ -612,7 +622,7 @@ TEST_F(WebMClusterParserTest, ParseTextTracks) {
"")));
parser_.reset(CreateParserWithDefaultDurationsAndOptionalTextTracks(
kNoTimestamp(), kNoTimestamp(), text_tracks));
kNoTimestamp, kNoTimestamp, text_tracks));
const BlockInfo kInputBlockInfo[] = {
{kAudioTrackNum, 0, 23, true, NULL, 0},
@ -630,7 +640,7 @@ TEST_F(WebMClusterParserTest, ParseTextTracks) {
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kInputBlockInfo, input_block_count));
ASSERT_TRUE(VerifyBuffers(kInputBlockInfo, input_block_count));
}
TEST_F(WebMClusterParserTest, TextTracksSimpleBlock) {
@ -641,7 +651,7 @@ TEST_F(WebMClusterParserTest, TextTracksSimpleBlock) {
"")));
parser_.reset(CreateParserWithDefaultDurationsAndOptionalTextTracks(
kNoTimestamp(), kNoTimestamp(), text_tracks));
kNoTimestamp, kNoTimestamp, text_tracks));
const BlockInfo kInputBlockInfo[] = {
{ kTextTrackNum, 33, 42, true },
@ -670,7 +680,7 @@ TEST_F(WebMClusterParserTest, ParseMultipleTextTracks) {
"")));
parser_.reset(CreateParserWithDefaultDurationsAndOptionalTextTracks(
kNoTimestamp(), kNoTimestamp(), text_tracks));
kNoTimestamp, kNoTimestamp, text_tracks));
const BlockInfo kInputBlockInfo[] = {
{kAudioTrackNum, 0, 23, true, NULL, 0},
@ -690,16 +700,12 @@ TEST_F(WebMClusterParserTest, ParseMultipleTextTracks) {
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
const WebMClusterParser::TextBufferQueueMap& text_map =
parser_->GetTextBuffers();
for (WebMClusterParser::TextBufferQueueMap::const_iterator itr =
text_map.begin();
itr != text_map.end();
++itr) {
for (TextBufferQueueMap::const_iterator itr = text_buffers_map_.begin();
itr != text_buffers_map_.end(); ++itr) {
const TextTracks::const_iterator find_result =
text_tracks.find(itr->first);
ASSERT_TRUE(find_result != text_tracks.end());
ASSERT_TRUE(VerifyTextBuffers(parser_, kInputBlockInfo, input_block_count,
ASSERT_TRUE(VerifyTextBuffers(kInputBlockInfo, input_block_count,
itr->first, itr->second));
}
}
@ -712,14 +718,14 @@ TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_EQ(1UL, parser_->GetVideoBuffers().size());
scoped_refptr<StreamParserBuffer> buffer = parser_->GetVideoBuffers()[0];
ASSERT_EQ(1UL, video_buffers_.size());
scoped_refptr<MediaSample> buffer = video_buffers_[0];
VerifyEncryptedBuffer(buffer);
}
TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
scoped_ptr<Cluster> cluster(
CreateEncryptedCluster(sizeof(kEncryptedFrame) - 1));
CreateEncryptedCluster(sizeof(kEncryptedFrame) - 2));
parser_.reset(CreateParserWithKeyIdsAndAudioCodec(
std::string(), "video_key_id", kUnknownAudioCodec));
@ -753,7 +759,7 @@ TEST_F(WebMClusterParserTest, ParseInvalidTextBlockGroupWithoutDuration) {
"")));
parser_.reset(CreateParserWithDefaultDurationsAndOptionalTextTracks(
kNoTimestamp(), kNoTimestamp(), text_tracks));
kNoTimestamp, kNoTimestamp, text_tracks));
const BlockInfo kBlockInfo[] = {
{ kTextTrackNum, 33, -42, false },
@ -791,14 +797,14 @@ TEST_F(WebMClusterParserTest, ParseWithDefaultDurationsSimpleBlocks) {
int result = parser_->Parse(cluster->data(), cluster->size() - 1);
EXPECT_GT(result, 0);
EXPECT_LT(result, cluster->size());
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count - 1));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count - 1));
parser_->Reset();
// Now parse a whole cluster to verify that all the blocks will get parsed.
result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count));
}
TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsSimpleBlocks) {
@ -809,7 +815,7 @@ TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsSimpleBlocks) {
// last block in a cluster is estimated independently for each track in the
// cluster. For video tracks we use the maximum seen so far. For audio we use
// the the minimum.
// TODO(chcunningham): Move audio over to use the maximum.
// TODO: Move audio over to use the maximum.
const int kExpectedAudioEstimationInMs = 22;
const int kExpectedVideoEstimationInMs = 34;
@ -834,16 +840,16 @@ TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsSimpleBlocks) {
int result = parser_->Parse(cluster1->data(), cluster1->size() - 1);
EXPECT_GT(result, 0);
EXPECT_LT(result, cluster1->size());
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo1, block_count1 - 3));
EXPECT_EQ(3UL, parser_->GetAudioBuffers().size());
EXPECT_EQ(1UL, parser_->GetVideoBuffers().size());
EXPECT_EQ(3UL, audio_buffers_.size());
EXPECT_EQ(1UL, video_buffers_.size());
ASSERT_TRUE(VerifyBuffers(kBlockInfo1, block_count1 - 3));
parser_->Reset();
// Now parse the full first cluster and verify all the blocks are parsed.
result = parser_->Parse(cluster1->data(), cluster1->size());
EXPECT_EQ(cluster1->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo1, block_count1));
ASSERT_TRUE(VerifyBuffers(kBlockInfo1, block_count1));
// Verify that the estimated frame duration is tracked across clusters for
// each track.
@ -858,7 +864,7 @@ TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsSimpleBlocks) {
scoped_ptr<Cluster> cluster2(CreateCluster(0, kBlockInfo2, block_count2));
result = parser_->Parse(cluster2->data(), cluster2->size());
EXPECT_EQ(cluster2->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo2, block_count2));
ASSERT_TRUE(VerifyBuffers(kBlockInfo2, block_count2));
}
TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsBlockGroups) {
@ -894,16 +900,16 @@ TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsBlockGroups) {
int result = parser_->Parse(cluster1->data(), cluster1->size() - 1);
EXPECT_GT(result, 0);
EXPECT_LT(result, cluster1->size());
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo1, block_count1 - 3));
EXPECT_EQ(3UL, parser_->GetAudioBuffers().size());
EXPECT_EQ(1UL, parser_->GetVideoBuffers().size());
EXPECT_EQ(3UL, audio_buffers_.size());
EXPECT_EQ(1UL, video_buffers_.size());
ASSERT_TRUE(VerifyBuffers(kBlockInfo1, block_count1 - 3));
parser_->Reset();
// Now parse the full first cluster and verify all the blocks are parsed.
result = parser_->Parse(cluster1->data(), cluster1->size());
EXPECT_EQ(cluster1->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo1, block_count1));
ASSERT_TRUE(VerifyBuffers(kBlockInfo1, block_count1));
// Verify that the estimated frame duration is tracked across clusters for
// each track.
@ -916,7 +922,7 @@ TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsBlockGroups) {
scoped_ptr<Cluster> cluster2(CreateCluster(0, kBlockInfo2, block_count2));
result = parser_->Parse(cluster2->data(), cluster2->size());
EXPECT_EQ(cluster2->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo2, block_count2));
ASSERT_TRUE(VerifyBuffers(kBlockInfo2, block_count2));
}
// TODO(wolenetz): Is parser behavior correct? See http://crbug.com/363433.
@ -952,14 +958,14 @@ TEST_F(WebMClusterParserTest,
int result = parser_->Parse(cluster->data(), cluster->size() - 1);
EXPECT_GT(result, 0);
EXPECT_LT(result, cluster->size());
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count - 1));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count - 1));
parser_->Reset();
// Now parse a whole cluster to verify that all the blocks will get parsed.
result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count));
}
TEST_F(WebMClusterParserTest,
@ -982,7 +988,7 @@ TEST_F(WebMClusterParserTest,
scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count));
}
TEST_F(WebMClusterParserTest,
@ -998,7 +1004,7 @@ TEST_F(WebMClusterParserTest,
scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count));
}
TEST_F(WebMClusterParserTest, ReadOpusDurationsSimpleBlockAtEndOfCluster) {
@ -1022,7 +1028,7 @@ TEST_F(WebMClusterParserTest, ReadOpusDurationsSimpleBlockAtEndOfCluster) {
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count));
loop_count++;
}
@ -1040,9 +1046,11 @@ TEST_F(WebMClusterParserTest, PreferOpusDurationsOverBlockDurations) {
parser_.reset(CreateParserWithKeyIdsAndAudioCodec(
std::string(), std::string(), kCodecOpus));
// Setting BlockDuration != Opus duration to see which one the parser uses.
int block_duration_ms = packet_ptr->duration_ms() + 10;
BlockInfo block_infos[] = {{kAudioTrackNum,
0,
block_duration_ms,
static_cast<double>(block_duration_ms),
false, // Not a SimpleBlock.
packet_ptr->data(),
packet_ptr->size()}};
@ -1056,7 +1064,7 @@ TEST_F(WebMClusterParserTest, PreferOpusDurationsOverBlockDurations) {
// duration to be that of the Opus packet to verify it was preferred.
block_infos[0].duration = packet_ptr->duration_ms();
ASSERT_TRUE(VerifyBuffers(parser_, block_infos, block_count));
ASSERT_TRUE(VerifyBuffers(block_infos, block_count));
loop_count++;
}
@ -1090,7 +1098,7 @@ TEST_F(WebMClusterParserTest, DontReadEncodedDurationWhenEncrypted) {
EXPECT_EQ(cluster->size(), result);
// Will verify that duration of buffer matches that of BlockDuration.
ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
ASSERT_TRUE(VerifyBuffers(kBlockInfo, block_count));
}
} // namespace media