Qt+Vs实现使用ffmpeg拉流显示推流视频(附源代码)

下面是通过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();
}

以上就是实现拉流的方式。

注:如果本文章对您有所帮助,请点赞收藏支持一下,谢谢。^_^

版权声明:本文为博主原创文章,转载请附上博文链接。

Qt + Visual Studio (VS)环境中,使用FFmpeg进行实时并在QLabel上显示视频,你可以按照以下步骤操作: 1. **环境配置**: - 首先确保已经安装了FFmpeg库,并添加到系统路径中。 - 安装Qt和Visual Studio插件如Qt VS Tools,以便支持Qt项目开发。 2. **创建Qt项目**: - 创建一个新的Qt Widgets Application项目,在VS中设置好QT5的支持。 3. **包含必要的头文件**: 在`.cpp`源文件中,包含FFmpeg的`avcodec.h`, `avformat.h`, 和`swscale.h`等头文件。 4. **初始化FFmpeg**: 使用FFmpeg的API函数,如`av_register_all()`初始化FFmpeg组件。 5. **打开输入**: 指定TCo地址,使用`av_open_input_file`打开输入文件,获取AVFormatContext结构。 6. **解析信息**: 调用`avformat_find_stream_info`来解析输入的信息,找到视频。 7. **选择视频解码器和分配内存**: 根据视频找到合适的解码器(`avcodec_find_decoder_by_name`),并为解码器数据分配内存。 8. **解码视频帧**: 读取视频帧,调用`avcodec_decode_video_frame`解码,然后处理解码后的frame。 9. **显示在QLabel**: 使用`QImage`或`QSurfaceFormat`将解码后的frame转换成可以显示的格式,通过`setPixmap`放到`QLabel`上。 10. **循环播放**: 设置一个while循环,不断从输入读取和解码帧,直到输入结束。 ```cpp // 示例代码片段 QThread *decodeThread = new QThread; AVFrame *frame = avcodec_alloc_frame(); QLabel *videoLabel = new QLabel; void decodeFrames(AVFormatContext *inputCtx) { while (!inputCtx->eof()) { if (av_read_frame(inputCtx, frame) >= 0) { // 解码、转换图像并显示 QImage image(QSize(frame->width, frame->height), QImage::Format_RGB32); // ... (在这里进行解码和图像转换) videoLabel->setPixmap(QPixmap::fromImage(image)); } else { break; // 输入结束 } } } void runDecode() { // ... (这里是在decodeThread上下文里解码和显示) } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值