#include "player.h" #include #include #include #include #include #include #include #include typedef struct DecoderThreadArgs { Player *player; const Channel *channel; } DecoderThreadArgs; static void player_set_error(Player *player, const char *message) { if (!player || !player->error_mutex) { return; } SDL_LockMutex(player->error_mutex); snprintf(player->last_error, sizeof(player->last_error), "%s", message ? message : "Unknown playback error"); SDL_UnlockMutex(player->error_mutex); } static int should_stop(Player *player) { return player ? SDL_AtomicGet(&player->stop_requested) : 1; } 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; AVPacket *packet = NULL; AVFrame *decoded_frame = NULL; struct SwsContext *sws_context = NULL; const AVCodec *codec = NULL; int video_stream_index = -1; int rc = -1; int rgb_stride = 0; double seek_seconds = 0.0; AVRational time_base; Uint64 wall_base_ms = 0; int first_frame = 1; free(args); if (avformat_open_input(&format_context, channel->file_path, NULL, NULL) < 0) { player_set_error(player, "Unable to open media file"); 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; break; } } 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; } decoded_frame = av_frame_alloc(); packet = av_packet_alloc(); if (!decoded_frame || !packet) { player_set_error(player, "Unable to allocate FFmpeg frame buffers"); goto cleanup; } rgb_stride = codec_context->width * 4; time_base = format_context->streams[video_stream_index]->time_base; sws_context = sws_getContext(codec_context->width, codec_context->height, codec_context->pix_fmt, codec_context->width, codec_context->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, NULL, NULL, NULL); if (!sws_context) { player_set_error(player, "Unable to initialize scaler"); goto cleanup; } seek_seconds = channel_live_position(channel, player->app_start_time, time(NULL)); 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); } while (!should_stop(player)) { if (av_read_frame(format_context, packet) < 0) { if (channel->duration_seconds > 0.0) { seek_seconds = 0.0; wall_base_ms = SDL_GetTicks64(); avformat_seek_file(format_context, video_stream_index, INT64_MIN, 0, INT64_MAX, 0); avcodec_flush_buffers(codec_context); continue; } break; } if (packet->stream_index != video_stream_index) { av_packet_unref(packet); continue; } if (avcodec_send_packet(codec_context, packet) < 0) { av_packet_unref(packet); continue; } av_packet_unref(packet); while (!should_stop(player)) { double frame_seconds; int delay_ms; int receive = avcodec_receive_frame(codec_context, decoded_frame); if (receive == AVERROR(EAGAIN) || receive == AVERROR_EOF) { break; } if (receive < 0) { player_set_error(player, "Video decode failed"); goto cleanup; } frame_seconds = decoded_frame->best_effort_timestamp == AV_NOPTS_VALUE ? seek_seconds : decoded_frame->best_effort_timestamp * av_q2d(time_base); if (first_frame) { wall_base_ms = SDL_GetTicks64(); first_frame = 0; } delay_ms = (int) (((frame_seconds - seek_seconds) * 1000.0) - (double) (SDL_GetTicks64() - wall_base_ms)); if (delay_ms > 1 && delay_ms < 250) { SDL_Delay((Uint32) delay_ms); } { FrameData frame = {0}; uint8_t *dest_data[4] = {0}; int dest_linesize[4] = {0}; frame.width = codec_context->width; frame.height = codec_context->height; frame.stride = rgb_stride; frame.pts_seconds = frame_seconds; frame.pixels = malloc((size_t) frame.stride * (size_t) frame.height); if (!frame.pixels) { player_set_error(player, "Unable to allocate frame buffer"); goto cleanup; } dest_data[0] = frame.pixels; dest_linesize[0] = frame.stride; sws_scale(sws_context, (const uint8_t *const *) decoded_frame->data, decoded_frame->linesize, 0, codec_context->height, dest_data, dest_linesize); frame_queue_push(&player->frame_queue, &frame); } } } rc = 0; cleanup: if (packet) { av_packet_free(&packet); } if (decoded_frame) { av_frame_free(&decoded_frame); } if (codec_context) { avcodec_free_context(&codec_context); } if (format_context) { avformat_close_input(&format_context); } if (sws_context) { sws_freeContext(sws_context); } return rc; } static void player_stop_thread(Player *player) { if (!player || !player->thread) { return; } SDL_AtomicSet(&player->stop_requested, 1); SDL_WaitThread(player->thread, NULL); player->thread = NULL; } int player_init(Player *player, const ChannelList *channels, time_t app_start_time) { if (!player || !channels) { return -1; } memset(player, 0, sizeof(*player)); player->channels = channels; player->current_index = -1; player->app_start_time = app_start_time; player->error_mutex = SDL_CreateMutex(); if (!player->error_mutex) { return -1; } if (frame_queue_init(&player->frame_queue) != 0) { SDL_DestroyMutex(player->error_mutex); memset(player, 0, sizeof(*player)); return -1; } return 0; } void player_destroy(Player *player) { if (!player) { return; } player_stop_thread(player); frame_queue_destroy(&player->frame_queue); if (player->error_mutex) { SDL_DestroyMutex(player->error_mutex); } memset(player, 0, sizeof(*player)); } int player_tune(Player *player, int channel_index) { DecoderThreadArgs *args; if (!player || !player->channels || channel_index < 0 || channel_index >= player->channels->count) { return -1; } player_stop_thread(player); frame_queue_clear(&player->frame_queue); SDL_AtomicSet(&player->stop_requested, 0); player->current_index = channel_index; player->tuning_blackout_until = SDL_GetTicks() + 200; player_set_error(player, ""); args = malloc(sizeof(*args)); if (!args) { player_set_error(player, "Unable to allocate thread args"); return -1; } args->player = player; args->channel = &player->channels->items[channel_index]; player->thread = SDL_CreateThread(decode_thread_main, "decoder", args); if (!player->thread) { free(args); player_set_error(player, "Unable to start decoder thread"); return -1; } return 0; } int player_consume_latest_frame(Player *player, FrameData *out) { if (!player || !out) { return 0; } return frame_queue_pop_latest(&player->frame_queue, out); } double player_live_position(const Player *player, time_t now) { if (!player || !player->channels || player->current_index < 0 || player->current_index >= player->channels->count) { return 0.0; } return channel_live_position(&player->channels->items[player->current_index], player->app_start_time, now); } int player_is_in_blackout(const Player *player) { return player && SDL_GetTicks() < player->tuning_blackout_until; } void player_get_error(Player *player, char *buffer, size_t buffer_size) { if (!player || !buffer || buffer_size == 0) { return; } SDL_LockMutex(player->error_mutex); snprintf(buffer, buffer_size, "%s", player->last_error); SDL_UnlockMutex(player->error_mutex); }