288 lines
8.5 KiB
C++
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);
|
|
}
|