Last active
March 17, 2026 18:49
-
-
Save rlapz/72ccbb0297a998eebe11ccc17ddf3039 to your computer and use it in GitHub Desktop.
WIP
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 <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, ¶m, 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