910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/audio_fifo.h"

#include <cstring>

#include "base/check_op.h"
#include "base/numerics/safe_math.h"
#include "base/trace_event/trace_event.h"
#include "base/types/zip.h"
#include "media/base/audio_bus.h"

namespace media {

// Given current position in the FIFO, the maximum number of elements in the
// FIFO and the size of the input; this method provides two output results:
// |size| and |wrap_size|. These two results can then be utilized for memcopy
// operations to and from the FIFO.
// Under "normal" circumstances, |size| will be equal to |in_size| and
// |wrap_size| will be zero. This case corresponding to the non-wrapping case
// where we have not yet reached the "edge" of the FIFO. If |pos| + |in_size|
// exceeds the total size of the FIFO, we must wrap around and start reusing
// a part the allocated memory. The size of this part is given by |wrap_size|.
static void GetSizes(size_t pos,
                     size_t max_size,
                     size_t in_size,
                     size_t* size,
                     size_t* wrap_size) {
  const size_t frames_before_end = base::CheckSub(max_size, pos).ValueOrDie();
  if (in_size > frames_before_end) {
    // Wrapping is required => derive size of each segment.
    *size = max_size - pos;
    *wrap_size = in_size - *size;
  } else {
    // Wrapping is not required.
    *size = in_size;
    *wrap_size = 0u;
  }
}

// Updates the read/write position with |step| modulo the maximum number of
// elements in the FIFO to ensure that the position counters wraps around at
// the endpoint.
static size_t UpdatePos(size_t pos, size_t step, int max_size) {
  return ((pos + step) % max_size);
}

AudioFifo::AudioFifo(int channels, int frames)
    : audio_bus_(AudioBus::Create(channels, frames)),
      max_frames_(base::checked_cast<size_t>(frames)) {}

AudioFifo::~AudioFifo() = default;

void AudioFifo::Push(const AudioBus* source) {
  Push(source, source->frames());
}

void AudioFifo::Push(const AudioBus* source, int source_size) {
  DCHECK(source);
  DCHECK_EQ(source->channels(), audio_bus_->channels());
  DCHECK_LE(source_size, source->frames());

  // Ensure that there is space for the new data in the FIFO.
  const size_t source_frames = base::checked_cast<size_t>(source_size);
  const size_t remaining_frames =
      base::CheckSub(max_frames_, frames_).ValueOrDie();
  CHECK_LE(source_frames, remaining_frames);

  TRACE_EVENT_BEGIN2(TRACE_DISABLED_BY_DEFAULT("audio"), "AudioFifo::Push",
                     "this", static_cast<void*>(this), "fifo frames", frames_);

  // Figure out if wrapping is needed and if so what segment sizes we need
  // when adding the new audio bus content to the FIFO.
  size_t append_size = 0u;
  size_t wrap_size = 0u;
  GetSizes(write_pos_, max_frames_, source_frames, &append_size, &wrap_size);

  // Copy all channels from the source to the FIFO. Wrap around if needed.
  for (auto [data_src, fifo_dest] :
       base::zip(source->AllChannels(), audio_bus_->AllChannels())) {
    auto [append_data, wrap_data] = data_src.split_at(append_size);

    // Append part of (or the complete) source to the FIFO.
    fifo_dest.subspan(write_pos_, append_size)
        .copy_from_nonoverlapping(append_data);

    if (wrap_size > 0) {
      // Wrapping is needed: copy remaining part from the source to the FIFO.
      fifo_dest.first(wrap_size).copy_from_nonoverlapping(wrap_data);
    }
  }

  frames_ += source_frames;
  DCHECK_LE(frames_, max_frames_);
  write_pos_ = UpdatePos(write_pos_, source_frames, max_frames_);
  TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("audio"), "AudioFifo::Push",
                   "frames", source_frames);
}

void AudioFifo::Consume(AudioBus* destination,
                        int start_frame,
                        int frames_to_consume) {
  DCHECK(destination);
  DCHECK_EQ(destination->channels(), audio_bus_->channels());

  const size_t dest_offset = base::checked_cast<size_t>(start_frame);
  const size_t frame_count = base::checked_cast<size_t>(frames_to_consume);

  // It is not possible to ask for more data than what is available in the FIFO.
  CHECK_LE(frame_count, frames_);

  // A copy from the FIFO to |destination| will only be performed if the
  // allocated memory in |destination| is sufficient.
  const size_t free_frames_in_dest =
      base::CheckSub(destination->frames(), dest_offset).ValueOrDie();
  CHECK_LE(frame_count, free_frames_in_dest);

  TRACE_EVENT_BEGIN2(TRACE_DISABLED_BY_DEFAULT("audio"), "AudioFifo::Consume",
                     "this", static_cast<void*>(this), "fifo frames", frames_);

  // Figure out if wrapping is needed and if so what segment sizes we need
  // when removing audio bus content from the FIFO.
  size_t consume_size = 0u;
  size_t wrap_size = 0u;
  GetSizes(read_pos_, max_frames_, frame_count, &consume_size, &wrap_size);

  // For all channels, remove the requested amount of data from the FIFO
  // and copy the content to the destination. Wrap around if needed.
  for (auto [data_dest, fifo_src] :
       base::zip(destination->AllChannels(), audio_bus_->AllChannels())) {
    auto [consume_data, wrap_data] =
        data_dest.subspan(dest_offset, frame_count).split_at(consume_size);

    // Copy a selected part of the FIFO to the destination.
    consume_data.copy_from_nonoverlapping(
        fifo_src.subspan(read_pos_, consume_size));
    if (wrap_size > 0) {
      // Wrapping is needed: copy remaining part to the destination.
      wrap_data.copy_from_nonoverlapping(fifo_src.first(wrap_size));
    }
  }

  frames_ -= frame_count;
  read_pos_ = UpdatePos(read_pos_, frame_count, max_frames_);
  TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("audio"), "AudioFifo::Consume",
                   "frames", frame_count);
}

void AudioFifo::Clear() {
  frames_ = 0u;
  read_pos_ = 0u;
  write_pos_ = 0u;
}

}  // namespace media