Add plumbing and support for crossfading StreamParserBuffers.

Per the MSE specification adds a fade-out section to StreamParserBuffer
which will contain buffers for crossfading.

Modifies SourceBufferStream::GetNextBuffer() to properly return these
buffers with a config change in between the fade out and fade in
sections.

BUG=334493
TEST=New unittests.
NOTRY=true

Review URL: https://codereview.chromium.org/125543002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@245413 0039d316-1c4b-4281-b951-d872f2087c98
parent 9efa2f01
...@@ -9,6 +9,15 @@ ...@@ -9,6 +9,15 @@
namespace media { namespace media {
static bool HasNestedFadeOutPreroll(
const std::vector<scoped_refptr<StreamParserBuffer> >& fade_out_preroll) {
for (size_t i = 0; i < fade_out_preroll.size(); ++i) {
if (!fade_out_preroll[i]->GetFadeOutPreroll().empty())
return true;
}
return false;
}
scoped_refptr<StreamParserBuffer> StreamParserBuffer::CreateEOSBuffer() { scoped_refptr<StreamParserBuffer> StreamParserBuffer::CreateEOSBuffer() {
return make_scoped_refptr(new StreamParserBuffer(NULL, 0, NULL, 0, false)); return make_scoped_refptr(new StreamParserBuffer(NULL, 0, NULL, 0, false));
} }
...@@ -63,4 +72,15 @@ void StreamParserBuffer::SetConfigId(int config_id) { ...@@ -63,4 +72,15 @@ void StreamParserBuffer::SetConfigId(int config_id) {
config_id_ = config_id; config_id_ = config_id;
} }
const std::vector<scoped_refptr<StreamParserBuffer> >&
StreamParserBuffer::GetFadeOutPreroll() const {
return fade_out_preroll_;
}
void StreamParserBuffer::SetFadeOutPreroll(
const std::vector<scoped_refptr<StreamParserBuffer> >& fade_out_preroll) {
DCHECK(!HasNestedFadeOutPreroll(fade_out_preroll));
fade_out_preroll_ = fade_out_preroll;
}
} // namespace media } // namespace media
...@@ -33,6 +33,13 @@ class MEDIA_EXPORT StreamParserBuffer : public DecoderBuffer { ...@@ -33,6 +33,13 @@ class MEDIA_EXPORT StreamParserBuffer : public DecoderBuffer {
int GetConfigId() const; int GetConfigId() const;
void SetConfigId(int config_id); void SetConfigId(int config_id);
// Buffers to be exhausted before using the data in this DecoderBuffer. Used
// to implement the Audio Splice Frame Algorithm per the MSE specification.
const std::vector<scoped_refptr<StreamParserBuffer> >& GetFadeOutPreroll()
const;
void SetFadeOutPreroll(
const std::vector<scoped_refptr<StreamParserBuffer> >& fade_out_preroll);
private: private:
StreamParserBuffer(const uint8* data, int data_size, StreamParserBuffer(const uint8* data, int data_size,
const uint8* side_data, int side_data_size, const uint8* side_data, int side_data_size,
...@@ -42,6 +49,8 @@ class MEDIA_EXPORT StreamParserBuffer : public DecoderBuffer { ...@@ -42,6 +49,8 @@ class MEDIA_EXPORT StreamParserBuffer : public DecoderBuffer {
bool is_keyframe_; bool is_keyframe_;
base::TimeDelta decode_timestamp_; base::TimeDelta decode_timestamp_;
int config_id_; int config_id_;
std::vector<scoped_refptr<StreamParserBuffer> > fade_out_preroll_;
DISALLOW_COPY_AND_ASSIGN(StreamParserBuffer); DISALLOW_COPY_AND_ASSIGN(StreamParserBuffer);
}; };
......
...@@ -345,12 +345,6 @@ class ChunkDemuxerStream : public DemuxerStream { ...@@ -345,12 +345,6 @@ class ChunkDemuxerStream : public DemuxerStream {
void CompletePendingReadIfPossible_Locked(); void CompletePendingReadIfPossible_Locked();
// Gets the value to pass to the next Read() callback. Returns true if
// |status| and |buffer| should be passed to the callback. False indicates
// that |status| and |buffer| were not set and more data is needed.
bool GetNextBuffer_Locked(DemuxerStream::Status* status,
scoped_refptr<StreamParserBuffer>* buffer);
// Specifies the type of the stream. // Specifies the type of the stream.
Type type_; Type type_;
......
...@@ -29,6 +29,15 @@ static bool AllowSameTimestamp( ...@@ -29,6 +29,15 @@ static bool AllowSameTimestamp(
return prev_is_keyframe || !current_is_keyframe; return prev_is_keyframe || !current_is_keyframe;
} }
// Returns the config ID of |buffer| if |buffer| has no fade out preroll or
// |index| is out of range. Otherwise returns the config ID for the fade out
// preroll buffer at position |index|.
static int GetConfigId(StreamParserBuffer* buffer, size_t index) {
return index < buffer->GetFadeOutPreroll().size()
? buffer->GetFadeOutPreroll()[index]->GetConfigId()
: buffer->GetConfigId();
}
// Helper class representing a range of buffered data. All buffers in a // Helper class representing a range of buffered data. All buffers in a
// SourceBufferRange are ordered sequentially in presentation order with no // SourceBufferRange are ordered sequentially in presentation order with no
// gaps. // gaps.
...@@ -346,7 +355,8 @@ SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config, ...@@ -346,7 +355,8 @@ SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config,
last_output_buffer_timestamp_(kNoTimestamp()), last_output_buffer_timestamp_(kNoTimestamp()),
max_interbuffer_distance_(kNoTimestamp()), max_interbuffer_distance_(kNoTimestamp()),
memory_limit_(kDefaultAudioMemoryLimit), memory_limit_(kDefaultAudioMemoryLimit),
config_change_pending_(false) { config_change_pending_(false),
fade_out_preroll_index_(0) {
DCHECK(audio_config.IsValidConfig()); DCHECK(audio_config.IsValidConfig());
audio_configs_.push_back(audio_config); audio_configs_.push_back(audio_config);
} }
...@@ -368,7 +378,8 @@ SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config, ...@@ -368,7 +378,8 @@ SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config,
last_output_buffer_timestamp_(kNoTimestamp()), last_output_buffer_timestamp_(kNoTimestamp()),
max_interbuffer_distance_(kNoTimestamp()), max_interbuffer_distance_(kNoTimestamp()),
memory_limit_(kDefaultVideoMemoryLimit), memory_limit_(kDefaultVideoMemoryLimit),
config_change_pending_(false) { config_change_pending_(false),
fade_out_preroll_index_(0) {
DCHECK(video_config.IsValidConfig()); DCHECK(video_config.IsValidConfig());
video_configs_.push_back(video_config); video_configs_.push_back(video_config);
} }
...@@ -391,7 +402,8 @@ SourceBufferStream::SourceBufferStream(const TextTrackConfig& text_config, ...@@ -391,7 +402,8 @@ SourceBufferStream::SourceBufferStream(const TextTrackConfig& text_config,
last_output_buffer_timestamp_(kNoTimestamp()), last_output_buffer_timestamp_(kNoTimestamp()),
max_interbuffer_distance_(kNoTimestamp()), max_interbuffer_distance_(kNoTimestamp()),
memory_limit_(kDefaultAudioMemoryLimit), memory_limit_(kDefaultAudioMemoryLimit),
config_change_pending_(false) { config_change_pending_(false),
fade_out_preroll_index_(0) {
} }
SourceBufferStream::~SourceBufferStream() { SourceBufferStream::~SourceBufferStream() {
...@@ -670,6 +682,8 @@ void SourceBufferStream::ResetSeekState() { ...@@ -670,6 +682,8 @@ void SourceBufferStream::ResetSeekState() {
track_buffer_.clear(); track_buffer_.clear();
config_change_pending_ = false; config_change_pending_ = false;
last_output_buffer_timestamp_ = kNoTimestamp(); last_output_buffer_timestamp_ = kNoTimestamp();
fade_out_preroll_index_ = 0;
fade_in_buffer_ = NULL;
} }
bool SourceBufferStream::ShouldSeekToStartOfBuffered( bool SourceBufferStream::ShouldSeekToStartOfBuffered(
...@@ -1097,17 +1111,75 @@ void SourceBufferStream::OnSetDuration(base::TimeDelta duration) { ...@@ -1097,17 +1111,75 @@ void SourceBufferStream::OnSetDuration(base::TimeDelta duration) {
SourceBufferStream::Status SourceBufferStream::GetNextBuffer( SourceBufferStream::Status SourceBufferStream::GetNextBuffer(
scoped_refptr<StreamParserBuffer>* out_buffer) { scoped_refptr<StreamParserBuffer>* out_buffer) {
if (!fade_in_buffer_) {
const SourceBufferStream::Status status = GetNextBufferInternal(out_buffer);
// Just return if GetNextBufferInternal() failed or there's no fade out
// preroll, there's nothing else to do.
if (status != SourceBufferStream::kSuccess ||
(*out_buffer)->GetFadeOutPreroll().empty()) {
return status;
}
// Setup fade in buffer and fall through into splice frame buffer handling.
fade_out_preroll_index_ = 0;
fade_in_buffer_.swap(*out_buffer);
}
DCHECK(fade_in_buffer_);
const std::vector<scoped_refptr<StreamParserBuffer> >& fade_out_preroll =
fade_in_buffer_->GetFadeOutPreroll();
// Are there any fade out buffers left to hand out?
if (fade_out_preroll_index_ < fade_out_preroll.size()) {
// Account for config changes which occur between fade out buffers.
if (current_config_index_ !=
fade_out_preroll[fade_out_preroll_index_]->GetConfigId()) {
config_change_pending_ = true;
DVLOG(1) << "Config change (fade out preroll config ID does not match).";
return SourceBufferStream::kConfigChange;
}
*out_buffer = fade_out_preroll[fade_out_preroll_index_++];
return SourceBufferStream::kSuccess;
}
// Did we hand out the last fade out buffer on the last call?
if (fade_out_preroll_index_ == fade_out_preroll.size()) {
fade_out_preroll_index_++;
config_change_pending_ = true;
DVLOG(1) << "Config change (forced for fade in of splice frame).";
return SourceBufferStream::kConfigChange;
}
// All fade out buffers have been handed out and a config change completed, so
// hand out the final buffer for fade in. Because a config change is always
// issued prior to handing out this buffer, any changes in config id have been
// inherently handled.
DCHECK_GT(fade_out_preroll_index_, fade_out_preroll.size());
out_buffer->swap(fade_in_buffer_);
fade_in_buffer_ = NULL;
fade_out_preroll_index_ = 0;
return SourceBufferStream::kSuccess;
}
SourceBufferStream::Status SourceBufferStream::GetNextBufferInternal(
scoped_refptr<StreamParserBuffer>* out_buffer) {
CHECK(!config_change_pending_); CHECK(!config_change_pending_);
if (!track_buffer_.empty()) { if (!track_buffer_.empty()) {
DCHECK(!selected_range_); DCHECK(!selected_range_);
if (track_buffer_.front()->GetConfigId() != current_config_index_) { scoped_refptr<StreamParserBuffer>& next_buffer = track_buffer_.front();
// If the next buffer is an audio splice frame, the next effective config id
// comes from the first fade out preroll buffer.
if (GetConfigId(next_buffer, 0) != current_config_index_) {
config_change_pending_ = true; config_change_pending_ = true;
DVLOG(1) << "Config change (track buffer config ID does not match)."; DVLOG(1) << "Config change (track buffer config ID does not match).";
return kConfigChange; return kConfigChange;
} }
*out_buffer = track_buffer_.front(); *out_buffer = next_buffer;
track_buffer_.pop_front(); track_buffer_.pop_front();
last_output_buffer_timestamp_ = (*out_buffer)->GetDecodeTimestamp(); last_output_buffer_timestamp_ = (*out_buffer)->GetDecodeTimestamp();
...@@ -1343,8 +1415,14 @@ bool SourceBufferStream::UpdateVideoConfig(const VideoDecoderConfig& config) { ...@@ -1343,8 +1415,14 @@ bool SourceBufferStream::UpdateVideoConfig(const VideoDecoderConfig& config) {
void SourceBufferStream::CompleteConfigChange() { void SourceBufferStream::CompleteConfigChange() {
config_change_pending_ = false; config_change_pending_ = false;
if (fade_in_buffer_) {
current_config_index_ =
GetConfigId(fade_in_buffer_, fade_out_preroll_index_);
return;
}
if (!track_buffer_.empty()) { if (!track_buffer_.empty()) {
current_config_index_ = track_buffer_.front()->GetConfigId(); current_config_index_ = GetConfigId(track_buffer_.front(), 0);
return; return;
} }
...@@ -1864,7 +1942,7 @@ bool SourceBufferRange::GetNextBuffer( ...@@ -1864,7 +1942,7 @@ bool SourceBufferRange::GetNextBuffer(
if (!HasNextBuffer()) if (!HasNextBuffer())
return false; return false;
*out_buffer = buffers_.at(next_buffer_index_); *out_buffer = buffers_[next_buffer_index_];
next_buffer_index_++; next_buffer_index_++;
return true; return true;
} }
...@@ -1876,7 +1954,9 @@ bool SourceBufferRange::HasNextBuffer() const { ...@@ -1876,7 +1954,9 @@ bool SourceBufferRange::HasNextBuffer() const {
int SourceBufferRange::GetNextConfigId() const { int SourceBufferRange::GetNextConfigId() const {
DCHECK(HasNextBuffer()); DCHECK(HasNextBuffer());
return buffers_.at(next_buffer_index_)->GetConfigId(); // If the next buffer is an audio splice frame, the next effective config id
// comes from the first fade out preroll buffer.
return GetConfigId(buffers_[next_buffer_index_], 0);
} }
base::TimeDelta SourceBufferRange::GetNextTimestamp() const { base::TimeDelta SourceBufferRange::GetNextTimestamp() const {
...@@ -1887,7 +1967,7 @@ base::TimeDelta SourceBufferRange::GetNextTimestamp() const { ...@@ -1887,7 +1967,7 @@ base::TimeDelta SourceBufferRange::GetNextTimestamp() const {
return kNoTimestamp(); return kNoTimestamp();
} }
return buffers_.at(next_buffer_index_)->GetDecodeTimestamp(); return buffers_[next_buffer_index_]->GetDecodeTimestamp();
} }
bool SourceBufferRange::HasNextBufferPosition() const { bool SourceBufferRange::HasNextBufferPosition() const {
......
...@@ -299,6 +299,11 @@ class MEDIA_EXPORT SourceBufferStream { ...@@ -299,6 +299,11 @@ class MEDIA_EXPORT SourceBufferStream {
Type GetType() const; Type GetType() const;
// See GetNextBuffer() for additional details. The internal method hands out
// buffers from the |track_buffer_| and |selected_range_| without additional
// processing for splice frame buffers; which is handled by GetNextBuffer().
Status GetNextBufferInternal(scoped_refptr<StreamParserBuffer>* out_buffer);
// Callback used to report error strings that can help the web developer // Callback used to report error strings that can help the web developer
// figure out what is wrong with the content. // figure out what is wrong with the content.
LogCB log_cb_; LogCB log_cb_;
...@@ -375,6 +380,15 @@ class MEDIA_EXPORT SourceBufferStream { ...@@ -375,6 +380,15 @@ class MEDIA_EXPORT SourceBufferStream {
// GetCurrentXXXDecoderConfig() has been called. // GetCurrentXXXDecoderConfig() has been called.
bool config_change_pending_; bool config_change_pending_;
// Used by GetNextBuffer() when a buffer with fade out is returned from
// GetNextBufferInternal(). Will be set to the returned buffer and will be
// consumed after the fade out section has been exhausted.
scoped_refptr<StreamParserBuffer> fade_in_buffer_;
// Indicates which of the fade out preroll buffers in |fade_in_buffer_| should
// be handled out next.
size_t fade_out_preroll_index_;
DISALLOW_COPY_AND_ASSIGN(SourceBufferStream); DISALLOW_COPY_AND_ASSIGN(SourceBufferStream);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment