add gapless

This commit is contained in:
William Bell
2025-12-27 22:56:19 +00:00
parent 74b8054ea2
commit 3250a97cca
3 changed files with 146 additions and 147 deletions

View File

@@ -1,2 +0,0 @@
export LIBGL_ALWAYS_SOFTWARE=1
python3 app.py

3
run.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
exec bin/player music/pink\ floyd/dark\ side\ of\ the\ moon/01\ Speak\ to\ Me.flac music/pink\ floyd/dark\ side\ of\ the\ moon/02\ Breathe\ \(In\ the\ Air\).flac

View File

@@ -1,5 +1,4 @@
// player.c #include <stdint.h>
#include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -14,166 +13,165 @@
#define OUT_RATE 48000 #define OUT_RATE 48000
#define OUT_CHANNELS 2 #define OUT_CHANNELS 2
#define OUT_FORMAT AV_SAMPLE_FMT_S16 #define OUT_FORMAT AV_SAMPLE_FMT_S16
#define OUT_LAYOUT AV_CH_LAYOUT_STEREO #define PRELOAD_SEC 10
int main(int argc, char **argv) { typedef struct {
if (argc != 2) { AVFormatContext *fmt;
fprintf(stderr, "usage: %s <audio-file>\n", argv[0]); AVCodecContext *dec;
return 1; SwrContext *swr;
} AVPacket *pkt;
AVFrame *frm;
int stream;
int64_t duration_samples;
int64_t played_samples;
int eof;
} Decoder;
const char *filename = argv[1]; /* ---------- Decoder helpers ---------- */
/* ---------- Open input ---------- */ static int decoder_open(Decoder *d, const char *path) {
memset(d, 0, sizeof(*d));
AVFormatContext *fmt = NULL; if (avformat_open_input(&d->fmt, path, NULL, NULL) < 0)
if (avformat_open_input(&fmt, filename, NULL, NULL) < 0) { return -1;
fprintf(stderr, "failed to open input\n");
return 1;
}
if (avformat_find_stream_info(fmt, NULL) < 0) { avformat_find_stream_info(d->fmt, NULL);
fprintf(stderr, "failed to read stream info\n");
return 1;
}
/* ---------- Find audio stream ---------- */ for (unsigned i = 0; i < d->fmt->nb_streams; i++) {
if (d->fmt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
int stream_index = -1; d->stream = i;
for (unsigned i = 0; i < fmt->nb_streams; i++) {
if (fmt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
stream_index = i;
break; break;
} }
} }
if (stream_index < 0) { AVStream *s = d->fmt->streams[d->stream];
fprintf(stderr, "no audio stream found\n"); const AVCodec *codec = avcodec_find_decoder(s->codecpar->codec_id);
return 1;
}
AVStream *stream = fmt->streams[stream_index]; d->dec = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(d->dec, s->codecpar);
avcodec_open2(d->dec, codec, NULL);
/* ---------- Decoder ---------- */ d->swr = swr_alloc();
AVChannelLayout out = AV_CHANNEL_LAYOUT_STEREO;
const AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id); av_opt_set_chlayout(d->swr, "out_chlayout", &out, 0);
if (!codec) { av_opt_set_int(d->swr, "out_sample_rate", OUT_RATE, 0);
fprintf(stderr, "decoder not found\n"); av_opt_set_sample_fmt(d->swr, "out_sample_fmt", OUT_FORMAT, 0);
return 1;
}
AVCodecContext *dec = avcodec_alloc_context3(codec); av_opt_set_chlayout(d->swr, "in_chlayout", &d->dec->ch_layout, 0);
avcodec_parameters_to_context(dec, stream->codecpar); av_opt_set_int(d->swr, "in_sample_rate", d->dec->sample_rate, 0);
av_opt_set_sample_fmt(d->swr, "in_sample_fmt", d->dec->sample_fmt, 0);
if (avcodec_open2(dec, codec, NULL) < 0) { swr_init(d->swr);
fprintf(stderr, "failed to open decoder\n");
return 1;
}
/* ---------- Resampler ---------- */ if (s->duration != AV_NOPTS_VALUE)
d->duration_samples =
av_rescale_q(s->duration, s->time_base, (AVRational){1, OUT_RATE});
SwrContext *swr = swr_alloc(); d->pkt = av_packet_alloc();
if (!swr) { d->frm = av_frame_alloc();
fprintf(stderr, "failed to alloc swr\n");
return 1;
}
AVChannelLayout out_ch_layout;
av_channel_layout_default(&out_ch_layout, OUT_CHANNELS);
av_opt_set_chlayout(swr, "out_chlayout", &out_ch_layout, 0);
av_opt_set_int(swr, "out_sample_rate", OUT_RATE, 0);
av_opt_set_sample_fmt(swr, "out_sample_fmt", OUT_FORMAT, 0);
av_opt_set_chlayout(swr, "in_chlayout", &dec->ch_layout, 0);
av_opt_set_int(swr, "in_sample_rate", dec->sample_rate, 0);
av_opt_set_sample_fmt(swr, "in_sample_fmt", dec->sample_fmt, 0);
if (swr_init(swr) < 0) {
fprintf(stderr, "failed to init swr\n");
return 1;
}
if (!swr || swr_init(swr) < 0) {
fprintf(stderr, "failed to init resampler\n");
return 1;
}
/* ---------- ALSA ---------- */
snd_pcm_t *pcm;
if (snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
fprintf(stderr, "failed to open ALSA device\n");
return 1;
}
snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED,
OUT_CHANNELS, OUT_RATE, 1,
500000 // 0.5s latency
);
/* ---------- Buffers ---------- */
AVPacket *pkt = av_packet_alloc();
AVFrame *frm = av_frame_alloc();
uint8_t *outbuf = NULL;
int out_linesize = 0;
int max_samples = 0;
/* ---------- Decode loop ---------- */
while (av_read_frame(fmt, pkt) >= 0) {
if (pkt->stream_index != stream_index) {
av_packet_unref(pkt);
continue;
}
avcodec_send_packet(dec, pkt);
while (avcodec_receive_frame(dec, frm) == 0) {
int needed =
av_rescale_rnd(swr_get_delay(swr, dec->sample_rate) + frm->nb_samples,
OUT_RATE, dec->sample_rate, AV_ROUND_UP);
if (needed > max_samples) {
av_freep(&outbuf);
av_samples_alloc(&outbuf, &out_linesize, OUT_CHANNELS, needed,
OUT_FORMAT, 1);
max_samples = needed;
}
int samples = swr_convert(swr, &outbuf, needed,
(const uint8_t **)frm->data, frm->nb_samples);
int written = snd_pcm_writei(pcm, outbuf, samples);
if (written < 0)
snd_pcm_recover(pcm, written, 1);
}
av_packet_unref(pkt);
}
/* ---------- Flush ---------- */
avcodec_send_packet(dec, NULL);
while (avcodec_receive_frame(dec, frm) == 0) {
int samples = swr_convert(swr, &outbuf, max_samples,
(const uint8_t **)frm->data, frm->nb_samples);
snd_pcm_writei(pcm, outbuf, samples);
}
snd_pcm_drain(pcm);
/* ---------- Cleanup ---------- */
av_freep(&outbuf);
av_frame_free(&frm);
av_packet_free(&pkt);
swr_free(&swr);
avcodec_free_context(&dec);
avformat_close_input(&fmt);
snd_pcm_close(pcm);
return 0; return 0;
} }
static void decoder_close(Decoder *d) {
if (!d)
return;
av_packet_free(&d->pkt);
av_frame_free(&d->frm);
swr_free(&d->swr);
avcodec_free_context(&d->dec);
avformat_close_input(&d->fmt);
}
/* ---------- Decode PCM ---------- */
static int decode_pcm(Decoder *d, uint8_t **out, int max_samples) {
while (1) {
if (!d->eof) {
if (av_read_frame(d->fmt, d->pkt) < 0) {
avcodec_send_packet(d->dec, NULL);
d->eof = 1;
} else if (d->pkt->stream_index == d->stream) {
avcodec_send_packet(d->dec, d->pkt);
}
av_packet_unref(d->pkt);
}
if (avcodec_receive_frame(d->dec, d->frm) == 0) {
int samples =
swr_convert(d->swr, out, max_samples, (const uint8_t **)d->frm->data,
d->frm->nb_samples);
d->played_samples += samples;
return samples;
}
if (d->eof)
return 0;
}
}
/* ---------- Main ---------- */
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s track1 track2 ...\n", argv[0]);
return 1;
}
snd_pcm_t *pcm;
snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0);
snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED,
OUT_CHANNELS, OUT_RATE, 1, 500000);
Decoder cur = {0}, next = {0};
int track = 1;
int next_loaded = 0;
decoder_open(&cur, argv[track++]);
uint8_t *buf = NULL;
int linesize = 0;
int max_samples = OUT_RATE * 2;
av_samples_alloc(&buf, &linesize, OUT_CHANNELS, max_samples, OUT_FORMAT, 1);
while (1) {
int samples = decode_pcm(&cur, &buf, max_samples);
if (samples > 0) {
snd_pcm_writei(pcm, buf, samples);
}
if (!next_loaded && cur.duration_samples &&
cur.duration_samples - cur.played_samples < PRELOAD_SEC * OUT_RATE &&
track < argc) {
decoder_open(&next, argv[track++]);
next_loaded = 1;
}
if (samples < max_samples && cur.eof) {
if (next_loaded) {
decoder_close(&cur);
cur = next;
memset(&next, 0, sizeof(next));
next_loaded = 0;
int samples_needed = max_samples - samples;
samples = decode_pcm(&cur, &buf, samples_needed);
printf("yo read %d samples\n", samples);
if (samples > 0) {
snd_pcm_writei(pcm, buf, samples);
}
continue;
}
break;
}
}
snd_pcm_drain(pcm);
snd_pcm_close(pcm);
decoder_close(&cur);
av_freep(&buf);
return 0;
}