pulseaudio-utils 的简单使用

2021年9月30日 · 1048 字 · 3 分钟

使用 Pulse Audio:

  • 查看 sinks, sources
  • 播放 wav 文件,
  • 录制系统输出的声音
  • 录制指定软件输出的声音
  • 通过代码(c),录制和播放 pcm 数据
## 安装
sudo apt install pulseaudio-utils

命令行工具的使用

基本使用

pactl list [TAB 联想]
#------------------------------------------------------
cards           --  list available cards
clients         --  list connected clients
modules         --  list loaded modules
samples         --  list samples
sink-inputs     --  list connected sink inputs
sinks           --  list available sinks
source-outputs  --  list connected source outputs
sources         --  list available sources
#------------------------------------------------------
## sinks
pactl list short sinks
#------------------------------------------------------
0	alsa_output.pci-0000_00_1f.3.analog-stereo	module-alsa-card.c	s16le 2ch 44100Hz	RUNNING
1	recording	module-null-sink.c	s16le 2ch 44100Hz	SUSPENDED
#------------------------------------------------------
## sources
pactl list short sources
#------------------------------------------------------
0	alsa_input.usb-046d_HD_Pro_Webcam_C920_D4E907BF-02.analog-stereo	module-alsa-card.c	s16le 2ch 32000Hz	SUSPENDED
1	alsa_output.pci-0000_00_1f.3.analog-stereo.monitor	module-alsa-card.c	s16le 2ch 44100Hz	RUNNING
2	alsa_input.pci-0000_00_1f.3.analog-stereo	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED
3	recording.monitor	module-null-sink.c	s16le 2ch 44100Hz	SUSPENDED
#------------------------------------------------------
## parec & pacat
## 录制
parec --channels=2 -d alsa_output.pci-0000_00_1f.3.analog-stereo.monitor out.wav

## 播放
pacat out.wav
## parecord & paplay
## 录制
parecord --channels=2 -d alsa_output.pci-0000_00_1f.3.analog-stereo.monitor out.wav

## 播放
paplay out.wav

????

> ll /usr/bin | grep "parec\|pacat\|paplay\|parecord"
#------------------------------------------------------
-rwxr-xr-x 1 root root        43K Sep 22  2020 pacat
lrwxrwxrwx 1 root root          5 Sep 22  2020 pamon -> pacat
lrwxrwxrwx 1 root root          5 Sep 22  2020 paplay -> pacat
lrwxrwxrwx 1 root root          5 Sep 22  2020 parec -> pacat
lrwxrwxrwx 1 root root          5 Sep 22  2020 parecord -> pacat
#------------------------------------------------------

原来 paplay,parec,parecord 都是 pacat 程序,但是,但是,但是,为啥我使用 parec 录制的 out.wav 文件,paplay 无法播放, pacat 可以播放;反之 parecord 录制的 out.wav 文件,paplaypacat 都可以播放 ??????

录制指定软件输出的声音

## 增加一个 sink,用于录制制定的 application
> pacmd load-module module-null-sink sink_name=recording sink_properties=device.description=recording

## 【可选】 将前面创建的 sink,再输出到默认输出设备,用于用户听取
> pacmd load-module module-combine-sink sink_name=combined sink_properties=device.description=combined \
  slaves=recording,alsa_output.pci-0000_00_1f.3.analog-stereo  

假如我们这里要录制 Chrome 浏览器播放的声音

## 寻找软件对应的 sink-input
> pactl list sink-inputs
#-output-----------------------------------------------
Sink Input #2
	Driver: protocol-native.c
	Owner Module: 13
	Client: 12
	Sink: 0
	Sample Specification: float32le 2ch 44100Hz
	Channel Map: front-left,front-right
	Format: pcm, format.sample_format = "\"float32le\""  format.rate = "44100"  format.channels = "2"  format.channel_map = "\"front-left,front-right\""
	Corked: no
	Mute: no
	Volume: front-left: 65536 / 100% / 0.00 dB,   front-right: 65536 / 100% / 0.00 dB
	        balance 0.00
	Buffer Latency: 44625 usec
	Sink Latency: 23599 usec
	Resample method: copy
	Properties:
		application.icon_name = "google-chrome"
		media.name = "Playback"
		application.name = "Google Chrome"
		native-protocol.peer = "UNIX socket client"
		native-protocol.version = "32"
		application.process.id = "5881"
		application.process.user = "ban"
		application.process.host = "ban-pc"
		application.process.binary = "chrome"
		application.language = "en_US.UTF-8"
		window.x11.display = ":0"
		application.process.machine_id = "7862687160be40c5925f67f9055ef4ea"
		application.process.session_id = "2"
		module-stream-restore.id = "sink-input-by-application-name:Google Chrome"
#------------------------------------------------------
## 寻找我们前面创建的 record sink
> pactl list short sinks
#-output-----------------------------------------------
0	alsa_output.pci-0000_00_1f.3.analog-stereo	module-alsa-card.c	s16le 2ch 44100Hz	RUNNING
1	recording	module-null-sink.c	s16le 2ch 44100Hz	SUSPENDED
#------------------------------------------------------
## 将 chrome 的 stream 桥接到 record sink 上
> pactl move-sink-input 2 1
## 其中 2 是 chrome stream 的 id
## 其中 1 是 record sind 的 id
## 录制
parec --channels=2 -d recording.monitor test.wav

## 播放
pacat test.wav

pulse-simple 依赖库的使用

main.c

#include <stdio.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include <string.h>
#include <errno.h>

static const int BUF_SIZE = 1024;

/* The Sample format to use */
static const pa_sample_spec ss = {
        .format = PA_SAMPLE_S16LE,
        .rate = 44100,
        .channels = 2
};

int pa_play_pcm(char *path) {
    pa_simple *s = NULL;
    int ret = 1, error;
    FILE *file = fopen(path, "rb");
    if (file == NULL) goto finish;

    /* Create a new playback stream */
    if (!(s = pa_simple_new(NULL, "pa.play", PA_STREAM_PLAYBACK,
                            NULL, "playback", &ss, NULL, NULL, &error))) {
        fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
        goto finish;
    }

    for (int i = 0;; ++i) {
        if (i % 50 == 0) fprintf(stderr, "read loop %d/2000\n", i);
        uint8_t buf[BUF_SIZE];
        size_t r;
        if ((r = fread(buf, sizeof(uint8_t), sizeof(buf), file)) <= 0) {
            if (r == 0) break;
            fprintf(stderr, __FILE__": read() failed or over : %s\n", strerror(errno));
            goto finish;
        }
        if (pa_simple_write(s, buf, (size_t) r, &error) < 0) {
            fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error));
            goto finish;
        }
    }

    /* Make sure that every single sample was played */
    if (pa_simple_drain(s, &error) < 0) {
        fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error));
        goto finish;
    }
    ret = 0;
    finish:
    if (s) pa_simple_free(s);
    if (file) fclose(file);
    return ret;
}

int pa_record_pcm(char *path) {
    pa_simple *s = NULL;
    int ret = 1, error = 0;
    FILE *file = fopen(path, "wb");
    if (file == NULL) goto finish;

    /* Create a new playback stream */
    if (!(s = pa_simple_new(NULL, "pa.record", PA_STREAM_RECORD,
                            "alsa_output.pci-0000_00_1f.3.analog-stereo.monitor",
                            "playback", &ss, NULL, NULL, &error))) {
        fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
        goto finish;
    }

    for (int i = 0; i < 2000; ++i) {
        if (i % 50 == 0) fprintf(stderr, "write loop %d/2000\n", i);
        uint8_t buf[BUF_SIZE];
        if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
            fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
            goto finish;
        }
        /* And write it to STDOUT */
        if (fwrite(buf, sizeof(uint8_t), sizeof(buf), file) != sizeof(buf)) {
            fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
            goto finish;
        }
    }
    ret = 0;
    finish:
    if (s) pa_simple_free(s);
    if (file) fclose(file);
    return ret;
}

int main() {

    char *file = (char *) "temp.pcm";
    pa_record_pcm(file);

    // start record
    pa_play_pcm(file);

    remove(file);
    return 0;
}
## 编译
> gcc main.c -o main.app -lpulse -lpulse-simple
## 运行
> ./main.app

REF

1. Linux audio recording guide
2. Record a program’s output with PulseAudio
3. PulseAudio cannot change the sink for one specific program
4. pacat-simple.c
5. parec-simple.c