zero_copy_video_player/videodecoder.cpp
2026-08-05 08:56:00 -03:00

288 lines
8.5 KiB
C++

#include "videodecoder.h"
#include <QDebug>
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<double>(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<int64_t>(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<qint64>(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<qint64>(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);
}