Qt5中通过FFmpeg拉流实现视频播放(简单的在Qt中使用FFmpeg的demo,没有做任何优化)
本地环境:
Qt5.15.2 + MSVC2019_64bit编译器
ffmpeg头文件路径:
ffmpeg库路径:
exe输出文件夹,需要ffmpeg的dll库加进来:
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
//当前C++兼容C语言
extern "C"
{
//avcodec:编解码(最重要的库)
#include
//avformat:封装格式处理
#include
//swscale:视频像素数据格式转换
#include
//avdevice:各种设备的输入输出
#include
//avutil:工具库(大部分库都需要这个库的支持)
#include
#include
#include
}
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);
}
}
}
}
QtConcurrent::run(this, &MainWindow::transVideoToImage, urls.at(1));
connect(this, &MainWindow::signal_devodeImage, this, [=](QImage *img) {
ui->label->setPixmap(QPixmap::fromImage(*img));
});
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);
}
}
}
}