Skip to content

Instantly share code, notes, and snippets.

@rlapz
Last active March 30, 2025 16:09
Show Gist options
  • Save rlapz/7f9195865816056a2c34bb059c3c8701 to your computer and use it in GitHub Desktop.
Save rlapz/7f9195865816056a2c34bb059c3c8701 to your computer and use it in GitHub Desktop.
A simple audio player (pipewire & ffmpeg) by DeepSeek.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pipewire/pipewire.h>
#include <spa/utils/defs.h>
#include <spa/utils/string.h>
#include <spa/utils/result.h>
#include <spa/param/audio/format-utils.h>
#include <spa/pod/builder.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
struct player_data {
struct pw_stream *stream;
struct pw_main_loop *loop;
AVFormatContext *format_ctx;
AVCodecContext *codec_ctx;
SwrContext *swr_ctx;
int audio_stream_idx;
uint8_t **convert_data;
int convert_linesize;
int64_t pts_offset;
};
static void on_process(void *userdata) {
struct player_data *data = userdata;
struct pw_buffer *b;
float *dst;
int ret;
AVPacket *pkt = av_packet_alloc();
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
pw_log_warn("out of buffers: %m");
av_packet_free(&pkt);
return;
}
dst = (float *)b->buffer->datas[0].data;
AVFrame *frame = av_frame_alloc();
if (!frame) {
pw_log_error("Failed to allocate frame");
av_packet_free(&pkt);
return;
}
while (1) {
ret = av_read_frame(data->format_ctx, pkt);
if (ret < 0) {
if (ret == AVERROR_EOF) {
pw_log_info("end of file");
} else {
pw_log_error("read frame error: %s", av_err2str(ret));
}
pw_main_loop_quit(data->loop);
av_frame_free(&frame);
av_packet_free(&pkt);
pw_stream_queue_buffer(data->stream, b);
return;
}
if (pkt->stream_index != data->audio_stream_idx) {
av_packet_unref(pkt);
continue;
}
ret = avcodec_send_packet(data->codec_ctx, pkt);
if (ret < 0) {
pw_log_error("error sending packet: %s", av_err2str(ret));
av_packet_unref(pkt);
continue;
}
ret = avcodec_receive_frame(data->codec_ctx, frame);
if (ret == AVERROR(EAGAIN)) {
av_packet_unref(pkt);
continue;
} else if (ret < 0) {
pw_log_error("error receiving frame: %s", av_err2str(ret));
av_packet_unref(pkt);
break;
}
// Convert to float planar
swr_convert(data->swr_ctx, (uint8_t **)&dst, frame->nb_samples,
(const uint8_t **)frame->extended_data, frame->nb_samples);
b->buffer->datas[0].chunk->offset = 0;
b->buffer->datas[0].chunk->stride = sizeof(float) * data->codec_ctx->ch_layout.nb_channels;
b->buffer->datas[0].chunk->size = frame->nb_samples * b->buffer->datas[0].chunk->stride;
av_frame_unref(frame);
av_packet_unref(pkt);
break;
}
av_frame_free(&frame);
av_packet_free(&pkt);
pw_stream_queue_buffer(data->stream, b);
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.process = on_process,
};
void cleanup(struct player_data *data) {
if (data->stream) {
pw_stream_destroy(data->stream);
}
if (data->loop) {
pw_main_loop_destroy(data->loop);
}
if (data->swr_ctx) {
swr_free(&data->swr_ctx);
}
if (data->codec_ctx) {
avcodec_free_context(&data->codec_ctx);
}
if (data->format_ctx) {
avformat_close_input(&data->format_ctx);
}
avformat_network_deinit();
}
int main(int argc, char *argv[]) {
struct player_data data = {0};
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder builder;
const struct spa_pod *param;
int ret;
if (argc < 2) {
fprintf(stderr, "Usage: %s <audio-file>\n", argv[0]);
return 1;
}
// Initialize FFmpeg
avformat_network_init();
// Open input file
if ((ret = avformat_open_input(&data.format_ctx, argv[1], NULL, NULL)) < 0) {
fprintf(stderr, "Could not open file '%s': %s\n", argv[1], av_err2str(ret));
return 1;
}
if ((ret = avformat_find_stream_info(data.format_ctx, NULL)) < 0) {
fprintf(stderr, "Could not find stream information: %s\n", av_err2str(ret));
cleanup(&data);
return 1;
}
// Find audio stream
data.audio_stream_idx = av_find_best_stream(data.format_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (data.audio_stream_idx < 0) {
fprintf(stderr, "Could not find audio stream in file\n");
cleanup(&data);
return 1;
}
AVStream *stream = data.format_ctx->streams[data.audio_stream_idx];
const AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
if (!codec) {
fprintf(stderr, "Unsupported codec\n");
cleanup(&data);
return 1;
}
data.codec_ctx = avcodec_alloc_context3(codec);
if (!data.codec_ctx) {
fprintf(stderr, "Could not allocate codec context\n");
cleanup(&data);
return 1;
}
if ((ret = avcodec_parameters_to_context(data.codec_ctx, stream->codecpar)) < 0) {
fprintf(stderr, "Could not copy codec parameters: %s\n", av_err2str(ret));
cleanup(&data);
return 1;
}
if ((ret = avcodec_open2(data.codec_ctx, codec, NULL)) < 0) {
fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
cleanup(&data);
return 1;
}
// Initialize PipeWire
pw_init(&argc, &argv);
data.loop = pw_main_loop_new(NULL);
if (!data.loop) {
fprintf(stderr, "Failed to create PipeWire main loop\n");
cleanup(&data);
return 1;
}
// Create audio stream
data.stream = pw_stream_new_simple(
pw_main_loop_get_loop(data.loop),
"audio-player",
pw_properties_new(
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Playback",
PW_KEY_MEDIA_ROLE, "Music",
NULL),
&stream_events,
&data);
if (!data.stream) {
fprintf(stderr, "Failed to create PipeWire stream\n");
cleanup(&data);
return 1;
}
// Configure audio format
enum spa_audio_format spa_format = SPA_AUDIO_FORMAT_F32;
uint32_t rate = data.codec_ctx->sample_rate;
uint32_t channels = data.codec_ctx->ch_layout.nb_channels;
// Set up resampler
data.swr_ctx = swr_alloc();
if (!data.swr_ctx) {
fprintf(stderr, "Failed to allocate resampler\n");
cleanup(&data);
return 1;
}
av_opt_set_int(data.swr_ctx, "in_sample_rate", data.codec_ctx->sample_rate, 0);
av_opt_set_int(data.swr_ctx, "out_sample_rate", rate, 0);
av_opt_set_sample_fmt(data.swr_ctx, "in_sample_fmt", data.codec_ctx->sample_fmt, 0);
av_opt_set_sample_fmt(data.swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
av_opt_set_chlayout(data.swr_ctx, "in_chlayout", &data.codec_ctx->ch_layout, 0);
av_opt_set_chlayout(data.swr_ctx, "out_chlayout", &data.codec_ctx->ch_layout, 0);
if ((ret = swr_init(data.swr_ctx)) < 0) {
fprintf(stderr, "Failed to initialize resampler: %s\n", av_err2str(ret));
cleanup(&data);
return 1;
}
// Initialize pod builder
spa_pod_builder_init(&builder, buffer, sizeof(buffer));
// Prepare format
param = spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat,
&SPA_AUDIO_INFO_RAW_INIT(
.format = spa_format,
.rate = rate,
.channels = channels));
if (!param) {
fprintf(stderr, "Failed to build audio format\n");
cleanup(&data);
return 1;
}
params[0] = param;
if ((ret = pw_stream_connect(data.stream,
PW_DIRECTION_OUTPUT,
PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
params, 1)) < 0) {
fprintf(stderr, "Failed to connect stream: %s\n", spa_strerror(ret));
cleanup(&data);
return 1;
}
// Start playback
printf("Playing %s...\n", argv[1]);
pw_main_loop_run(data.loop);
// Cleanup
cleanup(&data);
return 0;
}
#!/bin/sh
gcc -o audio_player_deepseek audio_player_deepseek.c $(pkg-config --cflags --libs libpipewire-0.3 libavformat libavcodec libswresample libavutil) -lm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment