Qt5 + FFmpeg

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

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

image.png
ffmpeg头文件路径:
Qt5 + FFmpeg_第1张图片
ffmpeg库路径:
Qt5 + FFmpeg_第2张图片
exe输出文件夹,需要ffmpeg的dll库加进来:
Qt5 + FFmpeg_第3张图片

  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 
    //avformat:封装格式处理
#include 
    //swscale:视频像素数据格式转换
#include 
    //avdevice:各种设备的输入输出
#include 
    //avutil:工具库(大部分库都需要这个库的支持)
#include 
#include 
#include 
}
  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 
#include 
#include 
#include 
#include 

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

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
Qt5 + FFmpeg_第4张图片

你可能感兴趣的:(FFmpeg,Qt,qt,ffmpeg,开发语言)