blob: 509213b3b7f3b2415c2886bcc4410c16d9a5a786 [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 "codec_adapter_cvsd_encoder.h"
#include <lib/media/codec_impl/codec_port.h>
#include <lib/media/codec_impl/log.h>
#include <safemath/safe_math.h>
namespace {
// The implementations are based off of the CVSD codec algorithm defined in
// Bluetooth Core Spec v5.3, Vol 2, Part B Sec 9.2.
uint8_t SingleEncode(CvsdParams* params, const int16_t& input_sample) {
const int16_t x = input_sample;
// Determine output value (EQ 15).
const uint8_t bit = (x >= params->accumulator) ? 0 : 1;
UpdateCvsdParams(params, bit);
return bit;
}
} // namespace
CodecAdapterCvsdEncoder::CodecAdapterCvsdEncoder(std::mutex& lock,
CodecAdapterEvents* codec_adapter_events)
: CodecAdapterSWImpl(lock, codec_adapter_events) {}
std::pair<fuchsia::media::FormatDetails, size_t> CodecAdapterCvsdEncoder::OutputFormatDetails() {
ZX_DEBUG_ASSERT(codec_params_);
fuchsia::media::AudioCompressedFormatCvsd cvsd;
fuchsia::media::AudioCompressedFormat compressed_format;
compressed_format.set_cvsd(std::move(cvsd));
fuchsia::media::AudioFormat audio_format;
audio_format.set_compressed(std::move(compressed_format));
fuchsia::media::FormatDetails format_details;
format_details.set_mime_type(kCvsdMimeType);
format_details.mutable_domain()->set_audio(std::move(audio_format));
// The bytes needed to store each output packet. Since we're doing 16-1
// compression for mono audio, where 2 byte input = 1 bit output. bytes needed
// to store each output packet is 1.
return {std::move(format_details), kOutputFrameSize};
}
CodecAdapterCvsdEncoder::InputLoopStatus CodecAdapterCvsdEncoder::ProcessFormatDetails(
const fuchsia::media::FormatDetails& format_details) {
if (!format_details.has_domain() || !format_details.domain().is_audio() ||
!format_details.domain().audio().is_uncompressed() ||
!format_details.domain().audio().uncompressed().is_pcm()) {
events_->onCoreCodecFailCodec(
"CVSD Encoder received input that was not uncompressed pcm audio.");
return kShouldTerminate;
}
if (!format_details.has_encoder_settings() || !format_details.encoder_settings().is_cvsd()) {
events_->onCoreCodecFailCodec("CVSD Encoder did not receive encoder settings.");
return kShouldTerminate;
}
auto& input_format = format_details.domain().audio().uncompressed().pcm();
if (input_format.pcm_mode != fuchsia::media::AudioPcmMode::LINEAR ||
input_format.bits_per_sample != 16 || input_format.channel_map.size() != 1) {
events_->onCoreCodecFailCodec(
"CVSD Encoder only encodes mono audio with signed 16 bit linear samples.");
return kShouldTerminate;
}
if (input_format.frames_per_second != kExpectedSamplingFreq) {
LOG(WARNING, "Expected sampling frequency %u got %u", kExpectedSamplingFreq, input_format.frames_per_second);
}
InitCvsdParams(codec_params_);
InitChunkInputStream(format_details);
return kOk;
}
// Encode input buffer of 16 byte size to produce one byte of output data.
int CodecAdapterCvsdEncoder::ProcessInputChunkData(const uint8_t* input_data,
size_t input_data_size, uint8_t* output_buffer,
size_t output_buffer_size) {
uint8_t output_byte = 0x00;
const int16_t* pcm = reinterpret_cast<const int16_t*>(input_data);
for (int idx = 0; idx < static_cast<int>(input_data_size / 2); ++idx, ++pcm) {
output_byte = static_cast<uint8_t>(output_byte << 1);
output_byte |= SingleEncode(&(*codec_params_), *pcm);
}
*output_buffer = output_byte;
return 1;
}
fuchsia::sysmem::BufferCollectionConstraints CodecAdapterCvsdEncoder::BufferCollectionConstraints(
CodecPort port) {
fuchsia::sysmem::BufferCollectionConstraints c;
c.min_buffer_count_for_camping = kMinBufferCountForCamping;
// Actual minimum buffer size is 16 bytes for input and 1 byte for output since we're doing
// 16:1 compression for encoding. However, sysmem will basically allocate at least a
// 4KiB page per buffer even if the client is also ok with less, so we default to
// system page size for now.
if (port == kInputPort) {
// TODO(dayeonglee): consider requiring a larger buffer, based on what
// seems like a minimum reasonable time duration per buffer, to keep the
// buffers per second <= ~120.
c.buffer_memory_constraints.min_size_bytes =
std::max(zx_system_get_page_size(), kInputFrameSize);
c.buffer_memory_constraints.max_size_bytes = kInputPerPacketBufferBytesMax;
} else {
c.buffer_memory_constraints.min_size_bytes =
std::max(zx_system_get_page_size(), (uint32_t)MinOutputBufferSize());
// Set to some arbitrary value.
c.buffer_memory_constraints.max_size_bytes = 0xFFFFFFFF;
}
return c;
}
TimestampExtrapolator CodecAdapterCvsdEncoder::CreateTimestampExtrapolator(
const fuchsia::media::FormatDetails& format_details) {
if (format_details.has_timebase()) {
auto& input_format = format_details.domain().audio().uncompressed().pcm();
// Bytes per second is sampling frequency * number_of_channels * bytes_per_sample.
const size_t bytes_per_second = input_format.channel_map.size() *
input_format.frames_per_second *
(input_format.bits_per_sample / 8ull);
return TimestampExtrapolator(format_details.timebase(), bytes_per_second);
}
return TimestampExtrapolator();
}