Qt5 + FFmpeg

3 篇文章 0 订阅

Qt5中通过FFmpeg拉流实现视频播放(简单的在Qt中使用FFmpeg的demo,没有做任何优化)
本地环境:
Qt5.15.2 + MSVC2019_64bit编译器

  1. 新建Qt项目,项目工程结构

image.png
ffmpeg头文件路径:
image.png
ffmpeg库路径:
image.png
exe输出文件夹,需要ffmpeg的dll库加进来:
image.png

  1. 在项目pro文件中添加ffmpeg头文件和库链接:
QT       += core gui concurrent

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

# 输出目录
DESTDIR = $${OUT_PWD}/../output
TARGET = FFmpegDemo

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# ffmpeg头文件
INCLUDEPATH += $$PWD/../3rdparty/ffmpeg/include
# ffmpeg库链接
LIBS += -L$$PWD/../3rdparty/ffmpeg/lib -lavformat -lavfilter -lavcodec -lswresample -lswscale -lavutil


# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

  1. 在Mainwindow.ui中加入一个QLabel控件即可。
  2. 在Mainwindow.h中添加ffmpeg头文件
//当前C++兼容C语言
extern "C"
{
//avcodec:编解码(最重要的库)
#include <libavcodec/avcodec.h>
    //avformat:封装格式处理
#include <libavformat/avformat.h>
    //swscale:视频像素数据格式转换
#include <libswscale/swscale.h>
    //avdevice:各种设备的输入输出
#include <libavdevice/avdevice.h>
    //avutil:工具库(大部分库都需要这个库的支持)
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
}
  1. 在Mainwindow.cpp中调用ffmpeg接口,实现对url地址的拉流播放
void MainWindow::transVideoToImage(const QString &url)
{
    //avdevice_register_all();  	  //硬件设备注册
    avformat_network_init();   	 //网络注册
    //创建封装格式上下文
    AVFormatContext *formatContext = avformat_alloc_context();

    //打开本地文件到封装格式上下文中。
    QString filename = url;
    int avformat_open_ret = avformat_open_input(&formatContext, filename.toLatin1(), NULL, NULL);
    if(avformat_open_ret < 0)
    {
        char *result = new char[64];
        av_strerror(avformat_open_ret,result,64);
        qDebug() << QString(u8"错误信息:%1").arg(result);
    }
    qDebug() << u8"打开多媒体文件成功!";

    //在封装格式上下文中获取音视频流信息
    int avformat_find_ret = avformat_find_stream_info(formatContext, NULL);
    if(avformat_find_ret < 0)
    {
        char *result = new char[64];
        av_strerror(avformat_open_ret,result,64);
        qDebug() << QString(u8"错误信息:%1").arg(result);
    }
    qDebug() << u8"获取音视频流信息成功!";

    //通过遍历封装格式上下文中所有的流信息,找到视频流并保存视频流索引。
    int streamIndex = -1;
    for(unsigned int i = 0; i < formatContext->nb_streams; i++)
    {
        if(formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            streamIndex = i;
            break;
        }
    }
    if(streamIndex == -1)
    {
        qDebug() << u8"找不到相应的流信息!";
    }

    //通过视频流索引,获取解码器上下文。
    AVCodecContext *codecContext = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(codecContext , formatContext->streams[streamIndex]->codecpar);

    //通过解码上下文的属性id寻找合适的解码器。
    const AVCodec *codec = avcodec_find_decoder(codecContext->codec_id);
    if(codec == NULL)
    {
        qDebug() << u8"找不到合适的解码器!";
    }

    //通过解码器上下文,打开寻找到的解码器。
    int avcodec_open_ret = avcodec_open2(codecContext,codec,NULL);
    if(avcodec_open_ret < 0)
    {
        char *result = new char[64];
        av_strerror(avformat_open_ret,result,64);
        qDebug() << QString(u8"错误信息:%1").arg(result);
    }
    qDebug() << u8"打开解码器成功!";

    //对接收数据的数据包、数据帧、数据缓冲区容器动态分配内存空间。
    //数据包初始化
    AVPacket *packet = av_packet_alloc();
    //输入视频帧初始化
    AVFrame *frame = av_frame_alloc();
    //输出RGB帧初始化
    AVFrame *frameRGB = av_frame_alloc();
    //给缓冲区动态分配内存
    uint8_t *pOutbuffer = static_cast<unsigned char *>(
        av_malloc(static_cast<size_t>(av_image_get_buffer_size(AV_PIX_FMT_RGB32, codecContext->width, codecContext->height, 1))));
    //初始化缓冲区
    av_image_fill_arrays(frameRGB->data, frameRGB->linesize, pOutbuffer, AV_PIX_FMT_RGB32, codecContext->width, codecContext->height, 1);

    //通过解码器上下文获取图像转换上下文
    SwsContext *sws = sws_getContext(codecContext->width,codecContext->height,codecContext->pix_fmt,
                                     codecContext->width,codecContext->height,AV_PIX_FMT_RGB32,
                                     SWS_BICUBIC,NULL,NULL,NULL);

    //从封装格式上下文读取信息到数据包,再从数据包读取到数据帧,最后将数据转为图像并显示。
    //计算解码帧数
    int frameNum = 0;
    //读取视频帧
    while(av_read_frame(formatContext,packet) >= 0)
    {
        if(packet->stream_index == streamIndex)
        {
            avcodec_send_packet(codecContext,packet);
            int decode_video_ret = avcodec_receive_frame(codecContext,frame);
            if(decode_video_ret >= 0)
            {
                //计算帧数
                frameNum++;
                qDebug() << QString(u8"正在解码播放第%1帧数据").arg(frameNum);
                //视频流数据转换为RGB图像数据
                sws_scale(sws, (const unsigned char* const*)frame->data,frame->linesize, 0,
                          codecContext->height,frameRGB->data,frameRGB->linesize);
                //数据转换为图像
                QImage *tmpImg = new QImage((uchar *)pOutbuffer,codecContext->width,
                                            codecContext->height,QImage::Format_RGB32);
                //加载到标签中显示
                // imgLabel->setPixmap(QPixmap::fromImage(img));
                emit signal_devodeImage(tmpImg);
                av_usleep(10 * 1000);
            }
        }
    }
}

  1. 由于在这个函数中时通过while死循环实现视频帧的读取和转化,所以需要再线程中实现该函数,不然UI界面无法加载出来。通过子线程运行该函数。
    QtConcurrent::run(this, &MainWindow::transVideoToImage, urls.at(1));
    connect(this, &MainWindow::signal_devodeImage, this, [=](QImage *img) {
        ui->label->setPixmap(QPixmap::fromImage(*img));
    });
  1. 推荐测试url地址:“rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid”(邓紫棋)
  2. 完整代码:

Mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QImage>
#include <QPixmap>
#include <QDebug>
#include <QtConcurrent>

//当前C++兼容C语言
extern "C"
{
//avcodec:编解码(最重要的库)
#include <libavcodec/avcodec.h>
    //avformat:封装格式处理
#include <libavformat/avformat.h>
    //swscale:视频像素数据格式转换
#include <libswscale/swscale.h>
    //avdevice:各种设备的输入输出
#include <libavdevice/avdevice.h>
    //avutil:工具库(大部分库都需要这个库的支持)
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
}

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private:
    Ui::MainWindow *ui;

    void transVideoToImage(const QString &url);

signals:
    void signal_devodeImage(QImage *);
};
#endif // MAINWINDOW_H

Mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

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

    QStringList urls;
    // todo 摄像头url
    urls << "rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp"
         << "rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid"
         << "http://vfx.mtime.cn/Video/2021/11/16/mp4/211116131456748178.mp4";

    QtConcurrent::run(this, &MainWindow::transVideoToImage, urls.at(1));

    connect(this, &MainWindow::signal_devodeImage, this, [=](QImage *img) {
        ui->label->setPixmap(QPixmap::fromImage(*img));
    });
}

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

void MainWindow::transVideoToImage(const QString &url)
{
    //avdevice_register_all();  	  //硬件设备注册
    avformat_network_init();   	 //网络注册
    //创建封装格式上下文
    AVFormatContext *formatContext = avformat_alloc_context();

    //打开本地文件到封装格式上下文中。
    QString filename = url;
    int avformat_open_ret = avformat_open_input(&formatContext, filename.toLatin1(), NULL, NULL);
    if(avformat_open_ret < 0)
    {
        char *result = new char[64];
        av_strerror(avformat_open_ret,result,64);
        qDebug() << QString(u8"错误信息:%1").arg(result);
    }
    qDebug() << u8"打开多媒体文件成功!";

    //在封装格式上下文中获取音视频流信息
    int avformat_find_ret = avformat_find_stream_info(formatContext, NULL);
    if(avformat_find_ret < 0)
    {
        char *result = new char[64];
        av_strerror(avformat_open_ret,result,64);
        qDebug() << QString(u8"错误信息:%1").arg(result);
    }
    qDebug() << u8"获取音视频流信息成功!";

    //通过遍历封装格式上下文中所有的流信息,找到视频流并保存视频流索引。
    int streamIndex = -1;
    for(unsigned int i = 0; i < formatContext->nb_streams; i++)
    {
        if(formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            streamIndex = i;
            break;
        }
    }
    if(streamIndex == -1)
    {
        qDebug() << u8"找不到相应的流信息!";
    }

    //通过视频流索引,获取解码器上下文。
    AVCodecContext *codecContext = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(codecContext , formatContext->streams[streamIndex]->codecpar);

    //通过解码上下文的属性id寻找合适的解码器。
    const AVCodec *codec = avcodec_find_decoder(codecContext->codec_id);
    if(codec == NULL)
    {
        qDebug() << u8"找不到合适的解码器!";
    }

    //通过解码器上下文,打开寻找到的解码器。
    int avcodec_open_ret = avcodec_open2(codecContext,codec,NULL);
    if(avcodec_open_ret < 0)
    {
        char *result = new char[64];
        av_strerror(avformat_open_ret,result,64);
        qDebug() << QString(u8"错误信息:%1").arg(result);
    }
    qDebug() << u8"打开解码器成功!";

    //对接收数据的数据包、数据帧、数据缓冲区容器动态分配内存空间。
    //数据包初始化
    AVPacket *packet = av_packet_alloc();
    //输入视频帧初始化
    AVFrame *frame = av_frame_alloc();
    //输出RGB帧初始化
    AVFrame *frameRGB = av_frame_alloc();
    //给缓冲区动态分配内存
    uint8_t *pOutbuffer = static_cast<unsigned char *>(
        av_malloc(static_cast<size_t>(av_image_get_buffer_size(AV_PIX_FMT_RGB32, codecContext->width, codecContext->height, 1))));
    //初始化缓冲区
    av_image_fill_arrays(frameRGB->data, frameRGB->linesize, pOutbuffer, AV_PIX_FMT_RGB32, codecContext->width, codecContext->height, 1);

    //通过解码器上下文获取图像转换上下文
    SwsContext *sws = sws_getContext(codecContext->width,codecContext->height,codecContext->pix_fmt,
                                     codecContext->width,codecContext->height,AV_PIX_FMT_RGB32,
                                     SWS_BICUBIC,NULL,NULL,NULL);

    //从封装格式上下文读取信息到数据包,再从数据包读取到数据帧,最后将数据转为图像并显示。
    //计算解码帧数
    int frameNum = 0;
    //读取视频帧
    while(av_read_frame(formatContext,packet) >= 0)
    {
        if(packet->stream_index == streamIndex)
        {
            avcodec_send_packet(codecContext,packet);
            int decode_video_ret = avcodec_receive_frame(codecContext,frame);
            if(decode_video_ret >= 0)
            {
                //计算帧数
                frameNum++;
                qDebug() << QString(u8"正在解码播放第%1帧数据").arg(frameNum);
                //视频流数据转换为RGB图像数据
                sws_scale(sws, (const unsigned char* const*)frame->data,frame->linesize, 0,
                          codecContext->height,frameRGB->data,frameRGB->linesize);
                //数据转换为图像
                QImage *tmpImg = new QImage((uchar *)pOutbuffer,codecContext->width,
                                            codecContext->height,QImage::Format_RGB32);
                //加载到标签中显示
                // imgLabel->setPixmap(QPixmap::fromImage(img));
                emit signal_devodeImage(tmpImg);
                av_usleep(10 * 1000);
            }
        }
    }
}

Mainwindow.ui
image.png

很高兴听到您对Qt5+FFmpeg多路H265视频监控项目的实战感兴趣!这是一个非常有挑战性的项目,需要涉及到视频处理、网络通信和用户界面设计等多个方面。以下是一些关键的步骤和技术要点,供您参考: 1. 安装Qt5FFmpeg:确保您的开发环境中安装了Qt5FFmpeg库。您可以从官方网站下载和安装它们,或者使用包管理器进行安装。 2. 视频解码和播放:使用FFmpeg库来解码H265编码的视频流,并使用Qt中的QMediaPlayer或QVideoWidget来实现视频播放功能。您可以使用FFmpeg的API来读取和解码视频帧,并将其传递给Qt的视频播放组件进行显示。 3. 多路视频处理:对于多路视频监控,您需要同时处理多个视频流。您可以使用多线程或异步编程来实现并行处理。每个视频流都需要一个独立的解码器和播放器实例来处理。 4. 网络通信:如果您需要从远程设备接收视频流,您可以使用Qt提供的网络模块来实现网络通信。您可以使用QtQTcpSocket或QUdpSocket类来接收和处理视频数据。 5. 用户界面设计:使用Qt的UI设计工具(如Qt Designer)创建一个用户友好的界面。您可以添加控件来显示视频流、控制播放、切换摄像头等功能。确保界面与视频处理逻辑进行交互。 请注意,这只是一个简要概述,Qt5+FFmpeg多路H265视频监控项目是一个复杂的项目,需要您具备一定的视频处理和编程经验。建议您先熟悉QtFFmpeg的基本用法,并进行相关学习和实践,以便更好地完成这个项目。祝您成功!如果您有任何进一步的问题,请随时提问。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

taciturn丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值