feat: accept any audio input (any rate/channels, wav/mp3/flac) via miniaudio (#2994)

* feat(runtime): accept any audio (any sample-rate/channels, wav/mp3/flac) via miniaudio

Replaces the 16k-mono-PCM16-only WAV reader with a miniaudio-based loader
(funasr-common/funasr_audio.h) that decodes wav/mp3/flac at any sample rate and
channel count, resampling to 16 kHz mono. Verified: a 44.1k stereo file and an 8k
mono file both transcribe correctly across cli/sensevoice/paraformer.

* fix(audio): inline loader (avoid duplicate symbols), null-path guard, check decoder read result (gemini review)

---------

Co-authored-by: LauraGPT <[email protected]>
This commit is contained in:
zhifu gao
2026-06-19 22:40:02 +08:00
committed by GitHub
co-authored by LauraGPT
parent 842e62f875
commit a83e1d9c38
8 changed files with 95928 additions and 50 deletions
@@ -1,5 +1,6 @@
set(TARGET llama-funasr-cli)
add_executable(${TARGET} funasr-cli.cpp)
install(TARGETS ${TARGET} RUNTIME)
target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../funasr-common)
target_link_libraries(${TARGET} PRIVATE llama ggml ${CMAKE_THREAD_LIBS_INIT})
target_compile_features(${TARGET} PRIVATE cxx_std_17)
@@ -23,34 +23,9 @@
#include <string>
#include <vector>
// ======================= WAV (16 kHz mono PCM16) =======================
static bool read_wav16(const char * path, std::vector<float> & out) {
FILE * f = fopen(path, "rb"); if (!f) return false;
char riff[4]; fread(riff, 1, 4, f);
if (strncmp(riff, "RIFF", 4)) { fclose(f); return false; }
fseek(f, 4, SEEK_CUR); char wave[4]; fread(wave, 1, 4, f);
if (strncmp(wave, "WAVE", 4)) { fclose(f); return false; }
uint16_t ch = 1, bits = 16; uint32_t sr = 16000;
while (!feof(f)) {
char id[4]; if (fread(id, 1, 4, f) != 4) break;
uint32_t sz; if (fread(&sz, 4, 1, f) != 1) break;
if (!strncmp(id, "fmt ", 4)) {
uint16_t fmt; fread(&fmt, 2, 1, f); fread(&ch, 2, 1, f);
fread(&sr, 4, 1, f); fseek(f, 6, SEEK_CUR); fread(&bits, 2, 1, f);
if (sz > 16) fseek(f, sz - 16, SEEK_CUR);
} else if (!strncmp(id, "data", 4)) {
if (ch < 1 || bits != 16) { fclose(f); fprintf(stderr, "only 16-bit PCM WAV supported (ch=%u bits=%u)\n", ch, bits); return false; }
int n = sz / (bits / 8);
std::vector<int16_t> pcm(n); fread(pcm.data(), 2, n, f);
out.resize(n / ch);
for (int i = 0; i < n / ch; i++) out[i] = pcm[i * ch] / 32768.0f; // ch0
fclose(f);
if (sr != 16000) fprintf(stderr, "warning: sample rate %u != 16000\n", sr);
return true;
} else fseek(f, sz, SEEK_CUR);
}
fclose(f); return false;
}
// any audio (wav/mp3/flac, any rate/channels) -> 16 kHz mono f32, via miniaudio
#define FUNASR_AUDIO_IMPLEMENTATION
#include "funasr_audio.h"
// ======================= kaldi fbank + LFR =======================
static const int FS=16000, WINLEN=400, SHIFT=160, NFFT=512, NMEL=80, LFR_M=7, LFR_N=6;
@@ -202,7 +177,7 @@ int main(int argc,char**argv){
if(enc_path.empty()||llm_path.empty()||wav_path.empty()){fprintf(stderr,"missing args\n");return 1;}
std::vector<float> wav;
if(!read_wav16(wav_path.c_str(),wav)){fprintf(stderr,"failed to read wav\n");return 1;}
if(!funasr_load_audio_16k_mono(wav_path.c_str(),wav)){fprintf(stderr,"failed to read audio\n");return 1;}
int64_t t0=ggml_time_us();
enc_model em; if(!load_enc(enc_path.c_str(),em))return 1;
@@ -0,0 +1,49 @@
// funasr_audio.h — load any audio file (WAV/MP3/FLAC/OGG...) as 16 kHz mono f32,
// via miniaudio's decoder (handles arbitrary sample rate, channels, bit depth).
// One TU must define FUNASR_AUDIO_IMPLEMENTATION before including.
#ifndef FUNASR_AUDIO_H
#define FUNASR_AUDIO_H
#include <vector>
// returns true on success; out = mono 16 kHz float samples in [-1,1]
inline bool funasr_load_audio_16k_mono(const char * path, std::vector<float> & out);
#endif
#ifdef FUNASR_AUDIO_IMPLEMENTATION
// keep only the decoder + data conversion (resampler/channel mixer); drop playback/capture
#define MA_NO_DEVICE_IO
#define MA_NO_ENGINE
#define MA_NO_GENERATION
#define MA_NO_THREADING
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#include <cstdio>
inline bool funasr_load_audio_16k_mono(const char * path, std::vector<float> & out) {
if (!path) { fprintf(stderr, "audio: null path\n"); return false; }
ma_decoder_config cfg = ma_decoder_config_init(ma_format_f32, 1, 16000); // f32, mono, 16k
ma_decoder dec;
if (ma_decoder_init_file(path, &cfg, &dec) != MA_SUCCESS) {
fprintf(stderr, "audio: failed to open/decode %s (supported: wav/mp3/flac)\n", path);
return false;
}
out.clear();
ma_uint64 nframes = 0;
if (ma_decoder_get_length_in_pcm_frames(&dec, &nframes) == MA_SUCCESS && nframes > 0) {
out.resize(nframes);
ma_uint64 got = 0;
ma_result r = ma_decoder_read_pcm_frames(&dec, out.data(), nframes, &got);
if (r != MA_SUCCESS && r != MA_AT_END) { ma_decoder_uninit(&dec); fprintf(stderr, "audio: decode error\n"); return false; }
out.resize(got);
} else {
// length unknown (e.g. some mp3 streams): read in chunks until EOF
std::vector<float> buf(16000);
for (;;) {
ma_uint64 got = 0;
ma_result r = ma_decoder_read_pcm_frames(&dec, buf.data(), buf.size(), &got);
out.insert(out.end(), buf.begin(), buf.begin() + got);
if (got < buf.size() || (r != MA_SUCCESS && r != MA_AT_END)) break;
}
}
ma_decoder_uninit(&dec);
return !out.empty();
}
#endif
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
set(TARGET llama-funasr-paraformer)
add_executable(${TARGET} funasr-paraformer.cpp)
install(TARGETS ${TARGET} RUNTIME)
target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../funasr-common)
target_link_libraries(${TARGET} PRIVATE ggml ${CMAKE_THREAD_LIBS_INIT})
target_compile_features(${TARGET} PRIVATE cxx_std_17)
@@ -19,13 +19,9 @@
static const float LN_EPS = 1e-5f;
// ===== WAV + kaldi fbank (shared) =====
static bool read_wav16(const char*p,std::vector<float>&o){FILE*f=fopen(p,"rb");if(!f)return false;char r[4];fread(r,1,4,f);
if(strncmp(r,"RIFF",4)){fclose(f);return false;}fseek(f,4,SEEK_CUR);char wv[4];fread(wv,1,4,f);if(strncmp(wv,"WAVE",4)){fclose(f);return false;}
uint16_t ch=1,bits=16;uint32_t sr=16000;while(!feof(f)){char id[4];if(fread(id,1,4,f)!=4)break;uint32_t sz;if(fread(&sz,4,1,f)!=1)break;
if(!strncmp(id,"fmt ",4)){uint16_t fm;fread(&fm,2,1,f);fread(&ch,2,1,f);fread(&sr,4,1,f);fseek(f,6,SEEK_CUR);fread(&bits,2,1,f);if(sz>16)fseek(f,sz-16,SEEK_CUR);}
else if(!strncmp(id,"data",4)){if(ch<1||bits!=16){fclose(f);fprintf(stderr,"only 16-bit PCM WAV (ch=%u bits=%u)\n",ch,bits);return false;}int n=sz/(bits/8);std::vector<int16_t>pc(n);fread(pc.data(),2,n,f);o.resize(n/ch);for(int i=0;i<n/ch;i++)o[i]=pc[i*ch]/32768.0f;fclose(f);return true;}
else fseek(f,sz,SEEK_CUR);}fclose(f);return false;}
// ===== audio loader: any wav/mp3/flac, any rate/channels -> 16k mono (miniaudio) =====
#define FUNASR_AUDIO_IMPLEMENTATION
#include "funasr_audio.h"
static const int FS=16000,WINLEN=400,SHIFT=160,NFFT=512,NMEL=80,LFR_M=7,LFR_N=6;
static const float PREEMPH=0.97f,LOWF=20.0f,HIGHF=8000.0f;
static inline float melf(float f){return 1127.0f*logf(1.0f+f/700.0f);}
@@ -135,7 +131,7 @@ int main(int argc,char**argv){
const int D=m.c.d_model,F=560,V=m.c.vocab;
// fbank
std::vector<float>wav;if(!read_wav16(wav_path.c_str(),wav)){fprintf(stderr,"read wav failed\n");return 1;}
std::vector<float>wav;if(!funasr_load_audio_16k_mono(wav_path.c_str(),wav)){fprintf(stderr,"read audio failed\n");return 1;}
int64_t t0=ggml_time_us();int T=0;auto fb=compute_fbank(wav,T);
// CMVN (Paraformer applies it): (x+shift)*scale
float*shift=(float*)m.g("cmvn.shift")->data,*scale=(float*)m.g("cmvn.scale")->data;
@@ -1,5 +1,6 @@
set(TARGET llama-funasr-sensevoice)
add_executable(${TARGET} funasr-sensevoice.cpp)
install(TARGETS ${TARGET} RUNTIME)
target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../funasr-common)
target_link_libraries(${TARGET} PRIVATE ggml ${CMAKE_THREAD_LIBS_INIT})
target_compile_features(${TARGET} PRIVATE cxx_std_17)
@@ -20,18 +20,9 @@
static const float LN_EPS = 1e-5f;
// ---- WAV (16k mono PCM16) + kaldi fbank (shared with funasr-cli) ----
static bool read_wav16(const char*path,std::vector<float>&out){
FILE*f=fopen(path,"rb"); if(!f)return false; char r[4]; fread(r,1,4,f);
if(strncmp(r,"RIFF",4)){fclose(f);return false;} fseek(f,4,SEEK_CUR); char wv[4]; fread(wv,1,4,f);
if(strncmp(wv,"WAVE",4)){fclose(f);return false;} uint16_t ch=1,bits=16; uint32_t sr=16000;
while(!feof(f)){char id[4]; if(fread(id,1,4,f)!=4)break; uint32_t sz; if(fread(&sz,4,1,f)!=1)break;
if(!strncmp(id,"fmt ",4)){uint16_t fmt; fread(&fmt,2,1,f); fread(&ch,2,1,f); fread(&sr,4,1,f); fseek(f,6,SEEK_CUR); fread(&bits,2,1,f); if(sz>16)fseek(f,sz-16,SEEK_CUR);}
else if(!strncmp(id,"data",4)){if(ch<1||bits!=16){fclose(f);fprintf(stderr,"only 16-bit PCM WAV (ch=%u bits=%u)\n",ch,bits);return false;} int n=sz/(bits/8); std::vector<int16_t> pcm(n); fread(pcm.data(),2,n,f); out.resize(n/ch);
for(int i=0;i<n/ch;i++)out[i]=pcm[i*ch]/32768.0f; fclose(f); if(sr!=16000)fprintf(stderr,"warn: sr %u\n",sr); return true;}
else fseek(f,sz,SEEK_CUR);}
fclose(f); return false;
}
// ---- audio loader: any wav/mp3/flac, any rate/channels -> 16k mono (miniaudio) ----
#define FUNASR_AUDIO_IMPLEMENTATION
#include "funasr_audio.h"
static const int FS=16000,WINLEN=400,SHIFT=160,NFFT=512,NMEL=80,LFR_M=7,LFR_N=6;
static const float PREEMPH=0.97f,LOWF=20.0f,HIGHF=8000.0f;
static inline float melf(float f){return 1127.0f*logf(1.0f+f/700.0f);}
@@ -122,7 +113,7 @@ int main(int argc,char**argv){
// fbank [T,F]: from wav (-a) or precomputed (-f)
int32_t T=0,Fc=F; std::vector<float> fb;
if(!wav_path.empty()){
std::vector<float> wav; if(!read_wav16(wav_path.c_str(),wav)){fprintf(stderr,"read wav failed\n");return 1;}
std::vector<float> wav; if(!funasr_load_audio_16k_mono(wav_path.c_str(),wav)){fprintf(stderr,"read audio failed\n");return 1;}
int t=0; fb=compute_fbank(wav,t); T=t;
} else {
FILE*f=fopen(fbank_path.c_str(),"rb"); if(!f){fprintf(stderr,"open fbank\n");return 1;}