QT开发中文网

给定以下一个场景,帮我设计一个程序处理逻辑,是作为客户端使用的 1.客户端接收服务端的视频帧 2.视频帧经过解码成rgb图像 3.将rgb图像送至qt框架使用qwidget 的qpaint绘制 并用qt代码实现一下,重点解释一下解码成的rgb数据和发送给qwidget的rgb数据,是否应该使用同一内存块,如果使用同一内存块的话,如何在多线程中做同步

设计思路:

  1. 使用ffmpeg库对视频帧进行解码,将解码后的rgb数据存储在内存块中。
  2. 在客户端使用Qt框架创建一个QWidget对象,并重写其paintEvent()函数,在该函数中使用QPainter绘制rgb图像。
  3. 创建一个线程用于接收服务端发送的视频帧,并将解码后的rgb数据传递给QWidget对象进行绘制。
  4. 使用Qt提供的信号和槽机制实现多线程间同步。

代码实现:

首先需要安装ffmpeg库并配置Qt项目的.pro文件,具体步骤可以参考Qt官网文档。

定义一个VideoDec类,用于视频帧解码和rgb数据存储:

class VideoDec : public QObject
{
    Q_OBJECT

public:
    VideoDec(QObject *parent = nullptr);
    ~VideoDec();

    bool open(const QString &fileName); // 打开视频文件
    void close(); // 关闭视频文件
    bool decode(); // 解码一帧视频
    uint8_t* getRgbData(); // 获取rgb数据指针
    int getWidth(); // 获取视频宽度
    int getHeight(); // 获取视频高度

private:
    AVFormatContext *m_formatCtx;
    AVCodecContext *m_codecCtx;
    AVFrame *m_frame;
    uint8_t *m_rgbData;
    struct SwsContext *m_swsCtx;
    int m_videoStreamIndex;
    int m_width;
    int m_height;
};

VideoDec::VideoDec(QObject *parent)
    : QObject(parent)
    , m_formatCtx(nullptr)
    , m_codecCtx(nullptr)
    , m_frame(nullptr)
    , m_rgbData(nullptr)
    , m_swsCtx(nullptr)
    , m_videoStreamIndex(-1)
    , m_width(0)
    , m_height(0)
{
}

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

bool VideoDec::open(const QString &fileName)
{
    // 打开视频文件
    if (avformat_open_input(&m_formatCtx, fileName.toStdString().c_str(), nullptr, nullptr) < 0) {
        qDebug() << "Failed to open file";
        return false;
    }

    // 查找流信息
    if (avformat_find_stream_info(m_formatCtx, nullptr) < 0) {
        qDebug() << "Failed to find stream info";
        avformat_close_input(&m_formatCtx);
        return false;
    }

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

    // 没有找到视频流
    if (m_videoStreamIndex == -1) {
        qDebug() << "Failed to find video stream";
        avformat_close_input(&m_formatCtx);
        return false;
    }

    // 创建解码器并打开
    AVCodec *codec = avcodec_find_decoder(m_formatCtx->streams[m_videoStreamIndex]->codecpar->codec_id);
    if (!codec) {
        qDebug() << "Failed to find codec";
        avformat_close_input(&m_formatCtx);
        return false;
    }
    m_codecCtx = avcodec_alloc_context3(codec);
    if (avcodec_parameters_to_context(m_codecCtx, m_formatCtx->streams[m_videoStreamIndex]->codecpar) < 0) {
        qDebug() << "Failed to set codec parameters";
        avformat_close_input(&m_formatCtx);
        return false;
    }
    if (avcodec_open2(m_codecCtx, codec, nullptr) < 0) {
        qDebug() << "Failed to open codec";
        avcodec_free_context(&m_codecCtx);
        avformat_close_input(&m_formatCtx);
        return false;
    }

    // 创建一个空帧用于存放解码后的数据
    m_frame = av_frame_alloc();
    if (!m_frame) {
        qDebug() << "Failed to allocate frame";
        avcodec_free_context(&m_codecCtx);
        avformat_close_input(&m_formatCtx);
        return false;
    }

    // 计算rgb数据内存大小并分配内存
    m_width = m_codecCtx->width;
    m_height = m_codecCtx->height;
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, m_width, m_height, 1);
    m_rgbData = static_cast<uint8_t*>(av_malloc(numBytes * sizeof(uint8_t)));
    if (!m_rgbData) {
        qDebug() << "Failed to allocate rgb data";
        av_frame_free(&m_frame);
        avcodec_free_context(&m_codecCtx);
        avformat_close_input(&m_formatCtx);
        return false;
    }

    // 创建图像转换上下文
    m_swsCtx = sws_getContext(m_width, m_height, m_codecCtx->pix_fmt,
                              m_width, m_height, AV_PIX_FMT_RGB24,
                              SWS_BILINEAR, nullptr, nullptr, nullptr);
    if (!m_swsCtx) {
        qDebug() << "Failed to create sws context";
        av_free(m_rgbData);
        av_frame_free(&m_frame);
        avcodec_free_context(&m_codecCtx);
        avformat_close_input(&m_formatCtx);
        return false;
    }

    qDebug() << "Video width:" << m_width << "height:" << m_height;

    return true;
}

void VideoDec::close()
{
    if (m_swsCtx) {
        sws_freeContext(m_swsCtx);
        m_swsCtx = nullptr;
    }
    if (m_rgbData) {
        av_free(m_rgbData);
        m_rgbData = 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) {
        avformat_close_input(&m_formatCtx);
        m_formatCtx = nullptr;
    }
}

bool VideoDec::decode()
{
    AVPacket packet;
    int ret = av_read_frame(m_formatCtx, &packet);
    if (ret < 0) {
        qDebug() << "Failed to read frame";
        return false;
    }

    if (packet.stream_index != m_videoStreamIndex) {
        av_packet_unref(&packet);
        return false;
    }

    ret = avcodec_send_packet(m_codecCtx, &packet);
    if (ret < 0) {
        qDebug() << "Failed to send packet for decoding";
        av_packet_unref(&packet);
        return false;
    }

    ret = avcodec_receive_frame(m_codecCtx, m_frame);
    if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
        qDebug() << "Failed to receive frame";
        av_packet_unref(&packet);
        return false;
    }

    if (ret >= 0) {
        sws_scale(m_swsCtx, m_frame->data, m_frame->linesize, 0, m_height,
                  &m_rgbData, nullptr);

        av_packet_unref(&packet);

        return true;
    }

    av_packet_unref(&packet);

    return false;
}

uint8_t* VideoDec::getRgbData()
{
    return m_rgbData;
}

int VideoDec::getWidth()
{
    return m_width;
}

int VideoDec::getHeight()
{
    return m_height;
}

在客户端中创建一个VideoWidget类,用于绘制rgb图像:

class VideoWidget : public QWidget
{
    Q_OBJECT

public:
    VideoWidget(QWidget *parent = nullptr);
    ~VideoWidget();

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    QImage m_image;
};

VideoWidget::VideoWidget(QWidget *parent)
    : QWidget(parent)
{
}

VideoWidget::~VideoWidget()
{
}

void VideoWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.drawImage(0, 0, m_image);
}

在主窗口中创建一个VideoPlayer类,用于视频播放和rgb数据传递:

class VideoPlayer : public QObject
{
    Q_OBJECT

public:
    VideoPlayer(QObject *parent = nullptr);

    bool open(const QString &fileName);
    void close();
    bool play(); // 开始播放视频
    void stop(); // 停止播放视频

signals:
    void imageReady(); // rgb数据已准备好的信号

private slots:
    void processFrame(); // 处理一帧视频帧的槽函数

private:
    QThread m_thread;
    VideoDec m_videoDec; // 视频解码器
    bool m_stopFlag;
};

VideoPlayer::VideoPlayer(QObject *parent)
    : QObject(parent)
    , m_stopFlag(true)
{
    m_videoDec.moveToThread(&m_thread);
    connect(&m_thread, &QThread::started, &m_videoDec, [&]() {
        while (!m_stopFlag && m_videoDec.decode()) {
            emit imageReady();
            std::this_thread::sleep_for(std::chrono::milliseconds(30));
        }
    });
    m_thread.start();
}

bool VideoPlayer::open(const QString &fileName)
{
    return m_videoDec.open(fileName);
}

void VideoPlayer::close()
{
    m_videoDec.close();
}

bool VideoPlayer::play()
{
    if (m_stopFlag) {
        m_stopFlag = false;
        QMetaObject::invokeMethod(&m_videoDec, &VideoDec::decode);
        return true;
    }
    return false;
}

void VideoPlayer::stop()
{
    m_stopFlag = true;
    m_thread.quit();
    m_thread.wait();
}

void VideoPlayer::processFrame()
{
    QImage image(m_videoDec.getRgbData(), m_videoDec.getWidth(), m_videoDec.getHeight(),
                 QImage::Format_RGB888);
    static_cast<VideoWidget*>(parent())->setPixmap(QPixmap::fromImage(image));
}

使用时,在主窗口的构造函数中创建一个VideoWidget对象和一个VideoPlayer对象,将VideoWidget对象添加到布局中,并连接VideoPlayer对象的imageReady()信号和VideoWidget对象的update()槽函数:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , m_videoWidget(new VideoWidget)
    , m_videoPlayer(new VideoPlayer)
{
    ui->setupUi(this);

    QGridLayout *layout = new QGridLayout(ui->centralWidget);
    layout->addWidget(m_videoWidget, 0, 0, 1, 1);

    connect(m_videoPlayer, &VideoPlayer::imageReady, m_videoWidget, &VideoWidget::update);
}

MainWindow::~MainWindow()
{
    delete ui;
}

需要注意的是,由于rgb数据在解码器线程中生成,而QWidget的paintEvent()函数在主线程中执行,因此需要在两个线程之间进行同步。这里使用Qt提供的信号和槽机制实现多线程间同步:当rgb数据准备好后,VideoPlayer对象发送imageReady()信号,VideoWidget对象接收到该信号后调用update()函数触发QWidget的重新绘制操作。由于update()函数在主线程中被调用,因此可以保证多线程间的同步。

本站部分文章来源于网络,版权归原作者所有,如有侵权请联系站长删除。
转载请注明出处:https://qt.0voice.com/?id=765

分享:
扫描分享到社交APP
上一篇
下一篇
发表列表
游客 游客
此处应有掌声~
评论列表

还没有评论,快来说点什么吧~

联系我们

在线咨询: 点击这里给我发消息

微信号:3007537140

上班时间: 10:30-22:30

关注我们
x

注册

已经有帐号?