Index: media/base/audio_splicer.cc |
diff --git a/media/base/audio_splicer.cc b/media/base/audio_splicer.cc |
deleted file mode 100644 |
index 0976c89432a3baef1ce06606b295b7d840c2bb4d..0000000000000000000000000000000000000000 |
--- a/media/base/audio_splicer.cc |
+++ /dev/null |
@@ -1,560 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include "media/base/audio_splicer.h" |
- |
-#include <stdint.h> |
-#include <cstdlib> |
-#include <deque> |
-#include <utility> |
- |
-#include "base/logging.h" |
-#include "base/macros.h" |
-#include "media/base/audio_buffer.h" |
-#include "media/base/audio_bus.h" |
-#include "media/base/audio_decoder_config.h" |
-#include "media/base/audio_timestamp_helper.h" |
-#include "media/base/media_log.h" |
-#include "media/base/vector_math.h" |
- |
-namespace media { |
- |
-namespace { |
- |
-enum { |
- // Minimum gap size needed before the splicer will take action to |
- // fill a gap. This avoids periodically inserting and then dropping samples |
- // when the buffer timestamps are slightly off because of timestamp rounding |
- // in the source content. Unit is frames. |
- kMinGapSize = 2, |
- |
- // Limits the number of MEDIA_LOG() per sanitizer instance warning the user |
- // about splicer overlaps within |kMaxTimeDeltaInMilliseconds| or gaps larger |
- // than |kMinGapSize| and less than |kMaxTimeDeltaInMilliseconds|. These |
- // warnings may be frequent for some streams, and number of sanitizer |
- // instances may be high, so keep this limit low to help reduce log spam. |
- kMaxSanitizerWarningLogs = 5, |
-}; |
- |
-// AudioBuffer::TrimStart() is not as accurate as the timestamp helper, so |
-// manually adjust the duration and timestamp after trimming. |
-void AccurateTrimStart(int frames_to_trim, |
- const scoped_refptr<AudioBuffer> buffer, |
- const AudioTimestampHelper& timestamp_helper) { |
- buffer->TrimStart(frames_to_trim); |
- buffer->set_timestamp(timestamp_helper.GetTimestamp()); |
-} |
- |
-// Returns an AudioBus whose frame buffer is backed by the provided AudioBuffer. |
-std::unique_ptr<AudioBus> CreateAudioBufferWrapper( |
- const scoped_refptr<AudioBuffer>& buffer) { |
- std::unique_ptr<AudioBus> wrapper = |
- AudioBus::CreateWrapper(buffer->channel_count()); |
- wrapper->set_frames(buffer->frame_count()); |
- for (int ch = 0; ch < buffer->channel_count(); ++ch) { |
- wrapper->SetChannelData( |
- ch, reinterpret_cast<float*>(buffer->channel_data()[ch])); |
- } |
- return wrapper; |
-} |
- |
-} // namespace |
- |
-class AudioStreamSanitizer { |
- public: |
- AudioStreamSanitizer(int samples_per_second, |
- const scoped_refptr<MediaLog>& media_log); |
- ~AudioStreamSanitizer(); |
- |
- // Resets the sanitizer state by clearing the output buffers queue, and |
- // resetting the timestamp helper. |
- void Reset(); |
- |
- // Similar to Reset(), but initializes the timestamp helper with the given |
- // parameters. |
- void ResetTimestampState(int64_t frame_count, base::TimeDelta base_timestamp); |
- |
- // Adds a new buffer full of samples or end of stream buffer to the splicer. |
- // Returns true if the buffer was accepted. False is returned if an error |
- // occurred. |
- bool AddInput(const scoped_refptr<AudioBuffer>& input); |
- |
- // Returns true if the sanitizer has a buffer to return. |
- bool HasNextBuffer() const; |
- |
- // Removes the next buffer from the output buffer queue and returns it; should |
- // only be called if HasNextBuffer() returns true. |
- scoped_refptr<AudioBuffer> GetNextBuffer(); |
- |
- // Returns the total frame count of all buffers available for output. |
- int GetFrameCount() const; |
- |
- const AudioTimestampHelper& timestamp_helper() { |
- return output_timestamp_helper_; |
- } |
- |
- // Transfer all buffers into |output|. Returns false if AddInput() on the |
- // |output| sanitizer fails for any buffer removed from |this|. |
- bool DrainInto(AudioStreamSanitizer* output); |
- |
- private: |
- void AddOutputBuffer(const scoped_refptr<AudioBuffer>& buffer); |
- |
- AudioTimestampHelper output_timestamp_helper_; |
- bool received_end_of_stream_ = false; |
- |
- typedef std::deque<scoped_refptr<AudioBuffer> > BufferQueue; |
- BufferQueue output_buffers_; |
- |
- scoped_refptr<MediaLog> media_log_; |
- |
- // To prevent log spam, counts the number of audio gap or overlaps warned in |
- // logs. |
- int num_warning_logs_ = 0; |
- |
- DISALLOW_ASSIGN(AudioStreamSanitizer); |
-}; |
- |
-AudioStreamSanitizer::AudioStreamSanitizer( |
- int samples_per_second, |
- const scoped_refptr<MediaLog>& media_log) |
- : output_timestamp_helper_(samples_per_second), media_log_(media_log) {} |
- |
-AudioStreamSanitizer::~AudioStreamSanitizer() {} |
- |
-void AudioStreamSanitizer::Reset() { |
- ResetTimestampState(0, kNoTimestamp); |
-} |
- |
-void AudioStreamSanitizer::ResetTimestampState(int64_t frame_count, |
- base::TimeDelta base_timestamp) { |
- output_buffers_.clear(); |
- received_end_of_stream_ = false; |
- output_timestamp_helper_.SetBaseTimestamp(base_timestamp); |
- if (frame_count > 0) |
- output_timestamp_helper_.AddFrames(frame_count); |
-} |
- |
-bool AudioStreamSanitizer::AddInput(const scoped_refptr<AudioBuffer>& input) { |
- DCHECK(!received_end_of_stream_ || input->end_of_stream()); |
- |
- if (input->end_of_stream()) { |
- output_buffers_.push_back(input); |
- received_end_of_stream_ = true; |
- return true; |
- } |
- |
- DCHECK(input->timestamp() != kNoTimestamp); |
- DCHECK(input->duration() > base::TimeDelta()); |
- DCHECK_GT(input->frame_count(), 0); |
- |
- if (output_timestamp_helper_.base_timestamp() == kNoTimestamp) |
- output_timestamp_helper_.SetBaseTimestamp(input->timestamp()); |
- |
- if (output_timestamp_helper_.base_timestamp() > input->timestamp()) { |
- MEDIA_LOG(ERROR, media_log_) |
- << "Audio splicing failed: unexpected timestamp sequence. base " |
- "timestamp=" |
- << output_timestamp_helper_.base_timestamp().InMicroseconds() |
- << "us, input timestamp=" << input->timestamp().InMicroseconds() |
- << "us"; |
- return false; |
- } |
- |
- const base::TimeDelta timestamp = input->timestamp(); |
- const base::TimeDelta expected_timestamp = |
- output_timestamp_helper_.GetTimestamp(); |
- const base::TimeDelta delta = timestamp - expected_timestamp; |
- |
- if (std::abs(delta.InMilliseconds()) > |
- AudioSplicer::kMaxTimeDeltaInMilliseconds) { |
- MEDIA_LOG(ERROR, media_log_) |
- << "Audio splicing failed: coded frame timestamp differs from " |
- "expected timestamp " << expected_timestamp.InMicroseconds() |
- << "us by " << delta.InMicroseconds() |
- << "us, more than threshold of +/-" |
- << AudioSplicer::kMaxTimeDeltaInMilliseconds |
- << "ms. Expected timestamp is based on decoded frames and frame rate."; |
- return false; |
- } |
- |
- int frames_to_fill = 0; |
- if (!delta.is_zero()) |
- frames_to_fill = output_timestamp_helper_.GetFramesToTarget(timestamp); |
- |
- if (frames_to_fill == 0 || std::abs(frames_to_fill) < kMinGapSize) { |
- AddOutputBuffer(input); |
- return true; |
- } |
- |
- if (frames_to_fill > 0) { |
- LIMITED_MEDIA_LOG(DEBUG, media_log_, num_warning_logs_, |
- kMaxSanitizerWarningLogs) |
- << "Audio splicer inserting silence for small gap of " |
- << delta.InMicroseconds() << "us at time " |
- << expected_timestamp.InMicroseconds() << "us."; |
- DVLOG(1) << "Gap detected @ " << expected_timestamp.InMicroseconds() |
- << " us: " << delta.InMicroseconds() << " us"; |
- |
- // Create a buffer with enough silence samples to fill the gap and |
- // add it to the output buffer. |
- scoped_refptr<AudioBuffer> gap = |
- AudioBuffer::CreateEmptyBuffer(input->channel_layout(), |
- input->channel_count(), |
- input->sample_rate(), |
- frames_to_fill, |
- expected_timestamp); |
- AddOutputBuffer(gap); |
- |
- // Add the input buffer now that the gap has been filled. |
- AddOutputBuffer(input); |
- return true; |
- } |
- |
- // Overlapping buffers marked as splice frames are handled by AudioSplicer, |
- // but decoder and demuxer quirks may sometimes produce overlapping samples |
- // which need to be sanitized. |
- // |
- // A crossfade can't be done here because only the current buffer is available |
- // at this point, not previous buffers. |
- LIMITED_MEDIA_LOG(DEBUG, media_log_, num_warning_logs_, |
- kMaxSanitizerWarningLogs) |
- << "Audio splicer skipping frames for small overlap of " |
- << -delta.InMicroseconds() << "us at time " |
- << expected_timestamp.InMicroseconds() << "us."; |
- DVLOG(1) << "Overlap detected @ " << expected_timestamp.InMicroseconds() |
- << " us: " << -delta.InMicroseconds() << " us"; |
- |
- const int frames_to_skip = -frames_to_fill; |
- if (input->frame_count() <= frames_to_skip) { |
- DVLOG(1) << "Dropping whole buffer"; |
- return true; |
- } |
- |
- // Copy the trailing samples that do not overlap samples already output |
- // into a new buffer. Add this new buffer to the output queue. |
- // |
- // TODO(acolwell): Implement a cross-fade here so the transition is less |
- // jarring. |
- AccurateTrimStart(frames_to_skip, input, output_timestamp_helper_); |
- AddOutputBuffer(input); |
- return true; |
-} |
- |
-bool AudioStreamSanitizer::HasNextBuffer() const { |
- return !output_buffers_.empty(); |
-} |
- |
-scoped_refptr<AudioBuffer> AudioStreamSanitizer::GetNextBuffer() { |
- scoped_refptr<AudioBuffer> ret = output_buffers_.front(); |
- output_buffers_.pop_front(); |
- return ret; |
-} |
- |
-void AudioStreamSanitizer::AddOutputBuffer( |
- const scoped_refptr<AudioBuffer>& buffer) { |
- output_timestamp_helper_.AddFrames(buffer->frame_count()); |
- output_buffers_.push_back(buffer); |
-} |
- |
-int AudioStreamSanitizer::GetFrameCount() const { |
- int frame_count = 0; |
- for (const auto& buffer : output_buffers_) |
- frame_count += buffer->frame_count(); |
- return frame_count; |
-} |
- |
-bool AudioStreamSanitizer::DrainInto(AudioStreamSanitizer* output) { |
- while (HasNextBuffer()) { |
- if (!output->AddInput(GetNextBuffer())) |
- return false; |
- } |
- return true; |
-} |
- |
-AudioSplicer::AudioSplicer(int samples_per_second, |
- const scoped_refptr<MediaLog>& media_log) |
- : max_crossfade_duration_( |
- base::TimeDelta::FromMilliseconds(kCrossfadeDurationInMilliseconds)), |
- splice_timestamp_(kNoTimestamp), |
- max_splice_end_timestamp_(kNoTimestamp), |
- output_sanitizer_( |
- new AudioStreamSanitizer(samples_per_second, media_log)), |
- pre_splice_sanitizer_( |
- new AudioStreamSanitizer(samples_per_second, media_log)), |
- post_splice_sanitizer_( |
- new AudioStreamSanitizer(samples_per_second, media_log)), |
- have_all_pre_splice_buffers_(false) {} |
- |
-AudioSplicer::~AudioSplicer() {} |
- |
-void AudioSplicer::Reset() { |
- output_sanitizer_->Reset(); |
- pre_splice_sanitizer_->Reset(); |
- post_splice_sanitizer_->Reset(); |
- have_all_pre_splice_buffers_ = false; |
- reset_splice_timestamps(); |
-} |
- |
-bool AudioSplicer::AddInput(const scoped_refptr<AudioBuffer>& input) { |
- // If we're not processing a splice, add the input to the output queue. |
- if (splice_timestamp_ == kNoTimestamp) { |
- DCHECK(!pre_splice_sanitizer_->HasNextBuffer()); |
- DCHECK(!post_splice_sanitizer_->HasNextBuffer()); |
- return output_sanitizer_->AddInput(input); |
- } |
- |
- const AudioTimestampHelper& output_ts_helper = |
- output_sanitizer_->timestamp_helper(); |
- |
- if (!have_all_pre_splice_buffers_) { |
- DCHECK(!input->end_of_stream()); |
- |
- // If the provided buffer is entirely before the splice point it can also be |
- // added to the output queue. |
- if (input->timestamp() + input->duration() < splice_timestamp_) { |
- DCHECK(!pre_splice_sanitizer_->HasNextBuffer()); |
- return output_sanitizer_->AddInput(input); |
- } |
- |
- // If we've encountered the first pre splice buffer, reset the pre splice |
- // sanitizer based on |output_sanitizer_|. This is done so that gaps and |
- // overlaps between buffers across the sanitizers are accounted for prior |
- // to calculating crossfade. |
- if (!pre_splice_sanitizer_->HasNextBuffer()) { |
- pre_splice_sanitizer_->ResetTimestampState( |
- output_ts_helper.frame_count(), output_ts_helper.base_timestamp()); |
- } |
- |
- return pre_splice_sanitizer_->AddInput(input); |
- } |
- |
- // The first post splice buffer is expected to match |splice_timestamp_|. |
- if (!post_splice_sanitizer_->HasNextBuffer()) |
- CHECK(splice_timestamp_ == input->timestamp()); |
- |
- // At this point we have all the fade out preroll buffers from the decoder. |
- // We now need to wait until we have enough data to perform the crossfade (or |
- // we receive an end of stream). |
- if (!post_splice_sanitizer_->AddInput(input)) |
- return false; |
- |
- // Ensure |output_sanitizer_| has a valid base timestamp so we can use it for |
- // timestamp calculations. |
- if (output_ts_helper.base_timestamp() == kNoTimestamp) { |
- output_sanitizer_->ResetTimestampState( |
- 0, pre_splice_sanitizer_->timestamp_helper().base_timestamp()); |
- } |
- |
- // If a splice frame was incorrectly marked due to poor demuxed timestamps, we |
- // may not actually have a splice. Here we check if any frames exist before |
- // the splice. In this case, just transfer all data to the output sanitizer. |
- const int frames_before_splice = |
- output_ts_helper.base_timestamp() == kNoTimestamp |
- ? 0 |
- : output_ts_helper.GetFramesToTarget(splice_timestamp_); |
- if (frames_before_splice < 0 || |
- pre_splice_sanitizer_->GetFrameCount() <= frames_before_splice) { |
- CHECK(pre_splice_sanitizer_->DrainInto(output_sanitizer_.get())); |
- |
- // If the file contains incorrectly muxed timestamps, there may be huge gaps |
- // between the demuxed and decoded timestamps. |
- if (!post_splice_sanitizer_->DrainInto(output_sanitizer_.get())) |
- return false; |
- |
- reset_splice_timestamps(); |
- return true; |
- } |
- |
- // Wait until we have enough data to crossfade or end of stream. |
- if (!input->end_of_stream() && |
- input->timestamp() + input->duration() < max_splice_end_timestamp_) { |
- return true; |
- } |
- |
- scoped_refptr<AudioBuffer> crossfade_buffer; |
- std::unique_ptr<AudioBus> pre_splice = |
- ExtractCrossfadeFromPreSplice(&crossfade_buffer); |
- |
- // Crossfade the pre splice and post splice sections and transfer all relevant |
- // buffers into |output_sanitizer_|. |
- CrossfadePostSplice(std::move(pre_splice), crossfade_buffer); |
- |
- // Clear the splice timestamp so new splices can be accepted. |
- reset_splice_timestamps(); |
- return true; |
-} |
- |
-bool AudioSplicer::HasNextBuffer() const { |
- return output_sanitizer_->HasNextBuffer(); |
-} |
- |
-scoped_refptr<AudioBuffer> AudioSplicer::GetNextBuffer() { |
- return output_sanitizer_->GetNextBuffer(); |
-} |
- |
-void AudioSplicer::SetSpliceTimestamp(base::TimeDelta splice_timestamp) { |
- if (splice_timestamp == kNoTimestamp) { |
- DCHECK(splice_timestamp_ != kNoTimestamp); |
- DCHECK(!have_all_pre_splice_buffers_); |
- have_all_pre_splice_buffers_ = true; |
- return; |
- } |
- |
- if (splice_timestamp_ == splice_timestamp) |
- return; |
- |
- // TODO(dalecurtis): We may need the concept of a future_splice_timestamp_ to |
- // handle cases where another splice comes in before we've received 5ms of |
- // data from the last one. Leave this as a CHECK for now to figure out if |
- // this case is possible. |
- CHECK(splice_timestamp_ == kNoTimestamp); |
- splice_timestamp_ = splice_timestamp; |
- max_splice_end_timestamp_ = splice_timestamp_ + max_crossfade_duration_; |
- pre_splice_sanitizer_->Reset(); |
- post_splice_sanitizer_->Reset(); |
- have_all_pre_splice_buffers_ = false; |
-} |
- |
-std::unique_ptr<AudioBus> AudioSplicer::ExtractCrossfadeFromPreSplice( |
- scoped_refptr<AudioBuffer>* crossfade_buffer) { |
- DCHECK(crossfade_buffer); |
- const AudioTimestampHelper& output_ts_helper = |
- output_sanitizer_->timestamp_helper(); |
- |
- int frames_before_splice = |
- output_ts_helper.GetFramesToTarget(splice_timestamp_); |
- |
- // Determine crossfade frame count based on available frames in each splicer |
- // and capping to the maximum crossfade duration. |
- const int max_crossfade_frame_count = |
- output_ts_helper.GetFramesToTarget(max_splice_end_timestamp_) - |
- frames_before_splice; |
- const int frames_to_crossfade = std::min( |
- max_crossfade_frame_count, |
- std::min(pre_splice_sanitizer_->GetFrameCount() - frames_before_splice, |
- post_splice_sanitizer_->GetFrameCount())); |
- // There must always be frames to crossfade, otherwise the splice should not |
- // have been generated. |
- DCHECK_GT(frames_to_crossfade, 0); |
- |
- int frames_read = 0; |
- std::unique_ptr<AudioBus> output_bus; |
- while (pre_splice_sanitizer_->HasNextBuffer() && |
- frames_read < frames_to_crossfade) { |
- scoped_refptr<AudioBuffer> preroll = pre_splice_sanitizer_->GetNextBuffer(); |
- |
- // We don't know the channel count until we see the first buffer, so wait |
- // until the first buffer to allocate the output AudioBus. |
- if (!output_bus) { |
- output_bus = |
- AudioBus::Create(preroll->channel_count(), frames_to_crossfade); |
- // Allocate output buffer for crossfade. |
- *crossfade_buffer = AudioBuffer::CreateBuffer(kSampleFormatPlanarF32, |
- preroll->channel_layout(), |
- preroll->channel_count(), |
- preroll->sample_rate(), |
- frames_to_crossfade); |
- } |
- |
- // There may be enough of a gap introduced during decoding such that an |
- // entire buffer exists before the splice point. |
- if (frames_before_splice >= preroll->frame_count()) { |
- // Adjust the number of frames remaining before the splice. NOTE: This is |
- // safe since |pre_splice_sanitizer_| is a continuation of the timeline in |
- // |output_sanitizer_|. As such we're guaranteed there are no gaps or |
- // overlaps in the timeline between the two sanitizers. |
- frames_before_splice -= preroll->frame_count(); |
- CHECK(output_sanitizer_->AddInput(preroll)); |
- continue; |
- } |
- |
- const int frames_to_read = |
- std::min(preroll->frame_count() - frames_before_splice, |
- output_bus->frames() - frames_read); |
- preroll->ReadFrames( |
- frames_to_read, frames_before_splice, frames_read, output_bus.get()); |
- frames_read += frames_to_read; |
- |
- // If only part of the buffer was consumed, trim it appropriately and stick |
- // it into the output queue. |
- if (frames_before_splice) { |
- preroll->TrimEnd(preroll->frame_count() - frames_before_splice); |
- CHECK(output_sanitizer_->AddInput(preroll)); |
- frames_before_splice = 0; |
- } |
- } |
- |
- // Ensure outputs were properly allocated. The method should not have been |
- // called if there is not enough data to crossfade. |
- // TODO(dalecurtis): Convert to DCHECK() once http://crbug.com/356073 fixed. |
- CHECK(output_bus); |
- CHECK(crossfade_buffer->get()); |
- |
- // All necessary buffers have been processed, it's safe to reset. |
- pre_splice_sanitizer_->Reset(); |
- DCHECK_EQ(output_bus->frames(), frames_read); |
- DCHECK_EQ(output_ts_helper.GetFramesToTarget(splice_timestamp_), 0); |
- return output_bus; |
-} |
- |
-void AudioSplicer::CrossfadePostSplice( |
- std::unique_ptr<AudioBus> pre_splice_bus, |
- const scoped_refptr<AudioBuffer>& crossfade_buffer) { |
- // Use the calculated timestamp and duration to ensure there's no extra gaps |
- // or overlaps to process when adding the buffer to |output_sanitizer_|. |
- const AudioTimestampHelper& output_ts_helper = |
- output_sanitizer_->timestamp_helper(); |
- crossfade_buffer->set_timestamp(output_ts_helper.GetTimestamp()); |
- |
- // AudioBuffer::ReadFrames() only allows output into an AudioBus, so wrap |
- // our AudioBuffer in one so we can avoid extra data copies. |
- std::unique_ptr<AudioBus> output_bus = |
- CreateAudioBufferWrapper(crossfade_buffer); |
- |
- // Extract crossfade section from the |post_splice_sanitizer_|. |
- int frames_read = 0, frames_to_trim = 0; |
- scoped_refptr<AudioBuffer> remainder; |
- while (post_splice_sanitizer_->HasNextBuffer() && |
- frames_read < output_bus->frames()) { |
- scoped_refptr<AudioBuffer> postroll = |
- post_splice_sanitizer_->GetNextBuffer(); |
- const int frames_to_read = |
- std::min(postroll->frame_count(), output_bus->frames() - frames_read); |
- postroll->ReadFrames(frames_to_read, 0, frames_read, output_bus.get()); |
- frames_read += frames_to_read; |
- |
- // If only part of the buffer was consumed, save it for after we've added |
- // the crossfade buffer |
- if (frames_to_read < postroll->frame_count()) { |
- DCHECK(!remainder.get()); |
- remainder.swap(postroll); |
- frames_to_trim = frames_to_read; |
- } |
- } |
- |
- DCHECK_EQ(output_bus->frames(), frames_read); |
- |
- // Crossfade the audio into |crossfade_buffer|. |
- for (int ch = 0; ch < output_bus->channels(); ++ch) { |
- vector_math::Crossfade(pre_splice_bus->channel(ch), |
- pre_splice_bus->frames(), |
- output_bus->channel(ch)); |
- } |
- |
- CHECK(output_sanitizer_->AddInput(crossfade_buffer)); |
- DCHECK_EQ(crossfade_buffer->frame_count(), output_bus->frames()); |
- |
- if (remainder.get()) { |
- // Trim off consumed frames. |
- AccurateTrimStart(frames_to_trim, remainder, output_ts_helper); |
- CHECK(output_sanitizer_->AddInput(remainder)); |
- } |
- |
- // Transfer all remaining buffers out and reset once empty. |
- CHECK(post_splice_sanitizer_->DrainInto(output_sanitizer_.get())); |
- post_splice_sanitizer_->Reset(); |
-} |
- |
-} // namespace media |