Implement effect offloading to Hexagon DSP

This commit is contained in:
anonymix007 2025-01-20 20:05:07 +03:00
parent bdf5053968
commit e2a6a406a2
17 changed files with 440 additions and 20 deletions

6
.gitignore vendored
View File

@ -24,6 +24,8 @@ build.ninja
# CMake
cmake-build-*/
android_*/
hexagon_*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
@ -147,3 +149,7 @@ local.properties
.gradle/
build/
.cxx/
### Misc
LOGS/
*-hexagon/

View File

@ -7,19 +7,17 @@ set(CMAKE_CXX_COMPILER_VERSION 20)
#add_compile_definitions(ANDROID_ARM_NEON=true)
#add_compile_definitions(ANDROID_PLATFORM=android-24)
project("ViPER4Android")
project("ViPER4Android" C CXX ASM)
enable_language(ASM)
add_compile_definitions(VIPER_VERSION=20240314)
# FFTS
#add_subdirectory(src/viper/ffts)
# ViPERFX
include_directories(src/include)
set(FILES
# Main
src/viper/ViPER.cpp
src/ViPER4Android.cpp
src/ViperContext.cpp
# Effects
@ -70,16 +68,66 @@ set(FILES
src/viper/utils/TimeConstDelay.cpp
src/viper/utils/WaveBuffer.cpp)
add_library(
# Sets the name of the library.
v4a_re
if(HEXAGON_SDK_ROOT)
include(${HEXAGON_SDK_ROOT}/build/cmake/hexagon_fun.cmake)
set(common_incs
src/include/
${CMAKE_CURRENT_BINARY_DIR}/
${HEXAGON_SDK_ROOT}/incs/
${HEXAGON_SDK_ROOT}/incs/stddef/
${HEXAGON_SDK_ROOT}/rtos/qurt/
${HEXAGON_SDK_ROOT}/utils/examples/
)
include_directories(${common_incs})
# Sets the library as a shared library.
SHARED
add_compile_options(-Wno-error=reorder-ctor
-Wno-error=unused-private-field
-Wno-error=unused-variable
-fvisibility=hidden
-DNDEBUG
-flto
-ggdb
"-D__QAIC_SKEL_EXPORT=__attribute__((visibility(\"default\")))")
# Provides a relative path to your source file(s).
${FILES})
if(${OS_TYPE} MATCHES "HLOS")
add_compile_options(-DHEXAGON_STUB -Oz)
SET(CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} -lc++")
set(util_srcs
${HEXAGON_SDK_ROOT}/utils/examples/dsp_capabilities_utils.c
${HEXAGON_SDK_ROOT}/utils/examples/pd_status_notification.c)
target_link_libraries(v4a_re log) # kissfft)
target_compile_options(v4a_re PRIVATE -flto -O3 -DNDEBUG)
#target_compile_options(v4afx_r PRIVATE -O2 -DNDEBUG -Wall -Wsign-conversion -Wno-unused-result -Wno-unneeded-internal-declaration -fstrict-aliasing -fvisibility=hidden -Wextra -Wno-unused-parameter)
add_library(v4a_re SHARED ${CMAKE_CURRENT_BINARY_DIR}/v4a_stub.c src/ViPER4Android.cpp src/ViperStub.cpp ${util_srcs})
build_idl(src/include/v4a.idl v4a_re)
choose_dsprpc(${DSP_TYPE} dsprpc)
target_link_options(v4a_re PUBLIC -llog)
link_custom_library(v4a_re ${dsprpc})
copy_binaries(v4a_re)
else()
add_compile_options(-O3)
add_library(v4a_skel SHARED ${CMAKE_CURRENT_BINARY_DIR}/v4a_skel.c src/version.c src/ViperHexagon.cpp ${FILES})
build_idl(src/include/v4a.idl v4a_skel)
target_link_libraries(v4a_skel ${HEXAGON_LIB_DIR}/${HEXAGON_ARCH}/G0/pic/libc++abi.so.1)
target_link_libraries(v4a_skel ${HEXAGON_LIB_DIR}/${HEXAGON_ARCH}/G0/pic/libc++.so.1)
copy_binaries(v4a_skel)
endif()
else()
include_directories(src/include)
add_library(
# Sets the name of the library.
v4a_re
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${FILES}
src/ViPER4Android.cpp)
target_link_libraries(v4a_re log) # kissfft)
target_compile_options(v4a_re PRIVATE -flto -O3 -DNDEBUG)
#target_compile_options(v4afx_r PRIVATE -O2 -DNDEBUG -Wall -Wsign-conversion -Wno-unused-result -Wno-unneeded-internal-declaration -fstrict-aliasing -fvisibility=hidden -Wextra -Wno-unused-parameter)
endif()

View File

@ -10,6 +10,8 @@ typedef enum {
PARAM_GET_DISABLE_REASON,
PARAM_GET_CONFIG,
PARAM_GET_ARCHITECTURE,
PARAM_GET_MAX,
} param_get_t;
typedef enum {
@ -53,4 +55,6 @@ typedef enum {
PARAM_SET_DYNAMIC_SYSTEM_Y_COEFFICIENTS,
PARAM_SET_DYNAMIC_SYSTEM_SIDE_GAIN,
PARAM_SET_DYNAMIC_SYSTEM_STRENGTH,
PARAM_SET_MAX,
} param_set_t;

View File

@ -6,7 +6,15 @@
#include "log.h"
#include "viper/constants.h"
#ifdef __hexagon__
#define SET(type, ptr, value) \
do { \
type v = value; \
memcpy(ptr, &v, sizeof(type)); \
} while (false)
#else
#define SET(type, ptr, value) (*(type *) (ptr) = (value))
#endif
ViperContext::ViperContext() :
config({}),
@ -17,6 +25,8 @@ ViperContext::ViperContext() :
VIPER_LOGI("ViperContext created");
}
ViperContext::~ViperContext() {}
void ViperContext::copyBufferConfig(buffer_config_t *dest, buffer_config_t *src) {
if (src->mask & EFFECT_CONFIG_BUFFER) {
dest->buffer = src->buffer;
@ -809,7 +819,7 @@ int32_t ViperContext::process(audio_buffer_t *inBuffer, audio_buffer_t *outBuffe
if (!enabled) {
return -ENODATA;
}
inBuffer = getBuffer(&config.inputCfg, inBuffer);
outBuffer = getBuffer(&config.outputCfg, outBuffer);
if (inBuffer == nullptr || outBuffer == nullptr ||

View File

@ -3,7 +3,11 @@
#include <vector>
#include <cstddef>
#include "essential.h"
#ifndef HEXAGON_STUB
#include "viper/ViPER.h"
#else
#include <mutex>
#endif
#include <string>
class ViperContext {
@ -18,11 +22,18 @@ public:
};
ViperContext();
~ViperContext();
int32_t handleCommand(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData);
int32_t process(audio_buffer_t *inBuffer, audio_buffer_t *outBuffer);
private:
#ifdef HEXAGON_STUB
std::mutex mHandleLock;
uint64_t handle = 0;
size_t in_size = 0;
size_t out_size = 0;
#else
effect_config_t config;
DisableReason disableReason;
@ -40,4 +51,5 @@ private:
int32_t handleGetParam(effect_param_t *pCmdParam, effect_param_t *pReplyParam, uint32_t *pReplySize);
void setDisableReason(DisableReason reason);
#endif
};

35
src/ViperHexagon.cpp Normal file
View File

@ -0,0 +1,35 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "v4a.h"
#include "log.h"
#include "ViperContext.h"
int v4a_open(const char *uri, remote_handle64 *handle) {
*handle = (remote_handle64) new ViperContext();
return 0;
}
int v4a_close(remote_handle64 handle) {
if (handle) {
delete reinterpret_cast<ViperContext *>(handle);
}
return 0;
}
int32_t v4a_command(remote_handle64 handle, uint32_t cmdCode, const uint8_t *pCmdData, int cmdSize, uint8_t *pReplyData, int replySize, uint32_t *pReplySize) {
return reinterpret_cast<ViperContext *>(handle)->handleCommand(cmdCode, cmdSize, (void *) pCmdData, pReplySize, (void *) pReplyData);
}
int32_t v4a_process(remote_handle64 handle, const uint8_t *inPcm, int inPcmLen, uint8_t *outPcm, int outPcmLen, uint32_t frameCount) {
audio_buffer_t in = {
.frameCount = frameCount,
.raw = (void *) inPcm,
};
audio_buffer_t out = {
.frameCount = frameCount,
.raw = (void *) outPcm,
};
return reinterpret_cast<ViperContext *>(handle)->process(&in, &out);
}

167
src/ViperStub.cpp Normal file
View File

@ -0,0 +1,167 @@
#include <remote.h>
#include <dsp_capabilities_utils.h>
extern "C" {
#include <pd_status_notification.h>
}
#include "v4a.h"
#include "ViperContext.h"
#include "ViPER4Android.h"
#include "log.h"
#include "hexagon.h"
#define STATUS_CONTEXT 0x37303061
int pd_status_notifier_callback(void *context, int domain, int session, remote_rpc_status_flags_t status){
int nErr = AEE_SUCCESS;
switch (status){
case FASTRPC_USER_PD_UP:
VIPER_LOGI("PD is up");
break;
case FASTRPC_USER_PD_EXIT:
VIPER_LOGI("PD closed");
break;
case FASTRPC_USER_PD_FORCE_KILL:
VIPER_LOGI("PD force kill");
break;
case FASTRPC_USER_PD_EXCEPTION:
VIPER_LOGI("PD exception");
break;
case FASTRPC_DSP_SSR:
VIPER_LOGI("DSP SSR");
break;
default:
nErr = AEE_EBADITEM;
break;
}
return nErr;
}
ViperContext::ViperContext() : mHandleLock() {
std::lock_guard<std::mutex> guard(mHandleLock);
VIPER_LOGI("ViperContext created");
int nErr = 0;
{
uint32_t cap = 0;
if (AEE_SUCCESS != (nErr = get_hex_arch_ver(CDSP_DOMAIN_ID, &cap))) {
VIPER_LOGF("get_hex_arch_ver failed: 0x%x", nErr);
} else {
VIPER_LOGI("CDSP arch: 0x%08x", cap);
}
}
{
struct remote_rpc_control_unsigned_module data;
data.domain = CDSP_DOMAIN_ID;
data.enable = 1;
if (AEE_SUCCESS != (nErr = remote_session_control(DSPRPC_CONTROL_UNSIGNED_MODULE, (void*)&data, sizeof(data)))) {
VIPER_LOGF("remote_session_control failed (unsigned PD): 0x%x", nErr);
}
}
{
struct remote_rpc_thread_params data;
data.domain = CDSP_DOMAIN_ID;
data.prio = -1;
data.stack_size = 7*1024*1024;;
if (AEE_SUCCESS != (nErr = remote_session_control(FASTRPC_THREAD_PARAMS, (void*)&data, sizeof(data)))) {
VIPER_LOGF("remote_session_control failed (stack size): 0x%x", nErr);
}
}
if(AEE_SUCCESS != (nErr = request_status_notifications_enable(CDSP_DOMAIN_ID, (void*)STATUS_CONTEXT, pd_status_notifier_callback))) {
if(nErr != AEE_EUNSUPPORTEDAPI) {
VIPER_LOGF("request_status_notifications_enable failed: 0x%x", nErr);
}
}
if (AEE_SUCCESS == (nErr = v4a_open(v4a_URI CDSP_DOMAIN, &handle))) {
VIPER_LOGI("Offloaded effect library initialized: 0x%lx", handle);
} else {
VIPER_LOGF("Failed to initialize offloaded effect library: 0x%x", nErr);
}
}
ViperContext::~ViperContext() {
std::lock_guard<std::mutex> guard(mHandleLock);
v4a_close(handle);
}
int32_t ViperContext::handleCommand(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *pReplySize, void *pReplyData) {
std::lock_guard<std::mutex> guard(mHandleLock);
hexagon_effect_config_t qdspCfg;
effect_config_t *cfg = nullptr;
if (cmdCode == EFFECT_CMD_SET_CONFIG) {
cfg = (effect_config_t *) pCmdData;
if (cfg->inputCfg.mask & EFFECT_CONFIG_FORMAT) {
switch (cfg->inputCfg.format) {
case AUDIO_FORMAT_PCM_FLOAT:
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
in_size = 4;
break;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
in_size = 3;
break;
case AUDIO_FORMAT_PCM_16_BIT:
in_size = 2;
break;
}
}
if (cfg->outputCfg.mask & EFFECT_CONFIG_FORMAT) {
switch (cfg->outputCfg.format) {
case AUDIO_FORMAT_PCM_FLOAT:
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
out_size = 4;
break;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
out_size = 3;
break;
case AUDIO_FORMAT_PCM_16_BIT:
out_size = 2;
break;
}
}
host2hexagon(cfg, &qdspCfg);
cmdSize = sizeof(qdspCfg);
pCmdData = (void *) &qdspCfg;
} else if (cmdCode == EFFECT_CMD_GET_CONFIG) {
cfg = (effect_config_t *) pReplyData;
*pReplySize = sizeof(qdspCfg);
pReplyData = (void *) &qdspCfg;
} else if (cmdCode == EFFECT_CMD_SET_PARAM) {
uint32_t key = *(uint32_t *) (((effect_param_t *) pCmdData)->data);
if (key >= PARAM_SET_MAX) {
VIPER_LOGE("handleSetParam: called with unknown key: %d", key);
return -EINVAL;
}
} else if (cmdCode == EFFECT_CMD_GET_PARAM) {
uint32_t key = *(uint32_t *) (((effect_param_t *) pCmdData)->data);
if (key >= PARAM_GET_MAX) {
VIPER_LOGE("handleGetParam: called with unknown key: %d", key);
return -EINVAL;
}
} else if (cmdCode == EFFECT_CMD_DUMP) {
return -EINVAL;
}
int replySize = pReplySize == nullptr ? 0 : *pReplySize;
int32_t result = v4a_command(handle, cmdCode, (const uint8_t *) pCmdData, cmdSize, (uint8_t *) pReplyData, replySize, pReplySize);
VIPER_LOGD("v4a_command: %d", result);
if (cmdCode == EFFECT_CMD_GET_CONFIG) {
hexagon2host(&qdspCfg, cfg);
*pReplySize = sizeof(*cfg);
}
return result;
}
int32_t ViperContext::process(audio_buffer_t *in, audio_buffer_t *out) {
std::lock_guard<std::mutex> guard(mHandleLock);
return v4a_process(handle, (const uint8_t *) in->raw, in->frameCount * in_size * 2, (uint8_t *) out->raw, in->frameCount * out_size * 2, in->frameCount);
}

View File

@ -392,6 +392,7 @@ enum effect_command_e
EFFECT_CMD_SET_FEATURE_CONFIG, // set current feature configuration
EFFECT_CMD_SET_AUDIO_SOURCE, // set the audio source (see audio.h, audio_source_t)
EFFECT_CMD_OFFLOAD, // set if effect thread is an offload one,
EFFECT_CMD_DUMP, // dump effect current state, for debugging
// send the ioHandle of the effect thread
EFFECT_CMD_FIRST_PROPRIETARY = 0x10000 // first proprietary command code
};

94
src/include/hexagon.h Normal file
View File

@ -0,0 +1,94 @@
#pragma once
#include "essential.h"
typedef struct {
uint32_t frameCount; // number of frames in buffer
union {
uint32_t raw; // raw pointer to start of buffer
uint32_t f32; // pointer to float 32 bit data at start of buffer
uint32_t s32; // pointer to signed 32 bit data at start of buffer
uint32_t s16; // pointer to signed 16 bit data at start of buffer
uint32_t u8; // pointer to unsigned 8 bit data at start of buffer
};
} hexagon_audio_buffer_t;
typedef struct
{
uint32_t getBuffer; // retrieve next buffer
uint32_t releaseBuffer; // release used buffer
uint32_t cookie; // for use by client of buffer provider functions
} hexagon_buffer_provider_t;
typedef struct {
hexagon_audio_buffer_t buffer; // buffer for use by process() function if not passed explicitly
uint32_t samplingRate; // sampling rate
uint32_t channels; // channel mask (see audio_channel_mask_t in audio.h)
hexagon_buffer_provider_t bufferProvider; // buffer provider
uint8_t format; // Audio format (see audio_format_t in audio.h)
uint8_t accessMode; // read/write or accumulate in buffer (effect_buffer_access_e)
uint16_t mask; // indicates which of the above fields is valid
} hexagon_buffer_config_t;
typedef struct {
hexagon_buffer_config_t inputCfg;
hexagon_buffer_config_t outputCfg;
} hexagon_effect_config_t;
static_assert(sizeof(hexagon_effect_config_t) == 64, "hexagon_effect_config_t must be 64 bytes");
static inline void host2hexagon(buffer_config_t *in, hexagon_buffer_config_t *out) {
if (in->mask & EFFECT_CONFIG_SMP_RATE) {
out->mask |= EFFECT_CONFIG_SMP_RATE;
out->samplingRate = in->samplingRate;
}
if (in->mask & EFFECT_CONFIG_CHANNELS) {
out->mask |= EFFECT_CONFIG_CHANNELS;
out->channels = in->channels;
}
if (in->mask & EFFECT_CONFIG_FORMAT) {
out->mask |= EFFECT_CONFIG_FORMAT;
out->format = in->format;
}
if (in->mask & EFFECT_CONFIG_ACC_MODE) {
out->mask |= EFFECT_CONFIG_ACC_MODE;
out->accessMode = in->accessMode;
}
}
static inline void host2hexagon(effect_config_t *in, hexagon_effect_config_t *out) {
memset(out, 0, sizeof(*out));
host2hexagon(&in->inputCfg, &out->inputCfg);
host2hexagon(&in->outputCfg, &out->outputCfg);
}
static inline void hexagon2host(hexagon_buffer_config_t *in, buffer_config_t *out) {
if (in->mask & EFFECT_CONFIG_SMP_RATE) {
out->mask |= EFFECT_CONFIG_SMP_RATE;
out->samplingRate = in->samplingRate;
}
if (in->mask & EFFECT_CONFIG_CHANNELS) {
out->mask |= EFFECT_CONFIG_CHANNELS;
out->channels = in->channels;
}
if (in->mask & EFFECT_CONFIG_FORMAT) {
out->mask |= EFFECT_CONFIG_FORMAT;
out->format = in->format;
}
if (in->mask & EFFECT_CONFIG_ACC_MODE) {
out->mask |= EFFECT_CONFIG_ACC_MODE;
out->accessMode = in->accessMode;
}
}
static inline void hexagon2host(hexagon_effect_config_t *in, effect_config_t *out) {
memset(out, 0, sizeof(*out));
hexagon2host(&in->inputCfg, &out->inputCfg);
hexagon2host(&in->outputCfg, &out->outputCfg);
}

9
src/include/v4a.idl Normal file
View File

@ -0,0 +1,9 @@
#include "AEEStdDef.idl"
#include "remote.idl"
const string IDL_VERSION = "1.0.0";
interface v4a : remote_handle64 {
int32_t command(in uint32_t cmdCode, in sequence<uint8_t> cmdData, inrout sequence<uint8_t> replyData, inrout uint32_t replySize);
int32_t process(in sequence<uint8_t> inPcm, rout sequence<uint8_t> outPcm, in uint32_t frameCount);
};

View File

@ -1,9 +1,23 @@
#pragma once
#include <android/log.h>
#define TAG "ViPER4Android"
#ifdef __hexagon__
#define FARF_HIGH 1
#define FARF_MEDIUM 1
#define FARF_ERROR 1
#define FARF_LOW 1
#include <HAP_farf.h>
#define VIPER_LOGF(...) FARF(FATAL, TAG ": " __VA_ARGS__)
#define VIPER_LOGD(...) FARF(LOW, TAG ": " __VA_ARGS__)
#define VIPER_LOGI(...) FARF(MEDIUM, TAG ": " __VA_ARGS__)
#define VIPER_LOGE(...) FARF(ERROR, TAG ": " __VA_ARGS__)
#else
#include <android/log.h>
#define VIPER_LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG, __VA_ARGS__)
#define VIPER_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define VIPER_LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define VIPER_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define VIPER_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#endif

9
src/version.c Normal file
View File

@ -0,0 +1,9 @@
#include "version_note.h"
const lib_ver_note_t so_ver __attribute__ ((section (".note.lib.ver")))
__attribute__ ((visibility ("default"))) = {
100,
0,
0,
"lib.ver.1.0.0.libv4a_skel.so:1.0.0",
};

View File

@ -8,12 +8,17 @@
#include "../log.h" // TODO: Remove this dependency
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
enum class Architecture : uint8_t {
UNKNOWN = 0,
ARM,
ARM64,
X86,
X86_64,
HEXAGON,
};
#if defined(__arm__)
@ -24,6 +29,8 @@ enum class Architecture : uint8_t {
#define VIPER_ARCHITECTURE Architecture::X86
#elif defined(__x86_64__)
#define VIPER_ARCHITECTURE Architecture::X86_64
#elif defined(__hexagon__)
#define VIPER_ARCHITECTURE Architecture::HEXAGON
#else
#warning "Unknown architecture"
#define VIPER_ARCHITECTURE Architecture::UNKNOWN
@ -37,4 +44,4 @@ enum class Architecture : uint8_t {
#define VIPER_NAME "ViPER4Android"
#define VIPER_AUTHORS "viper.WYF, Martmists, Iscle"
#define VIPER_DEFAULT_SAMPLING_RATE 44100
#define VIPER_DEFAULT_SAMPLING_RATE 44100

View File

@ -1,4 +1,5 @@
#include "Biquad.h"
#include "../constants.h"
#include <cmath>
// Iscle: Verified with the latest version at 13/12/2022

View File

@ -1,4 +1,5 @@
#include "HighShelf.h"
#include "../constants.h"
#include <cmath>
double HighShelf::Process(double sample) {

View File

@ -1,5 +1,6 @@
#include <cmath>
#include "IIR_1st.h"
#include "../constants.h"
// Seems to be taken from https://github.com/michaelwillis/dragonfly-reverb/blob/master/common/freeverb/efilter.cpp
// Or similar sources
@ -146,4 +147,4 @@ void IIR_1st::setZeroLPF(float frequency, uint32_t samplingRate) {
this->a1 = 0.f;
this->b0 = 1.f / (1.f + coeff);
this->b1 = coeff / (1.f + coeff);
}
}

View File

@ -1,4 +1,5 @@
#include "MultiBiquad.h"
#include "../constants.h"
#include <cmath>
MultiBiquad::MultiBiquad() {