文章目录
前言
通过上一讲,我们实现了一个加载pulseaudio的module-loopback的功能来实现侦听,那么除了加载模块的方式,有没有其他方式来实现侦听功能呢?
答案自然是有的,今天我们就写一个实例来实现
上一讲原文链接:
https://blog.csdn.net/qq_43257914/article/details/139513675?spm=1001.2014.3001.5501
提示:以下是本篇文章正文内容,下面案例可供参考
一、主要API
要实现这个功能,我们主要需要pulseaudio提供的三个API接口,分别是:
pa_simple_new
pa_simple_new 是 PulseAudio 提供的一个用于简化音频流处理的函数,它允许开发者以较为简单的方式创建音频输入或输出流,而不需要直接与复杂的 PulseAudio 上下文和主循环机制打交道。
函数原型:
pa_simple *pa_simple_new(
pa_mainloop_api *m, //m: 主循环 API,通常传 NULL,表示使用默认的主循环。
const char *name, //name: 用于识别此流的应用程序名称。
pa_stream_direction_t direction,//direction: 流的方向,PA_STREAM_RECORD 表示录音,PA_STREAM_PLAYBACK 表示播放。
const char *server, //server: 要连接的 PulseAudio 服务器地址,通常传 NULL,表示使用默认服务器。
const char *description, //description: 流的描述,通常用于在 PulseAudio UI 中显示。
const pa_sample_spec *spec, //spec: 采样规格,包括采样格式、通道数和采样率。
const pa_channel_map *map, //map: 通道映射,通常传 NULL,表示使用默认映射。
const pa_buffer_attr *attr, //attr: 缓冲属性,用于控制缓冲行为,如最大长度、目标长度等。
int *error //error: 用于返回错误代码的指针,如果函数调用失败,可以检查这个变量以了解失败原因。
);
pa_simple_read
pa_simple_read 是用于从 PulseAudio 录音流中读取数据的函数。
函数原型:
ssize_t pa_simple_read(
pa_simple *s, //s: 由 pa_simple_new 创建的录音流句柄。
void *buffer, //buffer: 用于存储读取数据的缓冲区。
size_t length, //length: 指定要读取的数据量,单位为字节。
int *error //error: 用于返回错误代码的指针。
);
pa_simple_write
pa_simple_write 是用于向 PulseAudio 播放流中写入数据的函数。
函数原型:
ssize_t pa_simple_write(
pa_simple *s, //s: 由 pa_simple_new 创建的播放流句柄。
const void *data, //data: 包含要写入数据的缓冲区。
size_t length, //length: 指定要写入的数据量,单位为字节。
int *error //error: 用于返回错误代码的指针。
);
这三个函数共同构成了一个基本的音频流处理框架。
pa_simple_new 负责创建音频流,pa_simple_read 和 pa_simple_write 则分别用于处理音频流的数据读写。
在使用这些函数时,需要确保正确设置了采样规格和缓冲属性,以满足特定的音频处理需求。
二、C代码实现
注意:
1、pa_buffer_attr参数必须设置,这里遇到过一个bug;
之前将此参数为NULL时,会有以下情况发生,当我们代码中设置的音频输入输出设备与电脑本身默认设置的音频输入输出设备不相同时,运行我们的代码会出现录音播放延迟的情况,然而当代码设置设备与本机设备相同时,则没有这个情况
这个pa_buffer_attr参数主要设置了最大缓冲器长度与目标缓冲区长度,如果不设置这个参数就会出现延迟问题,这是因为每一帧的音频数据是不同的,那么就会出现有时录制播放一帧音频的时间长,慢慢累积下来,就会导致延迟问题,导致不能实时录音并播放;
至于代码设置设置为与本机默认设备相同时,则不会有延迟情况,这是因为pulseaudio初始化选择默认设备的时候已经设置好了,自然不会出现此问题
2、如何查看音频设备端口
查看输出设备:
pactl list sinks short
查看输入设备:
pactl list sources short
#include <stdio.h> // 包含标准输入输出头文件
#include <stdlib.h> // 包含标准库头文件,用于malloc, free等
#include <pulse/simple.h> // 包含PulseAudio简单API头文件
#include <pulse/error.h> // 包含PulseAudio错误处理头文件
#include <string.h> // 包含字符串处理头文件
#define BUFSIZE 1024 // 定义缓冲区大小为1024字节
// 设定要使用的输入和输出设备的名称
const char *source_name_a = "alsa_input.usb-HECATE_G2_GAMING_HEADSET_HECATE_G2_GAMING_HEADSET_20190403-00.mono-fallback";
const char *sink_name_a = "alsa_output.usb-HECATE_G2_GAMING_HEADSET_HECATE_G2_GAMING_HEADSET_20190403-00.analog-stereo";
int main(int argc, char *argv[]) {
// 定义变量
int ret;
pa_simple *record_handle = NULL; // 定义PulseAudio简单API句柄,分别用于录音和播放
pa_simple *playback_handle = NULL;
pa_sample_spec record_ss, playback_ss; // 定义采样规格结构体,分别用于录音和播放
char error_message[PA_ERR_MAX]; // 定义错误消息缓冲区
// 设置录音和播放的采样格式,包括采样类型、通道数和采样率
record_ss.format = PA_SAMPLE_S16LE; // 16位小端格式
record_ss.channels = 1; // 单声道
record_ss.rate = 8000; // 8000 Hz采样率
playback_ss.format = PA_SAMPLE_S16LE;
playback_ss.channels = 1;
playback_ss.rate = 8000;
// 定义缓冲区属性结构体,用于配置缓冲行为
pa_buffer_attr ba;
memset(&ba, 0, sizeof(ba)); // 初始化结构体成员为0
ba.maxlength = 2048; // 最大缓冲区长度
ba.tlength = 1024; // 目标缓冲区长度
ba.prebuf = 0; // 预缓冲量
ba.minreq = 0; // 最小请求长度
ba.fragsize = 0; // 分片大小
// 创建录制流
record_handle = pa_simple_new(NULL, "Record", PA_STREAM_RECORD, source_name_a, "record", &record_ss, NULL, &ba, &ret);
if (!record_handle) {
fprintf(stderr, "Error: Failed to create record stream - %s\n", pa_strerror(ret));
goto cleanup;
}
// 创建播放流
playback_handle = pa_simple_new(NULL, "Playback", PA_STREAM_PLAYBACK, sink_name_a,"playback", &playback_ss, NULL, &ba, &ret);
if (!playback_handle) {
fprintf(stderr,"Error: Failed to create playback stream - %s\n",pa_strerror(ret));
goto cleanup;
}
// 录制并播放声音
while(1) {
// 定义缓冲区,用于存储录制的声音数据
uint8_t buf[BUFSIZE];
// 录制声音数据
if (pa_simple_read(record_handle,buf,sizeof(buf),&ret) < 0) {
fprintf(stderr,"Error: Failed to read record stream - %s\n",pa_strerror(ret));
goto cleanup;
}
// 播放声音数据
if (pa_simple_write(playback_handle,buf,sizeof(buf),&ret) < 0) {
fprintf(stderr,"Error: Failed to write playback stream - %s\n",pa_strerror(ret));
goto cleanup;
}
}
cleanup:
// 清理资源,关闭录音和播放流
if (record_handle)
pa_simple_free(record_handle);
if (playback_handle)
pa_simple_free(playback_handle);
return ret;
}
三、注意事项
1、必须装有 libpulsedev 包
dpkg -l |grep libpulse-dev
2、编译方式
gcc -D_GNU_SOURCE pa_record_playback_org.c -lpulse -lpulse-simple
3、运行说明
运行此代码时,需要注意pulseaudio必须在运行,因为我们这个实例是基于pulseaudio实现的