#include "videodecoder.h" #include enum AVPixelFormat VideoDecoder::m_hwPixFmt = AV_PIX_FMT_NONE; enum AVPixelFormat VideoDecoder::get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { Q_UNUSED(ctx); const enum AVPixelFormat *p; for (p = pix_fmts; *p != -1; p++) { if (*p == m_hwPixFmt) { return *p; } } qWarning() << "No se pudo acordar el formato de hardware pix format CUDA."; return AV_PIX_FMT_NONE; } VideoDecoder::VideoDecoder(QObject *parent) : QThread(parent) , m_running(false) , m_isPaused(true) , m_seekRequested(false) , m_seekTimeSecs(0.0) , m_duration(0.0) , m_videoWidth(0) , m_videoHeight(0) , m_videoStreamIndex(-1) , m_pausedElapsedMs(0) , m_formatContext(nullptr) , m_codecContext(nullptr) , m_hwDeviceCtx(nullptr) { m_currentFrame = av_frame_alloc(); } VideoDecoder::~VideoDecoder() { stopThread(); closeFile(); if (m_currentFrame) { av_frame_free(&m_currentFrame); m_currentFrame = nullptr; } } bool VideoDecoder::openFile(const QString &fileName) { closeFile(); m_fileName = fileName; // Abrir archivo de entrada if (avformat_open_input(&m_formatContext, fileName.toLocal8Bit().constData(), nullptr, nullptr) < 0) { qWarning() << "No se pudo abrir el archivo de entrada:" << fileName; return false; } // Buscar información de streams if (avformat_find_stream_info(m_formatContext, nullptr) < 0) { qWarning() << "No se pudo encontrar información de streams."; return false; } // Buscar stream de video m_videoStreamIndex = -1; for (unsigned int i = 0; i < m_formatContext->nb_streams; ++i) { if (m_formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { m_videoStreamIndex = i; break; } } if (m_videoStreamIndex == -1) { qWarning() << "No se encontró un stream de video en el archivo."; return false; } // Buscar y abrir decodificador AVCodecParameters *codecParams = m_formatContext->streams[m_videoStreamIndex]->codecpar; const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id); if (!codec) { qWarning() << "No se encontró un decodificador compatible."; return false; } m_codecContext = avcodec_alloc_context3(codec); if (!m_codecContext) { qWarning() << "No se pudo asignar memoria para el contexto del decodificador."; return false; } if (avcodec_parameters_to_context(m_codecContext, codecParams) < 0) { qWarning() << "No se pudieron copiar los parámetros del códec al contexto."; return false; } m_codecContext->thread_count = 0; // auto-detectar hilos // Forzar decodificación por hardware nativa mediante CUDA AVHWDeviceType devType = AV_HWDEVICE_TYPE_CUDA; m_hwPixFmt = AV_PIX_FMT_CUDA; if (av_hwdevice_ctx_create(&m_hwDeviceCtx, devType, nullptr, nullptr, 0) < 0) { qWarning() << "Error: No se pudo crear el dispositivo de hardware CUDA."; m_hwDeviceCtx = nullptr; m_hwPixFmt = AV_PIX_FMT_NONE; return false; } else { qDebug() << "Dispositivo de decodificación CUDA creado con éxito."; m_codecContext->hw_device_ctx = av_buffer_ref(m_hwDeviceCtx); m_codecContext->get_format = get_hw_format; } if (avcodec_open2(m_codecContext, codec, nullptr) < 0) { qWarning() << "No se pudo abrir el códec de video."; return false; } m_videoWidth = m_codecContext->width; m_videoHeight = m_codecContext->height; if (m_formatContext->duration != AV_NOPTS_VALUE) { m_duration = static_cast(m_formatContext->duration) / AV_TIME_BASE; } else { m_duration = 0.0; } m_isPaused = true; m_pausedElapsedMs = 0; m_playbackTimer.invalidate(); return true; } void VideoDecoder::closeFile() { if (m_codecContext) { avcodec_free_context(&m_codecContext); m_codecContext = nullptr; } if (m_formatContext) { avformat_close_input(&m_formatContext); m_formatContext = nullptr; } if (m_hwDeviceCtx) { av_buffer_unref(&m_hwDeviceCtx); m_hwDeviceCtx = nullptr; } { QMutexLocker frameLocker(&m_frameMutex); if (m_currentFrame) { av_frame_unref(m_currentFrame); } } m_videoStreamIndex = -1; m_videoWidth = 0; m_videoHeight = 0; m_duration = 0.0; } void VideoDecoder::play() { QMutexLocker locker(&m_mutex); if (m_isPaused) { m_isPaused = false; m_playbackTimer.restart(); m_waitCond.wakeAll(); } } void VideoDecoder::pause() { QMutexLocker locker(&m_mutex); if (!m_isPaused) { m_isPaused = true; m_pausedElapsedMs += m_playbackTimer.elapsed(); m_playbackTimer.invalidate(); } } void VideoDecoder::seek(double seconds) { QMutexLocker locker(&m_mutex); m_seekTimeSecs = seconds; m_seekRequested = true; m_waitCond.wakeAll(); } void VideoDecoder::stopThread() { { QMutexLocker locker(&m_mutex); m_running = false; m_waitCond.wakeAll(); } wait(); } qint64 VideoDecoder::getPlaybackTimeMs() const { if (m_isPaused || !m_playbackTimer.isValid()) { return m_pausedElapsedMs; } return m_pausedElapsedMs + m_playbackTimer.elapsed(); } bool VideoDecoder::getCurrentFrameRef(AVFrame *destFrame) { QMutexLocker frameLocker(&m_frameMutex); if (!m_currentFrame || m_currentFrame->width == 0) { return false; } av_frame_unref(destFrame); av_frame_ref(destFrame, m_currentFrame); return true; } void VideoDecoder::run() { m_running = true; AVPacket *packet = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); if (!packet || !frame) { qWarning() << "No se pudo asignar memoria para packet/frame."; m_running = false; return; } m_playbackTimer.restart(); m_pausedElapsedMs = 0; while (true) { { QMutexLocker locker(&m_mutex); if (!m_running) break; while (m_isPaused && m_running && !m_seekRequested) { m_waitCond.wait(&m_mutex); } if (!m_running) break; if (m_seekRequested) { int64_t targetTimestamp = static_cast(m_seekTimeSecs / av_q2d(m_formatContext->streams[m_videoStreamIndex]->time_base)); av_seek_frame(m_formatContext, m_videoStreamIndex, targetTimestamp, AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers(m_codecContext); m_pausedElapsedMs = static_cast(m_seekTimeSecs * 1000.0); if (!m_isPaused) { m_playbackTimer.restart(); } m_seekRequested = false; } } int readResult = av_read_frame(m_formatContext, packet); if (readResult < 0) { if (readResult == AVERROR_EOF) { emit playbackFinished(); } QThread::msleep(10); QMutexLocker locker(&m_mutex); m_isPaused = true; m_playbackTimer.invalidate(); continue; } if (packet->stream_index == m_videoStreamIndex) { if (avcodec_send_packet(m_codecContext, packet) >= 0) { while (avcodec_receive_frame(m_codecContext, frame) >= 0) { double ptsSecs = frame->best_effort_timestamp * av_q2d(m_formatContext->streams[m_videoStreamIndex]->time_base); qint64 ptsMs = static_cast(ptsSecs * 1000.0); qint64 playbackTime = getPlaybackTimeMs(); if (ptsMs > playbackTime) { qint64 delay = ptsMs - playbackTime; if (delay < 2000) { QThread::msleep(delay); } } // Guardar el fotograma CUDA en VRAM y avisar que está listo { QMutexLocker frameLocker(&m_frameMutex); av_frame_unref(m_currentFrame); av_frame_ref(m_currentFrame, frame); } emit frameReady(); emit positionChanged(ptsMs); } } } av_packet_unref(packet); } av_packet_free(&packet); av_frame_free(&frame); }