blob: eb181aa342bebcce8c77abe21beac507f98490c2 [file] [log] [blame]
// Copyright 2014 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/formats/mp2t/es_adapter_video.h"
#include <stddef.h>
#include "base/logging.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_decoder_config.h"
#include "media/formats/mp2t/mp2t_common.h"
namespace media {
namespace mp2t {
// Arbitrary decision about the frame duration when there is no previous
// hint about what could be the frame duration.
static const int kDefaultFrameDurationMs = 40;
// To calculate the frame duration, we make an assumption
// that the timestamp of the next frame in presentation order
// is no further than 5 frames away in decode order.
// TODO(damienv): the previous assumption should cover most of the practical
// cases. However, the right way to calculate the frame duration would be
// to emulate the H264 dpb bumping process.
static const size_t kHistorySize = 5;
EsAdapterVideo::EsAdapterVideo(NewVideoConfigCB new_video_config_cb,
EmitBufferCB emit_buffer_cb)
: new_video_config_cb_(std::move(new_video_config_cb)),
emit_buffer_cb_(std::move(emit_buffer_cb)),
has_valid_config_(false),
has_valid_frame_(false),
last_frame_duration_(base::Milliseconds(kDefaultFrameDurationMs)),
buffer_index_(0),
has_valid_initial_timestamp_(false),
discarded_frame_count_(0) {}
EsAdapterVideo::~EsAdapterVideo() {
}
void EsAdapterVideo::Flush() {
ProcessPendingBuffers(true);
}
void EsAdapterVideo::Reset() {
has_valid_config_ = false;
has_valid_frame_ = false;
last_frame_duration_ = base::Milliseconds(kDefaultFrameDurationMs);
config_list_.clear();
buffer_index_ = 0;
buffer_list_.clear();
emitted_pts_.clear();
has_valid_initial_timestamp_ = false;
min_pts_ = base::TimeDelta();
min_dts_ = DecodeTimestamp();
discarded_frame_count_ = 0;
}
void EsAdapterVideo::OnConfigChanged(
const VideoDecoderConfig& video_decoder_config) {
config_list_.push_back(
ConfigEntry(buffer_index_ + buffer_list_.size(), video_decoder_config));
has_valid_config_ = true;
ProcessPendingBuffers(false);
}
bool EsAdapterVideo::OnNewBuffer(
scoped_refptr<StreamParserBuffer> stream_parser_buffer) {
if (stream_parser_buffer->timestamp() == kNoTimestamp) {
if (has_valid_frame_) {
// There is currently no error concealment for a missing timestamp
// in the middle of the stream.
DVLOG(1) << "Missing timestamp in the middle of the stream";
return false;
}
if (!has_valid_initial_timestamp_) {
// MPEG-2 TS requires the first access unit to be given a timestamp.
// However, some streams do not comply with this requirement.
// So simply drop the frame if it is a leading frame with no timestamp.
DVLOG(1)
<< "Stream not compliant: ignoring leading frame with no timestamp";
return true;
}
// In all the other cases, this frame will be replaced by the following
// valid key frame, using timestamp interpolation.
DCHECK(has_valid_initial_timestamp_);
DCHECK_GE(discarded_frame_count_, 1);
discarded_frame_count_++;
return true;
}
// At this point, timestamps of the incoming frame are valid.
if (!has_valid_initial_timestamp_) {
min_pts_ = stream_parser_buffer->timestamp();
min_dts_ = stream_parser_buffer->GetDecodeTimestamp();
has_valid_initial_timestamp_ = true;
}
if (stream_parser_buffer->timestamp() < min_pts_)
min_pts_ = stream_parser_buffer->timestamp();
// Discard the incoming frame:
// - if it is not associated with any config,
// - or if no valid key frame has been found so far.
if (!has_valid_config_ ||
(!has_valid_frame_ && !stream_parser_buffer->is_key_frame())) {
discarded_frame_count_++;
return true;
}
has_valid_frame_ = true;
if (discarded_frame_count_ > 0)
ReplaceDiscardedFrames(*stream_parser_buffer);
buffer_list_.emplace_back(std::move(stream_parser_buffer));
ProcessPendingBuffers(false);
return true;
}
void EsAdapterVideo::ProcessPendingBuffers(bool flush) {
DCHECK(has_valid_config_);
while (!buffer_list_.empty() &&
(flush || buffer_list_.size() > kHistorySize)) {
// Signal a config change, just before emitting the corresponding frame.
if (!config_list_.empty() && config_list_.front().first == buffer_index_) {
new_video_config_cb_.Run(config_list_.front().second);
config_list_.pop_front();
}
scoped_refptr<StreamParserBuffer> buffer = std::move(buffer_list_.front());
buffer_list_.pop_front();
buffer_index_++;
if (buffer->duration() == kNoTimestamp) {
base::TimeDelta next_frame_pts = GetNextFramePts(buffer->timestamp());
if (next_frame_pts == kNoTimestamp) {
// This can happen when emitting the very last buffer
// or if the stream do not meet the assumption behind |kHistorySize|.
DVLOG(LOG_LEVEL_ES) << "Using last frame duration: "
<< last_frame_duration_.InMilliseconds();
buffer->set_duration(last_frame_duration_);
} else {
base::TimeDelta duration = next_frame_pts - buffer->timestamp();
DVLOG(LOG_LEVEL_ES) << "Frame duration: " << duration.InMilliseconds();
buffer->set_duration(duration);
}
}
emitted_pts_.push_back(buffer->timestamp());
if (emitted_pts_.size() > kHistorySize)
emitted_pts_.pop_front();
last_frame_duration_ = buffer->duration();
emit_buffer_cb_.Run(std::move(buffer));
}
}
base::TimeDelta EsAdapterVideo::GetNextFramePts(base::TimeDelta current_pts) {
base::TimeDelta next_pts = kNoTimestamp;
// Consider the timestamps of future frames (in decode order).
// Note: the next frame is not enough when the GOP includes some B frames.
for (BufferQueue::const_iterator it = buffer_list_.begin();
it != buffer_list_.end(); ++it) {
if ((*it)->timestamp() < current_pts)
continue;
if (next_pts == kNoTimestamp || next_pts > (*it)->timestamp())
next_pts = (*it)->timestamp();
}
// Consider the timestamps of previous frames (in decode order).
// In a simple GOP structure with B frames, the frame next to the last B
// frame (in presentation order) is located before in decode order.
for (std::list<base::TimeDelta>::const_iterator it = emitted_pts_.begin();
it != emitted_pts_.end(); ++it) {
if (*it < current_pts)
continue;
if (next_pts == kNoTimestamp || next_pts > *it)
next_pts = *it;
}
return next_pts;
}
void EsAdapterVideo::ReplaceDiscardedFrames(
const StreamParserBuffer& stream_parser_buffer) {
DCHECK_GT(discarded_frame_count_, 0);
DCHECK(stream_parser_buffer.is_key_frame());
// PTS/DTS are interpolated between the min PTS/DTS of discarded frames
// and the PTS/DTS of the first valid buffer.
// Note: |pts_delta| and |dts_delta| are calculated using integer division.
// Interpolation thus accumulutes small errors. However, since timestamps
// are given in microseconds, only a high number of discarded frames
// (in the order of 10000s) could have an impact and create a gap (from MSE
// point of view) between the last interpolated frame and
// |stream_parser_buffer|.
base::TimeDelta pts = min_pts_;
base::TimeDelta pts_delta =
(stream_parser_buffer.timestamp() - pts) / discarded_frame_count_;
DecodeTimestamp dts = min_dts_;
base::TimeDelta dts_delta =
(stream_parser_buffer.GetDecodeTimestamp() - dts) /
discarded_frame_count_;
for (int i = 0; i < discarded_frame_count_; i++) {
scoped_refptr<StreamParserBuffer> frame = StreamParserBuffer::CopyFrom(
stream_parser_buffer.data(), stream_parser_buffer.data_size(),
stream_parser_buffer.is_key_frame(), stream_parser_buffer.type(),
stream_parser_buffer.track_id());
frame->SetDecodeTimestamp(dts);
frame->set_timestamp(pts);
frame->set_duration(pts_delta);
buffer_list_.emplace_back(std::move(frame));
pts += pts_delta;
dts += dts_delta;
}
discarded_frame_count_ = 0;
}
} // namespace mp2t
} // namespace media