New directory based system for channels

This commit is contained in:
markmental 2026-03-28 00:25:06 -04:00
commit b84a3060c1
8 changed files with 466 additions and 241 deletions

View file

@ -184,6 +184,7 @@ static int queue_audio_frame(Player *player,
AVCodecContext *audio_codec_context,
AVRational audio_time_base,
AVFrame *audio_frame,
double pts_base_offset,
int *queued_any_audio) {
uint8_t **converted_data = NULL;
int out_rate;
@ -269,7 +270,7 @@ static int queue_audio_frame(Player *player,
audio_pts = audio_frame->best_effort_timestamp == AV_NOPTS_VALUE
? NAN
: audio_frame->best_effort_timestamp * av_q2d(audio_time_base);
: pts_base_offset + (audio_frame->best_effort_timestamp * av_q2d(audio_time_base));
frame_duration = (double) sample_count / (double) out_rate;
queued_limit = (int) (player_audio_bytes_per_second(player) * 0.18);
@ -303,187 +304,211 @@ static int queue_audio_frame(Player *player,
return 0;
}
typedef struct DecodeContext {
AVFormatContext *format_context;
AVCodecContext *video_codec_context;
AVCodecContext *audio_codec_context;
struct SwsContext *sws_context;
SwrContext *swr_context;
AVPacket *packet;
AVFrame *decoded_frame;
AVFrame *audio_frame;
AVRational video_time_base;
AVRational audio_time_base;
int video_stream_index;
int audio_stream_index;
} DecodeContext;
static void decode_context_reset(DecodeContext *ctx) {
if (!ctx) {
return;
}
if (ctx->packet) av_packet_free(&ctx->packet);
if (ctx->decoded_frame) av_frame_free(&ctx->decoded_frame);
if (ctx->audio_frame) av_frame_free(&ctx->audio_frame);
if (ctx->video_codec_context) avcodec_free_context(&ctx->video_codec_context);
if (ctx->audio_codec_context) avcodec_free_context(&ctx->audio_codec_context);
if (ctx->format_context) avformat_close_input(&ctx->format_context);
if (ctx->sws_context) sws_freeContext(ctx->sws_context);
if (ctx->swr_context) swr_free(&ctx->swr_context);
memset(ctx, 0, sizeof(*ctx));
ctx->video_stream_index = -1;
ctx->audio_stream_index = -1;
}
static int decode_context_open(DecodeContext *ctx, const ProgramEntry *program, Player *player) {
const AVCodec *video_codec = NULL;
const AVCodec *audio_codec = NULL;
decode_context_reset(ctx);
ctx->video_stream_index = -1;
ctx->audio_stream_index = -1;
if (avformat_open_input(&ctx->format_context, program->file_path, NULL, NULL) < 0) {
player_set_error(player, "Unable to open media file");
return -1;
}
if (avformat_find_stream_info(ctx->format_context, NULL) < 0) {
player_set_error(player, "Unable to read stream metadata");
return -1;
}
for (unsigned int i = 0; i < ctx->format_context->nb_streams; ++i) {
if (ctx->format_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
ctx->video_stream_index = (int) i;
} else if (ctx->format_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && ctx->audio_stream_index < 0) {
ctx->audio_stream_index = (int) i;
}
}
if (ctx->video_stream_index < 0) {
player_set_error(player, "No video stream found");
return -1;
}
video_codec = avcodec_find_decoder(ctx->format_context->streams[ctx->video_stream_index]->codecpar->codec_id);
if (!video_codec) {
player_set_error(player, "Unsupported video codec");
return -1;
}
ctx->video_codec_context = avcodec_alloc_context3(video_codec);
if (!ctx->video_codec_context ||
avcodec_parameters_to_context(ctx->video_codec_context, ctx->format_context->streams[ctx->video_stream_index]->codecpar) < 0 ||
avcodec_open2(ctx->video_codec_context, video_codec, NULL) < 0) {
player_set_error(player, "Unable to initialize decoder");
return -1;
}
if (ctx->audio_stream_index >= 0) {
audio_codec = avcodec_find_decoder(ctx->format_context->streams[ctx->audio_stream_index]->codecpar->codec_id);
if (audio_codec) {
ctx->audio_codec_context = avcodec_alloc_context3(audio_codec);
if (!ctx->audio_codec_context ||
avcodec_parameters_to_context(ctx->audio_codec_context, ctx->format_context->streams[ctx->audio_stream_index]->codecpar) < 0 ||
avcodec_open2(ctx->audio_codec_context, audio_codec, NULL) < 0) {
player_set_error(player, "Unable to initialize audio decoder");
return -1;
}
ctx->audio_time_base = ctx->format_context->streams[ctx->audio_stream_index]->time_base;
}
}
ctx->packet = av_packet_alloc();
ctx->decoded_frame = av_frame_alloc();
ctx->audio_frame = av_frame_alloc();
if (!ctx->packet || !ctx->decoded_frame || !ctx->audio_frame) {
player_set_error(player, "Unable to allocate FFmpeg frame buffers");
return -1;
}
ctx->video_time_base = ctx->format_context->streams[ctx->video_stream_index]->time_base;
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,
AV_PIX_FMT_YUV420P,
SWS_BILINEAR,
NULL,
NULL,
NULL);
if (!ctx->sws_context) {
player_set_error(player, "Unable to initialize scaler");
return -1;
}
player->has_audio_stream = ctx->audio_codec_context != NULL;
return 0;
}
static int decode_thread_main(void *userdata) {
DecoderThreadArgs *args = (DecoderThreadArgs *) userdata;
Player *player = args->player;
const Channel *channel = args->channel;
AVFormatContext *format_context = NULL;
AVCodecContext *codec_context = NULL;
AVCodecContext *audio_codec_context = NULL;
AVPacket *packet = NULL;
AVFrame *decoded_frame = NULL;
AVFrame *audio_frame = NULL;
struct SwsContext *sws_context = NULL;
SwrContext *swr_context = NULL;
const AVCodec *codec = NULL;
const AVCodec *audio_codec = NULL;
int video_stream_index = -1;
int audio_stream_index = -1;
int rc = -1;
DecodeContext ctx;
const ProgramEntry *program;
int current_program_index = 0;
double seek_seconds = 0.0;
AVRational time_base;
AVRational audio_time_base = {0};
int queued_any_audio = 0;
int queued_first_video = 0;
int rc = -1;
#define MAYBE_MARK_PREROLL_READY() \
do { \
if (queued_first_video && (queued_any_audio || !player->has_audio_stream)) { \
player_mark_preroll_ready(player); \
} \
} while (0)
#define MAYBE_MARK_PREROLL_READY() do { if (queued_first_video && (queued_any_audio || !player->has_audio_stream)) { player_mark_preroll_ready(player); } } while (0)
memset(&ctx, 0, sizeof(ctx));
ctx.video_stream_index = -1;
ctx.audio_stream_index = -1;
free(args);
if (avformat_open_input(&format_context, channel->file_path, NULL, NULL) < 0) {
player_set_error(player, "Unable to open media file");
program = channel_resolve_program(channel, player->app_start_ticks, SDL_GetTicks64(), &seek_seconds, &current_program_index);
if (!program || decode_context_open(&ctx, program, player) != 0) {
decode_context_reset(&ctx);
return -1;
}
if (avformat_find_stream_info(format_context, NULL) < 0) {
player_set_error(player, "Unable to read stream metadata");
goto cleanup;
}
for (unsigned int i = 0; i < format_context->nb_streams; ++i) {
if (format_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_index = (int) i;
} else if (format_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_stream_index < 0) {
audio_stream_index = (int) i;
if (seek_seconds > 0.0) {
int64_t seek_target = (int64_t) (seek_seconds / av_q2d(ctx.video_time_base));
avformat_seek_file(ctx.format_context, ctx.video_stream_index, INT64_MIN, seek_target, INT64_MAX, 0);
avcodec_flush_buffers(ctx.video_codec_context);
if (ctx.audio_codec_context) {
avcodec_flush_buffers(ctx.audio_codec_context);
}
}
if (video_stream_index < 0) {
player_set_error(player, "No video stream found");
goto cleanup;
}
codec = avcodec_find_decoder(format_context->streams[video_stream_index]->codecpar->codec_id);
if (!codec) {
player_set_error(player, "Unsupported video codec");
goto cleanup;
}
codec_context = avcodec_alloc_context3(codec);
if (!codec_context) {
player_set_error(player, "Unable to allocate codec context");
goto cleanup;
}
if (avcodec_parameters_to_context(codec_context, format_context->streams[video_stream_index]->codecpar) < 0 ||
avcodec_open2(codec_context, codec, NULL) < 0) {
player_set_error(player, "Unable to initialize decoder");
goto cleanup;
}
if (audio_stream_index >= 0) {
audio_codec = avcodec_find_decoder(format_context->streams[audio_stream_index]->codecpar->codec_id);
if (audio_codec) {
audio_codec_context = avcodec_alloc_context3(audio_codec);
if (!audio_codec_context) {
player_set_error(player, "Unable to allocate audio decoder context");
goto cleanup;
}
if (avcodec_parameters_to_context(audio_codec_context, format_context->streams[audio_stream_index]->codecpar) < 0 ||
avcodec_open2(audio_codec_context, audio_codec, NULL) < 0) {
player_set_error(player, "Unable to initialize audio decoder");
goto cleanup;
}
audio_time_base = format_context->streams[audio_stream_index]->time_base;
}
}
decoded_frame = av_frame_alloc();
audio_frame = av_frame_alloc();
packet = av_packet_alloc();
if (!decoded_frame || !audio_frame || !packet) {
player_set_error(player, "Unable to allocate FFmpeg frame buffers");
goto cleanup;
}
time_base = format_context->streams[video_stream_index]->time_base;
player->has_audio_stream = audio_codec_context != NULL;
sws_context = sws_getContext(codec_context->width,
codec_context->height,
codec_context->pix_fmt,
codec_context->width,
codec_context->height,
AV_PIX_FMT_YUV420P,
SWS_BILINEAR,
NULL,
NULL,
NULL);
if (!sws_context) {
player_set_error(player, "Unable to initialize scaler");
goto cleanup;
}
seek_seconds = channel_live_position_precise(channel, player->app_start_ticks, SDL_GetTicks64());
if (channel->duration_seconds > 0.0) {
int64_t seek_target = (int64_t) (seek_seconds / av_q2d(time_base));
avformat_seek_file(format_context, video_stream_index, INT64_MIN, seek_target, INT64_MAX, 0);
avcodec_flush_buffers(codec_context);
if (audio_codec_context) {
avcodec_flush_buffers(audio_codec_context);
}
player_clear_audio(player);
}
player_clear_audio(player);
while (!should_stop(player)) {
if (av_read_frame(format_context, packet) < 0) {
if (channel->duration_seconds > 0.0) {
seek_seconds = 0.0;
avformat_seek_file(format_context, video_stream_index, INT64_MIN, 0, INT64_MAX, 0);
avcodec_flush_buffers(codec_context);
if (audio_codec_context) {
avcodec_flush_buffers(audio_codec_context);
}
player_clear_audio(player);
continue;
if (av_read_frame(ctx.format_context, ctx.packet) < 0) {
current_program_index = (current_program_index + 1) % channel->program_count;
program = channel_program_at_index(channel, current_program_index);
queued_any_audio = 0;
queued_first_video = 0;
if (!program || decode_context_open(&ctx, program, player) != 0) {
goto cleanup;
}
break;
continue;
}
if (packet->stream_index == audio_stream_index && audio_codec_context) {
if (avcodec_send_packet(audio_codec_context, packet) >= 0) {
if (ctx.packet->stream_index == ctx.audio_stream_index && ctx.audio_codec_context) {
if (avcodec_send_packet(ctx.audio_codec_context, ctx.packet) >= 0) {
while (!should_stop(player)) {
int receive_audio = avcodec_receive_frame(audio_codec_context, audio_frame);
int receive_audio = avcodec_receive_frame(ctx.audio_codec_context, ctx.audio_frame);
if (receive_audio == AVERROR(EAGAIN) || receive_audio == AVERROR_EOF) {
break;
}
if (receive_audio < 0) {
player_set_error(player, "Audio decode failed");
av_packet_unref(packet);
av_packet_unref(ctx.packet);
goto cleanup;
}
if (queue_audio_frame(player, &swr_context, audio_codec_context, audio_time_base, audio_frame, &queued_any_audio) != 0) {
av_packet_unref(packet);
if (queue_audio_frame(player,
&ctx.swr_context,
ctx.audio_codec_context,
ctx.audio_time_base,
ctx.audio_frame,
program->start_offset_seconds,
&queued_any_audio) != 0) {
av_packet_unref(ctx.packet);
goto cleanup;
}
MAYBE_MARK_PREROLL_READY();
av_frame_unref(audio_frame);
av_frame_unref(ctx.audio_frame);
}
}
av_packet_unref(packet);
av_packet_unref(ctx.packet);
continue;
}
if (packet->stream_index != video_stream_index) {
av_packet_unref(packet);
if (ctx.packet->stream_index != ctx.video_stream_index) {
av_packet_unref(ctx.packet);
continue;
}
if (avcodec_send_packet(codec_context, packet) < 0) {
av_packet_unref(packet);
if (avcodec_send_packet(ctx.video_codec_context, ctx.packet) < 0) {
av_packet_unref(ctx.packet);
continue;
}
av_packet_unref(packet);
av_packet_unref(ctx.packet);
while (!should_stop(player)) {
double frame_seconds;
int receive = avcodec_receive_frame(codec_context, decoded_frame);
int receive = avcodec_receive_frame(ctx.video_codec_context, ctx.decoded_frame);
if (receive == AVERROR(EAGAIN) || receive == AVERROR_EOF) {
break;
}
@ -492,9 +517,9 @@ static int decode_thread_main(void *userdata) {
goto cleanup;
}
frame_seconds = decoded_frame->best_effort_timestamp == AV_NOPTS_VALUE
? seek_seconds
: decoded_frame->best_effort_timestamp * av_q2d(time_base);
frame_seconds = ctx.decoded_frame->best_effort_timestamp == AV_NOPTS_VALUE
? program->start_offset_seconds + seek_seconds
: program->start_offset_seconds + (ctx.decoded_frame->best_effort_timestamp * av_q2d(ctx.video_time_base));
{
FrameData frame = {0};
@ -502,20 +527,14 @@ static int decode_thread_main(void *userdata) {
int dest_linesize[4] = {0};
int image_size;
frame.width = codec_context->width;
frame.height = codec_context->height;
frame.width = ctx.video_codec_context->width;
frame.height = ctx.video_codec_context->height;
frame.pts_seconds = frame_seconds;
image_size = av_image_alloc(dest_data,
dest_linesize,
frame.width,
frame.height,
AV_PIX_FMT_YUV420P,
1);
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");
goto cleanup;
}
frame.buffer = dest_data[0];
frame.plane_y = dest_data[0];
frame.plane_u = dest_data[1];
@ -523,18 +542,15 @@ static int decode_thread_main(void *userdata) {
frame.pitch_y = dest_linesize[0];
frame.pitch_u = dest_linesize[1];
frame.pitch_v = dest_linesize[2];
sws_scale(sws_context,
(const uint8_t *const *) decoded_frame->data,
decoded_frame->linesize,
sws_scale(ctx.sws_context,
(const uint8_t *const *) ctx.decoded_frame->data,
ctx.decoded_frame->linesize,
0,
codec_context->height,
ctx.video_codec_context->height,
dest_data,
dest_linesize);
frame_queue_push(&player->frame_queue, &frame);
if (!queued_first_video) {
queued_first_video = 1;
}
queued_first_video = 1;
MAYBE_MARK_PREROLL_READY();
}
}
@ -543,30 +559,7 @@ static int decode_thread_main(void *userdata) {
rc = 0;
cleanup:
if (packet) {
av_packet_free(&packet);
}
if (decoded_frame) {
av_frame_free(&decoded_frame);
}
if (audio_frame) {
av_frame_free(&audio_frame);
}
if (codec_context) {
avcodec_free_context(&codec_context);
}
if (audio_codec_context) {
avcodec_free_context(&audio_codec_context);
}
if (format_context) {
avformat_close_input(&format_context);
}
if (sws_context) {
sws_freeContext(sws_context);
}
if (swr_context) {
swr_free(&swr_context);
}
decode_context_reset(&ctx);
player->has_audio_stream = 0;
return rc;