当前位置: 首页 > news >正文

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语言实现,我们需要做三件核心事:

  1. 读取WAV文件,并把它的数据部分进行Base64编码。
  2. 用libcurl构造一个携带了JSON数据的HTTP POST请求,并发送它。
  3. 收到服务器的回复后,用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.dataaudio.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星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

http://www.cnnetsun.cn/news/1816285.html

相关文章:

  • Qwen3.5-2B轻量化优势详解:相比Qwen3.5-8B显存降低62%,精度保留94%
  • 机械设计-哈工大课程学习-螺旋传动的摩擦类型与应用优化
  • 量化交易核心指标解析(四)——实战应用篇
  • Qwen3-ASR-0.6B在车载系统的语音交互实现
  • Phi-4-reasoning-vision-15B多场景实践:研发/测试/产品/运营人员协同使用
  • FreeSWITCH与ASR引擎对接:从语音流到文本解析的实战指南
  • PP-DocLayoutV3与STM32嵌入式系统集成:离线文档分析方案
  • 移动端Transformer新选择:拆解SwiftFormer的EAA注意力,看它如何做到又快又好
  • Comsol 拓扑优化实战:双目标函数与插值方法在热管理设计中的应用
  • 【实战指南】Jaspersoft Studio + JasperReports:从零构建企业级PDF报表
  • java项目-基于SpringBoot+Vue前后端分离的在线考试系统设计与实现(附资料)
  • 从底层逻辑到实战进阶,数据库设计全指南
  • Shell日志分析与故障排查实战
  • UML vs ADL:架构设计工具选型指南(含AADL在嵌入式系统的特殊优势)
  • 从零到一:基于Rook Operator的Ceph集群云原生部署与Kubernetes存储集成全攻略
  • FunASR语音识别WebUI功能全解析:文件上传、实时录音、结果导出
  • Canvas动画实战:用requestAnimationFrame打造会飘动的彩虹云朵
  • Ubuntu系统下TDengine Database的安装与性能测试实战
  • Wan2.1-UMT5提示词工程实战:写出高质量视频生成指令的秘诀
  • LightRAG知识图谱使用和工作流集成
  • MacOS通过Rclone与macFUSE实现FTP本地化挂载全攻略
  • 2026移动应用质量监控Bugly:跨平台崩溃性能统一实践
  • 邮件系统中的抗拒绝服务(DDoS)攻击防护
  • 如何解决B站缓存视频无法播放的困扰?m4s-converter让你真正拥有自己的收藏
  • 【网络实战】思科模拟器入门:手把手教你完成交换机VLAN基础配置
  • RexUniNLU开源可部署价值:规避数据隐私风险,满足金融/医疗合规要求
  • 永恒之蓝(MS17-010)漏洞复现+简单的后渗透操作(超详细)
  • 2026.4.10 11.14 发文测试
  • 逆向思维看安全:从SoftCnKiller的sign.txt机制,聊聊我们该如何手动构建自己的“流氓软件黑名单”
  • 震惊!Happy Horse 登顶全球AI视频榜单!国产又一王炸?