Skip to content

Instantly share code, notes, and snippets.

@hubenchang0515
Last active March 16, 2021 01:43
Show Gist options
  • Save hubenchang0515/278c309332cb6b29d10cbaee776458da to your computer and use it in GitHub Desktop.
Save hubenchang0515/278c309332cb6b29d10cbaee776458da to your computer and use it in GitHub Desktop.
Play and record sound by PulseAudio - 通过PulseAudio播放和录制声音
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <pulse/pulseaudio.h>
#ifdef DEBUG
#define LOG(...) printf(__VA_ARGS__)
#else
#define LOG(...)
#endif
typedef enum Method
{
METHOD_PLAY,
METHOD_RECORD
}Method;
typedef struct UserData
{
pa_context* context;
pa_stream* stream;
pa_mainloop_api* api;
FILE* fp;
Method method;
}UserData;
static void mainloop_quit(UserData* userdata, int ret);
static void context_state_callback(pa_context* context, void* userdata) ;
static void stream_write_callback(pa_stream* stream, size_t length, void* userdata);
static void stream_read_callback(pa_stream* stream, size_t length, void* userdata);
static void stream_drain_complete(pa_stream* stream, int success, void* userdata);
static void context_drain_complete(pa_context* c, void* userdata);
int main(int argc, char* argv[])
{
// 参数检查
if(argc != 3)
{
printf("pademo [play|record] <sound-file>\n");
return EXIT_FAILURE;
}
// 创建一个线程,并在该线程中创建 mainloop
pa_mainloop* mainloop = pa_mainloop_new();
if(mainloop == NULL)
{
fprintf(stderr, "pa_threaded_mainloop_new failed.\n");
return EXIT_FAILURE;
}
// 获取API
pa_mainloop_api* api = pa_mainloop_get_api(mainloop);
if(api == NULL)
{
pa_mainloop_free(mainloop);
fprintf(stderr, "pa_threaded_mainloop_get_api failed.\n");
return EXIT_FAILURE;
}
// 创建上下文
pa_context* context = pa_context_new(api, "demo");
if(context == NULL)
{
pa_mainloop_free(mainloop);
fprintf(stderr, "pa_context_new failed.\n");
return EXIT_FAILURE;
}
UserData data;
if(strcmp(argv[1],"play") == 0)
{
data.fp = fopen(argv[2], "rb");
data.method = METHOD_PLAY;
}
else if(strcmp(argv[1], "record") == 0)
{
data.fp = fopen(argv[2], "wb");
data.method = METHOD_RECORD;
}
else
{
fprintf(stderr, "Unknown method %s\n", argv[1]);
// 释放
pa_context_unref(context);
pa_mainloop_free(mainloop);
return EXIT_FAILURE;
}
// 设置状态变化的回调函数,这是主入口
data.context = context;
data.api = api;
pa_context_set_state_callback(context, context_state_callback, (void*)(&data));
// 开始建立连接
if(pa_context_connect(context, NULL, PA_CONTEXT_NOFAIL, NULL) < 0)
{
pa_context_unref(context);
pa_mainloop_free(mainloop);
fprintf(stderr, "pa_context_connect failed.\n");
return EXIT_FAILURE;
}
// 运行mainloop
int ret = pa_mainloop_run(mainloop, NULL);
// 退出
pa_context_unref(context);
pa_mainloop_free(mainloop);
return ret;
}
// 退出主循环
static void mainloop_quit(UserData* userdata, int ret)
{
LOG("mainloop_quit\n");
userdata->api->quit(userdata->api, ret);
}
// 状态变化的回调函数
static void context_state_callback(pa_context* context, void* userdata)
{
UserData* data = (UserData*)(userdata);
pa_context_state_t state = pa_context_get_state(context);
switch (state)
{
case PA_CONTEXT_READY: // 上下文就绪
{
LOG("PA_CONTEXT_READY\n");
// 创建spec
pa_sample_spec sampleSpec;
sampleSpec.rate = 44100;
sampleSpec.format = PA_SAMPLE_S16LE;
sampleSpec.channels = 2;
// 创建channel map
pa_channel_map channelMap;
pa_channel_map_init_stereo(&channelMap);
// 创建stream
pa_stream* stream = pa_stream_new(context, "demo-stream", &sampleSpec, &channelMap);
data->stream = stream;
if(data->method == METHOD_PLAY) // 播放
{
pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
pa_stream_set_write_callback(stream, stream_write_callback, userdata);
}
else if(data->method == METHOD_RECORD) // 录音
{
pa_stream_connect_record(stream, NULL, NULL, PA_STREAM_NOFLAGS);
pa_stream_set_read_callback(stream, stream_read_callback, userdata);
}
break;
}
case PA_CONTEXT_TERMINATED: // 结束
LOG("PA_CONTEXT_TERMINATED\n");
mainloop_quit(data, EXIT_SUCCESS);
break;
default:
LOG("context state %d\n", state);
}
}
// 播放的回调
static void stream_write_callback(pa_stream* stream, size_t length, void* userdata)
{
UserData* data = (UserData*)(userdata);
void* buffer;
while(true)
{
// 给buffer分配空间,不需要手动释放
pa_stream_begin_write(stream, &buffer, &length);
// 读取文件,写入stream
length = fread(buffer, 1, length, data->fp);
pa_stream_write(stream, buffer, length, NULL, 0, PA_SEEK_RELATIVE); // 会自动释放buffer
LOG("play %zu bytes\n", length);
// 文件读取完毕
if(feof(data->fp))
{
pa_stream_cancel_write(stream);
pa_stream_set_write_callback(stream, NULL, NULL); //清除回调
pa_operation* o = pa_stream_drain(stream, stream_drain_complete, data); // 设置播放完毕时的回调
if(o == NULL)
{
mainloop_quit(data, EXIT_FAILURE);
}
pa_operation_unref(o);
break;
}
}
}
// 录音的回调
static void stream_read_callback(pa_stream* stream, size_t length, void* userdata)
{
UserData* data = (UserData*)(userdata);
const void *buffer;
while(pa_stream_readable_size(stream) > 0)
{
pa_stream_peek(stream, &buffer, &length);
if(buffer == NULL || length <= 0)
{
continue;
}
fwrite(buffer, length, 1, data->fp);
fflush(data->fp);
LOG("record %zu bytes\n", length);
pa_stream_drop(stream);
}
}
// 播放完毕的回调
static void stream_drain_complete(pa_stream* stream, int success, void* userdata)
{
(void)(success);
LOG("stream_drain_complete\n");
UserData* data = (UserData*)(userdata);
// 释放stream
pa_stream_disconnect(stream);
pa_stream_unref(stream);
data->stream = NULL;
// 设置上下文结束的回调
pa_operation* o = pa_context_drain(data->context, context_drain_complete, NULL);
if (o == NULL)
{
pa_context_disconnect(data->context);
}
else
{
pa_operation_unref(o);
}
}
// 上下文结束的回调
static void context_drain_complete(pa_context* context, void* userdata)
{
(void)(userdata);
LOG("context_drain_complete\n");
pa_context_disconnect(context);
}
LIBS := -lpulse
OBJS := main.o
CFLAGS := $(SYSFLAGS) -W -Wall -Wextra
.PHONY: all debug clean
all: pademo
debug:
$(MAKE) all SYSFLAGS="-DDEBUG"
main.o:
pademo: $(OBJS)
$(CC) -o pademo $(OBJS) $(LIBS)
clean:
rm -f $(OBJS)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment