#include "playback.h" #include #include #include /* ============================================================ * 内部数据结构 * ============================================================ */ #define MAX_ACTIVE_PLAYBACKS 32 static PlaybackSession* g_active_playbacks[MAX_ACTIVE_PLAYBACKS]; static int g_playback_count = 0; static int g_playback_id_counter = 0; static pthread_mutex_t g_playback_mutex = PTHREAD_MUTEX_INITIALIZER; /* ============================================================ * 内部函数 * ============================================================ */ static PlaybackSession* find_playback(int playback_id) { for (int i = 0; i < g_playback_count; i++) { if (g_active_playbacks[i] != NULL && g_active_playbacks[i]->playback_id == playback_id) { return g_active_playbacks[i]; } } return NULL; } static int add_playback(PlaybackSession* session) { pthread_mutex_lock(&g_playback_mutex); if (g_playback_count >= MAX_ACTIVE_PLAYBACKS) { pthread_mutex_unlock(&g_playback_mutex); LOG_ERROR("Maximum playback sessions reached"); return FAILURE; } session->playback_id = ++g_playback_id_counter; session->state = PLAYBACK_STATE_IDLE; session->is_running = 0; session->frame_count = 0; pthread_mutex_init(&session->lock, NULL); g_active_playbacks[g_playback_count++] = session; pthread_mutex_unlock(&g_playback_mutex); LOG_INFO("Playback session added: ID=%d, Device=%s, Channel=%d", session->playback_id, session->device_id, session->channel); return SUCCESS; } static void remove_playback(int playback_id) { pthread_mutex_lock(&g_playback_mutex); for (int i = 0; i < g_playback_count; i++) { if (g_active_playbacks[i] != NULL && g_active_playbacks[i]->playback_id == playback_id) { pthread_mutex_destroy(&g_active_playbacks[i]->lock); g_active_playbacks[i] = NULL; g_playback_count--; /* 压缩数组 */ int write_idx = 0; for (int read_idx = 0; read_idx < g_playback_count; read_idx++) { if (g_active_playbacks[read_idx] != NULL) { g_active_playbacks[write_idx++] = g_active_playbacks[read_idx]; } } for (int j = write_idx; j < g_playback_count; j++) { g_active_playbacks[j] = NULL; } LOG_INFO("Playback session removed: ID=%d", playback_id); break; } } pthread_mutex_unlock(&g_playback_mutex); } /* ============================================================ * 公共接口实现 * ============================================================ */ int playback_init(void) { LOG_INFO("Initializing playback module..."); g_playback_count = 0; memset(g_active_playbacks, 0, sizeof(g_active_playbacks)); /* 确保录像目录存在 */ /* 目录由配置决定 */ LOG_INFO("Playback module initialized"); return SUCCESS; } void playback_cleanup(void) { LOG_INFO("Cleaning up playback module..."); playback_stop_all(); for (int i = 0; i < MAX_ACTIVE_PLAYBACKS; i++) { if (g_active_playbacks[i] != NULL) { pthread_mutex_destroy(&g_active_playbacks[i]->lock); if (g_active_playbacks[i]->stream) { stream_destroy_session(g_active_playbacks[i]->stream); } free(g_active_playbacks[i]); g_active_playbacks[i] = NULL; } } g_playback_count = 0; LOG_INFO("Playback module cleaned up"); } PlaybackSession* playback_start(const char* device_id, int channel, time_t start_time, time_t end_time, int speed, PlaybackDataCB data_cb, PlaybackStatusCB status_cb, void* user_data) { if (device_id == NULL || channel < 0) { LOG_ERROR("Invalid parameters"); return NULL; } LOG_INFO("Starting playback: Device=%s, Channel=%d, Start=%ld, End=%ld, Speed=%d", device_id, channel, (long)start_time, (long)end_time, speed); PlaybackSession* session = (PlaybackSession*)calloc(1, sizeof(PlaybackSession)); if (session == NULL) { LOG_ERROR("Failed to allocate playback session"); return NULL; } safe_strcpy(session->device_id, device_id, MAX_DEVICE_ID); session->channel = channel; session->start_time = start_time; session->end_time = end_time; session->speed = speed; session->state = PLAYBACK_STATE_STARTING; /* 创建关联的流会话用于接收回放数据 */ session->stream = stream_create_session(device_id, channel, STREAM_TYPE_SUB, CODEC_TYPE_H264, 1920, 1080, 25, 2048000); if (session->stream == NULL) { LOG_ERROR("Failed to create stream session for playback"); free(session); return NULL; } /* 设置回放数据回调 */ if (data_cb != NULL) { /* 在实际实现中,这里会注册到流媒体库的回调 */ session->stream->internal_handle = (void*)data_cb; } if (add_playback(session) != SUCCESS) { stream_destroy_session(session->stream); free(session); return NULL; } /* 调用HCNetSDK启动回放 * 完整流程: * 1. NET_ESTREAM_StartListenPlayBack - 开启SMS回放监听 * 2. NET_ESTREAM_SetExceptionCallBack - 注册异常回调 * 3. NET_ECMS_StartPlayBack - 向设备发送回放开始请求 * 4. NET_ESTREAM_SetPlayBackDataCB - 注册回放数据回调 * 5. NET_ECMS_StartPushPlayBack - 发送码流传输请求(可选) */ /* 步骤1: 开启SMS回放监听 */ stream_start_playback_listen("0.0.0.0", 7660, NULL, NULL); /* 步骤2: 向设备发送回放开始请求 */ /* NET_ECMS_StartPlayBack(user_id, dev_ip, dev_port, sms_ip, sms_port, channel) */ session->state = PLAYBACK_STATE_PLAYING; session->is_running = 1; /* 触发状态回调 */ if (status_cb) { status_cb(session, PLAYBACK_STATE_PLAYING, user_data); } LOG_INFO("Playback started: ID=%d, Device=%s, Channel=%d", session->playback_id, device_id, channel); return session; } int playback_stop(PlaybackSession* session) { if (session == NULL) return FAILURE; LOG_INFO("Stopping playback: ID=%d, Device=%s, Channel=%d", session->playback_id, session->device_id, session->channel); session->is_running = 0; session->state = PLAYBACK_STATE_STOPPING; /* 步骤7: 停止SMS码流转发 */ /* NET_ESTREAM_StopPlayBack() */ /* 步骤8: 向设备发送回放停止请求 */ /* NET_ECMS_StopPlayBack(user_id) */ /* 停止关联的流 */ if (session->stream) { stream_stop_push(session->stream); stream_destroy_session(session->stream); session->stream = NULL; } /* 步骤9: 停止SMS回放监听(如果没有其他回放在使用) */ /* NET_ESTREAM_StopListenPlayBack() */ remove_playback(session->playback_id); free(session); LOG_INFO("Playback stopped: ID=%d", session->playback_id); return SUCCESS; } int playback_pause(PlaybackSession* session) { if (session == NULL) return FAILURE; if (session->state != PLAYBACK_STATE_PLAYING) { return FAILURE; } LOG_INFO("Pausing playback: ID=%d", session->playback_id); session->state = PLAYBACK_STATE_PAUSED; /* 在实际实现中,这里会暂停码流转发 */ return SUCCESS; } int playback_resume(PlaybackSession* session) { if (session == NULL) return FAILURE; if (session->state != PLAYBACK_STATE_PAUSED) { return FAILURE; } LOG_INFO("Resuming playback: ID=%d", session->playback_id); session->state = PLAYBACK_STATE_PLAYING; /* 在实际实现中,这里会恢复码流转发 */ return SUCCESS; } int playback_get_file_list(const char* device_id, int channel, time_t start_time, time_t end_time, char files[][MAX_PATH_SIZE], int max_files) { if (device_id == NULL || files == NULL) { return FAILURE; } LOG_INFO("Getting file list: Device=%s, Channel=%d, Start=%ld, End=%ld", device_id, channel, (long)start_time, (long)end_time); /* 在实际实现中,这里会调用HCNetSDK查询录像文件 * 例如: NET_DVR_FindFirstFile / NET_DVR_FindNextFile / NET_DVR_FindClose */ /* 模拟实现: 返回空列表 */ (void)start_time; (void)end_time; (void)max_files; return 0; } int playback_download_file(const char* device_id, int channel, const char* file_name, const char* save_path) { if (device_id == NULL || file_name == NULL || save_path == NULL) { return FAILURE; } LOG_INFO("Downloading file: Device=%s, Channel=%d, File=%s, Path=%s", device_id, channel, file_name, save_path); /* 在实际实现中,这里会调用HCNetSDK下载录像文件 * 例如: NET_DVR_PlayBackByTime / NET_DVR_SaveRealData */ return SUCCESS; } int playback_get_active_sessions(PlaybackSession* sessions[], int max_sessions) { int count = 0; pthread_mutex_lock(&g_playback_mutex); for (int i = 0; i < g_playback_count && count < max_sessions; i++) { if (g_active_playbacks[i] != NULL) { sessions[count++] = g_active_playbacks[i]; } } pthread_mutex_unlock(&g_playback_mutex); return count; } void playback_stop_all(void) { pthread_mutex_lock(&g_playback_mutex); for (int i = 0; i < g_playback_count; i++) { if (g_active_playbacks[i] != NULL) { g_active_playbacks[i]->is_running = 0; if (g_active_playbacks[i]->stream) { stream_stop_push(g_active_playbacks[i]->stream); } } } pthread_mutex_unlock(&g_playback_mutex); } PlaybackState playback_get_state(PlaybackSession* session) { if (session == NULL) return PLAYBACK_STATE_ERROR; return session->state; }