blob: ada56c543873a68ae1fede5ba6075a7c9ac4f5b3 [file] [log] [blame]
// Copyright 2020 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 <lib/media/audio/effects/audio_effects.h>
#include <unistd.h>
#include <cstring>
#include <string>
#include <string_view>
#include <rapidjson/document.h>
namespace {
enum class EffectId {
// This effect inverts the sign of every sample.
INVERTER = 0,
// This effect sleeps for 20ms.
SLEEPER = 1,
// This effect doubles the incoming signal.
DOUBLER = 2,
// Total number of effects in this library.
COUNT = 3
};
struct Effect {
EffectId id;
uint32_t frame_rate;
uint16_t channels;
bool enabled;
};
// This key enables basic testing of runtime changes to effect configurations: { "enabled": bool }
// Also, a null or empty string results in an enabled effect (when no configuration is provided).
const char* kEnabled = "enabled";
zx_status_t ParseEnabledFromConfig(const char* config_cstr, size_t config_len, bool* enabled) {
// By default an effect is enabled unless there is a scema error, or we confirm we are disabled.
*enabled = true;
// If no configuration, this (simple) effect is enabled.
if (config_cstr == nullptr || !config_cstr[0]) {
return ZX_OK;
}
rapidjson::Document doc;
std::string parse_buffer(config_cstr);
const rapidjson::ParseResult parse_res = doc.ParseInsitu(parse_buffer.data());
// Invalid config string, so effect is disabled.
if (parse_res.IsError()) {
*enabled = false;
return ZX_ERR_INVALID_ARGS;
}
const auto it = doc.FindMember(kEnabled);
// Valid config, kEnabled key not found, so assume effect is enabled.
if (it == doc.MemberEnd()) {
return ZX_OK;
}
// kEnabled key was found, but its value isn't a bool. Invalid config, so effect is disabled.
if (!it->value.IsBool()) {
*enabled = false;
return ZX_ERR_INVALID_ARGS;
}
// kEnabled key's value is a bool! Are we enabled?
*enabled = it->value.GetBool();
return ZX_OK;
}
bool effect_get_info(uint32_t effect_id, fuchsia_audio_effects_description* desc) {
if (desc == nullptr) {
return false;
}
switch (effect_id) {
case static_cast<uint32_t>(EffectId::INVERTER):
strlcpy(desc->name, "inversion_filter", sizeof(desc->name));
break;
case static_cast<uint32_t>(EffectId::SLEEPER):
strlcpy(desc->name, "sleeper_filter", sizeof(desc->name));
break;
case static_cast<uint32_t>(EffectId::DOUBLER):
strlcpy(desc->name, "doubler_filter", sizeof(desc->name));
break;
default:
return false;
}
desc->incoming_channels = FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY;
desc->outgoing_channels = FUCHSIA_AUDIO_EFFECTS_CHANNELS_SAME_AS_IN;
return true;
}
fuchsia_audio_effects_handle_t effect_create(uint32_t effect_id, uint32_t frame_rate,
uint16_t channels_in, uint16_t channels_out,
const char* config, size_t config_length) {
if (channels_in != channels_out) {
return FUCHSIA_AUDIO_EFFECTS_INVALID_HANDLE;
}
switch (effect_id) {
case static_cast<uint32_t>(EffectId::INVERTER):
case static_cast<uint32_t>(EffectId::SLEEPER):
case static_cast<uint32_t>(EffectId::DOUBLER):
break;
default:
return FUCHSIA_AUDIO_EFFECTS_INVALID_HANDLE;
}
bool enabled;
zx_status_t status = ParseEnabledFromConfig(config, config_length, &enabled);
if (status != ZX_OK) {
return FUCHSIA_AUDIO_EFFECTS_INVALID_HANDLE;
}
auto e = new Effect{
.id = static_cast<EffectId>(effect_id),
.frame_rate = frame_rate,
.channels = channels_in,
.enabled = enabled,
};
return reinterpret_cast<fuchsia_audio_effects_handle_t>(e);
}
bool effect_update_configuration(fuchsia_audio_effects_handle_t handle, const char* config,
size_t config_length) {
if (handle == FUCHSIA_AUDIO_EFFECTS_INVALID_HANDLE) {
return false;
}
bool enabled;
zx_status_t status = ParseEnabledFromConfig(config, config_length, &enabled);
if (status != ZX_OK) {
return false;
}
reinterpret_cast<Effect*>(handle)->enabled = enabled;
return true;
}
bool effect_delete(fuchsia_audio_effects_handle_t handle) {
if (handle == FUCHSIA_AUDIO_EFFECTS_INVALID_HANDLE) {
return false;
}
delete reinterpret_cast<Effect*>(handle);
return true;
}
bool effect_get_parameters(fuchsia_audio_effects_handle_t handle,
fuchsia_audio_effects_parameters* params) {
if (handle == FUCHSIA_AUDIO_EFFECTS_INVALID_HANDLE || params == nullptr) {
return false;
}
auto e = reinterpret_cast<Effect*>(handle);
memset(params, 0, sizeof(*params));
params->frame_rate = e->frame_rate;
params->channels_in = e->channels;
params->channels_out = e->channels;
params->block_size_frames = FUCHSIA_AUDIO_EFFECTS_BLOCK_SIZE_ANY;
params->signal_latency_frames = 0;
params->max_frames_per_buffer = 0;
return true;
}
bool effect_process_inplace(fuchsia_audio_effects_handle_t handle, uint32_t num_frames,
float* audio_buff_in_out) {
if (handle == FUCHSIA_AUDIO_EFFECTS_INVALID_HANDLE || audio_buff_in_out == nullptr) {
return false;
}
auto e = reinterpret_cast<Effect*>(handle);
if (!e->enabled) {
return true;
}
switch (e->id) {
case EffectId::INVERTER:
for (uint32_t k = 0; k < num_frames * e->channels; k++) {
audio_buff_in_out[k] = -audio_buff_in_out[k];
}
return true;
case EffectId::SLEEPER:
usleep(20'000);
return true;
case EffectId::DOUBLER:
for (uint32_t k = 0; k < num_frames * e->channels; k++) {
audio_buff_in_out[k] *= 2.0f;
}
return true;
default:
return false;
}
}
bool effect_process(fuchsia_audio_effects_handle_t handle, uint32_t num_frames,
const float* audio_buff_in, float** audio_buff_out) {
return false; // this library supports in-place effects only
}
bool effect_flush(fuchsia_audio_effects_handle_t handle) {
return handle != FUCHSIA_AUDIO_EFFECTS_INVALID_HANDLE;
}
void effect_set_stream_info(fuchsia_audio_effects_handle_t handle,
const fuchsia_audio_effects_stream_info* stream_info) {}
} // namespace
DECLARE_FUCHSIA_AUDIO_EFFECTS_MODULE_V1{
static_cast<uint32_t>(EffectId::COUNT), // num_effects
&effect_get_info,
&effect_create,
&effect_update_configuration,
&effect_delete,
&effect_get_parameters,
&effect_process_inplace,
&effect_process,
&effect_flush,
&effect_set_stream_info,
};