下面是通过ffmpeg拉流解码显示推流的视频,通过openGL绘制视频。废话不多说,直接上代码
拉流头文件ffmpegutils.h
#ifndef FFMPEGUTILS_H
#define FFMPEGUTILS_H
#include <QObject>
#include <QThread>
#include <QDebug>
#include <QTime>
#include <QEventLoop>
#include <QPixmap>
#include <QTimer>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
class FFMPEGUtils : public QObject
{
Q_OBJECT
public:
explicit FFMPEGUtils(QObject *parent = nullptr);
~FFMPEGUtils();
bool initFFmpeg(QString fileName);
void clear();
private:
void onVideoDiversionProcess();
signals:
void sigToUpdateImage(const QImage &image);
void sendStop();
public slots:
void onStartPlay(QString name);
void onStopPlay();
void onFinish();
private:
AVFormatContext* pFormatCtx = Q_NULLPTR;
AVCodecContext* pCodecCtx = Q_NULLPTR;
AVFrame* pAvFrame = Q_NULLPTR;
AVFrame* pFrameRGB32 = Q_NULLPTR;
AVPacket* packet = Q_NULLPTR;
uint8_t *out_buffer = Q_NULLPTR;
SwsContext *img_convert_ctx = Q_NULLPTR;
int videoIndex;
bool is_stop = false;
bool is_finish = true;
};
#endif // FFMPEGUTILS_H
拉流源代码ffmpegutils.cpp
#include "ffmpegutils.h"
FFMPEGUtils::FFMPEGUtils(QObject *parent) : QObject(parent)
{
}
FFMPEGUtils::~FFMPEGUtils()
{
clear();
}
bool FFMPEGUtils::initFFmpeg(QString fileName)
{
clear();
AVDictionary* dict = nullptr;
//使用 TCP 方式
av_dict_set(&dict, "rtsp_transport", "tcp", 0);
//设置阻塞超时,否则可能在流断开时连接发生阻塞,微秒
av_dict_set(&dict, "stimeout", "3000000", 0);
//设置buffer_size缓存容量
//av_dict_set(&dict, "buffer_size", "1024000", 0);
av_dict_set(&dict, "buffer_size", "8192000", 0);
// 设置TCP连接最大延时时间 接收包间隔最大延迟,微秒
av_dict_set(&dict, "max_delay", "5000000", 0);
pFormatCtx = avformat_alloc_context();
int ret = avformat_open_input(&pFormatCtx,
fileName.toStdString().c_str(),
NULL,
&dict);
// 释放参数字典
if (dict)
av_dict_free(&dict);
if (ret != 0)
{
clear();
return false;
}
/*
* 用于找到媒体文件中的流信息,也就是获取视频和音频的相关数据,如编码格式、分辨率、采样率等
* 获取多媒体流的信息(视频文件信息),一个视频文件中可能会同时包括视频文件、音频文件、字幕文件等多个媒体流。
*/
ret = avformat_find_stream_info(pFormatCtx, NULL);
if (ret < 0)
{
clear();
return false;
}
videoIndex = -1;
videoIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (videoIndex < 0)
{
clear();
return false;
}
AVCodecParameters* parmeter = pFormatCtx->streams[videoIndex]->codecpar;
/*
* avcodec_find_decoder 通过解码器ID获取视频解码器(新版本返回值必须使用const)
*/
// const AVCodec* _pCodec = avcodec_find_encoder_by_name("nvenc_h264");
const AVCodec* _pCodec = avcodec_find_decoder(parmeter->codec_id);
if (_pCodec == NULL)
{
clear();
return false;
}
/*
* avcodec_alloc_context3 用于分配一个编解码器上下文(AVCodecContext)结构体
* AVCodecContext结构的主要作用是设置编码过程的参数
*/
pCodecCtx = avcodec_alloc_context3(_pCodec);//初始化一个编解码上下文
/*
* avcodec_parameters_to_context 该函数用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中
*/
ret = avcodec_parameters_to_context(pCodecCtx, parmeter);
if (ret < 0)
{
clear();
return false;
}
/*
* avcodec_open2 用于打开编解码器并初始化其上下文
*/
ret = avcodec_open2(pCodecCtx, _pCodec, NULL);
if (ret < 0)
{
clear();
return false;
}
//分配AVFrame并将其字段设置为默认值。
pAvFrame = av_frame_alloc();
pFrameRGB32 = av_frame_alloc(); //存储解码后转换的RGB数据
int size = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 4);
out_buffer = (uint8_t *)av_malloc(size);
ret = av_image_fill_arrays(pFrameRGB32->data, pFrameRGB32->linesize, out_buffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);
if (ret < 0)
{
clear();
return false;
}
//分配AVPacket并将其字段设置为默认值。
packet = av_packet_alloc();
/*
* av_new_packet 用于分配一个新的AVPacket结构体
*/
av_new_packet(packet, pCodecCtx->width * pCodecCtx->height);//分配packet的有效载荷并初始化其字段
return true;
}
void FFMPEGUtils::onStartPlay(QString name)
{
bool flag = initFFmpeg(name);
is_stop = false;
if (flag)
{
is_finish = false;
onVideoDiversionProcess();
}
else
{
qDebug() << "FFmpeg init fail...";
}
}
void FFMPEGUtils::onStopPlay()
{
is_stop = !is_stop;
}
void FFMPEGUtils::onFinish()
{
is_finish = true;
}
void FFMPEGUtils::clear()
{
if (pFrameRGB32)
{
av_frame_free(&pFrameRGB32);
pFrameRGB32 = nullptr;
}
if (pAvFrame)
{
av_frame_free(&pAvFrame);
pAvFrame = nullptr;
}
if (pCodecCtx)
{
avcodec_close(pCodecCtx);
pCodecCtx = nullptr;
}
// 因为avformat_flush不会刷新AVIOContext (pFormatCtx->pb)。如果有必要,在调用此函数之前调用avio_flush(pFormatCtx->pb)。
if (pFormatCtx && pFormatCtx->pb)
{
avio_flush(pFormatCtx->pb);
}
if (pFormatCtx)
{
avformat_flush(pFormatCtx); // 清理读取缓冲
avformat_close_input(&pFormatCtx);
pFormatCtx = nullptr;
}
if (img_convert_ctx)
{
sws_freeContext(img_convert_ctx);
img_convert_ctx = nullptr;
}
if (out_buffer) {
av_free(out_buffer);
out_buffer = nullptr;
}
}
void FFMPEGUtils::onVideoDiversionProcess()
{
/*
* sws_getContext 用于创建一个SWSContext结构体
* struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
* 参数srcW和srcH分别表示源图像的宽度和高度
* 参数srcFormat表示源图像的像素格式
* 参数dstFormat表示目标图像的像素格式
* 参数flags表示转换的标志;
* 参数srcFilter和dstFilter分别表示源图像和目标图像的滤波器;
* 参数param表示转换的参数。
*/
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
while (!is_finish)
{
//由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态
if (is_stop)
{
continue;
}
/*
* int av_read_frame(AVFormatContext *s, AVPacket *pkt);用于从输入流中读取一帧数据。
* 参数s是一个指向AVFormatContext指针的指针,表示要读取数据的输入流;
* 参数pkt是一个指向AVPacket指针的指针,表示要存储读取到的数据的AVPacket结构体。
*/
if (av_read_frame(pFormatCtx, packet) >= 0)
{
if (videoIndex == packet->stream_index) //此流是视频流
{
/*
* int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);用于将AVPacket结构体中的数据发送到解码器。
* 参数avctx是一个指向AVCodecContext指针的指针,表示要发送数据的解码器;
* 参数avpkt是一个指向AVPacket指针的指针,表示要发送的数据。
*/
/*
* int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);用于从解码器中接收解码后的帧数据。
* 参数avctx是一个指向AVCodecContext指针的指针,表示要接收数据的解码器;
* 参数frame是一个指向AVFrame指针的指针,表示要存储接收到的数据的AVFrame结构体。
*/
int ret = avcodec_send_packet(pCodecCtx, packet);
if (ret < 0)
{
continue;
}
ret = avcodec_receive_frame(pCodecCtx, pAvFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
//拉流显示视频时一开始报错: 返回值:-11,资源暂时不可用
//代表解码器暂时没有数据可读,需要输入更多的AVPacket。
//在代码中设定碰到这个错误时直接continue让avcodec_send_packet()对解码器输入更多数据
continue;
}
else if (ret < 0)
{
continue;
}
/*
* sws_scale 用于将图像从一个像素格式转换为另一个像素格式。
* 可以在同一个函数里实现:1.图像色彩空间转换, 2:分辨率缩放,3:前后图像滤波处理
*/
sws_scale(img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize,
0,
pCodecCtx->height,
pFrameRGB32->data,
pFrameRGB32->linesize);
QImage image((uchar*)pFrameRGB32->data[0], pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB32);
emit sigToUpdateImage(image);
//由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态
//QThread::msleep(40);
}
/*
* av_packet_free和av_packet_unref都是FFmpeg库中的函数,用于释放AVPacket结构体中的资源。
* av_packet_free函数会释放AVPacket结构体中的所有资源,包括存储数据的缓冲区等。
* av_packet_unref函数只会释放AVPacket结构体中的部分资源,例如释放存储数据的缓冲区等,但不会释放AVPacket结构体本身。
*/
av_packet_unref(packet);
}
else
{
is_finish = true;
emit sendStop();
qDebug() << "Wait for the video to stream...";
}
}
}
主窗口调用拉流实现头文件
#pragma once
#include <QWidget>
#include <QThread>
#include <QPointer>
#include "ui_realtimeVideoClass.h"
#include "opencv2/opencv.hpp"
#include "openGLWidget.h"
#include "ffmpegutils.h"
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
class realtimeVideoClass : public QWidget
{
Q_OBJECT
public:
realtimeVideoClass(QWidget *parent = nullptr);
~realtimeVideoClass();
void initRtspWork(QString str);
void startWork();
void stopWork();
signals:
void sigToStart(QString str);
void sigToStop();
void sigToFinish();
private:
Ui::realtimeVideoClassClass ui;
QPointer<FFMPEGUtils> ffmpeg_decode = new FFMPEGUtils;
QThread *thread_decode = Q_NULLPTR;
bool isRun = false;
};
主窗口调用拉流实现源文件
#include "realtimeVideoClass.h"
realtimeVideoClass::realtimeVideoClass(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
thread_decode = new QThread;
ffmpeg_decode->moveToThread(thread_decode);
connect(thread_decode, &QThread::finished, ffmpeg_decode, &FFMPEGUtils::deleteLater);
connect(thread_decode, &QThread::finished, thread_decode, &QThread::deleteLater);
connect(ffmpeg_decode, &FFMPEGUtils::sigToUpdateImage, this, [=](const QImage &image) {
if (!image.isNull() && isRun)
{
ui.widget->setCurrentImage(image);
}
});
connect(ffmpeg_decode, &FFMPEGUtils::sendStop, this, [=]() {
isRun = false;
});
connect(this, &realtimeVideoClass::sigToStart, ffmpeg_decode, &FFMPEGUtils::onStartPlay);
connect(this, &realtimeVideoClass::sigToFinish, ffmpeg_decode, &FFMPEGUtils::onFinish, Qt::DirectConnection);
//方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态
connect(this, &realtimeVideoClass::sigToStop, ffmpeg_decode, &FFMPEGUtils::onStopPlay, Qt::DirectConnection);
thread_decode->start();
}
realtimeVideoClass::~realtimeVideoClass()
{
emit sigToFinish();
if (thread_decode)
{
thread_decode->quit();
thread_decode->wait();
thread_decode->deleteLater();
thread_decode = Q_NULLPTR;
}
if (ffmpeg_decode)
{
ffmpeg_decode->deleteLater();
ffmpeg_decode = Q_NULLPTR;
}
}
void realtimeVideoClass::initRtspWork(QString str)
{
emit sigToStart(str);
}
void realtimeVideoClass::startWork()
{
if (!isRun)
{
isRun = true;
}
else
{
emit sigToStop();
}
/*改变视频输出区域尺寸*/
ui.widget->setMinimumSize(ui.widget->height() * ((float)16 / 9), ui.widget->height());
ui.widget->setMaximumSize(ui.widget->height() * ((float)16 / 9), ui.widget->height());
}
void realtimeVideoClass::stopWork()
{
emit sigToStop();
}
以上就是实现拉流的方式。
注:如果本文章对您有所帮助,请点赞收藏支持一下,谢谢。^_^
版权声明:本文为博主原创文章,转载请附上博文链接。