// 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 #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( const NewVideoConfigCB& new_video_config_cb, const EmitBufferCB& emit_buffer_cb) : new_video_config_cb_(new_video_config_cb), emit_buffer_cb_(emit_buffer_cb), has_valid_config_(false), has_valid_frame_(false), last_frame_duration_( base::TimeDelta::FromMilliseconds(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::TimeDelta::FromMilliseconds(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( const scoped_refptr& 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_.push_back(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 buffer = 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(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::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 scoped_refptr& 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 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_.push_back(frame); pts += pts_delta; dts += dts_delta; } discarded_frame_count_ = 0; } } // namespace mp2t } // namespace media