C语言基础入门:如何调用SenseVoice-Small语音识别REST API

张开发
2026/4/21 0:45:16 15 分钟阅读

分享文章

C语言基础入门:如何调用SenseVoice-Small语音识别REST API
C语言基础入门如何调用SenseVoice-Small语音识别REST API最近在做一个嵌入式设备上的语音交互项目需要把采集到的音频实时转成文字。找了一圈发现SenseVoice-Small这个模型在中文识别上效果不错而且有现成的REST API可以调用。但官方文档给的例子大多是Python的对于习惯了C/C、在资源受限环境下开发的工程师来说怎么用C语言去调这个API就成了一个挺实际的问题。如果你也遇到了类似的情况想在C程序里集成语音识别那这篇文章就是为你准备的。我会手把手带你走一遍完整的流程从读取一个WAV文件开始到把它编码成API能识别的格式再发送请求最后解析返回的文字结果。整个过程不需要复杂的框架用libcurl这个常见的HTTP客户端库就能搞定。代码我都测试过你可以直接拿去用或者根据自己的项目改改。1. 环境准备与工具选择在开始写代码之前我们得先把“工具箱”准备好。对于C语言项目来说选择合适的库能省去很多造轮子的时间。1.1 核心库libcurl我们要通过HTTP协议和远端的语音识别服务通信在C语言里libcurl几乎是完成这个任务的标准选择。它稳定、高效而且跨平台支持做得很好无论是Linux、Windows还是嵌入式系统都能找到对应的版本。怎么安装呢如果你用的是Ubuntu或者Debian系的Linux一条命令就行sudo apt-get install libcurl4-openssl-dev在Windows上你可以去libcurl的官网下载预编译好的库文件或者用vcpkg、MSYS2这样的包管理器来安装。安装好后记得在编译你的程序时加上链接库的参数-lcurl。1.2 辅助库cJSON语音识别API的请求和返回的数据都是JSON格式的。C语言处理JSON不像Python那么方便我们需要一个轻量级的解析库。cJSON是个很好的选择它只有一个头文件和一个源文件不依赖其他库特别适合集成到嵌入式项目里。你可以从GitHub上找到cJSON的源码直接把它下载下来放到你的项目目录里就行。使用的时候包含cJSON.h头文件并在编译时把cJSON.c一起编译进去。1.3 测试用的音频文件为了演示我们需要一个测试用的音频文件。建议使用标准的、单声道的WAV文件PCM格式采样率16kHz就行这符合大多数语音识别模型的输入要求。你可以自己用录音软件录一段或者从网上下载一个样例。我这里准备了一个叫test_audio.wav的文件里面是一句“今天天气怎么样”。准备好这些我们的开发环境就算搭好了。接下来我们看看整个调用流程是怎么串起来的。2. 理解API调用流程调用一个REST API听起来挺“现代”但拆解开来其实就是几个标准的步骤。对于语音识别整个过程可以看作一个“发送音频取回文字”的管道。首先语音识别服务假设你已经部署好了地址是http://your-server-ip:port/asr在等着我们发送一个HTTP POST请求。这个请求不能直接把WAV文件扔过去需要把二进制音频数据包装一下。通常API会要求我们将音频数据进行Base64编码然后放到一个JSON结构体里。这个JSON就像是一个信封里面写着“这是一段音频它的数据是XXX”。同时我们可能还需要在HTTP请求头里告诉服务器我们发送的是JSON格式的内容Content-Type: application/json。服务器收到请求后会进行识别然后把结果再用一个JSON包着还给我们。这个返回的JSON里最重要的就是text字段里面就是我们想要的识别文字。所以用C语言实现我们需要做三件核心事读取WAV文件并把它的数据部分进行Base64编码。用libcurl构造一个携带了JSON数据的HTTP POST请求并发送它。收到服务器的回复后用cJSON解析出里面的文字。理清了思路我们就从第一步开始看看怎么处理音频文件。3. 音频文件读取与Base64编码WAV文件有个标准的格式开头是一段文件头描述了音频的采样率、位深等信息后面才是真正的音频数据PCM样本。为了简单起见我们假设你用的已经是符合要求的WAV文件。我们的目标是跳过文件头读取纯音频数据并把它转换成Base64字符串。3.1 读取WAV文件数据下面这个函数load_wav_file会尝试打开一个WAV文件找到数据块的位置然后把音频数据读进内存。#include stdio.h #include stdlib.h #include string.h // 一个简单的结构体用来存放从WAV文件读取的数据和大小 typedef struct { unsigned char* data; long size; } AudioData; AudioData load_wav_file(const char* filename) { FILE* file fopen(filename, rb); AudioData audio {NULL, 0}; if (!file) { printf(无法打开文件: %s\n, filename); return audio; } // 寻找WAV文件中的“data”标记它后面就是音频数据 char chunk_id[5] {0}; unsigned int chunk_size 0; // 先读取“RIFF”头 fread(chunk_id, 1, 4, file); fread(chunk_size, 1, 4, file); fread(chunk_id, 1, 4, file); // 读取“WAVE” // 循环查找“data”块 while (1) { fread(chunk_id, 1, 4, file); fread(chunk_size, 1, 4, file); if (strncmp(chunk_id, data, 4) 0) { // 找到数据块 audio.data (unsigned char*)malloc(chunk_size); if (audio.data) { fread(audio.data, 1, chunk_size, file); audio.size chunk_size; } else { printf(内存分配失败\n); } break; } else { // 跳过其他块如fmt 块 fseek(file, chunk_size, SEEK_CUR); } // 简单防止文件格式错误导致死循环 if (ftell(file) chunk_size 8) { break; } } fclose(file); return audio; } // 记得用完数据后要释放内存 void free_audio_data(AudioData* audio) { if (audio-data) { free(audio-data); audio-data NULL; audio-size 0; } }这段代码跳过了WAV文件里复杂的头信息解析直接定位到存储音频数据的“data”块对于快速测试来说足够了。拿到audio.data和audio.size后我们就有了需要编码的原始二进制数据。3.2 实现Base64编码Base64编码是把二进制数据转换成由64个字符A-Z, a-z, 0-9, , /组成的文本。C标准库里没有现成的Base64函数但我们可以自己实现一个。下面是一个简洁的实现#include stdint.h // Base64编码表 static const char base64_table[] ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/; char* base64_encode(const unsigned char* data, size_t input_length) { size_t output_length 4 * ((input_length 2) / 3); // 计算输出字符串长度 char* encoded_data (char*)malloc(output_length 1); // 多分配1个字节放字符串结束符\0 if (encoded_data NULL) return NULL; for (size_t i 0, j 0; i input_length;) { uint32_t octet_a i input_length ? data[i] : 0; uint32_t octet_b i input_length ? data[i] : 0; uint32_t octet_c i input_length ? data[i] : 0; uint32_t triple (octet_a 16) | (octet_b 8) | octet_c; encoded_data[j] base64_table[(triple 18) 0x3F]; encoded_data[j] base64_table[(triple 12) 0x3F]; encoded_data[j] base64_table[(triple 6) 0x3F]; encoded_data[j] base64_table[triple 0x3F]; } // 处理填充字符 int padding input_length % 3; if (padding 0) { for (int i 0; i 3 - padding; i) { encoded_data[output_length - 1 - i] ; } } encoded_data[output_length] \0; // 字符串结尾 return encoded_data; }现在我们可以把这两步连起来读取WAV文件然后编码。AudioData audio load_wav_file(test_audio.wav); if (audio.data audio.size 0) { char* base64_audio base64_encode(audio.data, audio.size); printf(Base64编码后的音频数据前100字符: %.100s...\n, base64_audio); // 接下来base64_audio要放到JSON里 // ... 使用完毕后记得 free(base64_audio); } free_audio_data(audio);音频数据准备好之后下一步就是把它装进JSON“信封”并通过网络发送出去。4. 构造HTTP请求并调用API这是整个流程的核心环节。我们需要用libcurl来发送一个HTTP POST请求请求体是我们构造的JSON字符串。4.1 构造JSON请求体根据常见的语音识别API格式我们需要创建一个类似下面的JSON{ audio: 这里是Base64编码后的音频字符串, format: wav, sample_rate: 16000 }我们用cJSON库来构建它这比手动拼接字符串要安全可靠得多。#include cJSON.h char* build_request_json(const char* base64_audio) { cJSON* root cJSON_CreateObject(); if (!root) return NULL; // 添加audio字段 cJSON_AddStringToObject(root, audio, base64_audio); // 添加format字段告诉服务器是wav格式 cJSON_AddStringToObject(root, format, wav); // 添加采样率字段根据你的音频文件实际情况填写 cJSON_AddNumberToObject(root, sample_rate, 16000); // 将cJSON对象转换成字符串 char* json_string cJSON_PrintUnformatted(root); // 释放cJSON对象 cJSON_Delete(root); return json_string; // 记得在使用后free }4.2 使用libcurl发送POST请求现在我们有了JSON字符串可以用libcurl把它发送到服务器了。libcurl的使用有一套固定的模式初始化、设置选项、执行、清理。#include curl/curl.h // 这个回调函数用于存储服务器返回的数据 size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) { size_t realsize size * nmemb; char** response_ptr (char**)userp; // 重新分配内存追加新数据 *response_ptr (char*)realloc(*response_ptr, strlen(*response_ptr) realsize 1); if (*response_ptr NULL) { printf(内存分配失败\n); return 0; } strcat(*response_ptr, (char*)contents); return realsize; } char* send_asr_request(const char* api_url, const char* json_data) { CURL* curl; CURLcode res; char* response_data (char*)malloc(1); // 初始化为空字符串 response_data[0] \0; curl_global_init(CURL_GLOBAL_DEFAULT); curl curl_easy_init(); if (curl) { struct curl_slist* headers NULL; // 设置HTTP头告诉服务器我们发送的是JSON headers curl_slist_append(headers, Content-Type: application/json); // 设置libcurl的各种选项 curl_easy_setopt(curl, CURLOPT_URL, api_url); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_data); // 执行请求 res curl_easy_perform(curl); // 检查执行结果 if (res ! CURLE_OK) { fprintf(stderr, curl_easy_perform() 失败: %s\n, curl_easy_strerror(res)); free(response_data); response_data NULL; } // 清理 curl_slist_free_all(headers); curl_easy_cleanup(curl); } curl_global_cleanup(); return response_data; // 返回服务器响应可能是JSON也可能是错误信息 }把前面几步组合起来一个完整的调用过程就清晰了// 1. 读取并编码音频 AudioData audio load_wav_file(test_audio.wav); char* base64_audio base64_encode(audio.data, audio.size); free_audio_data(audio); // 2. 构建JSON请求 char* json_request build_request_json(base64_audio); free(base64_audio); // 编码数据已存入JSON可以释放 // 3. 发送请求 const char* api_url http://your-server-ip:port/asr; // 替换为你的实际地址 char* api_response send_asr_request(api_url, json_request); free(json_request); // 请求数据已发送可以释放 // 4. 处理响应 (下一步讲解) if (api_response) { printf(服务器响应: %s\n, api_response); // 接下来需要解析这个响应 free(api_response); }请求成功发送后我们会收到服务器的回复。最后一步就是从回复中提取出我们想要的文字。5. 解析JSON响应与错误处理服务器处理成功后通常会返回一个JSON对象。一个典型的成功响应可能长这样{ code: 0, message: success, text: 今天天气怎么样 }如果出错code字段会是非零值message里会有错误描述。我们的任务就是解析这个JSON取出text字段。5.1 使用cJSON解析响应#include cJSON.h int parse_asr_response(const char* json_response, char** out_text) { cJSON* root cJSON_Parse(json_response); if (!root) { printf(解析JSON响应失败\n); return -1; } // 检查返回码 cJSON* code_item cJSON_GetObjectItem(root, code); if (code_item cJSON_IsNumber(code_item)) { int code code_item-valueint; if (code ! 0) { // 处理错误 cJSON* msg_item cJSON_GetObjectItem(root, message); printf(识别失败错误码: %d, 信息: %s\n, code, msg_item ? cJSON_GetStringValue(msg_item) : 未知错误); cJSON_Delete(root); return code; } } // 获取识别文本 cJSON* text_item cJSON_GetObjectItem(root, text); if (text_item cJSON_IsString(text_item)) { const char* text cJSON_GetStringValue(text_item); if (text) { // 分配内存并复制文本到输出参数 *out_text (char*)malloc(strlen(text) 1); if (*out_text) { strcpy(*out_text, text); } } } else { printf(响应中未找到有效的text字段\n); } cJSON_Delete(root); return 0; // 成功 }5.2 整合与完整的示例现在我们把所有模块组合成一个完整的、可以编译运行的程序。为了清晰我们把错误处理也加进去。// main.c #include stdio.h #include stdlib.h #include string.h #include curl/curl.h #include cJSON.h // ... 这里需要插入前面定义的所有函数 // load_wav_file, free_audio_data, base64_encode, // build_request_json, write_callback, send_asr_request, parse_asr_response int main() { const char* filename test_audio.wav; const char* api_url http://your-server-ip:port/asr; // 请务必替换 printf(1. 正在加载音频文件 %s...\n, filename); AudioData audio load_wav_file(filename); if (!audio.data || audio.size 0) { printf(加载音频文件失败\n); return 1; } printf(2. 正在进行Base64编码...\n); char* base64_audio base64_encode(audio.data, audio.size); free_audio_data(audio); // 原始音频数据不再需要 if (!base64_audio) { printf(Base64编码失败\n); return 1; } printf(3. 正在构建JSON请求...\n); char* json_request build_request_json(base64_audio); free(base64_audio); // 编码数据已存入JSON if (!json_request) { printf(构建JSON请求失败\n); return 1; } printf(4. 正在发送请求到 %s ...\n, api_url); char* api_response send_asr_request(api_url, json_request); free(json_request); if (!api_response) { printf(发送请求失败或未收到响应\n); return 1; } printf(5. 正在解析服务器响应...\n); char* recognized_text NULL; int parse_result parse_asr_response(api_response, recognized_text); free(api_response); if (parse_result 0 recognized_text) { printf(\n 识别结果 \n); printf(%s\n, recognized_text); printf(\n); free(recognized_text); } else { printf(解析响应或提取文本失败\n); } return 0; }编译这个程序假设cJSON的源文件也在当前目录gcc -o c_asr_demo main.c cJSON.c -lcurl -lm然后运行它./c_asr_demo如果一切顺利你就能在终端上看到“今天天气怎么样”这行识别结果了。6. 总结走完这一遍你会发现用C语言调用一个现代的REST API并没有想象中那么复杂。核心就是三个库的配合用libcurl处理网络通信用cJSON处理数据封装与解析再自己写点代码处理文件I/O和Base64编码。在实际项目中你可能还需要考虑更多比如处理实时的音频流而不是文件、管理连接超时、加入更完善的日志和重试机制。但本文提供的这个框架已经是一个能跑起来的、坚实的起点。你可以根据需求轻松地对每个模块进行扩展和优化。这种直接在C层进行集成的方式特别适合对执行效率和资源占用有严格要求的场景比如嵌入式设备或高性能服务器。希望这个具体的例子能帮你打通从想法到实现的那一步。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章