// // Copyright © 2025 Agora // This file is part of TEN Framework, an open source project. // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // #include #include #include #include #include "fscvrt.h" #include "fscvrt_st.h" #include "biquad.h" // ========================================================================================== // internal tools // ========================================================================================== static int AUP_Fscvrt_FilterSet(int resampleRate, int* nsect, const float* B[_FSCVRT_MAXNSEC], const float* A[_FSCVRT_MAXNSEC], const float** G) { int idx; if (resampleRate == 2) { *nsect = _FSCVRT_1over2_LOWPASS_NSEC; for (idx = 0; idx < (*nsect); idx++) { B[idx] = &(_FSCVRT_1over2_LOWPASS_B[idx][0]); A[idx] = &(_FSCVRT_1over2_LOWPASS_A[idx][0]); } *G = _FSCVRT_1over2_LOWPASS_G; } else if (resampleRate == 3) { *nsect = _FSCVRT_1over3_LOWPASS_NSEC; for (idx = 0; idx < (*nsect); idx++) { B[idx] = &(_FSCVRT_1over3_LOWPASS_B[idx][0]); A[idx] = &(_FSCVRT_1over3_LOWPASS_A[idx][0]); } *G = _FSCVRT_1over3_LOWPASS_G; } else if (resampleRate == 4) { *nsect = _FSCVRT_1over4_LOWPASS_NSEC; for (idx = 0; idx < (*nsect); idx++) { B[idx] = &(_FSCVRT_1over4_LOWPASS_B[idx][0]); A[idx] = &(_FSCVRT_1over4_LOWPASS_A[idx][0]); } *G = _FSCVRT_1over4_LOWPASS_G; } else if (resampleRate == 6) { *nsect = _FSCVRT_1over6_LOWPASS_NSEC; for (idx = 0; idx < (*nsect); idx++) { B[idx] = &(_FSCVRT_1over6_LOWPASS_B[idx][0]); A[idx] = &(_FSCVRT_1over6_LOWPASS_A[idx][0]); } *G = _FSCVRT_1over6_LOWPASS_G; } else { // unknown resample rate return -1; } return 0; } static int AUP_Fscvrt_dynamMemPrepare(FscvrtSt* stHdl, void* memPtrExt, size_t memSize) { char* memPtr = NULL; int biquadInBufMemSize = 0; int biquadOutBufMemSize = 0; int totalMemSize = 0; if (stHdl == NULL) { return -1; } biquadInBufMemSize = _FSCVRT_ALIGN8(sizeof(float) * stHdl->biquadInBufLen); totalMemSize += biquadInBufMemSize; biquadOutBufMemSize = _FSCVRT_ALIGN8(sizeof(float) * stHdl->biquadOutBufLen); totalMemSize += biquadOutBufMemSize; totalMemSize = _FSCVRT_MAX(totalMemSize, 80); // if no external memory provided, we are only profiling the memory // requirement if (memPtrExt == NULL) { return (totalMemSize); } // if required memory is more than provided, error if ((size_t)totalMemSize > memSize) { return -1; } memPtr = (char*)memPtrExt; stHdl->biquadInBuf = NULL; if (biquadInBufMemSize != 0) { stHdl->biquadInBuf = (float*)memPtr; memPtr += biquadInBufMemSize; } stHdl->biquadOutBuf = NULL; if (biquadOutBufMemSize != 0) { stHdl->biquadOutBuf = (float*)memPtr; memPtr += biquadOutBufMemSize; } if (((int)(memPtr - (char*)memPtrExt)) > totalMemSize) { return -1; } return (totalMemSize); } static int AUP_Fscvrt_checkStatCfg(FscvrtStaticCfg* pCfg) { if (pCfg == NULL) { return -1; } if (pCfg->inputFs != 16000 && pCfg->inputFs != 24000 && pCfg->inputFs != 32000 && pCfg->inputFs != 48000) { return -1; } if (pCfg->outputFs != 16000 && pCfg->outputFs != 24000 && pCfg->outputFs != 32000 && pCfg->outputFs != 48000) { return -1; } if (pCfg->stepSz > AUP_FSCVRT_MAX_INPUT_LEN || pCfg->stepSz < 1) { return -1; } if (pCfg->inputType != 0) { pCfg->inputType = 1; } if (pCfg->outputType != 0) { pCfg->outputType = 1; } return 0; } static int AUP_Fscvrt_publishStaticCfg(FscvrtSt* stHdl) { int tmpRatio; int ret; int maxResmplRate = 0; stHdl->mode = 0; stHdl->upSmplRate = 1; stHdl->downSmplRate = 1; if (stHdl->stCfg.inputFs != stHdl->stCfg.outputFs) { if (stHdl->stCfg.outputFs > stHdl->stCfg.inputFs) { tmpRatio = (stHdl->stCfg.outputFs / stHdl->stCfg.inputFs); if (stHdl->stCfg.outputFs == tmpRatio * stHdl->stCfg.inputFs) { stHdl->mode = 1; stHdl->upSmplRate = tmpRatio; stHdl->downSmplRate = 1; } else { stHdl->mode = 3; stHdl->upSmplRate = _FSCVRT_COMMON_FS / stHdl->stCfg.inputFs; stHdl->downSmplRate = _FSCVRT_COMMON_FS / stHdl->stCfg.outputFs; } } else { // stHdl->stCfg.outputFs < stHdl->stCfg.inputFs tmpRatio = (stHdl->stCfg.inputFs / stHdl->stCfg.outputFs); if (stHdl->stCfg.inputFs == tmpRatio * stHdl->stCfg.outputFs) { stHdl->mode = 2; stHdl->upSmplRate = 1; stHdl->downSmplRate = tmpRatio; } else { stHdl->mode = 3; stHdl->upSmplRate = _FSCVRT_COMMON_FS / stHdl->stCfg.inputFs; stHdl->downSmplRate = _FSCVRT_COMMON_FS / stHdl->stCfg.outputFs; } } } if (stHdl->mode == 0) { stHdl->biquadInBufLen = 0; stHdl->biquadOutBufLen = 0; } else { stHdl->biquadInBufLen = stHdl->stCfg.stepSz * stHdl->upSmplRate; stHdl->biquadOutBufLen = 2 * (stHdl->stCfg.stepSz * stHdl->upSmplRate); } maxResmplRate = _FSCVRT_MAX(stHdl->upSmplRate, stHdl->downSmplRate); stHdl->nSec = 0; memset(stHdl->biquadB, 0, sizeof(stHdl->biquadB)); memset(stHdl->biquadA, 0, sizeof(stHdl->biquadA)); stHdl->biquadG = NULL; // gain for each section if (stHdl->mode != 0) { ret = AUP_Fscvrt_FilterSet(maxResmplRate, &(stHdl->nSec), stHdl->biquadB, stHdl->biquadA, &(stHdl->biquadG)); if (ret < 0) { return -1; } } return 0; } static int AUP_Fscvrt_resetVariables(FscvrtSt* stHdl) { stHdl->biquadInBufCnt = 0; stHdl->biquadOutBufCnt = 0; if (stHdl->dynamMemPtr != NULL && stHdl->dynamMemSize > 0) { memset(stHdl->dynamMemPtr, 0, stHdl->dynamMemSize); } return 0; } // ========================================================================================== // public APIs // ========================================================================================== int AUP_Fscvrt_create(void** stPtr) { FscvrtSt* tmpPtr; if (stPtr == NULL) { return -1; } *stPtr = (void*)malloc(sizeof(FscvrtSt)); if (*stPtr == NULL) { return -1; } memset(*stPtr, 0, sizeof(FscvrtSt)); tmpPtr = (FscvrtSt*)(*stPtr); tmpPtr->dynamMemPtr = NULL; tmpPtr->dynamMemSize = 0; tmpPtr->stCfg.inputFs = 24000; tmpPtr->stCfg.outputFs = 32000; tmpPtr->stCfg.stepSz = 240; // 10ms processing step tmpPtr->stCfg.inputType = 0; // short in tmpPtr->stCfg.outputType = 0; // short out if (AUP_Biquad_create(&(tmpPtr->biquadSt)) < 0) { return -1; } return 0; } int AUP_Fscvrt_destroy(void** stPtr) { FscvrtSt* stHdl; if (stPtr == NULL) { return 0; } stHdl = (FscvrtSt*)(*stPtr); if (stHdl == NULL) { return 0; } AUP_Biquad_destroy(&(stHdl->biquadSt)); if (stHdl->dynamMemPtr != NULL) { free(stHdl->dynamMemPtr); } stHdl->dynamMemPtr = NULL; free(stHdl); (*stPtr) = NULL; return 0; } int AUP_Fscvrt_memAllocate(void* stPtr, const FscvrtStaticCfg* pCfg) { FscvrtSt* stHdl = NULL; FscvrtStaticCfg tmpStatCfg = {0}; Biquad_StaticCfg bqStatCfg; int idx, ret; int totalMemSize = 0; if (stPtr == NULL || pCfg == NULL) { return -1; } stHdl = (FscvrtSt*)(stPtr); memcpy(&tmpStatCfg, pCfg, sizeof(FscvrtStaticCfg)); if (AUP_Fscvrt_checkStatCfg(&tmpStatCfg) < 0) { return -1; } memcpy(&(stHdl->stCfg), &tmpStatCfg, sizeof(FscvrtStaticCfg)); if (AUP_Fscvrt_publishStaticCfg(stHdl) < 0) { return -1; } // check memory requirement totalMemSize = AUP_Fscvrt_dynamMemPrepare(stHdl, NULL, 0); if (totalMemSize < 0) { return -1; } // allocate dynamic memory if ((size_t)totalMemSize > stHdl->dynamMemSize) { if (stHdl->dynamMemPtr != NULL) { free(stHdl->dynamMemPtr); stHdl->dynamMemSize = 0; } stHdl->dynamMemPtr = (void*)malloc(totalMemSize); if (stHdl->dynamMemPtr == NULL) { return -1; } stHdl->dynamMemSize = totalMemSize; } memset(stHdl->dynamMemPtr, 0, stHdl->dynamMemSize); // setup the pointers/variable if (AUP_Fscvrt_dynamMemPrepare(stHdl, stHdl->dynamMemPtr, stHdl->dynamMemSize) < 0) { return -1; } // memAllocation for upSmplBiquadSt and downSmplBiquadSt if (stHdl->nSec != 0) { if (stHdl->nSec > AGORA_UAP_BIQUAD_MAX_SECTION) { return -1; } memset(&bqStatCfg, 0, sizeof(Biquad_StaticCfg)); bqStatCfg.maxNSample = (size_t)(stHdl->biquadInBufLen); bqStatCfg.nsect = stHdl->nSec; for (idx = 0; idx < stHdl->nSec; idx++) { bqStatCfg.B[idx] = stHdl->biquadB[idx]; bqStatCfg.A[idx] = stHdl->biquadA[idx]; } bqStatCfg.G = stHdl->biquadG; ret = AUP_Biquad_memAllocate(stHdl->biquadSt, &bqStatCfg); if (ret < 0) { return -1; } } return 0; } int AUP_Fscvrt_init(void* stPtr) { FscvrtSt* stHdl; if (stPtr == NULL) { return -1; } stHdl = (FscvrtSt*)(stPtr); // clear/reset run-time variables if (AUP_Fscvrt_resetVariables(stHdl) < 0) { return -1; } // init submodules ... if (stHdl->biquadSt != NULL && stHdl->nSec != 0) { if (AUP_Biquad_init(stHdl->biquadSt) < 0) { return -1; } } return 0; } int AUP_Fscvrt_getStaticCfg(const void* stPtr, FscvrtStaticCfg* pCfg) { const FscvrtSt* stHdl; if (stPtr == NULL || pCfg == NULL) { return -1; } stHdl = (const FscvrtSt*)(stPtr); memcpy(pCfg, &(stHdl->stCfg), sizeof(FscvrtStaticCfg)); return 0; } int AUP_Fscvrt_getInfor(const void* stPtr, FscvrtGetData* buff) { const FscvrtSt* stHdl; int delayBiquad = 0; int tmp; if (stPtr == NULL || buff == NULL) { return -1; } stHdl = (const FscvrtSt*)(stPtr); if (stHdl->nSec != 0) { if (AUP_Biquad_getAlgDelay(stHdl->biquadSt, &delayBiquad) < 0) { return -1; } } if (stHdl->mode == 0) { buff->delayInInputFs = 0; } else if (stHdl->mode == 1) { buff->delayInInputFs = (int)roundf(delayBiquad / (float)(stHdl->upSmplRate)); } else if (stHdl->mode == 2) { // direct downsampling buff->delayInInputFs = delayBiquad; } else { // stHdl->mode == 3 buff->delayInInputFs = (int)roundf(delayBiquad / (float)(stHdl->upSmplRate)); } tmp = stHdl->stCfg.stepSz * stHdl->upSmplRate / stHdl->downSmplRate; if (tmp * stHdl->downSmplRate == stHdl->stCfg.stepSz * stHdl->upSmplRate) { buff->maxOutputStepSz = tmp; } else { buff->maxOutputStepSz = tmp + 1; } return 0; } int AUP_Fscvrt_proc(void* stPtr, const FscvrtInData* pIn, FscvrtOutData* pOut) { FscvrtSt* stHdl = NULL; const FscvrtStaticCfg* pCfg; Biquad_InputData bqdInData; Biquad_OutputData bqdOutData; const short* shortSrcPtr = NULL; const float* floatSrcPtr = NULL; short* shortTgtPtr = NULL; float* floatTgtPtr = NULL; int idx, tgtIdx; int nOutSamples = 0, samplesTaken = 0, samplesLeft = 0; int jumpRate; if (stPtr == NULL || pIn == NULL || pOut == NULL || pIn->inDataSeq == NULL || pOut->outDataSeq == NULL) { // pCtrl == NULL return -1; } stHdl = (FscvrtSt*)(stPtr); pCfg = (const FscvrtStaticCfg*)&(stHdl->stCfg); shortSrcPtr = (const short*)(pIn->inDataSeq); floatSrcPtr = (const float*)(pIn->inDataSeq); // ============================================================================== // mode-0: bypass if (stHdl->mode == 0) { // direct bypass if (pIn->outDataSeqLen < pCfg->stepSz) { return -1; } pOut->nOutData = pCfg->stepSz; pOut->outDataType = pCfg->outputType; if (pIn->inDataSeq == pOut->outDataSeq) { if (pCfg->outputType == pCfg->inputType) return 0; // we don't need to do anything return -1; // if input buffer and the output buffer are the same, but required // different data type: error, we currently do not support such usecase } if (pCfg->inputType == 0 && pCfg->outputType == 0) { memcpy(pOut->outDataSeq, pIn->inDataSeq, sizeof(short) * pCfg->stepSz); } else if (pCfg->inputType == 1 && pCfg->outputType == 1) { memcpy(pOut->outDataSeq, pIn->inDataSeq, sizeof(float) * pCfg->stepSz); } else if (pCfg->inputType == 0 && pCfg->outputType == 1) { for (idx = 0; idx < pCfg->stepSz; idx++) { ((float*)pOut->outDataSeq)[idx] = ((short*)pIn->inDataSeq)[idx]; } } else { // if (pCfg->inputType == 1 && pCfg->outputType == 0) for (idx = 0; idx < pCfg->stepSz; idx++) { ((short*)pOut->outDataSeq)[idx] = (short)_FSCVRT_FLOAT2SHORT(((float*)pIn->inDataSeq)[idx]); } } return 0; } // prepare input buffer for Biquad ..... memset(stHdl->biquadInBuf, 0, sizeof(float) * stHdl->biquadInBufLen); if (pCfg->inputType == 0) { for (idx = 0; idx < pCfg->stepSz; idx++) { stHdl->biquadInBuf[idx * (stHdl->upSmplRate)] = ((float)shortSrcPtr[idx]) * stHdl->upSmplRate; } } else { for (idx = 0; idx < pCfg->stepSz; idx++) { stHdl->biquadInBuf[idx * (stHdl->upSmplRate)] = floatSrcPtr[idx] * stHdl->upSmplRate; } } // biquad filtering ...... memset(&bqdInData, 0, sizeof(Biquad_InputData)); memset(&bqdOutData, 0, sizeof(Biquad_OutputData)); bqdInData.samplesPtr = (const void*)(stHdl->biquadInBuf); bqdInData.sampleType = 1; bqdInData.nsamples = (size_t)(pCfg->stepSz * stHdl->upSmplRate); bqdOutData.outputBuff = (void*)&(stHdl->biquadOutBuf[stHdl->biquadOutBufCnt]); if (stHdl->biquadOutBufCnt + (pCfg->stepSz * stHdl->upSmplRate) > stHdl->biquadOutBufLen) { return -1; } if (AUP_Biquad_proc(stHdl->biquadSt, &bqdInData, &bqdOutData) < 0) { return -1; } stHdl->biquadOutBufCnt += (pCfg->stepSz * stHdl->upSmplRate); // checking the output buffer ......... nOutSamples = stHdl->biquadOutBufCnt / stHdl->downSmplRate; if (pIn->outDataSeqLen < nOutSamples) { return -1; } // prepare output data, downsampling and throwing out ...... pOut->nOutData = nOutSamples; pOut->outDataType = pCfg->outputType; shortTgtPtr = (short*)pOut->outDataSeq; floatTgtPtr = (float*)pOut->outDataSeq; jumpRate = stHdl->downSmplRate; if (pCfg->outputType == 0) { // -> shortTgtPtr for (idx = (jumpRate - 1), tgtIdx = 0; idx < stHdl->biquadOutBufCnt; idx += jumpRate, tgtIdx++) { shortTgtPtr[tgtIdx] = _FSCVRT_FLOAT2SHORT(stHdl->biquadOutBuf[idx]); } } else { // -> floatTgtPtr for (idx = (jumpRate - 1), tgtIdx = 0; idx < stHdl->biquadOutBufCnt; idx += jumpRate, tgtIdx++) { floatTgtPtr[tgtIdx] = stHdl->biquadOutBuf[idx]; } } if (nOutSamples != tgtIdx) { return -1; } // update the stHdl->biquadOutBuf and stHdl->biquadOutBufCnt samplesTaken = nOutSamples * jumpRate; samplesLeft = stHdl->biquadOutBufCnt - samplesTaken; if (samplesLeft == 0) { stHdl->biquadOutBufCnt = 0; } else if (samplesLeft > 0) { stHdl->biquadOutBufCnt = samplesLeft; memmove(stHdl->biquadOutBuf, &(stHdl->biquadOutBuf[samplesTaken]), sizeof(float) * samplesLeft); } else { // samplesLeft < 0 stHdl->biquadOutBufCnt = 0; return -1; } return 0; }