10.1_demo/src/e_player_list.c
2026-03-03 09:25:50 +08:00

793 lines
25 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// e_player_list.c
#include "e_player_list.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "e_logger.h"
#include <sys/time.h>
#include "e_conf.h"
#include <errno.h>
#define GET_SCR_WIDTH() (LV_USE_HOR_SIZE)
#define GET_SCR_HEIGHT() (LV_USE_VER_SIZE)
#define USEC_TO_SECONDS (1000 * 1000)
#define TOLERANCE_MS 200 // 误差容忍度200ms
#define retry_max 5 // 播放失败最大重试次数
#define USEC_TO_MS 1000 // 微秒转毫秒
#define MS_TO_USEC 1000 // 毫秒转微秒
#define DECODE_REDUNDANCY_MS 1500 // 解码性能差设备的时间冗余 (1 秒)
static bool sync_play = true; // 是否开启同步播放
static int retry_count = 0;
extern int g_screen_rotation;
// ======================== 内部函数声明 ========================
static void *player_thread_func(void *arg);
static void dis_player_hole(int display_idx);
static int my_play_callback(void *user, int evt, void *info);
static int play_video(VideoPlayer *video_player, const MediaPath *file_path);
static void play_error(VideoPlayer *video_player, const int code);
static void xos_player_set_volume(void *player, int volume);
extern void fbdev_set_hole(int x, int y, int width, int height);
extern void fbdev2_set_hole(int x, int y, int width, int height);
// ======================== 播放器API实现 ========================
QUA_BOOL is_stop = QUA_FALSE;
VideoPlayer *video_player_init(int display_idx)
{
VideoPlayer *player = calloc(1, sizeof(VideoPlayer));
if (!player)
return NULL;
player->display_idx = display_idx;
snprintf(player->display, sizeof(player->display), "id:display%d", display_idx);
player->media_paths = calloc(MAX_PLAYLIST_ITEMS, sizeof(MediaPath *));
if (!player->media_paths)
{
LOGE("播放列表初始化失败:内存分配失败");
free(player);
return NULL;
}
pthread_mutex_init(&player->playlist_mutex, NULL);
pthread_mutex_init(&player->play_mutex, NULL);
pthread_cond_init(&player->play_cond, NULL);
return player;
}
// 设置是否开启同步播放
void video_player_set_sync_play(bool sync)
{
sync_play = sync;
}
/**
* 临时关闭播放器不销毁video_player。
* 清空播放列表,并停止播放
*/
void video_player_close(VideoPlayer *video_player)
{
if (!video_player)
return;
video_player->first_play = true;
if (video_player->stop_requested)
{
// 当下载失败后。黑洞已打开,但是视频并没有开始播放,因此此处添加关闭黑洞
dis_player_hole(video_player->display_idx);
return;
}
LOGI("开始关闭播放器");
// 停止播放线程
video_player->stop_requested = true;
// 发送条件变量信号通知播放线程停止
pthread_mutex_lock(&video_player->play_mutex);
pthread_cond_signal(&video_player->play_cond);
pthread_mutex_unlock(&video_player->play_mutex);
// 等待播放线程结束,设置超时避免死锁
if (video_player->thread_running)
{
LOGI("等待播放线程结束");
struct timespec timeout;
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 3; // 设置3秒超时
int join_result = pthread_timedjoin_np(video_player->player_thread, NULL, &timeout);
if (join_result == ETIMEDOUT)
{
LOGE("播放线程结束超时,强制取消线程");
pthread_cancel(video_player->player_thread);
}
video_player->thread_running = false;
LOGI("播放线程已结束");
}
// 安全销毁播放器
if (video_player->player)
{
LOGI("安全销毁播放器");
// 先尝试正常停止
int stop_result = qua_mm_player_stop(video_player->player);
if (stop_result != 0)
{
LOGE("播放器stop失败ret=%d尝试强制重置", stop_result);
qua_mm_player_reset(video_player->player);
}
// 等待一小段时间确保清理完成
usleep(100 * 1000); // 100ms
qua_mm_player_destroy(video_player->player);
video_player->player = NULL;
LOGI("播放器销毁完成");
}
// 清空播放列表
video_player_clear_playlist(video_player);
LOGI("关闭黑洞显示");
// 关闭黑洞
dis_player_hole(video_player->display_idx);
LOGI("播放器关闭完成");
}
/**
* 销毁播放器
*/
void video_player_destroy(VideoPlayer *video_player)
{
if (!video_player)
return;
// 先关闭播放器
if (!video_player->stop_requested)
{
video_player_close(video_player);
}
// 清理播放列表
pthread_mutex_lock(&video_player->playlist_mutex);
for (int i = 0; i < video_player->media_count; i++)
{
if (video_player->media_paths[i])
{
free(video_player->media_paths[i]);
video_player->media_paths[i] = NULL;
}
}
free(video_player->media_paths); // 释放二级指针数组
video_player->media_paths = NULL;
video_player->media_count = 0;
pthread_mutex_unlock(&video_player->playlist_mutex);
// 确保播放线程已停止
video_player->stop_requested = true;
if (video_player->thread_running)
{
pthread_join(video_player->player_thread, NULL);
}
// 确保播放器已清理
if (video_player->player)
{
LOGI("video_player_destroy中清理剩余播放器");
qua_mm_player_stop(video_player->player);
qua_mm_player_destroy(video_player->player);
video_player->player = NULL;
}
// 关闭黑洞
dis_player_hole(video_player->display_idx);
pthread_mutex_destroy(&video_player->playlist_mutex);
free(video_player);
}
// ======================== 线程安全的播放列表管理 ========================
void video_player_add_item(VideoPlayer *player, const MediaPath *local_path)
{
if (!player || !local_path)
return;
pthread_mutex_lock(&player->playlist_mutex);
if (player->media_count < MAX_PLAYLIST_ITEMS)
{
player->media_paths[player->media_count++] = local_path;
}
pthread_mutex_unlock(&player->playlist_mutex);
}
void video_player_clear_playlist(VideoPlayer *player)
{
if (!player)
return;
pthread_mutex_lock(&player->playlist_mutex);
for (int i = 0; i < player->media_count; i++)
{
if (player->media_paths[i])
{
free(player->media_paths[i]);
player->media_paths[i] = NULL;
}
}
player->media_count = 0;
player->current_index = 0;
player->first_play = true;
pthread_mutex_unlock(&player->playlist_mutex);
}
void video_player_set_double_buffer(VideoPlayer *video_player)
{
if (!video_player)
return;
video_player_clear_playlist(video_player);
// video_player->media_count = 0;
// video_player->current_index = 0;
}
void video_player_set_size(VideoPlayer *video_player, e_player_area area)
{
if (!video_player)
return;
if (area.width > GET_SCR_WIDTH())
{
area.width = GET_SCR_WIDTH();
}
if (area.height > GET_SCR_HEIGHT())
{
area.height = GET_SCR_HEIGHT();
}
video_player->area = area;
video_player_set_hole(video_player);
}
// ======================== 播放控制 ========================
// 获取毫秒时间戳
long long get_current_time_ms()
{
struct timespec ts;
// 获取单调时钟(自系统启动后流逝的时间,不受系统时间修改影响)
if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
{
LOGE("clock_gettime failed");
return 0;
}
return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; // 转换为毫秒
}
int video_player_play(VideoPlayer *player)
{
if (!player)
return -1;
// 确保有可播放的内容
pthread_mutex_lock(&player->playlist_mutex);
bool has_media = (player->media_count > 0);
pthread_mutex_unlock(&player->playlist_mutex);
if (!has_media)
{
LOGW("播放列表为空");
return -1;
}
if (player->thread_running)
{
LOGW("播放线程已在运行");
return -1;
}
player->stop_requested = false;
player->first_play = true;
// player->actual_play_ms = 0; // 重置播放耗时统计
if (pthread_create(&player->player_thread, NULL, player_thread_func, player) == 0)
{
player->thread_running = true;
LOGI("播放线程已启动");
return 0;
}
else
{
LOGE("播放线程创建失败");
return -1;
}
}
void video_player_next(VideoPlayer *player)
{
if (!player)
return;
pthread_mutex_lock(&player->playlist_mutex);
if (player->media_count > 0)
{
player->current_index = (player->current_index + 1) % player->media_count;
}
pthread_mutex_unlock(&player->playlist_mutex);
}
void video_player_set_hole(VideoPlayer *video_player)
{
if (!video_player)
return;
// 直接使用视频区域,不做坐标转换
// 因为 QuaPlayer 库已经处理了旋转后的实际显示位置
int x = video_player->area.x;
int y = video_player->area.y;
int width = video_player->area.width;
int height = video_player->area.height;
if (video_player->display_idx == 0)
{
LOGD("设置视频挖洞区域。x=%d,y=%d,w=%d,h=%d", x, y, width, height);
fbdev_set_hole(x, y, width, height);
}
else
{
LOGD("设置视频挖洞区域。x=%d,y=%d,w=%d,h=%d", x, y, width, height);
fbdev2_set_hole(x, y, width, height);
}
}
// ======================== 内部实现 ========================
static void *player_thread_func(void *arg)
{
VideoPlayer *player = (VideoPlayer *)arg;
while (!player->stop_requested)
{
pthread_mutex_lock(&player->playlist_mutex);
if (player->media_count == 0)
{
pthread_mutex_unlock(&player->playlist_mutex);
LOGW("播放列表为空,停止播放");
break;
}
int index = player->current_index % player->media_count;
MediaPath *media_path = player->media_paths[index];
// char file_path[MAX_URL_LEN];
// strncpy(file_path, player->media_paths[index], MAX_URL_LEN);
pthread_mutex_unlock(&player->playlist_mutex);
if (!media_path)
{
player->current_index = (player->current_index + 1) % player->media_count;
continue;
}
// LOGI("开始播放第 %d 个文件: %s", index, file_path);
is_stop = QUA_FALSE;
int ret = play_video(player, media_path);
if (player->first_play)
{
player->first_play = false;
}
// player->start_time_ms=0; // 重置播放开始时间
if (ret != 0)
{
retry_count++;
if (retry_count > retry_max)
{
// if(player->media_count==1){
// LOGE("播放失败,超过最大重试次数%d:%s",retry_count,file_path);
// break;
// }
LOGE("播放失败,超过最大重试次数%d:%s", retry_count, media_path->path);
break;
}
// else{
LOGE("播放失败,等待1秒后重试%d:%s", retry_count, media_path->path);
// sleep(2);
// }
sleep(1);
// continue;
}
else
{
retry_count = 0;
// LOGD("视频播放完成,理论时长:% lldms实际耗时:% lldms显示器 % d",
// player->video_duration_ms, player->actual_play_ms, player->display_idx);
}
// 播放完成后切换下一个(根据双屏冗余调整切换时机)
// #if DISP_COUNT == 2
// // 双屏模式:确保所有设备都播放完成后再切换(等待冗余时间)
// if (sync_play){
// long long wait_switch_ms = (player->video_duration_ms + DECODE_REDUNDANCY_MS) - player->actual_play_ms;
// if (wait_switch_ms > 0)
// {
// LOGD("双屏模式等待冗余时间:% lldms显示器 % d", wait_switch_ms, player->display_idx);
// usleep(wait_switch_ms * MS_TO_USEC);
// }
// }
// #endif
// 播放完成或失败后切换下一个
player->current_index = (player->current_index + 1) % player->media_count;
// LOGI("开始切换下一个视频");
}
player->thread_running = false;
LOGW("播放线程退出(显示器 % d", player->display_idx);
return NULL;
}
// 计算显示区域根据视频比例和屏幕比例计算显示区域的大小和位置。视频大小必须为8的倍数
static void calculate_display_rect(int img_width, int img_height, lv_area_t *area, qua_rect_t *rect, bool auto_center)
{
int is_screen_landscape = GET_SCR_WIDTH() > GET_SCR_HEIGHT();
int is_image_landscape = img_width > img_height;
int is_adaptive = (is_image_landscape != is_screen_landscape);
// LV_LOG_USER("area [%d %d] area rect size [%d %d]", area->x1, area->y1, area->x2, area->y2);
// LV_LOG_USER("img_width: %d, img_height:%d\n", img_width, img_height);
rect->x = area->x1;
rect->y = area->y1;
rect->width = area->x2 - area->x1 + 1;
rect->height = area->y2 - area->y1 + 1;
if (auto_center)
{
if (true)
{
float screen_ratio = (float)GET_SCR_WIDTH() / GET_SCR_HEIGHT();
float image_ratio = (float)img_width / img_height;
// LV_LOG_USER("screen_ratio =%f,image_ratio=%d", screen_ratio, image_ratio);
if (screen_ratio > image_ratio)
{
rect->width = GET_SCR_HEIGHT() * img_width / img_height;
rect->height = GET_SCR_HEIGHT();
rect->x = (GET_SCR_WIDTH() - rect->width) / 2;
rect->y = 0;
if (rect->x > 0 && rect->x % 2 > 0)
rect->x += 1;
}
else
{
rect->width = GET_SCR_WIDTH();
rect->height = GET_SCR_WIDTH() * img_height / img_width;
rect->x = 0;
rect->y = (GET_SCR_HEIGHT() - rect->height) / 2;
if (rect->y > 0 && rect->y % 2 > 0)
rect->y += 1;
// rect->height = (area->y2- area->y1)/ 4 *4;
// LV_LOG_USER("#######");
}
}
}
else
{
rect->x = area->x1 / 2 * 2;
rect->y = area->y1 / 2 * 2;
rect->width = area->x2 / 8 * 8;
rect->height = area->y2 / 8 * 8;
// LV_LOG_USER("@@@@@@@@@");
}
rect->width = rect->width / 8 * 8;
rect->height = rect->height / 8 * 8;
LOGD("display rect [%d %d %d %d]", rect->x, rect->y, rect->width, rect->height);
}
static int play_video(VideoPlayer *video_player, const MediaPath *media_path)
{
void *player = NULL;
QUA_S32 ret = 0;
QUA_BOOL pos_cb = QUA_TRUE;
if (!video_player->player)
{
if (video_player->display_idx == 0)
{
player = qua_mm_player_create("e_video_play");
}
else
{
player = qua_mm_player_create("e_video_play1");
}
if (player)
{
qua_mm_player_set_parameter(player, KEY_PARAMETER_VO_DISPLAY_ID, (QUA_VOID_PTR)video_player->display);
qua_mm_player_set_parameter(player, KEY_PARAMETER_VO_ROTATE, (QUA_VOID_PTR)&g_screen_rotation);
xos_player_set_volume(player, 0);
qua_mm_player_set_loop(player, QUA_FALSE);
video_player->player = player;
}
}
else
{
player = video_player->player;
// LOGI("reset player");
qua_mm_player_reset(player);
}
if (!player)
{
LOGE("创建播放器失败");
return -1;
}
ret = qua_mm_player_set_data_source(player, media_path->path);
if (ret != 0)
{
LOGE("设置播放源失败 %s", media_path->path);
goto fail;
}
qua_size_t size;
qua_mm_player_get_parameter(player, KEY_PARAMETER_VIDEO_SIZE, (QUA_VOID_PTR)&size);
// LOGI("获取到视频分辨率 %dx%d", size.width, size.height);
if (size.width * size.height > 1920 * 1080)
{
LOGE("视频分辨率超过1920*1080");
goto fail;
}
e_player_area video_area = video_player->area;
lv_area_t area = {0};
area.x1 = video_area.x;
area.x2 = video_area.width;
area.y1 = video_area.y;
area.y2 = video_area.height;
qua_rect_t rect = {0};
calculate_display_rect(size.width, size.height, &area, &rect, false);
qua_rect_t chn_rect;
chn_rect.x = 0;
chn_rect.y = 0;
chn_rect.width = GET_SCR_WIDTH();
chn_rect.height = GET_SCR_HEIGHT();
qua_mm_player_set_parameter(player, KEY_PARAMETER_VO_DISPLAY_RECT, (QUA_VOID_PTR)&chn_rect);
qua_mm_player_set_parameter(player, KEY_PARAMETER_VO_CHN_RECT, (QUA_VOID_PTR)&rect);
ret = qua_mm_player_prepare(player);
if (ret != 0)
{
LOGE("准备播放失败. ret=%d", ret);
goto fail;
}
ret = qua_mm_player_set_callback(player, my_play_callback, video_player);
if (ret != 0)
{
LOGE("设置回调失败. ret=%d", ret);
goto fail;
}
// qua_mm_player_set_parameter(player, KEY_PARAMETER_SET_CURRENT_POSITION_CALLBACK, (QUA_VOID *)&pos_cb);
// 获取视频时长并计算同步起始时间
long long video_duration_us = qua_mm_player_get_durationUs(player); // 转换为毫秒
video_player->video_duration_ms = video_duration_us / 1000; // 转换为毫秒
LOGD("获取视频时长:% lldms显示器 % d", video_player->video_duration_ms, video_player->display_idx);
// 同步播放处理
// QUA_S32 qua_mm_player_seek(QUA_VOID* player, QUA_U64 timeUs);
long long play_position_ms = 0;
if (sync_play && video_player->media_count == 1)
{
// long long currentTime=get_current_time_ms();
// long long minuteMillis= video_player->video_duration_ms+1000;
// if (video_player->first_play)
// {
// // 首次开机时 系统时间尚未校时,因此可能计算的位置不对
// long long viode_duration_ms = video_player->video_duration_ms + 600;
// long long positionMs = get_current_time_ms() % viode_duration_ms; // 当前应该播放的位置(毫秒)
// qua_mm_player_seek(player, positionMs * 1000); // 转换为微秒
// play_position_ms = positionMs; // 使播放器提前结束,防止下次播放时错过开始时间
// }
// else
// {
// long long viode_duration_ms= video_player->video_duration_ms+2000;
long long viode_duration_ms = video_player->video_duration_ms + 1000;
// long long positionMs = get_current_time_ms() % viode_duration_ms;// 当前应该播放的位置(毫秒)
long long t_remainder = get_current_time_ms() % viode_duration_ms;
LOGI("距离视频开始:%lld", t_remainder);
long long tolerance = 300;
while (!is_stop && !video_player->stop_requested)
{
long long currentTime = get_current_time_ms();
long long remainder = currentTime % viode_duration_ms;
if (remainder <= tolerance || (viode_duration_ms - remainder) <= tolerance)
{
break; // 开始播放
}
usleep(50 * MS_TO_USEC);
}
//}
}
video_player_set_hole(video_player);
ret = qua_mm_player_start(player);
if (ret != 0)
{
LOGE("播放器启动失败ret=% d显示器 % d", ret, video_player->display_idx);
goto fail;
}
if (play_position_ms > 0)
{
video_player->last_play_time_ms = get_current_time_ms() - play_position_ms;
}
else
{
video_player->last_play_time_ms = get_current_time_ms(); // 记录播放开始时间
}
// 计算结束时间
// long long end_time = get_current_time_ms() + video_player->video_duration_ms;
long long end_time = video_player->last_play_time_ms + video_player->video_duration_ms;
pthread_mutex_lock(&video_player->play_mutex);
while (get_current_time_ms() < end_time && !video_player->stop_requested)
{
int status = pthread_cond_timedwait(&video_player->play_cond, &video_player->play_mutex, (const struct timespec *)&(struct timespec){.tv_sec = end_time / 1000, .tv_nsec = (end_time % 1000) * 1000000});
if (status == ETIMEDOUT)
{
break;
}
}
pthread_mutex_unlock(&video_player->play_mutex);
// 发送播放通知
// event_play_start(media_path);
// usleep(video_duration_us);
// 使用循环检查方式替代usleep支持提前退出
// long long video_duration_ms = video_duration_us / 1000; // 转换为毫秒
// while (get_current_time_ms() - start_time < video_player->video_duration_ms&&!is_stop) {
// if (video_player->stop_requested) {
// LOGI("播放被提前终止(显示器 %d", video_player->display_idx);
// qua_mm_player_stop(player);
// return 0;
// }
// usleep(20 * 1000); // 20ms检查一次
// }
// 如果播放器中途被结束,则跳过
if (video_player->stop_requested)
{
return 0;
}
// LOGI("视频播放结束01");
// LOGI("~~~~~~~~~~~~~~~~视频播放结束~~~~~~~~~~~~~~~~");
ret = qua_mm_player_stop(player);
if (ret != 0)
{
LOGE("停止播放失败 ");
goto fail;
}
qua_mm_player_reset(player);
return 0;
fail:
if (player)
{
qua_mm_player_destroy(player);
player = NULL;
video_player->player = NULL;
}
return -1;
}
static void xos_player_set_volume(void *player, int volume)
{
// LV_LOG_USER("player volume is %d\n", volume);
qua_mm_player_set_volume(player, volume);
}
/**
* 视频播放器消息回调
*/
static int my_play_callback(void *user, int evt, void *info)
{
VideoPlayer *video_player = (VideoPlayer *)user;
if (evt == PLAYER_EVT_PLAYBACK_COMPLETE)
{
LOGD("收到播放完成回调(显示器 % d", video_player->display_idx);
is_stop = QUA_TRUE;
// pthread_mutex_lock(&video_player->is_stop_mutex);
// video_player->is_stop = QUA_TRUE;
// pthread_mutex_unlock(&video_player->is_stop_mutex);
}
else if (evt == PLAYER_EVT_CURRENT_POSITION)
{
// long pos = (long long)(*(QUA_U64 *)info) / USEC_TO_SECONDS;
// LOGI("video callback event POSITION %d", pos);
}
else
{
LOGD("收到播放器事件:% d显示器 % d", evt, video_player->display_idx);
}
return 0;
}
static void play_error(VideoPlayer *video_player, const int code)
{
LOGE("播放器出现异常,错误码: % d显示器 % d", code, video_player->display_idx);
}
static void dis_player_hole(int display_idx)
{
// 视频播放时,设置挖洞区域。
if (display_idx == 0)
{
fbdev_set_hole(0, 0, 0, 0);
}
else
{
fbdev2_set_hole(0, 0, 0, 0);
}
}
/*
* 检查播放器是否正常
*/
void video_check_play_state(VideoPlayer *player)
{
if (!player)
{
return;
}
if (player->thread_running)
{
LOGI("播放线程正在运行中...");
return;
}
// 判断播放列表是否为空、
pthread_mutex_lock(&player->playlist_mutex);
bool has_media = (player->media_count > 0);
pthread_mutex_unlock(&player->playlist_mutex);
if (!has_media)
{
LOGD("播放线程退出,播放列表为空");
return;
}
if (player->stop_requested)
{
LOGD("播放线程退出,用户主动调用退出");
return;
}
// 检查线程是否已结束
void *value = pthread_getspecific(player->player_thread);
if (value == (void *)1)
{
LOGI("播放器线程已退出");
// 计算距离上次播放时间
long long last_play_time_ms = player->last_play_time_ms;
long long current_time_ms = get_current_time_ms();
// 距离最后一次播放时间
long long time_diff_ms = current_time_ms - last_play_time_ms;
// 判断播放器是否停止;最后播放时间-当前播放视频的总时长+5秒。超过则说明播放意外退出。重新开始播放
// 如果距离上次播放时间小于视频总时长+5秒则等待5秒后重试
if (time_diff_ms > (player->video_duration_ms + 5000))
{
// 尝试开始播放
LOGI("播放线程退出,开始尝试恢复播放");
video_player_play(player);
}
}
else
{
LOGI("播放器线程尚未退出");
}
}