blob: de21d28462e5022a07032d815eaa054961ed0693 [file] [log] [blame]
// Copyright 2016 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 "garnet/bin/media/audio_core/mixer/output_producer.h"
#include <fbl/algorithm.h>
#include <cmath>
#include <limits>
#include <type_traits>
#include "garnet/bin/media/audio_core/mixer/constants.h"
#include "lib/fidl/cpp/clone.h"
#include "lib/fxl/logging.h"
namespace media::audio {
// Converting audio between float and int is surprisingly controversial.
// (blog.bjornroche.com/2009/12/int-float-int-its-jungle-out-there.html etc. --
// web-search "audio float int convert"). Our float32-based internal pipeline
// can accomodate float and int Sources without data loss (where Source is a
// client-submitted stream from AudioRenderer, or an input device), but for non-
// float Destinations (output device, or AudioCapturer stream to a client) we
// must clamp +1.0 values in DestConverter::Convert. When translating from float
// to int16 for example, we can translate -1.0 perfectly to -32768 (negative
// 0x8000), while +1.0 cannot become +32768 (positive 0x8000, exceeding int16's
// max) so it is clamped to 32767 (0x7FFF).
//
// Having said all this, the "practically clipping" value of +1.0 is rare in WAV
// files, and other sources should easily be able to reduce their input levels.
// Template to produce destination samples from normalized samples.
template <typename DType, typename Enable = void>
class DestConverter;
template <typename DType>
class DestConverter<
DType, typename std::enable_if<std::is_same<DType, uint8_t>::value>::type> {
public:
static inline constexpr DType Convert(float sample) {
return fbl::clamp<int32_t>(
round(sample * kFloatToInt8) + kOffsetInt8ToUint8,
std::numeric_limits<uint8_t>::min(),
std::numeric_limits<uint8_t>::max());
}
};
template <typename DType>
class DestConverter<
DType, typename std::enable_if<std::is_same<DType, int16_t>::value>::type> {
public:
static inline constexpr DType Convert(float sample) {
return fbl::clamp<int32_t>(round(sample * kFloatToInt16),
std::numeric_limits<int16_t>::min(),
std::numeric_limits<int16_t>::max());
}
};
template <typename DType>
class DestConverter<
DType, typename std::enable_if<std::is_same<DType, int32_t>::value>::type> {
public:
static inline constexpr DType Convert(float sample) {
return fbl::clamp<int64_t>(round(sample * kFloatToInt24In32), kMinInt24In32,
kMaxInt24In32);
}
};
template <typename DType>
class DestConverter<
DType, typename std::enable_if<std::is_same<DType, float>::value>::type> {
public:
// This will emit +1.0 values, which are legal per WAV format custom.
static inline constexpr DType Convert(float sample) {
return fbl::clamp(sample, -1.0f, 1.0f);
}
};
// Template to fill samples with silence based on sample type.
template <typename DType, typename Enable = void>
class SilenceMaker;
template <typename DType>
class SilenceMaker<
DType, typename std::enable_if<std::is_same<DType, int16_t>::value ||
std::is_same<DType, int32_t>::value ||
std::is_same<DType, float>::value>::type> {
public:
static inline void Fill(void* dest, size_t samples) {
// This works even if DType is float/double: per IEEE-754, all 0s == +0.0.
::memset(dest, 0, samples * sizeof(DType));
}
};
template <typename DType>
class SilenceMaker<
DType, typename std::enable_if<std::is_same<DType, uint8_t>::value>::type> {
public:
static inline void Fill(void* dest, size_t samples) {
::memset(dest, kOffsetInt8ToUint8, samples * sizeof(DType));
}
};
// A templated class which implements the ProduceOutput and FillWithSilence
// methods of OutputProducer
template <typename DType>
class OutputProducerImpl : public OutputProducer {
public:
explicit OutputProducerImpl(const fuchsia::media::AudioStreamTypePtr& format)
: OutputProducer(format, sizeof(DType)) {}
void ProduceOutput(const float* source, void* dest_void,
uint32_t frames) const override {
using DC = DestConverter<DType>;
auto* dest = static_cast<DType*>(dest_void);
// Previously we clamped here; because of rounding, this is different for
// each output type, so it is now handled in Convert() specializations.
for (size_t i = 0; i < (static_cast<size_t>(frames) * channels_); ++i) {
dest[i] = DC::Convert(source[i]);
}
}
void FillWithSilence(void* dest, uint32_t frames) const override {
SilenceMaker<DType>::Fill(dest, frames * channels_);
}
};
// Constructor/destructor for the common OutputProducer base class.
OutputProducer::OutputProducer(const fuchsia::media::AudioStreamTypePtr& format,
uint32_t bytes_per_sample)
: channels_(format->channels),
bytes_per_sample_(bytes_per_sample),
bytes_per_frame_(bytes_per_sample * format->channels) {
fidl::Clone(format, &format_);
}
// Selection routine which will instantiate a particular templatized version of
// the output producer.
OutputProducerPtr OutputProducer::Select(
const fuchsia::media::AudioStreamTypePtr& format) {
if (!format || format->channels == 0u) {
FXL_LOG(ERROR) << "Invalid output format";
return nullptr;
}
switch (format->sample_format) {
case fuchsia::media::AudioSampleFormat::UNSIGNED_8:
return OutputProducerPtr(new OutputProducerImpl<uint8_t>(format));
case fuchsia::media::AudioSampleFormat::SIGNED_16:
return OutputProducerPtr(new OutputProducerImpl<int16_t>(format));
case fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32:
return OutputProducerPtr(new OutputProducerImpl<int32_t>(format));
case fuchsia::media::AudioSampleFormat::FLOAT:
return OutputProducerPtr(new OutputProducerImpl<float>(format));
default:
FXL_LOG(ERROR) << "Unsupported output format "
<< (uint32_t)format->sample_format;
return nullptr;
}
}
} // namespace media::audio