Skip to content

Instantly share code, notes, and snippets.

@rlapz
Last active March 17, 2026 18:49
Show Gist options
  • Select an option

  • Save rlapz/72ccbb0297a998eebe11ccc17ddf3039 to your computer and use it in GitHub Desktop.

Select an option

Save rlapz/72ccbb0297a998eebe11ccc17ddf3039 to your computer and use it in GitHub Desktop.
WIP
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <portaudio.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libswresample/swresample.h>
#include <pthread.h>
#include "pa/pa_ringbuffer.h"
#define PLAYER_AUDIO_CHANNELS_COUNT (2)
#define PLAYER_AUDIO_SAMPLE_FORMAT paFloat32
#define PLAYER_AUDIO_SAMPLE_RATE (44100)
#define PLAYER_AUDIO_FRAME_BUFFER_SIZE (4096)
#define PLAYER_FILE_SAMPLE_RATE PLAYER_AUDIO_SAMPLE_RATE
#define PLAYER_FILE_SAMPLE_FORMAT AV_SAMPLE_FMT_FLT
#define PLAYER_SWR_BUFFER_SIZE (1024 * 1024)
#define PLAYER_RING_BUFFER_SIZE (4096 * 8)
#define PLAYER_RING_BUFFER_ELEM_SIZE (PLAYER_AUDIO_CHANNELS_COUNT * sizeof(float))
typedef struct {
volatile int is_alive;
AVPacket *pkt;
AVFrame *frame;
uint8_t *swr_buffer;
pthread_t thrd;
} PlayerState;
static int player_state_init(PlayerState *s);
static void player_state_deinit(PlayerState *s);
typedef struct {
volatile int is_alive;
AVFormatContext *format;
AVCodecContext *codec;
int index;
SwrContext *swr;
PaStream *stream;
PlayerState state;
PaUtilRingBuffer buffer;
} PlayerContext;
static int player_context_init(PlayerContext *c);
static void player_context_deinit(PlayerContext *c);
static int player_context_open_device(PlayerContext *c);
static void player_context_close_device(PlayerContext *c);
static int player_context_open_file(PlayerContext *c, const char file[]);
static void player_context_close_file(PlayerContext *c);
static int player_context_cb(const void *input, void *output, unsigned long count,
const PaStreamCallbackTimeInfo *time_info,
PaStreamCallbackFlags flags, void *udata);
static void *player_context_thrd(void *udata);
static int player_context_write(PlayerContext *c);
/*
* PlayerState
*/
static int
player_state_init(PlayerState *s)
{
AVPacket *pkt = av_packet_alloc();
if (pkt == NULL)
return -1;
AVFrame *frame = av_frame_alloc();
if (frame == NULL)
goto err0;
uint8_t *const swr_buffer = malloc(PLAYER_SWR_BUFFER_SIZE);
if (swr_buffer == NULL)
goto err1;
s->pkt = pkt;
s->frame = frame;
s->swr_buffer = swr_buffer;
s->is_alive = 0;
return 0;
err1:
av_frame_free(&frame);
err0:
av_packet_free(&pkt);
return -1;
}
static void
player_state_deinit(PlayerState *s)
{
av_packet_free(&s->pkt);
av_frame_free(&s->frame);
free(s->swr_buffer);
}
/*
* PlayerContext
*/
static int
player_context_init(PlayerContext *c)
{
memset(c, 0, sizeof(*c));
uint8_t *const buffer = malloc(PLAYER_RING_BUFFER_ELEM_SIZE * PLAYER_RING_BUFFER_SIZE);
if (buffer == NULL)
return -1; // TODO
ring_buffer_size_t ret = PaUtil_InitializeRingBuffer(&c->buffer, PLAYER_RING_BUFFER_ELEM_SIZE,
PLAYER_RING_BUFFER_SIZE, buffer);
if (ret < 0)
goto err0; // TODO
ret = player_state_init(&c->state);
if (ret < 0)
goto err0; // TODO
c->is_alive = 1;
ret = player_context_open_device(c);
if (ret < 0)
goto err1;
return 0;
err1:
player_state_deinit(&c->state);
err0:
free(buffer);
return -1;
}
static void
player_context_deinit(PlayerContext *c)
{
c->is_alive = 0;
Pa_StopStream(c->stream);
Pa_CloseStream(c->stream);
player_context_close_device(c);
player_state_deinit(&c->state);
free(c->buffer.buffer);
}
static int
player_context_open_device(PlayerContext *c)
{
PaError pe = Pa_Initialize();
if (pe != paNoError)
return -1; // TODO
const int host_api = Pa_GetDefaultHostApi();
if (host_api < 0)
goto err0; // TODO
const PaHostApiInfo *const host_api_info = Pa_GetHostApiInfo(host_api);
if (host_api_info == NULL)
goto err0; // TODO
const int device = host_api_info->defaultOutputDevice;
const PaDeviceInfo *const device_info = Pa_GetDeviceInfo(device);
if (device_info == NULL)
goto err0;
const PaStreamParameters param = {
.device = device,
.channelCount = PLAYER_AUDIO_CHANNELS_COUNT,
.sampleFormat = PLAYER_AUDIO_SAMPLE_FORMAT,
.suggestedLatency = device_info->defaultLowOutputLatency,
};
pe = Pa_OpenStream(&c->stream, NULL, &param, PLAYER_AUDIO_SAMPLE_RATE,
PLAYER_AUDIO_FRAME_BUFFER_SIZE, paClipOff | paDitherOff,
player_context_cb, c);
if (pe != paNoError)
goto err0; // TODO
pe = Pa_StartStream(c->stream);
if (pe != paNoError)
goto err1;
return 0;
err1:
Pa_CloseStream(c->stream);
err0:
Pa_Terminate();
return -1;
}
static void
player_context_close_device(PlayerContext *c)
{
Pa_Terminate();
(void)c;
}
static int
player_context_open_file(PlayerContext *c, const char file[])
{
int ret = avformat_open_input(&c->format, file, NULL, NULL);
if (ret < 0)
return -1; // TODO
ret = avformat_find_stream_info(c->format, NULL);
if (ret < 0)
goto err0; // TODO
c->index = -1;
for (int i = 0; i < c->format->nb_streams; i++) {
if (c->format->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
continue;
c->index = i;
break;
}
if (c->index < 0)
goto err0; // TODO
const AVCodecParameters *const cpar = c->format->streams[c->index]->codecpar;
const AVCodec *const codec = avcodec_find_decoder(cpar->codec_id);
c->codec = avcodec_alloc_context3(codec);
if (c->codec == NULL)
goto err0; // TODO
ret = avcodec_parameters_to_context(c->codec, cpar);
if (ret < 0)
goto err1; // TODO
ret = avcodec_open2(c->codec, codec, NULL);
if (ret < 0)
goto err1; // TODO
c->swr = swr_alloc();
if (c->swr == NULL)
goto err2; // TODO
AVChannelLayout chan;
av_channel_layout_default(&chan, PLAYER_AUDIO_CHANNELS_COUNT);
av_opt_set_chlayout(c->swr, "out_chlayout", &chan, 0);
av_opt_set_int(c->swr, "out_sample_fmt", PLAYER_FILE_SAMPLE_FORMAT, 0);
av_opt_set_int(c->swr, "out_sample_rate", PLAYER_FILE_SAMPLE_RATE, 0);
av_opt_set_chlayout(c->swr, "in_chlayout", &c->codec->ch_layout, 0);
av_opt_set_int(c->swr, "in_sample_fmt", c->codec->sample_fmt, 0);
av_opt_set_int(c->swr, "in_sample_rate", c->codec->sample_rate, 0);
ret = swr_init(c->swr);
if (ret < 0)
goto err3; // TODO
ret = pthread_create(&c->state.thrd, NULL, player_context_thrd, c);
if (ret != 0)
goto err4;
return 0;
err4:
swr_close(c->swr);
err3:
swr_free(&c->swr);
err2:
//avcodec_close(c->codec);
err1:
avcodec_free_context(&c->codec);
err0:
avformat_close_input(&c->format);
return -1;
}
static void
player_context_close_file(PlayerContext *c)
{
c->state.is_alive = 0;
pthread_join(c->state.thrd, NULL);
swr_close(c->swr);
swr_free(&c->swr);
avcodec_close(c->codec);
avcodec_free_context(&c->codec);
avformat_close_input(&c->format);
}
static int
player_context_cb(const void *input, void *output, unsigned long count,
const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags flags,
void *udata)
{
PlayerContext *const c = (PlayerContext *)udata;
const ring_buffer_size_t rd = PaUtil_ReadRingBuffer(&c->buffer, output, count);
if (rd < count) {
char *const ptr = (char *)output;
const size_t sz = (rd * PLAYER_RING_BUFFER_ELEM_SIZE);
memset(ptr + sz, 0, count - sz);
}
if (c->is_alive == 0 && PaUtil_GetRingBufferReadAvailable(&c->buffer) == 0)
return paComplete;
return paContinue;
}
static void *
player_context_thrd(void *udata)
{
PlayerContext *const c = (PlayerContext *)udata;
AVFormatContext *const ctx = c->format;
AVCodecContext *const codec = c->codec;
AVPacket *const pkt = c->state.pkt;
volatile int *const is_alive = &c->state.is_alive;
*is_alive = 1;
while (*is_alive) {
int ret = av_read_frame(ctx, pkt);
if (ret < 0)
break; // TODO
if (pkt->stream_index != c->index) {
av_packet_unref(pkt);
continue;
}
ret = avcodec_send_packet(codec, pkt);
if (ret != 0)
break; // TODO
av_packet_unref(pkt);
ret = player_context_write(c);
if (ret <= 0)
break;
}
puts("thrd: exiting...");
*is_alive = 0;
// TODO
c->is_alive = 0;
return NULL;
}
static int
player_context_write(PlayerContext *c)
{
AVCodecContext *const codec = c->codec;
AVFrame *const frm = c->state.frame;
SwrContext *const swr = c->swr;
uint8_t *const buffer = c->state.swr_buffer;
uint8_t *swr_buffer = buffer;
volatile int *const is_alive = &c->state.is_alive;
while (*is_alive) {
int ret = avcodec_receive_frame(codec, frm);
if (ret < 0)
break; // TODO
ret = swr_convert(swr, &swr_buffer, PLAYER_SWR_BUFFER_SIZE,
(const uint8_t **)frm->data, frm->nb_samples);
while (ret > 0) {
if (PaUtil_GetRingBufferWriteAvailable(&c->buffer) < ret) {
if (*is_alive == 0)
return 0;
// maybe this is not a good idea...
Pa_Sleep(100);
continue;
}
PaUtil_WriteRingBuffer(&c->buffer, buffer, ret);
// flushing...
ret = swr_convert(swr, &swr_buffer, PLAYER_SWR_BUFFER_SIZE, NULL, 0);
if (ret <= 0)
break;
puts("flushing...");
}
}
// continue
return 1;
}
int
main(int argc, char *argv[])
{
if (argc != 2)
return 1;
PlayerContext ctx;
int ret = player_context_init(&ctx);
if (ret < 0)
return 1;
ret = player_context_open_file(&ctx, argv[1]);
if (ret < 0) {
puts("error open file");
goto out0;
}
while (Pa_IsStreamActive(ctx.stream))
Pa_Sleep(1000);
player_context_close_file(&ctx);
out0:
player_context_deinit(&ctx);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment