327 lines
10 KiB
C
327 lines
10 KiB
C
|
|
#include "player.h"
|
||
|
|
|
||
|
|
#include <limits.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <libavcodec/avcodec.h>
|
||
|
|
#include <libavformat/avformat.h>
|
||
|
|
#include <libswscale/swscale.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|