Last active
August 3, 2024 13:19
-
-
Save rlapz/ca9fd46fb03ef1ed453c41730d3c780b to your computer and use it in GitHub Desktop.
A simple audio player using `ffmpeg` & `SDL2`. Powered by GPT-4o.
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 <libavformat/avformat.h> | |
#include <libavcodec/avcodec.h> | |
#include <libavutil/opt.h> | |
#include <libavutil/mathematics.h> | |
#include <libavutil/samplefmt.h> | |
#include <libswresample/swresample.h> | |
#include <SDL2/SDL.h> | |
#define SDL_AUDIO_BUFFER_SIZE 1024 | |
typedef struct AudioData { | |
uint8_t *pos; | |
int len; | |
} AudioData; | |
volatile int is_paused = 0; | |
volatile int is_stopped = 0; | |
volatile int is_quit = 0; | |
void audio_callback(void *userdata, Uint8 *stream, int len) { | |
AudioData *audio = (AudioData *)userdata; | |
if (is_stopped) { | |
SDL_memset(stream, 0, len); | |
return; | |
} | |
if (is_paused) { | |
SDL_memset(stream, 0, len); | |
return; | |
} | |
if (audio->len == 0) { | |
return; | |
} | |
len = (len > audio->len ? audio->len : len); | |
SDL_memcpy(stream, audio->pos, len); | |
audio->pos += len; | |
audio->len -= len; | |
} | |
int main(int argc, char *argv[]) { | |
if (argc < 2) { | |
printf("Usage: %s <audio_file>\n", argv[0]); | |
return -1; | |
} | |
const char *filename = argv[1]; | |
//av_register_all(); | |
AVFormatContext *format_ctx = avformat_alloc_context(); | |
if (avformat_open_input(&format_ctx, filename, NULL, NULL) != 0) { | |
fprintf(stderr, "Could not open file %s\n", filename); | |
return -1; | |
} | |
if (avformat_find_stream_info(format_ctx, NULL) < 0) { | |
fprintf(stderr, "Could not retrieve stream info\n"); | |
return -1; | |
} | |
int audio_stream_index = -1; | |
for (int i = 0; i < format_ctx->nb_streams; i++) { | |
if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { | |
audio_stream_index = i; | |
break; | |
} | |
} | |
if (audio_stream_index == -1) { | |
fprintf(stderr, "No audio stream found\n"); | |
return -1; | |
} | |
AVCodecParameters *codec_par = format_ctx->streams[audio_stream_index]->codecpar; | |
AVCodec *codec = avcodec_find_decoder(codec_par->codec_id); | |
if (!codec) { | |
fprintf(stderr, "Unsupported codec\n"); | |
return -1; | |
} | |
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); | |
avcodec_parameters_to_context(codec_ctx, codec_par); | |
avcodec_open2(codec_ctx, codec, NULL); | |
SDL_Init(SDL_INIT_AUDIO | SDL_INIT_EVENTS); | |
AudioData audio_data; | |
SDL_AudioSpec wanted_spec, obtained_spec; | |
wanted_spec.freq = codec_ctx->sample_rate; | |
wanted_spec.format = AUDIO_F32SYS; | |
wanted_spec.channels = codec_ctx->channels; | |
wanted_spec.silence = 0; | |
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE; | |
wanted_spec.callback = audio_callback; | |
wanted_spec.userdata = &audio_data; | |
if (SDL_OpenAudio(&wanted_spec, &obtained_spec) < 0) { | |
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError()); | |
return -1; | |
} | |
AVPacket packet; | |
AVFrame *frame = av_frame_alloc(); | |
struct SwrContext *swr_ctx = swr_alloc(); | |
av_opt_set_int(swr_ctx, "in_channel_layout", codec_ctx->channel_layout, 0); | |
av_opt_set_int(swr_ctx, "in_sample_rate", codec_ctx->sample_rate, 0); | |
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", codec_ctx->sample_fmt, 0); | |
av_opt_set_int(swr_ctx, "out_channel_layout", codec_ctx->channel_layout, 0); | |
av_opt_set_int(swr_ctx, "out_sample_rate", obtained_spec.freq, 0); | |
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0); | |
swr_init(swr_ctx); | |
SDL_PauseAudio(0); | |
while (!is_quit && av_read_frame(format_ctx, &packet) >= 0) { | |
if (packet.stream_index == audio_stream_index) { | |
if (avcodec_send_packet(codec_ctx, &packet) == 0) { | |
while (avcodec_receive_frame(codec_ctx, frame) == 0) { | |
int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, codec_ctx->sample_rate) + frame->nb_samples, obtained_spec.freq, codec_ctx->sample_rate, AV_ROUND_UP); | |
uint8_t **dst_data = NULL; | |
av_samples_alloc_array_and_samples(&dst_data, NULL, codec_ctx->channels, dst_nb_samples, AV_SAMPLE_FMT_FLT, 0); | |
swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **)frame->data, frame->nb_samples); | |
int buffer_size = av_samples_get_buffer_size(NULL, codec_ctx->channels, dst_nb_samples, AV_SAMPLE_FMT_FLT, 1); | |
audio_data.pos = dst_data[0]; | |
audio_data.len = buffer_size; | |
while (audio_data.len > 0 && !is_quit) { | |
SDL_Event event; | |
while (SDL_PollEvent(&event)) { | |
if (event.type == SDL_QUIT) { | |
is_quit = 1; | |
} else if (event.type == SDL_KEYDOWN) { | |
switch (event.key.keysym.sym) { | |
case SDLK_p: // Pause | |
is_paused = !is_paused; | |
if (!is_paused) SDL_PauseAudio(0); | |
break; | |
case SDLK_s: // Stop | |
is_stopped = 1; | |
SDL_PauseAudio(1); | |
break; | |
case SDLK_q: // Quit | |
is_quit = 1; | |
break; | |
} | |
} | |
} | |
SDL_Delay(1); | |
} | |
av_freep(&dst_data[0]); | |
} | |
} | |
} | |
av_packet_unref(&packet); | |
} | |
swr_free(&swr_ctx); | |
av_frame_free(&frame); | |
avcodec_close(codec_ctx); | |
avformat_close_input(&format_ctx); | |
SDL_CloseAudio(); | |
SDL_Quit(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment