diff --git a/Makefile b/Makefile index 22334cc..dcb5403 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ CC ?= cc CSTD ?= -std=c11 CFLAGS ?= -O2 -Wall -Wextra -Wpedantic $(CSTD) +CFLAGS += -MMD -MP SDL_CFLAGS := $(shell pkg-config --cflags SDL2 SDL2_ttf SDL2_image 2>/dev/null) SDL_LIBS := $(shell pkg-config --libs SDL2 SDL2_ttf SDL2_image 2>/dev/null) @@ -19,6 +20,7 @@ endif SRC := $(wildcard src/*.c) OBJ := $(SRC:.c=.o) +DEP := $(OBJ:.o=.d) BIN := passport-c-media-player CPPFLAGS += -I./src $(SDL_CFLAGS) $(FFMPEG_CFLAGS) @@ -31,6 +33,8 @@ all: $(BIN) $(BIN): $(OBJ) $(CC) $(OBJ) -o $@ $(LDLIBS) +-include $(DEP) + run: $(BIN) ./$(BIN) diff --git a/src/app.d b/src/app.d new file mode 100644 index 0000000..708163b --- /dev/null +++ b/src/app.d @@ -0,0 +1,8 @@ +src/app.o: src/app.c src/app.h src/channel.h src/player.h \ + src/frame_queue.h src/theme.h src/ui.h +src/app.h: +src/channel.h: +src/player.h: +src/frame_queue.h: +src/theme.h: +src/ui.h: diff --git a/src/channel.d b/src/channel.d new file mode 100644 index 0000000..644d8f4 --- /dev/null +++ b/src/channel.d @@ -0,0 +1,8 @@ +src/channel.o: src/channel.c src/channel.h \ + /usr/include/i386-linux-gnu/sys/stat.h \ + /usr/include/i386-linux-gnu/bits/stat.h \ + /usr/include/i386-linux-gnu/bits/struct_stat.h +src/channel.h: +/usr/include/i386-linux-gnu/sys/stat.h: +/usr/include/i386-linux-gnu/bits/stat.h: +/usr/include/i386-linux-gnu/bits/struct_stat.h: diff --git a/src/frame_queue.c b/src/frame_queue.c index 0a49fd7..562e9d4 100644 --- a/src/frame_queue.c +++ b/src/frame_queue.c @@ -9,10 +9,114 @@ void frame_data_free(FrameData *frame) { return; } - av_freep(&frame->buffer); + if (frame->pool && frame->buffer) { + video_buffer_pool_release(frame->pool, frame->buffer); + } memset(frame, 0, sizeof(*frame)); } +int frame_queue_is_full(FrameQueue *queue) { + int full; + if (!queue || !queue->mutex) { + return 0; + } + SDL_LockMutex(queue->mutex); + full = (queue->count >= FRAME_QUEUE_CAPACITY); + SDL_UnlockMutex(queue->mutex); + return full; +} + +int video_buffer_pool_init(VideoBufferPool *pool) { + int i; + int yuv420p_size; + + if (!pool) { + return -1; + } + + memset(pool, 0, sizeof(*pool)); + pool->mutex = SDL_CreateMutex(); + if (!pool->mutex) { + return -1; + } + + yuv420p_size = TARGET_WIDTH * TARGET_HEIGHT * 3 / 2; + pool->buffer_size = yuv420p_size; + + for (i = 0; i < VIDEO_BUFFER_POOL_SIZE; i++) { + pool->buffers[i] = (unsigned char *)av_malloc(yuv420p_size + 32); + if (!pool->buffers[i]) { + video_buffer_pool_destroy(pool); + return -1; + } + pool->available[i] = 1; + } + + return 0; +} + +void video_buffer_pool_destroy(VideoBufferPool *pool) { + int i; + if (!pool) { + return; + } + + if (pool->mutex) { + SDL_LockMutex(pool->mutex); + } + + for (i = 0; i < VIDEO_BUFFER_POOL_SIZE; i++) { + if (pool->buffers[i]) { + av_freep(&pool->buffers[i]); + } + } + + if (pool->mutex) { + SDL_UnlockMutex(pool->mutex); + SDL_DestroyMutex(pool->mutex); + } + + memset(pool, 0, sizeof(*pool)); +} + +unsigned char *video_buffer_pool_acquire(VideoBufferPool *pool) { + unsigned char *buffer = NULL; + int i; + + if (!pool || !pool->mutex) { + return NULL; + } + + SDL_LockMutex(pool->mutex); + for (i = 0; i < VIDEO_BUFFER_POOL_SIZE; i++) { + if (pool->available[i]) { + pool->available[i] = 0; + buffer = pool->buffers[i]; + break; + } + } + SDL_UnlockMutex(pool->mutex); + + return buffer; +} + +void video_buffer_pool_release(VideoBufferPool *pool, unsigned char *buffer) { + int i; + + if (!pool || !pool->mutex || !buffer) { + return; + } + + SDL_LockMutex(pool->mutex); + for (i = 0; i < VIDEO_BUFFER_POOL_SIZE; i++) { + if (pool->buffers[i] == buffer) { + pool->available[i] = 1; + break; + } + } + SDL_UnlockMutex(pool->mutex); +} + int frame_queue_init(FrameQueue *queue) { if (!queue) { return -1; diff --git a/src/frame_queue.d b/src/frame_queue.d new file mode 100644 index 0000000..6e86a35 --- /dev/null +++ b/src/frame_queue.d @@ -0,0 +1,2 @@ +src/frame_queue.o: src/frame_queue.c src/frame_queue.h +src/frame_queue.h: diff --git a/src/frame_queue.h b/src/frame_queue.h index d4cdf1e..f4ec35a 100644 --- a/src/frame_queue.h +++ b/src/frame_queue.h @@ -1,12 +1,17 @@ #ifndef FRAME_QUEUE_H #define FRAME_QUEUE_H +#include #include -#define FRAME_QUEUE_CAPACITY 12 +#define FRAME_QUEUE_CAPACITY 6 +#define TARGET_WIDTH 640 +#define TARGET_HEIGHT 360 +#define VIDEO_BUFFER_POOL_SIZE 16 typedef struct FrameData { unsigned char *buffer; + struct VideoBufferPool *pool; unsigned char *plane_y; unsigned char *plane_u; unsigned char *plane_v; @@ -26,6 +31,13 @@ typedef struct FrameQueue { SDL_cond *cond; } FrameQueue; +typedef struct VideoBufferPool { + unsigned char *buffers[VIDEO_BUFFER_POOL_SIZE]; + int buffer_size; + int available[VIDEO_BUFFER_POOL_SIZE]; + SDL_mutex *mutex; +} VideoBufferPool; + int frame_queue_init(FrameQueue *queue); void frame_queue_destroy(FrameQueue *queue); void frame_queue_clear(FrameQueue *queue); @@ -34,5 +46,11 @@ int frame_queue_pop_latest(FrameQueue *queue, FrameData *out); int frame_queue_peek_first(FrameQueue *queue, FrameData *out); int frame_queue_pop_first(FrameQueue *queue, FrameData *out); void frame_data_free(FrameData *frame); +int frame_queue_is_full(FrameQueue *queue); + +int video_buffer_pool_init(VideoBufferPool *pool); +void video_buffer_pool_destroy(VideoBufferPool *pool); +unsigned char *video_buffer_pool_acquire(VideoBufferPool *pool); +void video_buffer_pool_release(VideoBufferPool *pool, unsigned char *buffer); #endif diff --git a/src/main.d b/src/main.d new file mode 100644 index 0000000..fd0decd --- /dev/null +++ b/src/main.d @@ -0,0 +1,8 @@ +src/main.o: src/main.c src/app.h src/channel.h src/player.h \ + src/frame_queue.h src/theme.h src/ui.h +src/app.h: +src/channel.h: +src/player.h: +src/frame_queue.h: +src/theme.h: +src/ui.h: diff --git a/src/player.c b/src/player.c index e30f03c..32d9c90 100644 --- a/src/player.c +++ b/src/player.c @@ -15,6 +15,8 @@ #include #include +#define AUDIO_BUFFER_SAMPLES 48000 + typedef struct DecoderThreadArgs { Player *player; const Channel *channel; @@ -180,13 +182,13 @@ static int player_ensure_audio_device(Player *player) { } static int queue_audio_frame(Player *player, - SwrContext **swr_context, - AVCodecContext *audio_codec_context, - AVRational audio_time_base, - AVFrame *audio_frame, - double pts_base_offset, - int *queued_any_audio) { - uint8_t **converted_data = NULL; + SwrContext **swr_context, + AVCodecContext *audio_codec_context, + AVRational audio_time_base, + AVFrame *audio_frame, + double pts_base_offset, + int *queued_any_audio) { + uint8_t *converted_data[1] = {NULL}; int out_rate; int out_channels; enum AVSampleFormat out_format; @@ -196,10 +198,10 @@ static int queue_audio_frame(Player *player, int sample_count; int buffer_size; int queued_limit; - int line_size = 0; int result; double audio_pts; double frame_duration; + int wait_ms = 2; if (!player || !audio_codec_context || !audio_frame) { return -1; @@ -236,34 +238,34 @@ static int queue_audio_frame(Player *player, audio_codec_context->sample_rate, AV_ROUND_UP); - if (av_samples_alloc_array_and_samples(&converted_data, - &line_size, - out_channels, - max_samples, - out_format, - 0) < 0) { - av_channel_layout_uninit(&out_layout); - player_set_error(player, "Unable to allocate audio buffer"); - return -1; + if (max_samples > player->audio_convert_buffer_samples) { + int new_size = max_samples * out_channels * sizeof(int16_t); + uint8_t *new_buffer = (uint8_t *)av_realloc(player->audio_convert_buffer, new_size); + if (!new_buffer) { + av_channel_layout_uninit(&out_layout); + player_set_error(player, "Unable to resize audio buffer"); + return -1; + } + player->audio_convert_buffer = new_buffer; + player->audio_convert_buffer_samples = max_samples; + player->audio_convert_buffer_size = new_size; } + converted_data[0] = player->audio_convert_buffer; + sample_count = swr_convert(*swr_context, converted_data, max_samples, (const uint8_t * const *) audio_frame->extended_data, audio_frame->nb_samples); if (sample_count < 0) { - av_freep(&converted_data[0]); - av_freep(&converted_data); av_channel_layout_uninit(&out_layout); player_set_error(player, "Audio resample failed"); return -1; } - buffer_size = av_samples_get_buffer_size(&line_size, out_channels, sample_count, out_format, 1); + buffer_size = sample_count * out_channels * sizeof(int16_t); if (buffer_size <= 0) { - av_freep(&converted_data[0]); - av_freep(&converted_data); av_channel_layout_uninit(&out_layout); return 0; } @@ -275,12 +277,11 @@ static int queue_audio_frame(Player *player, queued_limit = (int) (player_audio_bytes_per_second(player) * 0.18); while (!should_stop(player) && (int) SDL_GetQueuedAudioSize(player->audio_device) > queued_limit) { - SDL_Delay(2); + SDL_Delay(wait_ms); + wait_ms = (wait_ms < 10) ? wait_ms + 1 : 10; } - result = SDL_QueueAudio(player->audio_device, converted_data[0], (Uint32) buffer_size); - av_freep(&converted_data[0]); - av_freep(&converted_data); + result = SDL_QueueAudio(player->audio_device, player->audio_convert_buffer, (Uint32) buffer_size); if (result != 0) { av_channel_layout_uninit(&out_layout); @@ -403,8 +404,8 @@ static int decode_context_open(DecodeContext *ctx, const ProgramEntry *program, ctx->sws_context = sws_getContext(ctx->video_codec_context->width, ctx->video_codec_context->height, ctx->video_codec_context->pix_fmt, - ctx->video_codec_context->width, - ctx->video_codec_context->height, + TARGET_WIDTH, + TARGET_HEIGHT, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, @@ -612,30 +613,62 @@ static int decode_thread_main(void *userdata) { FrameData frame = {0}; uint8_t *dest_data[4] = {0}; int dest_linesize[4] = {0}; - int image_size; + int needs_conversion = 1; - frame.width = ctx.video_codec_context->width; - frame.height = ctx.video_codec_context->height; + while (!should_stop(player) && frame_queue_is_full(&player->frame_queue)) { + SDL_Delay(5); + } + if (should_stop(player)) { + break; + } + + frame.width = TARGET_WIDTH; + frame.height = TARGET_HEIGHT; frame.pts_seconds = frame_seconds; - image_size = av_image_alloc(dest_data, dest_linesize, frame.width, frame.height, AV_PIX_FMT_YUV420P, 1); - if (image_size < 0) { - player_set_error(player, "Unable to allocate frame buffer"); + + frame.buffer = video_buffer_pool_acquire(&player->video_buffer_pool); + if (!frame.buffer) { + player_set_error(player, "Unable to acquire frame buffer from pool"); goto cleanup; } - frame.buffer = dest_data[0]; + + frame.pool = &player->video_buffer_pool; + dest_data[0] = frame.buffer; + dest_data[1] = frame.buffer + (TARGET_WIDTH * TARGET_HEIGHT); + dest_data[2] = frame.buffer + (TARGET_WIDTH * TARGET_HEIGHT * 5 / 4); + dest_linesize[0] = TARGET_WIDTH; + dest_linesize[1] = TARGET_WIDTH / 2; + dest_linesize[2] = TARGET_WIDTH / 2; + + if (ctx.video_codec_context->width == TARGET_WIDTH && + ctx.video_codec_context->height == TARGET_HEIGHT && + ctx.video_codec_context->pix_fmt == AV_PIX_FMT_YUV420P) { + needs_conversion = 0; + } + + if (needs_conversion) { + sws_scale(ctx.sws_context, + (const uint8_t *const *) ctx.decoded_frame->data, + ctx.decoded_frame->linesize, + 0, + ctx.video_codec_context->height, + dest_data, + dest_linesize); + } else { + av_image_copy(dest_data, dest_linesize, + (const uint8_t *const *) ctx.decoded_frame->data, + ctx.decoded_frame->linesize, + AV_PIX_FMT_YUV420P, + TARGET_WIDTH, TARGET_HEIGHT); + } + frame.plane_y = dest_data[0]; frame.plane_u = dest_data[1]; frame.plane_v = dest_data[2]; frame.pitch_y = dest_linesize[0]; frame.pitch_u = dest_linesize[1]; frame.pitch_v = dest_linesize[2]; - sws_scale(ctx.sws_context, - (const uint8_t *const *) ctx.decoded_frame->data, - ctx.decoded_frame->linesize, - 0, - ctx.video_codec_context->height, - dest_data, - dest_linesize); + frame_queue_push(&player->frame_queue, &frame); queued_first_video = 1; MAYBE_MARK_PREROLL_READY(); @@ -696,6 +729,28 @@ int player_init(Player *player, const ChannelList *channels, Uint64 app_start_ti return -1; } + if (video_buffer_pool_init(&player->video_buffer_pool) != 0) { + frame_queue_destroy(&player->frame_queue); + SDL_DestroyMutex(player->audio_mutex); + SDL_DestroyMutex(player->clock_mutex); + SDL_DestroyMutex(player->error_mutex); + memset(player, 0, sizeof(*player)); + return -1; + } + + player->audio_convert_buffer_samples = AUDIO_BUFFER_SAMPLES; + player->audio_convert_buffer_size = AUDIO_BUFFER_SAMPLES * 2 * sizeof(int16_t); + player->audio_convert_buffer = (uint8_t *)av_malloc(player->audio_convert_buffer_size); + if (!player->audio_convert_buffer) { + video_buffer_pool_destroy(&player->video_buffer_pool); + frame_queue_destroy(&player->frame_queue); + SDL_DestroyMutex(player->audio_mutex); + SDL_DestroyMutex(player->clock_mutex); + SDL_DestroyMutex(player->error_mutex); + memset(player, 0, sizeof(*player)); + return -1; + } + return 0; } @@ -706,6 +761,10 @@ void player_destroy(Player *player) { player_stop_thread(player); player_close_audio(player); + video_buffer_pool_destroy(&player->video_buffer_pool); + if (player->audio_convert_buffer) { + av_freep(&player->audio_convert_buffer); + } frame_queue_destroy(&player->frame_queue); if (player->audio_mutex) { SDL_DestroyMutex(player->audio_mutex); diff --git a/src/player.d b/src/player.d new file mode 100644 index 0000000..8d072b6 --- /dev/null +++ b/src/player.d @@ -0,0 +1,4 @@ +src/player.o: src/player.c src/player.h src/channel.h src/frame_queue.h +src/player.h: +src/channel.h: +src/frame_queue.h: diff --git a/src/player.h b/src/player.h index 0568cf6..a9fe1be 100644 --- a/src/player.h +++ b/src/player.h @@ -1,6 +1,7 @@ #ifndef PLAYER_H #define PLAYER_H +#include #include #include @@ -9,6 +10,7 @@ typedef struct Player { FrameQueue frame_queue; + VideoBufferPool video_buffer_pool; SDL_Thread *thread; SDL_atomic_t stop_requested; const ChannelList *channels; @@ -27,6 +29,9 @@ typedef struct Player { char audio_driver[32]; SDL_mutex *error_mutex; char last_error[256]; + uint8_t *audio_convert_buffer; + int audio_convert_buffer_size; + int audio_convert_buffer_samples; } Player; int player_init(Player *player, const ChannelList *channels, Uint64 app_start_ticks); diff --git a/src/ui.d b/src/ui.d new file mode 100644 index 0000000..f011b0c --- /dev/null +++ b/src/ui.d @@ -0,0 +1,4 @@ +src/ui.o: src/ui.c src/ui.h src/channel.h src/theme.h +src/ui.h: +src/channel.h: +src/theme.h: