Last active
March 30, 2025 16:09
-
-
Save rlapz/7f9195865816056a2c34bb059c3c8701 to your computer and use it in GitHub Desktop.
A simple audio player (pipewire & ffmpeg) by DeepSeek.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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