New directory based system for channels
This commit is contained in:
parent
3e6d29670c
commit
b84a3060c1
8 changed files with 466 additions and 241 deletions
357
src/player.c
357
src/player.c
|
|
@ -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, ¤t_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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue