Windonws 视频存储及播放

使用的FFMpeng库,库连接:https://download.csdn.net/download/yuchunhai321/92903428

使用的QT工程,编译器MSVC2015,编译成功,运行时需要将库里bin文件下的文件拷到可执行的文件目录下

工程源码:

pro文件中增加

INCLUDEPATH += $$PWD/FFMpegH264/lib/ffmpeg4.0.1/include

LIBS += -L$$PWD/FFMpegH264/lib/ffmpeg4.0.1/lib/win32/ -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
#ifndef VIDEO_RECORDER_H
#define VIDEO_RECORDER_H

#include <QObject>
#include <QImage>
#include <QThread>
#include <QMutex>
#include <QQueue>
#include <QWaitCondition>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
#include <libavdevice/avdevice.h>
}

class VideoRecorder : public QObject
{
    Q_OBJECT
public:
    explicit VideoRecorder(QObject *parent = nullptr);
    ~VideoRecorder();

    // 设置编码参数(可选,有默认值)
    void setVideoParameters(int width, int height, int fps = 25, int bitrate = 2000000);

    // 开始录制:传入输出文件名
    bool startRecording(const QString& filename);

    // 停止录制
    void stopRecording();

    // 添加一帧图片数据
    void addFrame(const QImage& image);
    void addFrame(const QByteArray& imageData);

signals:
    // 录制状态信号
    void recordingStarted();
    void recordingStopped(bool success);
    void errorOccurred(const QString& error);

    // 进度信号(可选)
    void frameEncoded(int frameCount);

private:
    // FFmpeg 相关成员
    AVFormatContext* m_formatCtx = nullptr;
    AVCodecContext* m_codecCtx = nullptr;
    AVStream* m_stream = nullptr;
    SwsContext* m_swsCtx = nullptr;
    AVFrame* m_frame = nullptr;

    // 视频参数
    int m_width = 1920;
    int m_height = 1080;
    int m_fps = 25;
    int m_bitrate = 2000000;  // 2 Mbps
    AVPixelFormat m_pixelFormat = AV_PIX_FMT_YUV420P;

    // 编码状态
    bool m_isRecording = false;
    int64_t m_ptsCounter = 0;
    int m_videoStreamIndex = -1;

    // 线程安全的数据队列
    QQueue<QImage> m_frameQueue;
    QMutex m_mutex;
    QWaitCondition m_condition;
    bool m_stopRequested = false;

    // 工作线程
    QThread* m_workerThread = nullptr;

    // 内部函数
    bool initializeFFmpeg(const QString& filename);
    void cleanupFFmpeg();
    bool encodeFrame(const QImage& image);
    void flushEncoder();
    bool convertImageToFrame(const QImage& image, AVFrame* frame);

private slots:
    void processFrames();  // 工作线程的主循环
};

class VideoSaver : public QObject
{
    Q_OBJECT
public:
    explicit VideoSaver(QObject *parent = nullptr);
    ~VideoSaver();

    // 打开视频文件准备保存
    bool open(const QString &filename, int width, int height, double fps = 25.0);

    // 写入一帧QImage图像
    bool writeFrame(const QImage &image);

    // 关闭文件,写入尾部信息
    void close();

private:
    bool initCodec(int width, int height, double fps);
    bool convertQImageToAVFrame(const QImage &image, AVFrame *frame);

    AVFormatContext *m_formatCtx;//全局上下文 用于打开视频文件
    AVCodecContext *m_codecCtx;//启动编解码器的上下文
    AVStream *m_stream;//媒体流
    AVFrame *m_frame;//解码后的原始数据
    AVPacket *m_packet;//编码后的压缩数据
    SwsContext *m_swsCtx;//图像尺寸/格式转换器

    int m_frameIndex;
    bool m_isOpen;
};

class VideoPlayer : public QObject
{
    Q_OBJECT

public:
    explicit VideoPlayer(QObject *parent = nullptr);
    ~VideoPlayer();

    // 打开视频文件
    bool open(const QString &filename);

    // 获取下一帧(返回QImage,调用者负责显示)
    bool getNextFrame(QImage &image);

    // 关闭并释放资源
    void close();

    // 获取视频信息
    int getWidth() const { return m_width; }
    int getHeight() const { return m_height; }
    double getFps() const { return m_fps; }
    int getTotalFrames() const { return m_totalFrames; }
    bool isOpen() const { return m_isOpen; }

    // 重置到开头
    void reset();

private:
    bool initDecoder();
    bool initSwsContext();
    bool decodeNextFrame();
    void cleanup();
    bool readNextPacket();

private:
    AVFormatContext *m_formatCtx;
    AVCodecContext *m_codecCtx;
    AVCodec *m_codec;
    AVFrame *m_frame;
    AVFrame *m_frameRGB;
    AVPacket *m_packet;
    struct SwsContext *m_swsCtx;

    int m_streamIndex;
    int m_width;
    int m_height;
    double m_fps;
    int m_totalFrames;
    bool m_isOpen;
    bool m_endOfFile;

    uint8_t *m_rgbBuffer;
    QImage m_currentImage;
};

#endif // VIDEO_RECORDER_H
#include "videorecorder.h"
#include <QDebug>
#include <QDir>

VideoRecorder::VideoRecorder(QObject *parent) : QObject(parent)
{
    // 初始化 FFmpeg 库
    avformat_network_init();

    // 重要:先移除父对象,然后才能移动线程
    this->setParent(nullptr);

    // 创建工作线程
    m_workerThread = new QThread(this);  // 这个可以保留父对象
    this->moveToThread(m_workerThread);

    // 连接信号和槽
    connect(m_workerThread, &QThread::started, this, &VideoRecorder::processFrames);
    connect(m_workerThread, &QThread::finished, m_workerThread, &QThread::deleteLater);

    m_workerThread->start();
}

VideoRecorder::~VideoRecorder()
{
    stopRecording();

    if (m_workerThread) {
        m_workerThread->quit();
        // 注意:这里不能直接调用 wait(),因为当前对象可能在主线程
        // 而 m_workerThread 是子对象,Qt 会自动处理
        // 但为了安全,可以等待一段时间
        if (!m_workerThread->wait(3000)) {
            qDebug() << "Thread didn't stop gracefully, terminating...";
            m_workerThread->terminate();
            m_workerThread->wait();
        }
    }

    cleanupFFmpeg();
    avformat_network_deinit();
}

void VideoRecorder::setVideoParameters(int width, int height, int fps, int bitrate)
{
    m_width = width;
    m_height = height;
    m_fps = fps;
    m_bitrate = bitrate;
}

bool VideoRecorder::startRecording(const QString& filename)
{
    QMutexLocker locker(&m_mutex);

    if (m_isRecording) {
        emit errorOccurred("Already recording");
        return false;
    }

    if (!initializeFFmpeg(filename)) {
        emit errorOccurred("Failed to initialize FFmpeg");
        return false;
    }

    m_isRecording = true;
    m_stopRequested = false;
    m_ptsCounter = 0;

    emit recordingStarted();
    qDebug() << "Recording started:" << filename;

    return true;
}

void VideoRecorder::stopRecording()
{
    if (!m_isRecording) {
        return;
    }

    // 标记停止请求
    {
        QMutexLocker locker(&m_mutex);
        m_stopRequested = true;
        m_condition.wakeAll();
    }
}

void VideoRecorder::addFrame(const QImage& image)
{
    if (!m_isRecording) {
        return;
    }

    // 检查队列大小,避免内存爆炸
    {
        QMutexLocker locker(&m_mutex);
        if (m_frameQueue.size() > 300) {  // 最多缓存300帧
            return;
        }
    }

    // 将图片转换为标准格式
    QImage standardized = image;
    if (standardized.format() != QImage::Format_RGB32 &&
        standardized.format() != QImage::Format_ARGB32) {
        standardized = standardized.convertToFormat(QImage::Format_RGB32);
    }

    // 缩放图片到目标尺寸
    if (standardized.width() != m_width || standardized.height() != m_height) {
        standardized = standardized.scaled(m_width, m_height,
                                          Qt::IgnoreAspectRatio,
                                          Qt::SmoothTransformation);
    }

    QMutexLocker locker(&m_mutex);
    m_frameQueue.enqueue(standardized);
    m_condition.wakeOne();  // 唤醒工作线程
}

void VideoRecorder::addFrame(const QByteArray &imageData)
{
    // 将 QByteArray 转换为 QImage
    QImage image;
    if (!image.loadFromData(imageData)) {
        qDebug() << "Error: Failed to load image from QByteArray";
        return ;
    }

    // 调用原有的 addFrame 函数
    return addFrame(image);
}

bool VideoRecorder::initializeFFmpeg(const QString& filename)
{
    int ret = 0;

    // 确保先清理之前的资源
    cleanupFFmpeg();

    // 1. 创建输出上下文
    ret = avformat_alloc_output_context2(&m_formatCtx, nullptr, "mp4",
                                          filename.toStdString().c_str());
    if (ret < 0 || !m_formatCtx) {
        char errbuf[256];
        av_strerror(ret, errbuf, sizeof(errbuf));
        qDebug() << "Failed to create output context:" << errbuf;
        return false;
    }

    // 2. 查找编码器 (H.264)
    const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        qDebug() << "H.264 encoder not found, trying MPEG-4...";
        codec = avcodec_find_encoder(AV_CODEC_ID_MPEG4);
        if (!codec) {
            qDebug() << "No suitable encoder found";
            return false;
        }
    }

    // 3. 创建视频流
    m_stream = avformat_new_stream(m_formatCtx, codec);
    if (!m_stream) {
        qDebug() << "Failed to create video stream";
        return false;
    }
    m_videoStreamIndex = m_stream->index;

    // 4. 创建编码器上下文
    m_codecCtx = avcodec_alloc_context3(codec);
    if (!m_codecCtx) {
        qDebug() << "Failed to allocate codec context";
        return false;
    }

    // 5. 设置编码参数
    m_codecCtx->codec_id = codec->id;
    m_codecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    m_codecCtx->width = m_width;
    m_codecCtx->height = m_height;
    m_codecCtx->time_base = AVRational{1, m_fps};
    m_codecCtx->framerate = AVRational{m_fps, 1};
    m_codecCtx->pix_fmt = m_pixelFormat;
    m_codecCtx->bit_rate = m_bitrate;
    m_codecCtx->gop_size = m_fps * 2;

    // 设置编码质量(对 MPEG-4 有效)
    if (codec->id == AV_CODEC_ID_MPEG4) {
        m_codecCtx->qmin = 10;
        m_codecCtx->qmax = 31;
    }

    // 6. 设置全局头(MP4 需要)
    if (m_formatCtx->oformat->flags & AVFMT_GLOBALHEADER) {
        m_codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    // 7. 打开编码器
    // 添加编码器选项,减少内部缓冲
    AVDictionary* opts = nullptr;
    av_dict_set(&opts, "tune", "zerolatency", 0);  // 零延迟模式
    av_dict_set(&opts, "preset", "ultrafast", 0);  // 最快编码
    av_dict_set(&opts, "rc_lookahead", "0", 0);    // 无前瞻缓冲

    ret = avcodec_open2(m_codecCtx, codec, &opts);
    if (ret < 0) {
        char errbuf[256];
        av_strerror(ret, errbuf, sizeof(errbuf));
        qDebug() << "Failed to open codec:" << errbuf;
        return false;
    }

    // 8. 将编码器参数复制到流
    ret = avcodec_parameters_from_context(m_stream->codecpar, m_codecCtx);
    if (ret < 0) {
        qDebug() << "Failed to copy codec parameters";
        return false;
    }

    // 9. 创建 AVFrame
    m_frame = av_frame_alloc();
    if (!m_frame) {
        qDebug() << "Failed to allocate frame";
        return false;
    }
    m_frame->format = m_codecCtx->pix_fmt;
    m_frame->width = m_width;
    m_frame->height = m_height;
    ret = av_frame_get_buffer(m_frame, 32);
    if (ret < 0) {
        qDebug() << "Failed to allocate frame buffer";
        return false;
    }

    // 10. 创建图像转换上下文 (RGB -> YUV)
    m_swsCtx = sws_getContext(m_width, m_height, AV_PIX_FMT_RGB32,
                              m_width, m_height, m_pixelFormat,
                              SWS_BILINEAR, nullptr, nullptr, nullptr);
    if (!m_swsCtx) {
        qDebug() << "Failed to create sws context";
        return false;
    }

    // 11. 打开输出文件
    ret = avio_open(&m_formatCtx->pb, filename.toStdString().c_str(), AVIO_FLAG_WRITE);
    if (ret < 0) {
        char errbuf[256];
        av_strerror(ret, errbuf, sizeof(errbuf));
        qDebug() << "Failed to open output file:" << errbuf;
        return false;
    }

    // 12. 写入文件头
    ret = avformat_write_header(m_formatCtx, nullptr);
    if (ret < 0) {
        char errbuf[256];
        av_strerror(ret, errbuf, sizeof(errbuf));
        qDebug() << "Failed to write header:" << errbuf;
        return false;
    }

    return true;
}

void VideoRecorder::cleanupFFmpeg()
{
    if (m_swsCtx) {
        sws_freeContext(m_swsCtx);
        m_swsCtx = nullptr;
    }

    if (m_frame) {
        av_frame_free(&m_frame);
        m_frame = nullptr;
    }

    if (m_codecCtx) {
        avcodec_free_context(&m_codecCtx);
        m_codecCtx = nullptr;
    }

    if (m_formatCtx) {
        if (m_formatCtx->pb) {
            avio_closep(&m_formatCtx->pb);
        }
        avformat_free_context(m_formatCtx);
        m_formatCtx = nullptr;
    }
}

bool VideoRecorder::encodeFrame(const QImage& image)
{
    if (!m_isRecording || !m_codecCtx) {
        return false;
    }

    // 1. 转换图片数据到 AVFrame
    if (!convertImageToFrame(image, m_frame)) {
        return false;
    }

    // 2. 设置 PTS
    m_frame->pts = m_ptsCounter++;

    // 3. 发送帧到编码器
    int ret = avcodec_send_frame(m_codecCtx, m_frame);
    if (ret < 0) {
        char errbuf[256];
        av_strerror(ret, errbuf, sizeof(errbuf));
        qDebug() << "Error sending frame to encoder:" << errbuf;
        return false;
    }

    // 4. 接收编码后的数据包
    AVPacket* pkt = av_packet_alloc();
    bool success = true;

    while (true) {
        ret = avcodec_receive_packet(m_codecCtx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            char errbuf[256];
            av_strerror(ret, errbuf, sizeof(errbuf));
            qDebug() << "Error receiving packet:" << errbuf;
            success = false;
            break;
        }

        // 5. 转换时间戳
        pkt->stream_index = m_videoStreamIndex;
        av_packet_rescale_ts(pkt, m_codecCtx->time_base,
                            m_stream->time_base);

        // 6. 写入文件
        ret = av_interleaved_write_frame(m_formatCtx, pkt);
        if (ret < 0) {
            char errbuf[256];
            av_strerror(ret, errbuf, sizeof(errbuf));
            qDebug() << "Error writing packet:" << errbuf;
            success = false;
        }

        av_packet_unref(pkt);
    }
    av_packet_free(&pkt);

    if (success) {
        emit frameEncoded(m_ptsCounter);
    }

    return success;
}

bool VideoRecorder::convertImageToFrame(const QImage& image, AVFrame* frame)
{
    // 确保 frame 可写
    if (av_frame_make_writable(frame) < 0) {
        return false;
    }

    // 准备源数据
    uint8_t* srcData[1] = { const_cast<uint8_t*>(image.bits()) };
    int srcLinesize[1] = { static_cast<int>(image.bytesPerLine()) };

    // 执行转换
    sws_scale(m_swsCtx, srcData, srcLinesize, 0, m_height,
              frame->data, frame->linesize);

    return true;
}

void VideoRecorder::flushEncoder()
{
    if (!m_codecCtx) {
        return;
    }

    // 发送 NULL 帧刷新编码器
    int ret = avcodec_send_frame(m_codecCtx, nullptr);
    if (ret < 0) {
        return;
    }

    // 接收所有剩余的编码数据包
    AVPacket* pkt = av_packet_alloc();

    while (true) {
        ret = avcodec_receive_packet(m_codecCtx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            break;
        }

        pkt->stream_index = m_videoStreamIndex;
        av_packet_rescale_ts(pkt, m_codecCtx->time_base,
                            m_stream->time_base);
        av_interleaved_write_frame(m_formatCtx, pkt);
        av_packet_unref(pkt);
    }
    av_packet_free(&pkt);

    // 写入文件尾部
    if (m_formatCtx) {
        av_write_trailer(m_formatCtx);
    }
}

void VideoRecorder::processFrames()
{
    while (true) {
        QImage frame;

        {
            QMutexLocker locker(&m_mutex);

            // 等待条件:有帧要处理,或者被要求停止
            while (m_frameQueue.isEmpty() && !m_stopRequested) {
                m_condition.wait(&m_mutex);
            }

            // 检查退出条件
            if (m_stopRequested && m_frameQueue.isEmpty()) {
                break;
            }

            // 取出一帧
            if (!m_frameQueue.isEmpty()) {
                frame = m_frameQueue.dequeue();
            }
        }

        // 编码帧
        if (m_isRecording && !frame.isNull()) {
            if (!encodeFrame(frame)) {
                qDebug() << "Failed to encode frame";
            }
        }
    }

    // 清理:刷新编码器并关闭文件
    if (m_isRecording) {
        qDebug() << "Flushing encoder...";
        flushEncoder();
        cleanupFFmpeg();
        m_isRecording = false;
        emit recordingStopped(true);
        qDebug() << "Recording finished, total frames:" << m_ptsCounter;
    }
}

#include <QDebug>

VideoSaver::VideoSaver(QObject *parent)
    : QObject(parent)
    , m_formatCtx(nullptr)
    , m_codecCtx(nullptr)
    , m_stream(nullptr)
    , m_frame(nullptr)
    , m_packet(nullptr)
    , m_swsCtx(nullptr)
    , m_frameIndex(0)
    , m_isOpen(false)
{
    // 初始化FFmpeg网络模块(某些格式需要)
    avformat_network_init();
}

VideoSaver::~VideoSaver()
{
    close();
}

bool VideoSaver::open(const QString &filename, int width, int height, double fps)
{
    if (m_isOpen) {
        qWarning() << "Video saver is already open";
        return false;
    }

    // 1. 分配输出上下文
    QString filePath = filename;
    // 输出上下文指针的指针  指定输出格式  输出格式的名称字符串  输出文件的路径或 URL
    int ret = avformat_alloc_output_context2(&m_formatCtx, nullptr, nullptr,
                                              filePath.toUtf8().constData());
    if (ret < 0 || !m_formatCtx) {
        qWarning() << "Failed to allocate output context";
        return false;
    }

    // 2. 初始化编码器和流
    if (!initCodec(width, height, fps)) {
        qWarning() << "Failed to init codec";
        return false;
    }

    // 3. 打开输出文件
    if (!(m_formatCtx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&m_formatCtx->pb, filePath.toUtf8().constData(), AVIO_FLAG_WRITE);
        if (ret < 0) {
            qWarning() << "Failed to open output file";
            return false;
        }
    }

    // 4. 写入文件头
    ret = avformat_write_header(m_formatCtx, nullptr);
    if (ret < 0) {
        qWarning() << "Failed to write header";
        return false;
    }

    m_isOpen = true;
    qDebug() << "Video saver opened:" << filename << "size:" << width << "x" << height << "fps:" << fps;
    return true;
}

bool VideoSaver::initCodec(int width, int height, double fps)
{
    // 查找H.264编码器(或MPEG4等其他编码器)
    const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        // 如果H.264不可用,回退到MPEG4
        codec = avcodec_find_encoder(AV_CODEC_ID_MPEG4);
        if (!codec) {
            qWarning() << "Codec not found";
            return false;
        }
    }

    // 创建视频流
    m_stream = avformat_new_stream(m_formatCtx, codec);
    if (!m_stream) {
        qWarning() << "Failed to create stream";
        return false;
    }
    m_stream->id = m_formatCtx->nb_streams - 1;

    // 分配编码器上下文
    m_codecCtx = avcodec_alloc_context3(codec);
    if (!m_codecCtx) {
        qWarning() << "Failed to allocate codec context";
        return false;
    }

    // 设置编码参数
    m_codecCtx->codec_id = codec->id;
    m_codecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    m_codecCtx->width = width;
    m_codecCtx->height = height;
    m_codecCtx->time_base = AVRational{1, static_cast<int>(fps)};
    m_codecCtx->framerate = AVRational{static_cast<int>(fps), 1};
    m_codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;  // H.264常用格式
    m_codecCtx->gop_size = 12;  // 关键帧间隔
    m_codecCtx->max_b_frames = 0;  // 不使用B帧,降低延迟

    // 设置码率(简单估算)
    m_codecCtx->bit_rate = width * height * fps * 0.1;  // 粗略估算
    if (m_codecCtx->bit_rate < 400000) m_codecCtx->bit_rate = 400000;
    if (m_codecCtx->bit_rate > 5000000) m_codecCtx->bit_rate = 5000000;

    // 某些格式需要全局头
    if (m_formatCtx->oformat->flags & AVFMT_GLOBALHEADER) {
        m_codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    // 打开编码器
    int ret = avcodec_open2(m_codecCtx, codec, nullptr);
    if (ret < 0) {
        qWarning() << "Failed to open codec";
        return false;
    }

    // 将编码器参数复制到流
    ret = avcodec_parameters_from_context(m_stream->codecpar, m_codecCtx);
    if (ret < 0) {
        qWarning() << "Failed to copy parameters to stream";
        return false;
    }
    m_stream->time_base = m_codecCtx->time_base;

    // 分配帧内存
    m_frame = av_frame_alloc();
    if (!m_frame) {
        qWarning() << "Failed to allocate frame";
        return false;
    }
    m_frame->format = m_codecCtx->pix_fmt;
    m_frame->width = width;
    m_frame->height = height;
    ret = av_frame_get_buffer(m_frame, 0);
    if (ret < 0) {
        qWarning() << "Failed to allocate frame buffer";
        return false;
    }

    // 分配数据包
    m_packet = av_packet_alloc();
    if (!m_packet) {
        qWarning() << "Failed to allocate packet";
        return false;
    }

    // 创建图像转换上下文(QImage RGB → YUV420P)
    m_swsCtx = sws_getContext(width, height, AV_PIX_FMT_RGB32,
                              width, height, AV_PIX_FMT_YUV420P,
                              SWS_BILINEAR, nullptr, nullptr, nullptr);
    if (!m_swsCtx) {
        qWarning() << "Failed to create sws context";
        return false;
    }

    return true;
}

bool VideoSaver::convertQImageToAVFrame(const QImage &image, AVFrame *frame)
{
    // 确保图像尺寸匹配
    if (image.width() != frame->width || image.height() != frame->height) {
        qWarning() << "Image size mismatch";
        return false;
    }

    // 确保帧数据可写
    if (av_frame_make_writable(frame) < 0) {
        qWarning() << "Frame not writable";
        return false;
    }

    // 准备源数据(QImage的数据指针)
    // 注意:QImage::bits()返回的指针需要确保QImage在整个转换过程中有效
    QImage img = image.convertToFormat(QImage::Format_RGB32);
    const uint8_t *inData[1] = { img.bits() };
    int inLinesize[1] = { img.bytesPerLine() };

    // 执行格式转换:RGB32 → YUV420P
    sws_scale(m_swsCtx, inData, inLinesize, 0, frame->height,
              frame->data, frame->linesize);

    return true;
}

bool VideoSaver::writeFrame(const QImage &image)
{
    if (!m_isOpen) {
        qWarning() << "Video saver not open";
        return false;
    }

    // 1. 将QImage转换为AVFrame
    if (!convertQImageToAVFrame(image, m_frame)) {
        return false;
    }

    // 设置帧的时间戳(PTS)
    m_frame->pts = m_frameIndex++;

    // 2. 发送帧到编码器
    int ret = avcodec_send_frame(m_codecCtx, m_frame);
    if (ret < 0) {
        qWarning() << "Error sending frame to encoder";
        return false;
    }

    // 3. 接收编码后的数据包
    while (ret >= 0) {
        ret = avcodec_receive_packet(m_codecCtx, m_packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            qWarning() << "Error receiving packet from encoder";
            return false;
        }

        // 4. 调整时间戳并写入文件
        av_packet_rescale_ts(m_packet, m_codecCtx->time_base, m_stream->time_base);
        m_packet->stream_index = m_stream->index;

        ret = av_interleaved_write_frame(m_formatCtx, m_packet);
        if (ret < 0) {
            qWarning() << "Error writing frame";
            return false;
        }
        av_packet_unref(m_packet);
    }

    return true;
}

void VideoSaver::close()
{
    if (!m_isOpen) return;

    // 1. 刷新编码器缓冲区
    avcodec_send_frame(m_codecCtx, nullptr);

    int ret;
    while (true) {
        ret = avcodec_receive_packet(m_codecCtx, m_packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret >= 0) {
            av_packet_rescale_ts(m_packet, m_codecCtx->time_base, m_stream->time_base);
            m_packet->stream_index = m_stream->index;
            av_interleaved_write_frame(m_formatCtx, m_packet);
            av_packet_unref(m_packet);
        }
    }

    // 2. 写入文件尾
    av_write_trailer(m_formatCtx);

    // 3. 清理资源
    if (m_swsCtx) sws_freeContext(m_swsCtx);
    if (m_packet) av_packet_free(&m_packet);
    if (m_frame) av_frame_free(&m_frame);
    if (m_codecCtx) avcodec_free_context(&m_codecCtx);
    if (m_formatCtx) {
        if (m_formatCtx->pb) avio_closep(&m_formatCtx->pb);
        avformat_free_context(m_formatCtx);
    }

    m_swsCtx = nullptr;
    m_packet = nullptr;
    m_frame = nullptr;
    m_codecCtx = nullptr;
    m_formatCtx = nullptr;
    m_isOpen = false;
    m_frameIndex = 0;

    qDebug() << "Video saver closed";
}


#include <QEventLoop>
#include <QTimer>

VideoPlayer::VideoPlayer(QObject *parent)
    : QObject(parent)
    , m_formatCtx(nullptr)
    , m_codecCtx(nullptr)
    , m_codec(nullptr)
    , m_frame(nullptr)
    , m_frameRGB(nullptr)
    , m_packet(nullptr)
    , m_swsCtx(nullptr)
    , m_streamIndex(-1)
    , m_width(0)
    , m_height(0)
    , m_fps(0.0)
    , m_totalFrames(0)
    , m_isOpen(false)
    , m_endOfFile(false)
    , m_rgbBuffer(nullptr)
{
    avformat_network_init();
}

VideoPlayer::~VideoPlayer()
{
    close();
}

bool VideoPlayer::open(const QString &filename)
{
    if (m_isOpen) {
        qWarning() << "VideoPlayer already open";
        return false;
    }

    // 1. 打开输入文件
    m_formatCtx = avformat_alloc_context();
    if (avformat_open_input(&m_formatCtx, filename.toStdString().c_str(), nullptr, nullptr) != 0) {
        qDebug() << "Failed to open input file:" << filename;
        return false;
    }

    // 2. 获取流信息
    if (avformat_find_stream_info(m_formatCtx, nullptr) < 0) {
        qDebug() << "Failed to find stream info";
        return false;
    }

    // 3. 查找视频流索引
    for (unsigned int i = 0; i < m_formatCtx->nb_streams; i++) {
        if (m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            m_streamIndex = i;
            break;
        }
    }

    if (m_streamIndex == -1) {
        qDebug() << "No video stream found";
        return false;
    }

    // 4. 获取视频参数
    AVCodecParameters *codecpar = m_formatCtx->streams[m_streamIndex]->codecpar;
    m_width = codecpar->width;
    m_height = codecpar->height;

    // 计算帧率
    AVRational timeBase = m_formatCtx->streams[m_streamIndex]->time_base;
    m_fps = av_q2d(av_guess_frame_rate(m_formatCtx, m_formatCtx->streams[m_streamIndex], nullptr));
    if (m_fps <= 0) {
        m_fps = 30.0;
    }

    // 获取总帧数
    if (m_formatCtx->streams[m_streamIndex]->nb_frames > 0) {
        m_totalFrames = m_formatCtx->streams[m_streamIndex]->nb_frames;
    } else {
        // 估算总帧数
        double duration = m_formatCtx->duration / (double)AV_TIME_BASE;
        m_totalFrames = static_cast<int>(duration * m_fps);
    }

    // 5. 初始化解码器
    if (!initDecoder()) {
        return false;
    }

    // 6. 初始化SWS转换上下文
    if (!initSwsContext()) {
        return false;
    }

    m_isOpen = true;
    m_endOfFile = false;

    qDebug() << "Video opened:" << filename
             << "Size:" << m_width << "x" << m_height
             << "FPS:" << m_fps
             << "Total frames:" << m_totalFrames;
    return true;
}

bool VideoPlayer::initDecoder()
{
    AVCodecParameters *codecpar = m_formatCtx->streams[m_streamIndex]->codecpar;

    // 查找解码器
    m_codec = avcodec_find_decoder(codecpar->codec_id);
    if (!m_codec) {
        qDebug() << "Decoder not found";
        return false;
    }

    // 分配解码器上下文
    m_codecCtx = avcodec_alloc_context3(m_codec);
    if (!m_codecCtx) {
        qDebug() << "Failed to allocate codec context";
        return false;
    }

    // 复制参数到解码器上下文
    if (avcodec_parameters_to_context(m_codecCtx, codecpar) < 0) {
        qDebug() << "Failed to copy parameters to context";
        return false;
    }

    // 打开解码器
    if (avcodec_open2(m_codecCtx, m_codec, nullptr) < 0) {
        qDebug() << "Failed to open decoder";
        return false;
    }

    // 分配帧和数据包
    m_frame = av_frame_alloc();
    m_frameRGB = av_frame_alloc();
    m_packet = av_packet_alloc();

    if (!m_frame || !m_frameRGB || !m_packet) {
        qDebug() << "Failed to allocate frame or packet";
        return false;
    }

    return true;
}

bool VideoPlayer::initSwsContext()
{
    // 分配RGB缓冲区
    m_rgbBuffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB32,
                                        m_width, m_height, 1));
    if (!m_rgbBuffer) {
        qDebug() << "Failed to allocate RGB buffer";
        return false;
    }

    av_image_fill_arrays(m_frameRGB->data, m_frameRGB->linesize, m_rgbBuffer,
                         AV_PIX_FMT_RGB32, m_width, m_height, 1);

    // 创建SWS上下文
    m_swsCtx = sws_getContext(m_width, m_height, m_codecCtx->pix_fmt,
                              m_width, m_height, AV_PIX_FMT_RGB32,
                              SWS_BICUBIC, nullptr, nullptr, nullptr);

    if (!m_swsCtx) {
        qDebug() << "Failed to create sws context";
        return false;
    }

    return true;
}

bool VideoPlayer::readNextPacket()
{
    while (true) {
        int ret = av_read_frame(m_formatCtx, m_packet);
        if (ret < 0) {
            if (ret == AVERROR_EOF) {
                m_endOfFile = true;
                return false;
            }
            continue;
        }

        if (m_packet->stream_index == m_streamIndex) {
            return true;
        }
        av_packet_unref(m_packet);
    }
}

bool VideoPlayer::decodeNextFrame()
{
    if (m_endOfFile) {
        return false;
    }

    while (true) {
        // 如果没有包了,读取下一个包
        if (m_packet->size <= 0) {
            if (!readNextPacket()) {
                return false;
            }
        }

        // 发送包到解码器
        int ret = avcodec_send_packet(m_codecCtx, m_packet);
        av_packet_unref(m_packet);

        if (ret < 0) {
            if (ret == AVERROR_EOF) {
                m_endOfFile = true;
                return false;
            }
            continue;
        }

        // 接收解码后的帧
        ret = avcodec_receive_frame(m_codecCtx, m_frame);
        if (ret == AVERROR(EAGAIN)) {
            continue;
        } else if (ret == AVERROR_EOF) {
            m_endOfFile = true;
            return false;
        } else if (ret < 0) {
            continue;
        }

        // 转换格式到RGB32
        sws_scale(m_swsCtx, m_frame->data, m_frame->linesize, 0, m_height,
                  m_frameRGB->data, m_frameRGB->linesize);

        // 创建QImage(拷贝数据,确保独立)
        m_currentImage = QImage(m_frameRGB->data[0], m_width, m_height,
                                QImage::Format_RGB32).copy();

        av_frame_unref(m_frame);
        return true;
    }
}

bool VideoPlayer::getNextFrame(QImage &image)
{
    if (!m_isOpen) {
        qWarning() << "VideoPlayer not open";
        return false;
    }

    if (m_endOfFile) {
        return false;
    }

    if (decodeNextFrame()) {
        image = m_currentImage;
        return true;
    }

    return false;
}

void VideoPlayer::reset()
{
    if (!m_isOpen) return;

    // 关闭并重新打开解码器
    avcodec_flush_buffers(m_codecCtx);

    // 重新定位到文件开头
    avformat_seek_file(m_formatCtx, -1, 0, 0, 0, AVSEEK_FLAG_BACKWARD);

    m_endOfFile = false;

    // 清空包
    if (m_packet) {
        av_packet_unref(m_packet);
    }
}

void VideoPlayer::close()
{
    if (!m_isOpen) return;

    cleanup();
    m_isOpen = false;
    m_endOfFile = false;
    qDebug() << "VideoPlayer closed";
}

void VideoPlayer::cleanup()
{
    if (m_swsCtx) {
        sws_freeContext(m_swsCtx);
        m_swsCtx = nullptr;
    }

    if (m_rgbBuffer) {
        av_free(m_rgbBuffer);
        m_rgbBuffer = nullptr;
    }

    if (m_frameRGB) {
        av_frame_free(&m_frameRGB);
        m_frameRGB = nullptr;
    }

    if (m_frame) {
        av_frame_free(&m_frame);
        m_frame = nullptr;
    }

    if (m_packet) {
        av_packet_free(&m_packet);
        m_packet = nullptr;
    }

    if (m_codecCtx) {
        avcodec_free_context(&m_codecCtx);
        m_codecCtx = nullptr;
    }

    if (m_formatCtx) {
        avformat_close_input(&m_formatCtx);
        m_formatCtx = nullptr;
    }
}
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTimer>
#include <QFile>

#include "videorecorder.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();
    void onTimeout_10s(); //只录制10s
    void onTimeout_noRestrictions(); //不限制时间
    void onRecordingStopped(bool success);

    void on_pushButton_2_clicked();

    void on_pushButton_3_clicked();

private:
    void videoRecorder();

    Ui::Widget *ui;

    VideoRecorder* m_recorder = nullptr;
    VideoSaver m_videoSaver;
    QTimer* m_timer = nullptr;
    int m_frameCount = 0;
};

#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"

#include <QDebug>
#include <QTime>
#include <QRandomGenerator>

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
}

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    qDebug() << "FFmpeg configuration:" << avcodec_configuration();
    qDebug() << "FFmpeg version:" << avcodec_version();

    // 初始化随机数生成器
    QRandomGenerator::global()->generate();

    // 创建录制器
    m_timer = new QTimer(this);
}

Widget::~Widget()
{
    delete ui;

    if (m_recorder) {
        m_recorder->stopRecording();
        m_recorder->deleteLater();
    }

    if (m_timer) {
        m_timer->stop();
        delete m_timer;
    }
}

void Widget::on_pushButton_clicked()
{
    // 如果已经在录制,先停止之前的
    if (m_recorder) {
        m_recorder->stopRecording();
        if (m_timer) {
            m_timer->stop();
        }
        m_recorder->deleteLater();
        m_recorder = nullptr;
    }

    videoRecorder();
}

void Widget::onTimeout_10s()
{
    if (!m_recorder) {
        return;
    }

    // 创建测试图片(模拟摄像头数据)
    QImage image(1920, 1080, QImage::Format_RGB32);

    // 创建渐变效果,更容易看出视频是否正常
    for (int y = 0; y < image.height(); ++y) {
        QRgb* line = reinterpret_cast<QRgb*>(image.scanLine(y));
        int r = (y + m_frameCount) % 255;
        int g = (m_frameCount * 2) % 255;
        int b = (y * 2 + m_frameCount) % 255;
        for (int x = 0; x < image.width(); ++x) {
            line[x] = qRgb(r, g, b);
        }
    }
    //测试 addFrame 输入
    QByteArray imageData(reinterpret_cast<const char*>(image.bits()),
                         image.sizeInBytes());

    // 添加帧到录制器
    m_recorder->addFrame(image);
    m_frameCount++;

    // 显示进度(每30帧显示一次)
    if (m_frameCount % 30 == 0) {
        qDebug() << "Added frame:" << m_frameCount;
    }

    // 录制300帧后自动停止(约10秒,30fps)
    if (m_frameCount >= 300) {
        qDebug() << "Auto stop after 300 frames";
        m_recorder->stopRecording();
        if (m_timer) {
            m_timer->stop();
        }
    }
}

void Widget::onTimeout_noRestrictions()
{
    // 创建测试图片(模拟摄像头数据)
    QImage image(1920, 1080, QImage::Format_RGB32);

    // 创建渐变效果,更容易看出视频是否正常
    for (int y = 0; y < image.height(); ++y) {
        QRgb* line = reinterpret_cast<QRgb*>(image.scanLine(y));
        int r = (y + m_frameCount) % 255;
        int g = (m_frameCount * 2) % 255;
        int b = (y * 2 + m_frameCount) % 255;
        for (int x = 0; x < image.width(); ++x) {
            line[x] = qRgb(r, g, b);
        }
    }
    //测试 addFrame 输入
    QByteArray imageData(reinterpret_cast<const char*>(image.bits()),
                         image.sizeInBytes());

    // 添加帧到录制器
    m_videoSaver.writeFrame(image);

    m_frameCount++;

    // 显示进度(每30帧显示一次)
    if (m_frameCount % 30 == 0) {
        qDebug() << "Added frame:" << m_frameCount;
    }
}

void Widget::onRecordingStopped(bool success)
{
    qDebug() << "Recording stopped signal received, success:" << success;

    if (m_timer) {
        m_timer->stop();
    }

    // 延迟删除录制器
    if (m_recorder) {
        m_recorder->deleteLater();
        m_recorder = nullptr;
    }
}

void Widget::videoRecorder()
{
    qDebug() << "Starting video recorder...";

    // 创建录制器
    m_recorder = new VideoRecorder(this);

    // 设置视频参数
    m_recorder->setVideoParameters(1920, 1080, 30, 2000000);

    // 连接信号槽
    connect(m_recorder, &VideoRecorder::recordingStarted, this, []() {
        qDebug() << "Recording started!";
    });

    connect(m_recorder, &VideoRecorder::recordingStopped, this, &Widget::onRecordingStopped);

    connect(m_recorder, &VideoRecorder::errorOccurred, this, [](const QString& error) {
        qDebug() << "Error:" << error;
    });

    connect(m_recorder, &VideoRecorder::frameEncoded, this, [](int count) {
        // 每30帧输出一次日志,减少刷屏
        static int lastLogCount = 0;
        if (count - lastLogCount >= 30) {
            qDebug() << "Encoded frame count:" << count;
            lastLogCount = count;
        }
    });

    // 开始录制
    if (!m_recorder->startRecording("output.mp4")) {
        qDebug() << "Failed to start recording";
        m_recorder->deleteLater();
        m_recorder = nullptr;
        return;
    }

    // 开始模拟数据源
    m_frameCount = 0;
    connect(m_timer, &QTimer::timeout, this, &Widget::onTimeout_10s);
    m_timer->start(33);  // 约30fps (1000/30 ≈ 33ms)

    qDebug() << "Timer started, will generate frames every 33ms";
}

void Widget::on_pushButton_2_clicked()
{
    static int m_clicked = 0;
    if(((m_clicked++) % 2) == 0){
        qDebug()<<"start";
        ui->pushButton_2->setText(QString::fromLocal8Bit("停止录制-不限时"));

        // 如果已经在录制,先停止之前的
        m_videoSaver.close();

        // 开始录制
        if (!m_videoSaver.open("output.mp4", 1920, 1080)) {
            m_videoSaver.close();
            return;
        }

        // 开始模拟数据源
        m_frameCount = 0;
        connect(m_timer, &QTimer::timeout, this, &Widget::onTimeout_noRestrictions);
        m_timer->start(33);  // 约30fps (1000/30 ≈ 33ms)

        qDebug() << "Timer started, will generate frames every 33ms";
    }else{
        qDebug()<<"stop";
        ui->pushButton_2->setText(QString::fromLocal8Bit("开始录制-不限时"));

        if (m_timer) {
            m_timer->stop();
        }

        m_videoSaver.close();
    }
}

void delay_ms(int ms)
{
    QTime stopTime;
    stopTime.start();
    while (stopTime.elapsed() < ms) {
        QCoreApplication::processEvents();
    }
}

//void Widget::on_pushButton_3_clicked()
//{
//    int ret;
//    int streamIndex = -1;
//    AVPacket *pkt = nullptr;
//    AVCodec *codec = nullptr;
//    AVFrame *frame = nullptr;
//    AVFrame *frameRGB = nullptr;
//    AVCodecContext *codecCtx = nullptr;
//    AVFormatContext *formatCtx = nullptr;
//    struct SwsContext *swsCtx = nullptr;
//    QString playUrl = "output.mp4";

//    // 1. 打开输入文件
//    formatCtx = avformat_alloc_context();
//    if (avformat_open_input(&formatCtx, playUrl.toStdString().c_str(), nullptr, nullptr) != 0) {
//        qDebug() << "Failed to open input file.";
//        return;
//    }

//    // 2. 获取流信息
//    if (avformat_find_stream_info(formatCtx, nullptr) < 0) {
//        qDebug() << "Failed to find stream info.";
//        goto cleanup;
//    }

//    // 3. 查找视频流索引
//    for (unsigned int i = 0; i < formatCtx->nb_streams; i++) {
//        if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
//            streamIndex = i;
//            break;
//        }
//    }

//    if (streamIndex == -1) {
//        qDebug() << "No video stream found.";
//        goto cleanup;
//    }

//    // 4. 初始化解码器
//    codecCtx = avcodec_alloc_context3(nullptr);
//    avcodec_parameters_to_context(codecCtx, formatCtx->streams[streamIndex]->codecpar);
//    codec = avcodec_find_decoder(codecCtx->codec_id);
//    if (!codec || avcodec_open2(codecCtx, codec, nullptr) < 0) {
//        qDebug() << "Failed to open decoder.";
//        goto cleanup;
//    }

//    // 5. 准备帧和格式转换
//    pkt = av_packet_alloc();
//    frame = av_frame_alloc();
//    frameRGB = av_frame_alloc();

//    uint8_t *buffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB32,
//                                        codecCtx->width, codecCtx->height, 1));
//    av_image_fill_arrays(frameRGB->data, frameRGB->linesize, buffer,
//                         AV_PIX_FMT_RGB32, codecCtx->width, codecCtx->height, 1);

//    swsCtx = sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
//                            codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB32,
//                            SWS_BICUBIC, nullptr, nullptr, nullptr);

//    // 6. 解码循环
//    while (av_read_frame(formatCtx, pkt) >= 0) {
//        if (pkt->stream_index == streamIndex) {
//            ret = avcodec_send_packet(codecCtx, pkt);
//            if (ret < 0) {
//                av_packet_unref(pkt);
//                continue;
//            }

//            while (ret >= 0) {
//                ret = avcodec_receive_frame(codecCtx, frame);
//                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
//                    break;
//                if (ret < 0)
//                    break;

//                sws_scale(swsCtx, frame->data, frame->linesize, 0, codecCtx->height,
//                          frameRGB->data, frameRGB->linesize);

//                QImage img(frameRGB->data[0], codecCtx->width, codecCtx->height, QImage::Format_RGB32);
//                ui->label->setPixmap(QPixmap::fromImage(img));
//                delay_ms(30);
//            }
//        }
//        av_packet_unref(pkt);
//    }

//    qDebug() << "Play finished!";
//    ui->label->setText(QString::fromLocal8Bit("视频播放完毕"));

//cleanup:
//    // 7. 释放资源
//    if (swsCtx) sws_freeContext(swsCtx);
//    if (frameRGB) av_frame_free(&frameRGB);
//    if (frame) av_frame_free(&frame);
//    if (pkt) av_packet_free(&pkt);
//    if (codecCtx) avcodec_free_context(&codecCtx);
//    if (formatCtx) avformat_close_input(&formatCtx);
//}

void Widget::on_pushButton_3_clicked()
{
    // 创建VideoPlayer实例
    VideoPlayer *player = new VideoPlayer(this);

    // 打开视频文件
    QString playUrl = "output.mp4";
    if (!player->open(playUrl)) {
        qDebug() << "Failed to open video file";
        delete player;
        return;
    }

    // 逐帧获取并显示
    QImage frame;
    int frameCount = 0;

    while (player->getNextFrame(frame)) {
        ui->label->setPixmap(QPixmap::fromImage(frame));
        frameCount++;

        // 延迟控制播放速度
        QEventLoop loop;
        QTimer::singleShot(30, &loop, &QEventLoop::quit);
        loop.exec();
    }

    qDebug() << "Play finished. Total frames:" << frameCount;
    ui->label->setText(QString::fromLocal8Bit("视频播放完毕"));

    // 关闭并释放资源
    player->close();
    delete player;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值