#include "read_thread.h" #include "AVPlayer2/playercontroller.h" #include #include Q_LOGGING_CATEGORY(playerControllerReadThread, "player.controller.ReadThread") extern int infinite_buffer; extern int64_t start_time; static int64_t duration = AV_NOPTS_VALUE; ReadThread::ReadThread(VideoState* pState) : m_pPlayData(pState) { qCDebug(playerControllerReadThread) << "[ReadThread] constructed, pState:" << (void*) pState; } ReadThread::~ReadThread() { qCDebug(playerControllerReadThread) << "[ReadThread] destructed."; stop(); join(); } void ReadThread::set_video_state(VideoState* pState) { assert(pState); m_pPlayData = pState; } int ReadThread::loop_read() { int ret = -1; VideoState* is = m_pPlayData; AVPacket* pkt = nullptr; int pkt_in_play_range = 0; int64_t stream_start_time = 0; int64_t pkt_ts = 0; // 重连计数器和配置 static int reconnect_count = 0; static const int max_fast_reconnect_attempts = 3; // 快速重连次数 static const int max_normal_reconnect_attempts = 10; // 正常重连次数 static bool infinite_reconnect_mode = false; // 无限重连模式 if (!is) return ret; pkt = av_packet_alloc(); if (!pkt) { av_log(nullptr, AV_LOG_FATAL, "Could not allocate packet.\n"); ret = AVERROR(ENOMEM); return ret; } qCDebug(playerControllerReadThread) << "[ReadThread] loop_read start, file:" << (is && is->ic ? is->ic->url : "null"); is->read_thread_exit = 0; for (;;) { if (isExit()) { qCDebug(playerControllerReadThread) << "[ReadThread] m_exit set, break."; break; } if (is->abort_request) { qCDebug(playerControllerReadThread) << "[ReadThread] abort_request set, break."; break; } if (is->paused != is->last_paused) { is->last_paused = is->paused; if (is->paused) is->read_pause_return = av_read_pause(is->ic); else av_read_play(is->ic); } if (is->seek_req) { qCDebug(playerControllerReadThread) << "[ReadThread] seek_req, seek_pos:" << is->seek_pos << ", seek_rel:" << is->seek_rel << ", seek_flags:" << is->seek_flags; int64_t seek_target = is->seek_pos; int64_t seek_min = is->seek_rel > 0 ? seek_target - is->seek_rel + 2 : INT64_MIN; int64_t seek_max = is->seek_rel < 0 ? seek_target - is->seek_rel - 2 : INT64_MAX; ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags); if (ret < 0) { av_log(nullptr, AV_LOG_ERROR, "%s: error while seeking\n", is->ic->url); qCWarning(playerControllerReadThread) << "[ReadThread] avformat_seek_file failed, ret:" << ret; } else { qCDebug(playerControllerReadThread) << "[ReadThread] avformat_seek_file success, flush queues."; if (is->audio_stream >= 0) packet_queue_flush(&is->audioq); if (is->subtitle_stream >= 0) packet_queue_flush(&is->subtitleq); if (is->video_stream >= 0) packet_queue_flush(&is->videoq); if (is->seek_flags & AVSEEK_FLAG_BYTE) { set_clock(&is->extclk, NAN, 0); } else { set_clock(&is->extclk, seek_target / (double) AV_TIME_BASE, 0); } } is->seek_req = 0; is->queue_attachments_req = 1; is->eof = 0; if (is->paused) step_to_next_frame(is); } if (is->queue_attachments_req) { qCDebug(playerControllerReadThread) << "[ReadThread] queue_attachments_req, video_st:" << (void*) is->video_st; if (is->video_st && is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC) { ret = av_packet_ref(pkt, &is->video_st->attached_pic); if (ret < 0) break; packet_queue_put(&is->videoq, pkt); packet_queue_put_nullpacket(&is->videoq, pkt, is->video_stream); } is->queue_attachments_req = 0; } /* if the queue are full, no need to read more */ if (infinite_buffer < 1 && (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE || (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq) && stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq) && stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq)))) { qCDebug(playerControllerReadThread) << "[ReadThread] queues full, waiting... audioq:" << is->audioq.size << ", videoq:" << is->videoq.size << ", subtitleq:" << is->subtitleq.size; std::unique_lock lock(m_mutex); m_cv.wait_for(lock, std::chrono::milliseconds(10), [this, is] { return isExit() || is->abort_request; }); continue; } if (!is->paused && (!is->audio_st || (is->auddec.finished == is->audioq.serial && frame_queue_nb_remaining(&is->sampq) == 0)) && (!is->video_st || (is->viddec.finished == is->videoq.serial && frame_queue_nb_remaining(&is->pictq) == 0))) { if (is->loop) { stream_seek(is, 0, 0, 0); } else { // autoexit break; } } ret = av_read_frame(is->ic, pkt); if (ret < 0) { char buf[256] = {0}; av_strerror(ret, buf, 256); qCWarning(playerControllerReadThread) << "[ReadThread] av_read_frame failed, ret:" << ret << ", eof:" << is->eof << ", pb_error:" << (is->ic->pb ? is->ic->pb->error : 0) << "error:" << QString::fromUtf8(buf); // 统一处理EOF和I/O错误 - 无感重连策略 bool shouldAttemptReconnect = false; bool isEOFError = (ret == AVERROR_EOF || avio_feof(is->ic->pb)) && !is->eof; bool isIOError = is->ic->pb && is->ic->pb->error; // 增强错误检测:检查更多可能的网络错误 bool isNetworkError = (ret == AVERROR(EAGAIN) || ret == AVERROR(ETIMEDOUT) || ret == AVERROR(ECONNRESET) || ret == AVERROR(EPIPE) || ret == AVERROR(ECONNABORTED) || ret == -5); // -5 是常见的I/O错误 if (isEOFError || isIOError || isNetworkError) { if (isEOFError) { qCDebug(playerControllerReadThread) << "[ReadThread] EOF reached for stream:" << is->filename; } if (isIOError) { qCWarning(playerControllerReadThread) << "[ReadThread] IO error detected, error code:" << is->ic->pb->error; } if (isNetworkError) { qCWarning(playerControllerReadThread) << "[ReadThread] Network error detected, return code:" << ret; } // 检查是否为实时流(RTMP、RTSP等) bool isRealTimeStream = false; if (is->filename) { QString filename = QString::fromUtf8(is->filename); isRealTimeStream = filename.startsWith("rtmp://") || filename.startsWith("rtsp://") || filename.startsWith("rtp://") || filename.startsWith("udp://") || filename.startsWith("tcp://") || filename.startsWith("http://") || filename.startsWith("https://"); } if (isRealTimeStream) { reconnect_count++; QString errorType = isEOFError ? "EOF" : (isIOError ? "I/O error" : "Network error"); // 分层重连策略 bool shouldContinueReconnect = false; int wait_time_ms = 0; if (reconnect_count <= max_fast_reconnect_attempts) { // 快速重连阶段:短间隔,快速恢复 wait_time_ms = 500 * reconnect_count; // 0.5s, 1s, 1.5s shouldContinueReconnect = true; qCWarning(playerControllerReadThread) << "[ReadThread] Fast reconnection" << errorType << "attempt" << reconnect_count << "/" << max_fast_reconnect_attempts; } else if (reconnect_count <= max_normal_reconnect_attempts) { // 正常重连阶段:较长间隔 wait_time_ms = 2000 + (reconnect_count - max_fast_reconnect_attempts) * 1000; // 2s, 3s, 4s... shouldContinueReconnect = true; qCWarning(playerControllerReadThread) << "[ReadThread] Normal reconnection" << errorType << "attempt" << reconnect_count << "/" << max_normal_reconnect_attempts; } else { // 无限重连模式:长间隔,但永不放弃 infinite_reconnect_mode = true; wait_time_ms = 10000; // 固定10秒间隔 shouldContinueReconnect = true; qCWarning(playerControllerReadThread) << "[ReadThread] Infinite reconnection mode" << errorType << "attempt" << reconnect_count << "(never give up)"; } if (shouldContinueReconnect) { // 清空当前的错误状态 if (is->ic && is->ic->pb) { is->ic->pb->error = 0; is->ic->pb->eof_reached = 0; } // 无感重连:不设置EOF状态,保持播放器运行 // 这样即使网络中断,播放器界面也不会退出 qCDebug(playerControllerReadThread) << "[ReadThread] Seamless reconnection: waiting" << wait_time_ms << "ms..."; std::unique_lock lock(m_mutex); m_cv.wait_for(lock, std::chrono::milliseconds(wait_time_ms), [this, is] { return isExit() || is->abort_request; }); if (!isExit() && !is->abort_request) { qCDebug(playerControllerReadThread) << "[ReadThread] Attempting seamless reconnection..."; continue; // 继续循环,永不放弃,真正的无感重连 } } } else { // 非实时流:传统处理方式 qCDebug(playerControllerReadThread) << "[ReadThread] File stream error, send null packets."; // 发送null packets并设置EOF if (isEOFError) { if (is->video_stream >= 0) packet_queue_put_nullpacket(&is->videoq, pkt, is->video_stream); if (is->audio_stream >= 0) packet_queue_put_nullpacket(&is->audioq, pkt, is->audio_stream); if (is->subtitle_stream >= 0) packet_queue_put_nullpacket(&is->subtitleq, pkt, is->subtitle_stream); is->eof = 1; } // 对于I/O错误,直接退出循环 if (isIOError) { break; } } } // 错误处理已经统一到上面的逻辑中 std::unique_lock lock(m_mutex); m_cv.wait_for(lock, std::chrono::milliseconds(10), [this, is] { return isExit() || is->abort_request; }); continue; } else { is->eof = 0; // 成功读取数据时重置重连计数器和状态 if (reconnect_count > 0) { if (infinite_reconnect_mode) { qCInfo(playerControllerReadThread) << "[ReadThread] Stream recovered from infinite reconnection mode after" << reconnect_count << "attempts"; infinite_reconnect_mode = false; } else { qCDebug(playerControllerReadThread) << "[ReadThread] Stream recovered, resetting reconnection counter"; } reconnect_count = 0; } qCDebug(playerControllerReadThread) << "[ReadThread] av_read_frame success, stream_index:" << pkt->stream_index << ", pts:" << pkt->pts; } /* check if packet is in play range specified by user, then queue, otherwise discard */ stream_start_time = is->ic->streams[pkt->stream_index]->start_time; pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts; pkt_in_play_range = duration == AV_NOPTS_VALUE || (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) * av_q2d(is->ic->streams[pkt->stream_index]->time_base) - (double) (start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000 <= ((double) duration / 1000000); if (pkt->stream_index == is->audio_stream && pkt_in_play_range) { qCDebug(playerControllerReadThread) << "[ReadThread] put audio packet, pts:" << pkt->pts; packet_queue_put(&is->audioq, pkt); } else if (pkt->stream_index == is->video_stream && pkt_in_play_range && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) { qCDebug(playerControllerReadThread) << "[ReadThread] put video packet, pts:" << pkt->pts; packet_queue_put(&is->videoq, pkt); } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) { qCDebug(playerControllerReadThread) << "[ReadThread] put subtitle packet, pts:" << pkt->pts; packet_queue_put(&is->subtitleq, pkt); } else { qCDebug(playerControllerReadThread) << "[ReadThread] drop packet, stream_index:" << pkt->stream_index; av_packet_unref(pkt); } } qCWarning(playerControllerReadThread) << "[ReadThread] loop_read about to exit, eof:" << is->eof << ", audioq.size:" << is->audioq.size << ", videoq.size:" << is->videoq.size << ", subtitleq.size:" << is->subtitleq.size << ", abort_request:" << is->abort_request << ", m_exit:" << (m_exit ? m_exit->load() : -1); qCDebug(playerControllerReadThread) << "[ReadThread] loop_read exit, set read_thread_exit = -1"; is->read_thread_exit = -1; if (!is->ic) avformat_close_input(&is->ic); av_packet_free(&pkt); return 0; } void ReadThread::run() { qCDebug(playerControllerReadThread) << "[ReadThread] run start"; int ret = loop_read(); qCDebug(playerControllerReadThread) << "[ReadThread] run end, loop_read ret:" << ret; // 可加日志输出 }