blob: e75cd356c5356a01d7a1feda3ffef82730ccdea0 [file]
// 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 "components/speech/audio_buffer.h"
#include <algorithm>
#include "base/check_op.h"
#include "base/compiler_specific.h"
namespace {
void VerifyBytesPerSample(int bytes_per_sample) {
CHECK(bytes_per_sample == 1 || bytes_per_sample == 2 ||
bytes_per_sample == 4);
}
base::AlignedHeapArray<uint8_t> MakeBackingData(size_t length,
int bytes_per_sample) {
if (!length) [[unlikely]] {
return base::AlignedHeapArray<uint8_t>();
}
return base::AlignedUninit<uint8_t>(length, bytes_per_sample);
}
} // namespace
AudioChunk::AudioChunk(int bytes_per_sample)
: bytes_per_sample_(bytes_per_sample) {
VerifyBytesPerSample(bytes_per_sample);
}
AudioChunk::AudioChunk(size_t length, int bytes_per_sample)
: data_(MakeBackingData(length, bytes_per_sample)),
bytes_per_sample_(bytes_per_sample) {
VerifyBytesPerSample(bytes_per_sample);
CHECK_EQ(length % bytes_per_sample, 0U);
std::ranges::fill(data_, 0);
}
AudioChunk::AudioChunk(const uint8_t* data, size_t length, int bytes_per_sample)
: data_(MakeBackingData(length, bytes_per_sample)),
bytes_per_sample_(bytes_per_sample) {
VerifyBytesPerSample(bytes_per_sample);
CHECK_EQ(length % bytes_per_sample, 0U);
// SAFETY: Per API contract, `data` must contain exactly `length` bytes.
base::span(data_).copy_from_nonoverlapping(
UNSAFE_BUFFERS(base::span(data, length)));
}
AudioChunk::AudioChunk(base::span<const uint8_t> data_span,
int bytes_per_sample)
: data_(MakeBackingData(data_span.size(), bytes_per_sample)),
bytes_per_sample_(bytes_per_sample) {
CHECK_EQ(data_span.size() % bytes_per_sample, 0U);
base::span(data_).copy_from_nonoverlapping(data_span);
}
AudioChunk::~AudioChunk() = default;
bool AudioChunk::IsEmpty() const {
return data_.empty();
}
size_t AudioChunk::NumSamples() const {
return data_.size() / bytes_per_sample_;
}
int16_t AudioChunk::GetSample16(size_t index) const {
// `SamplesData16AsSpan()` will check all the necessary safety constraints.
return SamplesData16AsSpan()[index];
}
std::string_view AudioChunk::AsStringView() const {
return std::string_view(reinterpret_cast<const char*>(data_.data()),
data_.size());
}
std::string AudioChunk::AsString() {
// The extra copy is unfortunate here...
// TODO(crbug.com/484089981): Delete this method when all uses have been
// replaced by `AsStringView()`.
return std::string(reinterpret_cast<const char*>(data_.data()), data_.size());
}
base::span<const int16_t> AudioChunk::SamplesData16AsSpan() const {
// SAFETY: `SamplesData16AsSpan()` returns a pointer to `data_`. Make sure
// this is safe by ensuring the byte size is a multiple of sizeof(int16_t) and
// the data is properly aligned (which the constructor should already
// guarantee).
CHECK_EQ(static_cast<size_t>(bytes_per_sample_), sizeof(int16_t));
CHECK(base::IsAligned(data_.data(), alignof(int16_t)));
return UNSAFE_BUFFERS(
base::span<const int16_t>(reinterpret_cast<const int16_t*>(data_.data()),
data_.size() / sizeof(int16_t)));
}
base::span<int16_t> AudioChunk::SamplesData16AsWriteableSpan() {
// SAFETY: `SamplesData16AsSpan()` returns a pointer to `data_`. Make sure
// this is safe by ensuring the byte size is a multiple of sizeof(int16_t) and
// the data is properly aligned (which the constructor should already
// guarantee).
CHECK_EQ(static_cast<size_t>(bytes_per_sample_), sizeof(int16_t));
CHECK(base::IsAligned(data_.data(), alignof(int16_t)));
return UNSAFE_BUFFERS(
base::span<int16_t>(reinterpret_cast<int16_t*>(data_.data()),
data_.size() / sizeof(int16_t)));
}
AudioBuffer::AudioBuffer(int bytes_per_sample)
: bytes_per_sample_(bytes_per_sample) {
VerifyBytesPerSample(bytes_per_sample);
}
AudioBuffer::~AudioBuffer() {
Clear();
}
void AudioBuffer::Enqueue(const uint8_t* data, size_t length) {
chunks_.push_back(new AudioChunk(data, length, bytes_per_sample_));
}
scoped_refptr<AudioChunk> AudioBuffer::DequeueSingleChunk() {
DCHECK(!chunks_.empty());
scoped_refptr<AudioChunk> chunk(chunks_.front());
chunks_.pop_front();
return chunk;
}
scoped_refptr<AudioChunk> AudioBuffer::DequeueAll() {
if (chunks_.empty()) {
return base::MakeRefCounted<AudioChunk>(bytes_per_sample_);
}
base::CheckedNumeric<size_t> total_size = 0;
// In order to improve performance, calculate in advance the total length
// and then copy the chunks.
for (const auto& chunk : chunks_) {
total_size += chunk->data().size();
}
auto merged_chunk = base::MakeRefCounted<AudioChunk>(total_size.ValueOrDie(),
bytes_per_sample_);
base::span<uint8_t> remaining_data = merged_chunk->writable_data();
for (const auto& chunk : chunks_) {
auto dest = remaining_data.take_first(chunk->data().size());
dest.copy_from_nonoverlapping(chunk->data());
}
CHECK(remaining_data.empty());
Clear();
return merged_chunk;
}
void AudioBuffer::Clear() {
chunks_.clear();
}
bool AudioBuffer::IsEmpty() const {
return chunks_.empty();
}